Fix crash when trying to free dialog
[siplcs.git] / src / sipe.c
blobf3746eeab7103e47401a3083dc8af2af4f3e4bef
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);
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;
2549 if (!dialog) return;
2551 g_free(dialog->with);
2552 entry = dialog->routes;
2553 while (entry) {
2554 g_free(entry->data);
2555 entry = g_slist_remove(entry, entry->data);
2557 entry = dialog->supported;
2558 while (entry) {
2559 g_free(entry->data);
2560 entry = g_slist_remove(entry, entry->data);
2563 g_free(dialog->callid);
2564 g_free(dialog->ourtag);
2565 g_free(dialog->theirtag);
2566 g_free(dialog->theirepid);
2567 g_free(dialog->request);
2569 g_free(dialog);
2572 static void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2574 GSList *entry;
2576 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2578 entry = session->dialogs;
2579 while (entry) {
2580 free_dialog(entry->data);
2581 entry = g_slist_remove(entry, entry->data);
2584 entry = session->outgoing_message_queue;
2585 while (entry) {
2586 g_free(entry->data);
2587 entry = g_slist_remove(entry, entry->data);
2590 entry = session->pending_invite_queue;
2591 while (entry) {
2592 g_free(entry->data);
2593 entry = g_slist_remove(entry, entry->data);
2596 g_hash_table_destroy(session->unconfirmed_messages);
2598 g_free(session->with);
2599 g_free(session->chat_name);
2600 g_free(session->callid);
2601 g_free(session->roster_manager);
2602 g_free(session);
2605 static gboolean
2606 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2608 gboolean ret = TRUE;
2610 if (msg->response != 200) {
2611 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2612 return FALSE;
2615 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2617 return ret;
2621 * Asks UA/proxy about its capabilities.
2623 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2625 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2626 gchar *contact = get_contact(sip);
2627 gchar *request;
2628 request = g_strdup_printf(
2629 "Accept: application/sdp\r\n"
2630 "Contact: %s\r\n", contact);
2632 g_free(contact);
2634 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2636 g_free(to);
2637 g_free(request);
2640 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2642 char *msg, *msg_tmp;
2643 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2644 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2645 g_free(msg_tmp);
2646 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2647 "possibly because one or more persons are offline:\n%s") ,
2648 msg ? msg : "");
2649 purple_conv_present_error(with, sip->account, msg_tmp);
2650 g_free(msg);
2651 g_free(msg_tmp);
2654 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2656 static gboolean
2657 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2659 gboolean ret = TRUE;
2660 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2661 struct sip_im_session * session = find_im_session(sip, with);
2662 struct sip_dialog *dialog;
2663 gchar *cseq;
2664 char *key;
2665 gchar *message;
2667 if (!session) {
2668 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2669 g_free(with);
2670 return FALSE;
2673 dialog = get_dialog(session, with);
2675 if (!dialog) {
2676 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2677 g_free(with);
2678 return FALSE;
2681 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2682 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2683 g_free(cseq);
2684 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2686 if (msg->response != 200) {
2687 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2689 sipe_present_message_undelivered_err(with, sip, message);
2690 im_session_destroy(sip, session);
2691 ret = FALSE;
2692 } else {
2693 g_hash_table_remove(session->unconfirmed_messages, key);
2694 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2695 key, g_hash_table_size(session->unconfirmed_messages));
2698 g_free(key);
2699 g_free(with);
2701 if (ret) sipe_im_process_queue(sip, session);
2702 return ret;
2705 static gboolean
2706 sipe_is_election_finished(struct sipe_account_data *sip,
2707 struct sip_im_session *session);
2709 static void
2710 sipe_election_result(struct sipe_account_data *sip,
2711 void *sess);
2713 static gboolean
2714 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2716 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2717 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2718 struct sip_dialog *dialog;
2719 struct sip_im_session *session;
2721 session = find_chat_session(sip, callid);
2722 if (!session) {
2723 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2724 return FALSE;
2727 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2728 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2729 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2730 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2732 if (xn_request_rm_response) {
2733 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2734 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2736 dialog = get_dialog(session, with);
2737 if (!dialog) {
2738 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2739 return FALSE;
2742 if (allow && !g_strcasecmp(allow, "true")) {
2743 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2744 dialog->election_vote = 1;
2745 } else if (allow && !g_strcasecmp(allow, "false")) {
2746 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2747 dialog->election_vote = -1;
2750 if (sipe_is_election_finished(sip, session)) {
2751 sipe_election_result(sip, session);
2754 } else if (xn_set_rm_response) {
2757 xmlnode_free(xn_action);
2761 return TRUE;
2764 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2766 gchar *hdr;
2767 gchar *tmp;
2768 char *msgformat;
2769 char *msgtext;
2770 gchar *msgr_value;
2771 gchar *msgr;
2773 sipe_parse_html(msg, &msgformat, &msgtext);
2774 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2776 msgr_value = sipmsg_get_msgr_string(msgformat);
2777 g_free(msgformat);
2778 if (msgr_value) {
2779 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2780 g_free(msgr_value);
2781 } else {
2782 msgr = g_strdup("");
2785 tmp = get_contact(sip);
2786 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2787 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2788 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2789 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2790 g_free(tmp);
2791 g_free(msgr);
2793 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2794 g_free(msgtext);
2795 g_free(hdr);
2799 static void
2800 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2802 GSList *entry2 = session->outgoing_message_queue;
2803 while (entry2) {
2804 char *queued_msg = entry2->data;
2805 struct sip_dialog *dialog;
2806 GSList *entry = session->dialogs;
2808 if (session->is_multiparty) {
2809 gchar *who = g_strdup_printf("sip:%s", sip->username);
2810 serv_got_chat_in(sip->gc, session->chat_id, who,
2811 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2812 g_free(who);
2815 while (entry) {
2816 char *key;
2818 dialog = entry->data;
2819 entry = entry->next;
2820 if (dialog->outgoing_invite) continue; //do not send messages as INVITE is not responded.
2822 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2823 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2824 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2825 key, g_hash_table_size(session->unconfirmed_messages));
2826 g_free(key);
2827 sipe_send_message(sip, dialog, queued_msg);
2830 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2831 g_free(queued_msg);
2835 static void
2836 sipe_get_route_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2838 GSList *hdr = msg->headers;
2839 gchar *contact;
2841 while(hdr) {
2842 struct siphdrelement *elem = hdr->data;
2843 if(!g_ascii_strcasecmp(elem->name, "Record-Route")) {
2844 gchar **parts = g_strsplit(elem->value, ",", 0);
2845 gchar **part = parts;
2847 while (*part) {
2848 gchar *route = sipmsg_find_part_of_header(*part, "<", ">", NULL);
2849 purple_debug_info("sipe", "sipe_get_route_header: route %s \n", route);
2850 dialog->routes = g_slist_append(dialog->routes, route);
2851 part++;
2854 g_strfreev(parts);
2856 hdr = g_slist_next(hdr);
2859 if (outgoing)
2861 dialog->routes = g_slist_reverse(dialog->routes);
2864 if (dialog->routes)
2866 dialog->request = dialog->routes->data;
2867 dialog->routes = g_slist_remove(dialog->routes, dialog->routes->data);
2870 contact = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Contact"), "<", ">", NULL);
2871 dialog->routes = g_slist_append(dialog->routes, contact);
2874 static void
2875 sipe_get_supported_header(struct sipmsg *msg, struct sip_dialog * dialog, gboolean outgoing)
2877 GSList *hdr = msg->headers;
2878 struct siphdrelement *elem;
2879 while(hdr)
2881 elem = hdr->data;
2882 if(!g_ascii_strcasecmp(elem->name, "Supported")
2883 && !g_slist_find_custom(dialog->supported, elem->value, (GCompareFunc)g_ascii_strcasecmp))
2885 dialog->supported = g_slist_append(dialog->supported, g_strdup(elem->value));
2888 hdr = g_slist_next(hdr);
2892 static void
2893 sipe_parse_dialog(struct sipmsg * msg, struct sip_dialog * dialog, gboolean outgoing)
2895 gchar *us = outgoing ? "From" : "To";
2896 gchar *them = outgoing ? "To" : "From";
2898 g_free(dialog->ourtag);
2899 g_free(dialog->theirtag);
2901 dialog->ourtag = find_tag(sipmsg_find_header(msg, us));
2902 dialog->theirtag = find_tag(sipmsg_find_header(msg, them));
2903 if (!dialog->theirepid) {
2904 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", ";", NULL);
2905 if (!dialog->theirepid) {
2906 dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, them), "epid=", NULL, NULL);
2910 // Catch a tag on the end of the To Header and get rid of it.
2911 if (dialog->theirepid && strstr(dialog->theirepid, "tag=")) {
2912 dialog->theirepid = strtok(dialog->theirepid, ";");
2915 sipe_get_route_header(msg, dialog, outgoing);
2916 sipe_get_supported_header(msg, dialog, outgoing);
2919 static void
2920 sipe_refer_notify(struct sipe_account_data *sip,
2921 struct sip_im_session *session,
2922 const gchar *who,
2923 int status,
2924 const gchar *desc)
2926 gchar *hdr;
2927 gchar *body;
2928 struct sip_dialog *dialog = get_dialog(session, who);
2930 hdr = g_strdup_printf(
2931 "Event: refer\r\n"
2932 "Subscription-State: %s\r\n"
2933 "Content-Type: message/sipfrag\r\n",
2934 status >= 200 ? "terminated" : "active");
2936 body = g_strdup_printf(
2937 "SIP/2.0 %d %s\r\n",
2938 status, desc);
2940 dialog->outgoing_invite = send_sip_request(sip->gc, "NOTIFY",
2941 who, who, hdr, body, dialog, NULL);
2943 g_free(hdr);
2944 g_free(body);
2947 static gboolean
2948 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2950 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2951 struct sip_im_session *session;
2952 struct sip_dialog *dialog;
2953 char *cseq;
2954 char *key;
2955 gchar *message;
2956 struct sipmsg *request_msg = trans->msg;
2958 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2959 gchar *referred_by;
2961 session = find_chat_session(sip, callid);
2962 if (!session) {
2963 session = find_im_session(sip, with);
2966 if (!session) {
2967 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2968 g_free(with);
2969 return FALSE;
2972 dialog = get_dialog(session, with);
2973 if (!dialog) {
2974 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2975 g_free(with);
2976 return FALSE;
2979 sipe_parse_dialog(msg, dialog, TRUE);
2981 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2982 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2983 g_free(cseq);
2984 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2986 if (msg->response != 200) {
2987 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2989 sipe_present_message_undelivered_err(with, sip, message);
2990 im_session_destroy(sip, session);
2991 g_free(key);
2992 g_free(with);
2993 return FALSE;
2996 dialog->cseq = 0;
2997 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
2998 dialog->outgoing_invite = NULL;
2999 dialog->is_established = TRUE;
3001 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3002 if (referred_by) {
3003 sipe_refer_notify(sip, session, referred_by, 200, "OK");
3004 g_free(referred_by);
3007 /* add user to chat if it is a multiparty session */
3008 if (session->is_multiparty) {
3009 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3010 with, NULL,
3011 PURPLE_CBFLAGS_NONE, TRUE);
3014 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3015 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3016 if (session->outgoing_message_queue) {
3017 char *queued_msg = session->outgoing_message_queue->data;
3018 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3019 g_free(queued_msg);
3023 sipe_im_process_queue(sip, session);
3025 g_hash_table_remove(session->unconfirmed_messages, key);
3026 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3027 key, g_hash_table_size(session->unconfirmed_messages));
3029 g_free(key);
3030 g_free(with);
3031 return TRUE;
3035 static void
3036 sipe_invite(struct sipe_account_data *sip,
3037 struct sip_im_session *session,
3038 const gchar *who,
3039 const gchar *msg_body,
3040 const gchar *referred_by,
3041 const gboolean is_triggered)
3043 gchar *hdr;
3044 gchar *to;
3045 gchar *contact;
3046 gchar *body;
3047 gchar *self;
3048 char *ms_text_format = g_strdup("");
3049 gchar *roster_manager;
3050 gchar *end_points;
3051 gchar *referred_by_str;
3052 struct sip_dialog *dialog = get_dialog(session, who);
3054 if (dialog && dialog->is_established) {
3055 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
3056 return;
3059 if (!dialog) {
3060 dialog = g_new0(struct sip_dialog, 1);
3061 session->dialogs = g_slist_append(session->dialogs, dialog);
3063 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3064 dialog->with = g_strdup(who);
3067 if (!(dialog->ourtag)) {
3068 dialog->ourtag = gentag();
3072 if (strstr(who, "sip:")) {
3073 to = g_strdup(who);
3074 } else {
3075 to = g_strdup_printf("sip:%s", who);
3078 if (msg_body) {
3079 char *msgformat;
3080 char *msgtext;
3081 char *base64_msg;
3082 gchar *msgr_value;
3083 gchar *msgr;
3084 char *key;
3086 sipe_parse_html(msg_body, &msgformat, &msgtext);
3087 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
3089 msgr_value = sipmsg_get_msgr_string(msgformat);
3090 g_free(msgformat);
3091 msgr = "";
3092 if (msgr_value) {
3093 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3094 g_free(msgr_value);
3097 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3098 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3099 g_free(msgtext);
3100 g_free(msgr);
3101 g_free(base64_msg);
3103 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3104 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3105 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3106 key, g_hash_table_size(session->unconfirmed_messages));
3107 g_free(key);
3110 contact = get_contact(sip);
3111 end_points = get_end_points(sip, session);
3112 self = g_strdup_printf("sip:%s", sip->username);
3113 roster_manager = g_strdup_printf(
3114 "Roster-Manager: %s\r\n"
3115 "EndPoints: %s\r\n",
3116 self,
3117 end_points);
3118 referred_by_str = referred_by ?
3119 g_strdup_printf(
3120 "Referred-By: %s\r\n",
3121 referred_by)
3122 : g_strdup("");
3123 hdr = g_strdup_printf(
3124 "%s"
3125 "%s"
3126 "%s"
3127 "%s"
3128 "Contact: %s\r\n%s"
3129 "Content-Type: application/sdp\r\n",
3130 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3131 referred_by_str,
3132 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3133 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3134 contact,
3135 ms_text_format);
3136 g_free(ms_text_format);
3137 g_free(self);
3139 body = g_strdup_printf(
3140 "v=0\r\n"
3141 "o=- 0 0 IN IP4 %s\r\n"
3142 "s=session\r\n"
3143 "c=IN IP4 %s\r\n"
3144 "t=0 0\r\n"
3145 "m=message %d sip null\r\n"
3146 "a=accept-types:text/plain text/html image/gif "
3147 "multipart/alternative application/im-iscomposing+xml\r\n",
3148 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3150 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3151 to, to, hdr, body, dialog, process_invite_response);
3153 g_free(to);
3154 g_free(roster_manager);
3155 g_free(end_points);
3156 g_free(referred_by_str);
3157 g_free(body);
3158 g_free(hdr);
3159 g_free(contact);
3162 static void
3163 sipe_refer(struct sipe_account_data *sip,
3164 struct sip_im_session *session,
3165 const gchar *who)
3167 gchar *hdr;
3168 gchar *contact;
3169 struct sip_dialog *dialog = get_dialog(session, session->roster_manager);
3171 contact = get_contact(sip);
3172 hdr = g_strdup_printf(
3173 "Contact: %s\r\n"
3174 "Refer-to: <%s>\r\n"
3175 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3176 "Require: com.microsoft.rtc-multiparty\r\n",
3177 contact,
3178 who,
3179 sip->username,
3180 dialog->ourtag ? ";tag=" : "",
3181 dialog->ourtag ? dialog->ourtag : "",
3182 get_epid(sip));
3184 send_sip_request(sip->gc, "REFER",
3185 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3187 g_free(hdr);
3188 g_free(contact);
3191 static void
3192 sipe_send_election_request_rm(struct sipe_account_data *sip,
3193 struct sip_im_session *session,
3194 const gchar *who,
3195 int bid)
3197 gchar *hdr;
3198 gchar *body;
3199 struct sip_dialog *dialog = get_dialog(session, who);
3201 hdr = "Content-Type: application/x-ms-mim\r\n";
3203 body = g_strdup_printf(
3204 "<?xml version=\"1.0\"?>\r\n"
3205 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3206 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3207 sip->username, bid);
3209 send_sip_request(sip->gc, "INFO",
3210 who, who, hdr, body, dialog, process_info_response);
3212 g_free(body);
3215 static void
3216 sipe_send_election_set_rm(struct sipe_account_data *sip,
3217 struct sip_im_session *session,
3218 const gchar *who)
3220 gchar *hdr;
3221 gchar *body;
3222 struct sip_dialog *dialog = get_dialog(session, who);
3224 hdr = "Content-Type: application/x-ms-mim\r\n";
3226 body = g_strdup_printf(
3227 "<?xml version=\"1.0\"?>\r\n"
3228 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3229 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3230 sip->username);
3232 send_sip_request(sip->gc, "INFO",
3233 who, who, hdr, body, dialog, process_info_response);
3235 g_free(body);
3238 static void
3239 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
3241 if (session) {
3242 struct sip_dialog *dialog;
3243 GSList *entry;
3244 entry = session->dialogs;
3245 while (entry) {
3246 dialog = entry->data;
3247 /* @TODO slow down BYE message sending rate */
3248 /* @see single subscription code */
3249 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3250 entry = entry->next;
3253 im_session_destroy(sip, session);
3257 static void
3258 sipe_convo_closed(PurpleConnection * gc, const char *who)
3260 struct sipe_account_data *sip = gc->proto_data;
3262 purple_debug_info("sipe", "conversation with %s closed\n", who);
3263 im_session_close(sip, find_im_session(sip, who));
3266 static void
3267 sipe_chat_leave (PurpleConnection *gc, int id)
3269 struct sipe_account_data *sip = gc->proto_data;
3270 struct sip_im_session * session = find_chat_session_by_id(sip, id);
3271 im_session_close(sip, session);
3274 static void
3275 im_session_close_all (struct sipe_account_data *sip)
3277 GSList *entry = sip->im_sessions;
3278 while (entry) {
3279 im_session_close (sip, entry->data);
3280 entry = sip->im_sessions;
3284 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
3286 struct sipe_account_data *sip = gc->proto_data;
3287 struct sip_im_session *session;
3288 struct sip_dialog *dialog;
3290 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3292 session = find_or_create_im_session(sip, who);
3293 dialog = get_dialog(session, who);
3295 // Queue the message
3296 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3298 if (dialog && dialog->callid) {
3299 sipe_im_process_queue(sip, session);
3300 } else if (!dialog || !dialog->outgoing_invite) {
3301 // Need to send the INVITE to get the outgoing dialog setup
3302 sipe_invite(sip, session, who, what, NULL, FALSE);
3305 return 1;
3308 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags)
3310 struct sipe_account_data *sip = gc->proto_data;
3311 struct sip_im_session *session;
3313 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3315 session = find_chat_session_by_id(sip, id);
3317 // Queue the message
3318 if (session) {
3319 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3320 g_strdup(what));
3321 sipe_im_process_queue(sip, session);
3324 return 1;
3327 /* End IM Session (INVITE and MESSAGE methods) */
3329 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3331 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3332 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3333 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3335 struct sip_im_session *session = find_chat_session(sip, callid);
3336 if (!session) {
3337 session = find_im_session(sip, from);
3340 if (!session) {
3341 return;
3344 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3345 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3346 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3347 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3349 sipmsg_remove_header(msg, "User-Agent");
3351 if (xn_request_rm) {
3352 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3353 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3354 gchar *body = g_strdup_printf(
3355 "<?xml version=\"1.0\"?>\r\n"
3356 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3357 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3358 sip->username,
3359 session->bid < bid ? "true" : "false");
3360 send_sip_response(sip->gc, msg, 200, "OK", body);
3361 g_free(body);
3362 } else if (xn_set_rm) {
3363 gchar *body;
3364 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3365 g_free(session->roster_manager);
3366 session->roster_manager = g_strdup(rm);
3368 body = g_strdup_printf(
3369 "<?xml version=\"1.0\"?>\r\n"
3370 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3371 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3372 sip->username);
3373 send_sip_response(sip->gc, msg, 200, "OK", body);
3374 g_free(body);
3376 xmlnode_free(xn_action);
3378 } else {
3379 /* looks like purple lacks typing notification for chat */
3380 if (!session->is_multiparty) {
3381 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3384 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3386 g_free(from);
3389 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3391 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3392 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3393 struct sip_im_session *session;
3394 struct sip_dialog *dialog;
3396 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3398 session = find_chat_session(sip, callid);
3399 if (!session) {
3400 session = find_im_session(sip, from);
3403 if (!session) {
3404 return;
3407 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3408 g_free(session->roster_manager);
3409 session->roster_manager = NULL;
3412 if (!session->is_multiparty) {
3413 // TODO Let the user know the other user left the conversation?
3414 im_session_destroy(sip, session);
3415 } else {
3416 dialog = get_dialog(session, from);
3417 session->dialogs = g_slist_remove(session->dialogs, dialog);
3418 free_dialog(dialog);
3420 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3422 if (!session->dialogs) {
3423 im_session_destroy(sip, session);
3427 g_free(from);
3430 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3432 gchar *self = g_strdup_printf("sip:%s", sip->username);
3433 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3434 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3435 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3436 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3437 struct sip_im_session *session;
3438 struct sip_dialog *dialog;
3440 session = find_chat_session(sip, callid);
3441 dialog = get_dialog(session, from);
3443 sipmsg_remove_header(msg, "User-Agent");
3444 sipmsg_remove_header(msg, "Refer-to");
3445 sipmsg_remove_header(msg, "Referred-By");
3446 sipmsg_remove_header(msg, "Require");
3448 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3449 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3450 } else {
3451 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3453 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3456 g_free(self);
3457 g_free(from);
3458 g_free(refer_to);
3459 g_free(referred_by);
3462 static unsigned int
3463 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3465 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3466 struct sip_im_session *session;
3467 struct sip_dialog *dialog;
3469 if (state == PURPLE_NOT_TYPING)
3470 return 0;
3472 session = find_im_session(sip, who);
3473 dialog = get_dialog(session, who);
3475 if (session && dialog) {
3476 send_sip_request(gc, "INFO", who, who,
3477 "Content-Type: application/xml\r\n",
3478 SIPE_SEND_TYPING, dialog, NULL);
3480 return SIPE_TYPING_SEND_TIMEOUT;
3483 static gboolean resend_timeout(struct sipe_account_data *sip)
3485 GSList *tmp = sip->transactions;
3486 time_t currtime = time(NULL);
3487 while (tmp) {
3488 struct transaction *trans = tmp->data;
3489 tmp = tmp->next;
3490 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3491 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3492 /* TODO 408 */
3493 } else {
3494 if ((currtime - trans->time > 2) && trans->retries == 0) {
3495 trans->retries++;
3496 sendout_sipmsg(sip, trans->msg);
3500 return TRUE;
3503 static void do_reauthenticate_cb(struct sipe_account_data *sip, void *unused)
3505 /* register again when security token expires */
3506 /* we have to start a new authentication as the security token
3507 * is almost expired by sending a not signed REGISTER message */
3508 purple_debug_info("sipe", "do a full reauthentication\n");
3509 sipe_auth_free(&sip->registrar);
3510 sipe_auth_free(&sip->proxy);
3511 sip->registerstatus = 0;
3512 do_register(sip);
3513 sip->reauthenticate_set = FALSE;
3516 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3518 gchar *from;
3519 gchar *contenttype;
3520 gboolean found = FALSE;
3522 from = parse_from(sipmsg_find_header(msg, "From"));
3524 if (!from) return;
3526 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3528 contenttype = sipmsg_find_header(msg, "Content-Type");
3529 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
3531 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3532 gchar *html = get_html_message(contenttype, msg->body);
3534 struct sip_im_session *session = find_chat_session(sip, callid);
3535 if (!session) {
3536 session = find_im_session(sip, from);
3539 if (session && session->is_multiparty) {
3540 serv_got_chat_in(sip->gc, session->chat_id, from,
3541 PURPLE_MESSAGE_RECV, html, time(NULL));
3542 } else {
3543 serv_got_im(sip->gc, from, html, 0, time(NULL));
3545 g_free(html);
3546 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3547 found = TRUE;
3549 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3550 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3551 xmlnode *state;
3552 gchar *statedata;
3554 if (!isc) {
3555 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3556 return;
3559 state = xmlnode_get_child(isc, "state");
3561 if (!state) {
3562 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3563 xmlnode_free(isc);
3564 return;
3567 statedata = xmlnode_get_data(state);
3568 if (statedata) {
3569 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3570 else serv_got_typing_stopped(sip->gc, from);
3572 g_free(statedata);
3574 xmlnode_free(isc);
3575 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3576 found = TRUE;
3578 if (!found) {
3579 purple_debug_info("sipe", "got unknown mime-type");
3580 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3582 g_free(from);
3585 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3587 gchar *ms_text_format;
3588 gchar *body;
3589 gchar *newTag = gentag();
3590 gchar *oldHeader;
3591 gchar *newHeader;
3592 gboolean is_multiparty = FALSE;
3593 gboolean is_triggered = FALSE;
3594 gboolean was_multiparty = TRUE;
3595 gboolean just_joined = FALSE;
3596 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3597 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3598 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3599 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
3600 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
3601 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
3602 gchar **end_points = NULL;
3603 struct sip_im_session *session;
3604 struct sip_dialog *dialog;
3606 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3608 /* Only accept text invitations */
3609 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3610 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3611 return;
3614 // TODO There *must* be a better way to clean up the To header to add a tag...
3615 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3616 oldHeader = sipmsg_find_header(msg, "To");
3617 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3618 sipmsg_remove_header(msg, "To");
3619 sipmsg_add_header(msg, "To", newHeader);
3620 g_free(newHeader);
3622 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3623 if (end_points_hdr) {
3624 end_points = g_strsplit(end_points_hdr, ",", 0);
3625 if (end_points[0] && end_points[1] && end_points[2]) {
3626 is_multiparty = TRUE;
3629 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
3630 is_triggered = TRUE;
3631 is_multiparty = TRUE;
3634 session = find_chat_session(sip, callid);
3635 /* Convert to multiparty */
3636 if (session && is_multiparty && !session->is_multiparty) {
3637 g_free(session->with);
3638 session->with = NULL;
3639 was_multiparty = FALSE;
3640 session->is_multiparty = TRUE;
3641 session->chat_id = rand();
3644 if (!session && is_multiparty) {
3645 session = find_or_create_chat_session(sip, callid);
3647 /* IM session */
3648 if (!session) {
3649 session = find_or_create_im_session(sip, from);
3652 if (!session->callid) {
3653 session->callid = g_strdup(callid);
3656 session->is_multiparty = is_multiparty;
3657 if (roster_manager) {
3658 session->roster_manager = g_strdup(roster_manager);
3661 if (is_multiparty && end_points) {
3662 int i = 0;
3663 while (end_points[i]) {
3664 gchar *end_point = parse_from(end_points[i]);
3665 gchar *epid = sipmsg_find_part_of_header(end_points[i], "epid=", ";", NULL);
3667 if (!g_strcasecmp(from, end_point) || !g_strcasecmp(to, end_point)) {
3668 i++;
3669 continue;
3672 dialog = get_dialog(session, end_point);
3673 if (!dialog) {
3674 dialog = g_new0(struct sip_dialog, 1);
3675 session->dialogs = g_slist_append(session->dialogs, dialog);
3677 dialog->callid = g_strdup(session->callid);
3678 dialog->with = g_strdup(end_point);
3679 dialog->theirepid = epid;
3681 just_joined = TRUE;
3683 /* send triggered INVITE */
3684 sipe_invite(sip, session, end_point, NULL, NULL, TRUE);
3685 } else {
3686 dialog->theirepid = epid;
3689 i++;
3693 if (session) {
3694 dialog = get_dialog(session, from);
3695 if (dialog) {
3696 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3697 } else {
3698 dialog = g_new0(struct sip_dialog, 1);
3699 session->dialogs = g_slist_append(session->dialogs, dialog);
3701 dialog->callid = g_strdup(session->callid);
3702 dialog->with = g_strdup(from);
3703 sipe_parse_dialog(msg, dialog, FALSE);
3705 if (!dialog->ourtag) {
3706 dialog->ourtag = newTag;
3707 newTag = NULL;
3710 just_joined = TRUE;
3712 } else {
3713 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3715 g_free(newTag);
3717 if (is_multiparty && !session->conv) {
3718 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3719 gchar *self = g_strdup_printf("sip:%s", sip->username);
3720 /* create prpl chat */
3721 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3722 session->chat_name = g_strdup(chat_name);
3723 /* add self */
3724 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3725 self, NULL,
3726 PURPLE_CBFLAGS_NONE, FALSE);
3727 g_free(chat_name);
3728 g_free(self);
3731 if (is_multiparty && !was_multiparty) {
3732 /* add current IM counterparty to chat */
3733 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3734 ((struct sip_dialog *)session->dialogs->data)->with, NULL,
3735 PURPLE_CBFLAGS_NONE, FALSE);
3739 /* add inviting party */
3740 if (just_joined) {
3741 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3742 from, NULL,
3743 PURPLE_CBFLAGS_NONE, TRUE);
3746 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3747 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3748 if (ms_text_format) {
3749 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3751 gchar *html = get_html_message(ms_text_format, NULL);
3752 if (html) {
3753 if (is_multiparty) {
3754 serv_got_chat_in(sip->gc, session->chat_id, from,
3755 PURPLE_MESSAGE_RECV, html, time(NULL));
3756 } else {
3757 serv_got_im(sip->gc, from, html, 0, time(NULL));
3759 g_free(html);
3760 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3764 g_free(from);
3766 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3767 sipmsg_remove_header(msg, "Ms-Text-Format");
3768 sipmsg_remove_header(msg, "EndPoints");
3769 sipmsg_remove_header(msg, "User-Agent");
3770 sipmsg_remove_header(msg, "Roster-Manager");
3771 sipmsg_remove_header(msg, "P-Asserted-Identity");
3772 sipmsg_remove_header(msg, "Require");
3774 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3775 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3777 body = g_strdup_printf(
3778 "v=0\r\n"
3779 "o=- 0 0 IN IP4 %s\r\n"
3780 "s=session\r\n"
3781 "c=IN IP4 %s\r\n"
3782 "t=0 0\r\n"
3783 "m=message %d sip sip:%s\r\n"
3784 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3785 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3786 sip->realport, sip->username);
3787 send_sip_response(sip->gc, msg, 200, "OK", body);
3788 g_free(body);
3791 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3793 gchar *body;
3795 sipmsg_remove_header(msg, "Ms-Conversation-ID");
3796 sipmsg_remove_header(msg, "EndPoints");
3797 sipmsg_remove_header(msg, "User-Agent");
3799 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3800 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3802 body = g_strdup_printf(
3803 "v=0\r\n"
3804 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3805 "s=session\r\n"
3806 "c=IN IP4 0.0.0.0\r\n"
3807 "t=0 0\r\n"
3808 "m=message %d sip sip:%s\r\n"
3809 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3810 sip->realport, sip->username);
3811 send_sip_response(sip->gc, msg, 200, "OK", body);
3812 g_free(body);
3815 static void sipe_connection_cleanup(struct sipe_account_data *);
3816 static void create_connection(struct sipe_account_data *, gchar *, int);
3818 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3820 gchar *tmp;
3821 const gchar *expires_header;
3822 int expires, i;
3823 GSList *hdr = msg->headers;
3824 GSList *entry;
3825 struct siphdrelement *elem;
3827 expires_header = sipmsg_find_header(msg, "Expires");
3828 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3829 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3831 switch (msg->response) {
3832 case 200:
3833 if (expires == 0) {
3834 sip->registerstatus = 0;
3835 } else {
3836 gchar *contact_hdr = NULL;
3837 gchar *gruu = NULL;
3838 gchar *epid;
3839 gchar *uuid;
3840 gchar *timeout;
3842 if (!sip->reregister_set) {
3843 gchar *action_name = g_strdup_printf("<%s>", "registration");
3844 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3845 g_free(action_name);
3846 sip->reregister_set = TRUE;
3849 sip->registerstatus = 3;
3851 #ifdef USE_KERBEROS
3852 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3853 #endif
3854 tmp = sipmsg_find_auth_header(msg, "NTLM");
3855 #ifdef USE_KERBEROS
3856 } else {
3857 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3859 #endif
3860 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3861 fill_auth(sip, tmp, &sip->registrar);
3863 if (!sip->reauthenticate_set) {
3864 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3865 guint reauth_timeout;
3866 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3867 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3868 reauth_timeout = sip->registrar.expires - 300;
3869 } else {
3870 /* NTLM: we have to reauthenticate as our security token expires
3871 after eight hours (be five minutes early) */
3872 reauth_timeout = (8 * 3600) - 300;
3874 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3875 g_free(action_name);
3876 sip->reauthenticate_set = TRUE;
3879 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3881 epid = get_epid(sip);
3882 uuid = generateUUIDfromEPID(epid);
3883 g_free(epid);
3885 // There can be multiple Contact headers (one per location where the user is logged in) so
3886 // make sure to only get the one for this uuid
3887 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3888 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3889 if (valid_contact) {
3890 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3891 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3892 g_free(valid_contact);
3893 break;
3894 } else {
3895 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3898 g_free(uuid);
3900 g_free(sip->contact);
3901 if(gruu) {
3902 sip->contact = g_strdup_printf("<%s>", gruu);
3903 g_free(gruu);
3904 } else {
3905 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3906 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);
3908 sip->msrtc_event_categories = FALSE;
3909 sip->batched_support = FALSE;
3911 while(hdr)
3913 elem = hdr->data;
3914 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3915 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3916 sip->msrtc_event_categories = TRUE;
3917 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3919 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3920 sip->batched_support = TRUE;
3921 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3924 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3925 gchar **caps = g_strsplit(elem->value,",",0);
3926 i = 0;
3927 while (caps[i]) {
3928 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3929 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3930 i++;
3932 g_strfreev(caps);
3934 hdr = g_slist_next(hdr);
3937 if (!sip->subscribed) { //do it just once, not every re-register
3938 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3939 //sipe_options_request(sip, sip->sipdomain);
3941 entry = sip->allow_events;
3942 while (entry) {
3943 tmp = entry->data;
3944 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3945 sipe_subscribe_roaming_contacts(sip, msg);
3947 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3948 sipe_subscribe_roaming_acl(sip, msg);
3950 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3951 sipe_subscribe_roaming_self(sip, msg);
3953 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3954 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3955 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3956 sipe_subscribe_roaming_provisioning(sip, msg);
3958 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3959 sipe_subscribe_presence_wpending(sip, msg);
3961 entry = entry->next;
3963 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3964 sip->subscribed = TRUE;
3967 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3968 "timeout=", ";", NULL);
3969 if (timeout != NULL) {
3970 sscanf(timeout, "%u", &sip->keepalive_timeout);
3971 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3972 sip->keepalive_timeout);
3973 g_free(timeout);
3976 // Should we remove the transaction here?
3977 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3978 transactions_remove(sip, tc);
3980 break;
3981 case 301:
3983 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3985 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3986 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3987 gchar **tmp;
3988 gchar *hostname;
3989 int port = 0;
3990 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3991 int i = 1;
3993 tmp = g_strsplit(parts[0], ":", 0);
3994 hostname = g_strdup(tmp[0]);
3995 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3996 g_strfreev(tmp);
3998 while (parts[i]) {
3999 tmp = g_strsplit(parts[i], "=", 0);
4000 if (tmp[1]) {
4001 if (g_strcasecmp("transport", tmp[0]) == 0) {
4002 if (g_strcasecmp("tcp", tmp[1]) == 0) {
4003 transport = SIPE_TRANSPORT_TCP;
4004 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
4005 transport = SIPE_TRANSPORT_UDP;
4009 g_strfreev(tmp);
4010 i++;
4012 g_strfreev(parts);
4014 /* Close old connection */
4015 sipe_connection_cleanup(sip);
4017 /* Create new connection */
4018 sip->transport = transport;
4019 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4020 hostname, port, TRANSPORT_DESCRIPTOR);
4021 create_connection(sip, hostname, port);
4023 g_free(redirect);
4025 break;
4026 case 401:
4027 if (sip->registerstatus != 2) {
4028 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
4029 if (sip->registrar.retries > 3) {
4030 sip->gc->wants_to_die = TRUE;
4031 purple_connection_error(sip->gc, _("Wrong Password"));
4032 return TRUE;
4034 #ifdef USE_KERBEROS
4035 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4036 #endif
4037 tmp = sipmsg_find_auth_header(msg, "NTLM");
4038 #ifdef USE_KERBEROS
4039 } else {
4040 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4042 #endif
4043 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
4044 fill_auth(sip, tmp, &sip->registrar);
4045 sip->registerstatus = 2;
4046 if (sip->account->disconnecting) {
4047 do_register_exp(sip, 0);
4048 } else {
4049 do_register(sip);
4052 break;
4053 case 403:
4055 gchar *warning = sipmsg_find_header(msg, "Warning");
4056 if (warning != NULL) {
4057 /* Example header:
4058 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4060 gchar **tmp = g_strsplit(warning, "\"", 0);
4061 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
4062 g_strfreev(tmp);
4063 } else {
4064 warning = g_strdup(_("You have been rejected by the server"));
4067 sip->gc->wants_to_die = TRUE;
4068 purple_connection_error(sip->gc, warning);
4069 g_free(warning);
4070 return TRUE;
4072 break;
4073 case 404:
4075 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4076 if (warning != NULL) {
4077 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4078 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
4079 g_free(reason);
4080 } else {
4081 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4084 sip->gc->wants_to_die = TRUE;
4085 purple_connection_error(sip->gc, warning);
4086 g_free(warning);
4087 return TRUE;
4089 break;
4090 case 503:
4092 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4093 if (warning != NULL) {
4094 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4095 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4096 g_free(reason);
4097 } else {
4098 warning = g_strdup(_("Service unavailable: no reason given"));
4101 sip->gc->wants_to_die = TRUE;
4102 purple_connection_error(sip->gc, warning);
4103 g_free(warning);
4104 return TRUE;
4106 break;
4108 return TRUE;
4111 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4113 const char *uri;
4114 xmlnode *xn_categories;
4115 xmlnode *xn_category;
4116 xmlnode *xn_node;
4117 const char *activity = NULL;
4119 xn_categories = xmlnode_from_str(data, len);
4120 uri = xmlnode_get_attrib(xn_categories, "uri");
4122 for (xn_category = xmlnode_get_child(xn_categories, "category");
4123 xn_category ;
4124 xn_category = xmlnode_get_next_twin(xn_category) )
4126 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4128 if (!strcmp(attrVar, "note"))
4130 if (uri) {
4131 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4133 if (sbuddy) {
4134 char *note;
4136 xn_node = xmlnode_get_child(xn_category, "note");
4137 if (!xn_node) continue;
4138 xn_node = xmlnode_get_child(xn_node, "body");
4139 if (!xn_node) continue;
4140 note = xmlnode_get_data(xn_node);
4141 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4142 g_free(sbuddy->annotation);
4143 sbuddy->annotation = NULL;
4144 if (note) sbuddy->annotation = g_strdup(note);
4145 g_free(note);
4150 else if(!strcmp(attrVar, "state"))
4152 char *data;
4153 int avail;
4154 xn_node = xmlnode_get_child(xn_category, "state");
4155 if (!xn_node) continue;
4156 xn_node = xmlnode_get_child(xn_node, "availability");
4157 if (!xn_node) continue;
4159 data = xmlnode_get_data(xn_node);
4160 avail = atoi(data);
4161 g_free(data);
4163 if (avail < 3000)
4164 activity = SIPE_STATUS_ID_UNKNOWN;
4165 else if (avail < 4500)
4166 activity = SIPE_STATUS_ID_AVAILABLE;
4167 else if (avail < 6000)
4168 activity = SIPE_STATUS_ID_BRB;
4169 else if (avail < 7500)
4170 activity = SIPE_STATUS_ID_ONPHONE;
4171 else if (avail < 9000)
4172 activity = SIPE_STATUS_ID_BUSY;
4173 else if (avail < 12000)
4174 activity = SIPE_STATUS_ID_DND;
4175 else if (avail < 18000)
4176 activity = SIPE_STATUS_ID_AWAY;
4177 else
4178 activity = SIPE_STATUS_ID_OFFLINE;
4181 if(activity) {
4182 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4183 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4186 xmlnode_free(xn_categories);
4189 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4191 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4192 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4193 payload->host = g_strdup(host);
4194 payload->buddies = server;
4195 sipe_subscribe_presence_batched_routed(sip, payload);
4196 sipe_subscribe_presence_batched_routed_free(payload);
4199 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4201 xmlnode *xn_list;
4202 xmlnode *xn_resource;
4203 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4204 g_free, NULL);
4205 GSList *server;
4206 gchar *host;
4208 xn_list = xmlnode_from_str(data, len);
4210 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4211 xn_resource;
4212 xn_resource = xmlnode_get_next_twin(xn_resource) )
4214 const char *uri, *state;
4215 xmlnode *xn_instance;
4217 xn_instance = xmlnode_get_child(xn_resource, "instance");
4218 if (!xn_instance) continue;
4220 uri = xmlnode_get_attrib(xn_resource, "uri");
4221 state = xmlnode_get_attrib(xn_instance, "state");
4222 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4224 if (strstr(state, "resubscribe")) {
4225 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4226 struct sipe_buddy *sbuddy;
4227 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4228 gchar *user = g_strdup(uri);
4229 host = g_strdup(poolFqdn);
4230 server = g_hash_table_lookup(servers, host);
4231 server = g_slist_append(server, user);
4232 g_hash_table_insert(servers, host, server);
4233 } else {
4234 sipe_subscribe_presence_single(sip, (void *) uri);
4236 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4237 if (sbuddy) {
4238 sbuddy->resubscribed = TRUE;
4243 /* Send out any deferred poolFqdn subscriptions */
4244 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4245 g_hash_table_destroy(servers);
4247 xmlnode_free(xn_list);
4250 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4252 const gchar *uri;
4253 gchar *getbasic;
4254 gchar *activity = NULL;
4255 xmlnode *pidf;
4256 xmlnode *basicstatus = NULL, *tuple, *status;
4257 gboolean isonline = FALSE;
4258 xmlnode *display_name_node;
4260 pidf = xmlnode_from_str(data, len);
4261 if (!pidf) {
4262 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4263 return;
4266 uri = xmlnode_get_attrib(pidf, "entity");
4268 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4270 if ((status = xmlnode_get_child(tuple, "status"))) {
4271 basicstatus = xmlnode_get_child(status, "basic");
4275 if (!basicstatus) {
4276 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4277 xmlnode_free(pidf);
4278 return;
4281 getbasic = xmlnode_get_data(basicstatus);
4282 if (!getbasic) {
4283 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4284 xmlnode_free(pidf);
4285 return;
4288 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4289 if (strstr(getbasic, "open")) {
4290 isonline = TRUE;
4292 g_free(getbasic);
4294 display_name_node = xmlnode_get_child(pidf, "display-name");
4295 // updating display name if alias was just URI
4296 if (display_name_node) {
4297 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4298 GSList *entry = buddies;
4299 PurpleBuddy *p_buddy;
4300 char * display_name = xmlnode_get_data(display_name_node);
4302 while (entry) {
4303 const char *server_alias;
4304 char *alias;
4306 p_buddy = entry->data;
4308 alias = (char *)purple_buddy_get_alias(p_buddy);
4309 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
4310 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4311 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4312 purple_blist_alias_buddy(p_buddy, display_name);
4314 g_free(alias);
4316 server_alias = purple_buddy_get_server_alias(p_buddy);
4317 if (display_name &&
4318 ( (server_alias && strcmp(display_name, server_alias))
4319 || !server_alias || strlen(server_alias) == 0 )
4321 purple_blist_server_alias_buddy(p_buddy, display_name);
4324 entry = entry->next;
4326 g_free(display_name);
4329 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4330 if ((status = xmlnode_get_child(tuple, "status"))) {
4331 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4332 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4333 activity = xmlnode_get_data(basicstatus);
4334 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4340 if (isonline) {
4341 const gchar * status_id = NULL;
4342 if (activity) {
4343 if (strstr(activity, "busy")) {
4344 status_id = SIPE_STATUS_ID_BUSY;
4345 } else if (strstr(activity, "away")) {
4346 status_id = SIPE_STATUS_ID_AWAY;
4350 if (!status_id) {
4351 status_id = SIPE_STATUS_ID_AVAILABLE;
4354 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4355 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4356 } else {
4357 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4360 g_free(activity);
4361 xmlnode_free(pidf);
4364 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4366 const char *availability;
4367 const char *activity;
4368 const char *display_name = NULL;
4369 const char *activity_name = NULL;
4370 const char *name;
4371 char *uri;
4372 int avl;
4373 int act;
4374 struct sipe_buddy *sbuddy;
4376 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4378 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4379 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4380 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4381 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4382 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4383 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4384 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4385 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4387 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4388 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4389 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4390 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4391 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4392 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4394 name = xmlnode_get_attrib(xn_presentity, "uri");
4395 uri = g_strdup_printf("sip:%s", name);
4396 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4397 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4399 // updating display name if alias was just URI
4400 if (xn_display_name) {
4401 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4402 GSList *entry = buddies;
4403 PurpleBuddy *p_buddy;
4404 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4406 while (entry) {
4407 const char *email_str, *server_alias;
4409 p_buddy = entry->data;
4411 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4412 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4413 purple_blist_alias_buddy(p_buddy, display_name);
4416 server_alias = purple_buddy_get_server_alias(p_buddy);
4417 if (display_name &&
4418 ( (server_alias && strcmp(display_name, server_alias))
4419 || !server_alias || strlen(server_alias) == 0 )
4421 purple_blist_server_alias_buddy(p_buddy, display_name);
4424 if (email) {
4425 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4426 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4427 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4431 entry = entry->next;
4435 avl = atoi(availability);
4436 act = atoi(activity);
4438 if(sip->msrtc_event_categories){
4439 if (act == 100 && avl == 0)
4440 activity_name = SIPE_STATUS_ID_OFFLINE;
4441 else if (act == 100 && avl == 300)
4442 activity_name = SIPE_STATUS_ID_AWAY;
4443 else if (act == 300 && avl == 300)
4444 activity_name = SIPE_STATUS_ID_BRB;
4445 else if (act == 400 && avl == 300)
4446 activity_name = SIPE_STATUS_ID_AVAILABLE;
4447 else if (act == 500 && act == 300)
4448 activity_name = SIPE_STATUS_ID_ONPHONE;
4449 else if (act == 600 && avl == 300)
4450 activity_name = SIPE_STATUS_ID_BUSY;
4451 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4452 if(avail){ //Check for LegacyInterop elements
4453 avl = atoi(avail);
4454 if(avl == 18500)
4455 activity_name = SIPE_STATUS_ID_OFFLINE;
4456 else if (avl == 3500)
4457 activity_name = SIPE_STATUS_ID_AVAILABLE;
4458 else if (avl == 15500)
4459 activity_name = SIPE_STATUS_ID_AWAY;
4460 else if (avl == 6500)
4461 activity_name = SIPE_STATUS_ID_BUSY;
4462 else if (avl == 12500)
4463 activity_name = SIPE_STATUS_ID_BRB;
4468 if(activity_name == NULL){
4469 if (act <= 100)
4470 activity_name = SIPE_STATUS_ID_AWAY;
4471 else if (act <= 150)
4472 activity_name = SIPE_STATUS_ID_LUNCH;
4473 else if (act <= 300)
4474 activity_name = SIPE_STATUS_ID_BRB;
4475 else if (act <= 400)
4476 activity_name = SIPE_STATUS_ID_AVAILABLE;
4477 else if (act <= 500)
4478 activity_name = SIPE_STATUS_ID_ONPHONE;
4479 else if (act <= 600)
4480 activity_name = SIPE_STATUS_ID_BUSY;
4481 else
4482 activity_name = SIPE_STATUS_ID_AVAILABLE;
4484 if (avl == 0)
4485 activity_name = SIPE_STATUS_ID_OFFLINE;
4488 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4489 if (sbuddy)
4491 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4492 sbuddy->annotation = NULL;
4493 if (note) { sbuddy->annotation = g_strdup(note); }
4495 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4496 sbuddy->device_name = NULL;
4497 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4500 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4501 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4502 g_free(note);
4503 xmlnode_free(xn_presentity);
4504 g_free(uri);
4507 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4509 char *ctype = sipmsg_find_header(msg, "Content-Type");
4511 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4513 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4514 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4516 const char *content = msg->body;
4517 unsigned length = msg->bodylen;
4518 PurpleMimeDocument *mime = NULL;
4520 if (strstr(ctype, "multipart"))
4522 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4523 const char *content_type;
4524 GList* parts;
4525 mime = purple_mime_document_parse(doc);
4526 parts = purple_mime_document_get_parts(mime);
4527 while(parts) {
4528 content = purple_mime_part_get_data(parts->data);
4529 length = purple_mime_part_get_length(parts->data);
4530 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4531 if(content_type && strstr(content_type,"application/rlmi+xml"))
4533 process_incoming_notify_rlmi_resub(sip, content, length);
4535 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4537 process_incoming_notify_msrtc(sip, content, length);
4539 else
4541 process_incoming_notify_rlmi(sip, content, length);
4543 parts = parts->next;
4545 g_free(doc);
4547 if (mime)
4549 purple_mime_document_free(mime);
4552 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4554 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4556 else if(strstr(ctype, "application/rlmi+xml"))
4558 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4561 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4563 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4565 else
4567 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4571 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4573 char *ctype = sipmsg_find_header(msg, "Content-Type");
4574 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4576 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4578 if (ctype &&
4579 strstr(ctype, "multipart") &&
4580 (strstr(ctype, "application/rlmi+xml") ||
4581 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4582 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4583 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4584 GList *parts = purple_mime_document_get_parts(mime);
4585 GSList *buddies = NULL;
4586 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4588 while (parts) {
4589 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4590 purple_mime_part_get_length(parts->data));
4591 gchar *uri = g_strdup(xmlnode_get_attrib(xml, "uri"));
4593 if (strstr(uri, "sip:") == NULL) {
4594 gchar *tmp = uri;
4595 uri = g_strdup_printf("sip:%s", tmp);
4596 g_free(tmp);
4598 buddies = g_slist_append(buddies, uri);
4599 xmlnode_free(xml);
4601 parts = parts->next;
4603 g_free(doc);
4604 if (mime) purple_mime_document_free(mime);
4606 payload->host = who;
4607 payload->buddies = buddies;
4608 sipe_schedule_action(action_name, timeout,
4609 sipe_subscribe_presence_batched_routed,
4610 sipe_subscribe_presence_batched_routed_free,
4611 sip, payload);
4612 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4614 } else {
4615 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4616 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4618 g_free(action_name);
4622 * Dispatcher for all incoming subscription information
4623 * whether it comes from NOTIFY, BENOTIFY requests or
4624 * piggy-backed to subscription's OK responce.
4626 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4627 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4629 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4631 gchar *event = sipmsg_find_header(msg, "Event");
4632 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4633 int timeout = 0;
4635 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4636 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
4638 if (!request)
4640 const gchar *expires_header;
4641 expires_header = sipmsg_find_header(msg, "Expires");
4642 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4643 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4644 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4647 if (!subscription_state || strstr(subscription_state, "active"))
4649 if (event && !g_ascii_strcasecmp(event, "presence"))
4651 sipe_process_presence(sip, msg);
4653 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4655 sipe_process_roaming_contacts(sip, msg, NULL);
4657 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
4659 sipe_process_roaming_self(sip, msg);
4661 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4663 sipe_process_roaming_acl(sip, msg);
4665 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4667 sipe_process_presence_wpending(sip, msg);
4669 else
4671 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
4675 //The server sends a (BE)NOTIFY with the status 'terminated'
4676 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4677 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4678 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4679 g_free(from);
4682 if (timeout && event) {// For LSC 2005 and OCS 2007
4683 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4684 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4686 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4687 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4688 g_free(action_name);
4690 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4691 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4693 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4694 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4695 g_free(action_name);
4697 else*/
4698 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4699 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4701 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4702 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4703 g_free(action_name);
4705 else if (!g_ascii_strcasecmp(event, "presence") &&
4706 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4708 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4709 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4710 if(sip->batched_support) {
4711 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
4712 if(!g_ascii_strcasecmp(who, my_self)){
4713 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4714 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4715 g_free(who); /* unused */
4717 else {
4718 sipe_process_presence_timeout(sip, msg, who, timeout);
4720 g_free(my_self);
4722 else {
4723 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4724 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4726 g_free(action_name);
4727 /* "who" will be freed by the action we just scheduled */
4731 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4733 sipe_process_registration_notify(sip, msg);
4736 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4737 if (request && !benotify)
4739 sipmsg_remove_header(msg, "Expires");
4740 sipmsg_remove_header(msg, "subscription-state");
4741 sipmsg_remove_header(msg, "Event");
4742 sipmsg_remove_header(msg, "Require");
4743 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4748 * unused. Needed?
4750 static gchar* gen_xpidf(struct sipe_account_data *sip)
4752 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4753 "<presence>\r\n"
4754 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4755 "<display name=\"sip:%s\"/>\r\n"
4756 "<atom id=\"1234\">\r\n"
4757 "<address uri=\"sip:%s\">\r\n"
4758 "<status status=\"%s\"/>\r\n"
4759 "</address>\r\n"
4760 "</atom>\r\n"
4761 "</presence>\r\n",
4762 sip->username,
4763 sip->username,
4764 sip->username,
4765 sip->status);
4766 return doc;
4771 static gchar* gen_pidf(struct sipe_account_data *sip)
4773 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4774 "<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"
4775 "<tuple id=\"0\">\r\n"
4776 "<status>\r\n"
4777 "<basic>open</basic>\r\n"
4778 "<ep:activities>\r\n"
4779 " <ep:activity>%s</ep:activity>\r\n"
4780 "</ep:activities>"
4781 "</status>\r\n"
4782 "</tuple>\r\n"
4783 "<ci:display-name>%s</ci:display-name>\r\n"
4784 "</presence>",
4785 sip->username,
4786 sip->status,
4787 sip->username);
4788 return doc;
4792 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4794 int availability = 300; // online
4795 int activity = 400; // Available
4796 gchar *name;
4797 gchar *body;
4798 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4799 activity = 100;
4800 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4801 activity = 150;
4802 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4803 activity = 300;
4804 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4805 activity = 400;
4806 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4807 activity = 500;
4808 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4809 activity = 600;
4810 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4811 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4812 availability = 0; // offline
4813 activity = 100;
4814 } else {
4815 activity = 400; // available
4818 name = g_strdup_printf("sip: sip:%s", sip->username);
4819 //@TODO: send user data - state; add hostname in upper case
4820 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4821 send_soap_request_with_cb(sip, body, NULL , NULL);
4822 g_free(name);
4823 g_free(body);
4826 static gboolean
4827 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4829 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4830 if (msg->response == 200) {
4831 sip->status_version = 0;
4832 send_presence_status(sip);
4834 return TRUE;
4837 static gboolean
4838 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4840 if (msg->response == 409) {
4841 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4842 // TODO need to parse the version #'s?
4843 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4844 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4845 gchar *tmp;
4846 gchar *hdr;
4848 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4850 tmp = get_contact(sip);
4851 hdr = g_strdup_printf("Contact: %s\r\n"
4852 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4854 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4856 g_free(tmp);
4857 g_free(hdr);
4858 g_free(uri);
4859 g_free(doc);
4861 return TRUE;
4864 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4866 int code;
4867 gchar *uri;
4868 gchar *doc;
4869 gchar *tmp;
4870 gchar *hdr;
4871 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4872 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4873 code = 12000;
4874 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4875 code = 9000;
4876 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4877 code = 7500;
4878 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4879 code = 6000;
4880 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4881 code = 4500;
4882 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4883 code = 3000;
4884 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4885 code = 0;
4886 } else {
4887 // Offline or invisible
4888 code = 18000;
4891 uri = g_strdup_printf("sip:%s", sip->username);
4892 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4893 sip->status_version, code,
4894 sip->status_version, code,
4895 sip->status_version, note ? note : "",
4896 sip->status_version, note ? note : "",
4897 sip->status_version, note ? note : ""
4899 sip->status_version++;
4901 tmp = get_contact(sip);
4902 hdr = g_strdup_printf("Contact: %s\r\n"
4903 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4905 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4907 g_free(tmp);
4908 g_free(hdr);
4909 g_free(uri);
4910 g_free(doc);
4913 static void send_presence_status(struct sipe_account_data *sip)
4915 PurpleStatus * status = purple_account_get_active_status(sip->account);
4916 const gchar *note;
4917 if (!status) return;
4919 note = purple_status_get_attr_string(status, "message");
4921 if(sip->msrtc_event_categories){
4922 send_presence_category_publish(sip, note);
4923 } else {
4924 send_presence_soap(sip, note);
4928 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4930 gboolean found = FALSE;
4931 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4932 if (msg->response == 0) { /* request */
4933 if (!strcmp(msg->method, "MESSAGE")) {
4934 process_incoming_message(sip, msg);
4935 found = TRUE;
4936 } else if (!strcmp(msg->method, "NOTIFY")) {
4937 purple_debug_info("sipe","send->process_incoming_notify\n");
4938 process_incoming_notify(sip, msg, TRUE, FALSE);
4939 found = TRUE;
4940 } else if (!strcmp(msg->method, "BENOTIFY")) {
4941 purple_debug_info("sipe","send->process_incoming_benotify\n");
4942 process_incoming_notify(sip, msg, TRUE, TRUE);
4943 found = TRUE;
4944 } else if (!strcmp(msg->method, "INVITE")) {
4945 process_incoming_invite(sip, msg);
4946 found = TRUE;
4947 } else if (!strcmp(msg->method, "REFER")) {
4948 process_incoming_refer(sip, msg);
4949 found = TRUE;
4950 } else if (!strcmp(msg->method, "OPTIONS")) {
4951 process_incoming_options(sip, msg);
4952 found = TRUE;
4953 } else if (!strcmp(msg->method, "INFO")) {
4954 process_incoming_info(sip, msg);
4955 found = TRUE;
4956 } else if (!strcmp(msg->method, "ACK")) {
4957 // ACK's don't need any response
4958 found = TRUE;
4959 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4960 // LCS 2005 sends us these - just respond 200 OK
4961 found = TRUE;
4962 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4963 } else if (!strcmp(msg->method, "BYE")) {
4964 process_incoming_bye(sip, msg);
4965 found = TRUE;
4966 } else {
4967 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4969 } else { /* response */
4970 struct transaction *trans = transactions_find(sip, msg);
4971 if (trans) {
4972 if (msg->response == 407) {
4973 gchar *resend, *auth, *ptmp;
4975 if (sip->proxy.retries > 30) return;
4976 sip->proxy.retries++;
4977 /* do proxy authentication */
4979 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4981 fill_auth(sip, ptmp, &sip->proxy);
4982 auth = auth_header(sip, &sip->proxy, trans->msg);
4983 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
4984 sipmsg_add_header_pos(trans->msg, "Proxy-Authorization", auth, 5);
4985 g_free(auth);
4986 resend = sipmsg_to_string(trans->msg);
4987 /* resend request */
4988 sendout_pkt(sip->gc, resend);
4989 g_free(resend);
4990 } else {
4991 if (msg->response == 100 || msg->response == 180) {
4992 /* ignore provisional response */
4993 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4994 } else {
4995 sip->proxy.retries = 0;
4996 if (!strcmp(trans->msg->method, "REGISTER")) {
4997 if (msg->response == 401)
4999 sip->registrar.retries++;
5001 else
5003 sip->registrar.retries = 0;
5005 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
5006 } else {
5007 if (msg->response == 401) {
5008 gchar *resend, *auth, *ptmp;
5010 if (sip->registrar.retries > 4) return;
5011 sip->registrar.retries++;
5013 #ifdef USE_KERBEROS
5014 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5015 #endif
5016 ptmp = sipmsg_find_auth_header(msg, "NTLM");
5017 #ifdef USE_KERBEROS
5018 } else {
5019 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
5021 #endif
5023 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
5025 fill_auth(sip, ptmp, &sip->registrar);
5026 auth = auth_header(sip, &sip->registrar, trans->msg);
5027 sipmsg_remove_header(trans->msg, "Proxy-Authorization");
5028 sipmsg_add_header(trans->msg, "Proxy-Authorization", auth);
5030 //sipmsg_remove_header(trans->msg, "Authorization");
5031 //sipmsg_add_header(trans->msg, "Authorization", auth);
5032 g_free(auth);
5033 resend = sipmsg_to_string(trans->msg);
5034 /* resend request */
5035 sendout_pkt(sip->gc, resend);
5036 g_free(resend);
5040 if (trans->callback) {
5041 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
5042 /* call the callback to process response*/
5043 (trans->callback)(sip, msg, trans);
5045 /* Not sure if this is needed or what needs to be done
5046 but transactions seem to be removed prematurely so
5047 this only removes them if the response is 200 OK */
5048 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
5049 /*Has a bug and it's unneccesary*/
5050 /*transactions_remove(sip, trans);*/
5054 found = TRUE;
5055 } else {
5056 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
5059 if (!found) {
5060 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
5064 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
5066 char *cur;
5067 char *dummy;
5068 struct sipmsg *msg;
5069 int restlen;
5070 cur = conn->inbuf;
5072 /* according to the RFC remove CRLF at the beginning */
5073 while (*cur == '\r' || *cur == '\n') {
5074 cur++;
5076 if (cur != conn->inbuf) {
5077 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
5078 conn->inbufused = strlen(conn->inbuf);
5081 /* Received a full Header? */
5082 sip->processing_input = TRUE;
5083 while (sip->processing_input &&
5084 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
5085 time_t currtime = time(NULL);
5086 cur += 2;
5087 cur[0] = '\0';
5088 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
5089 msg = sipmsg_parse_header(conn->inbuf);
5090 cur[0] = '\r';
5091 cur += 2;
5092 restlen = conn->inbufused - (cur - conn->inbuf);
5093 if (restlen >= msg->bodylen) {
5094 dummy = g_malloc(msg->bodylen + 1);
5095 memcpy(dummy, cur, msg->bodylen);
5096 dummy[msg->bodylen] = '\0';
5097 msg->body = dummy;
5098 cur += msg->bodylen;
5099 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
5100 conn->inbufused = strlen(conn->inbuf);
5101 } else {
5102 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5103 restlen, msg->bodylen, (int)strlen(conn->inbuf));
5104 sipmsg_free(msg);
5105 return;
5108 /*if (msg->body) {
5109 purple_debug_info("sipe", "body:\n%s", msg->body);
5112 // Verify the signature before processing it
5113 if (sip->registrar.gssapi_context) {
5114 struct sipmsg_breakdown msgbd;
5115 gchar *signature_input_str;
5116 gchar *rspauth;
5117 msgbd.msg = msg;
5118 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5119 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5121 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5123 if (rspauth != NULL) {
5124 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5125 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5126 process_input_message(sip, msg);
5127 } else {
5128 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5129 purple_connection_error(sip->gc, _("Invalid message signature received"));
5130 sip->gc->wants_to_die = TRUE;
5132 } else if (msg->response == 401) {
5133 purple_connection_error(sip->gc, _("Wrong Password"));
5134 sip->gc->wants_to_die = TRUE;
5136 g_free(signature_input_str);
5138 g_free(rspauth);
5139 sipmsg_breakdown_free(&msgbd);
5140 } else {
5141 process_input_message(sip, msg);
5144 sipmsg_free(msg);
5148 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
5150 PurpleConnection *gc = data;
5151 struct sipe_account_data *sip = gc->proto_data;
5152 struct sipmsg *msg;
5153 int len;
5154 time_t currtime;
5156 static char buffer[65536];
5157 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5158 buffer[len] = '\0';
5159 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5160 msg = sipmsg_parse_msg(buffer);
5161 if (msg) process_input_message(sip, msg);
5165 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5167 struct sipe_account_data *sip = gc->proto_data;
5168 PurpleSslConnection *gsc = sip->gsc;
5170 purple_debug_error("sipe", "%s",debug);
5171 purple_connection_error(gc, msg);
5173 /* Invalidate this connection. Next send will open a new one */
5174 if (gsc) {
5175 connection_remove(sip, gsc->fd);
5176 purple_ssl_close(gsc);
5178 sip->gsc = NULL;
5179 sip->fd = -1;
5182 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5184 PurpleConnection *gc = data;
5185 struct sipe_account_data *sip;
5186 struct sip_connection *conn;
5187 int readlen, len;
5188 gboolean firstread = TRUE;
5190 /* NOTE: This check *IS* necessary */
5191 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5192 purple_ssl_close(gsc);
5193 return;
5196 sip = gc->proto_data;
5197 conn = connection_find(sip, gsc->fd);
5198 if (conn == NULL) {
5199 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5200 gc->wants_to_die = TRUE;
5201 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5202 return;
5205 /* Read all available data from the SSL connection */
5206 do {
5207 /* Increase input buffer size as needed */
5208 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5209 conn->inbuflen += SIMPLE_BUF_INC;
5210 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5211 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5214 /* Try to read as much as there is space left in the buffer */
5215 readlen = conn->inbuflen - conn->inbufused - 1;
5216 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5218 if (len < 0 && errno == EAGAIN) {
5219 /* Try again later */
5220 return;
5221 } else if (len < 0) {
5222 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5223 return;
5224 } else if (firstread && (len == 0)) {
5225 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5226 return;
5229 conn->inbufused += len;
5230 firstread = FALSE;
5232 /* Equivalence indicates that there is possibly more data to read */
5233 } while (len == readlen);
5235 conn->inbuf[conn->inbufused] = '\0';
5236 process_input(sip, conn);
5240 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
5242 PurpleConnection *gc = data;
5243 struct sipe_account_data *sip = gc->proto_data;
5244 int len;
5245 struct sip_connection *conn = connection_find(sip, source);
5246 if (!conn) {
5247 purple_debug_error("sipe", "Connection not found!\n");
5248 return;
5251 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5252 conn->inbuflen += SIMPLE_BUF_INC;
5253 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5256 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5258 if (len < 0 && errno == EAGAIN)
5259 return;
5260 else if (len <= 0) {
5261 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5262 connection_remove(sip, source);
5263 if (sip->fd == source) sip->fd = -1;
5264 return;
5267 conn->inbufused += len;
5268 conn->inbuf[conn->inbufused] = '\0';
5270 process_input(sip, conn);
5273 /* Callback for new connections on incoming TCP port */
5274 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
5276 PurpleConnection *gc = data;
5277 struct sipe_account_data *sip = gc->proto_data;
5278 struct sip_connection *conn;
5280 int newfd = accept(source, NULL, NULL);
5282 conn = connection_create(sip, newfd);
5284 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5287 static void login_cb(gpointer data, gint source, const gchar *error_message)
5289 PurpleConnection *gc = data;
5290 struct sipe_account_data *sip;
5291 struct sip_connection *conn;
5293 if (!PURPLE_CONNECTION_IS_VALID(gc))
5295 if (source >= 0)
5296 close(source);
5297 return;
5300 if (source < 0) {
5301 purple_connection_error(gc, _("Could not connect"));
5302 return;
5305 sip = gc->proto_data;
5306 sip->fd = source;
5307 sip->last_keepalive = time(NULL);
5309 conn = connection_create(sip, source);
5311 do_register(sip);
5313 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5316 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5318 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5319 if (sip == NULL) return;
5321 do_register(sip);
5324 static guint sipe_ht_hash_nick(const char *nick)
5326 char *lc = g_utf8_strdown(nick, -1);
5327 guint bucket = g_str_hash(lc);
5328 g_free(lc);
5330 return bucket;
5333 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5335 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5338 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5340 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5342 sip->listen_data = NULL;
5344 if (listenfd == -1) {
5345 purple_connection_error(sip->gc, _("Could not create listen socket"));
5346 return;
5349 sip->fd = listenfd;
5351 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5352 sip->listenfd = sip->fd;
5354 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5356 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5357 do_register(sip);
5360 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
5362 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5363 int addr_size;
5365 sip->query_data = NULL;
5367 if (!hosts || !hosts->data) {
5368 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5369 return;
5372 addr_size = GPOINTER_TO_INT(hosts->data);
5373 hosts = g_slist_remove(hosts, hosts->data);
5374 memcpy(&(sip->serveraddr), hosts->data, addr_size);
5375 g_free(hosts->data);
5376 hosts = g_slist_remove(hosts, hosts->data);
5377 while (hosts) {
5378 hosts = g_slist_remove(hosts, hosts->data);
5379 g_free(hosts->data);
5380 hosts = g_slist_remove(hosts, hosts->data);
5383 /* create socket for incoming connections */
5384 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5385 sipe_udp_host_resolved_listen_cb, sip);
5386 if (sip->listen_data == NULL) {
5387 purple_connection_error(sip->gc, _("Could not create listen socket"));
5388 return;
5392 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
5393 gpointer data)
5395 PurpleConnection *gc = data;
5396 struct sipe_account_data *sip;
5398 /* If the connection is already disconnected, we don't need to do anything else */
5399 if (!PURPLE_CONNECTION_IS_VALID(gc))
5400 return;
5402 sip = gc->proto_data;
5403 sip->fd = -1;
5404 sip->gsc = NULL;
5406 switch(error) {
5407 case PURPLE_SSL_CONNECT_FAILED:
5408 purple_connection_error(gc, _("Connection Failed"));
5409 break;
5410 case PURPLE_SSL_HANDSHAKE_FAILED:
5411 purple_connection_error(gc, _("SSL Handshake Failed"));
5412 break;
5413 case PURPLE_SSL_CERTIFICATE_INVALID:
5414 purple_connection_error(gc, _("SSL Certificate Invalid"));
5415 break;
5419 static void
5420 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5422 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5423 PurpleProxyConnectData *connect_data;
5425 sip->listen_data = NULL;
5427 sip->listenfd = listenfd;
5428 if (sip->listenfd == -1) {
5429 purple_connection_error(sip->gc, _("Could not create listen socket"));
5430 return;
5433 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5434 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5435 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5436 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5437 sipe_newconn_cb, sip->gc);
5438 purple_debug_info("sipe", "connecting to %s port %d\n",
5439 sip->realhostname, sip->realport);
5440 /* open tcp connection to the server */
5441 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5442 sip->realport, login_cb, sip->gc);
5444 if (connect_data == NULL) {
5445 purple_connection_error(sip->gc, _("Couldn't create socket"));
5450 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5452 PurpleAccount *account = sip->account;
5453 PurpleConnection *gc = sip->gc;
5455 if (purple_account_get_bool(account, "useport", FALSE)) {
5456 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
5457 port = purple_account_get_int(account, "port", 0);
5458 } else {
5459 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5462 sip->realhostname = hostname;
5463 sip->realport = port;
5465 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5466 hostname, port);
5468 /* TODO: is there a good default grow size? */
5469 if (sip->transport != SIPE_TRANSPORT_UDP)
5470 sip->txbuf = purple_circ_buffer_new(0);
5472 if (sip->transport == SIPE_TRANSPORT_TLS) {
5473 /* SSL case */
5474 if (!purple_ssl_is_supported()) {
5475 gc->wants_to_die = TRUE;
5476 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5477 return;
5480 purple_debug_info("sipe", "using SSL\n");
5482 sip->gsc = purple_ssl_connect(account, hostname, port,
5483 login_cb_ssl, sipe_ssl_connect_failure, gc);
5484 if (sip->gsc == NULL) {
5485 purple_connection_error(gc, _("Could not create SSL context"));
5486 return;
5488 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5489 /* UDP case */
5490 purple_debug_info("sipe", "using UDP\n");
5492 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5493 if (sip->query_data == NULL) {
5494 purple_connection_error(gc, _("Could not resolve hostname"));
5496 } else {
5497 /* TCP case */
5498 purple_debug_info("sipe", "using TCP\n");
5499 /* create socket for incoming connections */
5500 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5501 sipe_tcp_connect_listen_cb, sip);
5502 if (sip->listen_data == NULL) {
5503 purple_connection_error(gc, _("Could not create listen socket"));
5504 return;
5509 /* Service list for autodection */
5510 static const struct sipe_service_data service_autodetect[] = {
5511 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5512 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5513 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5514 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5515 { NULL, NULL, 0 }
5518 /* Service list for SSL/TLS */
5519 static const struct sipe_service_data service_tls[] = {
5520 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5521 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5522 { NULL, NULL, 0 }
5525 /* Service list for TCP */
5526 static const struct sipe_service_data service_tcp[] = {
5527 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5528 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5529 { NULL, NULL, 0 }
5532 /* Service list for UDP */
5533 static const struct sipe_service_data service_udp[] = {
5534 { "sip", "udp", SIPE_TRANSPORT_UDP },
5535 { NULL, NULL, 0 }
5538 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5539 static void resolve_next_service(struct sipe_account_data *sip,
5540 const struct sipe_service_data *start)
5542 if (start) {
5543 sip->service_data = start;
5544 } else {
5545 sip->service_data++;
5546 if (sip->service_data->service == NULL) {
5547 gchar *hostname;
5548 /* Try connecting to the SIP hostname directly */
5549 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5550 if (sip->auto_transport) {
5551 // If SSL is supported, default to using it; OCS servers aren't configured
5552 // by default to accept TCP
5553 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5554 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5555 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5558 hostname = g_strdup(sip->sipdomain);
5559 create_connection(sip, hostname, 0);
5560 return;
5564 /* Try to resolve next service */
5565 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5566 sip->service_data->transport,
5567 sip->sipdomain,
5568 srvresolved, sip);
5571 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5573 struct sipe_account_data *sip = data;
5575 sip->srv_query_data = NULL;
5577 /* find the host to connect to */
5578 if (results) {
5579 gchar *hostname = g_strdup(resp->hostname);
5580 int port = resp->port;
5581 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5582 hostname, port);
5583 g_free(resp);
5585 sip->transport = sip->service_data->type;
5587 create_connection(sip, hostname, port);
5588 } else {
5589 resolve_next_service(sip, NULL);
5593 static void sipe_login(PurpleAccount *account)
5595 PurpleConnection *gc;
5596 struct sipe_account_data *sip;
5597 gchar **signinname_login, **userserver, **domain_user;
5598 const char *transport;
5600 const char *username = purple_account_get_username(account);
5601 gc = purple_account_get_connection(account);
5603 if (strpbrk(username, "\t\v\r\n") != NULL) {
5604 gc->wants_to_die = TRUE;
5605 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5606 return;
5609 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5610 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5611 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5612 sip->gc = gc;
5613 sip->account = account;
5614 sip->reregister_set = FALSE;
5615 sip->reauthenticate_set = FALSE;
5616 sip->subscribed = FALSE;
5617 sip->subscribed_buddies = FALSE;
5619 signinname_login = g_strsplit(username, ",", 2);
5621 userserver = g_strsplit(signinname_login[0], "@", 2);
5622 purple_connection_set_display_name(gc, userserver[0]);
5623 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
5624 sip->sipdomain = g_strdup(userserver[1]);
5626 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
5627 gc->wants_to_die = TRUE;
5628 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5629 return;
5632 domain_user = g_strsplit(signinname_login[1], "\\", 2);
5633 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
5634 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
5636 sip->password = g_strdup(purple_connection_get_password(gc));
5638 g_strfreev(userserver);
5639 g_strfreev(domain_user);
5640 g_strfreev(signinname_login);
5642 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5644 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5646 /* TODO: Set the status correctly. */
5647 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5649 transport = purple_account_get_string(account, "transport", "auto");
5650 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
5651 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
5652 SIPE_TRANSPORT_UDP;
5654 if (purple_account_get_bool(account, "useproxy", FALSE)) {
5655 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
5656 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
5657 } else if (strcmp(transport, "auto") == 0) {
5658 sip->auto_transport = TRUE;
5659 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5660 } else if (strcmp(transport, "tls") == 0) {
5661 resolve_next_service(sip, service_tls);
5662 } else if (strcmp(transport, "tcp") == 0) {
5663 resolve_next_service(sip, service_tcp);
5664 } else {
5665 resolve_next_service(sip, service_udp);
5669 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5671 connection_free_all(sip);
5673 g_free(sip->epid);
5674 sip->epid = NULL;
5676 if (sip->query_data != NULL)
5677 purple_dnsquery_destroy(sip->query_data);
5678 sip->query_data = NULL;
5680 if (sip->srv_query_data != NULL)
5681 purple_srv_cancel(sip->srv_query_data);
5682 sip->srv_query_data = NULL;
5684 if (sip->listen_data != NULL)
5685 purple_network_listen_cancel(sip->listen_data);
5686 sip->listen_data = NULL;
5688 if (sip->gsc != NULL)
5689 purple_ssl_close(sip->gsc);
5690 sip->gsc = NULL;
5692 sipe_auth_free(&sip->registrar);
5693 sipe_auth_free(&sip->proxy);
5695 if (sip->txbuf)
5696 purple_circ_buffer_destroy(sip->txbuf);
5697 sip->txbuf = NULL;
5699 g_free(sip->realhostname);
5700 sip->realhostname = NULL;
5702 if (sip->listenpa)
5703 purple_input_remove(sip->listenpa);
5704 sip->listenpa = 0;
5705 if (sip->tx_handler)
5706 purple_input_remove(sip->tx_handler);
5707 sip->tx_handler = 0;
5708 if (sip->resendtimeout)
5709 purple_timeout_remove(sip->resendtimeout);
5710 sip->resendtimeout = 0;
5711 if (sip->timeouts) {
5712 GSList *entry = sip->timeouts;
5713 while (entry) {
5714 struct scheduled_action *sched_action = entry->data;
5715 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5716 purple_timeout_remove(sched_action->timeout_handler);
5717 (*sched_action->destroy)(sched_action->payload);
5718 g_free(sched_action->name);
5719 g_free(sched_action);
5720 entry = entry->next;
5723 g_slist_free(sip->timeouts);
5725 if (sip->allow_events) {
5726 GSList *entry = sip->allow_events;
5727 while (entry) {
5728 g_free(entry->data);
5729 entry = entry->next;
5732 g_slist_free(sip->allow_events);
5734 if (sip->contact)
5735 g_free(sip->contact);
5736 sip->contact = NULL;
5737 if (sip->regcallid)
5738 g_free(sip->regcallid);
5739 sip->regcallid = NULL;
5741 sip->fd = -1;
5742 sip->processing_input = FALSE;
5746 * A callback for g_hash_table_foreach_remove
5748 static gboolean sipe_buddy_remove(gpointer key, gpointer buddy, gpointer user_data)
5750 sipe_free_buddy((struct sipe_buddy *) buddy);
5751 return(TRUE);
5754 static void sipe_close(PurpleConnection *gc)
5756 struct sipe_account_data *sip = gc->proto_data;
5758 if (sip) {
5759 /* leave all conversations */
5760 im_session_close_all(sip);
5762 /* unregister */
5763 do_register_exp(sip, 0);
5765 sipe_connection_cleanup(sip);
5766 g_free(sip->sipdomain);
5767 g_free(sip->username);
5768 g_free(sip->password);
5769 g_free(sip->authdomain);
5770 g_free(sip->authuser);
5771 g_free(sip->status);
5773 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
5774 g_hash_table_destroy(sip->buddies);
5776 g_free(gc->proto_data);
5777 gc->proto_data = NULL;
5780 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
5782 PurpleAccount *acct = purple_connection_get_account(gc);
5783 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
5784 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5785 if (conv == NULL)
5786 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5787 purple_conversation_present(conv);
5788 g_free(id);
5791 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5794 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5795 g_list_nth_data(row, 0), NULL, g_list_nth_data(row, 1));
5798 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5800 PurpleNotifySearchResults *results;
5801 PurpleNotifySearchColumn *column;
5802 xmlnode *searchResults;
5803 xmlnode *mrow;
5804 int match_count = 0;
5805 gboolean more = FALSE;
5806 gchar *secondary;
5808 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5810 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5811 if (!searchResults) {
5812 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5813 return FALSE;
5816 results = purple_notify_searchresults_new();
5818 if (results == NULL) {
5819 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5820 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5822 xmlnode_free(searchResults);
5823 return FALSE;
5826 column = purple_notify_searchresults_column_new(_("User Name"));
5827 purple_notify_searchresults_column_add(results, column);
5829 column = purple_notify_searchresults_column_new(_("Name"));
5830 purple_notify_searchresults_column_add(results, column);
5832 column = purple_notify_searchresults_column_new(_("Company"));
5833 purple_notify_searchresults_column_add(results, column);
5835 column = purple_notify_searchresults_column_new(_("Country"));
5836 purple_notify_searchresults_column_add(results, column);
5838 column = purple_notify_searchresults_column_new(_("Email"));
5839 purple_notify_searchresults_column_add(results, column);
5841 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5842 GList *row = NULL;
5844 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5845 row = g_list_append(row, g_strdup(uri_parts[1]));
5846 g_strfreev(uri_parts);
5848 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5849 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5850 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5851 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5853 purple_notify_searchresults_row_add(results, row);
5854 match_count++;
5857 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5858 char *data = xmlnode_get_data_unescaped(mrow);
5859 more = (g_strcasecmp(data, "true") == 0);
5860 g_free(data);
5863 secondary = g_strdup_printf(
5864 dngettext(GETTEXT_PACKAGE,
5865 "Found %d contact%s:",
5866 "Found %d contacts%s:", match_count),
5867 match_count, more ? _(" (more matched your query)") : "");
5869 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5870 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5871 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5873 g_free(secondary);
5874 xmlnode_free(searchResults);
5875 return TRUE;
5878 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5880 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5881 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5882 unsigned i = 0;
5884 do {
5885 PurpleRequestField *field = entries->data;
5886 const char *id = purple_request_field_get_id(field);
5887 const char *value = purple_request_field_string_get_value(field);
5889 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5891 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5892 } while ((entries = g_list_next(entries)) != NULL);
5893 attrs[i] = NULL;
5895 if (i > 0) {
5896 gchar *query = g_strjoinv(NULL, attrs);
5897 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5898 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5899 send_soap_request_with_cb(gc->proto_data, body,
5900 (TransCallback) process_search_contact_response, NULL);
5901 g_free(body);
5902 g_free(query);
5905 g_strfreev(attrs);
5908 static void sipe_show_find_contact(PurplePluginAction *action)
5910 PurpleConnection *gc = (PurpleConnection *) action->context;
5911 PurpleRequestFields *fields;
5912 PurpleRequestFieldGroup *group;
5913 PurpleRequestField *field;
5915 fields = purple_request_fields_new();
5916 group = purple_request_field_group_new(NULL);
5917 purple_request_fields_add_group(fields, group);
5919 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5920 purple_request_field_group_add_field(group, field);
5921 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5922 purple_request_field_group_add_field(group, field);
5923 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5924 purple_request_field_group_add_field(group, field);
5925 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5926 purple_request_field_group_add_field(group, field);
5928 purple_request_fields(gc,
5929 _("Search"),
5930 _("Search for a Contact"),
5931 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5932 fields,
5933 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5934 _("_Cancel"), NULL,
5935 purple_connection_get_account(gc), NULL, NULL, gc);
5938 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5940 GList *menu = NULL;
5941 PurplePluginAction *act;
5943 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5944 menu = g_list_prepend(menu, act);
5946 menu = g_list_reverse(menu);
5948 return menu;
5951 static void dummy_permit_deny(PurpleConnection *gc)
5955 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5957 return TRUE;
5961 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5963 return TRUE;
5967 static char *sipe_status_text(PurpleBuddy *buddy)
5969 struct sipe_account_data *sip;
5970 struct sipe_buddy *sbuddy;
5971 char *text = NULL;
5973 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5974 if (sip) //happens on pidgin exit
5976 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5977 if (sbuddy && sbuddy->annotation)
5979 text = g_strdup(sbuddy->annotation);
5983 return text;
5986 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5988 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5989 const PurpleStatus *status = purple_presence_get_active_status(presence);
5990 struct sipe_account_data *sip;
5991 struct sipe_buddy *sbuddy;
5992 char *annotation = NULL;
5994 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5995 if (sip) //happens on pidgin exit
5997 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5998 if (sbuddy)
6000 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
6004 //Layout
6005 if (purple_presence_is_online(presence))
6007 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
6010 if (annotation)
6012 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
6013 g_free(annotation);
6018 static GHashTable *
6019 sipe_get_account_text_table(PurpleAccount *account)
6021 GHashTable *table;
6022 table = g_hash_table_new(g_str_hash, g_str_equal);
6023 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
6024 return table;
6027 static PurpleBuddy *
6028 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6030 PurpleBuddy *clone;
6031 const gchar *server_alias, *email;
6032 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6034 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6036 purple_blist_add_buddy(clone, NULL, group, NULL);
6038 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
6039 if (server_alias) {
6040 purple_blist_server_alias_buddy(clone, server_alias);
6043 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6044 if (email) {
6045 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
6048 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6049 //for UI to update;
6050 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6051 return clone;
6054 static void
6055 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6057 PurpleBuddy *buddy, *b;
6058 PurpleConnection *gc;
6059 PurpleGroup * group = purple_find_group(group_name);
6061 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6063 buddy = (PurpleBuddy *)node;
6065 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
6066 gc = purple_account_get_connection(buddy->account);
6068 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6069 if (!b){
6070 b = purple_blist_add_buddy_clone(group, buddy);
6073 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6076 static void
6077 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6079 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6080 gchar *self = g_strdup_printf("sip:%s", sip->username);
6081 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
6082 struct sip_im_session *session;
6084 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6086 session = create_chat_session(sip);
6087 session->roster_manager = g_strdup(self);
6089 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6090 session->chat_name = g_strdup(chat_name);
6091 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6092 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6094 g_free(chat_name);
6095 g_free(self);
6098 static gboolean
6099 sipe_is_election_finished(struct sipe_account_data *sip,
6100 struct sip_im_session *session)
6102 struct sip_dialog *dialog;
6103 GSList *entry;
6104 gboolean res = TRUE;
6106 entry = session->dialogs;
6107 while (entry) {
6108 dialog = entry->data;
6109 if (dialog->election_vote == 0) {
6110 res = FALSE;
6111 break;
6113 entry = entry->next;
6116 if (res) {
6117 session->is_voting_in_progress = FALSE;
6119 return res;
6122 static void
6123 sipe_election_start(struct sipe_account_data *sip,
6124 struct sip_im_session *session)
6126 struct sip_dialog *dialog;
6127 GSList *entry;
6128 int election_timeout;
6130 if (session->is_voting_in_progress) {
6131 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6132 return;
6133 } else {
6134 session->is_voting_in_progress = TRUE;
6136 session->bid = rand();
6138 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6140 /* reset election_vote for each chat participant */
6141 entry = session->dialogs;
6142 while (entry) {
6143 dialog = entry->data;
6144 dialog->election_vote = 0;
6145 entry = entry->next;
6148 entry = session->dialogs;
6149 while (entry) {
6150 dialog = entry->data;
6151 /* send RequestRM to each chat participant*/
6152 sipe_send_election_request_rm(sip, session, dialog->with, session->bid);
6153 entry = entry->next;
6156 election_timeout = 15; /* sec */
6157 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6161 * @param who a URI to whom to invite to chat
6163 static void
6164 sipe_invite_to_chat(struct sipe_account_data *sip,
6165 struct sip_im_session *session,
6166 const char *who)
6168 gchar *self = g_strdup_printf("sip:%s", sip->username);
6170 if (session->roster_manager) {
6171 if (!strcmp(session->roster_manager, self)) {
6172 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6173 } else {
6174 sipe_refer(sip, session, who);
6176 } else {
6177 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6179 session->pending_invite_queue = slist_insert_unique_sorted(
6180 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6182 sipe_election_start(sip, session);
6185 g_free(self);
6188 static void
6189 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6190 struct sip_im_session *session)
6192 gchar *invitee;
6193 GSList *entry = session->pending_invite_queue;
6195 while (entry) {
6196 invitee = entry->data;
6197 sipe_invite_to_chat(sip, session, invitee);
6198 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6199 g_free(invitee);
6203 static void
6204 sipe_election_result(struct sipe_account_data *sip,
6205 void *sess)
6207 struct sip_im_session *session = (struct sip_im_session *)sess;
6208 struct sip_dialog *dialog;
6209 GSList *entry;
6210 gchar * rival;
6211 gboolean has_won = TRUE;
6213 if (session->roster_manager) {
6214 purple_debug_info("sipe",
6215 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6216 return;
6219 session->is_voting_in_progress = FALSE;
6221 entry = session->dialogs;
6222 while (entry) {
6223 dialog = entry->data;
6224 if (dialog->election_vote < 0) {
6225 has_won = FALSE;
6226 rival = dialog->with;
6227 break;
6229 entry = entry->next;
6232 if (has_won) {
6233 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6235 session->roster_manager = g_strdup_printf("sip:%s", sip->username);
6237 entry = session->dialogs;
6238 while (entry) {
6239 dialog = entry->data;
6240 /* send SetRM to each chat participant*/
6241 sipe_send_election_set_rm(sip, session, dialog->with);
6242 entry = entry->next;
6244 } else {
6245 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6247 session->bid = 0;
6249 sipe_process_pending_invite_queue(sip, session);
6252 static void
6253 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6255 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6256 struct sip_im_session *session;
6258 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6259 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6261 session = find_chat_session_by_name(sip, chat_name);
6263 sipe_invite_to_chat(sip, session, buddy->name);
6266 static void
6267 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6269 const gchar *email;
6270 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6272 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6273 if (email)
6275 char *mailto = g_strdup_printf("mailto:%s", email);
6276 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6277 #ifndef _WIN32
6279 pid_t pid;
6280 char *const parmList[] = {mailto, NULL};
6281 if ((pid = fork()) == -1)
6283 purple_debug_info("sipe", "fork() error\n");
6285 else if (pid == 0)
6287 execvp("xdg-email", parmList);
6288 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6291 #else
6293 BOOL ret;
6294 _flushall();
6295 errno = 0;
6296 //@TODO resolve env variable %WINDIR% first
6297 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6298 if (errno)
6300 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6303 #endif
6305 g_free(mailto);
6307 else
6309 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6314 * A menu which appear when right-clicking on buddy in contact list.
6316 static GList *
6317 sipe_buddy_menu(PurpleBuddy *buddy)
6319 PurpleBlistNode *g_node;
6320 PurpleGroup *group, *gr_parent;
6321 PurpleMenuAction *act;
6322 GList *menu = NULL;
6323 GList *menu_groups = NULL;
6324 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6325 struct sip_im_session *session;
6326 GSList *entry;
6327 gchar *self = g_strdup_printf("sip:%s", sip->username);
6329 act = purple_menu_action_new(_("New Chat"),
6330 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6331 NULL, NULL);
6332 menu = g_list_prepend(menu, act);
6334 entry = sip->im_sessions;
6335 while (entry) {
6336 session = entry->data;
6337 if (strcmp(self, buddy->name) && session->chat_name && !get_dialog(session, buddy->name)) {
6338 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_name);
6339 act = purple_menu_action_new(label,
6340 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6341 g_strdup(session->chat_name), NULL);
6342 g_free(label);
6343 menu = g_list_prepend(menu, act);
6345 entry = entry->next;
6348 act = purple_menu_action_new(_("Send Email..."),
6349 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6350 NULL, NULL);
6351 menu = g_list_prepend(menu, act);
6353 gr_parent = purple_buddy_get_group(buddy);
6354 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6355 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6356 continue;
6358 group = (PurpleGroup *)g_node;
6359 if (group == gr_parent)
6360 continue;
6362 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6363 continue;
6365 act = purple_menu_action_new(purple_group_get_name(group),
6366 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6367 group->name, NULL);
6368 menu_groups = g_list_prepend(menu_groups, act);
6370 menu_groups = g_list_reverse(menu_groups);
6372 act = purple_menu_action_new(_("Copy to"),
6373 NULL,
6374 NULL, menu_groups);
6375 menu = g_list_prepend(menu, act);
6376 menu = g_list_reverse(menu);
6378 g_free(self);
6379 return menu;
6382 static GList *
6383 sipe_blist_node_menu(PurpleBlistNode *node)
6385 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6386 return sipe_buddy_menu((PurpleBuddy *) node);
6387 } else {
6388 return NULL;
6392 static gboolean
6393 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6395 gboolean ret = TRUE;
6396 char *username = (char *)trans->payload;
6398 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6399 PurpleBuddy *pbuddy;
6400 struct sipe_buddy *sbuddy;
6401 const char *alias;
6402 char *server_alias = NULL;
6403 char *email = NULL;
6404 const char *device_name = NULL;
6406 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6408 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6409 alias = purple_buddy_get_local_alias(pbuddy);
6411 if (sip)
6413 //will query buddy UA's capabilities and send answer to log
6414 sipe_options_request(sip, username);
6416 sbuddy = g_hash_table_lookup(sip->buddies, username);
6417 if (sbuddy)
6419 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6423 if (msg->response != 200) {
6424 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6425 } else {
6426 xmlnode *searchResults;
6427 xmlnode *mrow;
6429 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6430 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6431 if (!searchResults) {
6432 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6433 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6434 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6435 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6436 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6437 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6438 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6439 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6440 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6441 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6442 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6443 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6444 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6445 if (!email || strcmp("", email)) {
6446 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6447 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6451 xmlnode_free(searchResults);
6454 purple_notify_user_info_add_section_break(info);
6456 if (!server_alias || !strcmp("", server_alias)) {
6457 g_free(server_alias);
6458 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6459 if (server_alias) {
6460 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6464 // same as server alias, do not present
6465 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6466 if (alias)
6468 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6471 if (!email || !strcmp("", email)) {
6472 g_free(email);
6473 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6474 if (email) {
6475 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6479 if (device_name)
6481 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6484 /* show a buddy's user info in a nice dialog box */
6485 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6486 username, /* buddy's username */
6487 info, /* body */
6488 NULL, /* callback called when dialog closed */
6489 NULL); /* userdata for callback */
6491 return ret;
6495 * AD search first, LDAP based
6497 static void sipe_get_info(PurpleConnection *gc, const char *username)
6499 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6500 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6502 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6503 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6504 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6505 g_free(body);
6506 g_free(row);
6509 static PurplePlugin *my_protocol = NULL;
6511 static PurplePluginProtocolInfo prpl_info =
6514 NULL, /* user_splits */
6515 NULL, /* protocol_options */
6516 NO_BUDDY_ICONS, /* icon_spec */
6517 sipe_list_icon, /* list_icon */
6518 NULL, /* list_emblems */
6519 sipe_status_text, /* status_text */
6520 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6521 sipe_status_types, /* away_states */
6522 sipe_blist_node_menu, /* blist_node_menu */
6523 NULL, /* chat_info */
6524 NULL, /* chat_info_defaults */
6525 sipe_login, /* login */
6526 sipe_close, /* close */
6527 sipe_im_send, /* send_im */
6528 NULL, /* set_info */ // TODO maybe
6529 sipe_send_typing, /* send_typing */
6530 sipe_get_info, /* get_info */
6531 sipe_set_status, /* set_status */
6532 NULL, /* set_idle */
6533 NULL, /* change_passwd */
6534 sipe_add_buddy, /* add_buddy */
6535 NULL, /* add_buddies */
6536 sipe_remove_buddy, /* remove_buddy */
6537 NULL, /* remove_buddies */
6538 sipe_add_permit, /* add_permit */
6539 sipe_add_deny, /* add_deny */
6540 sipe_add_deny, /* rem_permit */
6541 sipe_add_permit, /* rem_deny */
6542 dummy_permit_deny, /* set_permit_deny */
6543 NULL, /* join_chat */
6544 NULL, /* reject_chat */
6545 NULL, /* get_chat_name */
6546 NULL, /* chat_invite */
6547 sipe_chat_leave, /* chat_leave */
6548 NULL, /* chat_whisper */
6549 sipe_chat_send, /* chat_send */
6550 sipe_keep_alive, /* keepalive */
6551 NULL, /* register_user */
6552 NULL, /* get_cb_info */ // deprecated
6553 NULL, /* get_cb_away */ // deprecated
6554 sipe_alias_buddy, /* alias_buddy */
6555 sipe_group_buddy, /* group_buddy */
6556 sipe_rename_group, /* rename_group */
6557 NULL, /* buddy_free */
6558 sipe_convo_closed, /* convo_closed */
6559 purple_normalize_nocase, /* normalize */
6560 NULL, /* set_buddy_icon */
6561 sipe_remove_group, /* remove_group */
6562 NULL, /* get_cb_real_name */ // TODO?
6563 NULL, /* set_chat_topic */
6564 NULL, /* find_blist_chat */
6565 NULL, /* roomlist_get_list */
6566 NULL, /* roomlist_cancel */
6567 NULL, /* roomlist_expand_category */
6568 NULL, /* can_receive_file */
6569 NULL, /* send_file */
6570 NULL, /* new_xfer */
6571 NULL, /* offline_message */
6572 NULL, /* whiteboard_prpl_ops */
6573 sipe_send_raw, /* send_raw */
6574 NULL, /* roomlist_room_serialize */
6575 NULL, /* unregister_user */
6576 NULL, /* send_attention */
6577 NULL, /* get_attention_types */
6579 sizeof(PurplePluginProtocolInfo), /* struct_size */
6580 sipe_get_account_text_table, /* get_account_text_table */
6584 static PurplePluginInfo info = {
6585 PURPLE_PLUGIN_MAGIC,
6586 PURPLE_MAJOR_VERSION,
6587 PURPLE_MINOR_VERSION,
6588 PURPLE_PLUGIN_PROTOCOL, /**< type */
6589 NULL, /**< ui_requirement */
6590 0, /**< flags */
6591 NULL, /**< dependencies */
6592 PURPLE_PRIORITY_DEFAULT, /**< priority */
6593 "prpl-sipe", /**< id */
6594 "Microsoft LCS/OCS", /**< name */
6595 VERSION, /**< version */
6596 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6597 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6598 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6599 "Gabriel Burt <gburt@novell.com>", /**< author */
6600 PURPLE_WEBSITE, /**< homepage */
6601 sipe_plugin_load, /**< load */
6602 sipe_plugin_unload, /**< unload */
6603 sipe_plugin_destroy, /**< destroy */
6604 NULL, /**< ui_info */
6605 &prpl_info, /**< extra_info */
6606 NULL,
6607 sipe_actions,
6608 NULL,
6609 NULL,
6610 NULL,
6611 NULL
6614 static void sipe_plugin_destroy(PurplePlugin *plugin)
6616 GList *entry;
6618 entry = prpl_info.protocol_options;
6619 while (entry) {
6620 purple_account_option_destroy(entry->data);
6621 entry = g_list_delete_link(entry, entry);
6623 prpl_info.protocol_options = NULL;
6625 entry = prpl_info.user_splits;
6626 while (entry) {
6627 purple_account_user_split_destroy(entry->data);
6628 entry = g_list_delete_link(entry, entry);
6630 prpl_info.user_splits = NULL;
6633 static void init_plugin(PurplePlugin *plugin)
6635 PurpleAccountUserSplit *split;
6636 PurpleAccountOption *option;
6638 srand(time(NULL));
6640 #ifdef ENABLE_NLS
6641 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6642 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6643 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6644 textdomain(GETTEXT_PACKAGE);
6645 #endif
6647 purple_plugin_register(plugin);
6649 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
6650 purple_account_user_split_set_reverse(split, FALSE);
6651 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6653 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
6654 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6655 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6656 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6658 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
6659 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6660 // Translators: noun (networking port)
6661 option = purple_account_option_int_new(_("Port"), "port", 5061);
6662 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6664 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6665 purple_account_option_add_list_item(option, _("Auto"), "auto");
6666 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6667 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6668 purple_account_option_add_list_item(option, _("UDP"), "udp");
6669 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6671 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6672 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6674 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6675 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6677 #ifdef USE_KERBEROS
6678 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6679 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6681 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6682 * No login/password is taken into account if this option present,
6683 * instead used default credentials stored in OS.
6685 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6686 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6687 #endif
6688 my_protocol = plugin;
6691 /* I had to redefined the function for it load, but works */
6692 gboolean purple_init_plugin(PurplePlugin *plugin){
6693 plugin->info = &(info);
6694 init_plugin((plugin));
6695 sipe_plugin_load((plugin));
6696 return purple_plugin_register(plugin);
6700 Local Variables:
6701 mode: c
6702 c-file-style: "bsd"
6703 indent-tabs-mode: t
6704 tab-width: 8
6705 End: