Fix memory leak introduced by commit 8fdcd0435d142d13940e87043350ebb18b36d8b2
[siplcs.git] / src / sipe.c
blob780d701ed1cd2be7511bb9555570109b2790961a
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 "sipmsg.h"
79 #include "sipe-sign.h"
80 #include "dnssrv.h"
81 #include "request.h"
83 /* Keep in sync with sipe_transport_type! */
84 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
85 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
87 /* Status identifiers (see also: sipe_status_types()) */
88 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
89 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
90 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
91 /* PURPLE_STATUS_UNAVAILABLE: */
92 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
93 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
94 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
95 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
96 /* PURPLE_STATUS_AWAY: */
97 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
98 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
99 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
100 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
101 /* ??? PURPLE_STATUS_MOBILE */
102 /* ??? PURPLE_STATUS_TUNE */
104 /* Action name templates */
105 #define ACTION_NAME_PRESENCE "<presence><%s>"
107 static char *gentag()
109 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
112 static gchar *get_epid(struct sipe_account_data *sip)
114 if (!sip->epid) {
115 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
117 return g_strdup(sip->epid);
120 static char *genbranch()
122 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
123 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
124 rand() & 0xFFFF, rand() & 0xFFFF);
127 static char *gencallid()
129 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
130 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
131 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
132 rand() & 0xFFFF, rand() & 0xFFFF);
135 static gchar *find_tag(const gchar *hdr)
137 gchar * tag = sipmsg_find_part_of_header (hdr, "tag=", ";", NULL);
138 if (!tag) {
139 // In case it's at the end and there's no trailing ;
140 tag = sipmsg_find_part_of_header (hdr, "tag=", NULL, NULL);
142 return tag;
146 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
148 return "sipe";
151 static void sipe_plugin_destroy(PurplePlugin *plugin);
153 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
155 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
156 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
157 gpointer data);
159 static void sipe_close(PurpleConnection *gc);
161 static void send_presence_status(struct sipe_account_data *sip);
163 static void sendout_pkt(PurpleConnection *gc, const char *buf);
165 static void sipe_keep_alive(PurpleConnection *gc)
167 struct sipe_account_data *sip = gc->proto_data;
168 if (sip->transport == SIPE_TRANSPORT_UDP) {
169 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
170 gchar buf[2] = {0, 0};
171 purple_debug_info("sipe", "sending keep alive\n");
172 sendto(sip->fd, buf, 1, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in));
173 } else {
174 time_t now = time(NULL);
175 if ((sip->keepalive_timeout > 0) &&
176 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
177 #if PURPLE_VERSION_CHECK(2,4,0)
178 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
179 #endif
181 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
182 sendout_pkt(gc, "\r\n\r\n");
183 sip->last_keepalive = now;
188 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
190 struct sip_connection *ret = NULL;
191 GSList *entry = sip->openconns;
192 while (entry) {
193 ret = entry->data;
194 if (ret->fd == fd) return ret;
195 entry = entry->next;
197 return NULL;
200 static void sipe_auth_free(struct sip_auth *auth)
202 g_free(auth->opaque);
203 auth->opaque = NULL;
204 g_free(auth->realm);
205 auth->realm = NULL;
206 g_free(auth->target);
207 auth->target = NULL;
208 auth->type = AUTH_TYPE_UNSET;
209 auth->retries = 0;
210 auth->expires = 0;
211 g_free(auth->gssapi_data);
212 auth->gssapi_data = NULL;
213 sip_sec_destroy_context(auth->gssapi_context);
214 auth->gssapi_context = NULL;
217 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
219 struct sip_connection *ret = g_new0(struct sip_connection, 1);
220 ret->fd = fd;
221 sip->openconns = g_slist_append(sip->openconns, ret);
222 return ret;
225 static void connection_remove(struct sipe_account_data *sip, int fd)
227 struct sip_connection *conn = connection_find(sip, fd);
228 if (conn) {
229 sip->openconns = g_slist_remove(sip->openconns, conn);
230 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
231 g_free(conn->inbuf);
232 g_free(conn);
236 static void connection_free_all(struct sipe_account_data *sip)
238 struct sip_connection *ret = NULL;
239 GSList *entry = sip->openconns;
240 while (entry) {
241 ret = entry->data;
242 connection_remove(sip, ret->fd);
243 entry = sip->openconns;
247 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
249 gchar noncecount[9];
250 const char *authuser = sip->authuser;
251 gchar *response;
252 gchar *ret;
254 if (!authuser || strlen(authuser) < 1) {
255 authuser = sip->username;
258 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
259 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
261 // If we have a signature for the message, include that
262 if (msg->signature) {
263 return g_strdup_printf("%s qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth_protocol, auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
266 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
267 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
268 gchar *gssapi_data;
269 gchar *opaque;
271 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
272 &(auth->expires),
273 auth->type,
274 purple_account_get_bool(sip->account, "sso", TRUE),
275 sip->authdomain ? sip->authdomain : "",
276 authuser,
277 sip->password,
278 auth->target,
279 auth->gssapi_data);
280 if (!gssapi_data || !auth->gssapi_context)
281 return NULL;
283 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
284 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
285 g_free(opaque);
286 g_free(gssapi_data);
287 return ret;
290 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
292 } else { /* Digest */
294 /* Calculate new session key */
295 if (!auth->opaque) {
296 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
297 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
298 authuser, auth->realm, sip->password,
299 auth->gssapi_data, NULL);
302 sprintf(noncecount, "%08d", auth->nc++);
303 response = purple_cipher_http_digest_calculate_response("md5",
304 msg->method, msg->target, NULL, NULL,
305 auth->gssapi_data, noncecount, NULL,
306 auth->opaque);
307 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
309 ret = g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser, auth->realm, auth->gssapi_data, msg->target, noncecount, response);
310 g_free(response);
311 return ret;
315 static char *parse_attribute(const char *attrname, const char *source)
317 const char *tmp, *tmp2;
318 char *retval = NULL;
319 int len = strlen(attrname);
321 if (!strncmp(source, attrname, len)) {
322 tmp = source + len;
323 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
324 if (tmp2)
325 retval = g_strndup(tmp, tmp2 - tmp);
326 else
327 retval = g_strdup(tmp);
330 return retval;
333 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
335 int i;
336 gchar **parts;
338 if (!hdr) {
339 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
340 return;
343 if (!g_strncasecmp(hdr, "NTLM", 4)) {
344 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
345 auth->type = AUTH_TYPE_NTLM;
346 hdr += 5;
347 auth->nc = 1;
348 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
349 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
350 auth->type = AUTH_TYPE_KERBEROS;
351 hdr += 9;
352 auth->nc = 3;
353 } else {
354 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
355 auth->type = AUTH_TYPE_DIGEST;
356 hdr += 7;
359 parts = g_strsplit(hdr, "\", ", 0);
360 for (i = 0; parts[i]; i++) {
361 char *tmp;
363 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
365 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
366 g_free(auth->gssapi_data);
367 auth->gssapi_data = tmp;
369 if (auth->type == AUTH_TYPE_NTLM) {
370 /* NTLM module extracts nonce from gssapi-data */
371 auth->nc = 3;
374 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
375 /* Only used with AUTH_TYPE_DIGEST */
376 g_free(auth->gssapi_data);
377 auth->gssapi_data = tmp;
378 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
379 g_free(auth->opaque);
380 auth->opaque = tmp;
381 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
382 g_free(auth->realm);
383 auth->realm = tmp;
385 if (auth->type == AUTH_TYPE_DIGEST) {
386 /* Throw away old session key */
387 g_free(auth->opaque);
388 auth->opaque = NULL;
389 auth->nc = 1;
392 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
393 g_free(auth->target);
394 auth->target = tmp;
397 g_strfreev(parts);
399 return;
402 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
404 PurpleConnection *gc = data;
405 struct sipe_account_data *sip = gc->proto_data;
406 gsize max_write;
407 gssize written;
409 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
411 if (max_write == 0) {
412 if (sip->tx_handler != 0){
413 purple_input_remove(sip->tx_handler);
414 sip->tx_handler = 0;
416 return;
419 written = write(sip->fd, sip->txbuf->outptr, max_write);
421 if (written < 0 && errno == EAGAIN)
422 written = 0;
423 else if (written <= 0) {
424 /*TODO: do we really want to disconnect on a failure to write?*/
425 purple_connection_error(gc, _("Could not write"));
426 return;
429 purple_circ_buffer_mark_read(sip->txbuf, written);
432 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
434 PurpleConnection *gc = data;
435 struct sipe_account_data *sip = gc->proto_data;
436 gsize max_write;
437 gssize written;
439 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
441 if (max_write == 0) {
442 if (sip->tx_handler != 0) {
443 purple_input_remove(sip->tx_handler);
444 sip->tx_handler = 0;
445 return;
449 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
451 if (written < 0 && errno == EAGAIN)
452 written = 0;
453 else if (written <= 0) {
454 /*TODO: do we really want to disconnect on a failure to write?*/
455 purple_connection_error(gc, _("Could not write"));
456 return;
459 purple_circ_buffer_mark_read(sip->txbuf, written);
462 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
464 static void send_later_cb(gpointer data, gint source, const gchar *error)
466 PurpleConnection *gc = data;
467 struct sipe_account_data *sip;
468 struct sip_connection *conn;
470 if (!PURPLE_CONNECTION_IS_VALID(gc))
472 if (source >= 0)
473 close(source);
474 return;
477 if (source < 0) {
478 purple_connection_error(gc, _("Could not connect"));
479 return;
482 sip = gc->proto_data;
483 sip->fd = source;
484 sip->connecting = FALSE;
485 sip->last_keepalive = time(NULL);
487 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
489 /* If there is more to write now, we need to register a handler */
490 if (sip->txbuf->bufused > 0)
491 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
493 conn = connection_create(sip, source);
494 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
497 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
499 struct sipe_account_data *sip;
500 struct sip_connection *conn;
502 if (!PURPLE_CONNECTION_IS_VALID(gc))
504 if (gsc) purple_ssl_close(gsc);
505 return NULL;
508 sip = gc->proto_data;
509 sip->fd = gsc->fd;
510 sip->gsc = gsc;
511 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
512 sip->connecting = FALSE;
513 sip->last_keepalive = time(NULL);
515 conn = connection_create(sip, gsc->fd);
517 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
519 return sip;
522 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
524 PurpleConnection *gc = data;
525 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
526 if (sip == NULL) return;
528 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
530 /* If there is more to write now */
531 if (sip->txbuf->bufused > 0) {
532 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
537 static void sendlater(PurpleConnection *gc, const char *buf)
539 struct sipe_account_data *sip = gc->proto_data;
541 if (!sip->connecting) {
542 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
543 if (sip->transport == SIPE_TRANSPORT_TLS){
544 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
545 } else {
546 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
547 purple_connection_error(gc, _("Couldn't create socket"));
550 sip->connecting = TRUE;
553 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
554 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
556 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
559 static void sendout_pkt(PurpleConnection *gc, const char *buf)
561 struct sipe_account_data *sip = gc->proto_data;
562 time_t currtime = time(NULL);
563 int writelen = strlen(buf);
565 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
566 if (sip->transport == SIPE_TRANSPORT_UDP) {
567 if (sendto(sip->fd, buf, writelen, 0, (struct sockaddr*)&sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
568 purple_debug_info("sipe", "could not send packet\n");
570 } else {
571 int ret;
572 if (sip->fd < 0) {
573 sendlater(gc, buf);
574 return;
577 if (sip->tx_handler) {
578 ret = -1;
579 errno = EAGAIN;
580 } else{
581 if (sip->gsc){
582 ret = purple_ssl_write(sip->gsc, buf, writelen);
583 }else{
584 ret = write(sip->fd, buf, writelen);
588 if (ret < 0 && errno == EAGAIN)
589 ret = 0;
590 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
591 sendlater(gc, buf);
592 return;
595 if (ret < writelen) {
596 if (!sip->tx_handler){
597 if (sip->gsc){
598 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
600 else{
601 sip->tx_handler = purple_input_add(sip->fd,
602 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
603 gc);
607 /* XXX: is it OK to do this? You might get part of a request sent
608 with part of another. */
609 if (sip->txbuf->bufused > 0)
610 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
612 purple_circ_buffer_append(sip->txbuf, buf + ret,
613 writelen - ret);
618 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
620 sendout_pkt(gc, buf);
621 return len;
624 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
626 GSList *tmp = msg->headers;
627 gchar *name;
628 gchar *value;
629 GString *outstr = g_string_new("");
630 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
631 while (tmp) {
632 name = ((struct siphdrelement*) (tmp->data))->name;
633 value = ((struct siphdrelement*) (tmp->data))->value;
634 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
635 tmp = g_slist_next(tmp);
637 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
638 sendout_pkt(sip->gc, outstr->str);
639 g_string_free(outstr, TRUE);
642 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
644 gchar * buf;
645 if (sip->registrar.gssapi_context) {
646 struct sipmsg_breakdown msgbd;
647 gchar *signature_input_str;
648 msgbd.msg = msg;
649 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
650 msgbd.rand = g_strdup_printf("%08x", g_random_int());
651 sip->registrar.ntlm_num++;
652 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
653 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
654 if (signature_input_str != NULL) {
655 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
656 msg->signature = signature_hex;
657 msg->rand = g_strdup(msgbd.rand);
658 msg->num = g_strdup(msgbd.num);
659 g_free(signature_input_str);
661 sipmsg_breakdown_free(&msgbd);
664 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
665 buf = auth_header(sip, &sip->registrar, msg);
666 #ifdef USE_KERBEROS
667 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
668 #endif
669 sipmsg_add_header(msg, "Authorization", buf);
670 #ifdef USE_KERBEROS
671 } else {
672 sipmsg_add_header_pos(msg, "Authorization", buf, 5); // What's the point in 5?
674 #endif
675 g_free(buf);
676 } 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") || !strcmp(method, "REFER")) {
677 sip->registrar.nc = 3;
678 #ifdef USE_KERBEROS
679 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
680 #endif
681 sip->registrar.type = AUTH_TYPE_NTLM;
682 #ifdef USE_KERBEROS
683 } else {
684 sip->registrar.type = AUTH_TYPE_KERBEROS;
686 #endif
689 buf = auth_header(sip, &sip->registrar, msg);
690 sipmsg_add_header_pos(msg, "Proxy-Authorization", buf, 5);
691 g_free(buf);
692 } else {
693 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
697 static char *get_contact(struct sipe_account_data *sip)
699 return g_strdup(sip->contact);
703 * unused. Needed?
704 static char *get_contact_service(struct sipe_account_data *sip)
706 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()));
707 //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);
711 static void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
712 const char *text, const char *body)
714 gchar *name;
715 gchar *value;
716 GString *outstr = g_string_new("");
717 struct sipe_account_data *sip = gc->proto_data;
718 gchar *contact;
719 GSList *tmp;
721 sipmsg_remove_header(msg, "ms-user-data");
723 contact = get_contact(sip);
724 sipmsg_remove_header(msg, "Contact");
725 sipmsg_add_header(msg, "Contact", contact);
726 g_free(contact);
728 /* When sending the acknowlegements and errors, the content length from the original
729 message is still here, but there is no body; we need to make sure we're sending the
730 correct content length */
731 sipmsg_remove_header(msg, "Content-Length");
732 if (body) {
733 gchar len[12];
734 sprintf(len, "%" G_GSIZE_FORMAT , strlen(body));
735 sipmsg_add_header(msg, "Content-Length", len);
736 } else {
737 sipmsg_remove_header(msg, "Content-Type");
738 sipmsg_add_header(msg, "Content-Length", "0");
741 msg->response = code;
743 sipmsg_remove_header(msg, "Authentication-Info");
744 sign_outgoing_message(msg, sip, msg->method);
746 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
747 tmp = msg->headers;
748 while (tmp) {
749 name = ((struct siphdrelement*) (tmp->data))->name;
750 value = ((struct siphdrelement*) (tmp->data))->value;
752 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
753 tmp = g_slist_next(tmp);
755 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
756 sendout_pkt(gc, outstr->str);
757 g_string_free(outstr, TRUE);
760 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
762 if (trans->msg) sipmsg_free(trans->msg);
763 sip->transactions = g_slist_remove(sip->transactions, trans);
764 g_free(trans);
767 static struct transaction *
768 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
770 struct transaction *trans = g_new0(struct transaction, 1);
771 trans->time = time(NULL);
772 trans->msg = (struct sipmsg *)msg;
773 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
774 trans->callback = callback;
775 sip->transactions = g_slist_append(sip->transactions, trans);
776 return trans;
779 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
781 struct transaction *trans;
782 GSList *transactions = sip->transactions;
783 gchar *cseq = sipmsg_find_header(msg, "CSeq");
785 while (transactions) {
786 trans = transactions->data;
787 if (!strcmp(trans->cseq, cseq)) {
788 return trans;
790 transactions = transactions->next;
793 return NULL;
796 static struct transaction *
797 send_sip_request(PurpleConnection *gc, const gchar *method,
798 const gchar *url, const gchar *to, const gchar *addheaders,
799 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
801 struct sipe_account_data *sip = gc->proto_data;
802 const char *addh = "";
803 char *buf;
804 struct sipmsg *msg;
805 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
806 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
807 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
808 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
809 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
810 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
811 gchar *route = strdup("");
812 gchar *epid = get_epid(sip); // TODO generate one per account/login
813 int cseq = dialog ? ++dialog->cseq :
814 /* This breaks OCS2007: own presence, contact search, ?
815 1 .* as Call-Id is new in this case */
816 ++sip->cseq;
817 struct transaction *trans;
819 if (dialog && dialog->routes)
821 GSList *iter = dialog->routes;
823 while(iter)
825 char *tmp = route;
826 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
827 g_free(tmp);
828 iter = g_slist_next(iter);
832 if (!ourtag && !dialog) {
833 ourtag = gentag();
836 if (!strcmp(method, "REGISTER")) {
837 if (sip->regcallid) {
838 g_free(callid);
839 callid = g_strdup(sip->regcallid);
840 } else {
841 sip->regcallid = g_strdup(callid);
845 if (addheaders) addh = addheaders;
847 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
848 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
849 "From: <sip:%s>%s%s;epid=%s\r\n"
850 "To: <%s>%s%s%s%s\r\n"
851 "Max-Forwards: 70\r\n"
852 "CSeq: %d %s\r\n"
853 "User-Agent: %s\r\n"
854 "Call-ID: %s\r\n"
855 "%s%s"
856 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
857 method,
858 dialog && dialog->request ? dialog->request : url,
859 TRANSPORT_DESCRIPTOR,
860 purple_network_get_my_ip(-1),
861 sip->listenport,
862 branch ? ";branch=" : "",
863 branch ? branch : "",
864 sip->username,
865 ourtag ? ";tag=" : "",
866 ourtag ? ourtag : "",
867 epid,
869 theirtag ? ";tag=" : "",
870 theirtag ? theirtag : "",
871 theirepid ? ";epid=" : "",
872 theirepid ? theirepid : "",
873 cseq,
874 method,
875 useragent,
876 callid,
877 route,
878 addh,
879 body ? strlen(body) : 0,
880 body ? body : "");
883 //printf ("parsing msg buf:\n%s\n\n", buf);
884 msg = sipmsg_parse_msg(buf);
886 g_free(buf);
887 g_free(ourtag);
888 g_free(theirtag);
889 g_free(theirepid);
890 g_free(branch);
891 g_free(callid);
892 g_free(route);
893 g_free(epid);
895 sign_outgoing_message (msg, sip, method);
897 buf = sipmsg_to_string (msg);
899 /* add to ongoing transactions */
900 trans = transactions_add_buf(sip, msg, tc);
901 sendout_pkt(gc, buf);
902 g_free(buf);
904 return trans;
907 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
909 gchar *from = g_strdup_printf("sip:%s", sip->username);
910 gchar *contact = get_contact(sip);
911 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
912 "Content-Type: application/SOAP+xml\r\n",contact);
914 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
915 tr->payload = payload;
917 g_free(from);
918 g_free(contact);
919 g_free(hdr);
922 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
924 send_soap_request_with_cb(sip, body, NULL, NULL);
927 static char *get_contact_register(struct sipe_account_data *sip)
929 char *epid = get_epid(sip);
930 char *uuid = generateUUIDfromEPID(epid);
931 char *buf = g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, uuid);
932 g_free(uuid);
933 g_free(epid);
934 return(buf);
937 static void do_register_exp(struct sipe_account_data *sip, int expire)
939 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
940 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
941 char *to = g_strdup_printf("sip:%s", sip->username);
942 char *contact = get_contact_register(sip);
943 char *hdr = g_strdup_printf("Contact: %s\r\n"
944 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
945 "Event: registration\r\n"
946 "Allow-Events: presence\r\n"
947 "ms-keep-alive: UAC;hop-hop=yes\r\n"
948 "%s", contact, expires);
949 g_free(contact);
950 g_free(expires);
952 sip->registerstatus = 1;
954 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
955 process_register_response);
957 g_free(hdr);
958 g_free(uri);
959 g_free(to);
962 static void do_register_cb(struct sipe_account_data *sip, void *unused)
964 do_register_exp(sip, -1);
965 sip->reregister_set = FALSE;
968 static void do_register(struct sipe_account_data *sip)
970 do_register_exp(sip, -1);
974 * Returns URI from provided To or From header.
976 * Needs to g_free() after use.
978 * @return URI with sip: prefix
980 static gchar *parse_from(const gchar *hdr)
982 gchar *from;
983 const gchar *tmp, *tmp2 = hdr;
985 if (!hdr) return NULL;
986 purple_debug_info("sipe", "parsing address out of %s\n", hdr);
987 tmp = strchr(hdr, '<');
989 /* i hate the different SIP UA behaviours... */
990 if (tmp) { /* sip address in <...> */
991 tmp2 = tmp + 1;
992 tmp = strchr(tmp2, '>');
993 if (tmp) {
994 from = g_strndup(tmp2, tmp - tmp2);
995 } else {
996 purple_debug_info("sipe", "found < without > in From\n");
997 return NULL;
999 } else {
1000 tmp = strchr(tmp2, ';');
1001 if (tmp) {
1002 from = g_strndup(tmp2, tmp - tmp2);
1003 } else {
1004 from = g_strdup(tmp2);
1007 purple_debug_info("sipe", "got %s\n", from);
1008 return from;
1011 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
1013 va_list args;
1014 xmlnode * node = NULL;
1015 const gchar * name;
1017 va_start(args, parent);
1018 while ((name = va_arg(args, const char *)) != NULL) {
1019 node = xmlnode_get_child(parent, name);
1020 if (node == NULL) return NULL;
1021 parent = node;
1023 va_end(args);
1025 return node;
1029 static void
1030 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
1032 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
1033 send_soap_request(sip, body);
1034 g_free(body);
1037 static void
1038 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
1040 if (allow) {
1041 purple_debug_info("sipe", "Authorizing contact %s\n", who);
1042 } else {
1043 purple_debug_info("sipe", "Blocking contact %s\n", who);
1046 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
1049 static
1050 void sipe_auth_user_cb(void * data)
1052 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1053 if (!job) return;
1055 sipe_contact_allow_deny (job->sip, job->who, TRUE);
1056 g_free(job);
1059 static
1060 void sipe_deny_user_cb(void * data)
1062 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1063 if (!job) return;
1065 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1066 g_free(job);
1069 static void
1070 sipe_add_permit(PurpleConnection *gc, const char *name)
1072 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1073 sipe_contact_allow_deny(sip, name, TRUE);
1076 static void
1077 sipe_add_deny(PurpleConnection *gc, const char *name)
1079 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1080 sipe_contact_allow_deny(sip, name, FALSE);
1083 /*static void
1084 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1086 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1087 sipe_contact_set_acl(sip, name, "");
1090 static void
1091 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1093 xmlnode *watchers;
1094 xmlnode *watcher;
1095 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1096 if (msg->response != 0 && msg->response != 200) return;
1098 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1100 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1101 if (!watchers) return;
1103 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1104 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1105 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1106 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1108 // TODO pull out optional displayName to pass as alias
1109 if (remote_user) {
1110 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1111 job->who = remote_user;
1112 job->sip = sip;
1113 purple_account_request_authorization(
1114 sip->account,
1115 remote_user,
1116 NULL, // id
1117 alias,
1118 NULL, // message
1119 on_list,
1120 sipe_auth_user_cb,
1121 sipe_deny_user_cb,
1122 (void *) job);
1127 xmlnode_free(watchers);
1128 return;
1131 static void
1132 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1134 PurpleGroup * purple_group = purple_find_group(group->name);
1135 if (!purple_group) {
1136 purple_group = purple_group_new(group->name);
1137 purple_blist_add_group(purple_group, NULL);
1140 if (purple_group) {
1141 group->purple_group = purple_group;
1142 sip->groups = g_slist_append(sip->groups, group);
1143 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1144 } else {
1145 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1149 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1151 struct sipe_group *group;
1152 GSList *entry;
1153 if (sip == NULL) {
1154 return NULL;
1157 entry = sip->groups;
1158 while (entry) {
1159 group = entry->data;
1160 if (group->id == id) {
1161 return group;
1163 entry = entry->next;
1165 return NULL;
1168 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, gchar * name)
1170 struct sipe_group *group;
1171 GSList *entry;
1172 if (sip == NULL) {
1173 return NULL;
1176 entry = sip->groups;
1177 while (entry) {
1178 group = entry->data;
1179 if (!strcmp(group->name, name)) {
1180 return group;
1182 entry = entry->next;
1184 return NULL;
1187 static void
1188 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1190 gchar *body;
1191 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1192 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1193 send_soap_request(sip, body);
1194 g_free(body);
1195 g_free(group->name);
1196 group->name = g_strdup(name);
1200 * Only appends if no such value already stored.
1201 * Like Set in Java.
1203 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1204 GSList * res = list;
1205 if (!g_slist_find_custom(list, data, func)) {
1206 res = g_slist_insert_sorted(list, data, func);
1208 return res;
1211 static int
1212 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1213 return group1->id - group2->id;
1217 * Returns string like "2 4 7 8" - group ids buddy belong to.
1219 static gchar *
1220 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1221 int i = 0;
1222 gchar *res;
1223 //creating array from GList, converting int to gchar*
1224 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1225 GSList *entry = buddy->groups;
1226 while (entry) {
1227 struct sipe_group * group = entry->data;
1228 ids_arr[i] = g_strdup_printf("%d", group->id);
1229 entry = entry->next;
1230 i++;
1232 ids_arr[i] = NULL;
1233 res = g_strjoinv(" ", ids_arr);
1234 g_strfreev(ids_arr);
1235 return res;
1239 * Sends buddy update to server
1241 static void
1242 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1244 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1245 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1247 if (buddy && purple_buddy) {
1248 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1249 gchar *body;
1250 gchar *groups = sipe_get_buddy_groups_string(buddy);
1251 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1253 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1254 alias, groups, "true", buddy->name, sip->contacts_delta++
1256 send_soap_request(sip, body);
1257 g_free(groups);
1258 g_free(body);
1262 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1264 if (msg->response == 200) {
1265 struct sipe_group *group;
1266 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1267 xmlnode *xml;
1268 xmlnode *node;
1269 char *group_id;
1270 struct sipe_buddy *buddy;
1272 xml = xmlnode_from_str(msg->body, msg->bodylen);
1273 if (!xml) {
1274 g_free(ctx);
1275 return FALSE;
1278 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1279 if (!node) {
1280 g_free(ctx);
1281 xmlnode_free(xml);
1282 return FALSE;
1285 group_id = xmlnode_get_data(node);
1286 if (!group_id) {
1287 g_free(ctx);
1288 xmlnode_free(xml);
1289 return FALSE;
1292 group = g_new0(struct sipe_group, 1);
1293 group->id = (int)g_ascii_strtod(group_id, NULL);
1294 g_free(group_id);
1295 group->name = ctx->group_name;
1297 sipe_group_add(sip, group);
1299 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1300 if (buddy) {
1301 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1304 sipe_group_set_user(sip, ctx->user_name);
1306 g_free(ctx);
1307 xmlnode_free(xml);
1308 return TRUE;
1310 return FALSE;
1313 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1315 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1316 gchar *body;
1317 ctx->group_name = g_strdup(name);
1318 ctx->user_name = g_strdup(who);
1320 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1321 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1322 g_free(body);
1326 * Data structure for scheduled actions
1328 typedef void (*Action) (struct sipe_account_data *, void *);
1330 struct scheduled_action {
1331 /**
1332 * Name of action.
1333 * Format is <Event>[<Data>...]
1334 * Example: <presence><sip:user@domain.com> or <registration>
1336 gchar *name;
1337 guint timeout_handler;
1338 gboolean repetitive;
1339 Action action;
1340 GDestroyNotify destroy;
1341 struct sipe_account_data *sip;
1342 void *payload;
1346 * A timer callback
1347 * Should return FALSE if repetitive action is not needed
1349 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1351 gboolean ret;
1352 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1353 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1354 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1355 (sched_action->action)(sched_action->sip, sched_action->payload);
1356 ret = sched_action->repetitive;
1357 (*sched_action->destroy)(sched_action->payload);
1358 g_free(sched_action->name);
1359 g_free(sched_action);
1360 return ret;
1364 * Kills action timer effectively cancelling
1365 * scheduled action
1367 * @param name of action
1369 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1371 GSList *entry;
1373 if (!sip->timeouts || !name) return;
1375 entry = sip->timeouts;
1376 while (entry) {
1377 struct scheduled_action *sched_action = entry->data;
1378 if(!strcmp(sched_action->name, name)) {
1379 GSList *to_delete = entry;
1380 entry = entry->next;
1381 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1382 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1383 purple_timeout_remove(sched_action->timeout_handler);
1384 (*sched_action->destroy)(sched_action->payload);
1385 g_free(sched_action->name);
1386 g_free(sched_action);
1387 } else {
1388 entry = entry->next;
1393 static void
1394 sipe_schedule_action0(const gchar *name,
1395 int timeout,
1396 gboolean isSeconds,
1397 Action action,
1398 GDestroyNotify destroy,
1399 struct sipe_account_data *sip,
1400 void *payload)
1402 struct scheduled_action *sched_action;
1404 /* Make sure each action only exists once */
1405 sipe_cancel_scheduled_action(sip, name);
1407 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1408 sched_action = g_new0(struct scheduled_action, 1);
1409 sched_action->repetitive = FALSE;
1410 sched_action->name = g_strdup(name);
1411 sched_action->action = action;
1412 sched_action->destroy = destroy ? destroy : g_free;
1413 sched_action->sip = sip;
1414 sched_action->payload = payload;
1415 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1416 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1417 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1418 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1422 * Do schedule action for execution in the future.
1423 * Non repetitive execution.
1425 * @param name of action (will be copied)
1426 * @param timeout in seconds
1427 * @action callback function
1428 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1430 static void
1431 sipe_schedule_action(const gchar *name,
1432 int timeout,
1433 Action action,
1434 GDestroyNotify destroy,
1435 struct sipe_account_data *sip,
1436 void *payload)
1438 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1442 * Same as sipe_schedule_action() but timeout is in milliseconds.
1444 static void
1445 sipe_schedule_action_msec(const gchar *name,
1446 int timeout,
1447 Action action,
1448 GDestroyNotify destroy,
1449 struct sipe_account_data *sip,
1450 void *payload)
1452 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1456 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1458 static gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1460 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1462 process_incoming_notify(sip, msg, FALSE, FALSE);
1464 return TRUE;
1467 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1469 gchar *tmp = *resources_uri;
1470 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1471 g_free(tmp);
1474 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1476 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1477 if (sbuddy && !sbuddy->resubscribed) { // Only not resubscribed contacts; the first time everybody are included
1478 gchar *tmp = *resources_uri;
1479 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1480 g_free(tmp);
1485 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1486 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1487 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1488 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1489 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1492 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1494 gchar *contact = get_contact(sip);
1495 gchar *request;
1496 gchar *content;
1497 gchar *require = "";
1498 gchar *accept = "";
1499 gchar *autoextend = "";
1500 gchar *content_type;
1502 if (sip->msrtc_event_categories) {
1503 require = ", categoryList";
1504 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1505 content_type = "application/msrtc-adrl-categorylist+xml";
1506 content = g_strdup_printf(
1507 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1508 "<action name=\"subscribe\" id=\"63792024\">\n"
1509 "<adhocList>\n%s</adhocList>\n"
1510 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1511 "<category name=\"note\"/>\n"
1512 "<category name=\"state\"/>\n"
1513 "</categoryList>\n"
1514 "</action>\n"
1515 "</batchSub>", sip->username, resources_uri);
1516 } else {
1517 autoextend = "Supported: com.microsoft.autoextend\r\n";
1518 content_type = "application/adrl+xml";
1519 content = g_strdup_printf(
1520 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1521 "<create xmlns=\"\">\n%s</create>\n"
1522 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1524 g_free(resources_uri);
1526 request = g_strdup_printf(
1527 "Require: adhoclist%s\r\n"
1528 "Supported: eventlist\r\n"
1529 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1530 "Supported: ms-piggyback-first-notify\r\n"
1531 "%sSupported: ms-benotify\r\n"
1532 "Proxy-Require: ms-benotify\r\n"
1533 "Event: presence\r\n"
1534 "Content-Type: %s\r\n"
1535 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1536 g_free(contact);
1538 /* subscribe to buddy presence */
1539 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1540 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1542 g_free(content);
1543 g_free(to);
1544 g_free(request);
1547 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip, void *unused)
1549 gchar *to = g_strdup_printf("sip:%s", sip->username);
1550 gchar *resources_uri = g_strdup("");
1551 if (sip->msrtc_event_categories) {
1552 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1553 } else {
1554 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1556 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1559 struct presence_batched_routed {
1560 gchar *host;
1561 GSList *buddies;
1564 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1566 struct presence_batched_routed *data = payload;
1567 GSList *buddies = data->buddies;
1568 while (buddies) {
1569 g_free(buddies->data);
1570 buddies = buddies->next;
1572 g_slist_free(data->buddies);
1573 g_free(data->host);
1574 g_free(payload);
1577 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1579 struct presence_batched_routed *data = payload;
1580 GSList *buddies = data->buddies;
1581 gchar *resources_uri = g_strdup("");
1582 while (buddies) {
1583 gchar *tmp = resources_uri;
1584 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1585 g_free(tmp);
1586 buddies = buddies->next;
1588 sipe_subscribe_presence_batched_to(sip, resources_uri,
1589 g_strdup(data->host));
1593 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1594 * The user sends a single SUBSCRIBE request to the subscribed contact.
1595 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1599 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1601 gchar *to = strstr((char *)buddy_name, "sip:") ? g_strdup((char *)buddy_name) : g_strdup_printf("sip:%s", (char *)buddy_name);
1602 gchar *tmp = get_contact(sip);
1603 gchar *request;
1604 gchar *content;
1605 gchar *autoextend = "";
1607 if (!sip->msrtc_event_categories)
1608 autoextend = "Supported: com.microsoft.autoextend\r\n";
1610 request = g_strdup_printf(
1611 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1612 "Supported: ms-piggyback-first-notify\r\n"
1613 "%sSupported: ms-benotify\r\n"
1614 "Proxy-Require: ms-benotify\r\n"
1615 "Event: presence\r\n"
1616 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1617 "Contact: %s\r\n", autoextend,tmp);
1619 content = g_strdup_printf(
1620 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1621 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1622 "<resource uri=\"%s\"/>\n"
1623 "</adhocList>\n"
1624 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1625 "<category name=\"note\"/>\n"
1626 "<category name=\"state\"/>\n"
1627 "</categoryList>\n"
1628 "</action>\n"
1629 "</batchSub>", sip->username, to
1632 g_free(tmp);
1634 /* subscribe to buddy presence */
1635 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1637 g_free(content);
1638 g_free(to);
1639 g_free(request);
1642 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1644 if (!purple_status_is_active(status))
1645 return;
1647 if (account->gc) {
1648 struct sipe_account_data *sip = account->gc->proto_data;
1650 if (sip) {
1651 g_free(sip->status);
1652 sip->status = g_strdup(purple_status_get_id(status));
1653 send_presence_status(sip);
1658 static void
1659 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1661 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1662 sipe_group_set_user(sip, name);
1665 static void
1666 sipe_group_buddy(PurpleConnection *gc,
1667 const char *who,
1668 const char *old_group_name,
1669 const char *new_group_name)
1671 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1672 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1673 struct sipe_group * old_group = NULL;
1674 struct sipe_group * new_group;
1676 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1677 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1679 if(!buddy) { // buddy not in roaming list
1680 return;
1683 if (old_group_name) {
1684 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1686 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1688 if (old_group) {
1689 buddy->groups = g_slist_remove(buddy->groups, old_group);
1690 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1693 if (!new_group) {
1694 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1695 } else {
1696 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1697 sipe_group_set_user(sip, who);
1701 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1703 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1704 struct sipe_buddy *b;
1706 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1708 // Prepend sip: if needed
1709 if (strncmp("sip:", buddy->name, 4)) {
1710 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1711 purple_blist_rename_buddy(buddy, buf);
1712 g_free(buf);
1715 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1716 b = g_new0(struct sipe_buddy, 1);
1717 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1718 b->name = g_strdup(buddy->name);
1719 g_hash_table_insert(sip->buddies, b->name, b);
1720 sipe_group_buddy(gc, b->name, NULL, group->name);
1721 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1722 } else {
1723 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1727 static void sipe_free_buddy(struct sipe_buddy *buddy)
1729 // g_free(buddy->name); /* g_hash_table_foreach_remove does not like it */
1730 g_free(buddy->annotation);
1731 g_free(buddy->device_name);
1732 g_slist_free(buddy->groups);
1733 g_free(buddy);
1737 * Unassociates buddy from group first.
1738 * Then see if no groups left, removes buddy completely.
1739 * Otherwise updates buddy groups on server.
1741 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1743 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1744 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1745 struct sipe_group *g = NULL;
1747 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1749 if (!b) return;
1751 if (group) {
1752 g = sipe_group_find_by_name(sip, group->name);
1755 if (g) {
1756 b->groups = g_slist_remove(b->groups, g);
1757 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1760 if (g_slist_length(b->groups) < 1) {
1761 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1762 sipe_cancel_scheduled_action(sip, action_name);
1763 g_free(action_name);
1765 g_hash_table_remove(sip->buddies, buddy->name);
1767 if (b->name) {
1768 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1769 send_soap_request(sip, body);
1770 g_free(body);
1773 sipe_free_buddy(b);
1774 } else {
1775 //updates groups on server
1776 sipe_group_set_user(sip, b->name);
1781 static void
1782 sipe_rename_group(PurpleConnection *gc,
1783 const char *old_name,
1784 PurpleGroup *group,
1785 GList *moved_buddies)
1787 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1788 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1789 if (group) {
1790 sipe_group_rename(sip, s_group, group->name);
1791 } else {
1792 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1796 static void
1797 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1799 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1800 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1801 if (s_group) {
1802 gchar *body;
1803 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1804 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1805 send_soap_request(sip, body);
1806 g_free(body);
1808 sip->groups = g_slist_remove(sip->groups, s_group);
1809 g_free(s_group->name);
1810 g_free(s_group);
1811 } else {
1812 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1816 static GList *sipe_status_types(PurpleAccount *acc)
1818 PurpleStatusType *type;
1819 GList *types = NULL;
1821 // Online
1822 type = purple_status_type_new_with_attrs(
1823 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1824 // Translators: noun
1825 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1826 NULL);
1827 types = g_list_append(types, type);
1829 // Busy
1830 type = purple_status_type_new_with_attrs(
1831 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1832 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1833 NULL);
1834 types = g_list_append(types, type);
1836 // Do Not Disturb (not user settable)
1837 type = purple_status_type_new_with_attrs(
1838 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1839 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1840 NULL);
1841 types = g_list_append(types, type);
1843 // Be Right Back
1844 type = purple_status_type_new_with_attrs(
1845 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1846 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1847 NULL);
1848 types = g_list_append(types, type);
1850 // Away
1851 type = purple_status_type_new_with_attrs(
1852 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1853 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1854 NULL);
1855 types = g_list_append(types, type);
1857 //On The Phone
1858 type = purple_status_type_new_with_attrs(
1859 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1860 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1861 NULL);
1862 types = g_list_append(types, type);
1864 //Out To Lunch
1865 type = purple_status_type_new_with_attrs(
1866 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1867 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1868 NULL);
1869 types = g_list_append(types, type);
1871 //Appear Offline
1872 type = purple_status_type_new_full(
1873 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1874 types = g_list_append(types, type);
1876 // Offline
1877 type = purple_status_type_new_full(
1878 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1879 types = g_list_append(types, type);
1881 return types;
1885 * A callback for g_hash_table_foreach
1887 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1889 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1890 int time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1891 int timeout = (time_range * rand()) / RAND_MAX; /* random period within the range */
1892 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, buddy->name);
1896 * Removes entries from purple buddy list
1897 * that does not correspond ones in the roaming contact list.
1899 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1900 GSList *buddies = purple_find_buddies(sip->account, NULL);
1901 GSList *entry = buddies;
1902 struct sipe_buddy *buddy;
1903 PurpleBuddy *b;
1904 PurpleGroup *g;
1906 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1907 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1908 while (entry) {
1909 b = entry->data;
1910 g = purple_buddy_get_group(b);
1911 buddy = g_hash_table_lookup(sip->buddies, b->name);
1912 if(buddy) {
1913 gboolean in_sipe_groups = FALSE;
1914 GSList *entry2 = buddy->groups;
1915 while (entry2) {
1916 struct sipe_group *group = entry2->data;
1917 if (!strcmp(group->name, g->name)) {
1918 in_sipe_groups = TRUE;
1919 break;
1921 entry2 = entry2->next;
1923 if(!in_sipe_groups) {
1924 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1925 purple_blist_remove_buddy(b);
1927 } else {
1928 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1929 purple_blist_remove_buddy(b);
1931 entry = entry->next;
1935 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1937 int len = msg->bodylen;
1939 gchar *tmp = sipmsg_find_header(msg, "Event");
1940 xmlnode *item;
1941 xmlnode *isc;
1942 const gchar *contacts_delta;
1943 xmlnode *group_node;
1944 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1945 return FALSE;
1948 /* Convert the contact from XML to Purple Buddies */
1949 isc = xmlnode_from_str(msg->body, len);
1950 if (!isc) {
1951 return FALSE;
1954 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1955 if (contacts_delta) {
1956 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1959 if (!strcmp(isc->name, "contactList")) {
1961 /* Parse groups */
1962 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1963 struct sipe_group * group = g_new0(struct sipe_group, 1);
1964 const char *name = xmlnode_get_attrib(group_node, "name");
1966 if (!strncmp(name, "~", 1)) {
1967 // TODO translate
1968 name = "Other Contacts";
1970 group->name = g_strdup(name);
1971 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1973 sipe_group_add(sip, group);
1976 // Make sure we have at least one group
1977 if (g_slist_length(sip->groups) == 0) {
1978 struct sipe_group * group = g_new0(struct sipe_group, 1);
1979 PurpleGroup *purple_group;
1980 // TODO translate
1981 group->name = g_strdup("Other Contacts");
1982 group->id = 1;
1983 purple_group = purple_group_new(group->name);
1984 purple_blist_add_group(purple_group, NULL);
1985 sip->groups = g_slist_append(sip->groups, group);
1988 /* Parse contacts */
1989 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1990 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1991 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1992 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1993 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1994 gchar **item_groups;
1995 struct sipe_group *group = NULL;
1996 struct sipe_buddy *buddy = NULL;
1997 int i = 0;
1999 // assign to group Other Contacts if nothing else received
2000 if(!groups || !strcmp("", groups) ) {
2001 group = sipe_group_find_by_name(sip, "Other Contacts");
2002 groups = group ? g_strdup_printf("%d", group->id) : "1";
2005 item_groups = g_strsplit(groups, " ", 0);
2007 while (item_groups[i]) {
2008 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
2010 // If couldn't find the right group for this contact, just put them in the first group we have
2011 if (group == NULL && g_slist_length(sip->groups) > 0) {
2012 group = sip->groups->data;
2015 if (group != NULL) {
2016 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
2017 if (!b){
2018 b = purple_buddy_new(sip->account, buddy_name, uri);
2019 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
2022 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
2023 if (name != NULL && strlen(name) != 0) {
2024 purple_blist_alias_buddy(b, name);
2028 if (!buddy) {
2029 buddy = g_new0(struct sipe_buddy, 1);
2030 buddy->name = g_strdup(b->name);
2031 g_hash_table_insert(sip->buddies, buddy->name, buddy);
2034 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2036 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2037 } else {
2038 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2039 name);
2042 i++;
2043 } // while, contact groups
2044 g_strfreev(item_groups);
2045 g_free(groups);
2046 g_free(name);
2047 g_free(buddy_name);
2048 g_free(uri);
2050 } // for, contacts
2052 sipe_cleanup_local_blist(sip);
2054 xmlnode_free(isc);
2056 //subscribe to buddies
2057 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2058 if(sip->batched_support){
2059 sipe_subscribe_presence_batched(sip, NULL);
2061 else{
2062 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2064 sip->subscribed_buddies = TRUE;
2067 return 0;
2071 * Subscribe roaming contacts
2073 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2075 gchar *to = g_strdup_printf("sip:%s", sip->username);
2076 gchar *tmp = get_contact(sip);
2077 gchar *hdr = g_strdup_printf(
2078 "Event: vnd-microsoft-roaming-contacts\r\n"
2079 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2080 "Supported: com.microsoft.autoextend\r\n"
2081 "Supported: ms-benotify\r\n"
2082 "Proxy-Require: ms-benotify\r\n"
2083 "Supported: ms-piggyback-first-notify\r\n"
2084 "Contact: %s\r\n", tmp);
2085 g_free(tmp);
2087 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2088 g_free(to);
2089 g_free(hdr);
2092 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, void *unused)
2094 gchar *to = g_strdup_printf("sip:%s", sip->username);
2095 gchar *tmp = get_contact(sip);
2096 gchar *hdr = g_strdup_printf(
2097 "Event: presence.wpending\r\n"
2098 "Accept: text/xml+msrtc.wpending\r\n"
2099 "Supported: com.microsoft.autoextend\r\n"
2100 "Supported: ms-benotify\r\n"
2101 "Proxy-Require: ms-benotify\r\n"
2102 "Supported: ms-piggyback-first-notify\r\n"
2103 "Contact: %s\r\n", tmp);
2104 g_free(tmp);
2106 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2107 g_free(to);
2108 g_free(hdr);
2112 * Fires on deregistration event initiated by server.
2113 * [MS-SIPREGE] SIP extension.
2116 // 2007 Example
2118 // Content-Type: text/registration-event
2119 // subscription-state: terminated;expires=0
2120 // ms-diagnostics-public: 4141;reason="User disabled"
2122 // deregistered;event=rejected
2124 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2126 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2127 gchar *event = NULL;
2128 gchar *reason = NULL;
2129 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2131 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2132 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2134 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2135 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2136 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2137 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2138 } else {
2139 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2140 return;
2143 if (warning != NULL) {
2144 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2145 } else { // for LCS2005
2146 int error_id = 0;
2147 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2148 error_id = 4140; // [MS-SIPREGE]
2149 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2150 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2151 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2152 error_id = 4141;
2153 reason = g_strdup(_("User disabled")); // [MS-OCER]
2154 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2155 error_id = 4142;
2156 reason = g_strdup(_("User moved")); // [MS-OCER]
2159 g_free(event);
2160 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2161 g_free(reason);
2163 sip->gc->wants_to_die = TRUE;
2164 purple_connection_error(sip->gc, warning);
2165 g_free(warning);
2169 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2171 const gchar *contacts_delta;
2172 xmlnode *xml;
2174 xml = xmlnode_from_str(msg->body, msg->bodylen);
2175 if (!xml)
2177 return;
2180 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2181 if (contacts_delta)
2183 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2186 xmlnode_free(xml);
2192 * When we receive some self (BE) NOTIFY with a new subscriber
2193 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2197 static void sipe_process_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2199 gchar *contact;
2200 gchar *to;
2201 xmlnode *xml;
2202 xmlnode *node;
2203 char *display_name = NULL;
2204 PurpleBuddy *pbuddy;
2205 const char *alias;
2206 char *uri_alias;
2207 char *uri_user;
2209 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2211 xml = xmlnode_from_str(msg->body, msg->bodylen);
2212 if (!xml) return;
2214 contact = get_contact(sip);
2215 to = g_strdup_printf("sip:%s", sip->username);
2217 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2218 const char *user;
2219 const char *acknowledged;
2220 gchar *hdr;
2221 gchar *body;
2223 user = xmlnode_get_attrib(node, "user");
2224 if (!user) continue;
2225 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2226 uri_user = g_strdup_printf("sip:%s", user);
2227 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2228 if(pbuddy){
2229 alias = purple_buddy_get_local_alias(pbuddy);
2230 uri_alias = g_strdup_printf("sip:%s", alias);
2231 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2232 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2233 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2234 purple_blist_alias_buddy(pbuddy, display_name);
2236 g_free(display_name);
2237 g_free(uri_alias);
2239 g_free(uri_user);
2241 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2242 if(!g_ascii_strcasecmp(acknowledged,"false")){
2243 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2244 hdr = g_strdup_printf(
2245 "Contact: %s\r\n"
2246 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2248 body = g_strdup_printf(
2249 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2250 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2251 "</setSubscribers>", user);
2253 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2254 g_free(body);
2255 g_free(hdr);
2259 g_free(to);
2260 g_free(contact);
2261 xmlnode_free(xml);
2264 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2266 gchar *to = g_strdup_printf("sip:%s", sip->username);
2267 gchar *tmp = get_contact(sip);
2268 gchar *hdr = g_strdup_printf(
2269 "Event: vnd-microsoft-roaming-ACL\r\n"
2270 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2271 "Supported: com.microsoft.autoextend\r\n"
2272 "Supported: ms-benotify\r\n"
2273 "Proxy-Require: ms-benotify\r\n"
2274 "Supported: ms-piggyback-first-notify\r\n"
2275 "Contact: %s\r\n", tmp);
2276 g_free(tmp);
2278 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2279 g_free(to);
2280 g_free(hdr);
2284 * To request for presence information about the user, access level settings that have already been configured by the user
2285 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2286 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2289 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2291 gchar *to = g_strdup_printf("sip:%s", sip->username);
2292 gchar *tmp = get_contact(sip);
2293 gchar *hdr = g_strdup_printf(
2294 "Event: vnd-microsoft-roaming-self\r\n"
2295 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2296 "Supported: ms-benotify\r\n"
2297 "Proxy-Require: ms-benotify\r\n"
2298 "Supported: ms-piggyback-first-notify\r\n"
2299 "Contact: %s\r\n"
2300 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2302 gchar *body=g_strdup(
2303 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2304 "<roaming type=\"categories\"/>"
2305 "<roaming type=\"containers\"/>"
2306 "<roaming type=\"subscribers\"/></roamingList>");
2308 g_free(tmp);
2309 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2310 g_free(body);
2311 g_free(to);
2312 g_free(hdr);
2316 * For 2005 version
2318 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2320 gchar *to = g_strdup_printf("sip:%s", sip->username);
2321 gchar *tmp = get_contact(sip);
2322 gchar *hdr = g_strdup_printf(
2323 "Event: vnd-microsoft-provisioning\r\n"
2324 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2325 "Supported: com.microsoft.autoextend\r\n"
2326 "Supported: ms-benotify\r\n"
2327 "Proxy-Require: ms-benotify\r\n"
2328 "Supported: ms-piggyback-first-notify\r\n"
2329 "Expires: 0\r\n"
2330 "Contact: %s\r\n", tmp);
2332 g_free(tmp);
2333 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2334 g_free(to);
2335 g_free(hdr);
2338 /** Subscription for provisioning information to help with initial
2339 * configuration. This subscription is a one-time query (denoted by the Expires header,
2340 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2341 * configuration, meeting policies, and policy settings that Communicator must enforce.
2342 * TODO: for what we need this information.
2345 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2347 gchar *to = g_strdup_printf("sip:%s", sip->username);
2348 gchar *tmp = get_contact(sip);
2349 gchar *hdr = g_strdup_printf(
2350 "Event: vnd-microsoft-provisioning-v2\r\n"
2351 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2352 "Supported: com.microsoft.autoextend\r\n"
2353 "Supported: ms-benotify\r\n"
2354 "Proxy-Require: ms-benotify\r\n"
2355 "Supported: ms-piggyback-first-notify\r\n"
2356 "Expires: 0\r\n"
2357 "Contact: %s\r\n"
2358 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2359 gchar *body = g_strdup(
2360 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2361 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2362 "<provisioningGroup name=\"ucPolicy\"/>"
2363 "</provisioningGroupList>");
2365 g_free(tmp);
2366 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2367 g_free(body);
2368 g_free(to);
2369 g_free(hdr);
2372 /* IM Session (INVITE and MESSAGE methods) */
2374 static struct sip_dialog *
2375 get_dialog (struct sip_im_session *session,
2376 const gchar *who)
2378 struct sip_dialog *dialog;
2379 GSList *entry;
2380 if (session == NULL || who == NULL) {
2381 return NULL;
2384 entry = session->dialogs;
2385 while (entry) {
2386 dialog = entry->data;
2387 if (dialog->with && !strcmp(who, dialog->with)) {
2388 return dialog;
2390 entry = entry->next;
2392 return NULL;
2395 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2396 static gchar *
2397 get_end_points (struct sipe_account_data *sip,
2398 struct sip_im_session *session)
2400 gchar *tmp = NULL;
2401 gchar *res = g_strdup_printf("<sip:%s>", sip->username);
2402 struct sip_dialog *dialog;
2403 GSList *entry;
2404 if (session == NULL) {
2405 return NULL;
2408 entry = session->dialogs;
2409 while (entry) {
2410 dialog = entry->data;
2412 tmp = res;
2413 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2414 g_free(tmp);
2416 if (dialog->theirepid) {
2417 tmp = res;
2418 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2419 g_free(tmp);
2422 entry = entry->next;
2424 return res;
2427 static struct sip_im_session *
2428 find_chat_session_by_id (struct sipe_account_data *sip,
2429 int id)
2431 struct sip_im_session *session;
2432 GSList *entry;
2433 if (sip == NULL) {
2434 return NULL;
2437 entry = sip->im_sessions;
2438 while (entry) {
2439 session = entry->data;
2440 if (id == session->chat_id) {
2441 return session;
2443 entry = entry->next;
2445 return NULL;
2448 static struct sip_im_session *
2449 find_chat_session_by_name (struct sipe_account_data *sip,
2450 const char *chat_name)
2452 struct sip_im_session *session;
2453 GSList *entry;
2454 if (sip == NULL || chat_name == NULL) {
2455 return NULL;
2458 entry = sip->im_sessions;
2459 while (entry) {
2460 session = entry->data;
2461 if (session->chat_name && !g_strcasecmp(chat_name, session->chat_name)) {
2462 return session;
2464 entry = entry->next;
2466 return NULL;
2469 static struct sip_im_session *
2470 find_chat_session (struct sipe_account_data *sip,
2471 const char *callid)
2473 struct sip_im_session *session;
2474 GSList *entry;
2475 if (sip == NULL || callid == NULL) {
2476 return NULL;
2479 entry = sip->im_sessions;
2480 while (entry) {
2481 session = entry->data;
2482 if (session->callid && !g_strcasecmp(callid, session->callid)) {
2483 return session;
2485 entry = entry->next;
2487 return NULL;
2490 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2492 struct sip_im_session *session;
2493 GSList *entry;
2494 if (sip == NULL || who == NULL) {
2495 return NULL;
2498 entry = sip->im_sessions;
2499 while (entry) {
2500 session = entry->data;
2501 if (session->with && !strcmp(who, session->with)) {
2502 return session;
2504 entry = entry->next;
2506 return NULL;
2509 static struct sip_im_session *
2510 create_chat_session (struct sipe_account_data *sip)
2512 struct sip_im_session *session = g_new0(struct sip_im_session, 1);
2513 session->callid = gencallid();
2514 session->is_multiparty = TRUE;
2515 session->chat_id = rand();
2516 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2517 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2518 return session;
2521 static struct sip_im_session * find_or_create_chat_session (struct sipe_account_data *sip, const char *callid)
2523 struct sip_im_session *session = find_chat_session(sip, callid);
2524 if (!session) {
2525 session = create_chat_session(sip);
2526 session->callid = g_strdup(callid);
2528 return session;
2531 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2533 struct sip_im_session *session = find_im_session(sip, who);
2534 if (!session) {
2535 session = g_new0(struct sip_im_session, 1);
2536 session->is_multiparty = FALSE;
2537 session->with = g_strdup(who);
2538 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2539 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2541 return session;
2544 static void
2545 free_dialog(struct sip_dialog *dialog)
2547 GSList *entry = dialog->routes;
2548 g_free(dialog->with);
2549 while (entry) {
2550 g_free(entry->data);
2551 entry = g_slist_remove(entry, entry->data);
2553 entry = dialog->supported;
2554 while (entry) {
2555 g_free(entry->data);
2556 entry = g_slist_remove(entry, entry->data);
2559 g_free(dialog->ourtag);
2560 g_free(dialog->theirtag);
2561 g_free(dialog->theirepid);
2562 g_free(dialog->request);
2564 g_free(dialog);
2567 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2569 GSList *entry;
2571 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2573 entry = session->dialogs;
2574 while (entry) {
2575 free_dialog(entry->data);
2576 entry = g_slist_remove(entry, entry->data);
2579 entry = session->outgoing_message_queue;
2580 while (entry) {
2581 g_free(entry->data);
2582 entry = g_slist_remove(entry, entry->data);
2585 entry = session->pending_invite_queue;
2586 while (entry) {
2587 g_free(entry->data);
2588 entry = g_slist_remove(entry, entry->data);
2591 g_hash_table_destroy(session->unconfirmed_messages);
2593 g_free(session->with);
2594 g_free(session->chat_name);
2595 g_free(session->callid);
2596 g_free(session->roster_manager);
2597 g_free(session);
2600 static gboolean
2601 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2603 gboolean ret = TRUE;
2605 if (msg->response != 200) {
2606 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2607 return FALSE;
2610 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2612 return ret;
2616 * Asks UA/proxy about its capabilities.
2618 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2620 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2621 gchar *contact = get_contact(sip);
2622 gchar *request;
2623 request = g_strdup_printf(
2624 "Accept: application/sdp\r\n"
2625 "Contact: %s\r\n", contact);
2627 g_free(contact);
2629 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2631 g_free(to);
2632 g_free(request);
2635 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2637 char *msg, *msg_tmp;
2638 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2639 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2640 g_free(msg_tmp);
2641 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2642 "possibly because one or more persons are offline:\n%s") ,
2643 msg ? msg : "");
2644 purple_conv_present_error(with, sip->account, msg_tmp);
2645 g_free(msg);
2646 g_free(msg_tmp);
2649 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2651 static gboolean
2652 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2654 gboolean ret = TRUE;
2655 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2656 struct sip_im_session * session = find_im_session(sip, with);
2657 struct sip_dialog *dialog;
2658 gchar *cseq;
2659 char *key;
2660 gchar *message;
2662 if (!session) {
2663 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2664 g_free(with);
2665 return FALSE;
2668 dialog = get_dialog(session, with);
2670 if (!dialog) {
2671 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2672 g_free(with);
2673 return FALSE;
2676 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2677 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2678 g_free(cseq);
2679 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2681 if (msg->response != 200) {
2682 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2684 sipe_present_message_undelivered_err(with, sip, message);
2685 im_session_destroy(sip, session);
2686 ret = FALSE;
2687 } else {
2688 g_hash_table_remove(session->unconfirmed_messages, key);
2689 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2690 key, g_hash_table_size(session->unconfirmed_messages));
2693 g_free(key);
2694 g_free(with);
2696 if (ret) sipe_im_process_queue(sip, session);
2697 return ret;
2700 static gboolean
2701 sipe_is_election_finished(struct sipe_account_data *sip,
2702 struct sip_im_session *session);
2704 static void
2705 sipe_election_result(struct sipe_account_data *sip,
2706 void *sess);
2708 static gboolean
2709 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2711 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2712 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2713 struct sip_dialog *dialog;
2714 struct sip_im_session *session;
2716 session = find_chat_session(sip, callid);
2717 if (!session) {
2718 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2719 return FALSE;
2722 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2723 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2724 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2725 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2727 if (xn_request_rm_response) {
2728 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2729 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2731 dialog = get_dialog(session, with);
2732 if (!dialog) {
2733 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2734 return FALSE;
2737 if (allow && !g_strcasecmp(allow, "true")) {
2738 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2739 dialog->election_vote = 1;
2740 } else if (allow && !g_strcasecmp(allow, "false")) {
2741 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2742 dialog->election_vote = -1;
2745 if (sipe_is_election_finished(sip, session)) {
2746 sipe_election_result(sip, session);
2749 } else if (xn_set_rm_response) {
2752 xmlnode_free(xn_action);
2756 return TRUE;
2759 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2761 gchar *hdr;
2762 gchar *tmp;
2763 char *msgformat;
2764 char *msgtext;
2765 gchar *msgr_value;
2766 gchar *msgr;
2768 sipe_parse_html(msg, &msgformat, &msgtext);
2769 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2771 msgr_value = sipmsg_get_msgr_string(msgformat);
2772 g_free(msgformat);
2773 if (msgr_value) {
2774 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2775 g_free(msgr_value);
2776 } else {
2777 msgr = g_strdup("");
2780 tmp = get_contact(sip);
2781 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2782 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2783 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2784 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2785 g_free(tmp);
2786 g_free(msgr);
2788 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2789 g_free(msgtext);
2790 g_free(hdr);
2794 static void
2795 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2797 GSList *entry2 = session->outgoing_message_queue;
2798 while (entry2) {
2799 char *queued_msg = entry2->data;
2800 struct sip_dialog *dialog;
2801 GSList *entry = session->dialogs;
2803 if (session->is_multiparty) {
2804 serv_got_chat_in(sip->gc, session->chat_id, g_strdup_printf("sip:%s", sip->username),
2805 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2808 while (entry) {
2809 char *key;
2811 dialog = entry->data;
2812 if (dialog->outgoing_invite) continue; //do not send messages as INVITE is not responded.
2814 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2815 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2816 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2817 key, g_hash_table_size(session->unconfirmed_messages));
2818 g_free(key);
2819 sipe_send_message(sip, dialog, queued_msg);
2821 entry = entry->next;
2824 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2825 g_free(queued_msg);
2829 static void
2830 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2832 GSList *hdr = msg->headers;
2833 gchar *contact;
2835 while(hdr) {
2836 struct siphdrelement *elem = hdr->data;
2837 if(!g_ascii_strcasecmp(elem->name, "Record-Route")) {
2838 gchar **parts = g_strsplit(elem->value, ",", 0);
2839 gchar **part = parts;
2841 while (*part) {
2842 gchar *route = sipmsg_find_part_of_header(*part, "<", ">", NULL);
2843 purple_debug_info("sipe", "sipe_get_route_header: route %s \n", route);
2844 dialog->routes = g_slist_append(dialog->routes, route);
2845 part++;
2848 g_strfreev(parts);
2850 hdr = g_slist_next(hdr);
2853 if (outgoing)
2855 dialog->routes = g_slist_reverse(dialog->routes);
2858 if (dialog->routes)
2860 dialog->request = dialog->routes->data;
2861 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2864 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2865 dialog->routes = g_slist_append(dialog->routes, contact);
2868 static void
2869 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2871 GSList *hdr = msg->headers;
2872 struct siphdrelement *elem;
2873 while(hdr)
2875 elem = hdr->data;
2876 if(!g_ascii_strcasecmp(elem->name, "Supported")
2877 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2879 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2882 hdr = g_slist_next(hdr);
2886 static void
2887 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2889 gchar *us = outgoing ? "From" : "To";
2890 gchar *them = outgoing ? "To" : "From";
2892 g_free(dialog->ourtag);
2893 g_free(dialog->theirtag);
2895 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2896 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2897 if (!dialog->theirepid) {
2898 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2899 if (!dialog->theirepid) {
2900 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2904 // Catch a tag on the end of the To Header and get rid of it.
2905 if (dialog->theirepid && strstr(dialog->theirepid, "tag=")) {
2906 dialog->theirepid = strtok(dialog->theirepid, ";");
2909 sipe_get_route_header(msg, dialog, outgoing);
2910 sipe_get_supported_header(msg, dialog, outgoing);
2913 static void
2914 sipe_refer_notify(struct sipe_account_data *sip,
2915 struct sip_im_session *session,
2916 const gchar *who,
2917 int status,
2918 const gchar *desc)
2920 gchar *hdr;
2921 gchar *body;
2922 struct sip_dialog *dialog = get_dialog(session, who);
2924 hdr = g_strdup_printf(
2925 "Event: refer\r\n"
2926 "Subscription-State: %s\r\n"
2927 "Content-Type: message/sipfrag\r\n",
2928 status >= 200 ? "terminated" : "active");
2930 body = g_strdup_printf(
2931 "SIP/2.0 %d %s\r\n",
2932 status, desc);
2934 dialog->outgoing_invite = send_sip_request(sip->gc, "NOTIFY",
2935 who, who, hdr, body, dialog, NULL);
2937 g_free(hdr);
2938 g_free(body);
2941 static gboolean
2942 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2944 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2945 struct sip_im_session *session;
2946 struct sip_dialog *dialog;
2947 char *cseq;
2948 char *key;
2949 gchar *message;
2950 struct sipmsg *request_msg = trans->msg;
2952 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2953 gchar *referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
2955 session = find_chat_session(sip, callid);
2956 if (!session) {
2957 session = find_im_session(sip, with);
2960 if (!session) {
2961 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2962 g_free(with);
2963 return FALSE;
2966 dialog = get_dialog(session, with);
2967 if (!dialog) {
2968 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2969 g_free(with);
2970 return FALSE;
2973 sipe_parse_dialog(msg, dialog, TRUE);
2975 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2976 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2977 g_free(cseq);
2978 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2980 if (msg->response != 200) {
2981 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2983 sipe_present_message_undelivered_err(with, sip, message);
2984 im_session_destroy(sip, session);
2985 g_free(with);
2986 return FALSE;
2989 dialog->cseq = 0;
2990 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
2991 dialog->outgoing_invite = NULL;
2992 dialog->is_established = TRUE;
2994 if (referred_by) {
2995 sipe_refer_notify(sip, session, referred_by, 200, "OK");
2998 /* add user to chat if it is a multiparty session */
2999 if (session->is_multiparty) {
3000 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3001 with, NULL,
3002 PURPLE_CBFLAGS_NONE, TRUE);
3005 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3006 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3007 if (session->outgoing_message_queue) {
3008 char *queued_msg = session->outgoing_message_queue->data;
3009 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3010 g_free(queued_msg);
3014 sipe_im_process_queue(sip, session);
3016 g_hash_table_remove(session->unconfirmed_messages, key);
3017 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3018 key, g_hash_table_size(session->unconfirmed_messages));
3020 g_free(key);
3021 g_free(with);
3022 g_free(referred_by);
3023 return TRUE;
3027 static void
3028 sipe_invite(struct sipe_account_data *sip,
3029 struct sip_im_session *session,
3030 const gchar *who,
3031 const gchar *msg_body,
3032 const gchar *referred_by,
3033 const gboolean is_triggered)
3035 gchar *hdr;
3036 gchar *to;
3037 gchar *contact;
3038 gchar *body;
3039 gchar *self = g_strdup_printf("sip:%s", sip->username);
3040 char *ms_text_format = g_strdup("");
3041 gchar *roster_manager;
3042 gchar *end_points;
3043 gchar *referred_by_str;
3044 gchar *triggered;
3045 gchar *require_multiparty;
3046 struct sip_dialog *dialog = get_dialog(session, who);
3048 if (dialog && dialog->is_established) {
3049 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
3050 return;
3053 if (!dialog) {
3054 dialog = g_new0(struct sip_dialog, 1);
3055 session->dialogs = g_slist_append(session->dialogs, dialog);
3057 dialog->callid = session->callid ? session->callid : gencallid();
3058 dialog->with = g_strdup(who);
3061 if (!(dialog->ourtag)) {
3062 dialog->ourtag = gentag();
3066 if (strstr(who, "sip:")) {
3067 to = g_strdup(who);
3068 } else {
3069 to = g_strdup_printf("sip:%s", who);
3072 if (msg_body) {
3073 char *msgformat;
3074 char *msgtext;
3075 char *base64_msg;
3076 gchar *msgr_value;
3077 gchar *msgr;
3078 char *key;
3080 sipe_parse_html(msg_body, &msgformat, &msgtext);
3081 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
3083 msgr_value = sipmsg_get_msgr_string(msgformat);
3084 g_free(msgformat);
3085 msgr = "";
3086 if (msgr_value) {
3087 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3088 g_free(msgr_value);
3091 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3092 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3093 g_free(msgtext);
3094 g_free(msgr);
3095 g_free(base64_msg);
3097 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3098 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3099 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3100 key, g_hash_table_size(session->unconfirmed_messages));
3101 g_free(key);
3104 contact = get_contact(sip);
3105 end_points = get_end_points(sip, session);
3106 roster_manager = g_strdup_printf(
3107 "Roster-Manager: %s\r\n"
3108 "EndPoints: %s\r\n",
3109 self,
3110 end_points);
3111 referred_by_str = referred_by ?
3112 g_strdup_printf(
3113 "Referred-By: %s\r\n",
3114 referred_by)
3115 : g_strdup("");
3116 triggered =
3117 "TriggeredInvite: TRUE\r\n";
3118 require_multiparty =
3119 "Require: com.microsoft.rtc-multiparty\r\n";
3120 hdr = g_strdup_printf(
3121 "%s"
3122 "%s"
3123 "%s"
3124 "%s"
3125 "Contact: %s\r\n%s"
3126 "Content-Type: application/sdp\r\n",
3127 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3128 referred_by_str,
3129 is_triggered ? triggered : "",
3130 is_triggered || session->is_multiparty ? require_multiparty : "",
3131 contact,
3132 ms_text_format);
3133 g_free(ms_text_format);
3135 body = g_strdup_printf(
3136 "v=0\r\n"
3137 "o=- 0 0 IN IP4 %s\r\n"
3138 "s=session\r\n"
3139 "c=IN IP4 %s\r\n"
3140 "t=0 0\r\n"
3141 "m=message %d sip null\r\n"
3142 "a=accept-types:text/plain text/html image/gif "
3143 "multipart/alternative application/im-iscomposing+xml\r\n",
3144 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3146 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3147 to, to, hdr, body, dialog, process_invite_response);
3149 g_free(to);
3150 g_free(roster_manager);
3151 g_free(end_points);
3152 g_free(referred_by_str);
3153 g_free(body);
3154 g_free(hdr);
3155 g_free(contact);
3158 static void
3159 sipe_refer(struct sipe_account_data *sip,
3160 struct sip_im_session *session,
3161 const gchar *who)
3163 gchar *hdr;
3164 gchar *contact;
3165 struct sip_dialog *dialog = get_dialog(session, session->roster_manager);
3167 contact = get_contact(sip);
3168 hdr = g_strdup_printf(
3169 "Contact: %s\r\n"
3170 "Refer-to: <%s>\r\n"
3171 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3172 "Require: com.microsoft.rtc-multiparty\r\n",
3173 contact,
3174 who,
3175 sip->username,
3176 dialog->ourtag ? ";tag=" : "",
3177 dialog->ourtag ? dialog->ourtag : "",
3178 get_epid(sip));
3180 send_sip_request(sip->gc, "REFER",
3181 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3183 g_free(hdr);
3184 g_free(contact);
3187 static void
3188 sipe_send_election_request_rm(struct sipe_account_data *sip,
3189 struct sip_im_session *session,
3190 const gchar *who,
3191 int bid)
3193 gchar *hdr;
3194 gchar *body;
3195 struct sip_dialog *dialog = get_dialog(session, who);
3197 hdr = "Content-Type: application/x-ms-mim\r\n";
3199 body = g_strdup_printf(
3200 "<?xml version=\"1.0\"?>\r\n"
3201 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3202 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3203 sip->username, bid);
3205 send_sip_request(sip->gc, "INFO",
3206 who, who, hdr, body, dialog, process_info_response);
3208 g_free(body);
3211 static void
3212 sipe_send_election_set_rm(struct sipe_account_data *sip,
3213 struct sip_im_session *session,
3214 const gchar *who)
3216 gchar *hdr;
3217 gchar *body;
3218 struct sip_dialog *dialog = get_dialog(session, who);
3220 hdr = "Content-Type: application/x-ms-mim\r\n";
3222 body = g_strdup_printf(
3223 "<?xml version=\"1.0\"?>\r\n"
3224 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3225 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3226 sip->username);
3228 send_sip_request(sip->gc, "INFO",
3229 who, who, hdr, body, dialog, process_info_response);
3231 g_free(body);
3234 static void
3235 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
3237 if (session) {
3238 struct sip_dialog *dialog;
3239 GSList *entry;
3240 entry = session->dialogs;
3241 while (entry) {
3242 dialog = entry->data;
3243 /* @TODO slow down BYE message sending rate */
3244 /* @see single subscription code */
3245 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3246 entry = entry->next;
3249 im_session_destroy(sip, session);
3253 static void
3254 sipe_convo_closed(PurpleConnection * gc, const char *who)
3256 struct sipe_account_data *sip = gc->proto_data;
3258 purple_debug_info("sipe", "conversation with %s closed\n", who);
3259 im_session_close(sip, find_im_session(sip, who));
3262 static void
3263 sipe_chat_leave (PurpleConnection *gc, int id)
3265 struct sipe_account_data *sip = gc->proto_data;
3266 struct sip_im_session * session = find_chat_session_by_id(sip, id);
3267 im_session_close(sip, session);
3270 static void
3271 im_session_close_all (struct sipe_account_data *sip)
3273 GSList *entry = sip->im_sessions;
3274 while (entry) {
3275 im_session_close (sip, entry->data);
3276 entry = sip->im_sessions;
3280 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
3282 struct sipe_account_data *sip = gc->proto_data;
3283 struct sip_im_session *session;
3284 struct sip_dialog *dialog;
3286 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3288 session = find_or_create_im_session(sip, who);
3289 dialog = get_dialog(session, who);
3291 // Queue the message
3292 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3294 if (dialog && dialog->callid) {
3295 sipe_im_process_queue(sip, session);
3296 } else if (!dialog || !dialog->outgoing_invite) {
3297 // Need to send the INVITE to get the outgoing dialog setup
3298 sipe_invite(sip, session, who, what, NULL, FALSE);
3301 return 1;
3304 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags)
3306 struct sipe_account_data *sip = gc->proto_data;
3307 struct sip_im_session *session;
3309 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3311 session = find_chat_session_by_id(sip, id);
3313 // Queue the message
3314 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3316 sipe_im_process_queue(sip, session);
3318 return 1;
3321 /* End IM Session (INVITE and MESSAGE methods) */
3323 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3325 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3326 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3327 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3329 struct sip_im_session *session = find_chat_session(sip, callid);
3330 if (!session) {
3331 session = find_im_session(sip, from);
3334 if (!session) {
3335 return;
3338 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3339 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3340 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3341 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3343 sipmsg_remove_header(msg, "User-Agent");
3345 if (xn_request_rm) {
3346 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3347 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3348 gchar *body = g_strdup_printf(
3349 "<?xml version=\"1.0\"?>\r\n"
3350 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3351 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3352 sip->username,
3353 session->bid < bid ? "true" : "false");
3354 send_sip_response(sip->gc, msg, 200, "OK", body);
3355 g_free(body);
3356 } else if (xn_set_rm) {
3357 gchar *body;
3358 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3359 g_free(session->roster_manager);
3360 session->roster_manager = g_strdup(rm);
3362 body = g_strdup_printf(
3363 "<?xml version=\"1.0\"?>\r\n"
3364 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3365 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3366 sip->username);
3367 send_sip_response(sip->gc, msg, 200, "OK", body);
3368 g_free(body);
3370 xmlnode_free(xn_action);
3372 } else {
3373 /* looks like purple lacks typing notification for chat */
3374 if (!session->is_multiparty) {
3375 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3378 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3380 g_free(from);
3383 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3385 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3386 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3387 struct sip_im_session *session;
3388 struct sip_dialog *dialog;
3390 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3392 session = find_chat_session(sip, callid);
3393 if (!session) {
3394 session = find_im_session(sip, from);
3397 if (!session) {
3398 return;
3401 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3402 g_free(session->roster_manager);
3403 session->roster_manager = NULL;
3406 if (!session->is_multiparty) {
3407 // TODO Let the user know the other user left the conversation?
3408 im_session_destroy(sip, session);
3409 } else {
3410 dialog = get_dialog(session, from);
3411 session->dialogs = g_slist_remove(session->dialogs, dialog);
3412 free_dialog(dialog);
3414 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3416 if (!session->dialogs) {
3417 im_session_destroy(sip, session);
3421 g_free(from);
3424 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3426 gchar *self = g_strdup_printf("sip:%s", sip->username);
3427 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3428 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3429 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3430 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3431 struct sip_im_session *session;
3432 struct sip_dialog *dialog;
3434 session = find_chat_session(sip, callid);
3435 dialog = get_dialog(session, from);
3437 sipmsg_remove_header(msg, "User-Agent");
3438 sipmsg_remove_header(msg, "Refer-to");
3439 sipmsg_remove_header(msg, "Referred-By");
3440 sipmsg_remove_header(msg, "Require");
3442 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3443 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3444 } else {
3445 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3447 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3450 g_free(self);
3451 g_free(from);
3452 g_free(refer_to);
3453 g_free(referred_by);
3456 static unsigned int
3457 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3459 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3460 struct sip_im_session *session;
3461 struct sip_dialog *dialog;
3463 if (state == PURPLE_NOT_TYPING)
3464 return 0;
3466 session = find_im_session(sip, who);
3467 dialog = get_dialog(session, who);
3469 if (session && dialog) {
3470 send_sip_request(gc, "INFO", who, who,
3471 "Content-Type: application/xml\r\n",
3472 SIPE_SEND_TYPING, dialog, NULL);
3474 return SIPE_TYPING_SEND_TIMEOUT;
3477 static gboolean resend_timeout(struct sipe_account_data *sip)
3479 GSList *tmp = sip->transactions;
3480 time_t currtime = time(NULL);
3481 while (tmp) {
3482 struct transaction *trans = tmp->data;
3483 tmp = tmp->next;
3484 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3485 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3486 /* TODO 408 */
3487 } else {
3488 if ((currtime - trans->time > 2) && trans->retries == 0) {
3489 trans->retries++;
3490 sendout_sipmsg(sip, trans->msg);
3494 return TRUE;
3497 static void do_reauthenticate_cb(struct sipe_account_data *sip, void *unused)
3499 /* register again when security token expires */
3500 /* we have to start a new authentication as the security token
3501 * is almost expired by sending a not signed REGISTER message */
3502 purple_debug_info("sipe", "do a full reauthentication\n");
3503 sipe_auth_free(&sip->registrar);
3504 sipe_auth_free(&sip->proxy);
3505 sip->registerstatus = 0;
3506 do_register(sip);
3507 sip->reauthenticate_set = FALSE;
3510 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3512 gchar *from;
3513 gchar *contenttype;
3514 gboolean found = FALSE;
3516 from = parse_from(sipmsg_find_header(msg, "From"));
3518 if (!from) return;
3520 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3522 contenttype = sipmsg_find_header(msg, "Content-Type");
3523 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
3525 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3526 gchar *html = get_html_message(contenttype, msg->body);
3528 struct sip_im_session *session = find_chat_session(sip, callid);
3529 if (!session) {
3530 session = find_im_session(sip, from);
3533 if (session->is_multiparty) {
3534 serv_got_chat_in(sip->gc, session->chat_id, from,
3535 PURPLE_MESSAGE_RECV, html, time(NULL));
3536 } else {
3537 serv_got_im(sip->gc, from, html, 0, time(NULL));
3539 g_free(html);
3540 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3541 found = TRUE;
3543 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3544 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3545 xmlnode *state;
3546 gchar *statedata;
3548 if (!isc) {
3549 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3550 return;
3553 state = xmlnode_get_child(isc, "state");
3555 if (!state) {
3556 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3557 xmlnode_free(isc);
3558 return;
3561 statedata = xmlnode_get_data(state);
3562 if (statedata) {
3563 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3564 else serv_got_typing_stopped(sip->gc, from);
3566 g_free(statedata);
3568 xmlnode_free(isc);
3569 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3570 found = TRUE;
3572 if (!found) {
3573 purple_debug_info("sipe", "got unknown mime-type");
3574 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3576 g_free(from);
3579 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3581 gchar *ms_text_format;
3582 gchar *body;
3583 gchar *newTag = gentag();
3584 gchar *oldHeader;
3585 gchar *newHeader;
3586 gboolean is_multiparty;
3587 gboolean is_triggered = FALSE;
3588 gboolean was_multiparty;
3589 gboolean just_joined = FALSE;
3590 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3591 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3592 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3593 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
3594 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
3595 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
3596 gchar **end_points = NULL;
3597 struct sip_im_session *session;
3598 struct sip_dialog *dialog;
3600 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3602 /* Only accept text invitations */
3603 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3604 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3605 return;
3608 // TODO There *must* be a better way to clean up the To header to add a tag...
3609 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3610 oldHeader = sipmsg_find_header(msg, "To");
3611 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3612 sipmsg_remove_header(msg, "To");
3613 sipmsg_add_header(msg, "To", newHeader);
3614 g_free(newHeader);
3616 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3617 is_multiparty = FALSE;
3618 if (end_points_hdr) {
3619 end_points = g_strsplit(end_points_hdr, ",", 0);
3620 if (end_points[0] && end_points[1] && end_points[2]) {
3621 is_multiparty = TRUE;
3624 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
3625 is_triggered = TRUE;
3626 is_multiparty = TRUE;
3629 session = find_chat_session(sip, callid);
3630 /* Convert to multiparty */
3631 if (session && is_multiparty && !session->is_multiparty) {
3632 g_free(session->with);
3633 session->with = NULL;
3634 was_multiparty = session->is_multiparty;
3635 session->is_multiparty = TRUE;
3636 session->chat_id = rand();
3639 if (!session && is_multiparty) {
3640 session = find_or_create_chat_session(sip, callid);
3642 /* IM session */
3643 if (!session) {
3644 session = find_or_create_im_session(sip, from);
3647 if (!session->callid) {
3648 session->callid = g_strdup(callid);
3651 session->is_multiparty = is_multiparty;
3652 if (roster_manager) {
3653 session->roster_manager = g_strdup(roster_manager);
3656 if (is_multiparty && end_points) {
3657 int i = 0;
3658 while (end_points[i]) {
3659 gchar *end_point = parse_from(end_points[i]);
3660 gchar *epid = sipmsg_find_part_of_header(end_points[i], "epid=", ";", NULL);
3662 if (!g_strcasecmp(from, end_point) || !g_strcasecmp(to, end_point)) {
3663 i++;
3664 continue;
3667 dialog = get_dialog(session, end_point);
3668 if (!dialog) {
3669 dialog = g_new0(struct sip_dialog, 1);
3670 session->dialogs = g_slist_append(session->dialogs, dialog);
3672 dialog->callid = session->callid;
3673 dialog->with = g_strdup(end_point);
3674 dialog->theirepid = epid;
3676 just_joined = TRUE;
3678 /* send triggered INVITE */
3679 sipe_invite(sip, session, end_point, NULL, NULL, TRUE);
3680 } else {
3681 dialog->theirepid = epid;
3684 i++;
3688 if (session) {
3689 dialog = get_dialog(session, from);
3690 if (dialog) {
3691 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3692 } else {
3693 dialog = g_new0(struct sip_dialog, 1);
3694 session->dialogs = g_slist_append(session->dialogs, dialog);
3696 dialog->callid = session->callid;
3697 dialog->with = g_strdup(from);
3698 sipe_parse_dialog(msg, dialog, FALSE);
3700 if (!dialog->ourtag) {
3701 dialog->ourtag = newTag;
3702 newTag = NULL;
3705 just_joined = TRUE;
3707 } else {
3708 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3710 g_free(newTag);
3712 if (is_multiparty && !session->conv) {
3713 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3714 gchar *self = g_strdup_printf("sip:%s", sip->username);
3715 /* create prpl chat */
3716 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3717 session->chat_name = g_strdup(chat_name);
3718 /* add self */
3719 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3720 self, NULL,
3721 PURPLE_CBFLAGS_NONE, FALSE);
3722 g_free(chat_name);
3723 g_free(self);
3726 if (is_multiparty && !was_multiparty) {
3727 /* add current IM counterparty to chat */
3728 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3729 ((struct sip_dialog *)session->dialogs->data)->with, NULL,
3730 PURPLE_CBFLAGS_NONE, FALSE);
3734 /* add inviting party */
3735 if (just_joined) {
3736 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3737 from, NULL,
3738 PURPLE_CBFLAGS_NONE, TRUE);
3741 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3742 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3743 if (ms_text_format) {
3744 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3746 gchar *html = get_html_message(ms_text_format, NULL);
3747 if (html) {
3748 if (is_multiparty) {
3749 serv_got_chat_in(sip->gc, session->chat_id, from,
3750 PURPLE_MESSAGE_RECV, html, time(NULL));
3751 } else {
3752 serv_got_im(sip->gc, from, html, 0, time(NULL));
3754 g_free(html);
3755 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3759 g_free(from);
3761 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3762 sipmsg_remove_header(msg, "Ms-Text-Format");
3763 sipmsg_remove_header(msg, "EndPoints");
3764 sipmsg_remove_header(msg, "User-Agent");
3765 sipmsg_remove_header(msg, "Roster-Manager");
3766 sipmsg_remove_header(msg, "P-Asserted-Identity");
3767 sipmsg_remove_header(msg, "Require");
3769 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3770 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3772 body = g_strdup_printf(
3773 "v=0\r\n"
3774 "o=- 0 0 IN IP4 %s\r\n"
3775 "s=session\r\n"
3776 "c=IN IP4 %s\r\n"
3777 "t=0 0\r\n"
3778 "m=message %d sip sip:%s\r\n"
3779 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3780 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3781 sip->realport, sip->username);
3782 send_sip_response(sip->gc, msg, 200, "OK", body);
3783 g_free(body);
3786 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3788 gchar *body;
3790 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3791 sipmsg_remove_header(msg, "EndPoints");
3792 sipmsg_remove_header(msg, "User-Agent");
3794 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3795 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3797 body = g_strdup_printf(
3798 "v=0\r\n"
3799 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3800 "s=session\r\n"
3801 "c=IN IP4 0.0.0.0\r\n"
3802 "t=0 0\r\n"
3803 "m=message %d sip sip:%s\r\n"
3804 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3805 sip->realport, sip->username);
3806 send_sip_response(sip->gc, msg, 200, "OK", body);
3807 g_free(body);
3810 static void sipe_connection_cleanup(struct sipe_account_data *);
3811 static void create_connection(struct sipe_account_data *, gchar *, int);
3813 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3815 gchar *tmp;
3816 const gchar *expires_header;
3817 int expires, i;
3818 GSList *hdr = msg->headers;
3819 GSList *entry;
3820 struct siphdrelement *elem;
3822 expires_header = sipmsg_find_header(msg, "Expires");
3823 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3824 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3826 switch (msg->response) {
3827 case 200:
3828 if (expires == 0) {
3829 sip->registerstatus = 0;
3830 } else {
3831 gchar *contact_hdr = NULL;
3832 gchar *gruu = NULL;
3833 gchar *epid;
3834 gchar *uuid;
3835 gchar *timeout;
3837 if (!sip->reregister_set) {
3838 gchar *action_name = g_strdup_printf("<%s>", "registration");
3839 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3840 g_free(action_name);
3841 sip->reregister_set = TRUE;
3844 sip->registerstatus = 3;
3846 #ifdef USE_KERBEROS
3847 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3848 #endif
3849 tmp = sipmsg_find_auth_header(msg, "NTLM");
3850 #ifdef USE_KERBEROS
3851 } else {
3852 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3854 #endif
3855 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3856 fill_auth(sip, tmp, &sip->registrar);
3858 if (!sip->reauthenticate_set) {
3859 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3860 guint reauth_timeout;
3861 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3862 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3863 reauth_timeout = sip->registrar.expires - 300;
3864 } else {
3865 /* NTLM: we have to reauthenticate as our security token expires
3866 after eight hours (be five minutes early) */
3867 reauth_timeout = (8 * 3600) - 300;
3869 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3870 g_free(action_name);
3871 sip->reauthenticate_set = TRUE;
3874 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3876 epid = get_epid(sip);
3877 uuid = generateUUIDfromEPID(epid);
3878 g_free(epid);
3880 // There can be multiple Contact headers (one per location where the user is logged in) so
3881 // make sure to only get the one for this uuid
3882 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3883 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3884 if (valid_contact) {
3885 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3886 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3887 g_free(valid_contact);
3888 break;
3889 } else {
3890 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3893 g_free(uuid);
3895 g_free(sip->contact);
3896 if(gruu) {
3897 sip->contact = g_strdup_printf("<%s>", gruu);
3898 g_free(gruu);
3899 } else {
3900 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3901 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);
3903 sip->msrtc_event_categories = FALSE;
3904 sip->batched_support = FALSE;
3906 while(hdr)
3908 elem = hdr->data;
3909 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3910 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3911 sip->msrtc_event_categories = TRUE;
3912 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3914 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3915 sip->batched_support = TRUE;
3916 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3919 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3920 gchar **caps = g_strsplit(elem->value,",",0);
3921 i = 0;
3922 while (caps[i]) {
3923 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3924 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3925 i++;
3927 g_strfreev(caps);
3929 hdr = g_slist_next(hdr);
3932 if (!sip->subscribed) { //do it just once, not every re-register
3933 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3934 //sipe_options_request(sip, sip->sipdomain);
3936 entry = sip->allow_events;
3937 while (entry) {
3938 tmp = entry->data;
3939 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3940 sipe_subscribe_roaming_contacts(sip, msg);
3942 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3943 sipe_subscribe_roaming_acl(sip, msg);
3945 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3946 sipe_subscribe_roaming_self(sip, msg);
3948 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3949 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3950 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3951 sipe_subscribe_roaming_provisioning(sip, msg);
3953 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3954 sipe_subscribe_presence_wpending(sip, msg);
3956 entry = entry->next;
3958 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3959 sip->subscribed = TRUE;
3962 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3963 "timeout=", ";", NULL);
3964 if (timeout != NULL) {
3965 sscanf(timeout, "%u", &sip->keepalive_timeout);
3966 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3967 sip->keepalive_timeout);
3968 g_free(timeout);
3971 // Should we remove the transaction here?
3972 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3973 transactions_remove(sip, tc);
3975 break;
3976 case 301:
3978 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3980 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3981 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3982 gchar **tmp;
3983 gchar *hostname;
3984 int port = 0;
3985 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3986 int i = 1;
3988 tmp = g_strsplit(parts[0], ":", 0);
3989 hostname = g_strdup(tmp[0]);
3990 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3991 g_strfreev(tmp);
3993 while (parts[i]) {
3994 tmp = g_strsplit(parts[i], "=", 0);
3995 if (tmp[1]) {
3996 if (g_strcasecmp("transport", tmp[0]) == 0) {
3997 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3998 transport = SIPE_TRANSPORT_TCP;
3999 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
4000 transport = SIPE_TRANSPORT_UDP;
4004 g_strfreev(tmp);
4005 i++;
4007 g_strfreev(parts);
4009 /* Close old connection */
4010 sipe_connection_cleanup(sip);
4012 /* Create new connection */
4013 sip->transport = transport;
4014 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4015 hostname, port, TRANSPORT_DESCRIPTOR);
4016 create_connection(sip, hostname, port);
4018 g_free(redirect);
4020 break;
4021 case 401:
4022 if (sip->registerstatus != 2) {
4023 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
4024 if (sip->registrar.retries > 3) {
4025 sip->gc->wants_to_die = TRUE;
4026 purple_connection_error(sip->gc, _("Wrong Password"));
4027 return TRUE;
4029 #ifdef USE_KERBEROS
4030 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4031 #endif
4032 tmp = sipmsg_find_auth_header(msg, "NTLM");
4033 #ifdef USE_KERBEROS
4034 } else {
4035 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4037 #endif
4038 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
4039 fill_auth(sip, tmp, &sip->registrar);
4040 sip->registerstatus = 2;
4041 if (sip->account->disconnecting) {
4042 do_register_exp(sip, 0);
4043 } else {
4044 do_register(sip);
4047 break;
4048 case 403:
4050 gchar *warning = sipmsg_find_header(msg, "Warning");
4051 if (warning != NULL) {
4052 /* Example header:
4053 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4055 gchar **tmp = g_strsplit(warning, "\"", 0);
4056 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
4057 g_strfreev(tmp);
4058 } else {
4059 warning = g_strdup(_("You have been rejected by the server"));
4062 sip->gc->wants_to_die = TRUE;
4063 purple_connection_error(sip->gc, warning);
4064 g_free(warning);
4065 return TRUE;
4067 break;
4068 case 404:
4070 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4071 if (warning != NULL) {
4072 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4073 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
4074 g_free(reason);
4075 } else {
4076 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4079 sip->gc->wants_to_die = TRUE;
4080 purple_connection_error(sip->gc, warning);
4081 g_free(warning);
4082 return TRUE;
4084 break;
4085 case 503:
4087 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4088 if (warning != NULL) {
4089 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4090 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4091 g_free(reason);
4092 } else {
4093 warning = g_strdup(_("Service unavailable: no reason given"));
4096 sip->gc->wants_to_die = TRUE;
4097 purple_connection_error(sip->gc, warning);
4098 g_free(warning);
4099 return TRUE;
4101 break;
4103 return TRUE;
4106 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4108 const char *uri;
4109 xmlnode *xn_categories;
4110 xmlnode *xn_category;
4111 xmlnode *xn_node;
4112 const char *activity = NULL;
4114 xn_categories = xmlnode_from_str(data, len);
4115 uri = xmlnode_get_attrib(xn_categories, "uri");
4117 for (xn_category = xmlnode_get_child(xn_categories, "category");
4118 xn_category ;
4119 xn_category = xmlnode_get_next_twin(xn_category) )
4121 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4123 if (!strcmp(attrVar, "note"))
4125 if (uri) {
4126 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4128 if (sbuddy) {
4129 char *note;
4131 xn_node = xmlnode_get_child(xn_category, "note");
4132 if (!xn_node) continue;
4133 xn_node = xmlnode_get_child(xn_node, "body");
4134 if (!xn_node) continue;
4135 note = xmlnode_get_data(xn_node);
4136 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4137 g_free(sbuddy->annotation);
4138 sbuddy->annotation = NULL;
4139 if (note) sbuddy->annotation = g_strdup(note);
4140 g_free(note);
4145 else if(!strcmp(attrVar, "state"))
4147 char *data;
4148 int avail;
4149 xn_node = xmlnode_get_child(xn_category, "state");
4150 if (!xn_node) continue;
4151 xn_node = xmlnode_get_child(xn_node, "availability");
4152 if (!xn_node) continue;
4154 data = xmlnode_get_data(xn_node);
4155 avail = atoi(data);
4156 g_free(data);
4158 if (avail < 3000)
4159 activity = SIPE_STATUS_ID_UNKNOWN;
4160 else if (avail < 4500)
4161 activity = SIPE_STATUS_ID_AVAILABLE;
4162 else if (avail < 6000)
4163 activity = SIPE_STATUS_ID_BRB;
4164 else if (avail < 7500)
4165 activity = SIPE_STATUS_ID_ONPHONE;
4166 else if (avail < 9000)
4167 activity = SIPE_STATUS_ID_BUSY;
4168 else if (avail < 12000)
4169 activity = SIPE_STATUS_ID_DND;
4170 else if (avail < 18000)
4171 activity = SIPE_STATUS_ID_AWAY;
4172 else
4173 activity = SIPE_STATUS_ID_OFFLINE;
4176 if(activity) {
4177 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4178 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4181 xmlnode_free(xn_categories);
4184 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4186 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4187 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4188 payload->host = g_strdup(host);
4189 payload->buddies = server;
4190 sipe_subscribe_presence_batched_routed(sip, payload);
4191 sipe_subscribe_presence_batched_routed_free(payload);
4194 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4196 xmlnode *xn_list;
4197 xmlnode *xn_resource;
4198 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4199 g_free, NULL);
4200 GSList *server;
4201 gchar *host;
4203 xn_list = xmlnode_from_str(data, len);
4205 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4206 xn_resource;
4207 xn_resource = xmlnode_get_next_twin(xn_resource) )
4209 const char *uri, *state;
4210 xmlnode *xn_instance;
4212 xn_instance = xmlnode_get_child(xn_resource, "instance");
4213 if (!xn_instance) continue;
4215 uri = xmlnode_get_attrib(xn_resource, "uri");
4216 state = xmlnode_get_attrib(xn_instance, "state");
4217 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4219 if (strstr(state, "resubscribe")) {
4220 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4221 struct sipe_buddy *sbuddy;
4222 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4223 gchar *user = g_strdup(uri);
4224 host = g_strdup(poolFqdn);
4225 server = g_hash_table_lookup(servers, host);
4226 server = g_slist_append(server, user);
4227 g_hash_table_insert(servers, host, server);
4228 } else {
4229 sipe_subscribe_presence_single(sip, (void *) uri);
4231 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4232 if (sbuddy) {
4233 sbuddy->resubscribed = TRUE;
4238 /* Send out any deferred poolFqdn subscriptions */
4239 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4240 g_hash_table_destroy(servers);
4242 xmlnode_free(xn_list);
4245 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4247 const gchar *uri;
4248 gchar *getbasic;
4249 gchar *activity = NULL;
4250 xmlnode *pidf;
4251 xmlnode *basicstatus = NULL, *tuple, *status;
4252 gboolean isonline = FALSE;
4253 xmlnode *display_name_node;
4255 pidf = xmlnode_from_str(data, len);
4256 if (!pidf) {
4257 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4258 return;
4261 uri = xmlnode_get_attrib(pidf, "entity");
4263 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4265 if ((status = xmlnode_get_child(tuple, "status"))) {
4266 basicstatus = xmlnode_get_child(status, "basic");
4270 if (!basicstatus) {
4271 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4272 xmlnode_free(pidf);
4273 return;
4276 getbasic = xmlnode_get_data(basicstatus);
4277 if (!getbasic) {
4278 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4279 xmlnode_free(pidf);
4280 return;
4283 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4284 if (strstr(getbasic, "open")) {
4285 isonline = TRUE;
4287 g_free(getbasic);
4289 display_name_node = xmlnode_get_child(pidf, "display-name");
4290 // updating display name if alias was just URI
4291 if (display_name_node) {
4292 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4293 GSList *entry = buddies;
4294 PurpleBuddy *p_buddy;
4295 char * display_name = xmlnode_get_data(display_name_node);
4297 while (entry) {
4298 const char *server_alias;
4299 char *alias;
4301 p_buddy = entry->data;
4303 alias = (char *)purple_buddy_get_alias(p_buddy);
4304 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
4305 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4306 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4307 purple_blist_alias_buddy(p_buddy, display_name);
4309 g_free(alias);
4311 server_alias = purple_buddy_get_server_alias(p_buddy);
4312 if (display_name &&
4313 ( (server_alias && strcmp(display_name, server_alias))
4314 || !server_alias || strlen(server_alias) == 0 )
4316 purple_blist_server_alias_buddy(p_buddy, display_name);
4319 entry = entry->next;
4321 g_free(display_name);
4324 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4325 if ((status = xmlnode_get_child(tuple, "status"))) {
4326 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4327 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4328 activity = xmlnode_get_data(basicstatus);
4329 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4335 if (isonline) {
4336 const gchar * status_id = NULL;
4337 if (activity) {
4338 if (strstr(activity, "busy")) {
4339 status_id = SIPE_STATUS_ID_BUSY;
4340 } else if (strstr(activity, "away")) {
4341 status_id = SIPE_STATUS_ID_AWAY;
4345 if (!status_id) {
4346 status_id = SIPE_STATUS_ID_AVAILABLE;
4349 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4350 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4351 } else {
4352 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4355 g_free(activity);
4356 xmlnode_free(pidf);
4359 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4361 const char *availability;
4362 const char *activity;
4363 const char *display_name = NULL;
4364 const char *activity_name = NULL;
4365 const char *name;
4366 char *uri;
4367 int avl;
4368 int act;
4369 struct sipe_buddy *sbuddy;
4371 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4373 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4374 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4375 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4376 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4377 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4378 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4379 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4380 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4382 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4383 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4384 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4385 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4386 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4387 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4389 name = xmlnode_get_attrib(xn_presentity, "uri");
4390 uri = g_strdup_printf("sip:%s", name);
4391 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4392 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4394 // updating display name if alias was just URI
4395 if (xn_display_name) {
4396 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4397 GSList *entry = buddies;
4398 PurpleBuddy *p_buddy;
4399 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4401 while (entry) {
4402 const char *email_str, *server_alias;
4404 p_buddy = entry->data;
4406 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4407 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4408 purple_blist_alias_buddy(p_buddy, display_name);
4411 server_alias = purple_buddy_get_server_alias(p_buddy);
4412 if (display_name &&
4413 ( (server_alias && strcmp(display_name, server_alias))
4414 || !server_alias || strlen(server_alias) == 0 )
4416 purple_blist_server_alias_buddy(p_buddy, display_name);
4419 if (email) {
4420 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4421 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4422 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4426 entry = entry->next;
4430 avl = atoi(availability);
4431 act = atoi(activity);
4433 if(sip->msrtc_event_categories){
4434 if (act == 100 && avl == 0)
4435 activity_name = SIPE_STATUS_ID_OFFLINE;
4436 else if (act == 100 && avl == 300)
4437 activity_name = SIPE_STATUS_ID_AWAY;
4438 else if (act == 300 && avl == 300)
4439 activity_name = SIPE_STATUS_ID_BRB;
4440 else if (act == 400 && avl == 300)
4441 activity_name = SIPE_STATUS_ID_AVAILABLE;
4442 else if (act == 500 && act == 300)
4443 activity_name = SIPE_STATUS_ID_ONPHONE;
4444 else if (act == 600 && avl == 300)
4445 activity_name = SIPE_STATUS_ID_BUSY;
4446 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4447 if(avail){ //Check for LegacyInterop elements
4448 avl = atoi(avail);
4449 if(avl == 18500)
4450 activity_name = SIPE_STATUS_ID_OFFLINE;
4451 else if (avl == 3500)
4452 activity_name = SIPE_STATUS_ID_AVAILABLE;
4453 else if (avl == 15500)
4454 activity_name = SIPE_STATUS_ID_AWAY;
4455 else if (avl == 6500)
4456 activity_name = SIPE_STATUS_ID_BUSY;
4457 else if (avl == 12500)
4458 activity_name = SIPE_STATUS_ID_BRB;
4463 if(activity_name == NULL){
4464 if (act <= 100)
4465 activity_name = SIPE_STATUS_ID_AWAY;
4466 else if (act <= 150)
4467 activity_name = SIPE_STATUS_ID_LUNCH;
4468 else if (act <= 300)
4469 activity_name = SIPE_STATUS_ID_BRB;
4470 else if (act <= 400)
4471 activity_name = SIPE_STATUS_ID_AVAILABLE;
4472 else if (act <= 500)
4473 activity_name = SIPE_STATUS_ID_ONPHONE;
4474 else if (act <= 600)
4475 activity_name = SIPE_STATUS_ID_BUSY;
4476 else
4477 activity_name = SIPE_STATUS_ID_AVAILABLE;
4479 if (avl == 0)
4480 activity_name = SIPE_STATUS_ID_OFFLINE;
4483 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4484 if (sbuddy)
4486 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4487 sbuddy->annotation = NULL;
4488 if (note) { sbuddy->annotation = g_strdup(note); }
4490 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4491 sbuddy->device_name = NULL;
4492 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4495 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4496 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4497 g_free(note);
4498 xmlnode_free(xn_presentity);
4499 g_free(uri);
4502 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4504 char *ctype = sipmsg_find_header(msg, "Content-Type");
4506 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4508 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4509 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4511 const char *content = msg->body;
4512 unsigned length = msg->bodylen;
4513 PurpleMimeDocument *mime = NULL;
4515 if (strstr(ctype, "multipart"))
4517 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4518 const char *content_type;
4519 GList* parts;
4520 mime = purple_mime_document_parse(doc);
4521 parts = purple_mime_document_get_parts(mime);
4522 while(parts) {
4523 content = purple_mime_part_get_data(parts->data);
4524 length = purple_mime_part_get_length(parts->data);
4525 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4526 if(content_type && strstr(content_type,"application/rlmi+xml"))
4528 process_incoming_notify_rlmi_resub(sip, content, length);
4530 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4532 process_incoming_notify_msrtc(sip, content, length);
4534 else
4536 process_incoming_notify_rlmi(sip, content, length);
4538 parts = parts->next;
4540 g_free(doc);
4542 if (mime)
4544 purple_mime_document_free(mime);
4547 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4549 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4551 else if(strstr(ctype, "application/rlmi+xml"))
4553 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4556 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4558 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4560 else
4562 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4566 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4568 char *ctype = sipmsg_find_header(msg, "Content-Type");
4569 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4571 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4573 if (ctype &&
4574 strstr(ctype, "multipart") &&
4575 (strstr(ctype, "application/rlmi+xml") ||
4576 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4577 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4578 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4579 GList *parts = purple_mime_document_get_parts(mime);
4580 GSList *buddies = NULL;
4581 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4583 while (parts) {
4584 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4585 purple_mime_part_get_length(parts->data));
4586 gchar *uri = g_strdup(xmlnode_get_attrib(xml, "uri"));
4588 if (strstr(uri, "sip:") == NULL) {
4589 gchar *tmp = uri;
4590 uri = g_strdup_printf("sip:%s", tmp);
4591 g_free(tmp);
4593 buddies = g_slist_append(buddies, uri);
4594 xmlnode_free(xml);
4596 parts = parts->next;
4598 g_free(doc);
4599 if (mime) purple_mime_document_free(mime);
4601 payload->host = who;
4602 payload->buddies = buddies;
4603 sipe_schedule_action(action_name, timeout,
4604 sipe_subscribe_presence_batched_routed,
4605 sipe_subscribe_presence_batched_routed_free,
4606 sip, payload);
4607 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4609 } else {
4610 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4611 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4613 g_free(action_name);
4617 * Dispatcher for all incoming subscription information
4618 * whether it comes from NOTIFY, BENOTIFY requests or
4619 * piggy-backed to subscription's OK responce.
4621 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4622 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4624 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4626 gchar *event = sipmsg_find_header(msg, "Event");
4627 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4628 int timeout = 0;
4630 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4631 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
4633 if (!request)
4635 const gchar *expires_header;
4636 expires_header = sipmsg_find_header(msg, "Expires");
4637 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4638 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4639 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4642 if (!subscription_state || strstr(subscription_state, "active"))
4644 if (event && !g_ascii_strcasecmp(event, "presence"))
4646 sipe_process_presence(sip, msg);
4648 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4650 sipe_process_roaming_contacts(sip, msg, NULL);
4652 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
4654 sipe_process_roaming_self(sip, msg);
4656 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4658 sipe_process_roaming_acl(sip, msg);
4660 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4662 sipe_process_presence_wpending(sip, msg);
4664 else
4666 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
4670 //The server sends a (BE)NOTIFY with the status 'terminated'
4671 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4672 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4673 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4674 g_free(from);
4677 if (timeout && event) {// For LSC 2005 and OCS 2007
4678 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4679 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4681 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4682 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4683 g_free(action_name);
4685 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4686 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4688 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4689 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4690 g_free(action_name);
4692 else*/
4693 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4694 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4696 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4697 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4698 g_free(action_name);
4700 else if (!g_ascii_strcasecmp(event, "presence") &&
4701 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4703 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4704 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4705 if(sip->batched_support) {
4706 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
4707 if(!g_ascii_strcasecmp(who, my_self)){
4708 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4709 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4710 g_free(who); /* unused */
4712 else {
4713 sipe_process_presence_timeout(sip, msg, who, timeout);
4715 g_free(my_self);
4717 else {
4718 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4719 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4721 g_free(action_name);
4722 /* "who" will be freed by the action we just scheduled */
4726 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4728 sipe_process_registration_notify(sip, msg);
4731 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4732 if (request && !benotify)
4734 sipmsg_remove_header(msg, "Expires");
4735 sipmsg_remove_header(msg, "subscription-state");
4736 sipmsg_remove_header(msg, "Event");
4737 sipmsg_remove_header(msg, "Require");
4738 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4743 * unused. Needed?
4745 static gchar* gen_xpidf(struct sipe_account_data *sip)
4747 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4748 "<presence>\r\n"
4749 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4750 "<display name=\"sip:%s\"/>\r\n"
4751 "<atom id=\"1234\">\r\n"
4752 "<address uri=\"sip:%s\">\r\n"
4753 "<status status=\"%s\"/>\r\n"
4754 "</address>\r\n"
4755 "</atom>\r\n"
4756 "</presence>\r\n",
4757 sip->username,
4758 sip->username,
4759 sip->username,
4760 sip->status);
4761 return doc;
4766 static gchar* gen_pidf(struct sipe_account_data *sip)
4768 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4769 "<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"
4770 "<tuple id=\"0\">\r\n"
4771 "<status>\r\n"
4772 "<basic>open</basic>\r\n"
4773 "<ep:activities>\r\n"
4774 " <ep:activity>%s</ep:activity>\r\n"
4775 "</ep:activities>"
4776 "</status>\r\n"
4777 "</tuple>\r\n"
4778 "<ci:display-name>%s</ci:display-name>\r\n"
4779 "</presence>",
4780 sip->username,
4781 sip->status,
4782 sip->username);
4783 return doc;
4787 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4789 int availability = 300; // online
4790 int activity = 400; // Available
4791 gchar *name;
4792 gchar *body;
4793 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4794 activity = 100;
4795 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4796 activity = 150;
4797 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4798 activity = 300;
4799 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4800 activity = 400;
4801 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4802 activity = 500;
4803 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4804 activity = 600;
4805 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4806 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4807 availability = 0; // offline
4808 activity = 100;
4809 } else {
4810 activity = 400; // available
4813 name = g_strdup_printf("sip: sip:%s", sip->username);
4814 //@TODO: send user data - state; add hostname in upper case
4815 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4816 send_soap_request_with_cb(sip, body, NULL , NULL);
4817 g_free(name);
4818 g_free(body);
4821 static gboolean
4822 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4824 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4825 if (msg->response == 200) {
4826 sip->status_version = 0;
4827 send_presence_status(sip);
4829 return TRUE;
4832 static gboolean
4833 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4835 if (msg->response == 409) {
4836 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4837 // TODO need to parse the version #'s?
4838 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4839 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4840 gchar *tmp;
4841 gchar *hdr;
4843 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4845 tmp = get_contact(sip);
4846 hdr = g_strdup_printf("Contact: %s\r\n"
4847 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4849 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4851 g_free(tmp);
4852 g_free(hdr);
4853 g_free(uri);
4854 g_free(doc);
4856 return TRUE;
4859 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4861 int code;
4862 gchar *uri;
4863 gchar *doc;
4864 gchar *tmp;
4865 gchar *hdr;
4866 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4867 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4868 code = 12000;
4869 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4870 code = 9000;
4871 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4872 code = 7500;
4873 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4874 code = 6000;
4875 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4876 code = 4500;
4877 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4878 code = 3000;
4879 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4880 code = 0;
4881 } else {
4882 // Offline or invisible
4883 code = 18000;
4886 uri = g_strdup_printf("sip:%s", sip->username);
4887 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4888 sip->status_version, code,
4889 sip->status_version, code,
4890 sip->status_version, note ? note : "",
4891 sip->status_version, note ? note : "",
4892 sip->status_version, note ? note : ""
4894 sip->status_version++;
4896 tmp = get_contact(sip);
4897 hdr = g_strdup_printf("Contact: %s\r\n"
4898 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4900 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4902 g_free(tmp);
4903 g_free(hdr);
4904 g_free(uri);
4905 g_free(doc);
4908 static void send_presence_status(struct sipe_account_data *sip)
4910 PurpleStatus * status = purple_account_get_active_status(sip->account);
4911 const gchar *note;
4912 if (!status) return;
4914 note = purple_status_get_attr_string(status, "message");
4916 if(sip->msrtc_event_categories){
4917 send_presence_category_publish(sip, note);
4918 } else {
4919 send_presence_soap(sip, note);
4923 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4925 gboolean found = FALSE;
4926 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4927 if (msg->response == 0) { /* request */
4928 if (!strcmp(msg->method, "MESSAGE")) {
4929 process_incoming_message(sip, msg);
4930 found = TRUE;
4931 } else if (!strcmp(msg->method, "NOTIFY")) {
4932 purple_debug_info("sipe","send->process_incoming_notify\n");
4933 process_incoming_notify(sip, msg, TRUE, FALSE);
4934 found = TRUE;
4935 } else if (!strcmp(msg->method, "BENOTIFY")) {
4936 purple_debug_info("sipe","send->process_incoming_benotify\n");
4937 process_incoming_notify(sip, msg, TRUE, TRUE);
4938 found = TRUE;
4939 } else if (!strcmp(msg->method, "INVITE")) {
4940 process_incoming_invite(sip, msg);
4941 found = TRUE;
4942 } else if (!strcmp(msg->method, "REFER")) {
4943 process_incoming_refer(sip, msg);
4944 found = TRUE;
4945 } else if (!strcmp(msg->method, "OPTIONS")) {
4946 process_incoming_options(sip, msg);
4947 found = TRUE;
4948 } else if (!strcmp(msg->method, "INFO")) {
4949 process_incoming_info(sip, msg);
4950 found = TRUE;
4951 } else if (!strcmp(msg->method, "ACK")) {
4952 // ACK's don't need any response
4953 found = TRUE;
4954 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4955 // LCS 2005 sends us these - just respond 200 OK
4956 found = TRUE;
4957 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4958 } else if (!strcmp(msg->method, "BYE")) {
4959 process_incoming_bye(sip, msg);
4960 found = TRUE;
4961 } else {
4962 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4964 } else { /* response */
4965 struct transaction *trans = transactions_find(sip, msg);
4966 if (trans) {
4967 if (msg->response == 407) {
4968 gchar *resend, *auth, *ptmp;
4970 if (sip->proxy.retries > 30) return;
4971 sip->proxy.retries++;
4972 /* do proxy authentication */
4974 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4976 fill_auth(sip, ptmp, &sip->proxy);
4977 auth = auth_header(sip, &sip->proxy, trans->msg);
4978 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4979 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4980 g_free(auth);
4981 resend = sipmsg_to_string(trans->msg);
4982 /* resend request */
4983 sendout_pkt(sip->gc, resend);
4984 g_free(resend);
4985 } else {
4986 if (msg->response == 100 || msg->response == 180) {
4987 /* ignore provisional response */
4988 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4989 } else {
4990 sip->proxy.retries = 0;
4991 if (!strcmp(trans->msg->method, "REGISTER")) {
4992 if (msg->response == 401)
4994 sip->registrar.retries++;
4996 else
4998 sip->registrar.retries = 0;
5000 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
5001 } else {
5002 if (msg->response == 401) {
5003 gchar *resend, *auth, *ptmp;
5005 if (sip->registrar.retries > 4) return;
5006 sip->registrar.retries++;
5008 #ifdef USE_KERBEROS
5009 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5010 #endif
5011 ptmp = sipmsg_find_auth_header(msg, "NTLM");
5012 #ifdef USE_KERBEROS
5013 } else {
5014 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
5016 #endif
5018 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
5020 fill_auth(sip, ptmp, &sip->registrar);
5021 auth = auth_header(sip, &sip->registrar, trans->msg);
5022 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
5023 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
5025 //sipmsg_remove_header(trans->msg, "Authorization");
5026 //sipmsg_add_header(trans->msg, "Authorization", auth);
5027 g_free(auth);
5028 resend = sipmsg_to_string(trans->msg);
5029 /* resend request */
5030 sendout_pkt(sip->gc, resend);
5031 g_free(resend);
5035 if (trans->callback) {
5036 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
5037 /* call the callback to process response*/
5038 (trans->callback)(sip, msg, trans);
5040 /* Not sure if this is needed or what needs to be done
5041 but transactions seem to be removed prematurely so
5042 this only removes them if the response is 200 OK */
5043 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
5044 /*Has a bug and it's unneccesary*/
5045 /*transactions_remove(sip, trans);*/
5049 found = TRUE;
5050 } else {
5051 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
5054 if (!found) {
5055 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
5059 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
5061 char *cur;
5062 char *dummy;
5063 struct sipmsg *msg;
5064 int restlen;
5065 cur = conn->inbuf;
5067 /* according to the RFC remove CRLF at the beginning */
5068 while (*cur == '\r' || *cur == '\n') {
5069 cur++;
5071 if (cur != conn->inbuf) {
5072 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
5073 conn->inbufused = strlen(conn->inbuf);
5076 /* Received a full Header? */
5077 sip->processing_input = TRUE;
5078 while (sip->processing_input &&
5079 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
5080 time_t currtime = time(NULL);
5081 cur += 2;
5082 cur[0] = '\0';
5083 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
5084 msg = sipmsg_parse_header(conn->inbuf);
5085 cur[0] = '\r';
5086 cur += 2;
5087 restlen = conn->inbufused - (cur - conn->inbuf);
5088 if (restlen >= msg->bodylen) {
5089 dummy = g_malloc(msg->bodylen + 1);
5090 memcpy(dummy, cur, msg->bodylen);
5091 dummy[msg->bodylen] = '\0';
5092 msg->body = dummy;
5093 cur += msg->bodylen;
5094 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
5095 conn->inbufused = strlen(conn->inbuf);
5096 } else {
5097 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5098 restlen, msg->bodylen, (int)strlen(conn->inbuf));
5099 sipmsg_free(msg);
5100 return;
5103 /*if (msg->body) {
5104 purple_debug_info("sipe", "body:\n%s", msg->body);
5107 // Verify the signature before processing it
5108 if (sip->registrar.gssapi_context) {
5109 struct sipmsg_breakdown msgbd;
5110 gchar *signature_input_str;
5111 gchar *rspauth;
5112 msgbd.msg = msg;
5113 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5114 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5116 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5118 if (rspauth != NULL) {
5119 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5120 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5121 process_input_message(sip, msg);
5122 } else {
5123 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5124 purple_connection_error(sip->gc, _("Invalid message signature received"));
5125 sip->gc->wants_to_die = TRUE;
5127 } else if (msg->response == 401) {
5128 purple_connection_error(sip->gc, _("Wrong Password"));
5129 sip->gc->wants_to_die = TRUE;
5131 g_free(signature_input_str);
5133 g_free(rspauth);
5134 sipmsg_breakdown_free(&msgbd);
5135 } else {
5136 process_input_message(sip, msg);
5139 sipmsg_free(msg);
5143 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
5145 PurpleConnection *gc = data;
5146 struct sipe_account_data *sip = gc->proto_data;
5147 struct sipmsg *msg;
5148 int len;
5149 time_t currtime;
5151 static char buffer[65536];
5152 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5153 buffer[len] = '\0';
5154 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5155 msg = sipmsg_parse_msg(buffer);
5156 if (msg) process_input_message(sip, msg);
5160 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5162 struct sipe_account_data *sip = gc->proto_data;
5163 PurpleSslConnection *gsc = sip->gsc;
5165 purple_debug_error("sipe", "%s",debug);
5166 purple_connection_error(gc, msg);
5168 /* Invalidate this connection. Next send will open a new one */
5169 if (gsc) {
5170 connection_remove(sip, gsc->fd);
5171 purple_ssl_close(gsc);
5173 sip->gsc = NULL;
5174 sip->fd = -1;
5177 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5179 PurpleConnection *gc = data;
5180 struct sipe_account_data *sip;
5181 struct sip_connection *conn;
5182 int readlen, len;
5183 gboolean firstread = TRUE;
5185 /* NOTE: This check *IS* necessary */
5186 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5187 purple_ssl_close(gsc);
5188 return;
5191 sip = gc->proto_data;
5192 conn = connection_find(sip, gsc->fd);
5193 if (conn == NULL) {
5194 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5195 gc->wants_to_die = TRUE;
5196 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5197 return;
5200 /* Read all available data from the SSL connection */
5201 do {
5202 /* Increase input buffer size as needed */
5203 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5204 conn->inbuflen += SIMPLE_BUF_INC;
5205 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5206 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5209 /* Try to read as much as there is space left in the buffer */
5210 readlen = conn->inbuflen - conn->inbufused - 1;
5211 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5213 if (len < 0 && errno == EAGAIN) {
5214 /* Try again later */
5215 return;
5216 } else if (len < 0) {
5217 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5218 return;
5219 } else if (firstread && (len == 0)) {
5220 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5221 return;
5224 conn->inbufused += len;
5225 firstread = FALSE;
5227 /* Equivalence indicates that there is possibly more data to read */
5228 } while (len == readlen);
5230 conn->inbuf[conn->inbufused] = '\0';
5231 process_input(sip, conn);
5235 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
5237 PurpleConnection *gc = data;
5238 struct sipe_account_data *sip = gc->proto_data;
5239 int len;
5240 struct sip_connection *conn = connection_find(sip, source);
5241 if (!conn) {
5242 purple_debug_error("sipe", "Connection not found!\n");
5243 return;
5246 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5247 conn->inbuflen += SIMPLE_BUF_INC;
5248 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5251 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5253 if (len < 0 && errno == EAGAIN)
5254 return;
5255 else if (len <= 0) {
5256 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5257 connection_remove(sip, source);
5258 if (sip->fd == source) sip->fd = -1;
5259 return;
5262 conn->inbufused += len;
5263 conn->inbuf[conn->inbufused] = '\0';
5265 process_input(sip, conn);
5268 /* Callback for new connections on incoming TCP port */
5269 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
5271 PurpleConnection *gc = data;
5272 struct sipe_account_data *sip = gc->proto_data;
5273 struct sip_connection *conn;
5275 int newfd = accept(source, NULL, NULL);
5277 conn = connection_create(sip, newfd);
5279 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5282 static void login_cb(gpointer data, gint source, const gchar *error_message)
5284 PurpleConnection *gc = data;
5285 struct sipe_account_data *sip;
5286 struct sip_connection *conn;
5288 if (!PURPLE_CONNECTION_IS_VALID(gc))
5290 if (source >= 0)
5291 close(source);
5292 return;
5295 if (source < 0) {
5296 purple_connection_error(gc, _("Could not connect"));
5297 return;
5300 sip = gc->proto_data;
5301 sip->fd = source;
5302 sip->last_keepalive = time(NULL);
5304 conn = connection_create(sip, source);
5306 do_register(sip);
5308 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5311 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5313 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5314 if (sip == NULL) return;
5316 do_register(sip);
5319 static guint sipe_ht_hash_nick(const char *nick)
5321 char *lc = g_utf8_strdown(nick, -1);
5322 guint bucket = g_str_hash(lc);
5323 g_free(lc);
5325 return bucket;
5328 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5330 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5333 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5335 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5337 sip->listen_data = NULL;
5339 if (listenfd == -1) {
5340 purple_connection_error(sip->gc, _("Could not create listen socket"));
5341 return;
5344 sip->fd = listenfd;
5346 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5347 sip->listenfd = sip->fd;
5349 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5351 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5352 do_register(sip);
5355 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
5357 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5358 int addr_size;
5360 sip->query_data = NULL;
5362 if (!hosts || !hosts->data) {
5363 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5364 return;
5367 addr_size = GPOINTER_TO_INT(hosts->data);
5368 hosts = g_slist_remove(hosts, hosts->data);
5369 memcpy(&(sip->serveraddr), hosts->data, addr_size);
5370 g_free(hosts->data);
5371 hosts = g_slist_remove(hosts, hosts->data);
5372 while (hosts) {
5373 hosts = g_slist_remove(hosts, hosts->data);
5374 g_free(hosts->data);
5375 hosts = g_slist_remove(hosts, hosts->data);
5378 /* create socket for incoming connections */
5379 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5380 sipe_udp_host_resolved_listen_cb, sip);
5381 if (sip->listen_data == NULL) {
5382 purple_connection_error(sip->gc, _("Could not create listen socket"));
5383 return;
5387 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
5388 gpointer data)
5390 PurpleConnection *gc = data;
5391 struct sipe_account_data *sip;
5393 /* If the connection is already disconnected, we don't need to do anything else */
5394 if (!PURPLE_CONNECTION_IS_VALID(gc))
5395 return;
5397 sip = gc->proto_data;
5398 sip->fd = -1;
5399 sip->gsc = NULL;
5401 switch(error) {
5402 case PURPLE_SSL_CONNECT_FAILED:
5403 purple_connection_error(gc, _("Connection Failed"));
5404 break;
5405 case PURPLE_SSL_HANDSHAKE_FAILED:
5406 purple_connection_error(gc, _("SSL Handshake Failed"));
5407 break;
5408 case PURPLE_SSL_CERTIFICATE_INVALID:
5409 purple_connection_error(gc, _("SSL Certificate Invalid"));
5410 break;
5414 static void
5415 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5417 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5418 PurpleProxyConnectData *connect_data;
5420 sip->listen_data = NULL;
5422 sip->listenfd = listenfd;
5423 if (sip->listenfd == -1) {
5424 purple_connection_error(sip->gc, _("Could not create listen socket"));
5425 return;
5428 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5429 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5430 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5431 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5432 sipe_newconn_cb, sip->gc);
5433 purple_debug_info("sipe", "connecting to %s port %d\n",
5434 sip->realhostname, sip->realport);
5435 /* open tcp connection to the server */
5436 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5437 sip->realport, login_cb, sip->gc);
5439 if (connect_data == NULL) {
5440 purple_connection_error(sip->gc, _("Couldn't create socket"));
5445 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5447 PurpleAccount *account = sip->account;
5448 PurpleConnection *gc = sip->gc;
5450 if (purple_account_get_bool(account, "useport", FALSE)) {
5451 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
5452 port = purple_account_get_int(account, "port", 0);
5453 } else {
5454 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5457 sip->realhostname = hostname;
5458 sip->realport = port;
5460 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5461 hostname, port);
5463 /* TODO: is there a good default grow size? */
5464 if (sip->transport != SIPE_TRANSPORT_UDP)
5465 sip->txbuf = purple_circ_buffer_new(0);
5467 if (sip->transport == SIPE_TRANSPORT_TLS) {
5468 /* SSL case */
5469 if (!purple_ssl_is_supported()) {
5470 gc->wants_to_die = TRUE;
5471 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5472 return;
5475 purple_debug_info("sipe", "using SSL\n");
5477 sip->gsc = purple_ssl_connect(account, hostname, port,
5478 login_cb_ssl, sipe_ssl_connect_failure, gc);
5479 if (sip->gsc == NULL) {
5480 purple_connection_error(gc, _("Could not create SSL context"));
5481 return;
5483 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5484 /* UDP case */
5485 purple_debug_info("sipe", "using UDP\n");
5487 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5488 if (sip->query_data == NULL) {
5489 purple_connection_error(gc, _("Could not resolve hostname"));
5491 } else {
5492 /* TCP case */
5493 purple_debug_info("sipe", "using TCP\n");
5494 /* create socket for incoming connections */
5495 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5496 sipe_tcp_connect_listen_cb, sip);
5497 if (sip->listen_data == NULL) {
5498 purple_connection_error(gc, _("Could not create listen socket"));
5499 return;
5504 /* Service list for autodection */
5505 static const struct sipe_service_data service_autodetect[] = {
5506 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5507 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5508 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5509 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5510 { NULL, NULL, 0 }
5513 /* Service list for SSL/TLS */
5514 static const struct sipe_service_data service_tls[] = {
5515 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5516 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5517 { NULL, NULL, 0 }
5520 /* Service list for TCP */
5521 static const struct sipe_service_data service_tcp[] = {
5522 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5523 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5524 { NULL, NULL, 0 }
5527 /* Service list for UDP */
5528 static const struct sipe_service_data service_udp[] = {
5529 { "sip", "udp", SIPE_TRANSPORT_UDP },
5530 { NULL, NULL, 0 }
5533 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5534 static void resolve_next_service(struct sipe_account_data *sip,
5535 const struct sipe_service_data *start)
5537 if (start) {
5538 sip->service_data = start;
5539 } else {
5540 sip->service_data++;
5541 if (sip->service_data->service == NULL) {
5542 gchar *hostname;
5543 /* Try connecting to the SIP hostname directly */
5544 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5545 if (sip->auto_transport) {
5546 // If SSL is supported, default to using it; OCS servers aren't configured
5547 // by default to accept TCP
5548 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5549 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5550 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5553 hostname = g_strdup(sip->sipdomain);
5554 create_connection(sip, hostname, 0);
5555 return;
5559 /* Try to resolve next service */
5560 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5561 sip->service_data->transport,
5562 sip->sipdomain,
5563 srvresolved, sip);
5566 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5568 struct sipe_account_data *sip = data;
5570 sip->srv_query_data = NULL;
5572 /* find the host to connect to */
5573 if (results) {
5574 gchar *hostname = g_strdup(resp->hostname);
5575 int port = resp->port;
5576 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5577 hostname, port);
5578 g_free(resp);
5580 sip->transport = sip->service_data->type;
5582 create_connection(sip, hostname, port);
5583 } else {
5584 resolve_next_service(sip, NULL);
5588 static void sipe_login(PurpleAccount *account)
5590 PurpleConnection *gc;
5591 struct sipe_account_data *sip;
5592 gchar **signinname_login, **userserver, **domain_user;
5593 const char *transport;
5595 const char *username = purple_account_get_username(account);
5596 gc = purple_account_get_connection(account);
5598 if (strpbrk(username, "\t\v\r\n") != NULL) {
5599 gc->wants_to_die = TRUE;
5600 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5601 return;
5604 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5605 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5606 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5607 sip->gc = gc;
5608 sip->account = account;
5609 sip->reregister_set = FALSE;
5610 sip->reauthenticate_set = FALSE;
5611 sip->subscribed = FALSE;
5612 sip->subscribed_buddies = FALSE;
5614 signinname_login = g_strsplit(username, ",", 2);
5616 userserver = g_strsplit(signinname_login[0], "@", 2);
5617 purple_connection_set_display_name(gc, userserver[0]);
5618 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
5619 sip->sipdomain = g_strdup(userserver[1]);
5621 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
5622 gc->wants_to_die = TRUE;
5623 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5624 return;
5627 domain_user = g_strsplit(signinname_login[1], "\\", 2);
5628 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
5629 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
5631 sip->password = g_strdup(purple_connection_get_password(gc));
5633 g_strfreev(userserver);
5634 g_strfreev(domain_user);
5635 g_strfreev(signinname_login);
5637 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5639 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5641 /* TODO: Set the status correctly. */
5642 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5644 transport = purple_account_get_string(account, "transport", "auto");
5645 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
5646 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
5647 SIPE_TRANSPORT_UDP;
5649 if (purple_account_get_bool(account, "useproxy", FALSE)) {
5650 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
5651 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
5652 } else if (strcmp(transport, "auto") == 0) {
5653 sip->auto_transport = TRUE;
5654 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5655 } else if (strcmp(transport, "tls") == 0) {
5656 resolve_next_service(sip, service_tls);
5657 } else if (strcmp(transport, "tcp") == 0) {
5658 resolve_next_service(sip, service_tcp);
5659 } else {
5660 resolve_next_service(sip, service_udp);
5664 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5666 connection_free_all(sip);
5668 g_free(sip->epid);
5669 sip->epid = NULL;
5671 if (sip->query_data != NULL)
5672 purple_dnsquery_destroy(sip->query_data);
5673 sip->query_data = NULL;
5675 if (sip->srv_query_data != NULL)
5676 purple_srv_cancel(sip->srv_query_data);
5677 sip->srv_query_data = NULL;
5679 if (sip->listen_data != NULL)
5680 purple_network_listen_cancel(sip->listen_data);
5681 sip->listen_data = NULL;
5683 if (sip->gsc != NULL)
5684 purple_ssl_close(sip->gsc);
5685 sip->gsc = NULL;
5687 sipe_auth_free(&sip->registrar);
5688 sipe_auth_free(&sip->proxy);
5690 if (sip->txbuf)
5691 purple_circ_buffer_destroy(sip->txbuf);
5692 sip->txbuf = NULL;
5694 g_free(sip->realhostname);
5695 sip->realhostname = NULL;
5697 if (sip->listenpa)
5698 purple_input_remove(sip->listenpa);
5699 sip->listenpa = 0;
5700 if (sip->tx_handler)
5701 purple_input_remove(sip->tx_handler);
5702 sip->tx_handler = 0;
5703 if (sip->resendtimeout)
5704 purple_timeout_remove(sip->resendtimeout);
5705 sip->resendtimeout = 0;
5706 if (sip->timeouts) {
5707 GSList *entry = sip->timeouts;
5708 while (entry) {
5709 struct scheduled_action *sched_action = entry->data;
5710 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5711 purple_timeout_remove(sched_action->timeout_handler);
5712 (*sched_action->destroy)(sched_action->payload);
5713 g_free(sched_action->name);
5714 g_free(sched_action);
5715 entry = entry->next;
5718 g_slist_free(sip->timeouts);
5720 if (sip->allow_events) {
5721 GSList *entry = sip->allow_events;
5722 while (entry) {
5723 g_free(entry->data);
5724 entry = entry->next;
5727 g_slist_free(sip->allow_events);
5729 if (sip->contact)
5730 g_free(sip->contact);
5731 sip->contact = NULL;
5732 if (sip->regcallid)
5733 g_free(sip->regcallid);
5734 sip->regcallid = NULL;
5736 sip->fd = -1;
5737 sip->processing_input = FALSE;
5741 * A callback for g_hash_table_foreach_remove
5743 static void sipe_buddy_remove(gpointer key, struct sipe_buddy *buddy, gpointer user_data)
5745 sipe_free_buddy(buddy);
5748 static void sipe_close(PurpleConnection *gc)
5750 struct sipe_account_data *sip = gc->proto_data;
5752 if (sip) {
5753 /* leave all conversations */
5754 im_session_close_all(sip);
5756 /* unregister */
5757 do_register_exp(sip, 0);
5759 sipe_connection_cleanup(sip);
5760 g_free(sip->sipdomain);
5761 g_free(sip->username);
5762 g_free(sip->password);
5763 g_free(sip->authdomain);
5764 g_free(sip->authuser);
5765 g_free(sip->status);
5767 g_hash_table_foreach_remove(sip->buddies, (GHRFunc) sipe_buddy_remove, NULL);
5768 g_hash_table_destroy(sip->buddies);
5770 g_free(gc->proto_data);
5771 gc->proto_data = NULL;
5774 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
5776 PurpleAccount *acct = purple_connection_get_account(gc);
5777 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
5778 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5779 if (conv == NULL)
5780 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5781 purple_conversation_present(conv);
5782 g_free(id);
5785 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5788 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5789 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
5792 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5794 PurpleNotifySearchResults *results;
5795 PurpleNotifySearchColumn *column;
5796 xmlnode *searchResults;
5797 xmlnode *mrow;
5798 int match_count = 0;
5799 gboolean more = FALSE;
5800 gchar *secondary;
5802 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5804 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5805 if (!searchResults) {
5806 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5807 return FALSE;
5810 results = purple_notify_searchresults_new();
5812 if (results == NULL) {
5813 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5814 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5816 xmlnode_free(searchResults);
5817 return FALSE;
5820 column = purple_notify_searchresults_column_new(_("User Name"));
5821 purple_notify_searchresults_column_add(results, column);
5823 column = purple_notify_searchresults_column_new(_("Name"));
5824 purple_notify_searchresults_column_add(results, column);
5826 column = purple_notify_searchresults_column_new(_("Company"));
5827 purple_notify_searchresults_column_add(results, column);
5829 column = purple_notify_searchresults_column_new(_("Country"));
5830 purple_notify_searchresults_column_add(results, column);
5832 column = purple_notify_searchresults_column_new(_("Email"));
5833 purple_notify_searchresults_column_add(results, column);
5835 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5836 GList *row = NULL;
5838 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5839 row = g_list_append(row, g_strdup(uri_parts[1]));
5840 g_strfreev(uri_parts);
5842 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5843 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5844 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5845 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5847 purple_notify_searchresults_row_add(results, row);
5848 match_count++;
5851 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5852 char *data = xmlnode_get_data_unescaped(mrow);
5853 more = (g_strcasecmp(data, "true") == 0);
5854 g_free(data);
5857 secondary = g_strdup_printf(
5858 dngettext(GETTEXT_PACKAGE,
5859 "Found %d contact%s:",
5860 "Found %d contacts%s:", match_count),
5861 match_count, more ? _(" (more matched your query)") : "");
5863 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5864 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5865 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5867 g_free(secondary);
5868 xmlnode_free(searchResults);
5869 return TRUE;
5872 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5874 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5875 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5876 unsigned i = 0;
5878 do {
5879 PurpleRequestField *field = entries->data;
5880 const char *id = purple_request_field_get_id(field);
5881 const char *value = purple_request_field_string_get_value(field);
5883 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5885 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5886 } while ((entries = g_list_next(entries)) != NULL);
5887 attrs[i] = NULL;
5889 if (i > 0) {
5890 gchar *query = g_strjoinv(NULL, attrs);
5891 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5892 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5893 send_soap_request_with_cb(gc->proto_data, body,
5894 (TransCallback) process_search_contact_response, NULL);
5895 g_free(body);
5896 g_free(query);
5899 g_strfreev(attrs);
5902 static void sipe_show_find_contact(PurplePluginAction *action)
5904 PurpleConnection *gc = (PurpleConnection *) action->context;
5905 PurpleRequestFields *fields;
5906 PurpleRequestFieldGroup *group;
5907 PurpleRequestField *field;
5909 fields = purple_request_fields_new();
5910 group = purple_request_field_group_new(NULL);
5911 purple_request_fields_add_group(fields, group);
5913 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5914 purple_request_field_group_add_field(group, field);
5915 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5916 purple_request_field_group_add_field(group, field);
5917 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5918 purple_request_field_group_add_field(group, field);
5919 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5920 purple_request_field_group_add_field(group, field);
5922 purple_request_fields(gc,
5923 _("Search"),
5924 _("Search for a Contact"),
5925 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5926 fields,
5927 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5928 _("_Cancel"), NULL,
5929 purple_connection_get_account(gc), NULL, NULL, gc);
5932 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5934 GList *menu = NULL;
5935 PurplePluginAction *act;
5937 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5938 menu = g_list_prepend(menu, act);
5940 menu = g_list_reverse(menu);
5942 return menu;
5945 static void dummy_permit_deny(PurpleConnection *gc)
5949 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5951 return TRUE;
5955 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5957 return TRUE;
5961 static char *sipe_status_text(PurpleBuddy *buddy)
5963 struct sipe_account_data *sip;
5964 struct sipe_buddy *sbuddy;
5965 char *text = NULL;
5967 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5968 if (sip) //happens on pidgin exit
5970 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5971 if (sbuddy && sbuddy->annotation)
5973 text = g_strdup(sbuddy->annotation);
5977 return text;
5980 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5982 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5983 const PurpleStatus *status = purple_presence_get_active_status(presence);
5984 struct sipe_account_data *sip;
5985 struct sipe_buddy *sbuddy;
5986 char *annotation = NULL;
5988 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5989 if (sip) //happens on pidgin exit
5991 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5992 if (sbuddy)
5994 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5998 //Layout
5999 if (purple_presence_is_online(presence))
6001 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
6004 if (annotation)
6006 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
6007 g_free(annotation);
6012 static GHashTable *
6013 sipe_get_account_text_table(PurpleAccount *account)
6015 GHashTable *table;
6016 table = g_hash_table_new(g_str_hash, g_str_equal);
6017 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
6018 return table;
6021 static PurpleBuddy *
6022 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6024 PurpleBuddy *clone;
6025 const gchar *server_alias, *email;
6026 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6028 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6030 purple_blist_add_buddy(clone, NULL, group, NULL);
6032 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
6033 if (server_alias) {
6034 purple_blist_server_alias_buddy(clone, server_alias);
6037 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6038 if (email) {
6039 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
6042 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6043 //for UI to update;
6044 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6045 return clone;
6048 static void
6049 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6051 PurpleBuddy *buddy, *b;
6052 PurpleConnection *gc;
6053 PurpleGroup * group = purple_find_group(group_name);
6055 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6057 buddy = (PurpleBuddy *)node;
6059 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
6060 gc = purple_account_get_connection(buddy->account);
6062 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6063 if (!b){
6064 b = purple_blist_add_buddy_clone(group, buddy);
6067 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6070 static void
6071 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6073 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6074 gchar *self = g_strdup_printf("sip:%s", sip->username);
6075 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
6076 struct sip_im_session *session;
6078 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6080 session = create_chat_session(sip);
6081 session->roster_manager = g_strdup(self);
6083 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6084 session->chat_name = g_strdup(chat_name);
6085 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6086 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6088 g_free(chat_name);
6089 g_free(self);
6092 static gboolean
6093 sipe_is_election_finished(struct sipe_account_data *sip,
6094 struct sip_im_session *session)
6096 struct sip_dialog *dialog;
6097 GSList *entry;
6098 gboolean res = TRUE;
6100 entry = session->dialogs;
6101 while (entry) {
6102 dialog = entry->data;
6103 if (dialog->election_vote == 0) {
6104 res = FALSE;
6105 break;
6107 entry = entry->next;
6110 if (res) {
6111 session->is_voting_in_progress = FALSE;
6113 return res;
6116 static void
6117 sipe_election_start(struct sipe_account_data *sip,
6118 struct sip_im_session *session)
6120 struct sip_dialog *dialog;
6121 GSList *entry;
6122 int election_timeout;
6124 if (session->is_voting_in_progress) {
6125 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6126 return;
6127 } else {
6128 session->is_voting_in_progress = TRUE;
6130 session->bid = rand();
6132 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6134 /* reset election_vote for each chat participant */
6135 entry = session->dialogs;
6136 while (entry) {
6137 dialog = entry->data;
6138 dialog->election_vote = 0;
6139 entry = entry->next;
6142 entry = session->dialogs;
6143 while (entry) {
6144 dialog = entry->data;
6145 /* send RequestRM to each chat participant*/
6146 sipe_send_election_request_rm(sip, session, dialog->with, session->bid);
6147 entry = entry->next;
6150 election_timeout = 15; /* sec */
6151 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6155 * @param who a URI to whom to invite to chat
6157 static void
6158 sipe_invite_to_chat(struct sipe_account_data *sip,
6159 struct sip_im_session *session,
6160 const char *who)
6162 gchar *self = g_strdup_printf("sip:%s", sip->username);
6164 if (session->roster_manager) {
6165 if (!strcmp(session->roster_manager, self)) {
6166 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6167 } else {
6168 sipe_refer(sip, session, who);
6170 } else {
6171 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6173 session->pending_invite_queue = slist_insert_unique_sorted(
6174 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6176 sipe_election_start(sip, session);
6179 g_free(self);
6182 static void
6183 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6184 struct sip_im_session *session)
6186 gchar *invitee;
6187 GSList *entry = session->pending_invite_queue;
6189 while (entry) {
6190 invitee = entry->data;
6191 sipe_invite_to_chat(sip, session, invitee);
6192 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6193 g_free(invitee);
6197 static void
6198 sipe_election_result(struct sipe_account_data *sip,
6199 void *sess)
6201 struct sip_im_session *session = (struct sip_im_session *)sess;
6202 struct sip_dialog *dialog;
6203 GSList *entry;
6204 gchar * rival;
6205 gboolean has_won = TRUE;
6207 if (session->roster_manager) {
6208 purple_debug_info("sipe",
6209 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6210 return;
6213 session->is_voting_in_progress = FALSE;
6215 entry = session->dialogs;
6216 while (entry) {
6217 dialog = entry->data;
6218 if (dialog->election_vote < 0) {
6219 has_won = FALSE;
6220 rival = dialog->with;
6221 break;
6223 entry = entry->next;
6226 if (has_won) {
6227 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6229 session->roster_manager = g_strdup_printf("sip:%s", sip->username);
6231 entry = session->dialogs;
6232 while (entry) {
6233 dialog = entry->data;
6234 /* send SetRM to each chat participant*/
6235 sipe_send_election_set_rm(sip, session, dialog->with);
6236 entry = entry->next;
6238 } else {
6239 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6241 session->bid = 0;
6243 sipe_process_pending_invite_queue(sip, session);
6246 static void
6247 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6249 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6250 struct sip_im_session *session;
6252 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6253 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6255 session = find_chat_session_by_name(sip, chat_name);
6257 sipe_invite_to_chat(sip, session, buddy->name);
6260 static void
6261 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6263 const gchar *email;
6264 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6266 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6267 if (email)
6269 char *mailto = g_strdup_printf("mailto:%s", email);
6270 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6271 #ifndef _WIN32
6273 pid_t pid;
6274 char *const parmList[] = {mailto, NULL};
6275 if ((pid = fork()) == -1)
6277 purple_debug_info("sipe", "fork() error\n");
6279 else if (pid == 0)
6281 execvp("xdg-email", parmList);
6282 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6285 #else
6287 BOOL ret;
6288 _flushall();
6289 errno = 0;
6290 //@TODO resolve env variable %WINDIR% first
6291 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6292 if (errno)
6294 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6297 #endif
6299 g_free(mailto);
6301 else
6303 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6308 * A menu which appear when right-clicking on buddy in contact list.
6310 static GList *
6311 sipe_buddy_menu(PurpleBuddy *buddy)
6313 PurpleBlistNode *g_node;
6314 PurpleGroup *group, *gr_parent;
6315 PurpleMenuAction *act;
6316 GList *menu = NULL;
6317 GList *menu_groups = NULL;
6318 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6319 struct sip_im_session *session;
6320 GSList *entry;
6321 gchar *self = g_strdup_printf("sip:%s", sip->username);
6323 act = purple_menu_action_new(_("New Chat"),
6324 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6325 NULL, NULL);
6326 menu = g_list_prepend(menu, act);
6328 entry = sip->im_sessions;
6329 while (entry) {
6330 session = entry->data;
6331 if (strcmp(self, buddy->name) && session->chat_name && !get_dialog(session, buddy->name)) {
6332 act = purple_menu_action_new(g_strdup_printf(_("Invite to '%s'"), session->chat_name),
6333 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6334 g_strdup(session->chat_name), NULL);
6335 menu = g_list_prepend(menu, act);
6337 entry = entry->next;
6340 act = purple_menu_action_new(_("Send Email..."),
6341 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6342 NULL, NULL);
6343 menu = g_list_prepend(menu, act);
6345 gr_parent = purple_buddy_get_group(buddy);
6346 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6347 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6348 continue;
6350 group = (PurpleGroup *)g_node;
6351 if (group == gr_parent)
6352 continue;
6354 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6355 continue;
6357 act = purple_menu_action_new(purple_group_get_name(group),
6358 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6359 group->name, NULL);
6360 menu_groups = g_list_prepend(menu_groups, act);
6362 menu_groups = g_list_reverse(menu_groups);
6364 act = purple_menu_action_new(_("Copy to"),
6365 NULL,
6366 NULL, menu_groups);
6367 menu = g_list_prepend(menu, act);
6368 menu = g_list_reverse(menu);
6370 g_free(self);
6371 return menu;
6374 static GList *
6375 sipe_blist_node_menu(PurpleBlistNode *node)
6377 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6378 return sipe_buddy_menu((PurpleBuddy *) node);
6379 } else {
6380 return NULL;
6384 static gboolean
6385 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6387 gboolean ret = TRUE;
6388 char *username = (char *)trans->payload;
6390 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6391 PurpleBuddy *pbuddy;
6392 struct sipe_buddy *sbuddy;
6393 const char *alias;
6394 char *server_alias = NULL;
6395 char *email = NULL;
6396 const char *device_name = NULL;
6398 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6400 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6401 alias = purple_buddy_get_local_alias(pbuddy);
6403 if (sip)
6405 //will query buddy UA's capabilities and send answer to log
6406 sipe_options_request(sip, username);
6408 sbuddy = g_hash_table_lookup(sip->buddies, username);
6409 if (sbuddy)
6411 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6415 if (msg->response != 200) {
6416 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6417 } else {
6418 xmlnode *searchResults;
6419 xmlnode *mrow;
6421 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6422 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6423 if (!searchResults) {
6424 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6425 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6426 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6427 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6428 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6429 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6430 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6431 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6432 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6433 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6434 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6435 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6436 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6437 if (!email || strcmp("", email)) {
6438 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6439 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6443 xmlnode_free(searchResults);
6446 purple_notify_user_info_add_section_break(info);
6448 if (!server_alias || !strcmp("", server_alias)) {
6449 g_free(server_alias);
6450 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6451 if (server_alias) {
6452 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6456 // same as server alias, do not present
6457 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6458 if (alias)
6460 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6463 if (!email || !strcmp("", email)) {
6464 g_free(email);
6465 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6466 if (email) {
6467 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6471 if (device_name)
6473 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6476 /* show a buddy's user info in a nice dialog box */
6477 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6478 username, /* buddy's username */
6479 info, /* body */
6480 NULL, /* callback called when dialog closed */
6481 NULL); /* userdata for callback */
6483 return ret;
6487 * AD search first, LDAP based
6489 static void sipe_get_info(PurpleConnection *gc, const char *username)
6491 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6492 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6494 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6495 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6496 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6497 g_free(body);
6498 g_free(row);
6501 static PurplePlugin *my_protocol = NULL;
6503 static PurplePluginProtocolInfo prpl_info =
6506 NULL, /* user_splits */
6507 NULL, /* protocol_options */
6508 NO_BUDDY_ICONS, /* icon_spec */
6509 sipe_list_icon, /* list_icon */
6510 NULL, /* list_emblems */
6511 sipe_status_text, /* status_text */
6512 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6513 sipe_status_types, /* away_states */
6514 sipe_blist_node_menu, /* blist_node_menu */
6515 NULL, /* chat_info */
6516 NULL, /* chat_info_defaults */
6517 sipe_login, /* login */
6518 sipe_close, /* close */
6519 sipe_im_send, /* send_im */
6520 NULL, /* set_info */ // TODO maybe
6521 sipe_send_typing, /* send_typing */
6522 sipe_get_info, /* get_info */
6523 sipe_set_status, /* set_status */
6524 NULL, /* set_idle */
6525 NULL, /* change_passwd */
6526 sipe_add_buddy, /* add_buddy */
6527 NULL, /* add_buddies */
6528 sipe_remove_buddy, /* remove_buddy */
6529 NULL, /* remove_buddies */
6530 sipe_add_permit, /* add_permit */
6531 sipe_add_deny, /* add_deny */
6532 sipe_add_deny, /* rem_permit */
6533 sipe_add_permit, /* rem_deny */
6534 dummy_permit_deny, /* set_permit_deny */
6535 NULL, /* join_chat */
6536 NULL, /* reject_chat */
6537 NULL, /* get_chat_name */
6538 NULL, /* chat_invite */
6539 sipe_chat_leave, /* chat_leave */
6540 NULL, /* chat_whisper */
6541 sipe_chat_send, /* chat_send */
6542 sipe_keep_alive, /* keepalive */
6543 NULL, /* register_user */
6544 NULL, /* get_cb_info */ // deprecated
6545 NULL, /* get_cb_away */ // deprecated
6546 sipe_alias_buddy, /* alias_buddy */
6547 sipe_group_buddy, /* group_buddy */
6548 sipe_rename_group, /* rename_group */
6549 NULL, /* buddy_free */
6550 sipe_convo_closed, /* convo_closed */
6551 purple_normalize_nocase, /* normalize */
6552 NULL, /* set_buddy_icon */
6553 sipe_remove_group, /* remove_group */
6554 NULL, /* get_cb_real_name */ // TODO?
6555 NULL, /* set_chat_topic */
6556 NULL, /* find_blist_chat */
6557 NULL, /* roomlist_get_list */
6558 NULL, /* roomlist_cancel */
6559 NULL, /* roomlist_expand_category */
6560 NULL, /* can_receive_file */
6561 NULL, /* send_file */
6562 NULL, /* new_xfer */
6563 NULL, /* offline_message */
6564 NULL, /* whiteboard_prpl_ops */
6565 sipe_send_raw, /* send_raw */
6566 NULL, /* roomlist_room_serialize */
6567 NULL, /* unregister_user */
6568 NULL, /* send_attention */
6569 NULL, /* get_attention_types */
6571 sizeof(PurplePluginProtocolInfo), /* struct_size */
6572 sipe_get_account_text_table, /* get_account_text_table */
6576 static PurplePluginInfo info = {
6577 PURPLE_PLUGIN_MAGIC,
6578 PURPLE_MAJOR_VERSION,
6579 PURPLE_MINOR_VERSION,
6580 PURPLE_PLUGIN_PROTOCOL, /**< type */
6581 NULL, /**< ui_requirement */
6582 0, /**< flags */
6583 NULL, /**< dependencies */
6584 PURPLE_PRIORITY_DEFAULT, /**< priority */
6585 "prpl-sipe", /**< id */
6586 "Microsoft LCS/OCS", /**< name */
6587 VERSION, /**< version */
6588 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6589 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6590 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6591 "Gabriel Burt <gburt@novell.com>", /**< author */
6592 PURPLE_WEBSITE, /**< homepage */
6593 sipe_plugin_load, /**< load */
6594 sipe_plugin_unload, /**< unload */
6595 sipe_plugin_destroy, /**< destroy */
6596 NULL, /**< ui_info */
6597 &prpl_info, /**< extra_info */
6598 NULL,
6599 sipe_actions,
6600 NULL,
6601 NULL,
6602 NULL,
6603 NULL
6606 static void sipe_plugin_destroy(PurplePlugin *plugin)
6608 GList *entry;
6610 entry = prpl_info.protocol_options;
6611 while (entry) {
6612 purple_account_option_destroy(entry->data);
6613 entry = g_list_delete_link(entry, entry);
6615 prpl_info.protocol_options = NULL;
6617 entry = prpl_info.user_splits;
6618 while (entry) {
6619 purple_account_user_split_destroy(entry->data);
6620 entry = g_list_delete_link(entry, entry);
6622 prpl_info.user_splits = NULL;
6625 static void init_plugin(PurplePlugin *plugin)
6627 PurpleAccountUserSplit *split;
6628 PurpleAccountOption *option;
6630 srand(time(NULL));
6632 #ifdef ENABLE_NLS
6633 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6634 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6635 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6636 textdomain(GETTEXT_PACKAGE);
6637 #endif
6639 purple_plugin_register(plugin);
6641 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
6642 purple_account_user_split_set_reverse(split, FALSE);
6643 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6645 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
6646 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6647 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6648 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6650 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
6651 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6652 // Translators: noun (networking port)
6653 option = purple_account_option_int_new(_("Port"), "port", 5061);
6654 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6656 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6657 purple_account_option_add_list_item(option, _("Auto"), "auto");
6658 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6659 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6660 purple_account_option_add_list_item(option, _("UDP"), "udp");
6661 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6663 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6664 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6666 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6667 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6669 #ifdef USE_KERBEROS
6670 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6671 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6673 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6674 * No login/password is taken into account if this option present,
6675 * instead used default credentials stored in OS.
6677 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6678 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6679 #endif
6680 my_protocol = plugin;
6683 /* I had to redefined the function for it load, but works */
6684 gboolean purple_init_plugin(PurplePlugin *plugin){
6685 plugin->info = &(info);
6686 init_plugin((plugin));
6687 sipe_plugin_load((plugin));
6688 return purple_plugin_register(plugin);
6692 Local Variables:
6693 mode: c
6694 c-file-style: "bsd"
6695 indent-tabs-mode: t
6696 tab-width: 8
6697 End: