Start the Big Split...
[siplcs.git] / src / sipe.c
blobf6f0367b796ca1868f913a89956151890aa4b979
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 "sipe-conf.h"
79 #include "sipe-dialog.h"
80 #include "sipe-utils.h"
81 #include "sipmsg.h"
82 #include "sipe-sign.h"
83 #include "dnssrv.h"
84 #include "request.h"
86 /* Keep in sync with sipe_transport_type! */
87 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
88 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
90 /* Status identifiers (see also: sipe_status_types()) */
91 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
92 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
93 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
94 /* PURPLE_STATUS_UNAVAILABLE: */
95 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
96 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
97 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
98 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
99 /* PURPLE_STATUS_AWAY: */
100 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
101 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
102 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
103 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
104 /* ??? PURPLE_STATUS_MOBILE */
105 /* ??? PURPLE_STATUS_TUNE */
107 /* Action name templates */
108 #define ACTION_NAME_PRESENCE "<presence><%s>"
110 static gchar *get_epid(struct sipe_account_data *sip)
112 if (!sip->epid) {
113 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
115 return g_strdup(sip->epid);
118 static char *genbranch()
120 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
121 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
122 rand() & 0xFFFF, rand() & 0xFFFF);
125 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
127 return "sipe";
130 static void sipe_plugin_destroy(PurplePlugin *plugin);
132 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
134 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
135 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
136 gpointer data);
138 static void sipe_close(PurpleConnection *gc);
140 static void send_presence_status(struct sipe_account_data *sip);
142 static void sendout_pkt(PurpleConnection *gc, const char *buf);
144 static void sipe_keep_alive(PurpleConnection *gc)
146 struct sipe_account_data *sip = gc->proto_data;
147 if (sip->transport == SIPE_TRANSPORT_UDP) {
148 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
149 gchar buf[2] = {0, 0};
150 purple_debug_info("sipe", "sending keep alive\n");
151 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
152 } else {
153 time_t now = time(NULL);
154 if ((sip->keepalive_timeout > 0) &&
155 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
156 #if PURPLE_VERSION_CHECK(2,4,0)
157 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
158 #endif
160 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
161 sendout_pkt(gc, "\r\n\r\n");
162 sip->last_keepalive = now;
167 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
169 struct sip_connection *ret = NULL;
170 GSList *entry = sip->openconns;
171 while (entry) {
172 ret = entry->data;
173 if (ret->fd == fd) return ret;
174 entry = entry->next;
176 return NULL;
179 static void sipe_auth_free(struct sip_auth *auth)
181 g_free(auth->opaque);
182 auth->opaque = NULL;
183 g_free(auth->realm);
184 auth->realm = NULL;
185 g_free(auth->target);
186 auth->target = NULL;
187 auth->type = AUTH_TYPE_UNSET;
188 auth->retries = 0;
189 auth->expires = 0;
190 g_free(auth->gssapi_data);
191 auth->gssapi_data = NULL;
192 sip_sec_destroy_context(auth->gssapi_context);
193 auth->gssapi_context = NULL;
196 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
198 struct sip_connection *ret = g_new0(struct sip_connection, 1);
199 ret->fd = fd;
200 sip->openconns = g_slist_append(sip->openconns, ret);
201 return ret;
204 static void connection_remove(struct sipe_account_data *sip, int fd)
206 struct sip_connection *conn = connection_find(sip, fd);
207 if (conn) {
208 sip->openconns = g_slist_remove(sip->openconns, conn);
209 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
210 g_free(conn->inbuf);
211 g_free(conn);
215 static void connection_free_all(struct sipe_account_data *sip)
217 struct sip_connection *ret = NULL;
218 GSList *entry = sip->openconns;
219 while (entry) {
220 ret = entry->data;
221 connection_remove(sip, ret->fd);
222 entry = sip->openconns;
226 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
228 gchar noncecount[9];
229 const char *authuser = sip->authuser;
230 gchar *response;
231 gchar *ret;
233 if (!authuser || strlen(authuser) < 1) {
234 authuser = sip->username;
237 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
238 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
240 // If we have a signature for the message, include that
241 if (msg->signature) {
242 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);
245 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
246 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
247 gchar *gssapi_data;
248 gchar *opaque;
250 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
251 &(auth->expires),
252 auth->type,
253 purple_account_get_bool(sip->account, "sso", TRUE),
254 sip->authdomain ? sip->authdomain : "",
255 authuser,
256 sip->password,
257 auth->target,
258 auth->gssapi_data);
259 if (!gssapi_data || !auth->gssapi_context)
260 return NULL;
262 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
263 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
264 g_free(opaque);
265 g_free(gssapi_data);
266 return ret;
269 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
271 } else { /* Digest */
273 /* Calculate new session key */
274 if (!auth->opaque) {
275 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
276 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
277 authuser, auth->realm, sip->password,
278 auth->gssapi_data, NULL);
281 sprintf(noncecount, "%08d", auth->nc++);
282 response = purple_cipher_http_digest_calculate_response("md5",
283 msg->method, msg->target, NULL, NULL,
284 auth->gssapi_data, noncecount, NULL,
285 auth->opaque);
286 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
288 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);
289 g_free(response);
290 return ret;
294 static char *parse_attribute(const char *attrname, const char *source)
296 const char *tmp, *tmp2;
297 char *retval = NULL;
298 int len = strlen(attrname);
300 if (!strncmp(source, attrname, len)) {
301 tmp = source + len;
302 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
303 if (tmp2)
304 retval = g_strndup(tmp, tmp2 - tmp);
305 else
306 retval = g_strdup(tmp);
309 return retval;
312 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
314 int i;
315 gchar **parts;
317 if (!hdr) {
318 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
319 return;
322 if (!g_strncasecmp(hdr, "NTLM", 4)) {
323 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
324 auth->type = AUTH_TYPE_NTLM;
325 hdr += 5;
326 auth->nc = 1;
327 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
328 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
329 auth->type = AUTH_TYPE_KERBEROS;
330 hdr += 9;
331 auth->nc = 3;
332 } else {
333 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
334 auth->type = AUTH_TYPE_DIGEST;
335 hdr += 7;
338 parts = g_strsplit(hdr, "\", ", 0);
339 for (i = 0; parts[i]; i++) {
340 char *tmp;
342 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
344 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
345 g_free(auth->gssapi_data);
346 auth->gssapi_data = tmp;
348 if (auth->type == AUTH_TYPE_NTLM) {
349 /* NTLM module extracts nonce from gssapi-data */
350 auth->nc = 3;
353 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
354 /* Only used with AUTH_TYPE_DIGEST */
355 g_free(auth->gssapi_data);
356 auth->gssapi_data = tmp;
357 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
358 g_free(auth->opaque);
359 auth->opaque = tmp;
360 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
361 g_free(auth->realm);
362 auth->realm = tmp;
364 if (auth->type == AUTH_TYPE_DIGEST) {
365 /* Throw away old session key */
366 g_free(auth->opaque);
367 auth->opaque = NULL;
368 auth->nc = 1;
371 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
372 g_free(auth->target);
373 auth->target = tmp;
376 g_strfreev(parts);
378 return;
381 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
383 PurpleConnection *gc = data;
384 struct sipe_account_data *sip = gc->proto_data;
385 gsize max_write;
386 gssize written;
388 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
390 if (max_write == 0) {
391 if (sip->tx_handler != 0){
392 purple_input_remove(sip->tx_handler);
393 sip->tx_handler = 0;
395 return;
398 written = write(sip->fd, sip->txbuf->outptr, max_write);
400 if (written < 0 && errno == EAGAIN)
401 written = 0;
402 else if (written <= 0) {
403 /*TODO: do we really want to disconnect on a failure to write?*/
404 purple_connection_error(gc, _("Could not write"));
405 return;
408 purple_circ_buffer_mark_read(sip->txbuf, written);
411 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
413 PurpleConnection *gc = data;
414 struct sipe_account_data *sip = gc->proto_data;
415 gsize max_write;
416 gssize written;
418 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
420 if (max_write == 0) {
421 if (sip->tx_handler != 0) {
422 purple_input_remove(sip->tx_handler);
423 sip->tx_handler = 0;
424 return;
428 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
430 if (written < 0 && errno == EAGAIN)
431 written = 0;
432 else if (written <= 0) {
433 /*TODO: do we really want to disconnect on a failure to write?*/
434 purple_connection_error(gc, _("Could not write"));
435 return;
438 purple_circ_buffer_mark_read(sip->txbuf, written);
441 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
443 static void send_later_cb(gpointer data, gint source, const gchar *error)
445 PurpleConnection *gc = data;
446 struct sipe_account_data *sip;
447 struct sip_connection *conn;
449 if (!PURPLE_CONNECTION_IS_VALID(gc))
451 if (source >= 0)
452 close(source);
453 return;
456 if (source < 0) {
457 purple_connection_error(gc, _("Could not connect"));
458 return;
461 sip = gc->proto_data;
462 sip->fd = source;
463 sip->connecting = FALSE;
464 sip->last_keepalive = time(NULL);
466 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
468 /* If there is more to write now, we need to register a handler */
469 if (sip->txbuf->bufused > 0)
470 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
472 conn = connection_create(sip, source);
473 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
476 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
478 struct sipe_account_data *sip;
479 struct sip_connection *conn;
481 if (!PURPLE_CONNECTION_IS_VALID(gc))
483 if (gsc) purple_ssl_close(gsc);
484 return NULL;
487 sip = gc->proto_data;
488 sip->fd = gsc->fd;
489 sip->gsc = gsc;
490 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
491 sip->connecting = FALSE;
492 sip->last_keepalive = time(NULL);
494 conn = connection_create(sip, gsc->fd);
496 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
498 return sip;
501 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
503 PurpleConnection *gc = data;
504 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
505 if (sip == NULL) return;
507 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
509 /* If there is more to write now */
510 if (sip->txbuf->bufused > 0) {
511 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
516 static void sendlater(PurpleConnection *gc, const char *buf)
518 struct sipe_account_data *sip = gc->proto_data;
520 if (!sip->connecting) {
521 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
522 if (sip->transport == SIPE_TRANSPORT_TLS){
523 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
524 } else {
525 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
526 purple_connection_error(gc, _("Couldn't create socket"));
529 sip->connecting = TRUE;
532 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
533 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
535 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
538 static void sendout_pkt(PurpleConnection *gc, const char *buf)
540 struct sipe_account_data *sip = gc->proto_data;
541 time_t currtime = time(NULL);
542 int writelen = strlen(buf);
544 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
545 if (sip->transport == SIPE_TRANSPORT_UDP) {
546 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
547 purple_debug_info("sipe", "could not send packet\n");
549 } else {
550 int ret;
551 if (sip->fd < 0) {
552 sendlater(gc, buf);
553 return;
556 if (sip->tx_handler) {
557 ret = -1;
558 errno = EAGAIN;
559 } else{
560 if (sip->gsc){
561 ret = purple_ssl_write(sip->gsc, buf, writelen);
562 }else{
563 ret = write(sip->fd, buf, writelen);
567 if (ret < 0 && errno == EAGAIN)
568 ret = 0;
569 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
570 sendlater(gc, buf);
571 return;
574 if (ret < writelen) {
575 if (!sip->tx_handler){
576 if (sip->gsc){
577 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
579 else{
580 sip->tx_handler = purple_input_add(sip->fd,
581 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
582 gc);
586 /* XXX: is it OK to do this? You might get part of a request sent
587 with part of another. */
588 if (sip->txbuf->bufused > 0)
589 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
591 purple_circ_buffer_append(sip->txbuf, buf + ret,
592 writelen - ret);
597 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
599 sendout_pkt(gc, buf);
600 return len;
603 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
605 GSList *tmp = msg->headers;
606 gchar *name;
607 gchar *value;
608 GString *outstr = g_string_new("");
609 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
610 while (tmp) {
611 name = ((struct siphdrelement*) (tmp->data))->name;
612 value = ((struct siphdrelement*) (tmp->data))->value;
613 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
614 tmp = g_slist_next(tmp);
616 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
617 sendout_pkt(sip->gc, outstr->str);
618 g_string_free(outstr, TRUE);
621 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
623 gchar * buf;
624 if (sip->registrar.gssapi_context) {
625 struct sipmsg_breakdown msgbd;
626 gchar *signature_input_str;
627 msgbd.msg = msg;
628 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
629 msgbd.rand = g_strdup_printf("%08x", g_random_int());
630 sip->registrar.ntlm_num++;
631 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
632 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
633 if (signature_input_str != NULL) {
634 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
635 msg->signature = signature_hex;
636 msg->rand = g_strdup(msgbd.rand);
637 msg->num = g_strdup(msgbd.num);
638 g_free(signature_input_str);
640 sipmsg_breakdown_free(&msgbd);
643 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
644 buf = auth_header(sip, &sip->registrar, msg);
645 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
646 g_free(buf);
647 } 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")) {
648 sip->registrar.nc = 3;
649 #ifdef USE_KERBEROS
650 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
651 #endif
652 sip->registrar.type = AUTH_TYPE_NTLM;
653 #ifdef USE_KERBEROS
654 } else {
655 sip->registrar.type = AUTH_TYPE_KERBEROS;
657 #endif
660 buf = auth_header(sip, &sip->registrar, msg);
661 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
662 g_free(buf);
663 } else {
664 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
669 * unused. Needed?
670 static char *get_contact_service(struct sipe_account_data *sip)
672 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()));
673 //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);
677 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
678 const char *text, const char *body)
680 gchar *name;
681 gchar *value;
682 GString *outstr = g_string_new("");
683 struct sipe_account_data *sip = gc->proto_data;
684 gchar *contact;
685 GSList *tmp;
686 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
688 contact = get_contact(sip);
689 sipmsg_add_header(msg, "Contact", contact);
690 g_free(contact);
692 if (body) {
693 gchar len[12];
694 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
695 sipmsg_add_header(msg, "Content-Length", len);
696 } else {
697 sipmsg_add_header(msg, "Content-Length", "0");
700 msg->response = code;
702 sipmsg_strip_headers(msg, keepers);
703 sipmsg_merge_new_headers(msg);
704 sign_outgoing_message(msg, sip, msg->method);
706 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
707 tmp = msg->headers;
708 while (tmp) {
709 name = ((struct siphdrelement*) (tmp->data))->name;
710 value = ((struct siphdrelement*) (tmp->data))->value;
712 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
713 tmp = g_slist_next(tmp);
715 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
716 sendout_pkt(gc, outstr->str);
717 g_string_free(outstr, TRUE);
720 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
722 if (trans->msg) sipmsg_free(trans->msg);
723 sip->transactions = g_slist_remove(sip->transactions, trans);
724 g_free(trans);
727 static struct transaction *
728 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
730 struct transaction *trans = g_new0(struct transaction, 1);
731 trans->time = time(NULL);
732 trans->msg = (struct sipmsg *)msg;
733 trans->cseq = sipmsg_find_header(trans->msg, "CSeq");
734 trans->callback = callback;
735 sip->transactions = g_slist_append(sip->transactions, trans);
736 return trans;
739 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
741 struct transaction *trans;
742 GSList *transactions = sip->transactions;
743 gchar *cseq = sipmsg_find_header(msg, "CSeq");
745 while (transactions) {
746 trans = transactions->data;
747 if (!strcmp(trans->cseq, cseq)) {
748 return trans;
750 transactions = transactions->next;
753 return NULL;
756 struct transaction *
757 send_sip_request(PurpleConnection *gc, const gchar *method,
758 const gchar *url, const gchar *to, const gchar *addheaders,
759 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
761 struct sipe_account_data *sip = gc->proto_data;
762 const char *addh = "";
763 char *buf;
764 struct sipmsg *msg;
765 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
766 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
767 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
768 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
769 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
770 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
771 gchar *route = strdup("");
772 gchar *epid = get_epid(sip); // TODO generate one per account/login
773 int cseq = dialog ? ++dialog->cseq :
774 /* This breaks OCS2007: own presence, contact search, ?
775 1 .* as Call-Id is new in this case */
776 ++sip->cseq;
777 struct transaction *trans;
779 if (dialog && dialog->routes)
781 GSList *iter = dialog->routes;
783 while(iter)
785 char *tmp = route;
786 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
787 g_free(tmp);
788 iter = g_slist_next(iter);
792 if (!ourtag && !dialog) {
793 ourtag = gentag();
796 if (!strcmp(method, "REGISTER")) {
797 if (sip->regcallid) {
798 g_free(callid);
799 callid = g_strdup(sip->regcallid);
800 } else {
801 sip->regcallid = g_strdup(callid);
805 if (addheaders) addh = addheaders;
807 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
808 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
809 "From: <sip:%s>%s%s;epid=%s\r\n"
810 "To: <%s>%s%s%s%s\r\n"
811 "Max-Forwards: 70\r\n"
812 "CSeq: %d %s\r\n"
813 "User-Agent: %s\r\n"
814 "Call-ID: %s\r\n"
815 "%s%s"
816 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
817 method,
818 dialog && dialog->request ? dialog->request : url,
819 TRANSPORT_DESCRIPTOR,
820 purple_network_get_my_ip(-1),
821 sip->listenport,
822 branch ? ";branch=" : "",
823 branch ? branch : "",
824 sip->username,
825 ourtag ? ";tag=" : "",
826 ourtag ? ourtag : "",
827 epid,
829 theirtag ? ";tag=" : "",
830 theirtag ? theirtag : "",
831 theirepid ? ";epid=" : "",
832 theirepid ? theirepid : "",
833 cseq,
834 method,
835 useragent,
836 callid,
837 route,
838 addh,
839 body ? (gsize) strlen(body) : 0,
840 body ? body : "");
843 //printf ("parsing msg buf:\n%s\n\n", buf);
844 msg = sipmsg_parse_msg(buf);
846 g_free(buf);
847 g_free(ourtag);
848 g_free(theirtag);
849 g_free(theirepid);
850 g_free(branch);
851 g_free(callid);
852 g_free(route);
853 g_free(epid);
855 sign_outgoing_message (msg, sip, method);
857 buf = sipmsg_to_string (msg);
859 /* add to ongoing transactions */
860 trans = transactions_add_buf(sip, msg, tc);
861 sendout_pkt(gc, buf);
862 g_free(buf);
864 return trans;
867 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
869 gchar *from = g_strdup_printf("sip:%s", sip->username);
870 gchar *contact = get_contact(sip);
871 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
872 "Content-Type: application/SOAP+xml\r\n",contact);
874 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
875 tr->payload = payload;
877 g_free(from);
878 g_free(contact);
879 g_free(hdr);
882 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
884 send_soap_request_with_cb(sip, body, NULL, NULL);
887 static char *get_contact_register(struct sipe_account_data *sip)
889 char *epid = get_epid(sip);
890 char *uuid = generateUUIDfromEPID(epid);
891 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);
892 g_free(uuid);
893 g_free(epid);
894 return(buf);
897 static void do_register_exp(struct sipe_account_data *sip, int expire)
899 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
900 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
901 char *to = g_strdup_printf("sip:%s", sip->username);
902 char *contact = get_contact_register(sip);
903 char *hdr = g_strdup_printf("Contact: %s\r\n"
904 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
905 "Event: registration\r\n"
906 "Allow-Events: presence\r\n"
907 "ms-keep-alive: UAC;hop-hop=yes\r\n"
908 "%s", contact, expires);
909 g_free(contact);
910 g_free(expires);
912 sip->registerstatus = 1;
914 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
915 process_register_response);
917 g_free(hdr);
918 g_free(uri);
919 g_free(to);
922 static void do_register_cb(struct sipe_account_data *sip, void *unused)
924 do_register_exp(sip, -1);
925 sip->reregister_set = FALSE;
928 static void do_register(struct sipe_account_data *sip)
930 do_register_exp(sip, -1);
933 static xmlnode * xmlnode_get_descendant(xmlnode * parent, ...)
935 va_list args;
936 xmlnode * node = NULL;
937 const gchar * name;
939 va_start(args, parent);
940 while ((name = va_arg(args, const char *)) != NULL) {
941 node = xmlnode_get_child(parent, name);
942 if (node == NULL) return NULL;
943 parent = node;
945 va_end(args);
947 return node;
951 static void
952 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
954 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
955 send_soap_request(sip, body);
956 g_free(body);
959 static void
960 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
962 if (allow) {
963 purple_debug_info("sipe", "Authorizing contact %s\n", who);
964 } else {
965 purple_debug_info("sipe", "Blocking contact %s\n", who);
968 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
971 static
972 void sipe_auth_user_cb(void * data)
974 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
975 if (!job) return;
977 sipe_contact_allow_deny (job->sip, job->who, TRUE);
978 g_free(job);
981 static
982 void sipe_deny_user_cb(void * data)
984 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
985 if (!job) return;
987 sipe_contact_allow_deny (job->sip, job->who, FALSE);
988 g_free(job);
991 static void
992 sipe_add_permit(PurpleConnection *gc, const char *name)
994 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
995 sipe_contact_allow_deny(sip, name, TRUE);
998 static void
999 sipe_add_deny(PurpleConnection *gc, const char *name)
1001 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1002 sipe_contact_allow_deny(sip, name, FALSE);
1005 /*static void
1006 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1008 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1009 sipe_contact_set_acl(sip, name, "");
1012 static void
1013 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1015 xmlnode *watchers;
1016 xmlnode *watcher;
1017 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1018 if (msg->response != 0 && msg->response != 200) return;
1020 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1022 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1023 if (!watchers) return;
1025 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1026 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1027 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1028 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1030 // TODO pull out optional displayName to pass as alias
1031 if (remote_user) {
1032 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1033 job->who = remote_user;
1034 job->sip = sip;
1035 purple_account_request_authorization(
1036 sip->account,
1037 remote_user,
1038 _("you"), /* id */
1039 alias,
1040 NULL, /* message */
1041 on_list,
1042 sipe_auth_user_cb,
1043 sipe_deny_user_cb,
1044 (void *) job);
1049 xmlnode_free(watchers);
1050 return;
1053 static void
1054 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1056 PurpleGroup * purple_group = purple_find_group(group->name);
1057 if (!purple_group) {
1058 purple_group = purple_group_new(group->name);
1059 purple_blist_add_group(purple_group, NULL);
1062 if (purple_group) {
1063 group->purple_group = purple_group;
1064 sip->groups = g_slist_append(sip->groups, group);
1065 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1066 } else {
1067 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1071 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1073 struct sipe_group *group;
1074 GSList *entry;
1075 if (sip == NULL) {
1076 return NULL;
1079 entry = sip->groups;
1080 while (entry) {
1081 group = entry->data;
1082 if (group->id == id) {
1083 return group;
1085 entry = entry->next;
1087 return NULL;
1090 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1092 struct sipe_group *group;
1093 GSList *entry;
1094 if (sip == NULL) {
1095 return NULL;
1098 entry = sip->groups;
1099 while (entry) {
1100 group = entry->data;
1101 if (!strcmp(group->name, name)) {
1102 return group;
1104 entry = entry->next;
1106 return NULL;
1109 static void
1110 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1112 gchar *body;
1113 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1114 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1115 send_soap_request(sip, body);
1116 g_free(body);
1117 g_free(group->name);
1118 group->name = g_strdup(name);
1122 * Only appends if no such value already stored.
1123 * Like Set in Java.
1125 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1126 GSList * res = list;
1127 if (!g_slist_find_custom(list, data, func)) {
1128 res = g_slist_insert_sorted(list, data, func);
1130 return res;
1133 static int
1134 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1135 return group1->id - group2->id;
1139 * Returns string like "2 4 7 8" - group ids buddy belong to.
1141 static gchar *
1142 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1143 int i = 0;
1144 gchar *res;
1145 //creating array from GList, converting int to gchar*
1146 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1147 GSList *entry = buddy->groups;
1148 while (entry) {
1149 struct sipe_group * group = entry->data;
1150 ids_arr[i] = g_strdup_printf("%d", group->id);
1151 entry = entry->next;
1152 i++;
1154 ids_arr[i] = NULL;
1155 res = g_strjoinv(" ", ids_arr);
1156 g_strfreev(ids_arr);
1157 return res;
1161 * Sends buddy update to server
1163 static void
1164 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1166 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1167 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1169 if (buddy && purple_buddy) {
1170 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1171 gchar *body;
1172 gchar *groups = sipe_get_buddy_groups_string(buddy);
1173 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1175 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1176 alias, groups, "true", buddy->name, sip->contacts_delta++
1178 send_soap_request(sip, body);
1179 g_free(groups);
1180 g_free(body);
1184 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1186 if (msg->response == 200) {
1187 struct sipe_group *group;
1188 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1189 xmlnode *xml;
1190 xmlnode *node;
1191 char *group_id;
1192 struct sipe_buddy *buddy;
1194 xml = xmlnode_from_str(msg->body, msg->bodylen);
1195 if (!xml) {
1196 g_free(ctx);
1197 return FALSE;
1200 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1201 if (!node) {
1202 g_free(ctx);
1203 xmlnode_free(xml);
1204 return FALSE;
1207 group_id = xmlnode_get_data(node);
1208 if (!group_id) {
1209 g_free(ctx);
1210 xmlnode_free(xml);
1211 return FALSE;
1214 group = g_new0(struct sipe_group, 1);
1215 group->id = (int)g_ascii_strtod(group_id, NULL);
1216 g_free(group_id);
1217 group->name = ctx->group_name;
1219 sipe_group_add(sip, group);
1221 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1222 if (buddy) {
1223 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1226 sipe_group_set_user(sip, ctx->user_name);
1228 g_free(ctx);
1229 xmlnode_free(xml);
1230 return TRUE;
1232 return FALSE;
1235 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1237 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1238 gchar *body;
1239 ctx->group_name = g_strdup(name);
1240 ctx->user_name = g_strdup(who);
1242 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1243 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1244 g_free(body);
1248 * Data structure for scheduled actions
1250 typedef void (*Action) (struct sipe_account_data *, void *);
1252 struct scheduled_action {
1253 /**
1254 * Name of action.
1255 * Format is <Event>[<Data>...]
1256 * Example: <presence><sip:user@domain.com> or <registration>
1258 gchar *name;
1259 guint timeout_handler;
1260 gboolean repetitive;
1261 Action action;
1262 GDestroyNotify destroy;
1263 struct sipe_account_data *sip;
1264 void *payload;
1268 * A timer callback
1269 * Should return FALSE if repetitive action is not needed
1271 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1273 gboolean ret;
1274 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1275 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1276 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1277 (sched_action->action)(sched_action->sip, sched_action->payload);
1278 ret = sched_action->repetitive;
1279 (*sched_action->destroy)(sched_action->payload);
1280 g_free(sched_action->name);
1281 g_free(sched_action);
1282 return ret;
1286 * Kills action timer effectively cancelling
1287 * scheduled action
1289 * @param name of action
1291 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1293 GSList *entry;
1295 if (!sip->timeouts || !name) return;
1297 entry = sip->timeouts;
1298 while (entry) {
1299 struct scheduled_action *sched_action = entry->data;
1300 if(!strcmp(sched_action->name, name)) {
1301 GSList *to_delete = entry;
1302 entry = entry->next;
1303 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1304 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1305 purple_timeout_remove(sched_action->timeout_handler);
1306 (*sched_action->destroy)(sched_action->payload);
1307 g_free(sched_action->name);
1308 g_free(sched_action);
1309 } else {
1310 entry = entry->next;
1315 static void
1316 sipe_schedule_action0(const gchar *name,
1317 int timeout,
1318 gboolean isSeconds,
1319 Action action,
1320 GDestroyNotify destroy,
1321 struct sipe_account_data *sip,
1322 void *payload)
1324 struct scheduled_action *sched_action;
1326 /* Make sure each action only exists once */
1327 sipe_cancel_scheduled_action(sip, name);
1329 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1330 sched_action = g_new0(struct scheduled_action, 1);
1331 sched_action->repetitive = FALSE;
1332 sched_action->name = g_strdup(name);
1333 sched_action->action = action;
1334 sched_action->destroy = destroy ? destroy : g_free;
1335 sched_action->sip = sip;
1336 sched_action->payload = payload;
1337 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1338 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1339 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1340 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1344 * Do schedule action for execution in the future.
1345 * Non repetitive execution.
1347 * @param name of action (will be copied)
1348 * @param timeout in seconds
1349 * @action callback function
1350 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1352 static void
1353 sipe_schedule_action(const gchar *name,
1354 int timeout,
1355 Action action,
1356 GDestroyNotify destroy,
1357 struct sipe_account_data *sip,
1358 void *payload)
1360 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1364 * Same as sipe_schedule_action() but timeout is in milliseconds.
1366 static void
1367 sipe_schedule_action_msec(const gchar *name,
1368 int timeout,
1369 Action action,
1370 GDestroyNotify destroy,
1371 struct sipe_account_data *sip,
1372 void *payload)
1374 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1378 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1380 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1382 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1384 process_incoming_notify(sip, msg, FALSE, FALSE);
1386 return TRUE;
1389 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1391 gchar *tmp = *resources_uri;
1392 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1393 g_free(tmp);
1396 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1398 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1399 if (sbuddy && !sbuddy->resubscribed) { // Only not resubscribed contacts; the first time everybody are included
1400 gchar *tmp = *resources_uri;
1401 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1402 g_free(tmp);
1407 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1408 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1409 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1410 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1411 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1414 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1416 gchar *contact = get_contact(sip);
1417 gchar *request;
1418 gchar *content;
1419 gchar *require = "";
1420 gchar *accept = "";
1421 gchar *autoextend = "";
1422 gchar *content_type;
1424 if (sip->msrtc_event_categories) {
1425 require = ", categoryList";
1426 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1427 content_type = "application/msrtc-adrl-categorylist+xml";
1428 content = g_strdup_printf(
1429 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1430 "<action name=\"subscribe\" id=\"63792024\">\n"
1431 "<adhocList>\n%s</adhocList>\n"
1432 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1433 "<category name=\"note\"/>\n"
1434 "<category name=\"state\"/>\n"
1435 "</categoryList>\n"
1436 "</action>\n"
1437 "</batchSub>", sip->username, resources_uri);
1438 } else {
1439 autoextend = "Supported: com.microsoft.autoextend\r\n";
1440 content_type = "application/adrl+xml";
1441 content = g_strdup_printf(
1442 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1443 "<create xmlns=\"\">\n%s</create>\n"
1444 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1446 g_free(resources_uri);
1448 request = g_strdup_printf(
1449 "Require: adhoclist%s\r\n"
1450 "Supported: eventlist\r\n"
1451 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1452 "Supported: ms-piggyback-first-notify\r\n"
1453 "%sSupported: ms-benotify\r\n"
1454 "Proxy-Require: ms-benotify\r\n"
1455 "Event: presence\r\n"
1456 "Content-Type: %s\r\n"
1457 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1458 g_free(contact);
1460 /* subscribe to buddy presence */
1461 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1462 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1464 g_free(content);
1465 g_free(to);
1466 g_free(request);
1469 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip, void *unused)
1471 gchar *to = g_strdup_printf("sip:%s", sip->username);
1472 gchar *resources_uri = g_strdup("");
1473 if (sip->msrtc_event_categories) {
1474 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1475 } else {
1476 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1478 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1481 struct presence_batched_routed {
1482 gchar *host;
1483 GSList *buddies;
1486 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1488 struct presence_batched_routed *data = payload;
1489 GSList *buddies = data->buddies;
1490 while (buddies) {
1491 g_free(buddies->data);
1492 buddies = buddies->next;
1494 g_slist_free(data->buddies);
1495 g_free(data->host);
1496 g_free(payload);
1499 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1501 struct presence_batched_routed *data = payload;
1502 GSList *buddies = data->buddies;
1503 gchar *resources_uri = g_strdup("");
1504 while (buddies) {
1505 gchar *tmp = resources_uri;
1506 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1507 g_free(tmp);
1508 buddies = buddies->next;
1510 sipe_subscribe_presence_batched_to(sip, resources_uri,
1511 g_strdup(data->host));
1515 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1516 * The user sends a single SUBSCRIBE request to the subscribed contact.
1517 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1521 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1523 gchar *to = strstr((char *)buddy_name, "sip:") ? g_strdup((char *)buddy_name) : g_strdup_printf("sip:%s", (char *)buddy_name);
1524 gchar *tmp = get_contact(sip);
1525 gchar *request;
1526 gchar *content;
1527 gchar *autoextend = "";
1529 if (!sip->msrtc_event_categories)
1530 autoextend = "Supported: com.microsoft.autoextend\r\n";
1532 request = g_strdup_printf(
1533 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1534 "Supported: ms-piggyback-first-notify\r\n"
1535 "%sSupported: ms-benotify\r\n"
1536 "Proxy-Require: ms-benotify\r\n"
1537 "Event: presence\r\n"
1538 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1539 "Contact: %s\r\n", autoextend,tmp);
1541 content = g_strdup_printf(
1542 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1543 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1544 "<resource uri=\"%s\"/>\n"
1545 "</adhocList>\n"
1546 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1547 "<category name=\"note\"/>\n"
1548 "<category name=\"state\"/>\n"
1549 "</categoryList>\n"
1550 "</action>\n"
1551 "</batchSub>", sip->username, to
1554 g_free(tmp);
1556 /* subscribe to buddy presence */
1557 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1559 g_free(content);
1560 g_free(to);
1561 g_free(request);
1564 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1566 if (!purple_status_is_active(status))
1567 return;
1569 if (account->gc) {
1570 struct sipe_account_data *sip = account->gc->proto_data;
1572 if (sip) {
1573 g_free(sip->status);
1574 sip->status = g_strdup(purple_status_get_id(status));
1575 send_presence_status(sip);
1580 static void
1581 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1583 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1584 sipe_group_set_user(sip, name);
1587 static void
1588 sipe_group_buddy(PurpleConnection *gc,
1589 const char *who,
1590 const char *old_group_name,
1591 const char *new_group_name)
1593 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1594 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1595 struct sipe_group * old_group = NULL;
1596 struct sipe_group * new_group;
1598 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1599 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1601 if(!buddy) { // buddy not in roaming list
1602 return;
1605 if (old_group_name) {
1606 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1608 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1610 if (old_group) {
1611 buddy->groups = g_slist_remove(buddy->groups, old_group);
1612 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1615 if (!new_group) {
1616 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1617 } else {
1618 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1619 sipe_group_set_user(sip, who);
1623 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1625 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1626 struct sipe_buddy *b;
1628 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1630 // Prepend sip: if needed
1631 if (strncmp("sip:", buddy->name, 4)) {
1632 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1633 purple_blist_rename_buddy(buddy, buf);
1634 g_free(buf);
1637 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1638 b = g_new0(struct sipe_buddy, 1);
1639 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1640 b->name = g_strdup(buddy->name);
1641 g_hash_table_insert(sip->buddies, b->name, b);
1642 sipe_group_buddy(gc, b->name, NULL, group->name);
1643 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1644 } else {
1645 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1649 static void sipe_free_buddy(struct sipe_buddy *buddy)
1651 #ifndef _WIN32
1653 * We are calling g_hash_table_foreach_steal(). That means that no
1654 * key/value deallocation functions are called. Therefore the glib
1655 * hash code does not touch the key (buddy->name) or value (buddy)
1656 * of the to-be-deleted hash node at all. It follows that we
1658 * - MUST free the memory for the key ourselves and
1659 * - ARE allowed to do it in this function
1661 * Conclusion: glib must be broken on the Windows platform if sipe
1662 * crashes with SIGTRAP when closing. You'll have to live
1663 * with the memory leak until this is fixed.
1665 g_free(buddy->name);
1666 #endif
1667 g_free(buddy->annotation);
1668 g_free(buddy->device_name);
1669 g_slist_free(buddy->groups);
1670 g_free(buddy);
1674 * Unassociates buddy from group first.
1675 * Then see if no groups left, removes buddy completely.
1676 * Otherwise updates buddy groups on server.
1678 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1680 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1681 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1682 struct sipe_group *g = NULL;
1684 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1686 if (!b) return;
1688 if (group) {
1689 g = sipe_group_find_by_name(sip, group->name);
1692 if (g) {
1693 b->groups = g_slist_remove(b->groups, g);
1694 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1697 if (g_slist_length(b->groups) < 1) {
1698 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1699 sipe_cancel_scheduled_action(sip, action_name);
1700 g_free(action_name);
1702 g_hash_table_remove(sip->buddies, buddy->name);
1704 if (b->name) {
1705 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1706 send_soap_request(sip, body);
1707 g_free(body);
1710 sipe_free_buddy(b);
1711 } else {
1712 //updates groups on server
1713 sipe_group_set_user(sip, b->name);
1718 static void
1719 sipe_rename_group(PurpleConnection *gc,
1720 const char *old_name,
1721 PurpleGroup *group,
1722 GList *moved_buddies)
1724 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1725 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1726 if (group) {
1727 sipe_group_rename(sip, s_group, group->name);
1728 } else {
1729 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1733 static void
1734 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1736 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1737 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1738 if (s_group) {
1739 gchar *body;
1740 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1741 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1742 send_soap_request(sip, body);
1743 g_free(body);
1745 sip->groups = g_slist_remove(sip->groups, s_group);
1746 g_free(s_group->name);
1747 g_free(s_group);
1748 } else {
1749 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1753 static GList *sipe_status_types(PurpleAccount *acc)
1755 PurpleStatusType *type;
1756 GList *types = NULL;
1758 // Online
1759 type = purple_status_type_new_with_attrs(
1760 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1761 // Translators: noun
1762 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1763 NULL);
1764 types = g_list_append(types, type);
1766 // Busy
1767 type = purple_status_type_new_with_attrs(
1768 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1769 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1770 NULL);
1771 types = g_list_append(types, type);
1773 // Do Not Disturb (not user settable)
1774 type = purple_status_type_new_with_attrs(
1775 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1776 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1777 NULL);
1778 types = g_list_append(types, type);
1780 // Be Right Back
1781 type = purple_status_type_new_with_attrs(
1782 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1783 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1784 NULL);
1785 types = g_list_append(types, type);
1787 // Away
1788 type = purple_status_type_new_with_attrs(
1789 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1790 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1791 NULL);
1792 types = g_list_append(types, type);
1794 //On The Phone
1795 type = purple_status_type_new_with_attrs(
1796 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1797 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1798 NULL);
1799 types = g_list_append(types, type);
1801 //Out To Lunch
1802 type = purple_status_type_new_with_attrs(
1803 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1804 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1805 NULL);
1806 types = g_list_append(types, type);
1808 //Appear Offline
1809 type = purple_status_type_new_full(
1810 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1811 types = g_list_append(types, type);
1813 // Offline
1814 type = purple_status_type_new_full(
1815 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1816 types = g_list_append(types, type);
1818 return types;
1822 * A callback for g_hash_table_foreach
1824 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1826 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1827 int time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1828 int timeout = (time_range * rand()) / RAND_MAX; /* random period within the range */
1829 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, buddy->name);
1833 * Removes entries from purple buddy list
1834 * that does not correspond ones in the roaming contact list.
1836 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1837 GSList *buddies = purple_find_buddies(sip->account, NULL);
1838 GSList *entry = buddies;
1839 struct sipe_buddy *buddy;
1840 PurpleBuddy *b;
1841 PurpleGroup *g;
1843 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1844 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1845 while (entry) {
1846 b = entry->data;
1847 g = purple_buddy_get_group(b);
1848 buddy = g_hash_table_lookup(sip->buddies, b->name);
1849 if(buddy) {
1850 gboolean in_sipe_groups = FALSE;
1851 GSList *entry2 = buddy->groups;
1852 while (entry2) {
1853 struct sipe_group *group = entry2->data;
1854 if (!strcmp(group->name, g->name)) {
1855 in_sipe_groups = TRUE;
1856 break;
1858 entry2 = entry2->next;
1860 if(!in_sipe_groups) {
1861 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1862 purple_blist_remove_buddy(b);
1864 } else {
1865 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1866 purple_blist_remove_buddy(b);
1868 entry = entry->next;
1872 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1874 int len = msg->bodylen;
1876 gchar *tmp = sipmsg_find_header(msg, "Event");
1877 xmlnode *item;
1878 xmlnode *isc;
1879 const gchar *contacts_delta;
1880 xmlnode *group_node;
1881 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1882 return FALSE;
1885 /* Convert the contact from XML to Purple Buddies */
1886 isc = xmlnode_from_str(msg->body, len);
1887 if (!isc) {
1888 return FALSE;
1891 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1892 if (contacts_delta) {
1893 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1896 if (!strcmp(isc->name, "contactList")) {
1898 /* Parse groups */
1899 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1900 struct sipe_group * group = g_new0(struct sipe_group, 1);
1901 const char *name = xmlnode_get_attrib(group_node, "name");
1903 if (!strncmp(name, "~", 1)) {
1904 name = _("Other Contacts");
1906 group->name = g_strdup(name);
1907 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1909 sipe_group_add(sip, group);
1912 // Make sure we have at least one group
1913 if (g_slist_length(sip->groups) == 0) {
1914 struct sipe_group * group = g_new0(struct sipe_group, 1);
1915 PurpleGroup *purple_group;
1916 group->name = g_strdup(_("Other Contacts"));
1917 group->id = 1;
1918 purple_group = purple_group_new(group->name);
1919 purple_blist_add_group(purple_group, NULL);
1920 sip->groups = g_slist_append(sip->groups, group);
1923 /* Parse contacts */
1924 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1925 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1926 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1927 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1928 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1929 gchar **item_groups;
1930 struct sipe_group *group = NULL;
1931 struct sipe_buddy *buddy = NULL;
1932 int i = 0;
1934 // assign to group Other Contacts if nothing else received
1935 if(!groups || !strcmp("", groups) ) {
1936 group = sipe_group_find_by_name(sip, _("Other Contacts"));
1937 groups = group ? g_strdup_printf("%d", group->id) : "1";
1940 item_groups = g_strsplit(groups, " ", 0);
1942 while (item_groups[i]) {
1943 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1945 // If couldn't find the right group for this contact, just put them in the first group we have
1946 if (group == NULL && g_slist_length(sip->groups) > 0) {
1947 group = sip->groups->data;
1950 if (group != NULL) {
1951 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1952 if (!b){
1953 b = purple_buddy_new(sip->account, buddy_name, uri);
1954 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1957 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1958 if (name != NULL && strlen(name) != 0) {
1959 purple_blist_alias_buddy(b, name);
1963 if (!buddy) {
1964 buddy = g_new0(struct sipe_buddy, 1);
1965 buddy->name = g_strdup(b->name);
1966 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1969 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1971 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
1972 } else {
1973 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1974 name);
1977 i++;
1978 } // while, contact groups
1979 g_strfreev(item_groups);
1980 g_free(groups);
1981 g_free(name);
1982 g_free(buddy_name);
1983 g_free(uri);
1985 } // for, contacts
1987 sipe_cleanup_local_blist(sip);
1989 xmlnode_free(isc);
1991 //subscribe to buddies
1992 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1993 if(sip->batched_support){
1994 sipe_subscribe_presence_batched(sip, NULL);
1996 else{
1997 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
1999 sip->subscribed_buddies = TRUE;
2002 return 0;
2006 * Subscribe roaming contacts
2008 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2010 gchar *to = g_strdup_printf("sip:%s", sip->username);
2011 gchar *tmp = get_contact(sip);
2012 gchar *hdr = g_strdup_printf(
2013 "Event: vnd-microsoft-roaming-contacts\r\n"
2014 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2015 "Supported: com.microsoft.autoextend\r\n"
2016 "Supported: ms-benotify\r\n"
2017 "Proxy-Require: ms-benotify\r\n"
2018 "Supported: ms-piggyback-first-notify\r\n"
2019 "Contact: %s\r\n", tmp);
2020 g_free(tmp);
2022 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2023 g_free(to);
2024 g_free(hdr);
2027 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, void *unused)
2029 gchar *to = g_strdup_printf("sip:%s", sip->username);
2030 gchar *tmp = get_contact(sip);
2031 gchar *hdr = g_strdup_printf(
2032 "Event: presence.wpending\r\n"
2033 "Accept: text/xml+msrtc.wpending\r\n"
2034 "Supported: com.microsoft.autoextend\r\n"
2035 "Supported: ms-benotify\r\n"
2036 "Proxy-Require: ms-benotify\r\n"
2037 "Supported: ms-piggyback-first-notify\r\n"
2038 "Contact: %s\r\n", tmp);
2039 g_free(tmp);
2041 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2042 g_free(to);
2043 g_free(hdr);
2047 * Fires on deregistration event initiated by server.
2048 * [MS-SIPREGE] SIP extension.
2051 // 2007 Example
2053 // Content-Type: text/registration-event
2054 // subscription-state: terminated;expires=0
2055 // ms-diagnostics-public: 4141;reason="User disabled"
2057 // deregistered;event=rejected
2059 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2061 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2062 gchar *event = NULL;
2063 gchar *reason = NULL;
2064 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2066 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2067 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2069 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2070 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2071 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2072 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2073 } else {
2074 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2075 return;
2078 if (warning != NULL) {
2079 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2080 } else { // for LCS2005
2081 int error_id = 0;
2082 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2083 error_id = 4140; // [MS-SIPREGE]
2084 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2085 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2086 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2087 error_id = 4141;
2088 reason = g_strdup(_("User disabled")); // [MS-OCER]
2089 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2090 error_id = 4142;
2091 reason = g_strdup(_("User moved")); // [MS-OCER]
2094 g_free(event);
2095 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2096 g_free(reason);
2098 sip->gc->wants_to_die = TRUE;
2099 purple_connection_error(sip->gc, warning);
2100 g_free(warning);
2104 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2106 const gchar *contacts_delta;
2107 xmlnode *xml;
2109 xml = xmlnode_from_str(msg->body, msg->bodylen);
2110 if (!xml)
2112 return;
2115 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2116 if (contacts_delta)
2118 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2121 xmlnode_free(xml);
2124 static void
2125 free_container(struct sipe_container *container)
2127 GSList *entry;
2129 if (!container) return;
2131 entry = container->members;
2132 while (entry) {
2133 g_free(entry->data);
2134 entry = g_slist_remove(entry, entry->data);
2136 g_free(container);
2140 * Finds locally stored MS-PRES container member
2142 static struct sipe_container_member *
2143 sipe_find_container_member(struct sipe_container *container,
2144 const gchar *type,
2145 const gchar *value)
2147 struct sipe_container_member *member;
2148 GSList *entry;
2150 if (container == NULL || type == NULL) {
2151 return NULL;
2154 entry = container->members;
2155 while (entry) {
2156 member = entry->data;
2157 if (!g_strcasecmp(member->type, type)
2158 && ((!member->value && !value)
2159 || (value && member->value && !g_strcasecmp(member->value, value)))
2160 ) {
2161 return member;
2163 entry = entry->next;
2165 return NULL;
2169 * Finds locally stored MS-PRES container by id
2171 static struct sipe_container *
2172 sipe_find_container(struct sipe_account_data *sip,
2173 guint id)
2175 struct sipe_container *container;
2176 GSList *entry;
2178 if (sip == NULL) {
2179 return NULL;
2182 entry = sip->containers;
2183 while (entry) {
2184 container = entry->data;
2185 if (id == container->id) {
2186 return container;
2188 entry = entry->next;
2190 return NULL;
2194 * Access Levels
2195 * 32000 - Blocked
2196 * 400 - Personal
2197 * 300 - Team
2198 * 200 - Company
2199 * 100 - Public
2201 static int
2202 sipe_find_access_level(struct sipe_account_data *sip,
2203 const gchar *type,
2204 const gchar *value)
2206 guint containers[] = {32000, 400, 300, 200, 100};
2207 int i = 0;
2209 for (i = 0; i < 5; i++) {
2210 struct sipe_container_member *member;
2211 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2212 if (!container) continue;
2214 member = sipe_find_container_member(container, type, value);
2215 if (member) {
2216 return containers[i];
2220 return -1;
2223 static void
2224 sipe_send_set_container_members(struct sipe_account_data *sip,
2225 guint container_id,
2226 guint container_version,
2227 const gchar* action,
2228 const gchar* type,
2229 const gchar* value)
2231 gchar *self = g_strdup_printf("sip:%s", sip->username);
2232 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2233 gchar *contact;
2234 gchar *hdr;
2235 gchar *body = g_strdup_printf(
2236 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2237 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2238 "</setContainerMembers>",
2239 container_id,
2240 container_version,
2241 action,
2242 type,
2243 value_str);
2244 g_free(value_str);
2246 contact = get_contact(sip);
2247 hdr = g_strdup_printf("Contact: %s\r\n"
2248 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2249 g_free(contact);
2251 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2253 g_free(hdr);
2254 g_free(body);
2255 g_free(self);
2260 * When we receive some self (BE) NOTIFY with a new subscriber
2261 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2264 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
2266 gchar *contact;
2267 gchar *to;
2268 xmlnode *xml;
2269 xmlnode *node;
2270 xmlnode *node2;
2271 char *display_name = NULL;
2272 PurpleBuddy *pbuddy;
2273 const char *alias;
2274 char *uri_alias;
2275 char *uri_user;
2277 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2279 xml = xmlnode_from_str(msg->body, msg->bodylen);
2280 if (!xml) return;
2282 contact = get_contact(sip);
2283 to = g_strdup_printf("sip:%s", sip->username);
2285 /* containers */
2286 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
2287 guint id = atoi(xmlnode_get_attrib(node, "id"));
2288 struct sipe_container *container = sipe_find_container(sip, id);
2290 if (container) {
2291 sip->containers = g_slist_remove(sip->containers, container);
2292 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
2293 free_container(container);
2295 container = g_new0(struct sipe_container, 1);
2296 container->id = id;
2297 container->version = atoi(xmlnode_get_attrib(node, "version"));
2298 sip->containers = g_slist_append(sip->containers, container);
2299 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
2301 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
2302 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2303 member->type = xmlnode_get_attrib(node2, "type");
2304 member->value = xmlnode_get_attrib(node2, "value");
2305 container->members = g_slist_append(container->members, member);
2306 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
2307 member->type, member->value ? member->value : "");
2311 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
2312 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
2313 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
2314 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
2315 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
2316 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
2317 /* initial set-up to let counterparties see your status */
2318 if (sameEnterpriseAL < 0) {
2319 struct sipe_container *container = sipe_find_container(sip, 200);
2320 guint version = container ? container->version : 0;
2321 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
2323 if (federatedAL < 0) {
2324 struct sipe_container *container = sipe_find_container(sip, 100);
2325 guint version = container ? container->version : 0;
2326 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
2328 sip->access_level_set = TRUE;
2331 /* subscribers */
2332 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2333 const char *user;
2334 const char *acknowledged;
2335 gchar *hdr;
2336 gchar *body;
2338 user = xmlnode_get_attrib(node, "user");
2339 if (!user) continue;
2340 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2341 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2342 uri_user = g_strdup_printf("sip:%s", user);
2343 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2344 if(pbuddy){
2345 alias = purple_buddy_get_local_alias(pbuddy);
2346 uri_alias = g_strdup_printf("sip:%s", alias);
2347 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2348 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2349 purple_blist_alias_buddy(pbuddy, display_name);
2351 g_free(uri_alias);
2354 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2355 if(!g_ascii_strcasecmp(acknowledged,"false")){
2356 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2357 if (!purple_find_buddy(sip->account, uri_user)) {
2358 purple_account_request_add(sip->account, uri_user, _("you"), display_name, NULL);
2361 hdr = g_strdup_printf(
2362 "Contact: %s\r\n"
2363 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2365 body = g_strdup_printf(
2366 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2367 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2368 "</setSubscribers>", user);
2370 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2371 g_free(body);
2372 g_free(hdr);
2374 g_free(display_name);
2375 g_free(uri_user);
2378 g_free(to);
2379 g_free(contact);
2380 xmlnode_free(xml);
2383 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2385 gchar *to = g_strdup_printf("sip:%s", sip->username);
2386 gchar *tmp = get_contact(sip);
2387 gchar *hdr = g_strdup_printf(
2388 "Event: vnd-microsoft-roaming-ACL\r\n"
2389 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2390 "Supported: com.microsoft.autoextend\r\n"
2391 "Supported: ms-benotify\r\n"
2392 "Proxy-Require: ms-benotify\r\n"
2393 "Supported: ms-piggyback-first-notify\r\n"
2394 "Contact: %s\r\n", tmp);
2395 g_free(tmp);
2397 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2398 g_free(to);
2399 g_free(hdr);
2403 * To request for presence information about the user, access level settings that have already been configured by the user
2404 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2405 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2408 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2410 gchar *to = g_strdup_printf("sip:%s", sip->username);
2411 gchar *tmp = get_contact(sip);
2412 gchar *hdr = g_strdup_printf(
2413 "Event: vnd-microsoft-roaming-self\r\n"
2414 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2415 "Supported: ms-benotify\r\n"
2416 "Proxy-Require: ms-benotify\r\n"
2417 "Supported: ms-piggyback-first-notify\r\n"
2418 "Contact: %s\r\n"
2419 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2421 gchar *body=g_strdup(
2422 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2423 "<roaming type=\"categories\"/>"
2424 "<roaming type=\"containers\"/>"
2425 "<roaming type=\"subscribers\"/></roamingList>");
2427 g_free(tmp);
2428 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2429 g_free(body);
2430 g_free(to);
2431 g_free(hdr);
2435 * For 2005 version
2437 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2439 gchar *to = g_strdup_printf("sip:%s", sip->username);
2440 gchar *tmp = get_contact(sip);
2441 gchar *hdr = g_strdup_printf(
2442 "Event: vnd-microsoft-provisioning\r\n"
2443 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2444 "Supported: com.microsoft.autoextend\r\n"
2445 "Supported: ms-benotify\r\n"
2446 "Proxy-Require: ms-benotify\r\n"
2447 "Supported: ms-piggyback-first-notify\r\n"
2448 "Expires: 0\r\n"
2449 "Contact: %s\r\n", tmp);
2451 g_free(tmp);
2452 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2453 g_free(to);
2454 g_free(hdr);
2457 /** Subscription for provisioning information to help with initial
2458 * configuration. This subscription is a one-time query (denoted by the Expires header,
2459 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2460 * configuration, meeting policies, and policy settings that Communicator must enforce.
2461 * TODO: for what we need this information.
2464 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2466 gchar *to = g_strdup_printf("sip:%s", sip->username);
2467 gchar *tmp = get_contact(sip);
2468 gchar *hdr = g_strdup_printf(
2469 "Event: vnd-microsoft-provisioning-v2\r\n"
2470 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2471 "Supported: com.microsoft.autoextend\r\n"
2472 "Supported: ms-benotify\r\n"
2473 "Proxy-Require: ms-benotify\r\n"
2474 "Supported: ms-piggyback-first-notify\r\n"
2475 "Expires: 0\r\n"
2476 "Contact: %s\r\n"
2477 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2478 gchar *body = g_strdup(
2479 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2480 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2481 "<provisioningGroup name=\"ucPolicy\"/>"
2482 "</provisioningGroupList>");
2484 g_free(tmp);
2485 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2486 g_free(body);
2487 g_free(to);
2488 g_free(hdr);
2491 /* IM Session (INVITE and MESSAGE methods) */
2493 static struct sip_dialog *
2494 get_dialog (struct sip_im_session *session,
2495 const gchar *who)
2497 struct sip_dialog *dialog;
2498 GSList *entry;
2499 if (session == NULL || who == NULL) {
2500 return NULL;
2503 entry = session->dialogs;
2504 while (entry) {
2505 dialog = entry->data;
2506 if (dialog->with && !strcmp(who, dialog->with)) {
2507 return dialog;
2509 entry = entry->next;
2511 return NULL;
2514 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2515 static gchar *
2516 get_end_points (struct sipe_account_data *sip,
2517 struct sip_im_session *session)
2519 gchar *tmp = NULL;
2520 gchar *res = g_strdup_printf("<sip:%s>", sip->username);
2521 struct sip_dialog *dialog;
2522 GSList *entry;
2523 if (session == NULL) {
2524 return NULL;
2527 entry = session->dialogs;
2528 while (entry) {
2529 dialog = entry->data;
2531 tmp = res;
2532 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2533 g_free(tmp);
2535 if (dialog->theirepid) {
2536 tmp = res;
2537 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2538 g_free(tmp);
2541 entry = entry->next;
2543 return res;
2546 static struct sip_im_session *
2547 find_chat_session_by_id (struct sipe_account_data *sip,
2548 int id)
2550 struct sip_im_session *session;
2551 GSList *entry;
2552 if (sip == NULL) {
2553 return NULL;
2556 entry = sip->im_sessions;
2557 while (entry) {
2558 session = entry->data;
2559 if (id == session->chat_id) {
2560 return session;
2562 entry = entry->next;
2564 return NULL;
2567 static struct sip_im_session *
2568 find_chat_session_by_name (struct sipe_account_data *sip,
2569 const char *chat_name)
2571 struct sip_im_session *session;
2572 GSList *entry;
2573 if (sip == NULL || chat_name == NULL) {
2574 return NULL;
2577 entry = sip->im_sessions;
2578 while (entry) {
2579 session = entry->data;
2580 if (session->chat_name && !g_strcasecmp(chat_name, session->chat_name)) {
2581 return session;
2583 entry = entry->next;
2585 return NULL;
2588 static struct sip_im_session *
2589 find_chat_session (struct sipe_account_data *sip,
2590 const char *callid)
2592 struct sip_im_session *session;
2593 GSList *entry;
2594 if (sip == NULL || callid == NULL) {
2595 return NULL;
2598 entry = sip->im_sessions;
2599 while (entry) {
2600 session = entry->data;
2601 if (session->callid && !g_strcasecmp(callid, session->callid)) {
2602 return session;
2604 entry = entry->next;
2606 return NULL;
2609 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2611 struct sip_im_session *session;
2612 GSList *entry;
2613 if (sip == NULL || who == NULL) {
2614 return NULL;
2617 entry = sip->im_sessions;
2618 while (entry) {
2619 session = entry->data;
2620 if (session->with && !strcmp(who, session->with)) {
2621 return session;
2623 entry = entry->next;
2625 return NULL;
2628 struct sip_im_session *
2629 create_chat_session (struct sipe_account_data *sip)
2631 struct sip_im_session *session = g_new0(struct sip_im_session, 1);
2632 session->callid = gencallid();
2633 session->is_multiparty = TRUE;
2634 session->chat_id = rand();
2635 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2636 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2637 return session;
2640 static struct sip_im_session * find_or_create_chat_session (struct sipe_account_data *sip, const char *callid)
2642 struct sip_im_session *session = find_chat_session(sip, callid);
2643 if (!session) {
2644 session = create_chat_session(sip);
2645 session->callid = g_strdup(callid);
2647 return session;
2650 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2652 struct sip_im_session *session = find_im_session(sip, who);
2653 if (!session) {
2654 session = g_new0(struct sip_im_session, 1);
2655 session->is_multiparty = FALSE;
2656 session->with = g_strdup(who);
2657 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2658 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2660 return session;
2663 void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2665 GSList *entry;
2667 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2669 entry = session->dialogs;
2670 while (entry) {
2671 free_dialog(entry->data);
2672 entry = g_slist_remove(entry, entry->data);
2675 entry = session->outgoing_message_queue;
2676 while (entry) {
2677 g_free(entry->data);
2678 entry = g_slist_remove(entry, entry->data);
2681 entry = session->pending_invite_queue;
2682 while (entry) {
2683 g_free(entry->data);
2684 entry = g_slist_remove(entry, entry->data);
2687 g_hash_table_destroy(session->unconfirmed_messages);
2689 g_free(session->with);
2690 g_free(session->chat_name);
2691 g_free(session->callid);
2692 g_free(session->roster_manager);
2693 g_free(session->focus_uri);
2694 g_free(session);
2697 static gboolean
2698 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2700 gboolean ret = TRUE;
2702 if (msg->response != 200) {
2703 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2704 return FALSE;
2707 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2709 return ret;
2713 * Asks UA/proxy about its capabilities.
2715 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2717 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2718 gchar *contact = get_contact(sip);
2719 gchar *request;
2720 request = g_strdup_printf(
2721 "Accept: application/sdp\r\n"
2722 "Contact: %s\r\n", contact);
2724 g_free(contact);
2726 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2728 g_free(to);
2729 g_free(request);
2732 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2734 char *msg, *msg_tmp;
2735 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2736 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2737 g_free(msg_tmp);
2738 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2739 "possibly because one or more persons are offline:\n%s") ,
2740 msg ? msg : "");
2741 purple_conv_present_error(with, sip->account, msg_tmp);
2742 g_free(msg);
2743 g_free(msg_tmp);
2746 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2748 static gboolean
2749 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2751 gboolean ret = TRUE;
2752 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2753 struct sip_im_session * session = find_im_session(sip, with);
2754 struct sip_dialog *dialog;
2755 gchar *cseq;
2756 char *key;
2757 gchar *message;
2759 if (!session) {
2760 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2761 g_free(with);
2762 return FALSE;
2765 dialog = get_dialog(session, with);
2767 if (!dialog) {
2768 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2769 g_free(with);
2770 return FALSE;
2773 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2774 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2775 g_free(cseq);
2776 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2778 if (msg->response != 200) {
2779 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2781 sipe_present_message_undelivered_err(with, sip, message);
2782 im_session_destroy(sip, session);
2783 ret = FALSE;
2784 } else {
2785 g_hash_table_remove(session->unconfirmed_messages, key);
2786 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2787 key, g_hash_table_size(session->unconfirmed_messages));
2790 g_free(key);
2791 g_free(with);
2793 if (ret) sipe_im_process_queue(sip, session);
2794 return ret;
2797 static gboolean
2798 sipe_is_election_finished(struct sipe_account_data *sip,
2799 struct sip_im_session *session);
2801 static void
2802 sipe_election_result(struct sipe_account_data *sip,
2803 void *sess);
2805 static gboolean
2806 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2808 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2809 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2810 struct sip_dialog *dialog;
2811 struct sip_im_session *session;
2813 session = find_chat_session(sip, callid);
2814 if (!session) {
2815 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2816 return FALSE;
2819 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2820 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2821 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2822 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2824 if (xn_request_rm_response) {
2825 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2826 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2828 dialog = get_dialog(session, with);
2829 if (!dialog) {
2830 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2831 return FALSE;
2834 if (allow && !g_strcasecmp(allow, "true")) {
2835 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2836 dialog->election_vote = 1;
2837 } else if (allow && !g_strcasecmp(allow, "false")) {
2838 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2839 dialog->election_vote = -1;
2842 if (sipe_is_election_finished(sip, session)) {
2843 sipe_election_result(sip, session);
2846 } else if (xn_set_rm_response) {
2849 xmlnode_free(xn_action);
2853 return TRUE;
2856 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2858 gchar *hdr;
2859 gchar *tmp;
2860 char *msgformat;
2861 char *msgtext;
2862 gchar *msgr_value;
2863 gchar *msgr;
2865 sipe_parse_html(msg, &msgformat, &msgtext);
2866 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2868 msgr_value = sipmsg_get_msgr_string(msgformat);
2869 g_free(msgformat);
2870 if (msgr_value) {
2871 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2872 g_free(msgr_value);
2873 } else {
2874 msgr = g_strdup("");
2877 tmp = get_contact(sip);
2878 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2879 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2880 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2881 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2882 g_free(tmp);
2883 g_free(msgr);
2885 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2886 g_free(msgtext);
2887 g_free(hdr);
2891 static void
2892 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2894 GSList *entry2 = session->outgoing_message_queue;
2895 while (entry2) {
2896 char *queued_msg = entry2->data;
2897 struct sip_dialog *dialog;
2898 GSList *entry = session->dialogs;
2900 if (session->is_multiparty) {
2901 gchar *who = g_strdup_printf("sip:%s", sip->username);
2902 serv_got_chat_in(sip->gc, session->chat_id, who,
2903 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2904 g_free(who);
2907 while (entry) {
2908 char *key;
2910 dialog = entry->data;
2911 entry = entry->next;
2912 if (dialog->outgoing_invite) continue; //do not send messages as INVITE is not responded.
2914 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2915 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2916 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2917 key, g_hash_table_size(session->unconfirmed_messages));
2918 g_free(key);
2919 sipe_send_message(sip, dialog, queued_msg);
2922 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2923 g_free(queued_msg);
2927 static void
2928 sipe_refer_notify(struct sipe_account_data *sip,
2929 struct sip_im_session *session,
2930 const gchar *who,
2931 int status,
2932 const gchar *desc)
2934 gchar *hdr;
2935 gchar *body;
2936 struct sip_dialog *dialog = get_dialog(session, who);
2938 hdr = g_strdup_printf(
2939 "Event: refer\r\n"
2940 "Subscription-State: %s\r\n"
2941 "Content-Type: message/sipfrag\r\n",
2942 status >= 200 ? "terminated" : "active");
2944 body = g_strdup_printf(
2945 "SIP/2.0 %d %s\r\n",
2946 status, desc);
2948 dialog->outgoing_invite = send_sip_request(sip->gc, "NOTIFY",
2949 who, who, hdr, body, dialog, NULL);
2951 g_free(hdr);
2952 g_free(body);
2955 static gboolean
2956 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2958 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2959 struct sip_im_session *session;
2960 struct sip_dialog *dialog;
2961 char *cseq;
2962 char *key;
2963 gchar *message;
2964 struct sipmsg *request_msg = trans->msg;
2966 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2967 gchar *referred_by;
2969 session = find_chat_session(sip, callid);
2970 if (!session) {
2971 session = find_im_session(sip, with);
2974 if (!session) {
2975 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2976 g_free(with);
2977 return FALSE;
2980 dialog = get_dialog(session, with);
2981 if (!dialog) {
2982 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2983 g_free(with);
2984 return FALSE;
2987 sipe_parse_dialog(msg, dialog, TRUE);
2989 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2990 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2991 g_free(cseq);
2992 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2994 if (msg->response != 200) {
2995 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2997 sipe_present_message_undelivered_err(with, sip, message);
2998 im_session_destroy(sip, session);
2999 g_free(key);
3000 g_free(with);
3001 return FALSE;
3004 dialog->cseq = 0;
3005 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3006 dialog->outgoing_invite = NULL;
3007 dialog->is_established = TRUE;
3009 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3010 if (referred_by) {
3011 sipe_refer_notify(sip, session, referred_by, 200, "OK");
3012 g_free(referred_by);
3015 /* add user to chat if it is a multiparty session */
3016 if (session->is_multiparty) {
3017 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3018 with, NULL,
3019 PURPLE_CBFLAGS_NONE, TRUE);
3022 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3023 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3024 if (session->outgoing_message_queue) {
3025 char *queued_msg = session->outgoing_message_queue->data;
3026 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3027 g_free(queued_msg);
3031 sipe_im_process_queue(sip, session);
3033 g_hash_table_remove(session->unconfirmed_messages, key);
3034 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3035 key, g_hash_table_size(session->unconfirmed_messages));
3037 g_free(key);
3038 g_free(with);
3039 return TRUE;
3043 static void
3044 sipe_invite(struct sipe_account_data *sip,
3045 struct sip_im_session *session,
3046 const gchar *who,
3047 const gchar *msg_body,
3048 const gchar *referred_by,
3049 const gboolean is_triggered)
3051 gchar *hdr;
3052 gchar *to;
3053 gchar *contact;
3054 gchar *body;
3055 gchar *self;
3056 char *ms_text_format = g_strdup("");
3057 gchar *roster_manager;
3058 gchar *end_points;
3059 gchar *referred_by_str;
3060 struct sip_dialog *dialog = get_dialog(session, who);
3062 if (dialog && dialog->is_established) {
3063 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
3064 return;
3067 if (!dialog) {
3068 dialog = g_new0(struct sip_dialog, 1);
3069 session->dialogs = g_slist_append(session->dialogs, dialog);
3071 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3072 dialog->with = g_strdup(who);
3075 if (!(dialog->ourtag)) {
3076 dialog->ourtag = gentag();
3080 if (strstr(who, "sip:")) {
3081 to = g_strdup(who);
3082 } else {
3083 to = g_strdup_printf("sip:%s", who);
3086 if (msg_body) {
3087 char *msgformat;
3088 char *msgtext;
3089 char *base64_msg;
3090 gchar *msgr_value;
3091 gchar *msgr;
3092 char *key;
3094 sipe_parse_html(msg_body, &msgformat, &msgtext);
3095 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
3097 msgr_value = sipmsg_get_msgr_string(msgformat);
3098 g_free(msgformat);
3099 msgr = "";
3100 if (msgr_value) {
3101 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3102 g_free(msgr_value);
3105 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3106 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3107 g_free(msgtext);
3108 g_free(msgr);
3109 g_free(base64_msg);
3111 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3112 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3113 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3114 key, g_hash_table_size(session->unconfirmed_messages));
3115 g_free(key);
3118 contact = get_contact(sip);
3119 end_points = get_end_points(sip, session);
3120 self = g_strdup_printf("sip:%s", sip->username);
3121 roster_manager = g_strdup_printf(
3122 "Roster-Manager: %s\r\n"
3123 "EndPoints: %s\r\n",
3124 self,
3125 end_points);
3126 referred_by_str = referred_by ?
3127 g_strdup_printf(
3128 "Referred-By: %s\r\n",
3129 referred_by)
3130 : g_strdup("");
3131 hdr = g_strdup_printf(
3132 "%s"
3133 "%s"
3134 "%s"
3135 "%s"
3136 "Contact: %s\r\n%s"
3137 "Content-Type: application/sdp\r\n",
3138 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3139 referred_by_str,
3140 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3141 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3142 contact,
3143 ms_text_format);
3144 g_free(ms_text_format);
3145 g_free(self);
3147 body = g_strdup_printf(
3148 "v=0\r\n"
3149 "o=- 0 0 IN IP4 %s\r\n"
3150 "s=session\r\n"
3151 "c=IN IP4 %s\r\n"
3152 "t=0 0\r\n"
3153 "m=message %d sip null\r\n"
3154 "a=accept-types:text/plain text/html image/gif "
3155 "multipart/alternative application/im-iscomposing+xml\r\n",
3156 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3158 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3159 to, to, hdr, body, dialog, process_invite_response);
3161 g_free(to);
3162 g_free(roster_manager);
3163 g_free(end_points);
3164 g_free(referred_by_str);
3165 g_free(body);
3166 g_free(hdr);
3167 g_free(contact);
3170 static void
3171 sipe_refer(struct sipe_account_data *sip,
3172 struct sip_im_session *session,
3173 const gchar *who)
3175 gchar *hdr;
3176 gchar *contact;
3177 struct sip_dialog *dialog = get_dialog(session, session->roster_manager);
3179 contact = get_contact(sip);
3180 hdr = g_strdup_printf(
3181 "Contact: %s\r\n"
3182 "Refer-to: <%s>\r\n"
3183 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3184 "Require: com.microsoft.rtc-multiparty\r\n",
3185 contact,
3186 who,
3187 sip->username,
3188 dialog->ourtag ? ";tag=" : "",
3189 dialog->ourtag ? dialog->ourtag : "",
3190 get_epid(sip));
3192 send_sip_request(sip->gc, "REFER",
3193 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3195 g_free(hdr);
3196 g_free(contact);
3199 static void
3200 sipe_send_election_request_rm(struct sipe_account_data *sip,
3201 struct sip_im_session *session,
3202 const gchar *who,
3203 int bid)
3205 gchar *hdr;
3206 gchar *body;
3207 struct sip_dialog *dialog = get_dialog(session, who);
3209 hdr = "Content-Type: application/x-ms-mim\r\n";
3211 body = g_strdup_printf(
3212 "<?xml version=\"1.0\"?>\r\n"
3213 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3214 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3215 sip->username, bid);
3217 send_sip_request(sip->gc, "INFO",
3218 who, who, hdr, body, dialog, process_info_response);
3220 g_free(body);
3223 static void
3224 sipe_send_election_set_rm(struct sipe_account_data *sip,
3225 struct sip_im_session *session,
3226 const gchar *who)
3228 gchar *hdr;
3229 gchar *body;
3230 struct sip_dialog *dialog = get_dialog(session, who);
3232 hdr = "Content-Type: application/x-ms-mim\r\n";
3234 body = g_strdup_printf(
3235 "<?xml version=\"1.0\"?>\r\n"
3236 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3237 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3238 sip->username);
3240 send_sip_request(sip->gc, "INFO",
3241 who, who, hdr, body, dialog, process_info_response);
3243 g_free(body);
3246 static void
3247 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
3249 if (session) {
3250 struct sip_dialog *dialog;
3251 GSList *entry;
3252 entry = session->dialogs;
3253 while (entry) {
3254 dialog = entry->data;
3255 /* @TODO slow down BYE message sending rate */
3256 /* @see single subscription code */
3257 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3258 entry = entry->next;
3261 im_session_destroy(sip, session);
3265 static void
3266 sipe_convo_closed(PurpleConnection * gc, const char *who)
3268 struct sipe_account_data *sip = gc->proto_data;
3270 purple_debug_info("sipe", "conversation with %s closed\n", who);
3271 im_session_close(sip, find_im_session(sip, who));
3274 static void
3275 sipe_chat_leave (PurpleConnection *gc, int id)
3277 struct sipe_account_data *sip = gc->proto_data;
3278 struct sip_im_session * session = find_chat_session_by_id(sip, id);
3279 im_session_close(sip, session);
3282 static void
3283 im_session_close_all (struct sipe_account_data *sip)
3285 GSList *entry = sip->im_sessions;
3286 while (entry) {
3287 im_session_close (sip, entry->data);
3288 entry = sip->im_sessions;
3292 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
3294 struct sipe_account_data *sip = gc->proto_data;
3295 struct sip_im_session *session;
3296 struct sip_dialog *dialog;
3298 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3300 session = find_or_create_im_session(sip, who);
3301 dialog = get_dialog(session, who);
3303 // Queue the message
3304 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3306 if (dialog && dialog->callid) {
3307 sipe_im_process_queue(sip, session);
3308 } else if (!dialog || !dialog->outgoing_invite) {
3309 // Need to send the INVITE to get the outgoing dialog setup
3310 sipe_invite(sip, session, who, what, NULL, FALSE);
3313 return 1;
3316 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags)
3318 struct sipe_account_data *sip = gc->proto_data;
3319 struct sip_im_session *session;
3321 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3323 session = find_chat_session_by_id(sip, id);
3325 // Queue the message
3326 if (session) {
3327 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3328 g_strdup(what));
3329 sipe_im_process_queue(sip, session);
3332 return 1;
3335 /* End IM Session (INVITE and MESSAGE methods) */
3337 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3339 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3340 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3341 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3343 struct sip_im_session *session = find_chat_session(sip, callid);
3344 if (!session) {
3345 session = find_im_session(sip, from);
3348 if (!session) {
3349 return;
3352 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3353 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3354 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3355 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3357 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
3359 if (xn_request_rm) {
3360 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3361 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3362 gchar *body = g_strdup_printf(
3363 "<?xml version=\"1.0\"?>\r\n"
3364 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3365 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3366 sip->username,
3367 session->bid < bid ? "true" : "false");
3368 send_sip_response(sip->gc, msg, 200, "OK", body);
3369 g_free(body);
3370 } else if (xn_set_rm) {
3371 gchar *body;
3372 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3373 g_free(session->roster_manager);
3374 session->roster_manager = g_strdup(rm);
3376 body = g_strdup_printf(
3377 "<?xml version=\"1.0\"?>\r\n"
3378 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3379 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3380 sip->username);
3381 send_sip_response(sip->gc, msg, 200, "OK", body);
3382 g_free(body);
3384 xmlnode_free(xn_action);
3386 } else {
3387 /* looks like purple lacks typing notification for chat */
3388 if (!session->is_multiparty) {
3389 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3392 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3394 g_free(from);
3397 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3399 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3400 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3401 struct sip_im_session *session;
3402 struct sip_dialog *dialog;
3404 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3406 session = find_chat_session(sip, callid);
3407 if (!session) {
3408 session = find_im_session(sip, from);
3411 if (!session) {
3412 return;
3415 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3416 g_free(session->roster_manager);
3417 session->roster_manager = NULL;
3420 if (!session->is_multiparty) {
3421 // TODO Let the user know the other user left the conversation?
3422 im_session_destroy(sip, session);
3423 } else {
3424 dialog = get_dialog(session, from);
3425 session->dialogs = g_slist_remove(session->dialogs, dialog);
3426 free_dialog(dialog);
3428 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3430 if (!session->dialogs) {
3431 im_session_destroy(sip, session);
3435 g_free(from);
3438 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3440 gchar *self = g_strdup_printf("sip:%s", sip->username);
3441 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3442 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3443 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3444 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3445 struct sip_im_session *session;
3446 struct sip_dialog *dialog;
3448 session = find_chat_session(sip, callid);
3449 dialog = get_dialog(session, from);
3451 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3452 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3453 } else {
3454 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3456 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3459 g_free(self);
3460 g_free(from);
3461 g_free(refer_to);
3462 g_free(referred_by);
3465 static unsigned int
3466 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3468 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3469 struct sip_im_session *session;
3470 struct sip_dialog *dialog;
3472 if (state == PURPLE_NOT_TYPING)
3473 return 0;
3475 session = find_im_session(sip, who);
3476 dialog = get_dialog(session, who);
3478 if (session && dialog) {
3479 send_sip_request(gc, "INFO", who, who,
3480 "Content-Type: application/xml\r\n",
3481 SIPE_SEND_TYPING, dialog, NULL);
3483 return SIPE_TYPING_SEND_TIMEOUT;
3486 static gboolean resend_timeout(struct sipe_account_data *sip)
3488 GSList *tmp = sip->transactions;
3489 time_t currtime = time(NULL);
3490 while (tmp) {
3491 struct transaction *trans = tmp->data;
3492 tmp = tmp->next;
3493 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3494 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3495 /* TODO 408 */
3496 } else {
3497 if ((currtime - trans->time > 2) && trans->retries == 0) {
3498 trans->retries++;
3499 sendout_sipmsg(sip, trans->msg);
3503 return TRUE;
3506 static void do_reauthenticate_cb(struct sipe_account_data *sip, void *unused)
3508 /* register again when security token expires */
3509 /* we have to start a new authentication as the security token
3510 * is almost expired by sending a not signed REGISTER message */
3511 purple_debug_info("sipe", "do a full reauthentication\n");
3512 sipe_auth_free(&sip->registrar);
3513 sipe_auth_free(&sip->proxy);
3514 sip->registerstatus = 0;
3515 do_register(sip);
3516 sip->reauthenticate_set = FALSE;
3519 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3521 gchar *from;
3522 gchar *contenttype;
3523 gboolean found = FALSE;
3525 from = parse_from(sipmsg_find_header(msg, "From"));
3527 if (!from) return;
3529 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3531 contenttype = sipmsg_find_header(msg, "Content-Type");
3532 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
3534 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3535 gchar *html = get_html_message(contenttype, msg->body);
3537 struct sip_im_session *session = find_chat_session(sip, callid);
3538 if (!session) {
3539 session = find_im_session(sip, from);
3542 if (session && session->is_multiparty) {
3543 serv_got_chat_in(sip->gc, session->chat_id, from,
3544 PURPLE_MESSAGE_RECV, html, time(NULL));
3545 } else {
3546 serv_got_im(sip->gc, from, html, 0, time(NULL));
3548 g_free(html);
3549 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3550 found = TRUE;
3552 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3553 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3554 xmlnode *state;
3555 gchar *statedata;
3557 if (!isc) {
3558 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3559 return;
3562 state = xmlnode_get_child(isc, "state");
3564 if (!state) {
3565 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3566 xmlnode_free(isc);
3567 return;
3570 statedata = xmlnode_get_data(state);
3571 if (statedata) {
3572 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3573 else serv_got_typing_stopped(sip->gc, from);
3575 g_free(statedata);
3577 xmlnode_free(isc);
3578 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3579 found = TRUE;
3581 if (!found) {
3582 purple_debug_info("sipe", "got unknown mime-type");
3583 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3585 g_free(from);
3588 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3590 gchar *ms_text_format;
3591 gchar *body;
3592 gchar *newTag = gentag();
3593 gchar *oldHeader;
3594 gchar *newHeader;
3595 gboolean is_multiparty = FALSE;
3596 gboolean is_triggered = FALSE;
3597 gboolean was_multiparty = TRUE;
3598 gboolean just_joined = FALSE;
3599 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3600 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3601 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3602 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
3603 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
3604 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
3605 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3606 GSList *end_points = NULL;
3607 struct sip_im_session *session;
3608 struct sip_dialog *dialog;
3610 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3612 /* Invitation to join conference */
3613 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
3614 process_incoming_invite_conf(sip, msg);
3615 return;
3618 /* Only accept text invitations */
3619 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3620 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3621 return;
3624 // TODO There *must* be a better way to clean up the To header to add a tag...
3625 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3626 oldHeader = sipmsg_find_header(msg, "To");
3627 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3628 sipmsg_remove_header_now(msg, "To");
3629 sipmsg_add_header_now(msg, "To", newHeader);
3630 g_free(newHeader);
3632 if (end_points_hdr) {
3633 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
3635 if (g_slist_length(end_points) > 2) {
3636 is_multiparty = TRUE;
3639 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
3640 is_triggered = TRUE;
3641 is_multiparty = TRUE;
3644 session = find_chat_session(sip, callid);
3645 /* Convert to multiparty */
3646 if (session && is_multiparty && !session->is_multiparty) {
3647 g_free(session->with);
3648 session->with = NULL;
3649 was_multiparty = FALSE;
3650 session->is_multiparty = TRUE;
3651 session->chat_id = rand();
3654 if (!session && is_multiparty) {
3655 session = find_or_create_chat_session(sip, callid);
3657 /* IM session */
3658 if (!session) {
3659 session = find_or_create_im_session(sip, from);
3662 if (!session->callid) {
3663 session->callid = g_strdup(callid);
3666 session->is_multiparty = is_multiparty;
3667 if (roster_manager) {
3668 session->roster_manager = g_strdup(roster_manager);
3671 if (is_multiparty && end_points) {
3672 GSList *entry = end_points;
3673 while (entry) {
3674 struct sipendpoint *end_point = entry->data;
3675 entry = entry->next;
3677 if (!g_strcasecmp(from, end_point->contact) ||
3678 !g_strcasecmp(to, end_point->contact))
3679 continue;
3681 dialog = get_dialog(session, end_point->contact);
3682 if (dialog) {
3683 g_free(dialog->theirepid);
3684 dialog->theirepid = end_point->epid;
3685 end_point->epid = NULL;
3686 } else {
3687 dialog = g_new0(struct sip_dialog, 1);
3688 session->dialogs = g_slist_append(session->dialogs, dialog);
3690 dialog->callid = g_strdup(session->callid);
3691 dialog->with = end_point->contact;
3692 end_point->contact = NULL;
3693 dialog->theirepid = end_point->epid;
3694 end_point->epid = NULL;
3696 just_joined = TRUE;
3698 /* send triggered INVITE */
3699 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
3704 if (end_points) {
3705 GSList *entry = end_points;
3706 while (entry) {
3707 struct sipendpoint *end_point = entry->data;
3708 entry = entry->next;
3709 g_free(end_point->contact);
3710 g_free(end_point->epid);
3711 g_free(end_point);
3713 g_slist_free(end_points);
3716 if (session) {
3717 dialog = get_dialog(session, from);
3718 if (dialog) {
3719 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3720 } else {
3721 dialog = g_new0(struct sip_dialog, 1);
3722 session->dialogs = g_slist_append(session->dialogs, dialog);
3724 dialog->callid = g_strdup(session->callid);
3725 dialog->with = g_strdup(from);
3726 sipe_parse_dialog(msg, dialog, FALSE);
3728 if (!dialog->ourtag) {
3729 dialog->ourtag = newTag;
3730 newTag = NULL;
3733 just_joined = TRUE;
3735 } else {
3736 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3738 g_free(newTag);
3740 if (is_multiparty && !session->conv) {
3741 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3742 gchar *self = g_strdup_printf("sip:%s", sip->username);
3743 /* create prpl chat */
3744 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3745 session->chat_name = g_strdup(chat_name);
3746 /* add self */
3747 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3748 self, NULL,
3749 PURPLE_CBFLAGS_NONE, FALSE);
3750 g_free(chat_name);
3751 g_free(self);
3754 if (is_multiparty && !was_multiparty) {
3755 /* add current IM counterparty to chat */
3756 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3757 ((struct sip_dialog *)session->dialogs->data)->with, NULL,
3758 PURPLE_CBFLAGS_NONE, FALSE);
3762 /* add inviting party */
3763 if (just_joined) {
3764 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3765 from, NULL,
3766 PURPLE_CBFLAGS_NONE, TRUE);
3769 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3770 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3771 if (ms_text_format) {
3772 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3774 gchar *html = get_html_message(ms_text_format, NULL);
3775 if (html) {
3776 if (is_multiparty) {
3777 serv_got_chat_in(sip->gc, session->chat_id, from,
3778 PURPLE_MESSAGE_RECV, html, time(NULL));
3779 } else {
3780 serv_got_im(sip->gc, from, html, 0, time(NULL));
3782 g_free(html);
3783 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3787 g_free(from);
3789 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3790 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3791 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3793 body = g_strdup_printf(
3794 "v=0\r\n"
3795 "o=- 0 0 IN IP4 %s\r\n"
3796 "s=session\r\n"
3797 "c=IN IP4 %s\r\n"
3798 "t=0 0\r\n"
3799 "m=message %d sip sip:%s\r\n"
3800 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3801 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3802 sip->realport, sip->username);
3803 send_sip_response(sip->gc, msg, 200, "OK", body);
3804 g_free(body);
3807 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3809 gchar *body;
3811 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
3812 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3813 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3815 body = g_strdup_printf(
3816 "v=0\r\n"
3817 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3818 "s=session\r\n"
3819 "c=IN IP4 0.0.0.0\r\n"
3820 "t=0 0\r\n"
3821 "m=message %d sip sip:%s\r\n"
3822 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3823 sip->realport, sip->username);
3824 send_sip_response(sip->gc, msg, 200, "OK", body);
3825 g_free(body);
3828 static void sipe_connection_cleanup(struct sipe_account_data *);
3829 static void create_connection(struct sipe_account_data *, gchar *, int);
3831 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3833 gchar *tmp;
3834 const gchar *expires_header;
3835 int expires, i;
3836 GSList *hdr = msg->headers;
3837 GSList *entry;
3838 struct siphdrelement *elem;
3840 expires_header = sipmsg_find_header(msg, "Expires");
3841 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3842 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3844 switch (msg->response) {
3845 case 200:
3846 if (expires == 0) {
3847 sip->registerstatus = 0;
3848 } else {
3849 gchar *contact_hdr = NULL;
3850 gchar *gruu = NULL;
3851 gchar *epid;
3852 gchar *uuid;
3853 gchar *timeout;
3855 if (!sip->reregister_set) {
3856 gchar *action_name = g_strdup_printf("<%s>", "registration");
3857 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3858 g_free(action_name);
3859 sip->reregister_set = TRUE;
3862 sip->registerstatus = 3;
3864 #ifdef USE_KERBEROS
3865 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3866 #endif
3867 tmp = sipmsg_find_auth_header(msg, "NTLM");
3868 #ifdef USE_KERBEROS
3869 } else {
3870 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3872 #endif
3873 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3874 fill_auth(sip, tmp, &sip->registrar);
3876 if (!sip->reauthenticate_set) {
3877 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3878 guint reauth_timeout;
3879 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3880 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3881 reauth_timeout = sip->registrar.expires - 300;
3882 } else {
3883 /* NTLM: we have to reauthenticate as our security token expires
3884 after eight hours (be five minutes early) */
3885 reauth_timeout = (8 * 3600) - 300;
3887 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3888 g_free(action_name);
3889 sip->reauthenticate_set = TRUE;
3892 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3894 epid = get_epid(sip);
3895 uuid = generateUUIDfromEPID(epid);
3896 g_free(epid);
3898 // There can be multiple Contact headers (one per location where the user is logged in) so
3899 // make sure to only get the one for this uuid
3900 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3901 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3902 if (valid_contact) {
3903 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3904 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3905 g_free(valid_contact);
3906 break;
3907 } else {
3908 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3911 g_free(uuid);
3913 g_free(sip->contact);
3914 if(gruu) {
3915 sip->contact = g_strdup_printf("<%s>", gruu);
3916 g_free(gruu);
3917 } else {
3918 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3919 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);
3921 sip->msrtc_event_categories = FALSE;
3922 sip->batched_support = FALSE;
3924 while(hdr)
3926 elem = hdr->data;
3927 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3928 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3929 sip->msrtc_event_categories = TRUE;
3930 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3932 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3933 sip->batched_support = TRUE;
3934 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3937 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3938 gchar **caps = g_strsplit(elem->value,",",0);
3939 i = 0;
3940 while (caps[i]) {
3941 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3942 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3943 i++;
3945 g_strfreev(caps);
3947 hdr = g_slist_next(hdr);
3950 if (!sip->subscribed) { //do it just once, not every re-register
3951 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3952 //sipe_options_request(sip, sip->sipdomain);
3954 entry = sip->allow_events;
3955 while (entry) {
3956 tmp = entry->data;
3957 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3958 sipe_subscribe_roaming_contacts(sip, msg);
3960 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3961 sipe_subscribe_roaming_acl(sip, msg);
3963 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3964 sipe_subscribe_roaming_self(sip, msg);
3966 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3967 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3968 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3969 sipe_subscribe_roaming_provisioning(sip, msg);
3971 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3972 sipe_subscribe_presence_wpending(sip, msg);
3974 entry = entry->next;
3976 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3977 sip->subscribed = TRUE;
3980 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3981 "timeout=", ";", NULL);
3982 if (timeout != NULL) {
3983 sscanf(timeout, "%u", &sip->keepalive_timeout);
3984 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3985 sip->keepalive_timeout);
3986 g_free(timeout);
3989 // Should we remove the transaction here?
3990 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3991 transactions_remove(sip, tc);
3993 break;
3994 case 301:
3996 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3998 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3999 gchar **parts = g_strsplit(redirect + 4, ";", 0);
4000 gchar **tmp;
4001 gchar *hostname;
4002 int port = 0;
4003 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
4004 int i = 1;
4006 tmp = g_strsplit(parts[0], ":", 0);
4007 hostname = g_strdup(tmp[0]);
4008 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
4009 g_strfreev(tmp);
4011 while (parts[i]) {
4012 tmp = g_strsplit(parts[i], "=", 0);
4013 if (tmp[1]) {
4014 if (g_strcasecmp("transport", tmp[0]) == 0) {
4015 if (g_strcasecmp("tcp", tmp[1]) == 0) {
4016 transport = SIPE_TRANSPORT_TCP;
4017 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
4018 transport = SIPE_TRANSPORT_UDP;
4022 g_strfreev(tmp);
4023 i++;
4025 g_strfreev(parts);
4027 /* Close old connection */
4028 sipe_connection_cleanup(sip);
4030 /* Create new connection */
4031 sip->transport = transport;
4032 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4033 hostname, port, TRANSPORT_DESCRIPTOR);
4034 create_connection(sip, hostname, port);
4036 g_free(redirect);
4038 break;
4039 case 401:
4040 if (sip->registerstatus != 2) {
4041 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
4042 if (sip->registrar.retries > 3) {
4043 sip->gc->wants_to_die = TRUE;
4044 purple_connection_error(sip->gc, _("Wrong Password"));
4045 return TRUE;
4047 #ifdef USE_KERBEROS
4048 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4049 #endif
4050 tmp = sipmsg_find_auth_header(msg, "NTLM");
4051 #ifdef USE_KERBEROS
4052 } else {
4053 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4055 #endif
4056 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
4057 fill_auth(sip, tmp, &sip->registrar);
4058 sip->registerstatus = 2;
4059 if (sip->account->disconnecting) {
4060 do_register_exp(sip, 0);
4061 } else {
4062 do_register(sip);
4065 break;
4066 case 403:
4068 gchar *warning = sipmsg_find_header(msg, "Warning");
4069 if (warning != NULL) {
4070 /* Example header:
4071 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4073 gchar **tmp = g_strsplit(warning, "\"", 0);
4074 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
4075 g_strfreev(tmp);
4076 } else {
4077 warning = g_strdup(_("You have been rejected by the server"));
4080 sip->gc->wants_to_die = TRUE;
4081 purple_connection_error(sip->gc, warning);
4082 g_free(warning);
4083 return TRUE;
4085 break;
4086 case 404:
4088 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4089 if (warning != NULL) {
4090 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4091 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
4092 g_free(reason);
4093 } else {
4094 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4097 sip->gc->wants_to_die = TRUE;
4098 purple_connection_error(sip->gc, warning);
4099 g_free(warning);
4100 return TRUE;
4102 break;
4103 case 503:
4105 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4106 if (warning != NULL) {
4107 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4108 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4109 g_free(reason);
4110 } else {
4111 warning = g_strdup(_("Service unavailable: no reason given"));
4114 sip->gc->wants_to_die = TRUE;
4115 purple_connection_error(sip->gc, warning);
4116 g_free(warning);
4117 return TRUE;
4119 break;
4121 return TRUE;
4124 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4126 const char *uri;
4127 xmlnode *xn_categories;
4128 xmlnode *xn_category;
4129 xmlnode *xn_node;
4130 const char *activity = NULL;
4132 xn_categories = xmlnode_from_str(data, len);
4133 uri = xmlnode_get_attrib(xn_categories, "uri");
4135 for (xn_category = xmlnode_get_child(xn_categories, "category");
4136 xn_category ;
4137 xn_category = xmlnode_get_next_twin(xn_category) )
4139 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4141 if (!strcmp(attrVar, "note"))
4143 if (uri) {
4144 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4146 if (sbuddy) {
4147 char *note;
4149 xn_node = xmlnode_get_child(xn_category, "note");
4150 if (!xn_node) continue;
4151 xn_node = xmlnode_get_child(xn_node, "body");
4152 if (!xn_node) continue;
4153 note = xmlnode_get_data(xn_node);
4154 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4155 g_free(sbuddy->annotation);
4156 sbuddy->annotation = NULL;
4157 if (note) sbuddy->annotation = g_strdup(note);
4158 g_free(note);
4163 else if(!strcmp(attrVar, "state"))
4165 char *data;
4166 int avail;
4167 xn_node = xmlnode_get_child(xn_category, "state");
4168 if (!xn_node) continue;
4169 xn_node = xmlnode_get_child(xn_node, "availability");
4170 if (!xn_node) continue;
4172 data = xmlnode_get_data(xn_node);
4173 avail = atoi(data);
4174 g_free(data);
4176 if (avail < 3000)
4177 activity = SIPE_STATUS_ID_UNKNOWN;
4178 else if (avail < 4500)
4179 activity = SIPE_STATUS_ID_AVAILABLE;
4180 else if (avail < 6000)
4181 activity = SIPE_STATUS_ID_BRB;
4182 else if (avail < 7500)
4183 activity = SIPE_STATUS_ID_ONPHONE;
4184 else if (avail < 9000)
4185 activity = SIPE_STATUS_ID_BUSY;
4186 else if (avail < 12000)
4187 activity = SIPE_STATUS_ID_DND;
4188 else if (avail < 18000)
4189 activity = SIPE_STATUS_ID_AWAY;
4190 else
4191 activity = SIPE_STATUS_ID_OFFLINE;
4194 if(activity) {
4195 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4196 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4199 xmlnode_free(xn_categories);
4202 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4204 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4205 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4206 payload->host = g_strdup(host);
4207 payload->buddies = server;
4208 sipe_subscribe_presence_batched_routed(sip, payload);
4209 sipe_subscribe_presence_batched_routed_free(payload);
4212 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4214 xmlnode *xn_list;
4215 xmlnode *xn_resource;
4216 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4217 g_free, NULL);
4218 GSList *server;
4219 gchar *host;
4221 xn_list = xmlnode_from_str(data, len);
4223 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4224 xn_resource;
4225 xn_resource = xmlnode_get_next_twin(xn_resource) )
4227 const char *uri, *state;
4228 xmlnode *xn_instance;
4230 xn_instance = xmlnode_get_child(xn_resource, "instance");
4231 if (!xn_instance) continue;
4233 uri = xmlnode_get_attrib(xn_resource, "uri");
4234 state = xmlnode_get_attrib(xn_instance, "state");
4235 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4237 if (strstr(state, "resubscribe")) {
4238 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4239 struct sipe_buddy *sbuddy;
4240 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4241 gchar *user = g_strdup(uri);
4242 host = g_strdup(poolFqdn);
4243 server = g_hash_table_lookup(servers, host);
4244 server = g_slist_append(server, user);
4245 g_hash_table_insert(servers, host, server);
4246 } else {
4247 sipe_subscribe_presence_single(sip, (void *) uri);
4249 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4250 if (sbuddy) {
4251 sbuddy->resubscribed = TRUE;
4256 /* Send out any deferred poolFqdn subscriptions */
4257 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4258 g_hash_table_destroy(servers);
4260 xmlnode_free(xn_list);
4263 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4265 const gchar *uri;
4266 gchar *getbasic;
4267 gchar *activity = NULL;
4268 xmlnode *pidf;
4269 xmlnode *basicstatus = NULL, *tuple, *status;
4270 gboolean isonline = FALSE;
4271 xmlnode *display_name_node;
4273 pidf = xmlnode_from_str(data, len);
4274 if (!pidf) {
4275 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4276 return;
4279 uri = xmlnode_get_attrib(pidf, "entity");
4281 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4283 if ((status = xmlnode_get_child(tuple, "status"))) {
4284 basicstatus = xmlnode_get_child(status, "basic");
4288 if (!basicstatus) {
4289 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4290 xmlnode_free(pidf);
4291 return;
4294 getbasic = xmlnode_get_data(basicstatus);
4295 if (!getbasic) {
4296 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4297 xmlnode_free(pidf);
4298 return;
4301 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4302 if (strstr(getbasic, "open")) {
4303 isonline = TRUE;
4305 g_free(getbasic);
4307 display_name_node = xmlnode_get_child(pidf, "display-name");
4308 // updating display name if alias was just URI
4309 if (display_name_node) {
4310 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4311 GSList *entry = buddies;
4312 PurpleBuddy *p_buddy;
4313 char * display_name = xmlnode_get_data(display_name_node);
4315 while (entry) {
4316 const char *server_alias;
4317 char *alias;
4319 p_buddy = entry->data;
4321 alias = (char *)purple_buddy_get_alias(p_buddy);
4322 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
4323 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4324 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4325 purple_blist_alias_buddy(p_buddy, display_name);
4327 g_free(alias);
4329 server_alias = purple_buddy_get_server_alias(p_buddy);
4330 if (display_name &&
4331 ( (server_alias && strcmp(display_name, server_alias))
4332 || !server_alias || strlen(server_alias) == 0 )
4334 purple_blist_server_alias_buddy(p_buddy, display_name);
4337 entry = entry->next;
4339 g_free(display_name);
4342 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4343 if ((status = xmlnode_get_child(tuple, "status"))) {
4344 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4345 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4346 activity = xmlnode_get_data(basicstatus);
4347 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4353 if (isonline) {
4354 const gchar * status_id = NULL;
4355 if (activity) {
4356 if (strstr(activity, "busy")) {
4357 status_id = SIPE_STATUS_ID_BUSY;
4358 } else if (strstr(activity, "away")) {
4359 status_id = SIPE_STATUS_ID_AWAY;
4363 if (!status_id) {
4364 status_id = SIPE_STATUS_ID_AVAILABLE;
4367 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4368 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4369 } else {
4370 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4373 g_free(activity);
4374 xmlnode_free(pidf);
4377 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4379 const char *availability;
4380 const char *activity;
4381 const char *display_name = NULL;
4382 const char *activity_name = NULL;
4383 const char *name;
4384 char *uri;
4385 int avl;
4386 int act;
4387 struct sipe_buddy *sbuddy;
4389 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4391 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4392 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4393 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4394 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4395 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4396 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4397 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4398 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4400 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4401 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4402 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4403 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4404 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4405 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4407 name = xmlnode_get_attrib(xn_presentity, "uri");
4408 uri = g_strdup_printf("sip:%s", name);
4409 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4410 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4412 // updating display name if alias was just URI
4413 if (xn_display_name) {
4414 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4415 GSList *entry = buddies;
4416 PurpleBuddy *p_buddy;
4417 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4419 while (entry) {
4420 const char *email_str, *server_alias;
4422 p_buddy = entry->data;
4424 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4425 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4426 purple_blist_alias_buddy(p_buddy, display_name);
4429 server_alias = purple_buddy_get_server_alias(p_buddy);
4430 if (display_name &&
4431 ( (server_alias && strcmp(display_name, server_alias))
4432 || !server_alias || strlen(server_alias) == 0 )
4434 purple_blist_server_alias_buddy(p_buddy, display_name);
4437 if (email) {
4438 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4439 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4440 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4444 entry = entry->next;
4448 avl = atoi(availability);
4449 act = atoi(activity);
4451 if(sip->msrtc_event_categories){
4452 if (act == 100 && avl == 0)
4453 activity_name = SIPE_STATUS_ID_OFFLINE;
4454 else if (act == 100 && avl == 300)
4455 activity_name = SIPE_STATUS_ID_AWAY;
4456 else if (act == 300 && avl == 300)
4457 activity_name = SIPE_STATUS_ID_BRB;
4458 else if (act == 400 && avl == 300)
4459 activity_name = SIPE_STATUS_ID_AVAILABLE;
4460 else if (act == 500 && act == 300)
4461 activity_name = SIPE_STATUS_ID_ONPHONE;
4462 else if (act == 600 && avl == 300)
4463 activity_name = SIPE_STATUS_ID_BUSY;
4464 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4465 if(avail){ //Check for LegacyInterop elements
4466 avl = atoi(avail);
4467 if(avl == 18500)
4468 activity_name = SIPE_STATUS_ID_OFFLINE;
4469 else if (avl == 3500)
4470 activity_name = SIPE_STATUS_ID_AVAILABLE;
4471 else if (avl == 15500)
4472 activity_name = SIPE_STATUS_ID_AWAY;
4473 else if (avl == 6500)
4474 activity_name = SIPE_STATUS_ID_BUSY;
4475 else if (avl == 12500)
4476 activity_name = SIPE_STATUS_ID_BRB;
4481 if(activity_name == NULL){
4482 if (act <= 100)
4483 activity_name = SIPE_STATUS_ID_AWAY;
4484 else if (act <= 150)
4485 activity_name = SIPE_STATUS_ID_LUNCH;
4486 else if (act <= 300)
4487 activity_name = SIPE_STATUS_ID_BRB;
4488 else if (act <= 400)
4489 activity_name = SIPE_STATUS_ID_AVAILABLE;
4490 else if (act <= 500)
4491 activity_name = SIPE_STATUS_ID_ONPHONE;
4492 else if (act <= 600)
4493 activity_name = SIPE_STATUS_ID_BUSY;
4494 else
4495 activity_name = SIPE_STATUS_ID_AVAILABLE;
4497 if (avl == 0)
4498 activity_name = SIPE_STATUS_ID_OFFLINE;
4501 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4502 if (sbuddy)
4504 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4505 sbuddy->annotation = NULL;
4506 if (note) { sbuddy->annotation = g_strdup(note); }
4508 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4509 sbuddy->device_name = NULL;
4510 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4513 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4514 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4515 g_free(note);
4516 xmlnode_free(xn_presentity);
4517 g_free(uri);
4520 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4522 char *ctype = sipmsg_find_header(msg, "Content-Type");
4524 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4526 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4527 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4529 const char *content = msg->body;
4530 unsigned length = msg->bodylen;
4531 PurpleMimeDocument *mime = NULL;
4533 if (strstr(ctype, "multipart"))
4535 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4536 const char *content_type;
4537 GList* parts;
4538 mime = purple_mime_document_parse(doc);
4539 parts = purple_mime_document_get_parts(mime);
4540 while(parts) {
4541 content = purple_mime_part_get_data(parts->data);
4542 length = purple_mime_part_get_length(parts->data);
4543 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4544 if(content_type && strstr(content_type,"application/rlmi+xml"))
4546 process_incoming_notify_rlmi_resub(sip, content, length);
4548 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4550 process_incoming_notify_msrtc(sip, content, length);
4552 else
4554 process_incoming_notify_rlmi(sip, content, length);
4556 parts = parts->next;
4558 g_free(doc);
4560 if (mime)
4562 purple_mime_document_free(mime);
4565 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4567 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4569 else if(strstr(ctype, "application/rlmi+xml"))
4571 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4574 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4576 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4578 else
4580 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4584 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4586 char *ctype = sipmsg_find_header(msg, "Content-Type");
4587 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4589 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4591 if (ctype &&
4592 strstr(ctype, "multipart") &&
4593 (strstr(ctype, "application/rlmi+xml") ||
4594 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4595 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4596 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4597 GList *parts = purple_mime_document_get_parts(mime);
4598 GSList *buddies = NULL;
4599 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4601 while (parts) {
4602 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4603 purple_mime_part_get_length(parts->data));
4604 gchar *uri = g_strdup(xmlnode_get_attrib(xml, "uri"));
4606 if (strstr(uri, "sip:") == NULL) {
4607 gchar *tmp = uri;
4608 uri = g_strdup_printf("sip:%s", tmp);
4609 g_free(tmp);
4611 buddies = g_slist_append(buddies, uri);
4612 xmlnode_free(xml);
4614 parts = parts->next;
4616 g_free(doc);
4617 if (mime) purple_mime_document_free(mime);
4619 payload->host = who;
4620 payload->buddies = buddies;
4621 sipe_schedule_action(action_name, timeout,
4622 sipe_subscribe_presence_batched_routed,
4623 sipe_subscribe_presence_batched_routed_free,
4624 sip, payload);
4625 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4627 } else {
4628 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4629 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4631 g_free(action_name);
4635 * Dispatcher for all incoming subscription information
4636 * whether it comes from NOTIFY, BENOTIFY requests or
4637 * piggy-backed to subscription's OK responce.
4639 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4640 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4642 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4644 gchar *event = sipmsg_find_header(msg, "Event");
4645 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4646 int timeout = 0;
4648 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4649 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
4651 if (!request)
4653 const gchar *expires_header;
4654 expires_header = sipmsg_find_header(msg, "Expires");
4655 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4656 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4657 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4660 if (!subscription_state || strstr(subscription_state, "active"))
4662 if (event && !g_ascii_strcasecmp(event, "presence"))
4664 sipe_process_presence(sip, msg);
4666 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4668 sipe_process_roaming_contacts(sip, msg, NULL);
4670 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
4672 sipe_process_roaming_self(sip, msg);
4674 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4676 sipe_process_roaming_acl(sip, msg);
4678 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4680 sipe_process_presence_wpending(sip, msg);
4682 else
4684 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
4688 //The server sends a (BE)NOTIFY with the status 'terminated'
4689 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4690 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4691 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4692 g_free(from);
4695 if (timeout && event) {// For LSC 2005 and OCS 2007
4696 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4697 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4699 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4700 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4701 g_free(action_name);
4703 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4704 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4706 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4707 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4708 g_free(action_name);
4710 else*/
4711 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4712 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4714 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4715 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4716 g_free(action_name);
4718 else if (!g_ascii_strcasecmp(event, "presence") &&
4719 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4721 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4722 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4723 if(sip->batched_support) {
4724 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
4725 if(!g_ascii_strcasecmp(who, my_self)){
4726 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4727 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4728 g_free(who); /* unused */
4730 else {
4731 sipe_process_presence_timeout(sip, msg, who, timeout);
4733 g_free(my_self);
4735 else {
4736 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4737 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4739 g_free(action_name);
4740 /* "who" will be freed by the action we just scheduled */
4744 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4746 sipe_process_registration_notify(sip, msg);
4749 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4750 if (request && !benotify)
4752 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4757 * unused. Needed?
4759 static gchar* gen_xpidf(struct sipe_account_data *sip)
4761 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4762 "<presence>\r\n"
4763 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4764 "<display name=\"sip:%s\"/>\r\n"
4765 "<atom id=\"1234\">\r\n"
4766 "<address uri=\"sip:%s\">\r\n"
4767 "<status status=\"%s\"/>\r\n"
4768 "</address>\r\n"
4769 "</atom>\r\n"
4770 "</presence>\r\n",
4771 sip->username,
4772 sip->username,
4773 sip->username,
4774 sip->status);
4775 return doc;
4780 static gchar* gen_pidf(struct sipe_account_data *sip)
4782 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4783 "<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"
4784 "<tuple id=\"0\">\r\n"
4785 "<status>\r\n"
4786 "<basic>open</basic>\r\n"
4787 "<ep:activities>\r\n"
4788 " <ep:activity>%s</ep:activity>\r\n"
4789 "</ep:activities>"
4790 "</status>\r\n"
4791 "</tuple>\r\n"
4792 "<ci:display-name>%s</ci:display-name>\r\n"
4793 "</presence>",
4794 sip->username,
4795 sip->status,
4796 sip->username);
4797 return doc;
4801 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4803 int availability = 300; // online
4804 int activity = 400; // Available
4805 gchar *name;
4806 gchar *body;
4807 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4808 activity = 100;
4809 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4810 activity = 150;
4811 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4812 activity = 300;
4813 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4814 activity = 400;
4815 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4816 activity = 500;
4817 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4818 activity = 600;
4819 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4820 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4821 availability = 0; // offline
4822 activity = 100;
4823 } else {
4824 activity = 400; // available
4827 name = g_strdup_printf("sip: sip:%s", sip->username);
4828 //@TODO: send user data - state; add hostname in upper case
4829 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4830 send_soap_request_with_cb(sip, body, NULL , NULL);
4831 g_free(name);
4832 g_free(body);
4835 static gboolean
4836 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4838 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4839 if (msg->response == 200) {
4840 sip->status_version = 0;
4841 send_presence_status(sip);
4843 return TRUE;
4846 static gboolean
4847 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4849 if (msg->response == 409) {
4850 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4851 // TODO need to parse the version #'s?
4852 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4853 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4854 gchar *tmp;
4855 gchar *hdr;
4857 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4859 tmp = get_contact(sip);
4860 hdr = g_strdup_printf("Contact: %s\r\n"
4861 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4863 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4865 g_free(tmp);
4866 g_free(hdr);
4867 g_free(uri);
4868 g_free(doc);
4870 return TRUE;
4873 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4875 int code;
4876 gchar *uri;
4877 gchar *doc;
4878 gchar *tmp;
4879 gchar *hdr;
4880 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4881 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4882 code = 12000;
4883 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4884 code = 9000;
4885 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4886 code = 7500;
4887 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4888 code = 6000;
4889 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4890 code = 4500;
4891 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4892 code = 3000;
4893 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4894 code = 0;
4895 } else {
4896 // Offline or invisible
4897 code = 18000;
4900 uri = g_strdup_printf("sip:%s", sip->username);
4901 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4902 sip->status_version, code,
4903 sip->status_version, code,
4904 sip->status_version, note ? note : "",
4905 sip->status_version, note ? note : "",
4906 sip->status_version, note ? note : ""
4908 sip->status_version++;
4910 tmp = get_contact(sip);
4911 hdr = g_strdup_printf("Contact: %s\r\n"
4912 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4914 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4916 g_free(tmp);
4917 g_free(hdr);
4918 g_free(uri);
4919 g_free(doc);
4922 static void send_presence_status(struct sipe_account_data *sip)
4924 PurpleStatus * status = purple_account_get_active_status(sip->account);
4925 const gchar *note;
4926 if (!status) return;
4928 note = purple_status_get_attr_string(status, "message");
4930 if(sip->msrtc_event_categories){
4931 send_presence_category_publish(sip, note);
4932 } else {
4933 send_presence_soap(sip, note);
4937 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4939 gboolean found = FALSE;
4940 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4941 if (msg->response == 0) { /* request */
4942 if (!strcmp(msg->method, "MESSAGE")) {
4943 process_incoming_message(sip, msg);
4944 found = TRUE;
4945 } else if (!strcmp(msg->method, "NOTIFY")) {
4946 purple_debug_info("sipe","send->process_incoming_notify\n");
4947 process_incoming_notify(sip, msg, TRUE, FALSE);
4948 found = TRUE;
4949 } else if (!strcmp(msg->method, "BENOTIFY")) {
4950 purple_debug_info("sipe","send->process_incoming_benotify\n");
4951 process_incoming_notify(sip, msg, TRUE, TRUE);
4952 found = TRUE;
4953 } else if (!strcmp(msg->method, "INVITE")) {
4954 process_incoming_invite(sip, msg);
4955 found = TRUE;
4956 } else if (!strcmp(msg->method, "REFER")) {
4957 process_incoming_refer(sip, msg);
4958 found = TRUE;
4959 } else if (!strcmp(msg->method, "OPTIONS")) {
4960 process_incoming_options(sip, msg);
4961 found = TRUE;
4962 } else if (!strcmp(msg->method, "INFO")) {
4963 process_incoming_info(sip, msg);
4964 found = TRUE;
4965 } else if (!strcmp(msg->method, "ACK")) {
4966 // ACK's don't need any response
4967 found = TRUE;
4968 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4969 // LCS 2005 sends us these - just respond 200 OK
4970 found = TRUE;
4971 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4972 } else if (!strcmp(msg->method, "BYE")) {
4973 process_incoming_bye(sip, msg);
4974 found = TRUE;
4975 } else {
4976 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4978 } else { /* response */
4979 struct transaction *trans = transactions_find(sip, msg);
4980 if (trans) {
4981 if (msg->response == 407) {
4982 gchar *resend, *auth, *ptmp;
4984 if (sip->proxy.retries > 30) return;
4985 sip->proxy.retries++;
4986 /* do proxy authentication */
4988 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4990 fill_auth(sip, ptmp, &sip->proxy);
4991 auth = auth_header(sip, &sip->proxy, trans->msg);
4992 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4993 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4994 g_free(auth);
4995 resend = sipmsg_to_string(trans->msg);
4996 /* resend request */
4997 sendout_pkt(sip->gc, resend);
4998 g_free(resend);
4999 } else {
5000 if (msg->response == 100 || msg->response == 180) {
5001 /* ignore provisional response */
5002 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
5003 } else {
5004 sip->proxy.retries = 0;
5005 if (!strcmp(trans->msg->method, "REGISTER")) {
5006 if (msg->response == 401)
5008 sip->registrar.retries++;
5010 else
5012 sip->registrar.retries = 0;
5014 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
5015 } else {
5016 if (msg->response == 401) {
5017 gchar *resend, *auth, *ptmp;
5019 if (sip->registrar.retries > 4) return;
5020 sip->registrar.retries++;
5022 #ifdef USE_KERBEROS
5023 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5024 #endif
5025 ptmp = sipmsg_find_auth_header(msg, "NTLM");
5026 #ifdef USE_KERBEROS
5027 } else {
5028 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
5030 #endif
5032 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
5034 fill_auth(sip, ptmp, &sip->registrar);
5035 auth = auth_header(sip, &sip->registrar, trans->msg);
5036 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
5037 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
5039 //sipmsg_remove_header_now(trans->msg, "Authorization");
5040 //sipmsg_add_header(trans->msg, "Authorization", auth);
5041 g_free(auth);
5042 resend = sipmsg_to_string(trans->msg);
5043 /* resend request */
5044 sendout_pkt(sip->gc, resend);
5045 g_free(resend);
5049 if (trans->callback) {
5050 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
5051 /* call the callback to process response*/
5052 (trans->callback)(sip, msg, trans);
5054 /* Not sure if this is needed or what needs to be done
5055 but transactions seem to be removed prematurely so
5056 this only removes them if the response is 200 OK */
5057 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
5058 /*Has a bug and it's unneccesary*/
5059 /*transactions_remove(sip, trans);*/
5063 found = TRUE;
5064 } else {
5065 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
5068 if (!found) {
5069 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
5073 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
5075 char *cur;
5076 char *dummy;
5077 struct sipmsg *msg;
5078 int restlen;
5079 cur = conn->inbuf;
5081 /* according to the RFC remove CRLF at the beginning */
5082 while (*cur == '\r' || *cur == '\n') {
5083 cur++;
5085 if (cur != conn->inbuf) {
5086 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
5087 conn->inbufused = strlen(conn->inbuf);
5090 /* Received a full Header? */
5091 sip->processing_input = TRUE;
5092 while (sip->processing_input &&
5093 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
5094 time_t currtime = time(NULL);
5095 cur += 2;
5096 cur[0] = '\0';
5097 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
5098 msg = sipmsg_parse_header(conn->inbuf);
5099 cur[0] = '\r';
5100 cur += 2;
5101 restlen = conn->inbufused - (cur - conn->inbuf);
5102 if (restlen >= msg->bodylen) {
5103 dummy = g_malloc(msg->bodylen + 1);
5104 memcpy(dummy, cur, msg->bodylen);
5105 dummy[msg->bodylen] = '\0';
5106 msg->body = dummy;
5107 cur += msg->bodylen;
5108 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
5109 conn->inbufused = strlen(conn->inbuf);
5110 } else {
5111 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5112 restlen, msg->bodylen, (int)strlen(conn->inbuf));
5113 sipmsg_free(msg);
5114 return;
5117 /*if (msg->body) {
5118 purple_debug_info("sipe", "body:\n%s", msg->body);
5121 // Verify the signature before processing it
5122 if (sip->registrar.gssapi_context) {
5123 struct sipmsg_breakdown msgbd;
5124 gchar *signature_input_str;
5125 gchar *rspauth;
5126 msgbd.msg = msg;
5127 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5128 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5130 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5132 if (rspauth != NULL) {
5133 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5134 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5135 process_input_message(sip, msg);
5136 } else {
5137 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5138 purple_connection_error(sip->gc, _("Invalid message signature received"));
5139 sip->gc->wants_to_die = TRUE;
5141 } else if (msg->response == 401) {
5142 purple_connection_error(sip->gc, _("Wrong Password"));
5143 sip->gc->wants_to_die = TRUE;
5145 g_free(signature_input_str);
5147 g_free(rspauth);
5148 sipmsg_breakdown_free(&msgbd);
5149 } else {
5150 process_input_message(sip, msg);
5153 sipmsg_free(msg);
5157 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
5159 PurpleConnection *gc = data;
5160 struct sipe_account_data *sip = gc->proto_data;
5161 struct sipmsg *msg;
5162 int len;
5163 time_t currtime;
5165 static char buffer[65536];
5166 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5167 buffer[len] = '\0';
5168 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5169 msg = sipmsg_parse_msg(buffer);
5170 if (msg) process_input_message(sip, msg);
5174 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5176 struct sipe_account_data *sip = gc->proto_data;
5177 PurpleSslConnection *gsc = sip->gsc;
5179 purple_debug_error("sipe", "%s",debug);
5180 purple_connection_error(gc, msg);
5182 /* Invalidate this connection. Next send will open a new one */
5183 if (gsc) {
5184 connection_remove(sip, gsc->fd);
5185 purple_ssl_close(gsc);
5187 sip->gsc = NULL;
5188 sip->fd = -1;
5191 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5193 PurpleConnection *gc = data;
5194 struct sipe_account_data *sip;
5195 struct sip_connection *conn;
5196 int readlen, len;
5197 gboolean firstread = TRUE;
5199 /* NOTE: This check *IS* necessary */
5200 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5201 purple_ssl_close(gsc);
5202 return;
5205 sip = gc->proto_data;
5206 conn = connection_find(sip, gsc->fd);
5207 if (conn == NULL) {
5208 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5209 gc->wants_to_die = TRUE;
5210 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5211 return;
5214 /* Read all available data from the SSL connection */
5215 do {
5216 /* Increase input buffer size as needed */
5217 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5218 conn->inbuflen += SIMPLE_BUF_INC;
5219 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5220 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5223 /* Try to read as much as there is space left in the buffer */
5224 readlen = conn->inbuflen - conn->inbufused - 1;
5225 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5227 if (len < 0 && errno == EAGAIN) {
5228 /* Try again later */
5229 return;
5230 } else if (len < 0) {
5231 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5232 return;
5233 } else if (firstread && (len == 0)) {
5234 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5235 return;
5238 conn->inbufused += len;
5239 firstread = FALSE;
5241 /* Equivalence indicates that there is possibly more data to read */
5242 } while (len == readlen);
5244 conn->inbuf[conn->inbufused] = '\0';
5245 process_input(sip, conn);
5249 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
5251 PurpleConnection *gc = data;
5252 struct sipe_account_data *sip = gc->proto_data;
5253 int len;
5254 struct sip_connection *conn = connection_find(sip, source);
5255 if (!conn) {
5256 purple_debug_error("sipe", "Connection not found!\n");
5257 return;
5260 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5261 conn->inbuflen += SIMPLE_BUF_INC;
5262 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5265 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5267 if (len < 0 && errno == EAGAIN)
5268 return;
5269 else if (len <= 0) {
5270 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5271 connection_remove(sip, source);
5272 if (sip->fd == source) sip->fd = -1;
5273 return;
5276 conn->inbufused += len;
5277 conn->inbuf[conn->inbufused] = '\0';
5279 process_input(sip, conn);
5282 /* Callback for new connections on incoming TCP port */
5283 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
5285 PurpleConnection *gc = data;
5286 struct sipe_account_data *sip = gc->proto_data;
5287 struct sip_connection *conn;
5289 int newfd = accept(source, NULL, NULL);
5291 conn = connection_create(sip, newfd);
5293 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5296 static void login_cb(gpointer data, gint source, const gchar *error_message)
5298 PurpleConnection *gc = data;
5299 struct sipe_account_data *sip;
5300 struct sip_connection *conn;
5302 if (!PURPLE_CONNECTION_IS_VALID(gc))
5304 if (source >= 0)
5305 close(source);
5306 return;
5309 if (source < 0) {
5310 purple_connection_error(gc, _("Could not connect"));
5311 return;
5314 sip = gc->proto_data;
5315 sip->fd = source;
5316 sip->last_keepalive = time(NULL);
5318 conn = connection_create(sip, source);
5320 do_register(sip);
5322 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5325 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5327 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5328 if (sip == NULL) return;
5330 do_register(sip);
5333 static guint sipe_ht_hash_nick(const char *nick)
5335 char *lc = g_utf8_strdown(nick, -1);
5336 guint bucket = g_str_hash(lc);
5337 g_free(lc);
5339 return bucket;
5342 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5344 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5347 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5349 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5351 sip->listen_data = NULL;
5353 if (listenfd == -1) {
5354 purple_connection_error(sip->gc, _("Could not create listen socket"));
5355 return;
5358 sip->fd = listenfd;
5360 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5361 sip->listenfd = sip->fd;
5363 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5365 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5366 do_register(sip);
5369 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
5371 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5373 sip->query_data = NULL;
5375 if (!hosts || !hosts->data) {
5376 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5377 return;
5380 hosts = g_slist_remove(hosts, hosts->data);
5381 g_free(sip->serveraddr);
5382 sip->serveraddr = hosts->data;
5383 hosts = g_slist_remove(hosts, hosts->data);
5384 while (hosts) {
5385 hosts = g_slist_remove(hosts, hosts->data);
5386 g_free(hosts->data);
5387 hosts = g_slist_remove(hosts, hosts->data);
5390 /* create socket for incoming connections */
5391 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5392 sipe_udp_host_resolved_listen_cb, sip);
5393 if (sip->listen_data == NULL) {
5394 purple_connection_error(sip->gc, _("Could not create listen socket"));
5395 return;
5399 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
5400 gpointer data)
5402 PurpleConnection *gc = data;
5403 struct sipe_account_data *sip;
5405 /* If the connection is already disconnected, we don't need to do anything else */
5406 if (!PURPLE_CONNECTION_IS_VALID(gc))
5407 return;
5409 sip = gc->proto_data;
5410 sip->fd = -1;
5411 sip->gsc = NULL;
5413 switch(error) {
5414 case PURPLE_SSL_CONNECT_FAILED:
5415 purple_connection_error(gc, _("Connection Failed"));
5416 break;
5417 case PURPLE_SSL_HANDSHAKE_FAILED:
5418 purple_connection_error(gc, _("SSL Handshake Failed"));
5419 break;
5420 case PURPLE_SSL_CERTIFICATE_INVALID:
5421 purple_connection_error(gc, _("SSL Certificate Invalid"));
5422 break;
5426 static void
5427 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5429 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5430 PurpleProxyConnectData *connect_data;
5432 sip->listen_data = NULL;
5434 sip->listenfd = listenfd;
5435 if (sip->listenfd == -1) {
5436 purple_connection_error(sip->gc, _("Could not create listen socket"));
5437 return;
5440 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5441 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5442 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5443 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5444 sipe_newconn_cb, sip->gc);
5445 purple_debug_info("sipe", "connecting to %s port %d\n",
5446 sip->realhostname, sip->realport);
5447 /* open tcp connection to the server */
5448 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5449 sip->realport, login_cb, sip->gc);
5451 if (connect_data == NULL) {
5452 purple_connection_error(sip->gc, _("Couldn't create socket"));
5457 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5459 PurpleAccount *account = sip->account;
5460 PurpleConnection *gc = sip->gc;
5462 if (purple_account_get_bool(account, "useport", FALSE)) {
5463 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
5464 port = purple_account_get_int(account, "port", 0);
5465 } else {
5466 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5469 sip->realhostname = hostname;
5470 sip->realport = port;
5472 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5473 hostname, port);
5475 /* TODO: is there a good default grow size? */
5476 if (sip->transport != SIPE_TRANSPORT_UDP)
5477 sip->txbuf = purple_circ_buffer_new(0);
5479 if (sip->transport == SIPE_TRANSPORT_TLS) {
5480 /* SSL case */
5481 if (!purple_ssl_is_supported()) {
5482 gc->wants_to_die = TRUE;
5483 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5484 return;
5487 purple_debug_info("sipe", "using SSL\n");
5489 sip->gsc = purple_ssl_connect(account, hostname, port,
5490 login_cb_ssl, sipe_ssl_connect_failure, gc);
5491 if (sip->gsc == NULL) {
5492 purple_connection_error(gc, _("Could not create SSL context"));
5493 return;
5495 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5496 /* UDP case */
5497 purple_debug_info("sipe", "using UDP\n");
5499 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5500 if (sip->query_data == NULL) {
5501 purple_connection_error(gc, _("Could not resolve hostname"));
5503 } else {
5504 /* TCP case */
5505 purple_debug_info("sipe", "using TCP\n");
5506 /* create socket for incoming connections */
5507 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5508 sipe_tcp_connect_listen_cb, sip);
5509 if (sip->listen_data == NULL) {
5510 purple_connection_error(gc, _("Could not create listen socket"));
5511 return;
5516 /* Service list for autodection */
5517 static const struct sipe_service_data service_autodetect[] = {
5518 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5519 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5520 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5521 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5522 { NULL, NULL, 0 }
5525 /* Service list for SSL/TLS */
5526 static const struct sipe_service_data service_tls[] = {
5527 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5528 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5529 { NULL, NULL, 0 }
5532 /* Service list for TCP */
5533 static const struct sipe_service_data service_tcp[] = {
5534 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5535 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5536 { NULL, NULL, 0 }
5539 /* Service list for UDP */
5540 static const struct sipe_service_data service_udp[] = {
5541 { "sip", "udp", SIPE_TRANSPORT_UDP },
5542 { NULL, NULL, 0 }
5545 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5546 static void resolve_next_service(struct sipe_account_data *sip,
5547 const struct sipe_service_data *start)
5549 if (start) {
5550 sip->service_data = start;
5551 } else {
5552 sip->service_data++;
5553 if (sip->service_data->service == NULL) {
5554 gchar *hostname;
5555 /* Try connecting to the SIP hostname directly */
5556 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5557 if (sip->auto_transport) {
5558 // If SSL is supported, default to using it; OCS servers aren't configured
5559 // by default to accept TCP
5560 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5561 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5562 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5565 hostname = g_strdup(sip->sipdomain);
5566 create_connection(sip, hostname, 0);
5567 return;
5571 /* Try to resolve next service */
5572 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5573 sip->service_data->transport,
5574 sip->sipdomain,
5575 srvresolved, sip);
5578 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5580 struct sipe_account_data *sip = data;
5582 sip->srv_query_data = NULL;
5584 /* find the host to connect to */
5585 if (results) {
5586 gchar *hostname = g_strdup(resp->hostname);
5587 int port = resp->port;
5588 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5589 hostname, port);
5590 g_free(resp);
5592 sip->transport = sip->service_data->type;
5594 create_connection(sip, hostname, port);
5595 } else {
5596 resolve_next_service(sip, NULL);
5600 static void sipe_login(PurpleAccount *account)
5602 PurpleConnection *gc;
5603 struct sipe_account_data *sip;
5604 gchar **signinname_login, **userserver, **domain_user;
5605 const char *transport;
5607 const char *username = purple_account_get_username(account);
5608 gc = purple_account_get_connection(account);
5610 if (strpbrk(username, "\t\v\r\n") != NULL) {
5611 gc->wants_to_die = TRUE;
5612 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5613 return;
5616 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5617 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5618 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5619 sip->gc = gc;
5620 sip->account = account;
5621 sip->reregister_set = FALSE;
5622 sip->reauthenticate_set = FALSE;
5623 sip->subscribed = FALSE;
5624 sip->subscribed_buddies = FALSE;
5626 signinname_login = g_strsplit(username, ",", 2);
5628 userserver = g_strsplit(signinname_login[0], "@", 2);
5629 purple_connection_set_display_name(gc, userserver[0]);
5630 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
5631 sip->sipdomain = g_strdup(userserver[1]);
5633 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
5634 gc->wants_to_die = TRUE;
5635 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5636 return;
5639 domain_user = g_strsplit(signinname_login[1], "\\", 2);
5640 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
5641 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
5643 sip->password = g_strdup(purple_connection_get_password(gc));
5645 g_strfreev(userserver);
5646 g_strfreev(domain_user);
5647 g_strfreev(signinname_login);
5649 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5651 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5653 /* TODO: Set the status correctly. */
5654 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5656 transport = purple_account_get_string(account, "transport", "auto");
5657 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
5658 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
5659 SIPE_TRANSPORT_UDP;
5661 if (purple_account_get_bool(account, "useproxy", FALSE)) {
5662 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
5663 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
5664 } else if (strcmp(transport, "auto") == 0) {
5665 sip->auto_transport = TRUE;
5666 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5667 } else if (strcmp(transport, "tls") == 0) {
5668 resolve_next_service(sip, service_tls);
5669 } else if (strcmp(transport, "tcp") == 0) {
5670 resolve_next_service(sip, service_tcp);
5671 } else {
5672 resolve_next_service(sip, service_udp);
5676 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5678 connection_free_all(sip);
5680 g_free(sip->epid);
5681 sip->epid = NULL;
5683 if (sip->query_data != NULL)
5684 purple_dnsquery_destroy(sip->query_data);
5685 sip->query_data = NULL;
5687 if (sip->srv_query_data != NULL)
5688 purple_srv_cancel(sip->srv_query_data);
5689 sip->srv_query_data = NULL;
5691 if (sip->listen_data != NULL)
5692 purple_network_listen_cancel(sip->listen_data);
5693 sip->listen_data = NULL;
5695 if (sip->gsc != NULL)
5696 purple_ssl_close(sip->gsc);
5697 sip->gsc = NULL;
5699 sipe_auth_free(&sip->registrar);
5700 sipe_auth_free(&sip->proxy);
5702 if (sip->txbuf)
5703 purple_circ_buffer_destroy(sip->txbuf);
5704 sip->txbuf = NULL;
5706 g_free(sip->realhostname);
5707 sip->realhostname = NULL;
5709 if (sip->listenpa)
5710 purple_input_remove(sip->listenpa);
5711 sip->listenpa = 0;
5712 if (sip->tx_handler)
5713 purple_input_remove(sip->tx_handler);
5714 sip->tx_handler = 0;
5715 if (sip->resendtimeout)
5716 purple_timeout_remove(sip->resendtimeout);
5717 sip->resendtimeout = 0;
5718 if (sip->timeouts) {
5719 GSList *entry = sip->timeouts;
5720 while (entry) {
5721 struct scheduled_action *sched_action = entry->data;
5722 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5723 purple_timeout_remove(sched_action->timeout_handler);
5724 (*sched_action->destroy)(sched_action->payload);
5725 g_free(sched_action->name);
5726 g_free(sched_action);
5727 entry = entry->next;
5730 g_slist_free(sip->timeouts);
5732 if (sip->allow_events) {
5733 GSList *entry = sip->allow_events;
5734 while (entry) {
5735 g_free(entry->data);
5736 entry = entry->next;
5739 g_slist_free(sip->allow_events);
5741 if (sip->containers) {
5742 GSList *entry = sip->containers;
5743 while (entry) {
5744 free_container((struct sipe_container *)entry->data);
5745 entry = entry->next;
5748 g_slist_free(sip->containers);
5750 if (sip->contact)
5751 g_free(sip->contact);
5752 sip->contact = NULL;
5753 if (sip->regcallid)
5754 g_free(sip->regcallid);
5755 sip->regcallid = NULL;
5757 if (sip->serveraddr)
5758 g_free(sip->serveraddr);
5759 sip->serveraddr = NULL;
5761 sip->fd = -1;
5762 sip->processing_input = FALSE;
5766 * A callback for g_hash_table_foreach_remove
5768 static gboolean sipe_buddy_remove(gpointer key, gpointer buddy, gpointer user_data)
5770 sipe_free_buddy((struct sipe_buddy *) buddy);
5772 /* We must return TRUE as the key/value have already been deleted */
5773 return(TRUE);
5776 static void sipe_close(PurpleConnection *gc)
5778 struct sipe_account_data *sip = gc->proto_data;
5780 if (sip) {
5781 /* leave all conversations */
5782 im_session_close_all(sip);
5784 /* unregister */
5785 do_register_exp(sip, 0);
5787 sipe_connection_cleanup(sip);
5788 g_free(sip->sipdomain);
5789 g_free(sip->username);
5790 g_free(sip->password);
5791 g_free(sip->authdomain);
5792 g_free(sip->authuser);
5793 g_free(sip->status);
5795 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
5796 g_hash_table_destroy(sip->buddies);
5798 g_free(gc->proto_data);
5799 gc->proto_data = NULL;
5802 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
5804 PurpleAccount *acct = purple_connection_get_account(gc);
5805 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
5806 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5807 if (conv == NULL)
5808 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5809 purple_conversation_present(conv);
5810 g_free(id);
5813 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5816 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5817 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5820 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5822 PurpleNotifySearchResults *results;
5823 PurpleNotifySearchColumn *column;
5824 xmlnode *searchResults;
5825 xmlnode *mrow;
5826 int match_count = 0;
5827 gboolean more = FALSE;
5828 gchar *secondary;
5830 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5832 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5833 if (!searchResults) {
5834 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5835 return FALSE;
5838 results = purple_notify_searchresults_new();
5840 if (results == NULL) {
5841 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5842 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5844 xmlnode_free(searchResults);
5845 return FALSE;
5848 column = purple_notify_searchresults_column_new(_("User Name"));
5849 purple_notify_searchresults_column_add(results, column);
5851 column = purple_notify_searchresults_column_new(_("Name"));
5852 purple_notify_searchresults_column_add(results, column);
5854 column = purple_notify_searchresults_column_new(_("Company"));
5855 purple_notify_searchresults_column_add(results, column);
5857 column = purple_notify_searchresults_column_new(_("Country"));
5858 purple_notify_searchresults_column_add(results, column);
5860 column = purple_notify_searchresults_column_new(_("Email"));
5861 purple_notify_searchresults_column_add(results, column);
5863 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5864 GList *row = NULL;
5866 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5867 row = g_list_append(row, g_strdup(uri_parts[1]));
5868 g_strfreev(uri_parts);
5870 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5871 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5872 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5873 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5875 purple_notify_searchresults_row_add(results, row);
5876 match_count++;
5879 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5880 char *data = xmlnode_get_data_unescaped(mrow);
5881 more = (g_strcasecmp(data, "true") == 0);
5882 g_free(data);
5885 secondary = g_strdup_printf(
5886 dngettext(GETTEXT_PACKAGE,
5887 "Found %d contact%s:",
5888 "Found %d contacts%s:", match_count),
5889 match_count, more ? _(" (more matched your query)") : "");
5891 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5892 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5893 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5895 g_free(secondary);
5896 xmlnode_free(searchResults);
5897 return TRUE;
5900 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5902 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5903 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5904 unsigned i = 0;
5906 do {
5907 PurpleRequestField *field = entries->data;
5908 const char *id = purple_request_field_get_id(field);
5909 const char *value = purple_request_field_string_get_value(field);
5911 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5913 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5914 } while ((entries = g_list_next(entries)) != NULL);
5915 attrs[i] = NULL;
5917 if (i > 0) {
5918 gchar *query = g_strjoinv(NULL, attrs);
5919 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5920 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5921 send_soap_request_with_cb(gc->proto_data, body,
5922 (TransCallback) process_search_contact_response, NULL);
5923 g_free(body);
5924 g_free(query);
5927 g_strfreev(attrs);
5930 static void sipe_show_find_contact(PurplePluginAction *action)
5932 PurpleConnection *gc = (PurpleConnection *) action->context;
5933 PurpleRequestFields *fields;
5934 PurpleRequestFieldGroup *group;
5935 PurpleRequestField *field;
5937 fields = purple_request_fields_new();
5938 group = purple_request_field_group_new(NULL);
5939 purple_request_fields_add_group(fields, group);
5941 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5942 purple_request_field_group_add_field(group, field);
5943 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5944 purple_request_field_group_add_field(group, field);
5945 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5946 purple_request_field_group_add_field(group, field);
5947 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5948 purple_request_field_group_add_field(group, field);
5950 purple_request_fields(gc,
5951 _("Search"),
5952 _("Search for a Contact"),
5953 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5954 fields,
5955 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5956 _("_Cancel"), NULL,
5957 purple_connection_get_account(gc), NULL, NULL, gc);
5960 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5962 GList *menu = NULL;
5963 PurplePluginAction *act;
5965 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5966 menu = g_list_prepend(menu, act);
5968 menu = g_list_reverse(menu);
5970 return menu;
5973 static void dummy_permit_deny(PurpleConnection *gc)
5977 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5979 return TRUE;
5983 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5985 return TRUE;
5989 static char *sipe_status_text(PurpleBuddy *buddy)
5991 struct sipe_account_data *sip;
5992 struct sipe_buddy *sbuddy;
5993 char *text = NULL;
5995 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5996 if (sip) //happens on pidgin exit
5998 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5999 if (sbuddy && sbuddy->annotation)
6001 text = g_strdup(sbuddy->annotation);
6005 return text;
6008 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
6010 const PurplePresence *presence = purple_buddy_get_presence(buddy);
6011 const PurpleStatus *status = purple_presence_get_active_status(presence);
6012 struct sipe_account_data *sip;
6013 struct sipe_buddy *sbuddy;
6014 char *annotation = NULL;
6016 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
6017 if (sip) //happens on pidgin exit
6019 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
6020 if (sbuddy)
6022 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
6026 //Layout
6027 if (purple_presence_is_online(presence))
6029 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
6032 if (annotation)
6034 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
6035 g_free(annotation);
6040 static GHashTable *
6041 sipe_get_account_text_table(PurpleAccount *account)
6043 GHashTable *table;
6044 table = g_hash_table_new(g_str_hash, g_str_equal);
6045 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
6046 return table;
6049 static PurpleBuddy *
6050 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6052 PurpleBuddy *clone;
6053 const gchar *server_alias, *email;
6054 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6056 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6058 purple_blist_add_buddy(clone, NULL, group, NULL);
6060 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
6061 if (server_alias) {
6062 purple_blist_server_alias_buddy(clone, server_alias);
6065 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6066 if (email) {
6067 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
6070 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6071 //for UI to update;
6072 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6073 return clone;
6076 static void
6077 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6079 PurpleBuddy *buddy, *b;
6080 PurpleConnection *gc;
6081 PurpleGroup * group = purple_find_group(group_name);
6083 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6085 buddy = (PurpleBuddy *)node;
6087 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
6088 gc = purple_account_get_connection(buddy->account);
6090 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6091 if (!b){
6092 b = purple_blist_add_buddy_clone(group, buddy);
6095 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6098 static void
6099 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6101 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6102 gchar *self = g_strdup_printf("sip:%s", sip->username);
6103 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
6104 struct sip_im_session *session;
6106 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6108 session = create_chat_session(sip);
6109 session->roster_manager = g_strdup(self);
6111 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6112 session->chat_name = g_strdup(chat_name);
6113 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6114 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6116 g_free(chat_name);
6117 g_free(self);
6120 static gboolean
6121 sipe_is_election_finished(struct sipe_account_data *sip,
6122 struct sip_im_session *session)
6124 struct sip_dialog *dialog;
6125 GSList *entry;
6126 gboolean res = TRUE;
6128 entry = session->dialogs;
6129 while (entry) {
6130 dialog = entry->data;
6131 if (dialog->election_vote == 0) {
6132 res = FALSE;
6133 break;
6135 entry = entry->next;
6138 if (res) {
6139 session->is_voting_in_progress = FALSE;
6141 return res;
6144 static void
6145 sipe_election_start(struct sipe_account_data *sip,
6146 struct sip_im_session *session)
6148 struct sip_dialog *dialog;
6149 GSList *entry;
6150 int election_timeout;
6152 if (session->is_voting_in_progress) {
6153 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6154 return;
6155 } else {
6156 session->is_voting_in_progress = TRUE;
6158 session->bid = rand();
6160 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6162 /* reset election_vote for each chat participant */
6163 entry = session->dialogs;
6164 while (entry) {
6165 dialog = entry->data;
6166 dialog->election_vote = 0;
6167 entry = entry->next;
6170 entry = session->dialogs;
6171 while (entry) {
6172 dialog = entry->data;
6173 /* send RequestRM to each chat participant*/
6174 sipe_send_election_request_rm(sip, session, dialog->with, session->bid);
6175 entry = entry->next;
6178 election_timeout = 15; /* sec */
6179 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6183 * @param who a URI to whom to invite to chat
6185 static void
6186 sipe_invite_to_chat(struct sipe_account_data *sip,
6187 struct sip_im_session *session,
6188 const char *who)
6190 gchar *self = g_strdup_printf("sip:%s", sip->username);
6192 if (session->roster_manager) {
6193 if (!strcmp(session->roster_manager, self)) {
6194 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6195 } else {
6196 sipe_refer(sip, session, who);
6198 } else {
6199 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6201 session->pending_invite_queue = slist_insert_unique_sorted(
6202 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6204 sipe_election_start(sip, session);
6207 g_free(self);
6210 static void
6211 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6212 struct sip_im_session *session)
6214 gchar *invitee;
6215 GSList *entry = session->pending_invite_queue;
6217 while (entry) {
6218 invitee = entry->data;
6219 sipe_invite_to_chat(sip, session, invitee);
6220 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6221 g_free(invitee);
6225 static void
6226 sipe_election_result(struct sipe_account_data *sip,
6227 void *sess)
6229 struct sip_im_session *session = (struct sip_im_session *)sess;
6230 struct sip_dialog *dialog;
6231 GSList *entry;
6232 gchar * rival;
6233 gboolean has_won = TRUE;
6235 if (session->roster_manager) {
6236 purple_debug_info("sipe",
6237 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6238 return;
6241 session->is_voting_in_progress = FALSE;
6243 entry = session->dialogs;
6244 while (entry) {
6245 dialog = entry->data;
6246 if (dialog->election_vote < 0) {
6247 has_won = FALSE;
6248 rival = dialog->with;
6249 break;
6251 entry = entry->next;
6254 if (has_won) {
6255 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6257 session->roster_manager = g_strdup_printf("sip:%s", sip->username);
6259 entry = session->dialogs;
6260 while (entry) {
6261 dialog = entry->data;
6262 /* send SetRM to each chat participant*/
6263 sipe_send_election_set_rm(sip, session, dialog->with);
6264 entry = entry->next;
6266 } else {
6267 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6269 session->bid = 0;
6271 sipe_process_pending_invite_queue(sip, session);
6274 static void
6275 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6277 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6278 struct sip_im_session *session;
6280 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6281 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6283 session = find_chat_session_by_name(sip, chat_name);
6285 sipe_invite_to_chat(sip, session, buddy->name);
6288 static void
6289 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6291 const gchar *email;
6292 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6294 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6295 if (email)
6297 char *mailto = g_strdup_printf("mailto:%s", email);
6298 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6299 #ifndef _WIN32
6301 pid_t pid;
6302 char *const parmList[] = {mailto, NULL};
6303 if ((pid = fork()) == -1)
6305 purple_debug_info("sipe", "fork() error\n");
6307 else if (pid == 0)
6309 execvp("xdg-email", parmList);
6310 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6313 #else
6315 BOOL ret;
6316 _flushall();
6317 errno = 0;
6318 //@TODO resolve env variable %WINDIR% first
6319 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6320 if (errno)
6322 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6325 #endif
6327 g_free(mailto);
6329 else
6331 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6336 * A menu which appear when right-clicking on buddy in contact list.
6338 static GList *
6339 sipe_buddy_menu(PurpleBuddy *buddy)
6341 PurpleBlistNode *g_node;
6342 PurpleGroup *group, *gr_parent;
6343 PurpleMenuAction *act;
6344 GList *menu = NULL;
6345 GList *menu_groups = NULL;
6346 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6347 struct sip_im_session *session;
6348 GSList *entry;
6349 gchar *self = g_strdup_printf("sip:%s", sip->username);
6351 act = purple_menu_action_new(_("New Chat"),
6352 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6353 NULL, NULL);
6354 menu = g_list_prepend(menu, act);
6356 entry = sip->im_sessions;
6357 while (entry) {
6358 session = entry->data;
6359 if (strcmp(self, buddy->name) && session->chat_name && !get_dialog(session, buddy->name)) {
6360 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_name);
6361 act = purple_menu_action_new(label,
6362 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6363 g_strdup(session->chat_name), NULL);
6364 g_free(label);
6365 menu = g_list_prepend(menu, act);
6367 entry = entry->next;
6370 act = purple_menu_action_new(_("Send Email..."),
6371 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6372 NULL, NULL);
6373 menu = g_list_prepend(menu, act);
6375 gr_parent = purple_buddy_get_group(buddy);
6376 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6377 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6378 continue;
6380 group = (PurpleGroup *)g_node;
6381 if (group == gr_parent)
6382 continue;
6384 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6385 continue;
6387 act = purple_menu_action_new(purple_group_get_name(group),
6388 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6389 group->name, NULL);
6390 menu_groups = g_list_prepend(menu_groups, act);
6392 menu_groups = g_list_reverse(menu_groups);
6394 act = purple_menu_action_new(_("Copy to"),
6395 NULL,
6396 NULL, menu_groups);
6397 menu = g_list_prepend(menu, act);
6398 menu = g_list_reverse(menu);
6400 g_free(self);
6401 return menu;
6404 static GList *
6405 sipe_blist_node_menu(PurpleBlistNode *node)
6407 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6408 return sipe_buddy_menu((PurpleBuddy *) node);
6409 } else {
6410 return NULL;
6414 static gboolean
6415 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6417 gboolean ret = TRUE;
6418 char *username = (char *)trans->payload;
6420 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6421 PurpleBuddy *pbuddy;
6422 struct sipe_buddy *sbuddy;
6423 const char *alias;
6424 char *server_alias = NULL;
6425 char *email = NULL;
6426 const char *device_name = NULL;
6428 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6430 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6431 alias = purple_buddy_get_local_alias(pbuddy);
6433 if (sip)
6435 //will query buddy UA's capabilities and send answer to log
6436 sipe_options_request(sip, username);
6438 sbuddy = g_hash_table_lookup(sip->buddies, username);
6439 if (sbuddy)
6441 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6445 if (msg->response != 200) {
6446 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6447 } else {
6448 xmlnode *searchResults;
6449 xmlnode *mrow;
6451 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6452 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6453 if (!searchResults) {
6454 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6455 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6456 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6457 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6458 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6459 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6460 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6461 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6462 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6463 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6464 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6465 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6466 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6467 if (!email || strcmp("", email)) {
6468 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6469 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6473 xmlnode_free(searchResults);
6476 purple_notify_user_info_add_section_break(info);
6478 if (!server_alias || !strcmp("", server_alias)) {
6479 g_free(server_alias);
6480 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6481 if (server_alias) {
6482 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6486 // same as server alias, do not present
6487 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6488 if (alias)
6490 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6493 if (!email || !strcmp("", email)) {
6494 g_free(email);
6495 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6496 if (email) {
6497 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6501 if (device_name)
6503 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6506 /* show a buddy's user info in a nice dialog box */
6507 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6508 username, /* buddy's username */
6509 info, /* body */
6510 NULL, /* callback called when dialog closed */
6511 NULL); /* userdata for callback */
6513 return ret;
6517 * AD search first, LDAP based
6519 static void sipe_get_info(PurpleConnection *gc, const char *username)
6521 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6522 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6524 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6525 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6526 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6527 g_free(body);
6528 g_free(row);
6531 static PurplePlugin *my_protocol = NULL;
6533 static PurplePluginProtocolInfo prpl_info =
6536 NULL, /* user_splits */
6537 NULL, /* protocol_options */
6538 NO_BUDDY_ICONS, /* icon_spec */
6539 sipe_list_icon, /* list_icon */
6540 NULL, /* list_emblems */
6541 sipe_status_text, /* status_text */
6542 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6543 sipe_status_types, /* away_states */
6544 sipe_blist_node_menu, /* blist_node_menu */
6545 NULL, /* chat_info */
6546 NULL, /* chat_info_defaults */
6547 sipe_login, /* login */
6548 sipe_close, /* close */
6549 sipe_im_send, /* send_im */
6550 NULL, /* set_info */ // TODO maybe
6551 sipe_send_typing, /* send_typing */
6552 sipe_get_info, /* get_info */
6553 sipe_set_status, /* set_status */
6554 NULL, /* set_idle */
6555 NULL, /* change_passwd */
6556 sipe_add_buddy, /* add_buddy */
6557 NULL, /* add_buddies */
6558 sipe_remove_buddy, /* remove_buddy */
6559 NULL, /* remove_buddies */
6560 sipe_add_permit, /* add_permit */
6561 sipe_add_deny, /* add_deny */
6562 sipe_add_deny, /* rem_permit */
6563 sipe_add_permit, /* rem_deny */
6564 dummy_permit_deny, /* set_permit_deny */
6565 NULL, /* join_chat */
6566 NULL, /* reject_chat */
6567 NULL, /* get_chat_name */
6568 NULL, /* chat_invite */
6569 sipe_chat_leave, /* chat_leave */
6570 NULL, /* chat_whisper */
6571 sipe_chat_send, /* chat_send */
6572 sipe_keep_alive, /* keepalive */
6573 NULL, /* register_user */
6574 NULL, /* get_cb_info */ // deprecated
6575 NULL, /* get_cb_away */ // deprecated
6576 sipe_alias_buddy, /* alias_buddy */
6577 sipe_group_buddy, /* group_buddy */
6578 sipe_rename_group, /* rename_group */
6579 NULL, /* buddy_free */
6580 sipe_convo_closed, /* convo_closed */
6581 purple_normalize_nocase, /* normalize */
6582 NULL, /* set_buddy_icon */
6583 sipe_remove_group, /* remove_group */
6584 NULL, /* get_cb_real_name */ // TODO?
6585 NULL, /* set_chat_topic */
6586 NULL, /* find_blist_chat */
6587 NULL, /* roomlist_get_list */
6588 NULL, /* roomlist_cancel */
6589 NULL, /* roomlist_expand_category */
6590 NULL, /* can_receive_file */
6591 NULL, /* send_file */
6592 NULL, /* new_xfer */
6593 NULL, /* offline_message */
6594 NULL, /* whiteboard_prpl_ops */
6595 sipe_send_raw, /* send_raw */
6596 NULL, /* roomlist_room_serialize */
6597 NULL, /* unregister_user */
6598 NULL, /* send_attention */
6599 NULL, /* get_attention_types */
6601 sizeof(PurplePluginProtocolInfo), /* struct_size */
6602 sipe_get_account_text_table, /* get_account_text_table */
6606 static PurplePluginInfo info = {
6607 PURPLE_PLUGIN_MAGIC,
6608 PURPLE_MAJOR_VERSION,
6609 PURPLE_MINOR_VERSION,
6610 PURPLE_PLUGIN_PROTOCOL, /**< type */
6611 NULL, /**< ui_requirement */
6612 0, /**< flags */
6613 NULL, /**< dependencies */
6614 PURPLE_PRIORITY_DEFAULT, /**< priority */
6615 "prpl-sipe", /**< id */
6616 "Microsoft LCS/OCS", /**< name */
6617 VERSION, /**< version */
6618 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6619 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6620 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6621 "Gabriel Burt <gburt@novell.com>", /**< author */
6622 PURPLE_WEBSITE, /**< homepage */
6623 sipe_plugin_load, /**< load */
6624 sipe_plugin_unload, /**< unload */
6625 sipe_plugin_destroy, /**< destroy */
6626 NULL, /**< ui_info */
6627 &prpl_info, /**< extra_info */
6628 NULL,
6629 sipe_actions,
6630 NULL,
6631 NULL,
6632 NULL,
6633 NULL
6636 static void sipe_plugin_destroy(PurplePlugin *plugin)
6638 GList *entry;
6640 entry = prpl_info.protocol_options;
6641 while (entry) {
6642 purple_account_option_destroy(entry->data);
6643 entry = g_list_delete_link(entry, entry);
6645 prpl_info.protocol_options = NULL;
6647 entry = prpl_info.user_splits;
6648 while (entry) {
6649 purple_account_user_split_destroy(entry->data);
6650 entry = g_list_delete_link(entry, entry);
6652 prpl_info.user_splits = NULL;
6655 static void init_plugin(PurplePlugin *plugin)
6657 PurpleAccountUserSplit *split;
6658 PurpleAccountOption *option;
6660 srand(time(NULL));
6662 #ifdef ENABLE_NLS
6663 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6664 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6665 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6666 textdomain(GETTEXT_PACKAGE);
6667 #endif
6669 purple_plugin_register(plugin);
6671 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
6672 purple_account_user_split_set_reverse(split, FALSE);
6673 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6675 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
6676 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6677 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6678 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6680 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
6681 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6682 // Translators: noun (networking port)
6683 option = purple_account_option_int_new(_("Port"), "port", 5061);
6684 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6686 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6687 purple_account_option_add_list_item(option, _("Auto"), "auto");
6688 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6689 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6690 purple_account_option_add_list_item(option, _("UDP"), "udp");
6691 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6693 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6694 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6696 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6697 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6699 #ifdef USE_KERBEROS
6700 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6701 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6703 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6704 * No login/password is taken into account if this option present,
6705 * instead used default credentials stored in OS.
6707 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6708 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6709 #endif
6710 my_protocol = plugin;
6713 /* I had to redefined the function for it load, but works */
6714 gboolean purple_init_plugin(PurplePlugin *plugin){
6715 plugin->info = &(info);
6716 init_plugin((plugin));
6717 sipe_plugin_load((plugin));
6718 return purple_plugin_register(plugin);
6722 Local Variables:
6723 mode: c
6724 c-file-style: "bsd"
6725 indent-tabs-mode: t
6726 tab-width: 8
6727 End: