Fix compilation error in last commit
[siplcs.git] / src / sipe.c
blob545845dc0918bf11e823f588f07ce68338f8e08a
1 /**
2 * @file sipe.c
4 * pidgin-sipe
6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2009 pier11 <pier11@kinozal.tv>
8 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
12 * ***
13 * Thanks to Google's Summer of Code Program and the helpful mentors
14 * ***
16 * Session-based SIP MESSAGE documentation:
17 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
19 * This program is free software; you can redistribute it and/or modify
20 * it under the terms of the GNU General Public License as published by
21 * the Free Software Foundation; either version 2 of the License, or
22 * (at your option) any later version.
24 * This program is distributed in the hope that it will be useful,
25 * but WITHOUT ANY WARRANTY; without even the implied warranty of
26 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
27 * GNU General Public License for more details.
29 * You should have received a copy of the GNU General Public License
30 * along with this program; if not, write to the Free Software
31 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34 #ifndef _WIN32
35 #include <sys/socket.h>
36 #include <sys/ioctl.h>
37 #include <sys/types.h>
38 #include <netinet/in.h>
39 #include <net/if.h>
40 #else
41 #ifdef _DLL
42 #define _WS2TCPIP_H_
43 #define _WINSOCK2API_
44 #define _LIBC_INTERNAL_
45 #endif /* _DLL */
46 #include "internal.h"
47 #endif /* _WIN32 */
49 #include <time.h>
50 #include <stdio.h>
51 #include <errno.h>
52 #include <string.h>
53 #include <glib.h>
56 #include "accountopt.h"
57 #include "blist.h"
58 #include "conversation.h"
59 #include "dnsquery.h"
60 #include "debug.h"
61 #include "notify.h"
62 #include "privacy.h"
63 #include "prpl.h"
64 #include "plugin.h"
65 #include "util.h"
66 #include "version.h"
67 #include "network.h"
68 #include "xmlnode.h"
69 #include "mime.h"
71 #include "sipe.h"
72 #include "sipe-chat.h"
73 #include "sipe-conf.h"
74 #include "sipe-dialog.h"
75 #include "sipe-nls.h"
76 #include "sipe-session.h"
77 #include "sipe-utils.h"
78 #include "sipmsg.h"
79 #include "sipe-sign.h"
80 #include "dnssrv.h"
81 #include "request.h"
83 /* Keep in sync with sipe_transport_type! */
84 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
85 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
87 /* Status identifiers (see also: sipe_status_types()) */
88 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
89 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
90 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
91 /* PURPLE_STATUS_UNAVAILABLE: */
92 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
93 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
94 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
95 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
96 /* PURPLE_STATUS_AWAY: */
97 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
98 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
99 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
100 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
101 /* ??? PURPLE_STATUS_MOBILE */
102 /* ??? PURPLE_STATUS_TUNE */
104 /* Action name templates */
105 #define ACTION_NAME_PRESENCE "<presence><%s>"
107 /* Our publication type keys. OCS 2007+
108 * Format: SIPE_PUB_{Category}[_{SubSategory}]
110 #define SIPE_PUB_DEVICE "000"
111 #define SIPE_PUB_STATE_MACHINE "100"
112 #define SIPE_PUB_NOTE "200"
114 static char *genbranch()
116 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
117 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
118 rand() & 0xFFFF, rand() & 0xFFFF);
121 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
122 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
124 return "sipe";
127 static void sipe_plugin_destroy(PurplePlugin *plugin);
129 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
131 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
132 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
133 gpointer data);
135 static void sipe_close(PurpleConnection *gc);
137 static void send_presence_status(struct sipe_account_data *sip);
139 static void sendout_pkt(PurpleConnection *gc, const char *buf);
141 static void sipe_keep_alive(PurpleConnection *gc)
143 struct sipe_account_data *sip = gc->proto_data;
144 if (sip->transport == SIPE_TRANSPORT_UDP) {
145 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
146 gchar buf[2] = {0, 0};
147 purple_debug_info("sipe", "sending keep alive\n");
148 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
149 } else {
150 time_t now = time(NULL);
151 if ((sip->keepalive_timeout > 0) &&
152 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
153 #if PURPLE_VERSION_CHECK(2,4,0)
154 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
155 #endif
157 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
158 sendout_pkt(gc, "\r\n\r\n");
159 sip->last_keepalive = now;
164 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
166 struct sip_connection *ret = NULL;
167 GSList *entry = sip->openconns;
168 while (entry) {
169 ret = entry->data;
170 if (ret->fd == fd) return ret;
171 entry = entry->next;
173 return NULL;
176 static void sipe_auth_free(struct sip_auth *auth)
178 g_free(auth->opaque);
179 auth->opaque = NULL;
180 g_free(auth->realm);
181 auth->realm = NULL;
182 g_free(auth->target);
183 auth->target = NULL;
184 auth->type = AUTH_TYPE_UNSET;
185 auth->retries = 0;
186 auth->expires = 0;
187 g_free(auth->gssapi_data);
188 auth->gssapi_data = NULL;
189 sip_sec_destroy_context(auth->gssapi_context);
190 auth->gssapi_context = NULL;
193 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
195 struct sip_connection *ret = g_new0(struct sip_connection, 1);
196 ret->fd = fd;
197 sip->openconns = g_slist_append(sip->openconns, ret);
198 return ret;
201 static void connection_remove(struct sipe_account_data *sip, int fd)
203 struct sip_connection *conn = connection_find(sip, fd);
204 if (conn) {
205 sip->openconns = g_slist_remove(sip->openconns, conn);
206 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
207 g_free(conn->inbuf);
208 g_free(conn);
212 static void connection_free_all(struct sipe_account_data *sip)
214 struct sip_connection *ret = NULL;
215 GSList *entry = sip->openconns;
216 while (entry) {
217 ret = entry->data;
218 connection_remove(sip, ret->fd);
219 entry = sip->openconns;
223 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
225 gchar noncecount[9];
226 const char *authuser = sip->authuser;
227 gchar *response;
228 gchar *ret;
230 if (!authuser || strlen(authuser) < 1) {
231 authuser = sip->username;
234 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
235 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
237 // If we have a signature for the message, include that
238 if (msg->signature) {
239 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);
242 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
243 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
244 gchar *gssapi_data;
245 gchar *opaque;
247 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
248 &(auth->expires),
249 auth->type,
250 purple_account_get_bool(sip->account, "sso", TRUE),
251 sip->authdomain ? sip->authdomain : "",
252 authuser,
253 sip->password,
254 auth->target,
255 auth->gssapi_data);
256 if (!gssapi_data || !auth->gssapi_context) {
257 sip->gc->wants_to_die = TRUE;
258 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
259 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(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,
382 SIPE_UNUSED_PARAMETER gint source,
383 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
385 PurpleConnection *gc = data;
386 struct sipe_account_data *sip = gc->proto_data;
387 gsize max_write;
388 gssize written;
390 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
392 if (max_write == 0) {
393 if (sip->tx_handler != 0){
394 purple_input_remove(sip->tx_handler);
395 sip->tx_handler = 0;
397 return;
400 written = write(sip->fd, sip->txbuf->outptr, max_write);
402 if (written < 0 && errno == EAGAIN)
403 written = 0;
404 else if (written <= 0) {
405 /*TODO: do we really want to disconnect on a failure to write?*/
406 purple_connection_error(gc, _("Could not write"));
407 return;
410 purple_circ_buffer_mark_read(sip->txbuf, written);
413 static void sipe_canwrite_cb_ssl(gpointer data,
414 SIPE_UNUSED_PARAMETER gint src,
415 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
417 PurpleConnection *gc = data;
418 struct sipe_account_data *sip = gc->proto_data;
419 gsize max_write;
420 gssize written;
422 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
424 if (max_write == 0) {
425 if (sip->tx_handler != 0) {
426 purple_input_remove(sip->tx_handler);
427 sip->tx_handler = 0;
428 return;
432 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
434 if (written < 0 && errno == EAGAIN)
435 written = 0;
436 else if (written <= 0) {
437 /*TODO: do we really want to disconnect on a failure to write?*/
438 purple_connection_error(gc, _("Could not write"));
439 return;
442 purple_circ_buffer_mark_read(sip->txbuf, written);
445 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
447 static void send_later_cb(gpointer data, gint source,
448 SIPE_UNUSED_PARAMETER const gchar *error)
450 PurpleConnection *gc = data;
451 struct sipe_account_data *sip;
452 struct sip_connection *conn;
454 if (!PURPLE_CONNECTION_IS_VALID(gc))
456 if (source >= 0)
457 close(source);
458 return;
461 if (source < 0) {
462 purple_connection_error(gc, _("Could not connect"));
463 return;
466 sip = gc->proto_data;
467 sip->fd = source;
468 sip->connecting = FALSE;
469 sip->last_keepalive = time(NULL);
471 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
473 /* If there is more to write now, we need to register a handler */
474 if (sip->txbuf->bufused > 0)
475 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
477 conn = connection_create(sip, source);
478 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
481 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
483 struct sipe_account_data *sip;
484 struct sip_connection *conn;
486 if (!PURPLE_CONNECTION_IS_VALID(gc))
488 if (gsc) purple_ssl_close(gsc);
489 return NULL;
492 sip = gc->proto_data;
493 sip->fd = gsc->fd;
494 sip->gsc = gsc;
495 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
496 sip->connecting = FALSE;
497 sip->last_keepalive = time(NULL);
499 conn = connection_create(sip, gsc->fd);
501 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
503 return sip;
506 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
507 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
509 PurpleConnection *gc = data;
510 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
511 if (sip == NULL) return;
513 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
515 /* If there is more to write now */
516 if (sip->txbuf->bufused > 0) {
517 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
522 static void sendlater(PurpleConnection *gc, const char *buf)
524 struct sipe_account_data *sip = gc->proto_data;
526 if (!sip->connecting) {
527 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
528 if (sip->transport == SIPE_TRANSPORT_TLS){
529 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
530 } else {
531 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
532 purple_connection_error(gc, _("Couldn't create socket"));
535 sip->connecting = TRUE;
538 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
539 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
541 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
544 static void sendout_pkt(PurpleConnection *gc, const char *buf)
546 struct sipe_account_data *sip = gc->proto_data;
547 time_t currtime = time(NULL);
548 int writelen = strlen(buf);
550 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
551 if (sip->transport == SIPE_TRANSPORT_UDP) {
552 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
553 purple_debug_info("sipe", "could not send packet\n");
555 } else {
556 int ret;
557 if (sip->fd < 0) {
558 sendlater(gc, buf);
559 return;
562 if (sip->tx_handler) {
563 ret = -1;
564 errno = EAGAIN;
565 } else{
566 if (sip->gsc){
567 ret = purple_ssl_write(sip->gsc, buf, writelen);
568 }else{
569 ret = write(sip->fd, buf, writelen);
573 if (ret < 0 && errno == EAGAIN)
574 ret = 0;
575 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
576 sendlater(gc, buf);
577 return;
580 if (ret < writelen) {
581 if (!sip->tx_handler){
582 if (sip->gsc){
583 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
585 else{
586 sip->tx_handler = purple_input_add(sip->fd,
587 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
588 gc);
592 /* XXX: is it OK to do this? You might get part of a request sent
593 with part of another. */
594 if (sip->txbuf->bufused > 0)
595 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
597 purple_circ_buffer_append(sip->txbuf, buf + ret,
598 writelen - ret);
603 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
605 sendout_pkt(gc, buf);
606 return len;
609 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
611 GSList *tmp = msg->headers;
612 gchar *name;
613 gchar *value;
614 GString *outstr = g_string_new("");
615 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
616 while (tmp) {
617 name = ((struct siphdrelement*) (tmp->data))->name;
618 value = ((struct siphdrelement*) (tmp->data))->value;
619 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
620 tmp = g_slist_next(tmp);
622 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
623 sendout_pkt(sip->gc, outstr->str);
624 g_string_free(outstr, TRUE);
627 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
629 gchar * buf;
631 if (sip->registrar.type == AUTH_TYPE_UNSET) {
632 return;
635 if (sip->registrar.gssapi_context) {
636 struct sipmsg_breakdown msgbd;
637 gchar *signature_input_str;
638 msgbd.msg = msg;
639 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
640 msgbd.rand = g_strdup_printf("%08x", g_random_int());
641 sip->registrar.ntlm_num++;
642 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
643 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
644 if (signature_input_str != NULL) {
645 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
646 msg->signature = signature_hex;
647 msg->rand = g_strdup(msgbd.rand);
648 msg->num = g_strdup(msgbd.num);
649 g_free(signature_input_str);
651 sipmsg_breakdown_free(&msgbd);
654 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
655 buf = auth_header(sip, &sip->registrar, msg);
656 if (buf) {
657 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
659 g_free(buf);
660 } 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")) {
661 sip->registrar.nc = 3;
662 #ifdef USE_KERBEROS
663 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
664 #endif
665 sip->registrar.type = AUTH_TYPE_NTLM;
666 #ifdef USE_KERBEROS
667 } else {
668 sip->registrar.type = AUTH_TYPE_KERBEROS;
670 #endif
673 buf = auth_header(sip, &sip->registrar, msg);
674 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
675 g_free(buf);
676 } else {
677 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
682 * unused. Needed?
683 static char *get_contact_service(struct sipe_account_data *sip)
685 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()));
686 //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);
690 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
691 const char *text, const char *body)
693 gchar *name;
694 gchar *value;
695 GString *outstr = g_string_new("");
696 struct sipe_account_data *sip = gc->proto_data;
697 gchar *contact;
698 GSList *tmp;
699 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
701 contact = get_contact(sip);
702 sipmsg_add_header(msg, "Contact", contact);
703 g_free(contact);
705 if (body) {
706 gchar len[12];
707 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
708 sipmsg_add_header(msg, "Content-Length", len);
709 } else {
710 sipmsg_add_header(msg, "Content-Length", "0");
713 msg->response = code;
715 sipmsg_strip_headers(msg, keepers);
716 sipmsg_merge_new_headers(msg);
717 sign_outgoing_message(msg, sip, msg->method);
719 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
720 tmp = msg->headers;
721 while (tmp) {
722 name = ((struct siphdrelement*) (tmp->data))->name;
723 value = ((struct siphdrelement*) (tmp->data))->value;
725 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
726 tmp = g_slist_next(tmp);
728 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
729 sendout_pkt(gc, outstr->str);
730 g_string_free(outstr, TRUE);
733 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
735 sip->transactions = g_slist_remove(sip->transactions, trans);
736 if (trans->msg) sipmsg_free(trans->msg);
737 g_free(trans->key);
738 g_free(trans);
741 static struct transaction *
742 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
744 gchar *call_id = NULL;
745 gchar *cseq = NULL;
746 struct transaction *trans = g_new0(struct transaction, 1);
748 trans->time = time(NULL);
749 trans->msg = (struct sipmsg *)msg;
750 call_id = sipmsg_find_header(trans->msg, "Call-ID");
751 cseq = sipmsg_find_header(trans->msg, "CSeq");
752 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
753 trans->callback = callback;
754 sip->transactions = g_slist_append(sip->transactions, trans);
755 return trans;
758 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
760 struct transaction *trans;
761 GSList *transactions = sip->transactions;
762 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
763 gchar *cseq = sipmsg_find_header(msg, "CSeq");
764 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
766 while (transactions) {
767 trans = transactions->data;
768 if (!g_strcasecmp(trans->key, key)) {
769 g_free(key);
770 return trans;
772 transactions = transactions->next;
775 g_free(key);
776 return NULL;
779 struct transaction *
780 send_sip_request(PurpleConnection *gc, const gchar *method,
781 const gchar *url, const gchar *to, const gchar *addheaders,
782 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
784 struct sipe_account_data *sip = gc->proto_data;
785 const char *addh = "";
786 char *buf;
787 struct sipmsg *msg;
788 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
789 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
790 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
791 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
792 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
793 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
794 gchar *route = strdup("");
795 gchar *epid = get_epid(sip); // TODO generate one per account/login
796 int cseq = dialog ? ++dialog->cseq :
797 /* This breaks OCS2007: own presence, contact search, ?
798 1 .* as Call-Id is new in this case */
799 ++sip->cseq;
800 struct transaction *trans;
802 if (dialog && dialog->routes)
804 GSList *iter = dialog->routes;
806 while(iter)
808 char *tmp = route;
809 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
810 g_free(tmp);
811 iter = g_slist_next(iter);
815 if (!ourtag && !dialog) {
816 ourtag = gentag();
819 if (!strcmp(method, "REGISTER")) {
820 if (sip->regcallid) {
821 g_free(callid);
822 callid = g_strdup(sip->regcallid);
823 } else {
824 sip->regcallid = g_strdup(callid);
828 if (addheaders) addh = addheaders;
830 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
831 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
832 "From: <sip:%s>%s%s;epid=%s\r\n"
833 "To: <%s>%s%s%s%s\r\n"
834 "Max-Forwards: 70\r\n"
835 "CSeq: %d %s\r\n"
836 "User-Agent: %s\r\n"
837 "Call-ID: %s\r\n"
838 "%s%s"
839 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
840 method,
841 dialog && dialog->request ? dialog->request : url,
842 TRANSPORT_DESCRIPTOR,
843 purple_network_get_my_ip(-1),
844 sip->listenport,
845 branch ? ";branch=" : "",
846 branch ? branch : "",
847 sip->username,
848 ourtag ? ";tag=" : "",
849 ourtag ? ourtag : "",
850 epid,
852 theirtag ? ";tag=" : "",
853 theirtag ? theirtag : "",
854 theirepid ? ";epid=" : "",
855 theirepid ? theirepid : "",
856 cseq,
857 method,
858 useragent,
859 callid,
860 route,
861 addh,
862 body ? (gsize) strlen(body) : 0,
863 body ? body : "");
866 //printf ("parsing msg buf:\n%s\n\n", buf);
867 msg = sipmsg_parse_msg(buf);
869 g_free(buf);
870 g_free(ourtag);
871 g_free(theirtag);
872 g_free(theirepid);
873 g_free(branch);
874 g_free(callid);
875 g_free(route);
876 g_free(epid);
878 sign_outgoing_message (msg, sip, method);
880 buf = sipmsg_to_string (msg);
882 /* add to ongoing transactions */
883 trans = transactions_add_buf(sip, msg, tc);
884 sendout_pkt(gc, buf);
885 g_free(buf);
887 return trans;
890 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
892 gchar *from = sip_uri_self(sip);
893 gchar *contact = get_contact(sip);
894 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
895 "Content-Type: application/SOAP+xml\r\n",contact);
897 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
898 tr->payload = payload;
900 g_free(from);
901 g_free(contact);
902 g_free(hdr);
905 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
907 send_soap_request_with_cb(sip, body, NULL, NULL);
910 static char *get_contact_register(struct sipe_account_data *sip)
912 char *epid = get_epid(sip);
913 char *uuid = generateUUIDfromEPID(epid);
914 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);
915 g_free(uuid);
916 g_free(epid);
917 return(buf);
920 static void do_register_exp(struct sipe_account_data *sip, int expire)
922 char *uri;
923 char *expires;
924 char *to;
925 char *contact;
926 char *hdr;
928 if (!sip->sipdomain) return;
930 uri = sip_uri_from_name(sip->sipdomain);
931 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
932 to = sip_uri_self(sip);
933 contact = get_contact_register(sip);
934 hdr = g_strdup_printf("Contact: %s\r\n"
935 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
936 "Event: registration\r\n"
937 "Allow-Events: presence\r\n"
938 "ms-keep-alive: UAC;hop-hop=yes\r\n"
939 "%s", contact, expires);
940 g_free(contact);
941 g_free(expires);
943 sip->registerstatus = 1;
945 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
946 process_register_response);
948 g_free(hdr);
949 g_free(uri);
950 g_free(to);
953 static void do_register_cb(struct sipe_account_data *sip,
954 SIPE_UNUSED_PARAMETER void *unused)
956 do_register_exp(sip, -1);
957 sip->reregister_set = FALSE;
960 static void do_register(struct sipe_account_data *sip)
962 do_register_exp(sip, -1);
965 static void
966 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
968 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
969 send_soap_request(sip, body);
970 g_free(body);
973 static void
974 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
976 if (allow) {
977 purple_debug_info("sipe", "Authorizing contact %s\n", who);
978 } else {
979 purple_debug_info("sipe", "Blocking contact %s\n", who);
982 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
985 static
986 void sipe_auth_user_cb(void * data)
988 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
989 if (!job) return;
991 sipe_contact_allow_deny (job->sip, job->who, TRUE);
992 g_free(job);
995 static
996 void sipe_deny_user_cb(void * data)
998 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
999 if (!job) return;
1001 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1002 g_free(job);
1005 static void
1006 sipe_add_permit(PurpleConnection *gc, const char *name)
1008 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1009 sipe_contact_allow_deny(sip, name, TRUE);
1012 static void
1013 sipe_add_deny(PurpleConnection *gc, const char *name)
1015 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1016 sipe_contact_allow_deny(sip, name, FALSE);
1019 /*static void
1020 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1022 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1023 sipe_contact_set_acl(sip, name, "");
1026 static void
1027 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1029 xmlnode *watchers;
1030 xmlnode *watcher;
1031 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1032 if (msg->response != 0 && msg->response != 200) return;
1034 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1036 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1037 if (!watchers) return;
1039 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1040 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1041 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1042 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1044 // TODO pull out optional displayName to pass as alias
1045 if (remote_user) {
1046 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1047 job->who = remote_user;
1048 job->sip = sip;
1049 purple_account_request_authorization(
1050 sip->account,
1051 remote_user,
1052 _("you"), /* id */
1053 alias,
1054 NULL, /* message */
1055 on_list,
1056 sipe_auth_user_cb,
1057 sipe_deny_user_cb,
1058 (void *) job);
1063 xmlnode_free(watchers);
1064 return;
1067 static void
1068 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1070 PurpleGroup * purple_group = purple_find_group(group->name);
1071 if (!purple_group) {
1072 purple_group = purple_group_new(group->name);
1073 purple_blist_add_group(purple_group, NULL);
1076 if (purple_group) {
1077 group->purple_group = purple_group;
1078 sip->groups = g_slist_append(sip->groups, group);
1079 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1080 } else {
1081 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1085 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1087 struct sipe_group *group;
1088 GSList *entry;
1089 if (sip == NULL) {
1090 return NULL;
1093 entry = sip->groups;
1094 while (entry) {
1095 group = entry->data;
1096 if (group->id == id) {
1097 return group;
1099 entry = entry->next;
1101 return NULL;
1104 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1106 struct sipe_group *group;
1107 GSList *entry;
1108 if (sip == NULL) {
1109 return NULL;
1112 entry = sip->groups;
1113 while (entry) {
1114 group = entry->data;
1115 if (!strcmp(group->name, name)) {
1116 return group;
1118 entry = entry->next;
1120 return NULL;
1123 static void
1124 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1126 gchar *body;
1127 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1128 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1129 send_soap_request(sip, body);
1130 g_free(body);
1131 g_free(group->name);
1132 group->name = g_strdup(name);
1136 * Only appends if no such value already stored.
1137 * Like Set in Java.
1139 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1140 GSList * res = list;
1141 if (!g_slist_find_custom(list, data, func)) {
1142 res = g_slist_insert_sorted(list, data, func);
1144 return res;
1147 static int
1148 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1149 return group1->id - group2->id;
1153 * Returns string like "2 4 7 8" - group ids buddy belong to.
1155 static gchar *
1156 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1157 int i = 0;
1158 gchar *res;
1159 //creating array from GList, converting int to gchar*
1160 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1161 GSList *entry = buddy->groups;
1162 while (entry) {
1163 struct sipe_group * group = entry->data;
1164 ids_arr[i] = g_strdup_printf("%d", group->id);
1165 entry = entry->next;
1166 i++;
1168 ids_arr[i] = NULL;
1169 res = g_strjoinv(" ", ids_arr);
1170 g_strfreev(ids_arr);
1171 return res;
1175 * Sends buddy update to server
1177 static void
1178 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1180 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1181 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1183 if (buddy && purple_buddy) {
1184 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1185 gchar *body;
1186 gchar *groups = sipe_get_buddy_groups_string(buddy);
1187 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1189 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1190 alias, groups, "true", buddy->name, sip->contacts_delta++
1192 send_soap_request(sip, body);
1193 g_free(groups);
1194 g_free(body);
1198 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1200 if (msg->response == 200) {
1201 struct sipe_group *group;
1202 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1203 xmlnode *xml;
1204 xmlnode *node;
1205 char *group_id;
1206 struct sipe_buddy *buddy;
1208 xml = xmlnode_from_str(msg->body, msg->bodylen);
1209 if (!xml) {
1210 g_free(ctx);
1211 return FALSE;
1214 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1215 if (!node) {
1216 g_free(ctx);
1217 xmlnode_free(xml);
1218 return FALSE;
1221 group_id = xmlnode_get_data(node);
1222 if (!group_id) {
1223 g_free(ctx);
1224 xmlnode_free(xml);
1225 return FALSE;
1228 group = g_new0(struct sipe_group, 1);
1229 group->id = (int)g_ascii_strtod(group_id, NULL);
1230 g_free(group_id);
1231 group->name = ctx->group_name;
1233 sipe_group_add(sip, group);
1235 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1236 if (buddy) {
1237 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1240 sipe_group_set_user(sip, ctx->user_name);
1242 g_free(ctx);
1243 xmlnode_free(xml);
1244 return TRUE;
1246 return FALSE;
1249 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1251 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1252 gchar *body;
1253 ctx->group_name = g_strdup(name);
1254 ctx->user_name = g_strdup(who);
1256 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1257 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1258 g_free(body);
1262 * Data structure for scheduled actions
1264 typedef void (*Action) (struct sipe_account_data *, void *);
1266 struct scheduled_action {
1268 * Name of action.
1269 * Format is <Event>[<Data>...]
1270 * Example: <presence><sip:user@domain.com> or <registration>
1272 gchar *name;
1273 guint timeout_handler;
1274 gboolean repetitive;
1275 Action action;
1276 GDestroyNotify destroy;
1277 struct sipe_account_data *sip;
1278 void *payload;
1282 * A timer callback
1283 * Should return FALSE if repetitive action is not needed
1285 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1287 gboolean ret;
1288 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1289 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1290 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1291 (sched_action->action)(sched_action->sip, sched_action->payload);
1292 ret = sched_action->repetitive;
1293 if (sched_action->destroy) {
1294 (*sched_action->destroy)(sched_action->payload);
1296 g_free(sched_action->name);
1297 g_free(sched_action);
1298 return ret;
1302 * Kills action timer effectively cancelling
1303 * scheduled action
1305 * @param name of action
1307 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1309 GSList *entry;
1311 if (!sip->timeouts || !name) return;
1313 entry = sip->timeouts;
1314 while (entry) {
1315 struct scheduled_action *sched_action = entry->data;
1316 if(!strcmp(sched_action->name, name)) {
1317 GSList *to_delete = entry;
1318 entry = entry->next;
1319 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1320 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1321 purple_timeout_remove(sched_action->timeout_handler);
1322 if (sched_action->destroy) {
1323 (*sched_action->destroy)(sched_action->payload);
1325 g_free(sched_action->name);
1326 g_free(sched_action);
1327 } else {
1328 entry = entry->next;
1333 static void
1334 sipe_schedule_action0(const gchar *name,
1335 int timeout,
1336 gboolean isSeconds,
1337 Action action,
1338 GDestroyNotify destroy,
1339 struct sipe_account_data *sip,
1340 void *payload)
1342 struct scheduled_action *sched_action;
1344 /* Make sure each action only exists once */
1345 sipe_cancel_scheduled_action(sip, name);
1347 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1348 sched_action = g_new0(struct scheduled_action, 1);
1349 sched_action->repetitive = FALSE;
1350 sched_action->name = g_strdup(name);
1351 sched_action->action = action;
1352 sched_action->destroy = destroy;
1353 sched_action->sip = sip;
1354 sched_action->payload = payload;
1355 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1356 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1357 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1358 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1362 * Do schedule action for execution in the future.
1363 * Non repetitive execution.
1365 * @param name of action (will be copied)
1366 * @param timeout in seconds
1367 * @action callback function
1368 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1370 static void
1371 sipe_schedule_action(const gchar *name,
1372 int timeout,
1373 Action action,
1374 GDestroyNotify destroy,
1375 struct sipe_account_data *sip,
1376 void *payload)
1378 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1382 * Same as sipe_schedule_action() but timeout is in milliseconds.
1384 static void
1385 sipe_schedule_action_msec(const gchar *name,
1386 int timeout,
1387 Action action,
1388 GDestroyNotify destroy,
1389 struct sipe_account_data *sip,
1390 void *payload)
1392 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1396 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1398 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1399 SIPE_UNUSED_PARAMETER struct transaction *tc)
1401 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1403 process_incoming_notify(sip, msg, FALSE, FALSE);
1405 return TRUE;
1408 static void sipe_subscribe_resource_uri(const char *name,
1409 SIPE_UNUSED_PARAMETER gpointer value,
1410 gchar **resources_uri)
1412 gchar *tmp = *resources_uri;
1413 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1414 g_free(tmp);
1417 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1419 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1420 if (sbuddy && !sbuddy->resubscribed) { // Only not resubscribed contacts; the first time everybody are included
1421 gchar *tmp = *resources_uri;
1422 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1423 g_free(tmp);
1428 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1429 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1430 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1431 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1432 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1435 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1437 gchar *contact = get_contact(sip);
1438 gchar *request;
1439 gchar *content;
1440 gchar *require = "";
1441 gchar *accept = "";
1442 gchar *autoextend = "";
1443 gchar *content_type;
1445 if (sip->msrtc_event_categories) {
1446 require = ", categoryList";
1447 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1448 content_type = "application/msrtc-adrl-categorylist+xml";
1449 content = g_strdup_printf(
1450 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1451 "<action name=\"subscribe\" id=\"63792024\">\n"
1452 "<adhocList>\n%s</adhocList>\n"
1453 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1454 "<category name=\"note\"/>\n"
1455 "<category name=\"state\"/>\n"
1456 "</categoryList>\n"
1457 "</action>\n"
1458 "</batchSub>", sip->username, resources_uri);
1459 } else {
1460 autoextend = "Supported: com.microsoft.autoextend\r\n";
1461 content_type = "application/adrl+xml";
1462 content = g_strdup_printf(
1463 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1464 "<create xmlns=\"\">\n%s</create>\n"
1465 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1467 g_free(resources_uri);
1469 request = g_strdup_printf(
1470 "Require: adhoclist%s\r\n"
1471 "Supported: eventlist\r\n"
1472 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1473 "Supported: ms-piggyback-first-notify\r\n"
1474 "%sSupported: ms-benotify\r\n"
1475 "Proxy-Require: ms-benotify\r\n"
1476 "Event: presence\r\n"
1477 "Content-Type: %s\r\n"
1478 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1479 g_free(contact);
1481 /* subscribe to buddy presence */
1482 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1483 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1485 g_free(content);
1486 g_free(to);
1487 g_free(request);
1490 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1491 SIPE_UNUSED_PARAMETER void *unused)
1493 gchar *to = sip_uri_self(sip);
1494 gchar *resources_uri = g_strdup("");
1495 if (sip->msrtc_event_categories) {
1496 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1497 } else {
1498 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1500 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1503 struct presence_batched_routed {
1504 gchar *host;
1505 GSList *buddies;
1508 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1510 struct presence_batched_routed *data = payload;
1511 GSList *buddies = data->buddies;
1512 while (buddies) {
1513 g_free(buddies->data);
1514 buddies = buddies->next;
1516 g_slist_free(data->buddies);
1517 g_free(data->host);
1518 g_free(payload);
1521 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1523 struct presence_batched_routed *data = payload;
1524 GSList *buddies = data->buddies;
1525 gchar *resources_uri = g_strdup("");
1526 while (buddies) {
1527 gchar *tmp = resources_uri;
1528 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1529 g_free(tmp);
1530 buddies = buddies->next;
1532 sipe_subscribe_presence_batched_to(sip, resources_uri,
1533 g_strdup(data->host));
1537 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1538 * The user sends a single SUBSCRIBE request to the subscribed contact.
1539 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1543 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1545 gchar *to = sip_uri((char *)buddy_name);
1546 gchar *tmp = get_contact(sip);
1547 gchar *request;
1548 gchar *content;
1549 gchar *autoextend = "";
1551 if (!sip->msrtc_event_categories)
1552 autoextend = "Supported: com.microsoft.autoextend\r\n";
1554 request = g_strdup_printf(
1555 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1556 "Supported: ms-piggyback-first-notify\r\n"
1557 "%sSupported: ms-benotify\r\n"
1558 "Proxy-Require: ms-benotify\r\n"
1559 "Event: presence\r\n"
1560 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1561 "Contact: %s\r\n", autoextend,tmp);
1563 content = g_strdup_printf(
1564 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1565 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1566 "<resource uri=\"%s\"/>\n"
1567 "</adhocList>\n"
1568 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1569 "<category name=\"note\"/>\n"
1570 "<category name=\"state\"/>\n"
1571 "</categoryList>\n"
1572 "</action>\n"
1573 "</batchSub>", sip->username, to
1576 g_free(tmp);
1578 /* subscribe to buddy presence */
1579 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1581 g_free(content);
1582 g_free(to);
1583 g_free(request);
1586 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1588 if (!purple_status_is_active(status))
1589 return;
1591 if (account->gc) {
1592 struct sipe_account_data *sip = account->gc->proto_data;
1594 if (sip) {
1595 g_free(sip->status);
1596 sip->status = g_strdup(purple_status_get_id(status));
1597 send_presence_status(sip);
1602 static void
1603 sipe_alias_buddy(PurpleConnection *gc, const char *name,
1604 SIPE_UNUSED_PARAMETER const char *alias)
1606 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1607 sipe_group_set_user(sip, name);
1610 static void
1611 sipe_group_buddy(PurpleConnection *gc,
1612 const char *who,
1613 const char *old_group_name,
1614 const char *new_group_name)
1616 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1617 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1618 struct sipe_group * old_group = NULL;
1619 struct sipe_group * new_group;
1621 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1622 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1624 if(!buddy) { // buddy not in roaming list
1625 return;
1628 if (old_group_name) {
1629 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1631 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1633 if (old_group) {
1634 buddy->groups = g_slist_remove(buddy->groups, old_group);
1635 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1638 if (!new_group) {
1639 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1640 } else {
1641 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1642 sipe_group_set_user(sip, who);
1646 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1648 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1649 struct sipe_buddy *b;
1651 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1653 // Prepend sip: if needed
1654 if (strncmp("sip:", buddy->name, 4)) {
1655 gchar *buf = sip_uri_from_name(buddy->name);
1656 purple_blist_rename_buddy(buddy, buf);
1657 g_free(buf);
1660 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1661 b = g_new0(struct sipe_buddy, 1);
1662 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1663 b->name = g_strdup(buddy->name);
1664 g_hash_table_insert(sip->buddies, b->name, b);
1665 sipe_group_buddy(gc, b->name, NULL, group->name);
1666 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1667 } else {
1668 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1672 static void sipe_free_buddy(struct sipe_buddy *buddy)
1674 #ifndef _WIN32
1676 * We are calling g_hash_table_foreach_steal(). That means that no
1677 * key/value deallocation functions are called. Therefore the glib
1678 * hash code does not touch the key (buddy->name) or value (buddy)
1679 * of the to-be-deleted hash node at all. It follows that we
1681 * - MUST free the memory for the key ourselves and
1682 * - ARE allowed to do it in this function
1684 * Conclusion: glib must be broken on the Windows platform if sipe
1685 * crashes with SIGTRAP when closing. You'll have to live
1686 * with the memory leak until this is fixed.
1688 g_free(buddy->name);
1689 #endif
1690 g_free(buddy->annotation);
1691 g_free(buddy->device_name);
1692 g_slist_free(buddy->groups);
1693 g_free(buddy);
1697 * Unassociates buddy from group first.
1698 * Then see if no groups left, removes buddy completely.
1699 * Otherwise updates buddy groups on server.
1701 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1703 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1704 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1705 struct sipe_group *g = NULL;
1707 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1709 if (!b) return;
1711 if (group) {
1712 g = sipe_group_find_by_name(sip, group->name);
1715 if (g) {
1716 b->groups = g_slist_remove(b->groups, g);
1717 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1720 if (g_slist_length(b->groups) < 1) {
1721 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1722 sipe_cancel_scheduled_action(sip, action_name);
1723 g_free(action_name);
1725 g_hash_table_remove(sip->buddies, buddy->name);
1727 if (b->name) {
1728 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1729 send_soap_request(sip, body);
1730 g_free(body);
1733 sipe_free_buddy(b);
1734 } else {
1735 //updates groups on server
1736 sipe_group_set_user(sip, b->name);
1741 static void
1742 sipe_rename_group(PurpleConnection *gc,
1743 const char *old_name,
1744 PurpleGroup *group,
1745 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1747 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1748 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1749 if (group) {
1750 sipe_group_rename(sip, s_group, group->name);
1751 } else {
1752 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1756 static void
1757 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1759 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1760 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1761 if (s_group) {
1762 gchar *body;
1763 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1764 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1765 send_soap_request(sip, body);
1766 g_free(body);
1768 sip->groups = g_slist_remove(sip->groups, s_group);
1769 g_free(s_group->name);
1770 g_free(s_group);
1771 } else {
1772 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1776 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
1778 PurpleStatusType *type;
1779 GList *types = NULL;
1781 // Online
1782 type = purple_status_type_new_with_attrs(
1783 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1784 // Translators: noun
1785 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1786 NULL);
1787 types = g_list_append(types, type);
1789 // Busy
1790 type = purple_status_type_new_with_attrs(
1791 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1792 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1793 NULL);
1794 types = g_list_append(types, type);
1796 // Do Not Disturb (not user settable)
1797 type = purple_status_type_new_with_attrs(
1798 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1799 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1800 NULL);
1801 types = g_list_append(types, type);
1803 // Be Right Back
1804 type = purple_status_type_new_with_attrs(
1805 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1806 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1807 NULL);
1808 types = g_list_append(types, type);
1810 // Away
1811 type = purple_status_type_new_with_attrs(
1812 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1813 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1814 NULL);
1815 types = g_list_append(types, type);
1817 //On The Phone
1818 type = purple_status_type_new_with_attrs(
1819 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1820 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1821 NULL);
1822 types = g_list_append(types, type);
1824 //Out To Lunch
1825 type = purple_status_type_new_with_attrs(
1826 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1827 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1828 NULL);
1829 types = g_list_append(types, type);
1831 //Appear Offline
1832 type = purple_status_type_new_full(
1833 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1834 types = g_list_append(types, type);
1836 // Offline
1837 type = purple_status_type_new_full(
1838 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1839 types = g_list_append(types, type);
1841 return types;
1845 * A callback for g_hash_table_foreach
1847 static void sipe_buddy_subscribe_cb(SIPE_UNUSED_PARAMETER char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1849 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1850 int time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1851 int timeout = (time_range * rand()) / RAND_MAX; /* random period within the range */
1852 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy->name));
1856 * Removes entries from purple buddy list
1857 * that does not correspond ones in the roaming contact list.
1859 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1860 GSList *buddies = purple_find_buddies(sip->account, NULL);
1861 GSList *entry = buddies;
1862 struct sipe_buddy *buddy;
1863 PurpleBuddy *b;
1864 PurpleGroup *g;
1866 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1867 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1868 while (entry) {
1869 b = entry->data;
1870 g = purple_buddy_get_group(b);
1871 buddy = g_hash_table_lookup(sip->buddies, b->name);
1872 if(buddy) {
1873 gboolean in_sipe_groups = FALSE;
1874 GSList *entry2 = buddy->groups;
1875 while (entry2) {
1876 struct sipe_group *group = entry2->data;
1877 if (!strcmp(group->name, g->name)) {
1878 in_sipe_groups = TRUE;
1879 break;
1881 entry2 = entry2->next;
1883 if(!in_sipe_groups) {
1884 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1885 purple_blist_remove_buddy(b);
1887 } else {
1888 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1889 purple_blist_remove_buddy(b);
1891 entry = entry->next;
1895 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
1897 int len = msg->bodylen;
1899 gchar *tmp = sipmsg_find_header(msg, "Event");
1900 xmlnode *item;
1901 xmlnode *isc;
1902 const gchar *contacts_delta;
1903 xmlnode *group_node;
1904 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1905 return FALSE;
1908 /* Convert the contact from XML to Purple Buddies */
1909 isc = xmlnode_from_str(msg->body, len);
1910 if (!isc) {
1911 return FALSE;
1914 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1915 if (contacts_delta) {
1916 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1919 if (!strcmp(isc->name, "contactList")) {
1921 /* Parse groups */
1922 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1923 struct sipe_group * group = g_new0(struct sipe_group, 1);
1924 const char *name = xmlnode_get_attrib(group_node, "name");
1926 if (!strncmp(name, "~", 1)) {
1927 name = _("Other Contacts");
1929 group->name = g_strdup(name);
1930 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1932 sipe_group_add(sip, group);
1935 // Make sure we have at least one group
1936 if (g_slist_length(sip->groups) == 0) {
1937 struct sipe_group * group = g_new0(struct sipe_group, 1);
1938 PurpleGroup *purple_group;
1939 group->name = g_strdup(_("Other Contacts"));
1940 group->id = 1;
1941 purple_group = purple_group_new(group->name);
1942 purple_blist_add_group(purple_group, NULL);
1943 sip->groups = g_slist_append(sip->groups, group);
1946 /* Parse contacts */
1947 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1948 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1949 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1950 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1951 gchar * buddy_name = sip_uri_from_name(uri);
1952 gchar **item_groups;
1953 struct sipe_group *group = NULL;
1954 struct sipe_buddy *buddy = NULL;
1955 int i = 0;
1957 // assign to group Other Contacts if nothing else received
1958 if(!groups || !strcmp("", groups) ) {
1959 group = sipe_group_find_by_name(sip, _("Other Contacts"));
1960 groups = group ? g_strdup_printf("%d", group->id) : "1";
1963 item_groups = g_strsplit(groups, " ", 0);
1965 while (item_groups[i]) {
1966 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1968 // If couldn't find the right group for this contact, just put them in the first group we have
1969 if (group == NULL && g_slist_length(sip->groups) > 0) {
1970 group = sip->groups->data;
1973 if (group != NULL) {
1974 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1975 if (!b){
1976 b = purple_buddy_new(sip->account, buddy_name, uri);
1977 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1980 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1981 if (name != NULL && strlen(name) != 0) {
1982 purple_blist_alias_buddy(b, name);
1986 if (!buddy) {
1987 buddy = g_new0(struct sipe_buddy, 1);
1988 buddy->name = g_strdup(b->name);
1989 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1992 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1994 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
1995 } else {
1996 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1997 name);
2000 i++;
2001 } // while, contact groups
2002 g_strfreev(item_groups);
2003 g_free(groups);
2004 g_free(name);
2005 g_free(buddy_name);
2006 g_free(uri);
2008 } // for, contacts
2010 sipe_cleanup_local_blist(sip);
2012 xmlnode_free(isc);
2014 //subscribe to buddies
2015 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2016 if(sip->batched_support){
2017 sipe_subscribe_presence_batched(sip, NULL);
2019 else{
2020 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2022 sip->subscribed_buddies = TRUE;
2025 return 0;
2029 * Subscribe roaming contacts
2031 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2033 gchar *to = sip_uri_self(sip);
2034 gchar *tmp = get_contact(sip);
2035 gchar *hdr = g_strdup_printf(
2036 "Event: vnd-microsoft-roaming-contacts\r\n"
2037 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2038 "Supported: com.microsoft.autoextend\r\n"
2039 "Supported: ms-benotify\r\n"
2040 "Proxy-Require: ms-benotify\r\n"
2041 "Supported: ms-piggyback-first-notify\r\n"
2042 "Contact: %s\r\n", tmp);
2043 g_free(tmp);
2045 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2046 g_free(to);
2047 g_free(hdr);
2050 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2051 SIPE_UNUSED_PARAMETER void *unused)
2053 gchar *to = sip_uri_self(sip);
2054 gchar *tmp = get_contact(sip);
2055 gchar *hdr = g_strdup_printf(
2056 "Event: presence.wpending\r\n"
2057 "Accept: text/xml+msrtc.wpending\r\n"
2058 "Supported: com.microsoft.autoextend\r\n"
2059 "Supported: ms-benotify\r\n"
2060 "Proxy-Require: ms-benotify\r\n"
2061 "Supported: ms-piggyback-first-notify\r\n"
2062 "Contact: %s\r\n", tmp);
2063 g_free(tmp);
2065 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2066 g_free(to);
2067 g_free(hdr);
2071 * Fires on deregistration event initiated by server.
2072 * [MS-SIPREGE] SIP extension.
2075 // 2007 Example
2077 // Content-Type: text/registration-event
2078 // subscription-state: terminated;expires=0
2079 // ms-diagnostics-public: 4141;reason="User disabled"
2081 // deregistered;event=rejected
2083 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2085 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2086 gchar *event = NULL;
2087 gchar *reason = NULL;
2088 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2090 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2091 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2093 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2094 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2095 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2096 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2097 } else {
2098 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2099 return;
2102 if (warning != NULL) {
2103 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2104 } else { // for LCS2005
2105 int error_id = 0;
2106 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2107 error_id = 4140; // [MS-SIPREGE]
2108 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2109 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2110 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2111 error_id = 4141;
2112 reason = g_strdup(_("User disabled")); // [MS-OCER]
2113 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2114 error_id = 4142;
2115 reason = g_strdup(_("User moved")); // [MS-OCER]
2118 g_free(event);
2119 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2120 g_free(reason);
2122 sip->gc->wants_to_die = TRUE;
2123 purple_connection_error(sip->gc, warning);
2124 g_free(warning);
2128 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2130 xmlnode *xn_provision_group_list;
2131 xmlnode *node;
2133 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2135 /* provisionGroup */
2136 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2137 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2138 g_free(sip->focus_factory_uri);
2139 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2140 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2141 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2142 break;
2145 xmlnode_free(xn_provision_group_list);
2148 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2150 const gchar *contacts_delta;
2151 xmlnode *xml;
2153 xml = xmlnode_from_str(msg->body, msg->bodylen);
2154 if (!xml)
2156 return;
2159 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2160 if (contacts_delta)
2162 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2165 xmlnode_free(xml);
2168 static void
2169 free_container(struct sipe_container *container)
2171 GSList *entry;
2173 if (!container) return;
2175 entry = container->members;
2176 while (entry) {
2177 g_free(entry->data);
2178 entry = g_slist_remove(entry, entry->data);
2180 g_free(container);
2184 * Finds locally stored MS-PRES container member
2186 static struct sipe_container_member *
2187 sipe_find_container_member(struct sipe_container *container,
2188 const gchar *type,
2189 const gchar *value)
2191 struct sipe_container_member *member;
2192 GSList *entry;
2194 if (container == NULL || type == NULL) {
2195 return NULL;
2198 entry = container->members;
2199 while (entry) {
2200 member = entry->data;
2201 if (!g_strcasecmp(member->type, type)
2202 && ((!member->value && !value)
2203 || (value && member->value && !g_strcasecmp(member->value, value)))
2205 return member;
2207 entry = entry->next;
2209 return NULL;
2213 * Finds locally stored MS-PRES container by id
2215 static struct sipe_container *
2216 sipe_find_container(struct sipe_account_data *sip,
2217 guint id)
2219 struct sipe_container *container;
2220 GSList *entry;
2222 if (sip == NULL) {
2223 return NULL;
2226 entry = sip->containers;
2227 while (entry) {
2228 container = entry->data;
2229 if (id == container->id) {
2230 return container;
2232 entry = entry->next;
2234 return NULL;
2238 * Access Levels
2239 * 32000 - Blocked
2240 * 400 - Personal
2241 * 300 - Team
2242 * 200 - Company
2243 * 100 - Public
2245 static int
2246 sipe_find_access_level(struct sipe_account_data *sip,
2247 const gchar *type,
2248 const gchar *value)
2250 guint containers[] = {32000, 400, 300, 200, 100};
2251 int i = 0;
2253 for (i = 0; i < 5; i++) {
2254 struct sipe_container_member *member;
2255 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2256 if (!container) continue;
2258 member = sipe_find_container_member(container, type, value);
2259 if (member) {
2260 return containers[i];
2264 return -1;
2267 static void
2268 sipe_send_set_container_members(struct sipe_account_data *sip,
2269 guint container_id,
2270 guint container_version,
2271 const gchar* action,
2272 const gchar* type,
2273 const gchar* value)
2275 gchar *self = sip_uri_self(sip);
2276 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2277 gchar *contact;
2278 gchar *hdr;
2279 gchar *body = g_strdup_printf(
2280 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2281 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2282 "</setContainerMembers>",
2283 container_id,
2284 container_version,
2285 action,
2286 type,
2287 value_str);
2288 g_free(value_str);
2290 contact = get_contact(sip);
2291 hdr = g_strdup_printf("Contact: %s\r\n"
2292 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2293 g_free(contact);
2295 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2297 g_free(hdr);
2298 g_free(body);
2299 g_free(self);
2302 static void
2303 free_publication(struct sipe_publication *publication)
2305 g_free(publication->category);
2306 g_free(publication);
2309 /* key is <category><instance><container> */
2310 static gboolean
2311 sipe_is_our_publication(struct sipe_account_data *sip,
2312 const gchar *key)
2314 GSList *entry;
2316 /* filling keys for our publications if not yet cached */
2317 if (!sip->our_publication_keys) {
2318 guint device_instance = sipe_get_pub_instance(sip, SIPE_PUB_DEVICE);
2319 guint machine_instance = sipe_get_pub_instance(sip, SIPE_PUB_STATE_MACHINE);
2321 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2322 g_strdup_printf("<%s><%d><%d>", "device", device_instance, 2));
2323 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2324 g_strdup_printf("<%s><%d><%d>", "state", machine_instance, 2));
2325 sip->our_publication_keys = g_slist_append(sip->our_publication_keys,
2326 g_strdup_printf("<%s><%d><%d>", "state", machine_instance, 3));
2329 entry = sip->our_publication_keys;
2330 while (entry) {
2331 if (!strcmp(entry->data, key)) {
2332 return TRUE;
2334 entry = entry->next;
2336 return FALSE;
2340 * When we receive some self (BE) NOTIFY with a new subscriber
2341 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2344 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
2346 gchar *contact;
2347 gchar *to;
2348 xmlnode *xml;
2349 xmlnode *node;
2350 xmlnode *node2;
2351 char *display_name = NULL;
2352 PurpleBuddy *pbuddy;
2353 const char *alias;
2354 char *uri_alias;
2355 char *uri_user;
2356 GHashTable *our_publications_tmp = NULL;
2358 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2360 xml = xmlnode_from_str(msg->body, msg->bodylen);
2361 if (!xml) return;
2363 contact = get_contact(sip);
2364 to = sip_uri_self(sip);
2367 /* categories */
2368 for (node = xmlnode_get_descendant(xml, "categories", "category", NULL); node; node = xmlnode_get_next_twin(node)) {
2369 const gchar *name = xmlnode_get_attrib(node, "name");
2370 guint instance = atoi(xmlnode_get_attrib(node, "instance"));
2371 guint container = atoi(xmlnode_get_attrib(node, "container"));
2372 guint version = atoi(xmlnode_get_attrib(node, "version"));
2373 /* key is <category><instance><container> */
2374 gchar *key = g_strdup_printf("<%s><%d><%d>", name, instance, container);
2375 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key, version);
2376 if (sipe_is_our_publication(sip, key)) {
2377 struct sipe_publication *publication = g_new0(struct sipe_publication, 1);
2378 publication->category = g_strdup(name);
2379 publication->instance = instance;
2380 publication->container = container;
2381 publication->version = version;
2383 if (!our_publications_tmp) {
2384 our_publications_tmp = g_hash_table_new_full(
2385 g_str_hash, g_str_equal,
2386 g_free, (GDestroyNotify)free_publication);
2388 g_hash_table_insert(our_publications_tmp, g_strdup(key), publication);
2389 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key, version);
2391 g_free(key);
2393 if (our_publications_tmp) {
2394 g_hash_table_destroy(sip->our_publications);
2395 sip->our_publications = our_publications_tmp;
2396 our_publications_tmp = NULL;
2398 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
2399 sip->our_publications ? (int) g_hash_table_size(sip->our_publications) : -1);
2401 /* containers */
2402 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
2403 guint id = atoi(xmlnode_get_attrib(node, "id"));
2404 struct sipe_container *container = sipe_find_container(sip, id);
2406 if (container) {
2407 sip->containers = g_slist_remove(sip->containers, container);
2408 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
2409 free_container(container);
2411 container = g_new0(struct sipe_container, 1);
2412 container->id = id;
2413 container->version = atoi(xmlnode_get_attrib(node, "version"));
2414 sip->containers = g_slist_append(sip->containers, container);
2415 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
2417 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
2418 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2419 member->type = xmlnode_get_attrib(node2, "type");
2420 member->value = xmlnode_get_attrib(node2, "value");
2421 container->members = g_slist_append(container->members, member);
2422 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
2423 member->type, member->value ? member->value : "");
2427 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
2428 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
2429 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
2430 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
2431 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
2432 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
2433 /* initial set-up to let counterparties see your status */
2434 if (sameEnterpriseAL < 0) {
2435 struct sipe_container *container = sipe_find_container(sip, 200);
2436 guint version = container ? container->version : 0;
2437 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
2439 if (federatedAL < 0) {
2440 struct sipe_container *container = sipe_find_container(sip, 100);
2441 guint version = container ? container->version : 0;
2442 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
2444 sip->access_level_set = TRUE;
2447 /* subscribers */
2448 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2449 const char *user;
2450 const char *acknowledged;
2451 gchar *hdr;
2452 gchar *body;
2454 user = xmlnode_get_attrib(node, "user");
2455 if (!user) continue;
2456 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2457 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2458 uri_user = sip_uri_from_name(user);
2459 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2460 if(pbuddy){
2461 alias = purple_buddy_get_local_alias(pbuddy);
2462 uri_alias = sip_uri_from_name(alias);
2463 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2464 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2465 purple_blist_alias_buddy(pbuddy, display_name);
2467 g_free(uri_alias);
2470 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2471 if(!g_ascii_strcasecmp(acknowledged,"false")){
2472 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2473 if (!purple_find_buddy(sip->account, uri_user)) {
2474 purple_account_request_add(sip->account, uri_user, _("you"), display_name, NULL);
2477 hdr = g_strdup_printf(
2478 "Contact: %s\r\n"
2479 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2481 body = g_strdup_printf(
2482 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2483 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2484 "</setSubscribers>", user);
2486 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2487 g_free(body);
2488 g_free(hdr);
2490 g_free(display_name);
2491 g_free(uri_user);
2494 g_free(to);
2495 g_free(contact);
2496 xmlnode_free(xml);
2499 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
2501 gchar *to = sip_uri_self(sip);
2502 gchar *tmp = get_contact(sip);
2503 gchar *hdr = g_strdup_printf(
2504 "Event: vnd-microsoft-roaming-ACL\r\n"
2505 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2506 "Supported: com.microsoft.autoextend\r\n"
2507 "Supported: ms-benotify\r\n"
2508 "Proxy-Require: ms-benotify\r\n"
2509 "Supported: ms-piggyback-first-notify\r\n"
2510 "Contact: %s\r\n", tmp);
2511 g_free(tmp);
2513 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2514 g_free(to);
2515 g_free(hdr);
2519 * To request for presence information about the user, access level settings that have already been configured by the user
2520 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2521 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2524 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
2526 gchar *to = sip_uri_self(sip);
2527 gchar *tmp = get_contact(sip);
2528 gchar *hdr = g_strdup_printf(
2529 "Event: vnd-microsoft-roaming-self\r\n"
2530 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2531 "Supported: ms-benotify\r\n"
2532 "Proxy-Require: ms-benotify\r\n"
2533 "Supported: ms-piggyback-first-notify\r\n"
2534 "Contact: %s\r\n"
2535 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2537 gchar *body=g_strdup(
2538 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2539 "<roaming type=\"categories\"/>"
2540 "<roaming type=\"containers\"/>"
2541 "<roaming type=\"subscribers\"/></roamingList>");
2543 g_free(tmp);
2544 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2545 g_free(body);
2546 g_free(to);
2547 g_free(hdr);
2551 * For 2005 version
2553 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
2555 gchar *to = sip_uri_self(sip);
2556 gchar *tmp = get_contact(sip);
2557 gchar *hdr = g_strdup_printf(
2558 "Event: vnd-microsoft-provisioning\r\n"
2559 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2560 "Supported: com.microsoft.autoextend\r\n"
2561 "Supported: ms-benotify\r\n"
2562 "Proxy-Require: ms-benotify\r\n"
2563 "Supported: ms-piggyback-first-notify\r\n"
2564 "Expires: 0\r\n"
2565 "Contact: %s\r\n", tmp);
2567 g_free(tmp);
2568 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2569 g_free(to);
2570 g_free(hdr);
2573 /** Subscription for provisioning information to help with initial
2574 * configuration. This subscription is a one-time query (denoted by the Expires header,
2575 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2576 * configuration, meeting policies, and policy settings that Communicator must enforce.
2577 * TODO: for what we need this information.
2580 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
2582 gchar *to = sip_uri_self(sip);
2583 gchar *tmp = get_contact(sip);
2584 gchar *hdr = g_strdup_printf(
2585 "Event: vnd-microsoft-provisioning-v2\r\n"
2586 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2587 "Supported: com.microsoft.autoextend\r\n"
2588 "Supported: ms-benotify\r\n"
2589 "Proxy-Require: ms-benotify\r\n"
2590 "Supported: ms-piggyback-first-notify\r\n"
2591 "Expires: 0\r\n"
2592 "Contact: %s\r\n"
2593 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2594 gchar *body = g_strdup(
2595 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2596 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2597 "<provisioningGroup name=\"ucPolicy\"/>"
2598 "</provisioningGroupList>");
2600 g_free(tmp);
2601 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2602 g_free(body);
2603 g_free(to);
2604 g_free(hdr);
2607 /* IM Session (INVITE and MESSAGE methods) */
2609 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2610 static gchar *
2611 get_end_points (struct sipe_account_data *sip,
2612 struct sip_session *session)
2614 gchar *res;
2616 if (session == NULL) {
2617 return NULL;
2620 res = g_strdup_printf("<sip:%s>", sip->username);
2622 SIPE_DIALOG_FOREACH {
2623 gchar *tmp = res;
2624 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2625 g_free(tmp);
2627 if (dialog->theirepid) {
2628 tmp = res;
2629 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2630 g_free(tmp);
2632 } SIPE_DIALOG_FOREACH_END;
2634 return res;
2637 static gboolean
2638 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
2639 struct sipmsg *msg,
2640 SIPE_UNUSED_PARAMETER struct transaction *trans)
2642 gboolean ret = TRUE;
2644 if (msg->response != 200) {
2645 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2646 return FALSE;
2649 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2651 return ret;
2655 * Asks UA/proxy about its capabilities.
2657 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2659 gchar *to = sip_uri(who);
2660 gchar *contact = get_contact(sip);
2661 gchar *request = g_strdup_printf(
2662 "Accept: application/sdp\r\n"
2663 "Contact: %s\r\n", contact);
2664 g_free(contact);
2666 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2668 g_free(to);
2669 g_free(request);
2672 static void
2673 sipe_notify_user(struct sipe_account_data *sip,
2674 struct sip_session *session,
2675 PurpleMessageFlags flags,
2676 const gchar *message)
2678 PurpleConversation *conv;
2680 if (!session->conv) {
2681 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
2682 } else {
2683 conv = session->conv;
2685 purple_conversation_write(conv, NULL, message, flags, time(NULL));
2688 void
2689 sipe_present_info(struct sipe_account_data *sip,
2690 struct sip_session *session,
2691 const gchar *message)
2693 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
2696 static void
2697 sipe_present_err(struct sipe_account_data *sip,
2698 struct sip_session *session,
2699 const gchar *message)
2701 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
2704 void
2705 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
2706 struct sip_session *session,
2707 const gchar *who,
2708 const gchar *message)
2710 char *msg, *msg_tmp;
2712 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2713 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2714 g_free(msg_tmp);
2715 msg_tmp = g_strdup_printf( _("This message was not delivered to %s because one or more recipients are offline:\n%s") ,
2716 who ? who : "", msg ? msg : "");
2717 sipe_present_err(sip, session, msg_tmp);
2718 g_free(msg_tmp);
2719 g_free(msg);
2723 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
2725 static gboolean
2726 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
2727 SIPE_UNUSED_PARAMETER struct transaction *trans)
2729 gboolean ret = TRUE;
2730 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2731 struct sip_session *session = sipe_session_find_im(sip, with);
2732 struct sip_dialog *dialog;
2733 gchar *cseq;
2734 char *key;
2735 gchar *message;
2737 if (!session) {
2738 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2739 g_free(with);
2740 return FALSE;
2743 dialog = sipe_dialog_find(session, with);
2744 if (!dialog) {
2745 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2746 g_free(with);
2747 return FALSE;
2750 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2751 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2752 g_free(cseq);
2753 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2755 if (msg->response >= 400) {
2756 PurpleBuddy *pbuddy;
2757 gchar *alias = with;
2759 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
2761 if ((pbuddy = purple_find_buddy(sip->account, with))) {
2762 alias = (gchar *)purple_buddy_get_alias(pbuddy);
2765 sipe_present_message_undelivered_err(sip, session, alias, message);
2766 ret = FALSE;
2767 } else {
2768 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
2769 if (message_id) {
2770 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
2771 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
2772 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
2775 g_hash_table_remove(session->unconfirmed_messages, key);
2776 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2777 key, g_hash_table_size(session->unconfirmed_messages));
2780 g_free(key);
2781 g_free(with);
2783 if (ret) sipe_im_process_queue(sip, session);
2784 return ret;
2787 static gboolean
2788 sipe_is_election_finished(struct sip_session *session);
2790 static void
2791 sipe_election_result(struct sipe_account_data *sip,
2792 void *sess);
2794 static gboolean
2795 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
2796 SIPE_UNUSED_PARAMETER struct transaction *trans)
2798 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2799 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2800 struct sip_dialog *dialog;
2801 struct sip_session *session;
2803 session = sipe_session_find_chat_by_callid(sip, callid);
2804 if (!session) {
2805 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2806 return FALSE;
2809 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2810 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2811 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2812 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2814 if (xn_request_rm_response) {
2815 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2816 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2818 dialog = sipe_dialog_find(session, with);
2819 if (!dialog) {
2820 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2821 return FALSE;
2824 if (allow && !g_strcasecmp(allow, "true")) {
2825 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2826 dialog->election_vote = 1;
2827 } else if (allow && !g_strcasecmp(allow, "false")) {
2828 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2829 dialog->election_vote = -1;
2832 if (sipe_is_election_finished(session)) {
2833 sipe_election_result(sip, session);
2836 } else if (xn_set_rm_response) {
2839 xmlnode_free(xn_action);
2843 return TRUE;
2846 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2848 gchar *hdr;
2849 gchar *tmp;
2850 char *msgformat;
2851 char *msgtext;
2852 gchar *msgr_value;
2853 gchar *msgr;
2855 sipe_parse_html(msg, &msgformat, &msgtext);
2856 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2858 msgr_value = sipmsg_get_msgr_string(msgformat);
2859 g_free(msgformat);
2860 if (msgr_value) {
2861 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2862 g_free(msgr_value);
2863 } else {
2864 msgr = g_strdup("");
2867 tmp = get_contact(sip);
2868 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2869 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2870 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2871 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2872 g_free(tmp);
2873 g_free(msgr);
2875 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2876 g_free(msgtext);
2877 g_free(hdr);
2881 static void
2882 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
2884 GSList *entry2 = session->outgoing_message_queue;
2885 while (entry2) {
2886 char *queued_msg = entry2->data;
2888 /* for multiparty chat or conference */
2889 if (session->is_multiparty || session->focus_uri) {
2890 gchar *who = sip_uri_self(sip);
2891 serv_got_chat_in(sip->gc, session->chat_id, who,
2892 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2893 g_free(who);
2896 SIPE_DIALOG_FOREACH {
2897 char *key;
2899 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
2901 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2902 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2903 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2904 key, g_hash_table_size(session->unconfirmed_messages));
2905 g_free(key);
2907 sipe_send_message(sip, dialog, queued_msg);
2908 } SIPE_DIALOG_FOREACH_END;
2910 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2911 g_free(queued_msg);
2915 static void
2916 sipe_refer_notify(struct sipe_account_data *sip,
2917 struct sip_session *session,
2918 const gchar *who,
2919 int status,
2920 const gchar *desc)
2922 gchar *hdr;
2923 gchar *body;
2924 struct sip_dialog *dialog = sipe_dialog_find(session, who);
2926 hdr = g_strdup_printf(
2927 "Event: refer\r\n"
2928 "Subscription-State: %s\r\n"
2929 "Content-Type: message/sipfrag\r\n",
2930 status >= 200 ? "terminated" : "active");
2932 body = g_strdup_printf(
2933 "SIP/2.0 %d %s\r\n",
2934 status, desc);
2936 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
2938 g_free(hdr);
2939 g_free(body);
2942 static gboolean
2943 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2945 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2946 struct sip_session *session;
2947 struct sip_dialog *dialog;
2948 char *cseq;
2949 char *key;
2950 gchar *message;
2951 struct sipmsg *request_msg = trans->msg;
2953 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2954 gchar *referred_by;
2956 session = sipe_session_find_chat_by_callid(sip, callid);
2957 if (!session) {
2958 session = sipe_session_find_im(sip, with);
2960 if (!session) {
2961 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2962 g_free(with);
2963 return FALSE;
2966 dialog = sipe_dialog_find(session, with);
2967 if (!dialog) {
2968 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2969 g_free(with);
2970 return FALSE;
2973 sipe_dialog_parse(dialog, msg, TRUE);
2975 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2976 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2977 g_free(cseq);
2978 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2980 if (msg->response != 200) {
2981 PurpleBuddy *pbuddy;
2982 gchar *alias = with;
2984 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2986 if ((pbuddy = purple_find_buddy(sip->account, with))) {
2987 alias = (gchar *)purple_buddy_get_alias(pbuddy);
2990 if (message) {
2991 sipe_present_message_undelivered_err(sip, session, alias, message);
2992 } else {
2993 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
2994 sipe_present_err(sip, session, tmp_msg);
2995 g_free(tmp_msg);
2998 sipe_dialog_remove(session, with);
3000 g_free(key);
3001 g_free(with);
3002 return FALSE;
3005 dialog->cseq = 0;
3006 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3007 dialog->outgoing_invite = NULL;
3008 dialog->is_established = TRUE;
3010 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
3011 if (referred_by) {
3012 sipe_refer_notify(sip, session, referred_by, 200, "OK");
3013 g_free(referred_by);
3016 /* add user to chat if it is a multiparty session */
3017 if (session->is_multiparty) {
3018 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3019 with, NULL,
3020 PURPLE_CBFLAGS_NONE, TRUE);
3023 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3024 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3025 if (session->outgoing_message_queue) {
3026 char *queued_msg = session->outgoing_message_queue->data;
3027 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3028 g_free(queued_msg);
3032 sipe_im_process_queue(sip, session);
3034 g_hash_table_remove(session->unconfirmed_messages, key);
3035 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3036 key, g_hash_table_size(session->unconfirmed_messages));
3038 g_free(key);
3039 g_free(with);
3040 return TRUE;
3044 void
3045 sipe_invite(struct sipe_account_data *sip,
3046 struct sip_session *session,
3047 const gchar *who,
3048 const gchar *msg_body,
3049 const gchar *referred_by,
3050 const gboolean is_triggered)
3052 gchar *hdr;
3053 gchar *to;
3054 gchar *contact;
3055 gchar *body;
3056 gchar *self;
3057 char *ms_text_format = g_strdup("");
3058 gchar *roster_manager;
3059 gchar *end_points;
3060 gchar *referred_by_str;
3061 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3063 if (dialog && dialog->is_established) {
3064 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
3065 return;
3068 if (!dialog) {
3069 dialog = sipe_dialog_add(session);
3070 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3071 dialog->with = g_strdup(who);
3074 if (!(dialog->ourtag)) {
3075 dialog->ourtag = gentag();
3078 to = sip_uri(who);
3080 if (msg_body) {
3081 char *msgformat;
3082 char *msgtext;
3083 char *base64_msg;
3084 gchar *msgr_value;
3085 gchar *msgr;
3086 char *key;
3088 sipe_parse_html(msg_body, &msgformat, &msgtext);
3089 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
3091 msgr_value = sipmsg_get_msgr_string(msgformat);
3092 g_free(msgformat);
3093 msgr = "";
3094 if (msgr_value) {
3095 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3096 g_free(msgr_value);
3099 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3100 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3101 g_free(msgtext);
3102 g_free(msgr);
3103 g_free(base64_msg);
3105 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3106 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3107 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
3108 key, g_hash_table_size(session->unconfirmed_messages));
3109 g_free(key);
3112 contact = get_contact(sip);
3113 end_points = get_end_points(sip, session);
3114 self = sip_uri_self(sip);
3115 roster_manager = g_strdup_printf(
3116 "Roster-Manager: %s\r\n"
3117 "EndPoints: %s\r\n",
3118 self,
3119 end_points);
3120 referred_by_str = referred_by ?
3121 g_strdup_printf(
3122 "Referred-By: %s\r\n",
3123 referred_by)
3124 : g_strdup("");
3125 hdr = g_strdup_printf(
3126 "Supported: ms-sender\r\n"
3127 "%s"
3128 "%s"
3129 "%s"
3130 "%s"
3131 "Contact: %s\r\n%s"
3132 "Content-Type: application/sdp\r\n",
3133 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3134 referred_by_str,
3135 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3136 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3137 contact,
3138 ms_text_format);
3139 g_free(ms_text_format);
3140 g_free(self);
3142 body = g_strdup_printf(
3143 "v=0\r\n"
3144 "o=- 0 0 IN IP4 %s\r\n"
3145 "s=session\r\n"
3146 "c=IN IP4 %s\r\n"
3147 "t=0 0\r\n"
3148 "m=message %d sip null\r\n"
3149 "a=accept-types:text/plain text/html image/gif "
3150 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3151 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3153 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3154 to, to, hdr, body, dialog, process_invite_response);
3156 g_free(to);
3157 g_free(roster_manager);
3158 g_free(end_points);
3159 g_free(referred_by_str);
3160 g_free(body);
3161 g_free(hdr);
3162 g_free(contact);
3165 static void
3166 sipe_refer(struct sipe_account_data *sip,
3167 struct sip_session *session,
3168 const gchar *who)
3170 gchar *hdr;
3171 gchar *contact;
3172 struct sip_dialog *dialog = sipe_dialog_find(session,
3173 session->roster_manager);
3175 contact = get_contact(sip);
3176 hdr = g_strdup_printf(
3177 "Contact: %s\r\n"
3178 "Refer-to: <%s>\r\n"
3179 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3180 "Require: com.microsoft.rtc-multiparty\r\n",
3181 contact,
3182 who,
3183 sip->username,
3184 dialog->ourtag ? ";tag=" : "",
3185 dialog->ourtag ? dialog->ourtag : "",
3186 get_epid(sip));
3188 send_sip_request(sip->gc, "REFER",
3189 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3191 g_free(hdr);
3192 g_free(contact);
3195 static void
3196 sipe_send_election_request_rm(struct sipe_account_data *sip,
3197 struct sip_dialog *dialog,
3198 int bid)
3200 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3202 gchar *body = g_strdup_printf(
3203 "<?xml version=\"1.0\"?>\r\n"
3204 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3205 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3206 sip->username, bid);
3208 send_sip_request(sip->gc, "INFO",
3209 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3211 g_free(body);
3214 static void
3215 sipe_send_election_set_rm(struct sipe_account_data *sip,
3216 struct sip_dialog *dialog)
3218 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3220 gchar *body = g_strdup_printf(
3221 "<?xml version=\"1.0\"?>\r\n"
3222 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3223 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3224 sip->username);
3226 send_sip_request(sip->gc, "INFO",
3227 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3229 g_free(body);
3232 static void
3233 sipe_session_close(struct sipe_account_data *sip,
3234 struct sip_session * session)
3236 if (session && session->focus_uri) {
3237 conf_session_close(sip, session);
3240 if (session) {
3241 SIPE_DIALOG_FOREACH {
3242 /* @TODO slow down BYE message sending rate */
3243 /* @see single subscription code */
3244 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3245 } SIPE_DIALOG_FOREACH_END;
3247 sipe_session_remove(sip, session);
3251 static void
3252 sipe_session_close_all(struct sipe_account_data *sip)
3254 GSList *entry;
3255 while ((entry = sip->sessions) != NULL) {
3256 sipe_session_close(sip, entry->data);
3260 static void
3261 sipe_convo_closed(PurpleConnection * gc, const char *who)
3263 struct sipe_account_data *sip = gc->proto_data;
3265 purple_debug_info("sipe", "conversation with %s closed\n", who);
3266 sipe_session_close(sip, sipe_session_find_im(sip, who));
3269 static void
3270 sipe_chat_leave (PurpleConnection *gc, int id)
3272 struct sipe_account_data *sip = gc->proto_data;
3273 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
3275 sipe_session_close(sip, session);
3278 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3279 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3281 struct sipe_account_data *sip = gc->proto_data;
3282 struct sip_session *session;
3283 struct sip_dialog *dialog;
3285 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3287 session = sipe_session_find_or_add_im(sip, who);
3288 dialog = sipe_dialog_find(session, who);
3290 // Queue the message
3291 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3293 if (dialog && dialog->callid) {
3294 sipe_im_process_queue(sip, session);
3295 } else if (!dialog || !dialog->outgoing_invite) {
3296 // Need to send the INVITE to get the outgoing dialog setup
3297 sipe_invite(sip, session, who, what, NULL, FALSE);
3300 return 1;
3303 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
3304 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3306 struct sipe_account_data *sip = gc->proto_data;
3307 struct sip_session *session;
3309 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3311 session = sipe_session_find_chat_by_id(sip, id);
3313 // Queue the message
3314 if (session && session->dialogs) {
3315 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3316 g_strdup(what));
3317 sipe_im_process_queue(sip, session);
3320 return 1;
3323 /* End IM Session (INVITE and MESSAGE methods) */
3325 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3327 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3328 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3329 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3330 struct sip_session *session;
3332 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
3334 session = sipe_session_find_chat_by_callid(sip, callid);
3335 if (!session) {
3336 session = sipe_session_find_im(sip, from);
3338 if (!session) {
3339 g_free(from);
3340 return;
3343 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3344 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3345 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3346 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3348 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
3350 if (xn_request_rm) {
3351 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3352 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3353 gchar *body = g_strdup_printf(
3354 "<?xml version=\"1.0\"?>\r\n"
3355 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3356 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3357 sip->username,
3358 session->bid < bid ? "true" : "false");
3359 send_sip_response(sip->gc, msg, 200, "OK", body);
3360 g_free(body);
3361 } else if (xn_set_rm) {
3362 gchar *body;
3363 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3364 g_free(session->roster_manager);
3365 session->roster_manager = g_strdup(rm);
3367 body = g_strdup_printf(
3368 "<?xml version=\"1.0\"?>\r\n"
3369 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3370 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3371 sip->username);
3372 send_sip_response(sip->gc, msg, 200, "OK", body);
3373 g_free(body);
3375 xmlnode_free(xn_action);
3377 } else {
3378 /* looks like purple lacks typing notification for chat */
3379 if (!session->is_multiparty && !session->focus_uri) {
3380 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
3381 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
3382 "status");
3383 if (status && !strcmp(status, "type")) {
3384 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3385 } else if (status && !strcmp(status, "idle")) {
3386 serv_got_typing_stopped(sip->gc, from);
3388 xmlnode_free(xn_keyboard_activity);
3391 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3393 g_free(from);
3396 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3398 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3399 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3400 struct sip_session *session;
3402 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3404 session = sipe_session_find_chat_by_callid(sip, callid);
3405 if (!session) {
3406 session = sipe_session_find_im(sip, from);
3408 if (!session) {
3409 g_free(from);
3410 return;
3413 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3414 g_free(session->roster_manager);
3415 session->roster_manager = NULL;
3418 /* This what BYE is essentially for - terminating dialog */
3419 sipe_dialog_remove(session, from);
3420 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
3421 sipe_conf_immcu_closed(sip, session);
3422 } else if (session->is_multiparty) {
3423 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3426 g_free(from);
3429 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3431 gchar *self = sip_uri_self(sip);
3432 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3433 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3434 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3435 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3436 struct sip_session *session;
3437 struct sip_dialog *dialog;
3439 session = sipe_session_find_chat_by_callid(sip, callid);
3440 dialog = sipe_dialog_find(session, from);
3442 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3443 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3444 } else {
3445 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3447 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3450 g_free(self);
3451 g_free(from);
3452 g_free(refer_to);
3453 g_free(referred_by);
3456 static unsigned int
3457 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3459 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3460 struct sip_session *session;
3461 struct sip_dialog *dialog;
3463 if (state == PURPLE_NOT_TYPING)
3464 return 0;
3466 session = sipe_session_find_im(sip, who);
3467 dialog = sipe_dialog_find(session, who);
3469 if (session && dialog && dialog->is_established) {
3470 send_sip_request(gc, "INFO", who, who,
3471 "Content-Type: application/xml\r\n",
3472 SIPE_SEND_TYPING, dialog, NULL);
3474 return SIPE_TYPING_SEND_TIMEOUT;
3477 static gboolean resend_timeout(struct sipe_account_data *sip)
3479 GSList *tmp = sip->transactions;
3480 time_t currtime = time(NULL);
3481 while (tmp) {
3482 struct transaction *trans = tmp->data;
3483 tmp = tmp->next;
3484 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3485 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3486 /* TODO 408 */
3487 } else {
3488 if ((currtime - trans->time > 2) && trans->retries == 0) {
3489 trans->retries++;
3490 sendout_sipmsg(sip, trans->msg);
3494 return TRUE;
3497 static void do_reauthenticate_cb(struct sipe_account_data *sip,
3498 SIPE_UNUSED_PARAMETER void *unused)
3500 /* register again when security token expires */
3501 /* we have to start a new authentication as the security token
3502 * is almost expired by sending a not signed REGISTER message */
3503 purple_debug_info("sipe", "do a full reauthentication\n");
3504 sipe_auth_free(&sip->registrar);
3505 sipe_auth_free(&sip->proxy);
3506 sip->registerstatus = 0;
3507 do_register(sip);
3508 sip->reauthenticate_set = FALSE;
3511 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3513 gchar *from;
3514 gchar *contenttype;
3515 gboolean found = FALSE;
3517 from = parse_from(sipmsg_find_header(msg, "From"));
3519 if (!from) return;
3521 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3523 contenttype = sipmsg_find_header(msg, "Content-Type");
3524 if (!strncmp(contenttype, "text/plain", 10)
3525 || !strncmp(contenttype, "text/html", 9)
3526 || !strncmp(contenttype, "multipart/related", 21))
3528 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3529 gchar *html = get_html_message(contenttype, msg->body);
3531 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
3532 if (!session) {
3533 session = sipe_session_find_im(sip, from);
3536 if (session && session->focus_uri) { /* a conference */
3537 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
3538 gchar *sender = parse_from(tmp);
3539 g_free(tmp);
3540 serv_got_chat_in(sip->gc, session->chat_id, sender,
3541 PURPLE_MESSAGE_RECV, html, time(NULL));
3542 g_free(sender);
3543 } else if (session && session->is_multiparty) { /* a multiparty chat */
3544 serv_got_chat_in(sip->gc, session->chat_id, from,
3545 PURPLE_MESSAGE_RECV, html, time(NULL));
3546 } else {
3547 serv_got_im(sip->gc, from, html, 0, time(NULL));
3549 g_free(html);
3550 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3551 found = TRUE;
3553 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3554 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3555 xmlnode *state;
3556 gchar *statedata;
3558 if (!isc) {
3559 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3560 return;
3563 state = xmlnode_get_child(isc, "state");
3565 if (!state) {
3566 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3567 xmlnode_free(isc);
3568 return;
3571 statedata = xmlnode_get_data(state);
3572 if (statedata) {
3573 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3574 else serv_got_typing_stopped(sip->gc, from);
3576 g_free(statedata);
3578 xmlnode_free(isc);
3579 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3580 found = TRUE;
3582 if (!found) {
3583 purple_debug_info("sipe", "got unknown mime-type");
3584 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3586 g_free(from);
3589 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3591 gchar *ms_text_format;
3592 gchar *body;
3593 gchar *newTag;
3594 gchar *oldHeader;
3595 gchar *newHeader;
3596 gboolean is_multiparty = FALSE;
3597 gboolean is_triggered = FALSE;
3598 gboolean was_multiparty = TRUE;
3599 gboolean just_joined = FALSE;
3600 gchar *from;
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_session *session;
3609 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3611 /* Invitation to join conference */
3612 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
3613 process_incoming_invite_conf(sip, msg);
3614 return;
3617 /* Only accept text invitations */
3618 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3619 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3620 return;
3623 // TODO There *must* be a better way to clean up the To header to add a tag...
3624 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3625 oldHeader = sipmsg_find_header(msg, "To");
3626 newTag = gentag();
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 = sipe_session_find_chat_by_callid(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 = sipe_session_find_or_add_chat_by_callid(sip, callid);
3657 /* IM session */
3658 from = parse_from(sipmsg_find_header(msg, "From"));
3659 if (!session) {
3660 session = sipe_session_find_or_add_im(sip, from);
3663 g_free(session->callid);
3664 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 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3673 GSList *entry = end_points;
3674 while (entry) {
3675 struct sip_dialog *dialog;
3676 struct sipendpoint *end_point = entry->data;
3677 entry = entry->next;
3679 if (!g_strcasecmp(from, end_point->contact) ||
3680 !g_strcasecmp(to, end_point->contact))
3681 continue;
3683 dialog = sipe_dialog_find(session, end_point->contact);
3684 if (dialog) {
3685 g_free(dialog->theirepid);
3686 dialog->theirepid = end_point->epid;
3687 end_point->epid = NULL;
3688 } else {
3689 dialog = sipe_dialog_add(session);
3691 dialog->callid = g_strdup(session->callid);
3692 dialog->with = end_point->contact;
3693 end_point->contact = NULL;
3694 dialog->theirepid = end_point->epid;
3695 end_point->epid = NULL;
3697 just_joined = TRUE;
3699 /* send triggered INVITE */
3700 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
3703 g_free(to);
3706 if (end_points) {
3707 GSList *entry = end_points;
3708 while (entry) {
3709 struct sipendpoint *end_point = entry->data;
3710 entry = entry->next;
3711 g_free(end_point->contact);
3712 g_free(end_point->epid);
3713 g_free(end_point);
3715 g_slist_free(end_points);
3718 if (session) {
3719 struct sip_dialog *dialog = sipe_dialog_find(session, from);
3720 if (dialog) {
3721 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3722 } else {
3723 dialog = sipe_dialog_add(session);
3725 dialog->callid = g_strdup(session->callid);
3726 dialog->with = g_strdup(from);
3727 sipe_dialog_parse(dialog, msg, FALSE);
3729 if (!dialog->ourtag) {
3730 dialog->ourtag = newTag;
3731 newTag = NULL;
3734 just_joined = TRUE;
3736 } else {
3737 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3739 g_free(newTag);
3741 if (is_multiparty && !session->conv) {
3742 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3743 gchar *self = sip_uri_self(sip);
3744 /* create prpl chat */
3745 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3746 session->chat_name = g_strdup(chat_name);
3747 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
3748 /* add self */
3749 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3750 self, NULL,
3751 PURPLE_CBFLAGS_NONE, FALSE);
3752 g_free(chat_name);
3753 g_free(self);
3756 if (is_multiparty && !was_multiparty) {
3757 /* add current IM counterparty to chat */
3758 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3759 sipe_dialog_first(session)->with, NULL,
3760 PURPLE_CBFLAGS_NONE, FALSE);
3763 /* add inviting party to chat */
3764 if (just_joined && session->conv) {
3765 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3766 from, NULL,
3767 PURPLE_CBFLAGS_NONE, TRUE);
3770 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3771 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3772 if (ms_text_format) {
3773 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3775 gchar *html = get_html_message(ms_text_format, NULL);
3776 if (html) {
3777 if (is_multiparty) {
3778 serv_got_chat_in(sip->gc, session->chat_id, from,
3779 PURPLE_MESSAGE_RECV, html, time(NULL));
3780 } else {
3781 serv_got_im(sip->gc, from, html, 0, time(NULL));
3783 g_free(html);
3784 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3788 g_free(from);
3790 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3791 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3792 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3794 body = g_strdup_printf(
3795 "v=0\r\n"
3796 "o=- 0 0 IN IP4 %s\r\n"
3797 "s=session\r\n"
3798 "c=IN IP4 %s\r\n"
3799 "t=0 0\r\n"
3800 "m=message %d sip sip:%s\r\n"
3801 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3802 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3803 sip->realport, sip->username);
3804 send_sip_response(sip->gc, msg, 200, "OK", body);
3805 g_free(body);
3808 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3810 gchar *body;
3812 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
3813 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3814 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3816 body = g_strdup_printf(
3817 "v=0\r\n"
3818 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3819 "s=session\r\n"
3820 "c=IN IP4 0.0.0.0\r\n"
3821 "t=0 0\r\n"
3822 "m=message %d sip sip:%s\r\n"
3823 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3824 sip->realport, sip->username);
3825 send_sip_response(sip->gc, msg, 200, "OK", body);
3826 g_free(body);
3829 static void sipe_connection_cleanup(struct sipe_account_data *);
3830 static void create_connection(struct sipe_account_data *, gchar *, int);
3832 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3834 gchar *tmp;
3835 const gchar *expires_header;
3836 int expires, i;
3837 GSList *hdr = msg->headers;
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 if (tmp) {
3874 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3875 fill_auth(tmp, &sip->registrar);
3878 if (!sip->reauthenticate_set) {
3879 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3880 guint reauth_timeout;
3881 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3882 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3883 reauth_timeout = sip->registrar.expires - 300;
3884 } else {
3885 /* NTLM: we have to reauthenticate as our security token expires
3886 after eight hours (be five minutes early) */
3887 reauth_timeout = (8 * 3600) - 300;
3889 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3890 g_free(action_name);
3891 sip->reauthenticate_set = TRUE;
3894 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3896 epid = get_epid(sip);
3897 uuid = generateUUIDfromEPID(epid);
3898 g_free(epid);
3900 // There can be multiple Contact headers (one per location where the user is logged in) so
3901 // make sure to only get the one for this uuid
3902 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3903 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3904 if (valid_contact) {
3905 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3906 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3907 g_free(valid_contact);
3908 break;
3909 } else {
3910 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3913 g_free(uuid);
3915 g_free(sip->contact);
3916 if(gruu) {
3917 sip->contact = g_strdup_printf("<%s>", gruu);
3918 g_free(gruu);
3919 } else {
3920 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3921 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);
3923 sip->msrtc_event_categories = FALSE;
3924 sip->batched_support = FALSE;
3926 while(hdr)
3928 elem = hdr->data;
3929 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3930 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3931 sip->msrtc_event_categories = TRUE;
3932 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3934 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3935 sip->batched_support = TRUE;
3936 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3939 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3940 gchar **caps = g_strsplit(elem->value,",",0);
3941 i = 0;
3942 while (caps[i]) {
3943 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3944 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3945 i++;
3947 g_strfreev(caps);
3949 hdr = g_slist_next(hdr);
3952 /* subscriptions */
3953 if (!sip->subscribed) { //do it just once, not every re-register
3955 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
3956 (GCompareFunc)g_ascii_strcasecmp)) {
3957 sipe_subscribe_roaming_contacts(sip);
3960 /* For 2007+ it does not make sence to subscribe to:
3961 * vnd-microsoft-roaming-ACL
3962 * vnd-microsoft-provisioning (not v2)
3963 * presence.wpending
3964 * These are for backward compatibility.
3966 if (sip->msrtc_event_categories)
3968 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
3969 (GCompareFunc)g_ascii_strcasecmp)) {
3970 sipe_subscribe_roaming_self(sip);
3972 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
3973 (GCompareFunc)g_ascii_strcasecmp)) {
3974 sipe_subscribe_roaming_provisioning_v2(sip);
3977 /* For 2005- servers */
3978 else
3980 //sipe_options_request(sip, sip->sipdomain);
3982 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
3983 (GCompareFunc)g_ascii_strcasecmp)) {
3984 sipe_subscribe_roaming_acl(sip);
3986 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
3987 (GCompareFunc)g_ascii_strcasecmp)) {
3988 sipe_subscribe_roaming_provisioning(sip);
3990 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
3991 (GCompareFunc)g_ascii_strcasecmp)) {
3992 sipe_subscribe_presence_wpending(sip, msg);
3995 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3996 sip->subscribed = TRUE;
3999 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
4000 "timeout=", ";", NULL);
4001 if (timeout != NULL) {
4002 sscanf(timeout, "%u", &sip->keepalive_timeout);
4003 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
4004 sip->keepalive_timeout);
4005 g_free(timeout);
4008 // Should we remove the transaction here?
4009 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
4010 transactions_remove(sip, tc);
4012 break;
4013 case 301:
4015 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
4017 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
4018 gchar **parts = g_strsplit(redirect + 4, ";", 0);
4019 gchar **tmp;
4020 gchar *hostname;
4021 int port = 0;
4022 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
4023 int i = 1;
4025 tmp = g_strsplit(parts[0], ":", 0);
4026 hostname = g_strdup(tmp[0]);
4027 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
4028 g_strfreev(tmp);
4030 while (parts[i]) {
4031 tmp = g_strsplit(parts[i], "=", 0);
4032 if (tmp[1]) {
4033 if (g_strcasecmp("transport", tmp[0]) == 0) {
4034 if (g_strcasecmp("tcp", tmp[1]) == 0) {
4035 transport = SIPE_TRANSPORT_TCP;
4036 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
4037 transport = SIPE_TRANSPORT_UDP;
4041 g_strfreev(tmp);
4042 i++;
4044 g_strfreev(parts);
4046 /* Close old connection */
4047 sipe_connection_cleanup(sip);
4049 /* Create new connection */
4050 sip->transport = transport;
4051 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4052 hostname, port, TRANSPORT_DESCRIPTOR);
4053 create_connection(sip, hostname, port);
4055 g_free(redirect);
4057 break;
4058 case 401:
4059 if (sip->registerstatus != 2) {
4060 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
4061 if (sip->registrar.retries > 3) {
4062 sip->gc->wants_to_die = TRUE;
4063 purple_connection_error(sip->gc, _("Wrong Password"));
4064 return TRUE;
4066 #ifdef USE_KERBEROS
4067 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4068 #endif
4069 tmp = sipmsg_find_auth_header(msg, "NTLM");
4070 #ifdef USE_KERBEROS
4071 } else {
4072 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4074 #endif
4075 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
4076 fill_auth(tmp, &sip->registrar);
4077 sip->registerstatus = 2;
4078 if (sip->account->disconnecting) {
4079 do_register_exp(sip, 0);
4080 } else {
4081 do_register(sip);
4084 break;
4085 case 403:
4087 gchar *warning = sipmsg_find_header(msg, "Warning");
4088 if (warning != NULL) {
4089 /* Example header:
4090 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4092 gchar **tmp = g_strsplit(warning, "\"", 0);
4093 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
4094 g_strfreev(tmp);
4095 } else {
4096 warning = g_strdup(_("You have been rejected by the server"));
4099 sip->gc->wants_to_die = TRUE;
4100 purple_connection_error(sip->gc, warning);
4101 g_free(warning);
4102 return TRUE;
4104 break;
4105 case 404:
4107 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4108 if (warning != NULL) {
4109 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4110 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
4111 g_free(reason);
4112 } else {
4113 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4116 sip->gc->wants_to_die = TRUE;
4117 purple_connection_error(sip->gc, warning);
4118 g_free(warning);
4119 return TRUE;
4121 break;
4122 case 503:
4124 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4125 if (warning != NULL) {
4126 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4127 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4128 g_free(reason);
4129 } else {
4130 warning = g_strdup(_("Service unavailable: no reason given"));
4133 sip->gc->wants_to_die = TRUE;
4134 purple_connection_error(sip->gc, warning);
4135 g_free(warning);
4136 return TRUE;
4138 break;
4140 return TRUE;
4143 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4145 const char *uri;
4146 xmlnode *xn_categories;
4147 xmlnode *xn_category;
4148 xmlnode *xn_node;
4149 const char *activity = NULL;
4151 xn_categories = xmlnode_from_str(data, len);
4152 uri = xmlnode_get_attrib(xn_categories, "uri");
4154 for (xn_category = xmlnode_get_child(xn_categories, "category");
4155 xn_category ;
4156 xn_category = xmlnode_get_next_twin(xn_category) )
4158 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4160 if (!strcmp(attrVar, "note"))
4162 if (uri) {
4163 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4165 if (sbuddy) {
4166 char *note;
4168 xn_node = xmlnode_get_child(xn_category, "note");
4169 if (!xn_node) continue;
4170 xn_node = xmlnode_get_child(xn_node, "body");
4171 if (!xn_node) continue;
4172 note = xmlnode_get_data(xn_node);
4173 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4174 g_free(sbuddy->annotation);
4175 sbuddy->annotation = NULL;
4176 if (note) sbuddy->annotation = g_strdup(note);
4177 g_free(note);
4182 else if(!strcmp(attrVar, "state"))
4184 char *data;
4185 int avail;
4186 xn_node = xmlnode_get_child(xn_category, "state");
4187 if (!xn_node) continue;
4188 xn_node = xmlnode_get_child(xn_node, "availability");
4189 if (!xn_node) continue;
4191 data = xmlnode_get_data(xn_node);
4192 avail = atoi(data);
4193 g_free(data);
4195 if (avail < 3000)
4196 activity = SIPE_STATUS_ID_UNKNOWN;
4197 else if (avail < 4500)
4198 activity = SIPE_STATUS_ID_AVAILABLE;
4199 else if (avail < 6000)
4200 activity = SIPE_STATUS_ID_BRB;
4201 else if (avail < 7500)
4202 activity = SIPE_STATUS_ID_ONPHONE;
4203 else if (avail < 9000)
4204 activity = SIPE_STATUS_ID_BUSY;
4205 else if (avail < 12000)
4206 activity = SIPE_STATUS_ID_DND;
4207 else if (avail < 18000)
4208 activity = SIPE_STATUS_ID_AWAY;
4209 else
4210 activity = SIPE_STATUS_ID_OFFLINE;
4213 if(activity) {
4214 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4215 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4218 xmlnode_free(xn_categories);
4221 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4223 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4224 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4225 payload->host = g_strdup(host);
4226 payload->buddies = server;
4227 sipe_subscribe_presence_batched_routed(sip, payload);
4228 sipe_subscribe_presence_batched_routed_free(payload);
4231 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4233 xmlnode *xn_list;
4234 xmlnode *xn_resource;
4235 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4236 g_free, NULL);
4237 GSList *server;
4238 gchar *host;
4240 xn_list = xmlnode_from_str(data, len);
4242 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4243 xn_resource;
4244 xn_resource = xmlnode_get_next_twin(xn_resource) )
4246 const char *uri, *state;
4247 xmlnode *xn_instance;
4249 xn_instance = xmlnode_get_child(xn_resource, "instance");
4250 if (!xn_instance) continue;
4252 uri = xmlnode_get_attrib(xn_resource, "uri");
4253 state = xmlnode_get_attrib(xn_instance, "state");
4254 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4256 if (strstr(state, "resubscribe")) {
4257 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4258 struct sipe_buddy *sbuddy;
4259 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4260 gchar *user = g_strdup(uri);
4261 host = g_strdup(poolFqdn);
4262 server = g_hash_table_lookup(servers, host);
4263 server = g_slist_append(server, user);
4264 g_hash_table_insert(servers, host, server);
4265 } else {
4266 sipe_subscribe_presence_single(sip, (void *) uri);
4268 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4269 if (sbuddy) {
4270 sbuddy->resubscribed = TRUE;
4275 /* Send out any deferred poolFqdn subscriptions */
4276 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4277 g_hash_table_destroy(servers);
4279 xmlnode_free(xn_list);
4282 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4284 const gchar *uri;
4285 gchar *getbasic;
4286 gchar *activity = NULL;
4287 xmlnode *pidf;
4288 xmlnode *basicstatus = NULL, *tuple, *status;
4289 gboolean isonline = FALSE;
4290 xmlnode *display_name_node;
4292 pidf = xmlnode_from_str(data, len);
4293 if (!pidf) {
4294 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4295 return;
4298 uri = xmlnode_get_attrib(pidf, "entity");
4300 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4302 if ((status = xmlnode_get_child(tuple, "status"))) {
4303 basicstatus = xmlnode_get_child(status, "basic");
4307 if (!basicstatus) {
4308 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4309 xmlnode_free(pidf);
4310 return;
4313 getbasic = xmlnode_get_data(basicstatus);
4314 if (!getbasic) {
4315 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4316 xmlnode_free(pidf);
4317 return;
4320 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4321 if (strstr(getbasic, "open")) {
4322 isonline = TRUE;
4324 g_free(getbasic);
4326 display_name_node = xmlnode_get_child(pidf, "display-name");
4327 // updating display name if alias was just URI
4328 if (display_name_node) {
4329 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4330 GSList *entry = buddies;
4331 PurpleBuddy *p_buddy;
4332 char * display_name = xmlnode_get_data(display_name_node);
4334 while (entry) {
4335 const char *server_alias;
4336 char *alias;
4338 p_buddy = entry->data;
4340 alias = (char *)purple_buddy_get_alias(p_buddy);
4341 alias = alias ? sip_uri_from_name(alias) : NULL;
4342 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4343 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4344 purple_blist_alias_buddy(p_buddy, display_name);
4346 g_free(alias);
4348 server_alias = purple_buddy_get_server_alias(p_buddy);
4349 if (display_name &&
4350 ( (server_alias && strcmp(display_name, server_alias))
4351 || !server_alias || strlen(server_alias) == 0 )
4353 purple_blist_server_alias_buddy(p_buddy, display_name);
4356 entry = entry->next;
4358 g_free(display_name);
4361 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4362 if ((status = xmlnode_get_child(tuple, "status"))) {
4363 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4364 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4365 activity = xmlnode_get_data(basicstatus);
4366 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4372 if (isonline) {
4373 const gchar * status_id = NULL;
4374 if (activity) {
4375 if (strstr(activity, "busy")) {
4376 status_id = SIPE_STATUS_ID_BUSY;
4377 } else if (strstr(activity, "away")) {
4378 status_id = SIPE_STATUS_ID_AWAY;
4382 if (!status_id) {
4383 status_id = SIPE_STATUS_ID_AVAILABLE;
4386 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4387 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4388 } else {
4389 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4392 g_free(activity);
4393 xmlnode_free(pidf);
4396 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4398 const char *availability;
4399 const char *activity;
4400 const char *display_name = NULL;
4401 const char *activity_name = NULL;
4402 const char *name;
4403 char *uri;
4404 int avl;
4405 int act;
4406 struct sipe_buddy *sbuddy;
4408 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4410 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4411 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4412 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4413 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4414 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4415 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4416 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4417 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4419 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4420 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4421 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4422 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4423 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4424 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4426 name = xmlnode_get_attrib(xn_presentity, "uri");
4427 uri = sip_uri_from_name(name);
4428 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4429 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4431 // updating display name if alias was just URI
4432 if (xn_display_name) {
4433 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4434 GSList *entry = buddies;
4435 PurpleBuddy *p_buddy;
4436 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4438 while (entry) {
4439 const char *email_str, *server_alias;
4441 p_buddy = entry->data;
4443 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4444 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4445 purple_blist_alias_buddy(p_buddy, display_name);
4448 server_alias = purple_buddy_get_server_alias(p_buddy);
4449 if (display_name &&
4450 ( (server_alias && strcmp(display_name, server_alias))
4451 || !server_alias || strlen(server_alias) == 0 )
4453 purple_blist_server_alias_buddy(p_buddy, display_name);
4456 if (email) {
4457 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4458 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4459 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4463 entry = entry->next;
4467 avl = atoi(availability);
4468 act = atoi(activity);
4470 if(sip->msrtc_event_categories){
4471 if (act == 100 && avl == 0)
4472 activity_name = SIPE_STATUS_ID_OFFLINE;
4473 else if (act == 100 && avl == 300)
4474 activity_name = SIPE_STATUS_ID_AWAY;
4475 else if (act == 300 && avl == 300)
4476 activity_name = SIPE_STATUS_ID_BRB;
4477 else if (act == 400 && avl == 300)
4478 activity_name = SIPE_STATUS_ID_AVAILABLE;
4479 else if (act == 500 && act == 300)
4480 activity_name = SIPE_STATUS_ID_ONPHONE;
4481 else if (act == 600 && avl == 300)
4482 activity_name = SIPE_STATUS_ID_BUSY;
4483 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4484 if(avail){ //Check for LegacyInterop elements
4485 avl = atoi(avail);
4486 if(avl == 18500)
4487 activity_name = SIPE_STATUS_ID_OFFLINE;
4488 else if (avl == 3500)
4489 activity_name = SIPE_STATUS_ID_AVAILABLE;
4490 else if (avl == 15500)
4491 activity_name = SIPE_STATUS_ID_AWAY;
4492 else if (avl == 6500)
4493 activity_name = SIPE_STATUS_ID_BUSY;
4494 else if (avl == 12500)
4495 activity_name = SIPE_STATUS_ID_BRB;
4500 if(activity_name == NULL){
4501 if (act <= 100)
4502 activity_name = SIPE_STATUS_ID_AWAY;
4503 else if (act <= 150)
4504 activity_name = SIPE_STATUS_ID_LUNCH;
4505 else if (act <= 300)
4506 activity_name = SIPE_STATUS_ID_BRB;
4507 else if (act <= 400)
4508 activity_name = SIPE_STATUS_ID_AVAILABLE;
4509 else if (act <= 500)
4510 activity_name = SIPE_STATUS_ID_ONPHONE;
4511 else if (act <= 600)
4512 activity_name = SIPE_STATUS_ID_BUSY;
4513 else
4514 activity_name = SIPE_STATUS_ID_AVAILABLE;
4516 if (avl == 0)
4517 activity_name = SIPE_STATUS_ID_OFFLINE;
4520 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4521 if (sbuddy)
4523 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4524 sbuddy->annotation = NULL;
4525 if (note) { sbuddy->annotation = g_strdup(note); }
4527 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4528 sbuddy->device_name = NULL;
4529 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4532 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4533 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4534 g_free(note);
4535 xmlnode_free(xn_presentity);
4536 g_free(uri);
4539 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4541 char *ctype = sipmsg_find_header(msg, "Content-Type");
4543 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4545 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4546 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4548 const char *content = msg->body;
4549 unsigned length = msg->bodylen;
4550 PurpleMimeDocument *mime = NULL;
4552 if (strstr(ctype, "multipart"))
4554 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4555 const char *content_type;
4556 GList* parts;
4557 mime = purple_mime_document_parse(doc);
4558 parts = purple_mime_document_get_parts(mime);
4559 while(parts) {
4560 content = purple_mime_part_get_data(parts->data);
4561 length = purple_mime_part_get_length(parts->data);
4562 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4563 if(content_type && strstr(content_type,"application/rlmi+xml"))
4565 process_incoming_notify_rlmi_resub(sip, content, length);
4567 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4569 process_incoming_notify_msrtc(sip, content, length);
4571 else
4573 process_incoming_notify_rlmi(sip, content, length);
4575 parts = parts->next;
4577 g_free(doc);
4579 if (mime)
4581 purple_mime_document_free(mime);
4584 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4586 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4588 else if(strstr(ctype, "application/rlmi+xml"))
4590 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4593 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4595 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4597 else
4599 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4603 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4605 char *ctype = sipmsg_find_header(msg, "Content-Type");
4606 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4608 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4610 if (ctype &&
4611 strstr(ctype, "multipart") &&
4612 (strstr(ctype, "application/rlmi+xml") ||
4613 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4614 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4615 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4616 GList *parts = purple_mime_document_get_parts(mime);
4617 GSList *buddies = NULL;
4618 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4620 while (parts) {
4621 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4622 purple_mime_part_get_length(parts->data));
4623 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
4625 buddies = g_slist_append(buddies, uri);
4626 xmlnode_free(xml);
4628 parts = parts->next;
4630 g_free(doc);
4631 if (mime) purple_mime_document_free(mime);
4633 payload->host = who;
4634 payload->buddies = buddies;
4635 sipe_schedule_action(action_name, timeout,
4636 sipe_subscribe_presence_batched_routed,
4637 sipe_subscribe_presence_batched_routed_free,
4638 sip, payload);
4639 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4641 } else {
4642 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4643 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4645 g_free(action_name);
4649 * Dispatcher for all incoming subscription information
4650 * whether it comes from NOTIFY, BENOTIFY requests or
4651 * piggy-backed to subscription's OK responce.
4653 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4654 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4656 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4658 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4659 gchar *event = sipmsg_find_header(msg, "Event");
4660 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4661 int timeout = 0;
4663 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4664 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n\n", subscription_state ? subscription_state : "");
4666 /* implicit subscriptions */
4667 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4668 sipe_process_imdn(sip, msg);
4671 if (!request)
4673 const gchar *expires_header;
4674 expires_header = sipmsg_find_header(msg, "Expires");
4675 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4676 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4677 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4680 /* for one off subscriptions (send with Expire: 0) */
4681 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
4683 sipe_process_provisioning_v2(sip, msg);
4686 if (!subscription_state || strstr(subscription_state, "active"))
4688 if (event && !g_ascii_strcasecmp(event, "presence"))
4690 sipe_process_presence(sip, msg);
4692 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4694 sipe_process_roaming_contacts(sip, msg);
4696 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
4698 sipe_process_roaming_self(sip, msg);
4700 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4702 sipe_process_roaming_acl(sip, msg);
4704 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4706 sipe_process_presence_wpending(sip, msg);
4708 else if (event && !g_ascii_strcasecmp(event, "conference"))
4710 sipe_process_conference(sip, msg);
4714 //The server sends a (BE)NOTIFY with the status 'terminated'
4715 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4716 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4717 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4718 g_free(from);
4721 if (timeout && event) {// For LSC 2005 and OCS 2007
4722 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4723 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4725 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4726 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4727 g_free(action_name);
4729 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4730 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4732 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4733 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4734 g_free(action_name);
4736 else*/
4737 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4738 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4740 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4741 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4742 g_free(action_name);
4744 else if (!g_ascii_strcasecmp(event, "presence") &&
4745 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4747 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4748 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4749 if(sip->batched_support) {
4750 gchar *my_self = sip_uri_self(sip);
4751 if(!g_ascii_strcasecmp(who, my_self)){
4752 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4753 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4754 g_free(who); /* unused */
4756 else {
4757 sipe_process_presence_timeout(sip, msg, who, timeout);
4759 g_free(my_self);
4761 else {
4762 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, who);
4763 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4765 g_free(action_name);
4766 /* "who" will be freed by the action we just scheduled */
4770 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4772 sipe_process_registration_notify(sip, msg);
4775 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4776 if (request && !benotify)
4778 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4783 * unused. Needed?
4785 static gchar* gen_xpidf(struct sipe_account_data *sip)
4787 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4788 "<presence>\r\n"
4789 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4790 "<display name=\"sip:%s\"/>\r\n"
4791 "<atom id=\"1234\">\r\n"
4792 "<address uri=\"sip:%s\">\r\n"
4793 "<status status=\"%s\"/>\r\n"
4794 "</address>\r\n"
4795 "</atom>\r\n"
4796 "</presence>\r\n",
4797 sip->username,
4798 sip->username,
4799 sip->username,
4800 sip->status);
4801 return doc;
4806 static gchar* gen_pidf(struct sipe_account_data *sip)
4808 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4809 "<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"
4810 "<tuple id=\"0\">\r\n"
4811 "<status>\r\n"
4812 "<basic>open</basic>\r\n"
4813 "<ep:activities>\r\n"
4814 " <ep:activity>%s</ep:activity>\r\n"
4815 "</ep:activities>"
4816 "</status>\r\n"
4817 "</tuple>\r\n"
4818 "<ci:display-name>%s</ci:display-name>\r\n"
4819 "</presence>",
4820 sip->username,
4821 sip->status,
4822 sip->username);
4823 return doc;
4827 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4829 int availability = 300; // online
4830 int activity = 400; // Available
4831 gchar *name;
4832 gchar *body;
4833 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4834 activity = 100;
4835 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4836 activity = 150;
4837 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4838 activity = 300;
4839 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4840 activity = 400;
4841 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4842 activity = 500;
4843 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4844 activity = 600;
4845 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4846 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4847 availability = 0; // offline
4848 activity = 100;
4849 } else {
4850 activity = 400; // available
4853 name = g_strdup_printf("sip: sip:%s", sip->username);
4854 //@TODO: send user data - state; add hostname in upper case
4855 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4856 send_soap_request_with_cb(sip, body, NULL , NULL);
4857 g_free(name);
4858 g_free(body);
4861 static gboolean
4862 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg,
4863 SIPE_UNUSED_PARAMETER struct transaction *tc)
4865 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4866 if (msg->response == 200) {
4867 sip->status_version = 0;
4868 send_presence_status(sip);
4870 return TRUE;
4873 static gboolean
4874 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg,
4875 SIPE_UNUSED_PARAMETER struct transaction *tc)
4877 if (msg->response == 409) {
4878 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4879 // TODO need to parse the version #'s?
4880 gchar *uri = sip_uri_self(sip);
4881 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4882 gchar *tmp;
4883 gchar *hdr;
4885 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4887 tmp = get_contact(sip);
4888 hdr = g_strdup_printf("Contact: %s\r\n"
4889 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4891 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4893 g_free(tmp);
4894 g_free(hdr);
4895 g_free(uri);
4896 g_free(doc);
4898 return TRUE;
4901 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4903 int code;
4904 gchar *uri;
4905 gchar *doc;
4906 gchar *tmp;
4907 gchar *hdr;
4908 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4909 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4910 code = 12000;
4911 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4912 code = 9000;
4913 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4914 code = 7500;
4915 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4916 code = 6000;
4917 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4918 code = 4500;
4919 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4920 code = 3000;
4921 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4922 code = 0;
4923 } else {
4924 // Offline or invisible
4925 code = 18000;
4928 uri = sip_uri_self(sip);
4929 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4930 sip->status_version, code,
4931 sip->status_version, code,
4932 sip->status_version, note ? note : "",
4933 sip->status_version, note ? note : "",
4934 sip->status_version, note ? note : ""
4936 sip->status_version++;
4938 tmp = get_contact(sip);
4939 hdr = g_strdup_printf("Contact: %s\r\n"
4940 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4942 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4944 g_free(tmp);
4945 g_free(hdr);
4946 g_free(uri);
4947 g_free(doc);
4950 static void send_presence_status(struct sipe_account_data *sip)
4952 PurpleStatus * status = purple_account_get_active_status(sip->account);
4953 const gchar *note;
4954 if (!status) return;
4956 note = purple_status_get_attr_string(status, "message");
4958 if(sip->msrtc_event_categories){
4959 send_presence_category_publish(sip, note);
4960 } else {
4961 send_presence_soap(sip, note);
4965 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4967 gboolean found = FALSE;
4968 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4969 if (msg->response == 0) { /* request */
4970 if (!strcmp(msg->method, "MESSAGE")) {
4971 process_incoming_message(sip, msg);
4972 found = TRUE;
4973 } else if (!strcmp(msg->method, "NOTIFY")) {
4974 purple_debug_info("sipe","send->process_incoming_notify\n");
4975 process_incoming_notify(sip, msg, TRUE, FALSE);
4976 found = TRUE;
4977 } else if (!strcmp(msg->method, "BENOTIFY")) {
4978 purple_debug_info("sipe","send->process_incoming_benotify\n");
4979 process_incoming_notify(sip, msg, TRUE, TRUE);
4980 found = TRUE;
4981 } else if (!strcmp(msg->method, "INVITE")) {
4982 process_incoming_invite(sip, msg);
4983 found = TRUE;
4984 } else if (!strcmp(msg->method, "REFER")) {
4985 process_incoming_refer(sip, msg);
4986 found = TRUE;
4987 } else if (!strcmp(msg->method, "OPTIONS")) {
4988 process_incoming_options(sip, msg);
4989 found = TRUE;
4990 } else if (!strcmp(msg->method, "INFO")) {
4991 process_incoming_info(sip, msg);
4992 found = TRUE;
4993 } else if (!strcmp(msg->method, "ACK")) {
4994 // ACK's don't need any response
4995 found = TRUE;
4996 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4997 // LCS 2005 sends us these - just respond 200 OK
4998 found = TRUE;
4999 send_sip_response(sip->gc, msg, 200, "OK", NULL);
5000 } else if (!strcmp(msg->method, "BYE")) {
5001 process_incoming_bye(sip, msg);
5002 found = TRUE;
5003 } else {
5004 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
5006 } else { /* response */
5007 struct transaction *trans = transactions_find(sip, msg);
5008 if (trans) {
5009 if (msg->response == 407) {
5010 gchar *resend, *auth, *ptmp;
5012 if (sip->proxy.retries > 30) return;
5013 sip->proxy.retries++;
5014 /* do proxy authentication */
5016 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
5018 fill_auth(ptmp, &sip->proxy);
5019 auth = auth_header(sip, &sip->proxy, trans->msg);
5020 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
5021 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
5022 g_free(auth);
5023 resend = sipmsg_to_string(trans->msg);
5024 /* resend request */
5025 sendout_pkt(sip->gc, resend);
5026 g_free(resend);
5027 } else {
5028 if (msg->response == 100 || msg->response == 180) {
5029 /* ignore provisional response */
5030 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
5031 } else {
5032 sip->proxy.retries = 0;
5033 if (!strcmp(trans->msg->method, "REGISTER")) {
5034 if (msg->response == 401)
5036 sip->registrar.retries++;
5038 else
5040 sip->registrar.retries = 0;
5042 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
5043 } else {
5044 if (msg->response == 401) {
5045 gchar *resend, *auth, *ptmp;
5047 if (sip->registrar.retries > 4) return;
5048 sip->registrar.retries++;
5050 #ifdef USE_KERBEROS
5051 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5052 #endif
5053 ptmp = sipmsg_find_auth_header(msg, "NTLM");
5054 #ifdef USE_KERBEROS
5055 } else {
5056 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
5058 #endif
5060 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
5062 fill_auth(ptmp, &sip->registrar);
5063 auth = auth_header(sip, &sip->registrar, trans->msg);
5064 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
5065 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
5067 //sipmsg_remove_header_now(trans->msg, "Authorization");
5068 //sipmsg_add_header(trans->msg, "Authorization", auth);
5069 g_free(auth);
5070 resend = sipmsg_to_string(trans->msg);
5071 /* resend request */
5072 sendout_pkt(sip->gc, resend);
5073 g_free(resend);
5077 if (trans->callback) {
5078 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
5079 /* call the callback to process response*/
5080 (trans->callback)(sip, msg, trans);
5082 /* Not sure if this is needed or what needs to be done
5083 but transactions seem to be removed prematurely so
5084 this only removes them if the response is 200 OK */
5085 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
5086 /*Has a bug and it's unneccesary*/
5087 /*transactions_remove(sip, trans);*/
5091 found = TRUE;
5092 } else {
5093 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
5096 if (!found) {
5097 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
5101 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
5103 char *cur;
5104 char *dummy;
5105 struct sipmsg *msg;
5106 int restlen;
5107 cur = conn->inbuf;
5109 /* according to the RFC remove CRLF at the beginning */
5110 while (*cur == '\r' || *cur == '\n') {
5111 cur++;
5113 if (cur != conn->inbuf) {
5114 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
5115 conn->inbufused = strlen(conn->inbuf);
5118 /* Received a full Header? */
5119 sip->processing_input = TRUE;
5120 while (sip->processing_input &&
5121 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
5122 time_t currtime = time(NULL);
5123 cur += 2;
5124 cur[0] = '\0';
5125 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
5126 msg = sipmsg_parse_header(conn->inbuf);
5127 cur[0] = '\r';
5128 cur += 2;
5129 restlen = conn->inbufused - (cur - conn->inbuf);
5130 if (restlen >= msg->bodylen) {
5131 dummy = g_malloc(msg->bodylen + 1);
5132 memcpy(dummy, cur, msg->bodylen);
5133 dummy[msg->bodylen] = '\0';
5134 msg->body = dummy;
5135 cur += msg->bodylen;
5136 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
5137 conn->inbufused = strlen(conn->inbuf);
5138 } else {
5139 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5140 restlen, msg->bodylen, (int)strlen(conn->inbuf));
5141 sipmsg_free(msg);
5142 return;
5145 /*if (msg->body) {
5146 purple_debug_info("sipe", "body:\n%s", msg->body);
5149 // Verify the signature before processing it
5150 if (sip->registrar.gssapi_context) {
5151 struct sipmsg_breakdown msgbd;
5152 gchar *signature_input_str;
5153 gchar *rspauth;
5154 msgbd.msg = msg;
5155 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5156 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5158 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5160 if (rspauth != NULL) {
5161 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5162 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5163 process_input_message(sip, msg);
5164 } else {
5165 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5166 purple_connection_error(sip->gc, _("Invalid message signature received"));
5167 sip->gc->wants_to_die = TRUE;
5169 } else if (msg->response == 401) {
5170 purple_connection_error(sip->gc, _("Wrong Password"));
5171 sip->gc->wants_to_die = TRUE;
5173 g_free(signature_input_str);
5175 g_free(rspauth);
5176 sipmsg_breakdown_free(&msgbd);
5177 } else {
5178 process_input_message(sip, msg);
5181 sipmsg_free(msg);
5185 static void sipe_udp_process(gpointer data, gint source,
5186 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
5188 PurpleConnection *gc = data;
5189 struct sipe_account_data *sip = gc->proto_data;
5190 struct sipmsg *msg;
5191 int len;
5192 time_t currtime;
5194 static char buffer[65536];
5195 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5196 buffer[len] = '\0';
5197 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5198 msg = sipmsg_parse_msg(buffer);
5199 if (msg) process_input_message(sip, msg);
5203 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5205 struct sipe_account_data *sip = gc->proto_data;
5206 PurpleSslConnection *gsc = sip->gsc;
5208 purple_debug_error("sipe", "%s",debug);
5209 purple_connection_error(gc, msg);
5211 /* Invalidate this connection. Next send will open a new one */
5212 if (gsc) {
5213 connection_remove(sip, gsc->fd);
5214 purple_ssl_close(gsc);
5216 sip->gsc = NULL;
5217 sip->fd = -1;
5220 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
5221 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5223 PurpleConnection *gc = data;
5224 struct sipe_account_data *sip;
5225 struct sip_connection *conn;
5226 int readlen, len;
5227 gboolean firstread = TRUE;
5229 /* NOTE: This check *IS* necessary */
5230 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5231 purple_ssl_close(gsc);
5232 return;
5235 sip = gc->proto_data;
5236 conn = connection_find(sip, gsc->fd);
5237 if (conn == NULL) {
5238 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5239 gc->wants_to_die = TRUE;
5240 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5241 return;
5244 /* Read all available data from the SSL connection */
5245 do {
5246 /* Increase input buffer size as needed */
5247 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5248 conn->inbuflen += SIMPLE_BUF_INC;
5249 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5250 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5253 /* Try to read as much as there is space left in the buffer */
5254 readlen = conn->inbuflen - conn->inbufused - 1;
5255 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5257 if (len < 0 && errno == EAGAIN) {
5258 /* Try again later */
5259 return;
5260 } else if (len < 0) {
5261 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5262 return;
5263 } else if (firstread && (len == 0)) {
5264 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5265 return;
5268 conn->inbufused += len;
5269 firstread = FALSE;
5271 /* Equivalence indicates that there is possibly more data to read */
5272 } while (len == readlen);
5274 conn->inbuf[conn->inbufused] = '\0';
5275 process_input(sip, conn);
5279 static void sipe_input_cb(gpointer data, gint source,
5280 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5282 PurpleConnection *gc = data;
5283 struct sipe_account_data *sip = gc->proto_data;
5284 int len;
5285 struct sip_connection *conn = connection_find(sip, source);
5286 if (!conn) {
5287 purple_debug_error("sipe", "Connection not found!\n");
5288 return;
5291 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5292 conn->inbuflen += SIMPLE_BUF_INC;
5293 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5296 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5298 if (len < 0 && errno == EAGAIN)
5299 return;
5300 else if (len <= 0) {
5301 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5302 connection_remove(sip, source);
5303 if (sip->fd == source) sip->fd = -1;
5304 return;
5307 conn->inbufused += len;
5308 conn->inbuf[conn->inbufused] = '\0';
5310 process_input(sip, conn);
5313 /* Callback for new connections on incoming TCP port */
5314 static void sipe_newconn_cb(gpointer data, gint source,
5315 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5317 PurpleConnection *gc = data;
5318 struct sipe_account_data *sip = gc->proto_data;
5319 struct sip_connection *conn;
5321 int newfd = accept(source, NULL, NULL);
5323 conn = connection_create(sip, newfd);
5325 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5328 static void login_cb(gpointer data, gint source,
5329 SIPE_UNUSED_PARAMETER const gchar *error_message)
5331 PurpleConnection *gc = data;
5332 struct sipe_account_data *sip;
5333 struct sip_connection *conn;
5335 if (!PURPLE_CONNECTION_IS_VALID(gc))
5337 if (source >= 0)
5338 close(source);
5339 return;
5342 if (source < 0) {
5343 purple_connection_error(gc, _("Could not connect"));
5344 return;
5347 sip = gc->proto_data;
5348 sip->fd = source;
5349 sip->last_keepalive = time(NULL);
5351 conn = connection_create(sip, source);
5353 do_register(sip);
5355 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5358 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
5359 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5361 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5362 if (sip == NULL) return;
5364 do_register(sip);
5367 static guint sipe_ht_hash_nick(const char *nick)
5369 char *lc = g_utf8_strdown(nick, -1);
5370 guint bucket = g_str_hash(lc);
5371 g_free(lc);
5373 return bucket;
5376 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5378 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5381 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5383 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5385 sip->listen_data = NULL;
5387 if (listenfd == -1) {
5388 purple_connection_error(sip->gc, _("Could not create listen socket"));
5389 return;
5392 sip->fd = listenfd;
5394 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5395 sip->listenfd = sip->fd;
5397 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5399 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5400 do_register(sip);
5403 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
5404 SIPE_UNUSED_PARAMETER const char *error_message)
5406 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5408 sip->query_data = NULL;
5410 if (!hosts || !hosts->data) {
5411 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5412 return;
5415 hosts = g_slist_remove(hosts, hosts->data);
5416 g_free(sip->serveraddr);
5417 sip->serveraddr = hosts->data;
5418 hosts = g_slist_remove(hosts, hosts->data);
5419 while (hosts) {
5420 hosts = g_slist_remove(hosts, hosts->data);
5421 g_free(hosts->data);
5422 hosts = g_slist_remove(hosts, hosts->data);
5425 /* create socket for incoming connections */
5426 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5427 sipe_udp_host_resolved_listen_cb, sip);
5428 if (sip->listen_data == NULL) {
5429 purple_connection_error(sip->gc, _("Could not create listen socket"));
5430 return;
5434 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
5435 PurpleSslErrorType error,
5436 gpointer data)
5438 PurpleConnection *gc = data;
5439 struct sipe_account_data *sip;
5441 /* If the connection is already disconnected, we don't need to do anything else */
5442 if (!PURPLE_CONNECTION_IS_VALID(gc))
5443 return;
5445 sip = gc->proto_data;
5446 sip->fd = -1;
5447 sip->gsc = NULL;
5449 switch(error) {
5450 case PURPLE_SSL_CONNECT_FAILED:
5451 purple_connection_error(gc, _("Connection Failed"));
5452 break;
5453 case PURPLE_SSL_HANDSHAKE_FAILED:
5454 purple_connection_error(gc, _("SSL Handshake Failed"));
5455 break;
5456 case PURPLE_SSL_CERTIFICATE_INVALID:
5457 purple_connection_error(gc, _("SSL Certificate Invalid"));
5458 break;
5462 static void
5463 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5465 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5466 PurpleProxyConnectData *connect_data;
5468 sip->listen_data = NULL;
5470 sip->listenfd = listenfd;
5471 if (sip->listenfd == -1) {
5472 purple_connection_error(sip->gc, _("Could not create listen socket"));
5473 return;
5476 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5477 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5478 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5479 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5480 sipe_newconn_cb, sip->gc);
5481 purple_debug_info("sipe", "connecting to %s port %d\n",
5482 sip->realhostname, sip->realport);
5483 /* open tcp connection to the server */
5484 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5485 sip->realport, login_cb, sip->gc);
5487 if (connect_data == NULL) {
5488 purple_connection_error(sip->gc, _("Couldn't create socket"));
5492 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5494 PurpleAccount *account = sip->account;
5495 PurpleConnection *gc = sip->gc;
5497 if (port == 0) {
5498 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5501 sip->realhostname = hostname;
5502 sip->realport = port;
5504 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5505 hostname, port);
5507 /* TODO: is there a good default grow size? */
5508 if (sip->transport != SIPE_TRANSPORT_UDP)
5509 sip->txbuf = purple_circ_buffer_new(0);
5511 if (sip->transport == SIPE_TRANSPORT_TLS) {
5512 /* SSL case */
5513 if (!purple_ssl_is_supported()) {
5514 gc->wants_to_die = TRUE;
5515 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5516 return;
5519 purple_debug_info("sipe", "using SSL\n");
5521 sip->gsc = purple_ssl_connect(account, hostname, port,
5522 login_cb_ssl, sipe_ssl_connect_failure, gc);
5523 if (sip->gsc == NULL) {
5524 purple_connection_error(gc, _("Could not create SSL context"));
5525 return;
5527 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5528 /* UDP case */
5529 purple_debug_info("sipe", "using UDP\n");
5531 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5532 if (sip->query_data == NULL) {
5533 purple_connection_error(gc, _("Could not resolve hostname"));
5535 } else {
5536 /* TCP case */
5537 purple_debug_info("sipe", "using TCP\n");
5538 /* create socket for incoming connections */
5539 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5540 sipe_tcp_connect_listen_cb, sip);
5541 if (sip->listen_data == NULL) {
5542 purple_connection_error(gc, _("Could not create listen socket"));
5543 return;
5548 /* Service list for autodection */
5549 static const struct sipe_service_data service_autodetect[] = {
5550 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5551 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5552 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5553 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5554 { NULL, NULL, 0 }
5557 /* Service list for SSL/TLS */
5558 static const struct sipe_service_data service_tls[] = {
5559 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5560 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5561 { NULL, NULL, 0 }
5564 /* Service list for TCP */
5565 static const struct sipe_service_data service_tcp[] = {
5566 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5567 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5568 { NULL, NULL, 0 }
5571 /* Service list for UDP */
5572 static const struct sipe_service_data service_udp[] = {
5573 { "sip", "udp", SIPE_TRANSPORT_UDP },
5574 { NULL, NULL, 0 }
5577 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5578 static void resolve_next_service(struct sipe_account_data *sip,
5579 const struct sipe_service_data *start)
5581 if (start) {
5582 sip->service_data = start;
5583 } else {
5584 sip->service_data++;
5585 if (sip->service_data->service == NULL) {
5586 gchar *hostname;
5587 /* Try connecting to the SIP hostname directly */
5588 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5589 if (sip->auto_transport) {
5590 // If SSL is supported, default to using it; OCS servers aren't configured
5591 // by default to accept TCP
5592 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5593 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5594 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5597 hostname = g_strdup(sip->sipdomain);
5598 create_connection(sip, hostname, 0);
5599 return;
5603 /* Try to resolve next service */
5604 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5605 sip->service_data->transport,
5606 sip->sipdomain,
5607 srvresolved, sip);
5610 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5612 struct sipe_account_data *sip = data;
5614 sip->srv_query_data = NULL;
5616 /* find the host to connect to */
5617 if (results) {
5618 gchar *hostname = g_strdup(resp->hostname);
5619 int port = resp->port;
5620 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5621 hostname, port);
5622 g_free(resp);
5624 sip->transport = sip->service_data->type;
5626 create_connection(sip, hostname, port);
5627 } else {
5628 resolve_next_service(sip, NULL);
5632 static void sipe_login(PurpleAccount *account)
5634 PurpleConnection *gc;
5635 struct sipe_account_data *sip;
5636 gchar **signinname_login, **userserver;
5637 const char *transport;
5639 const char *username = purple_account_get_username(account);
5640 gc = purple_account_get_connection(account);
5642 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
5644 if (strpbrk(username, "\t\v\r\n") != NULL) {
5645 gc->wants_to_die = TRUE;
5646 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5647 return;
5650 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5651 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5652 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5653 sip->gc = gc;
5654 sip->account = account;
5655 sip->reregister_set = FALSE;
5656 sip->reauthenticate_set = FALSE;
5657 sip->subscribed = FALSE;
5658 sip->subscribed_buddies = FALSE;
5660 /* username format: <username>,[<optional login>] */
5661 signinname_login = g_strsplit(username, ",", 2);
5662 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
5664 /* ensure that username format is name@domain */
5665 if (!strchr(signinname_login[0], '@') || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
5666 g_strfreev(signinname_login);
5667 gc->wants_to_die = TRUE;
5668 purple_connection_error(gc, _("Username should be valid SIP URI\nExample: user@company.com"));
5669 return;
5671 sip->username = g_strdup(signinname_login[0]);
5673 /* login name specified? */
5674 if (signinname_login[1] && strlen(signinname_login[1])) {
5675 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
5676 gboolean has_domain = domain_user[1] != NULL;
5677 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
5678 sip->authdomain = has_domain ? g_strdup(domain_user[0]) : NULL;
5679 sip->authuser = g_strdup(domain_user[has_domain ? 1 : 0]);
5680 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
5681 sip->authdomain ? sip->authdomain : "", sip->authuser);
5682 g_strfreev(domain_user);
5685 userserver = g_strsplit(signinname_login[0], "@", 2);
5686 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
5687 purple_connection_set_display_name(gc, userserver[0]);
5688 sip->sipdomain = g_strdup(userserver[1]);
5689 g_strfreev(userserver);
5690 g_strfreev(signinname_login);
5692 if (strchr(sip->username, ' ') != NULL) {
5693 gc->wants_to_die = TRUE;
5694 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5695 return;
5698 sip->password = g_strdup(purple_connection_get_password(gc));
5700 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5702 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5704 /* TODO: Set the status correctly. */
5705 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5707 sip->auto_transport = FALSE;
5708 transport = purple_account_get_string(account, "transport", "auto");
5709 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
5710 if (userserver[0]) {
5711 /* Use user specified server[:port] */
5712 int port = 0;
5714 if (userserver[1])
5715 port = atoi(userserver[1]);
5717 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
5718 userserver[0], port);
5720 if (strcmp(transport, "auto") == 0) {
5721 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5722 } else if (strcmp(transport, "tls") == 0) {
5723 sip->transport = SIPE_TRANSPORT_TLS;
5724 } else if (strcmp(transport, "tcp") == 0) {
5725 sip->transport = SIPE_TRANSPORT_TCP;
5726 } else {
5727 sip->transport = SIPE_TRANSPORT_UDP;
5730 create_connection(sip, g_strdup(userserver[0]), port);
5731 } else {
5732 /* Server auto-discovery */
5733 if (strcmp(transport, "auto") == 0) {
5734 sip->auto_transport = TRUE;
5735 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5736 } else if (strcmp(transport, "tls") == 0) {
5737 resolve_next_service(sip, service_tls);
5738 } else if (strcmp(transport, "tcp") == 0) {
5739 resolve_next_service(sip, service_tcp);
5740 } else {
5741 resolve_next_service(sip, service_udp);
5744 g_strfreev(userserver);
5747 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5749 connection_free_all(sip);
5751 g_free(sip->epid);
5752 sip->epid = NULL;
5754 if (sip->query_data != NULL)
5755 purple_dnsquery_destroy(sip->query_data);
5756 sip->query_data = NULL;
5758 if (sip->srv_query_data != NULL)
5759 purple_srv_cancel(sip->srv_query_data);
5760 sip->srv_query_data = NULL;
5762 if (sip->listen_data != NULL)
5763 purple_network_listen_cancel(sip->listen_data);
5764 sip->listen_data = NULL;
5766 if (sip->gsc != NULL)
5767 purple_ssl_close(sip->gsc);
5768 sip->gsc = NULL;
5770 sipe_auth_free(&sip->registrar);
5771 sipe_auth_free(&sip->proxy);
5773 if (sip->txbuf)
5774 purple_circ_buffer_destroy(sip->txbuf);
5775 sip->txbuf = NULL;
5777 g_free(sip->realhostname);
5778 sip->realhostname = NULL;
5780 if (sip->listenpa)
5781 purple_input_remove(sip->listenpa);
5782 sip->listenpa = 0;
5783 if (sip->tx_handler)
5784 purple_input_remove(sip->tx_handler);
5785 sip->tx_handler = 0;
5786 if (sip->resendtimeout)
5787 purple_timeout_remove(sip->resendtimeout);
5788 sip->resendtimeout = 0;
5789 if (sip->timeouts) {
5790 GSList *entry = sip->timeouts;
5791 while (entry) {
5792 struct scheduled_action *sched_action = entry->data;
5793 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5794 purple_timeout_remove(sched_action->timeout_handler);
5795 if (sched_action->destroy) {
5796 (*sched_action->destroy)(sched_action->payload);
5798 g_free(sched_action->name);
5799 g_free(sched_action);
5800 entry = entry->next;
5803 g_slist_free(sip->timeouts);
5805 if (sip->allow_events) {
5806 GSList *entry = sip->allow_events;
5807 while (entry) {
5808 g_free(entry->data);
5809 entry = entry->next;
5812 g_slist_free(sip->allow_events);
5814 if (sip->containers) {
5815 GSList *entry = sip->containers;
5816 while (entry) {
5817 free_container((struct sipe_container *)entry->data);
5818 entry = entry->next;
5821 g_slist_free(sip->containers);
5823 if (sip->contact)
5824 g_free(sip->contact);
5825 sip->contact = NULL;
5826 if (sip->regcallid)
5827 g_free(sip->regcallid);
5828 sip->regcallid = NULL;
5830 if (sip->serveraddr)
5831 g_free(sip->serveraddr);
5832 sip->serveraddr = NULL;
5834 if (sip->focus_factory_uri)
5835 g_free(sip->focus_factory_uri);
5836 sip->focus_factory_uri = NULL;
5838 sip->fd = -1;
5839 sip->processing_input = FALSE;
5843 * A callback for g_hash_table_foreach_remove
5845 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5846 SIPE_UNUSED_PARAMETER gpointer user_data)
5848 sipe_free_buddy((struct sipe_buddy *) buddy);
5850 /* We must return TRUE as the key/value have already been deleted */
5851 return(TRUE);
5854 static void sipe_close(PurpleConnection *gc)
5856 struct sipe_account_data *sip = gc->proto_data;
5858 if (sip) {
5859 /* leave all conversations */
5860 sipe_session_close_all(sip);
5861 sipe_session_remove_all(sip);
5863 /* unregister */
5864 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
5865 do_register_exp(sip, 0);
5868 sipe_connection_cleanup(sip);
5869 g_free(sip->sipdomain);
5870 g_free(sip->username);
5871 g_free(sip->password);
5872 g_free(sip->authdomain);
5873 g_free(sip->authuser);
5874 g_free(sip->status);
5876 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
5877 g_hash_table_destroy(sip->buddies);
5878 g_hash_table_destroy(sip->our_publications);
5880 if (sip->our_publication_keys) {
5881 GSList *entry = sip->our_publication_keys;
5882 while (entry) {
5883 g_free(entry->data);
5884 entry = entry->next;
5887 g_slist_free(sip->our_publication_keys);
5889 g_free(gc->proto_data);
5890 gc->proto_data = NULL;
5893 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5894 SIPE_UNUSED_PARAMETER void *user_data)
5896 PurpleAccount *acct = purple_connection_get_account(gc);
5897 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5898 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5899 if (conv == NULL)
5900 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5901 purple_conversation_present(conv);
5902 g_free(id);
5905 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5906 SIPE_UNUSED_PARAMETER void *user_data)
5909 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5910 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5913 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
5914 SIPE_UNUSED_PARAMETER struct transaction *tc)
5916 PurpleNotifySearchResults *results;
5917 PurpleNotifySearchColumn *column;
5918 xmlnode *searchResults;
5919 xmlnode *mrow;
5920 int match_count = 0;
5921 gboolean more = FALSE;
5922 gchar *secondary;
5924 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5926 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5927 if (!searchResults) {
5928 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5929 return FALSE;
5932 results = purple_notify_searchresults_new();
5934 if (results == NULL) {
5935 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5936 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5938 xmlnode_free(searchResults);
5939 return FALSE;
5942 column = purple_notify_searchresults_column_new(_("User Name"));
5943 purple_notify_searchresults_column_add(results, column);
5945 column = purple_notify_searchresults_column_new(_("Name"));
5946 purple_notify_searchresults_column_add(results, column);
5948 column = purple_notify_searchresults_column_new(_("Company"));
5949 purple_notify_searchresults_column_add(results, column);
5951 column = purple_notify_searchresults_column_new(_("Country"));
5952 purple_notify_searchresults_column_add(results, column);
5954 column = purple_notify_searchresults_column_new(_("Email"));
5955 purple_notify_searchresults_column_add(results, column);
5957 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5958 GList *row = NULL;
5960 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5961 row = g_list_append(row, g_strdup(uri_parts[1]));
5962 g_strfreev(uri_parts);
5964 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5965 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5966 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5967 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5969 purple_notify_searchresults_row_add(results, row);
5970 match_count++;
5973 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5974 char *data = xmlnode_get_data_unescaped(mrow);
5975 more = (g_strcasecmp(data, "true") == 0);
5976 g_free(data);
5979 secondary = g_strdup_printf(
5980 dngettext(GETTEXT_PACKAGE,
5981 "Found %d contact%s:",
5982 "Found %d contacts%s:", match_count),
5983 match_count, more ? _(" (more matched your query)") : "");
5985 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5986 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5987 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5989 g_free(secondary);
5990 xmlnode_free(searchResults);
5991 return TRUE;
5994 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5996 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5997 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5998 unsigned i = 0;
6000 do {
6001 PurpleRequestField *field = entries->data;
6002 const char *id = purple_request_field_get_id(field);
6003 const char *value = purple_request_field_string_get_value(field);
6005 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
6007 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
6008 } while ((entries = g_list_next(entries)) != NULL);
6009 attrs[i] = NULL;
6011 if (i > 0) {
6012 gchar *query = g_strjoinv(NULL, attrs);
6013 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
6014 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
6015 send_soap_request_with_cb(gc->proto_data, body,
6016 (TransCallback) process_search_contact_response, NULL);
6017 g_free(body);
6018 g_free(query);
6021 g_strfreev(attrs);
6024 static void sipe_show_find_contact(PurplePluginAction *action)
6026 PurpleConnection *gc = (PurpleConnection *) action->context;
6027 PurpleRequestFields *fields;
6028 PurpleRequestFieldGroup *group;
6029 PurpleRequestField *field;
6031 fields = purple_request_fields_new();
6032 group = purple_request_field_group_new(NULL);
6033 purple_request_fields_add_group(fields, group);
6035 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
6036 purple_request_field_group_add_field(group, field);
6037 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
6038 purple_request_field_group_add_field(group, field);
6039 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
6040 purple_request_field_group_add_field(group, field);
6041 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
6042 purple_request_field_group_add_field(group, field);
6044 purple_request_fields(gc,
6045 _("Search"),
6046 _("Search for a Contact"),
6047 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
6048 fields,
6049 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
6050 _("_Cancel"), NULL,
6051 purple_connection_get_account(gc), NULL, NULL, gc);
6054 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
6055 SIPE_UNUSED_PARAMETER gpointer context)
6057 GList *menu = NULL;
6058 PurplePluginAction *act;
6060 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
6061 menu = g_list_prepend(menu, act);
6063 menu = g_list_reverse(menu);
6065 return menu;
6068 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
6072 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
6074 return TRUE;
6078 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
6080 return TRUE;
6084 static char *sipe_status_text(PurpleBuddy *buddy)
6086 struct sipe_account_data *sip;
6087 struct sipe_buddy *sbuddy;
6088 char *text = NULL;
6090 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
6091 if (sip) //happens on pidgin exit
6093 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
6094 if (sbuddy && sbuddy->annotation)
6096 text = g_strdup(sbuddy->annotation);
6100 return text;
6103 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
6105 const PurplePresence *presence = purple_buddy_get_presence(buddy);
6106 const PurpleStatus *status = purple_presence_get_active_status(presence);
6107 struct sipe_account_data *sip;
6108 struct sipe_buddy *sbuddy;
6109 char *annotation = NULL;
6111 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
6112 if (sip) //happens on pidgin exit
6114 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
6115 if (sbuddy)
6117 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
6121 //Layout
6122 if (purple_presence_is_online(presence))
6124 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
6127 if (annotation)
6129 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
6130 g_free(annotation);
6135 static GHashTable *
6136 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
6138 GHashTable *table;
6139 table = g_hash_table_new(g_str_hash, g_str_equal);
6140 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
6141 return table;
6144 static PurpleBuddy *
6145 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6147 PurpleBuddy *clone;
6148 const gchar *server_alias, *email;
6149 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6151 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6153 purple_blist_add_buddy(clone, NULL, group, NULL);
6155 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
6156 if (server_alias) {
6157 purple_blist_server_alias_buddy(clone, server_alias);
6160 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6161 if (email) {
6162 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
6165 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6166 //for UI to update;
6167 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6168 return clone;
6171 static void
6172 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6174 PurpleBuddy *buddy, *b;
6175 PurpleConnection *gc;
6176 PurpleGroup * group = purple_find_group(group_name);
6178 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6180 buddy = (PurpleBuddy *)node;
6182 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
6183 gc = purple_account_get_connection(buddy->account);
6185 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6186 if (!b){
6187 b = purple_blist_add_buddy_clone(group, buddy);
6190 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6193 static void
6194 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6196 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6198 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6200 /* 2007+ conference */
6201 if (sip->msrtc_event_categories)
6203 sipe_conf_add(sip, buddy->name);
6205 else /* 2005- multiparty chat */
6207 gchar *self = sip_uri_self(sip);
6208 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
6209 struct sip_session *session;
6211 session = sipe_session_add_chat(sip);
6212 session->roster_manager = g_strdup(self);
6214 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6215 session->chat_name = g_strdup(chat_name);
6216 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
6217 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6218 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6220 g_free(chat_name);
6221 g_free(self);
6225 static gboolean
6226 sipe_is_election_finished(struct sip_session *session)
6228 gboolean res = TRUE;
6230 SIPE_DIALOG_FOREACH {
6231 if (dialog->election_vote == 0) {
6232 res = FALSE;
6233 break;
6235 } SIPE_DIALOG_FOREACH_END;
6237 if (res) {
6238 session->is_voting_in_progress = FALSE;
6240 return res;
6243 static void
6244 sipe_election_start(struct sipe_account_data *sip,
6245 struct sip_session *session)
6247 int election_timeout;
6249 if (session->is_voting_in_progress) {
6250 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6251 return;
6252 } else {
6253 session->is_voting_in_progress = TRUE;
6255 session->bid = rand();
6257 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6259 SIPE_DIALOG_FOREACH {
6260 /* reset election_vote for each chat participant */
6261 dialog->election_vote = 0;
6263 /* send RequestRM to each chat participant*/
6264 sipe_send_election_request_rm(sip, dialog, session->bid);
6265 } SIPE_DIALOG_FOREACH_END;
6267 election_timeout = 15; /* sec */
6268 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6272 * @param who a URI to whom to invite to chat
6274 void
6275 sipe_invite_to_chat(struct sipe_account_data *sip,
6276 struct sip_session *session,
6277 const gchar *who)
6279 /* a conference */
6280 if (session->focus_uri)
6282 sipe_invite_conf(sip, session, who);
6284 else /* a multi-party chat */
6286 gchar *self = sip_uri_self(sip);
6287 if (session->roster_manager) {
6288 if (!strcmp(session->roster_manager, self)) {
6289 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6290 } else {
6291 sipe_refer(sip, session, who);
6293 } else {
6294 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6296 session->pending_invite_queue = slist_insert_unique_sorted(
6297 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6299 sipe_election_start(sip, session);
6301 g_free(self);
6305 void
6306 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6307 struct sip_session *session)
6309 gchar *invitee;
6310 GSList *entry = session->pending_invite_queue;
6312 while (entry) {
6313 invitee = entry->data;
6314 sipe_invite_to_chat(sip, session, invitee);
6315 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6316 g_free(invitee);
6320 static void
6321 sipe_election_result(struct sipe_account_data *sip,
6322 void *sess)
6324 struct sip_session *session = (struct sip_session *)sess;
6325 gchar *rival;
6326 gboolean has_won = TRUE;
6328 if (session->roster_manager) {
6329 purple_debug_info("sipe",
6330 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6331 return;
6334 session->is_voting_in_progress = FALSE;
6336 SIPE_DIALOG_FOREACH {
6337 if (dialog->election_vote < 0) {
6338 has_won = FALSE;
6339 rival = dialog->with;
6340 break;
6342 } SIPE_DIALOG_FOREACH_END;
6344 if (has_won) {
6345 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6347 session->roster_manager = sip_uri_self(sip);
6349 SIPE_DIALOG_FOREACH {
6350 /* send SetRM to each chat participant*/
6351 sipe_send_election_set_rm(sip, dialog);
6352 } SIPE_DIALOG_FOREACH_END;
6353 } else {
6354 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6356 session->bid = 0;
6358 sipe_process_pending_invite_queue(sip, session);
6362 * For 2007+ conference only.
6364 static void
6365 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_name)
6367 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6368 struct sip_session *session;
6370 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
6371 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_name=%s\n", chat_name);
6373 session = sipe_session_find_chat_by_name(sip, chat_name);
6375 sipe_conf_modify_user_role(sip, session, buddy->name);
6379 * For 2007+ conference only.
6381 static void
6382 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_name)
6384 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6385 struct sip_session *session;
6387 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
6388 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_name=%s\n", chat_name);
6390 session = sipe_session_find_chat_by_name(sip, chat_name);
6392 sipe_conf_delete_user(sip, session, buddy->name);
6395 static void
6396 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6398 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6399 struct sip_session *session;
6401 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6402 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6404 session = sipe_session_find_chat_by_name(sip, chat_name);
6406 sipe_invite_to_chat(sip, session, buddy->name);
6409 static void
6410 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6412 const gchar *email;
6413 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6415 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6416 if (email)
6418 char *mailto = g_strdup_printf("mailto:%s", email);
6419 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6420 #ifndef _WIN32
6422 pid_t pid;
6423 char *const parmList[] = {mailto, NULL};
6424 if ((pid = fork()) == -1)
6426 purple_debug_info("sipe", "fork() error\n");
6428 else if (pid == 0)
6430 execvp("xdg-email", parmList);
6431 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6434 #else
6436 BOOL ret;
6437 _flushall();
6438 errno = 0;
6439 //@TODO resolve env variable %WINDIR% first
6440 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6441 if (errno)
6443 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6446 #endif
6448 g_free(mailto);
6450 else
6452 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6457 * A menu which appear when right-clicking on buddy in contact list.
6459 static GList *
6460 sipe_buddy_menu(PurpleBuddy *buddy)
6462 PurpleBlistNode *g_node;
6463 PurpleGroup *group, *gr_parent;
6464 PurpleMenuAction *act;
6465 GList *menu = NULL;
6466 GList *menu_groups = NULL;
6467 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6468 gchar *self = sip_uri_self(sip);
6470 SIPE_SESSION_FOREACH {
6471 if (strcmp(self, buddy->name) && session->chat_name && session->conv)
6473 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
6475 PurpleConvChatBuddyFlags flags;
6476 PurpleConvChatBuddyFlags flags_us;
6478 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
6479 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
6480 if (session->focus_uri
6481 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
6482 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6484 gchar *label = g_strdup_printf(_("Make Leader of '%s'"), session->chat_name);
6485 act = purple_menu_action_new(label,
6486 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6487 g_strdup(session->chat_name), NULL);
6488 g_free(label);
6489 menu = g_list_prepend(menu, act);
6492 if (session->focus_uri
6493 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6495 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_name);
6496 act = purple_menu_action_new(label,
6497 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6498 g_strdup(session->chat_name), NULL);
6499 g_free(label);
6500 menu = g_list_prepend(menu, act);
6503 else
6505 if (!session->focus_uri
6506 || (session->focus_uri && !session->locked))
6508 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_name);
6509 act = purple_menu_action_new(label,
6510 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6511 g_strdup(session->chat_name), NULL);
6512 g_free(label);
6513 menu = g_list_prepend(menu, act);
6517 } SIPE_SESSION_FOREACH_END;
6519 act = purple_menu_action_new(_("New Chat"),
6520 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6521 NULL, NULL);
6522 menu = g_list_prepend(menu, act);
6524 act = purple_menu_action_new(_("Send Email..."),
6525 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6526 NULL, NULL);
6527 menu = g_list_prepend(menu, act);
6529 gr_parent = purple_buddy_get_group(buddy);
6530 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6531 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6532 continue;
6534 group = (PurpleGroup *)g_node;
6535 if (group == gr_parent)
6536 continue;
6538 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6539 continue;
6541 act = purple_menu_action_new(purple_group_get_name(group),
6542 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6543 group->name, NULL);
6544 menu_groups = g_list_prepend(menu_groups, act);
6546 menu_groups = g_list_reverse(menu_groups);
6548 act = purple_menu_action_new(_("Copy to"),
6549 NULL,
6550 NULL, menu_groups);
6551 menu = g_list_prepend(menu, act);
6552 menu = g_list_reverse(menu);
6554 g_free(self);
6555 return menu;
6558 static void
6559 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
6561 struct sipe_account_data *sip = chat->account->gc->proto_data;
6562 struct sip_session *session;
6564 session = sipe_session_find_chat_by_name(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
6565 sipe_conf_modify_conference_lock(sip, session, locked);
6568 static void
6569 sipe_chat_menu_unlock_cb(PurpleChat *chat)
6571 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
6572 sipe_conf_modify_lock(chat, FALSE);
6575 static void
6576 sipe_chat_menu_lock_cb(PurpleChat *chat)
6578 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
6579 sipe_conf_modify_lock(chat, TRUE);
6582 static GList *
6583 sipe_chat_menu(PurpleChat *chat)
6585 PurpleMenuAction *act;
6586 PurpleConvChatBuddyFlags flags_us;
6587 GList *menu = NULL;
6588 struct sipe_account_data *sip = chat->account->gc->proto_data;
6589 struct sip_session *session;
6590 gchar *self = sip_uri_self(sip);
6592 session = sipe_session_find_chat_by_name(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
6593 if (!session) return NULL;
6595 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
6597 if (session->focus_uri
6598 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6600 if (session->locked) {
6601 act = purple_menu_action_new(_("Unlock Conversation"),
6602 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
6603 NULL, NULL);
6604 menu = g_list_prepend(menu, act);
6605 } else {
6606 act = purple_menu_action_new(_("Lock Conversation"),
6607 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
6608 NULL, NULL);
6609 menu = g_list_prepend(menu, act);
6613 menu = g_list_reverse(menu);
6615 g_free(self);
6616 return menu;
6619 static GList *
6620 sipe_blist_node_menu(PurpleBlistNode *node)
6622 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6623 return sipe_buddy_menu((PurpleBuddy *) node);
6624 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
6625 return sipe_chat_menu((PurpleChat *)node);
6626 } else {
6627 return NULL;
6631 static gboolean
6632 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6634 gboolean ret = TRUE;
6635 char *username = (char *)trans->payload;
6637 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6638 PurpleBuddy *pbuddy;
6639 struct sipe_buddy *sbuddy;
6640 const char *alias;
6641 char *server_alias = NULL;
6642 char *email = NULL;
6643 const char *device_name = NULL;
6645 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6647 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6648 alias = purple_buddy_get_local_alias(pbuddy);
6650 if (sip)
6652 //will query buddy UA's capabilities and send answer to log
6653 sipe_options_request(sip, username);
6655 sbuddy = g_hash_table_lookup(sip->buddies, username);
6656 if (sbuddy)
6658 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6662 if (msg->response != 200) {
6663 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6664 } else {
6665 xmlnode *searchResults;
6666 xmlnode *mrow;
6668 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6669 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6670 if (!searchResults) {
6671 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6672 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6673 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6674 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6675 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6676 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6677 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6678 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6679 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6680 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6681 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6682 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6683 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6684 if (!email || strcmp("", email)) {
6685 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6686 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6690 xmlnode_free(searchResults);
6693 purple_notify_user_info_add_section_break(info);
6695 if (!server_alias || !strcmp("", server_alias)) {
6696 g_free(server_alias);
6697 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6698 if (server_alias) {
6699 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6703 // same as server alias, do not present
6704 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6705 if (alias)
6707 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6710 if (!email || !strcmp("", email)) {
6711 g_free(email);
6712 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6713 if (email) {
6714 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6718 if (device_name)
6720 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6723 /* show a buddy's user info in a nice dialog box */
6724 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6725 username, /* buddy's username */
6726 info, /* body */
6727 NULL, /* callback called when dialog closed */
6728 NULL); /* userdata for callback */
6730 return ret;
6734 * AD search first, LDAP based
6736 static void sipe_get_info(PurpleConnection *gc, const char *username)
6738 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6739 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6741 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6742 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6743 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6744 g_free(body);
6745 g_free(row);
6748 static PurplePlugin *my_protocol = NULL;
6750 static PurplePluginProtocolInfo prpl_info =
6752 OPT_PROTO_CHAT_TOPIC,
6753 NULL, /* user_splits */
6754 NULL, /* protocol_options */
6755 NO_BUDDY_ICONS, /* icon_spec */
6756 sipe_list_icon, /* list_icon */
6757 NULL, /* list_emblems */
6758 sipe_status_text, /* status_text */
6759 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6760 sipe_status_types, /* away_states */
6761 sipe_blist_node_menu, /* blist_node_menu */
6762 NULL, /* chat_info */
6763 NULL, /* chat_info_defaults */
6764 sipe_login, /* login */
6765 sipe_close, /* close */
6766 sipe_im_send, /* send_im */
6767 NULL, /* set_info */ // TODO maybe
6768 sipe_send_typing, /* send_typing */
6769 sipe_get_info, /* get_info */
6770 sipe_set_status, /* set_status */
6771 NULL, /* set_idle */
6772 NULL, /* change_passwd */
6773 sipe_add_buddy, /* add_buddy */
6774 NULL, /* add_buddies */
6775 sipe_remove_buddy, /* remove_buddy */
6776 NULL, /* remove_buddies */
6777 sipe_add_permit, /* add_permit */
6778 sipe_add_deny, /* add_deny */
6779 sipe_add_deny, /* rem_permit */
6780 sipe_add_permit, /* rem_deny */
6781 dummy_permit_deny, /* set_permit_deny */
6782 NULL, /* join_chat */
6783 NULL, /* reject_chat */
6784 NULL, /* get_chat_name */
6785 sipe_chat_invite, /* chat_invite */
6786 sipe_chat_leave, /* chat_leave */
6787 NULL, /* chat_whisper */
6788 sipe_chat_send, /* chat_send */
6789 sipe_keep_alive, /* keepalive */
6790 NULL, /* register_user */
6791 NULL, /* get_cb_info */ // deprecated
6792 NULL, /* get_cb_away */ // deprecated
6793 sipe_alias_buddy, /* alias_buddy */
6794 sipe_group_buddy, /* group_buddy */
6795 sipe_rename_group, /* rename_group */
6796 NULL, /* buddy_free */
6797 sipe_convo_closed, /* convo_closed */
6798 purple_normalize_nocase, /* normalize */
6799 NULL, /* set_buddy_icon */
6800 sipe_remove_group, /* remove_group */
6801 NULL, /* get_cb_real_name */ // TODO?
6802 NULL, /* set_chat_topic */
6803 NULL, /* find_blist_chat */
6804 NULL, /* roomlist_get_list */
6805 NULL, /* roomlist_cancel */
6806 NULL, /* roomlist_expand_category */
6807 NULL, /* can_receive_file */
6808 NULL, /* send_file */
6809 NULL, /* new_xfer */
6810 NULL, /* offline_message */
6811 NULL, /* whiteboard_prpl_ops */
6812 sipe_send_raw, /* send_raw */
6813 NULL, /* roomlist_room_serialize */
6814 NULL, /* unregister_user */
6815 NULL, /* send_attention */
6816 NULL, /* get_attention_types */
6818 sizeof(PurplePluginProtocolInfo), /* struct_size */
6819 sipe_get_account_text_table, /* get_account_text_table */
6823 static PurplePluginInfo info = {
6824 PURPLE_PLUGIN_MAGIC,
6825 PURPLE_MAJOR_VERSION,
6826 PURPLE_MINOR_VERSION,
6827 PURPLE_PLUGIN_PROTOCOL, /**< type */
6828 NULL, /**< ui_requirement */
6829 0, /**< flags */
6830 NULL, /**< dependencies */
6831 PURPLE_PRIORITY_DEFAULT, /**< priority */
6832 "prpl-sipe", /**< id */
6833 "Microsoft LCS/OCS", /**< name */
6834 VERSION, /**< version */
6835 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6836 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6837 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6838 "Gabriel Burt <gburt@novell.com>", /**< author */
6839 PURPLE_WEBSITE, /**< homepage */
6840 sipe_plugin_load, /**< load */
6841 sipe_plugin_unload, /**< unload */
6842 sipe_plugin_destroy, /**< destroy */
6843 NULL, /**< ui_info */
6844 &prpl_info, /**< extra_info */
6845 NULL,
6846 sipe_actions,
6847 NULL,
6848 NULL,
6849 NULL,
6850 NULL
6853 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
6855 GList *entry;
6857 entry = prpl_info.protocol_options;
6858 while (entry) {
6859 purple_account_option_destroy(entry->data);
6860 entry = g_list_delete_link(entry, entry);
6862 prpl_info.protocol_options = NULL;
6864 entry = prpl_info.user_splits;
6865 while (entry) {
6866 purple_account_user_split_destroy(entry->data);
6867 entry = g_list_delete_link(entry, entry);
6869 prpl_info.user_splits = NULL;
6872 static void init_plugin(PurplePlugin *plugin)
6874 PurpleAccountUserSplit *split;
6875 PurpleAccountOption *option;
6877 srand(time(NULL));
6879 #ifdef ENABLE_NLS
6880 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6881 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6882 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6883 textdomain(GETTEXT_PACKAGE);
6884 #endif
6886 purple_plugin_register(plugin);
6888 split = purple_account_user_split_new(_("Login \n user or DOMAIN\\user or\n user@company.com "), NULL, ',');
6889 purple_account_user_split_set_reverse(split, FALSE);
6890 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6892 option = purple_account_option_string_new(_("Server[:Port]\n(Leave empty for auto-discovery)"), "server", "");
6893 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6895 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6896 purple_account_option_add_list_item(option, _("Auto"), "auto");
6897 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6898 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6899 purple_account_option_add_list_item(option, _("UDP"), "udp");
6900 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6902 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6903 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6905 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6906 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6908 #ifdef USE_KERBEROS
6909 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6910 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6912 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6913 * No login/password is taken into account if this option present,
6914 * instead used default credentials stored in OS.
6916 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6917 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6918 #endif
6919 my_protocol = plugin;
6922 /* I had to redefined the function for it load, but works */
6923 gboolean purple_init_plugin(PurplePlugin *plugin){
6924 plugin->info = &(info);
6925 init_plugin((plugin));
6926 sipe_plugin_load((plugin));
6927 return purple_plugin_register(plugin);
6931 Local Variables:
6932 mode: c
6933 c-file-style: "bsd"
6934 indent-tabs-mode: t
6935 tab-width: 8
6936 End: