Add more debugging to sipe_login() user name processing
[siplcs.git] / src / sipe.c
blob14cb41c7baf1d0eea619280be73de99a01f4fcae
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>"
108 static gchar *get_epid(struct sipe_account_data *sip)
110 if (!sip->epid) {
111 gchar *self_sip_uri = sip_uri_self(sip);
112 sip->epid = sipe_get_epid(self_sip_uri,
113 sipe_get_host_name(),
114 purple_network_get_my_ip(-1));
115 g_free(self_sip_uri);
117 return g_strdup(sip->epid);
120 static char *genbranch()
122 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
123 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
124 rand() & 0xFFFF, rand() & 0xFFFF);
127 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
128 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
130 return "sipe";
133 static void sipe_plugin_destroy(PurplePlugin *plugin);
135 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
137 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
138 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
139 gpointer data);
141 static void sipe_close(PurpleConnection *gc);
143 static void send_presence_status(struct sipe_account_data *sip);
145 static void sendout_pkt(PurpleConnection *gc, const char *buf);
147 static void sipe_keep_alive(PurpleConnection *gc)
149 struct sipe_account_data *sip = gc->proto_data;
150 if (sip->transport == SIPE_TRANSPORT_UDP) {
151 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
152 gchar buf[2] = {0, 0};
153 purple_debug_info("sipe", "sending keep alive\n");
154 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
155 } else {
156 time_t now = time(NULL);
157 if ((sip->keepalive_timeout > 0) &&
158 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
159 #if PURPLE_VERSION_CHECK(2,4,0)
160 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
161 #endif
163 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
164 sendout_pkt(gc, "\r\n\r\n");
165 sip->last_keepalive = now;
170 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
172 struct sip_connection *ret = NULL;
173 GSList *entry = sip->openconns;
174 while (entry) {
175 ret = entry->data;
176 if (ret->fd == fd) return ret;
177 entry = entry->next;
179 return NULL;
182 static void sipe_auth_free(struct sip_auth *auth)
184 g_free(auth->opaque);
185 auth->opaque = NULL;
186 g_free(auth->realm);
187 auth->realm = NULL;
188 g_free(auth->target);
189 auth->target = NULL;
190 auth->type = AUTH_TYPE_UNSET;
191 auth->retries = 0;
192 auth->expires = 0;
193 g_free(auth->gssapi_data);
194 auth->gssapi_data = NULL;
195 sip_sec_destroy_context(auth->gssapi_context);
196 auth->gssapi_context = NULL;
199 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
201 struct sip_connection *ret = g_new0(struct sip_connection, 1);
202 ret->fd = fd;
203 sip->openconns = g_slist_append(sip->openconns, ret);
204 return ret;
207 static void connection_remove(struct sipe_account_data *sip, int fd)
209 struct sip_connection *conn = connection_find(sip, fd);
210 if (conn) {
211 sip->openconns = g_slist_remove(sip->openconns, conn);
212 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
213 g_free(conn->inbuf);
214 g_free(conn);
218 static void connection_free_all(struct sipe_account_data *sip)
220 struct sip_connection *ret = NULL;
221 GSList *entry = sip->openconns;
222 while (entry) {
223 ret = entry->data;
224 connection_remove(sip, ret->fd);
225 entry = sip->openconns;
229 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
231 gchar noncecount[9];
232 const char *authuser = sip->authuser;
233 gchar *response;
234 gchar *ret;
236 if (!authuser || strlen(authuser) < 1) {
237 authuser = sip->username;
240 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
241 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
243 // If we have a signature for the message, include that
244 if (msg->signature) {
245 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);
248 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
249 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
250 gchar *gssapi_data;
251 gchar *opaque;
253 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
254 &(auth->expires),
255 auth->type,
256 purple_account_get_bool(sip->account, "sso", TRUE),
257 sip->authdomain ? sip->authdomain : "",
258 authuser,
259 sip->password,
260 auth->target,
261 auth->gssapi_data);
262 if (!gssapi_data || !auth->gssapi_context) {
263 sip->gc->wants_to_die = TRUE;
264 purple_connection_error(sip->gc, _("Failed to authenticate to server"));
265 return NULL;
268 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
269 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
270 g_free(opaque);
271 g_free(gssapi_data);
272 return ret;
275 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
277 } else { /* Digest */
279 /* Calculate new session key */
280 if (!auth->opaque) {
281 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
282 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
283 authuser, auth->realm, sip->password,
284 auth->gssapi_data, NULL);
287 sprintf(noncecount, "%08d", auth->nc++);
288 response = purple_cipher_http_digest_calculate_response("md5",
289 msg->method, msg->target, NULL, NULL,
290 auth->gssapi_data, noncecount, NULL,
291 auth->opaque);
292 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
294 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);
295 g_free(response);
296 return ret;
300 static char *parse_attribute(const char *attrname, const char *source)
302 const char *tmp, *tmp2;
303 char *retval = NULL;
304 int len = strlen(attrname);
306 if (!strncmp(source, attrname, len)) {
307 tmp = source + len;
308 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
309 if (tmp2)
310 retval = g_strndup(tmp, tmp2 - tmp);
311 else
312 retval = g_strdup(tmp);
315 return retval;
318 static void fill_auth(gchar *hdr, struct sip_auth *auth)
320 int i;
321 gchar **parts;
323 if (!hdr) {
324 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
325 return;
328 if (!g_strncasecmp(hdr, "NTLM", 4)) {
329 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
330 auth->type = AUTH_TYPE_NTLM;
331 hdr += 5;
332 auth->nc = 1;
333 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
334 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
335 auth->type = AUTH_TYPE_KERBEROS;
336 hdr += 9;
337 auth->nc = 3;
338 } else {
339 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
340 auth->type = AUTH_TYPE_DIGEST;
341 hdr += 7;
344 parts = g_strsplit(hdr, "\", ", 0);
345 for (i = 0; parts[i]; i++) {
346 char *tmp;
348 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
350 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
351 g_free(auth->gssapi_data);
352 auth->gssapi_data = tmp;
354 if (auth->type == AUTH_TYPE_NTLM) {
355 /* NTLM module extracts nonce from gssapi-data */
356 auth->nc = 3;
359 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
360 /* Only used with AUTH_TYPE_DIGEST */
361 g_free(auth->gssapi_data);
362 auth->gssapi_data = tmp;
363 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
364 g_free(auth->opaque);
365 auth->opaque = tmp;
366 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
367 g_free(auth->realm);
368 auth->realm = tmp;
370 if (auth->type == AUTH_TYPE_DIGEST) {
371 /* Throw away old session key */
372 g_free(auth->opaque);
373 auth->opaque = NULL;
374 auth->nc = 1;
377 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
378 g_free(auth->target);
379 auth->target = tmp;
382 g_strfreev(parts);
384 return;
387 static void sipe_canwrite_cb(gpointer data,
388 SIPE_UNUSED_PARAMETER gint source,
389 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
391 PurpleConnection *gc = data;
392 struct sipe_account_data *sip = gc->proto_data;
393 gsize max_write;
394 gssize written;
396 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
398 if (max_write == 0) {
399 if (sip->tx_handler != 0){
400 purple_input_remove(sip->tx_handler);
401 sip->tx_handler = 0;
403 return;
406 written = write(sip->fd, sip->txbuf->outptr, max_write);
408 if (written < 0 && errno == EAGAIN)
409 written = 0;
410 else if (written <= 0) {
411 /*TODO: do we really want to disconnect on a failure to write?*/
412 purple_connection_error(gc, _("Could not write"));
413 return;
416 purple_circ_buffer_mark_read(sip->txbuf, written);
419 static void sipe_canwrite_cb_ssl(gpointer data,
420 SIPE_UNUSED_PARAMETER gint src,
421 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
423 PurpleConnection *gc = data;
424 struct sipe_account_data *sip = gc->proto_data;
425 gsize max_write;
426 gssize written;
428 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
430 if (max_write == 0) {
431 if (sip->tx_handler != 0) {
432 purple_input_remove(sip->tx_handler);
433 sip->tx_handler = 0;
434 return;
438 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
440 if (written < 0 && errno == EAGAIN)
441 written = 0;
442 else if (written <= 0) {
443 /*TODO: do we really want to disconnect on a failure to write?*/
444 purple_connection_error(gc, _("Could not write"));
445 return;
448 purple_circ_buffer_mark_read(sip->txbuf, written);
451 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
453 static void send_later_cb(gpointer data, gint source,
454 SIPE_UNUSED_PARAMETER const gchar *error)
456 PurpleConnection *gc = data;
457 struct sipe_account_data *sip;
458 struct sip_connection *conn;
460 if (!PURPLE_CONNECTION_IS_VALID(gc))
462 if (source >= 0)
463 close(source);
464 return;
467 if (source < 0) {
468 purple_connection_error(gc, _("Could not connect"));
469 return;
472 sip = gc->proto_data;
473 sip->fd = source;
474 sip->connecting = FALSE;
475 sip->last_keepalive = time(NULL);
477 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
479 /* If there is more to write now, we need to register a handler */
480 if (sip->txbuf->bufused > 0)
481 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
483 conn = connection_create(sip, source);
484 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
487 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
489 struct sipe_account_data *sip;
490 struct sip_connection *conn;
492 if (!PURPLE_CONNECTION_IS_VALID(gc))
494 if (gsc) purple_ssl_close(gsc);
495 return NULL;
498 sip = gc->proto_data;
499 sip->fd = gsc->fd;
500 sip->gsc = gsc;
501 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
502 sip->connecting = FALSE;
503 sip->last_keepalive = time(NULL);
505 conn = connection_create(sip, gsc->fd);
507 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
509 return sip;
512 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
513 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
515 PurpleConnection *gc = data;
516 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
517 if (sip == NULL) return;
519 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
521 /* If there is more to write now */
522 if (sip->txbuf->bufused > 0) {
523 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
528 static void sendlater(PurpleConnection *gc, const char *buf)
530 struct sipe_account_data *sip = gc->proto_data;
532 if (!sip->connecting) {
533 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
534 if (sip->transport == SIPE_TRANSPORT_TLS){
535 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
536 } else {
537 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
538 purple_connection_error(gc, _("Couldn't create socket"));
541 sip->connecting = TRUE;
544 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
545 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
547 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
550 static void sendout_pkt(PurpleConnection *gc, const char *buf)
552 struct sipe_account_data *sip = gc->proto_data;
553 time_t currtime = time(NULL);
554 int writelen = strlen(buf);
556 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
557 if (sip->transport == SIPE_TRANSPORT_UDP) {
558 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
559 purple_debug_info("sipe", "could not send packet\n");
561 } else {
562 int ret;
563 if (sip->fd < 0) {
564 sendlater(gc, buf);
565 return;
568 if (sip->tx_handler) {
569 ret = -1;
570 errno = EAGAIN;
571 } else{
572 if (sip->gsc){
573 ret = purple_ssl_write(sip->gsc, buf, writelen);
574 }else{
575 ret = write(sip->fd, buf, writelen);
579 if (ret < 0 && errno == EAGAIN)
580 ret = 0;
581 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
582 sendlater(gc, buf);
583 return;
586 if (ret < writelen) {
587 if (!sip->tx_handler){
588 if (sip->gsc){
589 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
591 else{
592 sip->tx_handler = purple_input_add(sip->fd,
593 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
594 gc);
598 /* XXX: is it OK to do this? You might get part of a request sent
599 with part of another. */
600 if (sip->txbuf->bufused > 0)
601 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
603 purple_circ_buffer_append(sip->txbuf, buf + ret,
604 writelen - ret);
609 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
611 sendout_pkt(gc, buf);
612 return len;
615 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
617 GSList *tmp = msg->headers;
618 gchar *name;
619 gchar *value;
620 GString *outstr = g_string_new("");
621 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
622 while (tmp) {
623 name = ((struct siphdrelement*) (tmp->data))->name;
624 value = ((struct siphdrelement*) (tmp->data))->value;
625 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
626 tmp = g_slist_next(tmp);
628 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
629 sendout_pkt(sip->gc, outstr->str);
630 g_string_free(outstr, TRUE);
633 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
635 gchar * buf;
637 if (sip->registrar.type == AUTH_TYPE_UNSET) {
638 return;
641 if (sip->registrar.gssapi_context) {
642 struct sipmsg_breakdown msgbd;
643 gchar *signature_input_str;
644 msgbd.msg = msg;
645 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
646 msgbd.rand = g_strdup_printf("%08x", g_random_int());
647 sip->registrar.ntlm_num++;
648 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
649 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
650 if (signature_input_str != NULL) {
651 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
652 msg->signature = signature_hex;
653 msg->rand = g_strdup(msgbd.rand);
654 msg->num = g_strdup(msgbd.num);
655 g_free(signature_input_str);
657 sipmsg_breakdown_free(&msgbd);
660 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
661 buf = auth_header(sip, &sip->registrar, msg);
662 if (buf) {
663 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
665 g_free(buf);
666 } 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")) {
667 sip->registrar.nc = 3;
668 #ifdef USE_KERBEROS
669 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
670 #endif
671 sip->registrar.type = AUTH_TYPE_NTLM;
672 #ifdef USE_KERBEROS
673 } else {
674 sip->registrar.type = AUTH_TYPE_KERBEROS;
676 #endif
679 buf = auth_header(sip, &sip->registrar, msg);
680 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
681 g_free(buf);
682 } else {
683 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
688 * unused. Needed?
689 static char *get_contact_service(struct sipe_account_data *sip)
691 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()));
692 //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);
696 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
697 const char *text, const char *body)
699 gchar *name;
700 gchar *value;
701 GString *outstr = g_string_new("");
702 struct sipe_account_data *sip = gc->proto_data;
703 gchar *contact;
704 GSList *tmp;
705 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
707 contact = get_contact(sip);
708 sipmsg_add_header(msg, "Contact", contact);
709 g_free(contact);
711 if (body) {
712 gchar len[12];
713 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
714 sipmsg_add_header(msg, "Content-Length", len);
715 } else {
716 sipmsg_add_header(msg, "Content-Length", "0");
719 msg->response = code;
721 sipmsg_strip_headers(msg, keepers);
722 sipmsg_merge_new_headers(msg);
723 sign_outgoing_message(msg, sip, msg->method);
725 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
726 tmp = msg->headers;
727 while (tmp) {
728 name = ((struct siphdrelement*) (tmp->data))->name;
729 value = ((struct siphdrelement*) (tmp->data))->value;
731 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
732 tmp = g_slist_next(tmp);
734 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
735 sendout_pkt(gc, outstr->str);
736 g_string_free(outstr, TRUE);
739 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
741 sip->transactions = g_slist_remove(sip->transactions, trans);
742 if (trans->msg) sipmsg_free(trans->msg);
743 g_free(trans->key);
744 g_free(trans);
747 static struct transaction *
748 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
750 gchar *call_id = NULL;
751 gchar *cseq = NULL;
752 struct transaction *trans = g_new0(struct transaction, 1);
754 trans->time = time(NULL);
755 trans->msg = (struct sipmsg *)msg;
756 call_id = sipmsg_find_header(trans->msg, "Call-ID");
757 cseq = sipmsg_find_header(trans->msg, "CSeq");
758 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
759 trans->callback = callback;
760 sip->transactions = g_slist_append(sip->transactions, trans);
761 return trans;
764 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
766 struct transaction *trans;
767 GSList *transactions = sip->transactions;
768 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
769 gchar *cseq = sipmsg_find_header(msg, "CSeq");
770 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
772 while (transactions) {
773 trans = transactions->data;
774 if (!g_strcasecmp(trans->key, key)) {
775 g_free(key);
776 return trans;
778 transactions = transactions->next;
781 g_free(key);
782 return NULL;
785 struct transaction *
786 send_sip_request(PurpleConnection *gc, const gchar *method,
787 const gchar *url, const gchar *to, const gchar *addheaders,
788 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
790 struct sipe_account_data *sip = gc->proto_data;
791 const char *addh = "";
792 char *buf;
793 struct sipmsg *msg;
794 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
795 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
796 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
797 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
798 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
799 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
800 gchar *route = strdup("");
801 gchar *epid = get_epid(sip); // TODO generate one per account/login
802 int cseq = dialog ? ++dialog->cseq :
803 /* This breaks OCS2007: own presence, contact search, ?
804 1 .* as Call-Id is new in this case */
805 ++sip->cseq;
806 struct transaction *trans;
808 if (dialog && dialog->routes)
810 GSList *iter = dialog->routes;
812 while(iter)
814 char *tmp = route;
815 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
816 g_free(tmp);
817 iter = g_slist_next(iter);
821 if (!ourtag && !dialog) {
822 ourtag = gentag();
825 if (!strcmp(method, "REGISTER")) {
826 if (sip->regcallid) {
827 g_free(callid);
828 callid = g_strdup(sip->regcallid);
829 } else {
830 sip->regcallid = g_strdup(callid);
834 if (addheaders) addh = addheaders;
836 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
837 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
838 "From: <sip:%s>%s%s;epid=%s\r\n"
839 "To: <%s>%s%s%s%s\r\n"
840 "Max-Forwards: 70\r\n"
841 "CSeq: %d %s\r\n"
842 "User-Agent: %s\r\n"
843 "Call-ID: %s\r\n"
844 "%s%s"
845 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
846 method,
847 dialog && dialog->request ? dialog->request : url,
848 TRANSPORT_DESCRIPTOR,
849 purple_network_get_my_ip(-1),
850 sip->listenport,
851 branch ? ";branch=" : "",
852 branch ? branch : "",
853 sip->username,
854 ourtag ? ";tag=" : "",
855 ourtag ? ourtag : "",
856 epid,
858 theirtag ? ";tag=" : "",
859 theirtag ? theirtag : "",
860 theirepid ? ";epid=" : "",
861 theirepid ? theirepid : "",
862 cseq,
863 method,
864 useragent,
865 callid,
866 route,
867 addh,
868 body ? (gsize) strlen(body) : 0,
869 body ? body : "");
872 //printf ("parsing msg buf:\n%s\n\n", buf);
873 msg = sipmsg_parse_msg(buf);
875 g_free(buf);
876 g_free(ourtag);
877 g_free(theirtag);
878 g_free(theirepid);
879 g_free(branch);
880 g_free(callid);
881 g_free(route);
882 g_free(epid);
884 sign_outgoing_message (msg, sip, method);
886 buf = sipmsg_to_string (msg);
888 /* add to ongoing transactions */
889 trans = transactions_add_buf(sip, msg, tc);
890 sendout_pkt(gc, buf);
891 g_free(buf);
893 return trans;
896 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
898 gchar *from = sip_uri_self(sip);
899 gchar *contact = get_contact(sip);
900 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
901 "Content-Type: application/SOAP+xml\r\n",contact);
903 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
904 tr->payload = payload;
906 g_free(from);
907 g_free(contact);
908 g_free(hdr);
911 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
913 send_soap_request_with_cb(sip, body, NULL, NULL);
916 static char *get_contact_register(struct sipe_account_data *sip)
918 char *epid = get_epid(sip);
919 char *uuid = generateUUIDfromEPID(epid);
920 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);
921 g_free(uuid);
922 g_free(epid);
923 return(buf);
926 static void do_register_exp(struct sipe_account_data *sip, int expire)
928 char *uri;
929 char *expires;
930 char *to;
931 char *contact;
932 char *hdr;
934 if (!sip->sipdomain) return;
936 uri = sip_uri_from_name(sip->sipdomain);
937 expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
938 to = sip_uri_self(sip);
939 contact = get_contact_register(sip);
940 hdr = g_strdup_printf("Contact: %s\r\n"
941 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
942 "Event: registration\r\n"
943 "Allow-Events: presence\r\n"
944 "ms-keep-alive: UAC;hop-hop=yes\r\n"
945 "%s", contact, expires);
946 g_free(contact);
947 g_free(expires);
949 sip->registerstatus = 1;
951 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
952 process_register_response);
954 g_free(hdr);
955 g_free(uri);
956 g_free(to);
959 static void do_register_cb(struct sipe_account_data *sip,
960 SIPE_UNUSED_PARAMETER void *unused)
962 do_register_exp(sip, -1);
963 sip->reregister_set = FALSE;
966 static void do_register(struct sipe_account_data *sip)
968 do_register_exp(sip, -1);
971 static void
972 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
974 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
975 send_soap_request(sip, body);
976 g_free(body);
979 static void
980 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
982 if (allow) {
983 purple_debug_info("sipe", "Authorizing contact %s\n", who);
984 } else {
985 purple_debug_info("sipe", "Blocking contact %s\n", who);
988 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
991 static
992 void sipe_auth_user_cb(void * data)
994 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
995 if (!job) return;
997 sipe_contact_allow_deny (job->sip, job->who, TRUE);
998 g_free(job);
1001 static
1002 void sipe_deny_user_cb(void * data)
1004 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
1005 if (!job) return;
1007 sipe_contact_allow_deny (job->sip, job->who, FALSE);
1008 g_free(job);
1011 static void
1012 sipe_add_permit(PurpleConnection *gc, const char *name)
1014 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1015 sipe_contact_allow_deny(sip, name, TRUE);
1018 static void
1019 sipe_add_deny(PurpleConnection *gc, const char *name)
1021 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1022 sipe_contact_allow_deny(sip, name, FALSE);
1025 /*static void
1026 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1028 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1029 sipe_contact_set_acl(sip, name, "");
1032 static void
1033 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1035 xmlnode *watchers;
1036 xmlnode *watcher;
1037 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1038 if (msg->response != 0 && msg->response != 200) return;
1040 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1042 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1043 if (!watchers) return;
1045 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1046 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1047 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1048 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1050 // TODO pull out optional displayName to pass as alias
1051 if (remote_user) {
1052 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1053 job->who = remote_user;
1054 job->sip = sip;
1055 purple_account_request_authorization(
1056 sip->account,
1057 remote_user,
1058 _("you"), /* id */
1059 alias,
1060 NULL, /* message */
1061 on_list,
1062 sipe_auth_user_cb,
1063 sipe_deny_user_cb,
1064 (void *) job);
1069 xmlnode_free(watchers);
1070 return;
1073 static void
1074 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1076 PurpleGroup * purple_group = purple_find_group(group->name);
1077 if (!purple_group) {
1078 purple_group = purple_group_new(group->name);
1079 purple_blist_add_group(purple_group, NULL);
1082 if (purple_group) {
1083 group->purple_group = purple_group;
1084 sip->groups = g_slist_append(sip->groups, group);
1085 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1086 } else {
1087 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1091 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1093 struct sipe_group *group;
1094 GSList *entry;
1095 if (sip == NULL) {
1096 return NULL;
1099 entry = sip->groups;
1100 while (entry) {
1101 group = entry->data;
1102 if (group->id == id) {
1103 return group;
1105 entry = entry->next;
1107 return NULL;
1110 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1112 struct sipe_group *group;
1113 GSList *entry;
1114 if (sip == NULL) {
1115 return NULL;
1118 entry = sip->groups;
1119 while (entry) {
1120 group = entry->data;
1121 if (!strcmp(group->name, name)) {
1122 return group;
1124 entry = entry->next;
1126 return NULL;
1129 static void
1130 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1132 gchar *body;
1133 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1134 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1135 send_soap_request(sip, body);
1136 g_free(body);
1137 g_free(group->name);
1138 group->name = g_strdup(name);
1142 * Only appends if no such value already stored.
1143 * Like Set in Java.
1145 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1146 GSList * res = list;
1147 if (!g_slist_find_custom(list, data, func)) {
1148 res = g_slist_insert_sorted(list, data, func);
1150 return res;
1153 static int
1154 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1155 return group1->id - group2->id;
1159 * Returns string like "2 4 7 8" - group ids buddy belong to.
1161 static gchar *
1162 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1163 int i = 0;
1164 gchar *res;
1165 //creating array from GList, converting int to gchar*
1166 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1167 GSList *entry = buddy->groups;
1168 while (entry) {
1169 struct sipe_group * group = entry->data;
1170 ids_arr[i] = g_strdup_printf("%d", group->id);
1171 entry = entry->next;
1172 i++;
1174 ids_arr[i] = NULL;
1175 res = g_strjoinv(" ", ids_arr);
1176 g_strfreev(ids_arr);
1177 return res;
1181 * Sends buddy update to server
1183 static void
1184 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1186 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1187 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1189 if (buddy && purple_buddy) {
1190 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1191 gchar *body;
1192 gchar *groups = sipe_get_buddy_groups_string(buddy);
1193 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1195 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1196 alias, groups, "true", buddy->name, sip->contacts_delta++
1198 send_soap_request(sip, body);
1199 g_free(groups);
1200 g_free(body);
1204 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1206 if (msg->response == 200) {
1207 struct sipe_group *group;
1208 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1209 xmlnode *xml;
1210 xmlnode *node;
1211 char *group_id;
1212 struct sipe_buddy *buddy;
1214 xml = xmlnode_from_str(msg->body, msg->bodylen);
1215 if (!xml) {
1216 g_free(ctx);
1217 return FALSE;
1220 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1221 if (!node) {
1222 g_free(ctx);
1223 xmlnode_free(xml);
1224 return FALSE;
1227 group_id = xmlnode_get_data(node);
1228 if (!group_id) {
1229 g_free(ctx);
1230 xmlnode_free(xml);
1231 return FALSE;
1234 group = g_new0(struct sipe_group, 1);
1235 group->id = (int)g_ascii_strtod(group_id, NULL);
1236 g_free(group_id);
1237 group->name = ctx->group_name;
1239 sipe_group_add(sip, group);
1241 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1242 if (buddy) {
1243 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1246 sipe_group_set_user(sip, ctx->user_name);
1248 g_free(ctx);
1249 xmlnode_free(xml);
1250 return TRUE;
1252 return FALSE;
1255 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1257 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1258 gchar *body;
1259 ctx->group_name = g_strdup(name);
1260 ctx->user_name = g_strdup(who);
1262 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1263 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1264 g_free(body);
1268 * Data structure for scheduled actions
1270 typedef void (*Action) (struct sipe_account_data *, void *);
1272 struct scheduled_action {
1274 * Name of action.
1275 * Format is <Event>[<Data>...]
1276 * Example: <presence><sip:user@domain.com> or <registration>
1278 gchar *name;
1279 guint timeout_handler;
1280 gboolean repetitive;
1281 Action action;
1282 GDestroyNotify destroy;
1283 struct sipe_account_data *sip;
1284 void *payload;
1288 * A timer callback
1289 * Should return FALSE if repetitive action is not needed
1291 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1293 gboolean ret;
1294 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1295 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1296 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1297 (sched_action->action)(sched_action->sip, sched_action->payload);
1298 ret = sched_action->repetitive;
1299 if (sched_action->destroy) {
1300 (*sched_action->destroy)(sched_action->payload);
1302 g_free(sched_action->name);
1303 g_free(sched_action);
1304 return ret;
1308 * Kills action timer effectively cancelling
1309 * scheduled action
1311 * @param name of action
1313 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1315 GSList *entry;
1317 if (!sip->timeouts || !name) return;
1319 entry = sip->timeouts;
1320 while (entry) {
1321 struct scheduled_action *sched_action = entry->data;
1322 if(!strcmp(sched_action->name, name)) {
1323 GSList *to_delete = entry;
1324 entry = entry->next;
1325 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1326 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1327 purple_timeout_remove(sched_action->timeout_handler);
1328 if (sched_action->destroy) {
1329 (*sched_action->destroy)(sched_action->payload);
1331 g_free(sched_action->name);
1332 g_free(sched_action);
1333 } else {
1334 entry = entry->next;
1339 static void
1340 sipe_schedule_action0(const gchar *name,
1341 int timeout,
1342 gboolean isSeconds,
1343 Action action,
1344 GDestroyNotify destroy,
1345 struct sipe_account_data *sip,
1346 void *payload)
1348 struct scheduled_action *sched_action;
1350 /* Make sure each action only exists once */
1351 sipe_cancel_scheduled_action(sip, name);
1353 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1354 sched_action = g_new0(struct scheduled_action, 1);
1355 sched_action->repetitive = FALSE;
1356 sched_action->name = g_strdup(name);
1357 sched_action->action = action;
1358 sched_action->destroy = destroy;
1359 sched_action->sip = sip;
1360 sched_action->payload = payload;
1361 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1362 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1363 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1364 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1368 * Do schedule action for execution in the future.
1369 * Non repetitive execution.
1371 * @param name of action (will be copied)
1372 * @param timeout in seconds
1373 * @action callback function
1374 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1376 static void
1377 sipe_schedule_action(const gchar *name,
1378 int timeout,
1379 Action action,
1380 GDestroyNotify destroy,
1381 struct sipe_account_data *sip,
1382 void *payload)
1384 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1388 * Same as sipe_schedule_action() but timeout is in milliseconds.
1390 static void
1391 sipe_schedule_action_msec(const gchar *name,
1392 int timeout,
1393 Action action,
1394 GDestroyNotify destroy,
1395 struct sipe_account_data *sip,
1396 void *payload)
1398 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1402 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1404 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1405 SIPE_UNUSED_PARAMETER struct transaction *tc)
1407 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1409 process_incoming_notify(sip, msg, FALSE, FALSE);
1411 return TRUE;
1414 static void sipe_subscribe_resource_uri(const char *name,
1415 SIPE_UNUSED_PARAMETER gpointer value,
1416 gchar **resources_uri)
1418 gchar *tmp = *resources_uri;
1419 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1420 g_free(tmp);
1423 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1425 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1426 if (sbuddy && !sbuddy->resubscribed) { // Only not resubscribed contacts; the first time everybody are included
1427 gchar *tmp = *resources_uri;
1428 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1429 g_free(tmp);
1434 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1435 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1436 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1437 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1438 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1441 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1443 gchar *contact = get_contact(sip);
1444 gchar *request;
1445 gchar *content;
1446 gchar *require = "";
1447 gchar *accept = "";
1448 gchar *autoextend = "";
1449 gchar *content_type;
1451 if (sip->msrtc_event_categories) {
1452 require = ", categoryList";
1453 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1454 content_type = "application/msrtc-adrl-categorylist+xml";
1455 content = g_strdup_printf(
1456 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1457 "<action name=\"subscribe\" id=\"63792024\">\n"
1458 "<adhocList>\n%s</adhocList>\n"
1459 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1460 "<category name=\"note\"/>\n"
1461 "<category name=\"state\"/>\n"
1462 "</categoryList>\n"
1463 "</action>\n"
1464 "</batchSub>", sip->username, resources_uri);
1465 } else {
1466 autoextend = "Supported: com.microsoft.autoextend\r\n";
1467 content_type = "application/adrl+xml";
1468 content = g_strdup_printf(
1469 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1470 "<create xmlns=\"\">\n%s</create>\n"
1471 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1473 g_free(resources_uri);
1475 request = g_strdup_printf(
1476 "Require: adhoclist%s\r\n"
1477 "Supported: eventlist\r\n"
1478 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1479 "Supported: ms-piggyback-first-notify\r\n"
1480 "%sSupported: ms-benotify\r\n"
1481 "Proxy-Require: ms-benotify\r\n"
1482 "Event: presence\r\n"
1483 "Content-Type: %s\r\n"
1484 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1485 g_free(contact);
1487 /* subscribe to buddy presence */
1488 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1489 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1491 g_free(content);
1492 g_free(to);
1493 g_free(request);
1496 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1497 SIPE_UNUSED_PARAMETER void *unused)
1499 gchar *to = sip_uri_self(sip);
1500 gchar *resources_uri = g_strdup("");
1501 if (sip->msrtc_event_categories) {
1502 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1503 } else {
1504 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1506 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1509 struct presence_batched_routed {
1510 gchar *host;
1511 GSList *buddies;
1514 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1516 struct presence_batched_routed *data = payload;
1517 GSList *buddies = data->buddies;
1518 while (buddies) {
1519 g_free(buddies->data);
1520 buddies = buddies->next;
1522 g_slist_free(data->buddies);
1523 g_free(data->host);
1524 g_free(payload);
1527 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1529 struct presence_batched_routed *data = payload;
1530 GSList *buddies = data->buddies;
1531 gchar *resources_uri = g_strdup("");
1532 while (buddies) {
1533 gchar *tmp = resources_uri;
1534 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1535 g_free(tmp);
1536 buddies = buddies->next;
1538 sipe_subscribe_presence_batched_to(sip, resources_uri,
1539 g_strdup(data->host));
1543 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1544 * The user sends a single SUBSCRIBE request to the subscribed contact.
1545 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1549 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1551 gchar *to = sip_uri((char *)buddy_name);
1552 gchar *tmp = get_contact(sip);
1553 gchar *request;
1554 gchar *content;
1555 gchar *autoextend = "";
1557 if (!sip->msrtc_event_categories)
1558 autoextend = "Supported: com.microsoft.autoextend\r\n";
1560 request = g_strdup_printf(
1561 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1562 "Supported: ms-piggyback-first-notify\r\n"
1563 "%sSupported: ms-benotify\r\n"
1564 "Proxy-Require: ms-benotify\r\n"
1565 "Event: presence\r\n"
1566 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1567 "Contact: %s\r\n", autoextend,tmp);
1569 content = g_strdup_printf(
1570 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1571 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1572 "<resource uri=\"%s\"/>\n"
1573 "</adhocList>\n"
1574 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1575 "<category name=\"note\"/>\n"
1576 "<category name=\"state\"/>\n"
1577 "</categoryList>\n"
1578 "</action>\n"
1579 "</batchSub>", sip->username, to
1582 g_free(tmp);
1584 /* subscribe to buddy presence */
1585 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1587 g_free(content);
1588 g_free(to);
1589 g_free(request);
1592 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1594 if (!purple_status_is_active(status))
1595 return;
1597 if (account->gc) {
1598 struct sipe_account_data *sip = account->gc->proto_data;
1600 if (sip) {
1601 g_free(sip->status);
1602 sip->status = g_strdup(purple_status_get_id(status));
1603 send_presence_status(sip);
1608 static void
1609 sipe_alias_buddy(PurpleConnection *gc, const char *name,
1610 SIPE_UNUSED_PARAMETER const char *alias)
1612 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1613 sipe_group_set_user(sip, name);
1616 static void
1617 sipe_group_buddy(PurpleConnection *gc,
1618 const char *who,
1619 const char *old_group_name,
1620 const char *new_group_name)
1622 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1623 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1624 struct sipe_group * old_group = NULL;
1625 struct sipe_group * new_group;
1627 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1628 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1630 if(!buddy) { // buddy not in roaming list
1631 return;
1634 if (old_group_name) {
1635 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1637 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1639 if (old_group) {
1640 buddy->groups = g_slist_remove(buddy->groups, old_group);
1641 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1644 if (!new_group) {
1645 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1646 } else {
1647 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1648 sipe_group_set_user(sip, who);
1652 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1654 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1655 struct sipe_buddy *b;
1657 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1659 // Prepend sip: if needed
1660 if (strncmp("sip:", buddy->name, 4)) {
1661 gchar *buf = sip_uri_from_name(buddy->name);
1662 purple_blist_rename_buddy(buddy, buf);
1663 g_free(buf);
1666 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1667 b = g_new0(struct sipe_buddy, 1);
1668 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1669 b->name = g_strdup(buddy->name);
1670 g_hash_table_insert(sip->buddies, b->name, b);
1671 sipe_group_buddy(gc, b->name, NULL, group->name);
1672 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1673 } else {
1674 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1678 static void sipe_free_buddy(struct sipe_buddy *buddy)
1680 #ifndef _WIN32
1682 * We are calling g_hash_table_foreach_steal(). That means that no
1683 * key/value deallocation functions are called. Therefore the glib
1684 * hash code does not touch the key (buddy->name) or value (buddy)
1685 * of the to-be-deleted hash node at all. It follows that we
1687 * - MUST free the memory for the key ourselves and
1688 * - ARE allowed to do it in this function
1690 * Conclusion: glib must be broken on the Windows platform if sipe
1691 * crashes with SIGTRAP when closing. You'll have to live
1692 * with the memory leak until this is fixed.
1694 g_free(buddy->name);
1695 #endif
1696 g_free(buddy->annotation);
1697 g_free(buddy->device_name);
1698 g_slist_free(buddy->groups);
1699 g_free(buddy);
1703 * Unassociates buddy from group first.
1704 * Then see if no groups left, removes buddy completely.
1705 * Otherwise updates buddy groups on server.
1707 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1709 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1710 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1711 struct sipe_group *g = NULL;
1713 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1715 if (!b) return;
1717 if (group) {
1718 g = sipe_group_find_by_name(sip, group->name);
1721 if (g) {
1722 b->groups = g_slist_remove(b->groups, g);
1723 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1726 if (g_slist_length(b->groups) < 1) {
1727 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1728 sipe_cancel_scheduled_action(sip, action_name);
1729 g_free(action_name);
1731 g_hash_table_remove(sip->buddies, buddy->name);
1733 if (b->name) {
1734 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1735 send_soap_request(sip, body);
1736 g_free(body);
1739 sipe_free_buddy(b);
1740 } else {
1741 //updates groups on server
1742 sipe_group_set_user(sip, b->name);
1747 static void
1748 sipe_rename_group(PurpleConnection *gc,
1749 const char *old_name,
1750 PurpleGroup *group,
1751 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1753 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1754 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1755 if (group) {
1756 sipe_group_rename(sip, s_group, group->name);
1757 } else {
1758 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1762 static void
1763 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1765 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1766 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1767 if (s_group) {
1768 gchar *body;
1769 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1770 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1771 send_soap_request(sip, body);
1772 g_free(body);
1774 sip->groups = g_slist_remove(sip->groups, s_group);
1775 g_free(s_group->name);
1776 g_free(s_group);
1777 } else {
1778 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1782 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
1784 PurpleStatusType *type;
1785 GList *types = NULL;
1787 // Online
1788 type = purple_status_type_new_with_attrs(
1789 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1790 // Translators: noun
1791 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1792 NULL);
1793 types = g_list_append(types, type);
1795 // Busy
1796 type = purple_status_type_new_with_attrs(
1797 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1798 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1799 NULL);
1800 types = g_list_append(types, type);
1802 // Do Not Disturb (not user settable)
1803 type = purple_status_type_new_with_attrs(
1804 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1805 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1806 NULL);
1807 types = g_list_append(types, type);
1809 // Be Right Back
1810 type = purple_status_type_new_with_attrs(
1811 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1812 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1813 NULL);
1814 types = g_list_append(types, type);
1816 // Away
1817 type = purple_status_type_new_with_attrs(
1818 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1819 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1820 NULL);
1821 types = g_list_append(types, type);
1823 //On The Phone
1824 type = purple_status_type_new_with_attrs(
1825 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1826 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1827 NULL);
1828 types = g_list_append(types, type);
1830 //Out To Lunch
1831 type = purple_status_type_new_with_attrs(
1832 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1833 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1834 NULL);
1835 types = g_list_append(types, type);
1837 //Appear Offline
1838 type = purple_status_type_new_full(
1839 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1840 types = g_list_append(types, type);
1842 // Offline
1843 type = purple_status_type_new_full(
1844 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1845 types = g_list_append(types, type);
1847 return types;
1851 * A callback for g_hash_table_foreach
1853 static void sipe_buddy_subscribe_cb(SIPE_UNUSED_PARAMETER char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1855 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1856 int time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1857 int timeout = (time_range * rand()) / RAND_MAX; /* random period within the range */
1858 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy->name));
1862 * Removes entries from purple buddy list
1863 * that does not correspond ones in the roaming contact list.
1865 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1866 GSList *buddies = purple_find_buddies(sip->account, NULL);
1867 GSList *entry = buddies;
1868 struct sipe_buddy *buddy;
1869 PurpleBuddy *b;
1870 PurpleGroup *g;
1872 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1873 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1874 while (entry) {
1875 b = entry->data;
1876 g = purple_buddy_get_group(b);
1877 buddy = g_hash_table_lookup(sip->buddies, b->name);
1878 if(buddy) {
1879 gboolean in_sipe_groups = FALSE;
1880 GSList *entry2 = buddy->groups;
1881 while (entry2) {
1882 struct sipe_group *group = entry2->data;
1883 if (!strcmp(group->name, g->name)) {
1884 in_sipe_groups = TRUE;
1885 break;
1887 entry2 = entry2->next;
1889 if(!in_sipe_groups) {
1890 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1891 purple_blist_remove_buddy(b);
1893 } else {
1894 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1895 purple_blist_remove_buddy(b);
1897 entry = entry->next;
1901 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
1903 int len = msg->bodylen;
1905 gchar *tmp = sipmsg_find_header(msg, "Event");
1906 xmlnode *item;
1907 xmlnode *isc;
1908 const gchar *contacts_delta;
1909 xmlnode *group_node;
1910 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1911 return FALSE;
1914 /* Convert the contact from XML to Purple Buddies */
1915 isc = xmlnode_from_str(msg->body, len);
1916 if (!isc) {
1917 return FALSE;
1920 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1921 if (contacts_delta) {
1922 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1925 if (!strcmp(isc->name, "contactList")) {
1927 /* Parse groups */
1928 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1929 struct sipe_group * group = g_new0(struct sipe_group, 1);
1930 const char *name = xmlnode_get_attrib(group_node, "name");
1932 if (!strncmp(name, "~", 1)) {
1933 name = _("Other Contacts");
1935 group->name = g_strdup(name);
1936 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1938 sipe_group_add(sip, group);
1941 // Make sure we have at least one group
1942 if (g_slist_length(sip->groups) == 0) {
1943 struct sipe_group * group = g_new0(struct sipe_group, 1);
1944 PurpleGroup *purple_group;
1945 group->name = g_strdup(_("Other Contacts"));
1946 group->id = 1;
1947 purple_group = purple_group_new(group->name);
1948 purple_blist_add_group(purple_group, NULL);
1949 sip->groups = g_slist_append(sip->groups, group);
1952 /* Parse contacts */
1953 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1954 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1955 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1956 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1957 gchar * buddy_name = sip_uri_from_name(uri);
1958 gchar **item_groups;
1959 struct sipe_group *group = NULL;
1960 struct sipe_buddy *buddy = NULL;
1961 int i = 0;
1963 // assign to group Other Contacts if nothing else received
1964 if(!groups || !strcmp("", groups) ) {
1965 group = sipe_group_find_by_name(sip, _("Other Contacts"));
1966 groups = group ? g_strdup_printf("%d", group->id) : "1";
1969 item_groups = g_strsplit(groups, " ", 0);
1971 while (item_groups[i]) {
1972 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1974 // If couldn't find the right group for this contact, just put them in the first group we have
1975 if (group == NULL && g_slist_length(sip->groups) > 0) {
1976 group = sip->groups->data;
1979 if (group != NULL) {
1980 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1981 if (!b){
1982 b = purple_buddy_new(sip->account, buddy_name, uri);
1983 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1986 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1987 if (name != NULL && strlen(name) != 0) {
1988 purple_blist_alias_buddy(b, name);
1992 if (!buddy) {
1993 buddy = g_new0(struct sipe_buddy, 1);
1994 buddy->name = g_strdup(b->name);
1995 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1998 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
2000 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
2001 } else {
2002 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2003 name);
2006 i++;
2007 } // while, contact groups
2008 g_strfreev(item_groups);
2009 g_free(groups);
2010 g_free(name);
2011 g_free(buddy_name);
2012 g_free(uri);
2014 } // for, contacts
2016 sipe_cleanup_local_blist(sip);
2018 xmlnode_free(isc);
2020 //subscribe to buddies
2021 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2022 if(sip->batched_support){
2023 sipe_subscribe_presence_batched(sip, NULL);
2025 else{
2026 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2028 sip->subscribed_buddies = TRUE;
2031 return 0;
2035 * Subscribe roaming contacts
2037 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2039 gchar *to = sip_uri_self(sip);
2040 gchar *tmp = get_contact(sip);
2041 gchar *hdr = g_strdup_printf(
2042 "Event: vnd-microsoft-roaming-contacts\r\n"
2043 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2044 "Supported: com.microsoft.autoextend\r\n"
2045 "Supported: ms-benotify\r\n"
2046 "Proxy-Require: ms-benotify\r\n"
2047 "Supported: ms-piggyback-first-notify\r\n"
2048 "Contact: %s\r\n", tmp);
2049 g_free(tmp);
2051 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2052 g_free(to);
2053 g_free(hdr);
2056 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2057 SIPE_UNUSED_PARAMETER void *unused)
2059 gchar *to = sip_uri_self(sip);
2060 gchar *tmp = get_contact(sip);
2061 gchar *hdr = g_strdup_printf(
2062 "Event: presence.wpending\r\n"
2063 "Accept: text/xml+msrtc.wpending\r\n"
2064 "Supported: com.microsoft.autoextend\r\n"
2065 "Supported: ms-benotify\r\n"
2066 "Proxy-Require: ms-benotify\r\n"
2067 "Supported: ms-piggyback-first-notify\r\n"
2068 "Contact: %s\r\n", tmp);
2069 g_free(tmp);
2071 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2072 g_free(to);
2073 g_free(hdr);
2077 * Fires on deregistration event initiated by server.
2078 * [MS-SIPREGE] SIP extension.
2081 // 2007 Example
2083 // Content-Type: text/registration-event
2084 // subscription-state: terminated;expires=0
2085 // ms-diagnostics-public: 4141;reason="User disabled"
2087 // deregistered;event=rejected
2089 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2091 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2092 gchar *event = NULL;
2093 gchar *reason = NULL;
2094 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2096 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2097 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2099 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2100 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2101 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2102 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2103 } else {
2104 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2105 return;
2108 if (warning != NULL) {
2109 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2110 } else { // for LCS2005
2111 int error_id = 0;
2112 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2113 error_id = 4140; // [MS-SIPREGE]
2114 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2115 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2116 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2117 error_id = 4141;
2118 reason = g_strdup(_("User disabled")); // [MS-OCER]
2119 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2120 error_id = 4142;
2121 reason = g_strdup(_("User moved")); // [MS-OCER]
2124 g_free(event);
2125 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2126 g_free(reason);
2128 sip->gc->wants_to_die = TRUE;
2129 purple_connection_error(sip->gc, warning);
2130 g_free(warning);
2134 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2136 xmlnode *xn_provision_group_list;
2137 xmlnode *node;
2139 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2141 /* provisionGroup */
2142 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2143 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2144 g_free(sip->focus_factory_uri);
2145 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2146 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2147 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2148 break;
2151 xmlnode_free(xn_provision_group_list);
2154 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2156 const gchar *contacts_delta;
2157 xmlnode *xml;
2159 xml = xmlnode_from_str(msg->body, msg->bodylen);
2160 if (!xml)
2162 return;
2165 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2166 if (contacts_delta)
2168 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2171 xmlnode_free(xml);
2174 static void
2175 free_container(struct sipe_container *container)
2177 GSList *entry;
2179 if (!container) return;
2181 entry = container->members;
2182 while (entry) {
2183 g_free(entry->data);
2184 entry = g_slist_remove(entry, entry->data);
2186 g_free(container);
2190 * Finds locally stored MS-PRES container member
2192 static struct sipe_container_member *
2193 sipe_find_container_member(struct sipe_container *container,
2194 const gchar *type,
2195 const gchar *value)
2197 struct sipe_container_member *member;
2198 GSList *entry;
2200 if (container == NULL || type == NULL) {
2201 return NULL;
2204 entry = container->members;
2205 while (entry) {
2206 member = entry->data;
2207 if (!g_strcasecmp(member->type, type)
2208 && ((!member->value && !value)
2209 || (value && member->value && !g_strcasecmp(member->value, value)))
2211 return member;
2213 entry = entry->next;
2215 return NULL;
2219 * Finds locally stored MS-PRES container by id
2221 static struct sipe_container *
2222 sipe_find_container(struct sipe_account_data *sip,
2223 guint id)
2225 struct sipe_container *container;
2226 GSList *entry;
2228 if (sip == NULL) {
2229 return NULL;
2232 entry = sip->containers;
2233 while (entry) {
2234 container = entry->data;
2235 if (id == container->id) {
2236 return container;
2238 entry = entry->next;
2240 return NULL;
2244 * Access Levels
2245 * 32000 - Blocked
2246 * 400 - Personal
2247 * 300 - Team
2248 * 200 - Company
2249 * 100 - Public
2251 static int
2252 sipe_find_access_level(struct sipe_account_data *sip,
2253 const gchar *type,
2254 const gchar *value)
2256 guint containers[] = {32000, 400, 300, 200, 100};
2257 int i = 0;
2259 for (i = 0; i < 5; i++) {
2260 struct sipe_container_member *member;
2261 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2262 if (!container) continue;
2264 member = sipe_find_container_member(container, type, value);
2265 if (member) {
2266 return containers[i];
2270 return -1;
2273 static void
2274 sipe_send_set_container_members(struct sipe_account_data *sip,
2275 guint container_id,
2276 guint container_version,
2277 const gchar* action,
2278 const gchar* type,
2279 const gchar* value)
2281 gchar *self = sip_uri_self(sip);
2282 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2283 gchar *contact;
2284 gchar *hdr;
2285 gchar *body = g_strdup_printf(
2286 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2287 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2288 "</setContainerMembers>",
2289 container_id,
2290 container_version,
2291 action,
2292 type,
2293 value_str);
2294 g_free(value_str);
2296 contact = get_contact(sip);
2297 hdr = g_strdup_printf("Contact: %s\r\n"
2298 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2299 g_free(contact);
2301 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2303 g_free(hdr);
2304 g_free(body);
2305 g_free(self);
2310 * When we receive some self (BE) NOTIFY with a new subscriber
2311 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2314 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
2316 gchar *contact;
2317 gchar *to;
2318 xmlnode *xml;
2319 xmlnode *node;
2320 xmlnode *node2;
2321 char *display_name = NULL;
2322 PurpleBuddy *pbuddy;
2323 const char *alias;
2324 char *uri_alias;
2325 char *uri_user;
2327 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2329 xml = xmlnode_from_str(msg->body, msg->bodylen);
2330 if (!xml) return;
2332 contact = get_contact(sip);
2333 to = sip_uri_self(sip);
2335 /* containers */
2336 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
2337 guint id = atoi(xmlnode_get_attrib(node, "id"));
2338 struct sipe_container *container = sipe_find_container(sip, id);
2340 if (container) {
2341 sip->containers = g_slist_remove(sip->containers, container);
2342 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
2343 free_container(container);
2345 container = g_new0(struct sipe_container, 1);
2346 container->id = id;
2347 container->version = atoi(xmlnode_get_attrib(node, "version"));
2348 sip->containers = g_slist_append(sip->containers, container);
2349 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
2351 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
2352 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2353 member->type = xmlnode_get_attrib(node2, "type");
2354 member->value = xmlnode_get_attrib(node2, "value");
2355 container->members = g_slist_append(container->members, member);
2356 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
2357 member->type, member->value ? member->value : "");
2361 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
2362 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
2363 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
2364 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
2365 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
2366 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
2367 /* initial set-up to let counterparties see your status */
2368 if (sameEnterpriseAL < 0) {
2369 struct sipe_container *container = sipe_find_container(sip, 200);
2370 guint version = container ? container->version : 0;
2371 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
2373 if (federatedAL < 0) {
2374 struct sipe_container *container = sipe_find_container(sip, 100);
2375 guint version = container ? container->version : 0;
2376 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
2378 sip->access_level_set = TRUE;
2381 /* subscribers */
2382 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2383 const char *user;
2384 const char *acknowledged;
2385 gchar *hdr;
2386 gchar *body;
2388 user = xmlnode_get_attrib(node, "user");
2389 if (!user) continue;
2390 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2391 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2392 uri_user = sip_uri_from_name(user);
2393 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2394 if(pbuddy){
2395 alias = purple_buddy_get_local_alias(pbuddy);
2396 uri_alias = sip_uri_from_name(alias);
2397 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2398 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2399 purple_blist_alias_buddy(pbuddy, display_name);
2401 g_free(uri_alias);
2404 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2405 if(!g_ascii_strcasecmp(acknowledged,"false")){
2406 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2407 if (!purple_find_buddy(sip->account, uri_user)) {
2408 purple_account_request_add(sip->account, uri_user, _("you"), display_name, NULL);
2411 hdr = g_strdup_printf(
2412 "Contact: %s\r\n"
2413 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2415 body = g_strdup_printf(
2416 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2417 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2418 "</setSubscribers>", user);
2420 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2421 g_free(body);
2422 g_free(hdr);
2424 g_free(display_name);
2425 g_free(uri_user);
2428 g_free(to);
2429 g_free(contact);
2430 xmlnode_free(xml);
2433 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
2435 gchar *to = sip_uri_self(sip);
2436 gchar *tmp = get_contact(sip);
2437 gchar *hdr = g_strdup_printf(
2438 "Event: vnd-microsoft-roaming-ACL\r\n"
2439 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2440 "Supported: com.microsoft.autoextend\r\n"
2441 "Supported: ms-benotify\r\n"
2442 "Proxy-Require: ms-benotify\r\n"
2443 "Supported: ms-piggyback-first-notify\r\n"
2444 "Contact: %s\r\n", tmp);
2445 g_free(tmp);
2447 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2448 g_free(to);
2449 g_free(hdr);
2453 * To request for presence information about the user, access level settings that have already been configured by the user
2454 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2455 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2458 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
2460 gchar *to = sip_uri_self(sip);
2461 gchar *tmp = get_contact(sip);
2462 gchar *hdr = g_strdup_printf(
2463 "Event: vnd-microsoft-roaming-self\r\n"
2464 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2465 "Supported: ms-benotify\r\n"
2466 "Proxy-Require: ms-benotify\r\n"
2467 "Supported: ms-piggyback-first-notify\r\n"
2468 "Contact: %s\r\n"
2469 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2471 gchar *body=g_strdup(
2472 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2473 "<roaming type=\"categories\"/>"
2474 "<roaming type=\"containers\"/>"
2475 "<roaming type=\"subscribers\"/></roamingList>");
2477 g_free(tmp);
2478 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2479 g_free(body);
2480 g_free(to);
2481 g_free(hdr);
2485 * For 2005 version
2487 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
2489 gchar *to = sip_uri_self(sip);
2490 gchar *tmp = get_contact(sip);
2491 gchar *hdr = g_strdup_printf(
2492 "Event: vnd-microsoft-provisioning\r\n"
2493 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2494 "Supported: com.microsoft.autoextend\r\n"
2495 "Supported: ms-benotify\r\n"
2496 "Proxy-Require: ms-benotify\r\n"
2497 "Supported: ms-piggyback-first-notify\r\n"
2498 "Expires: 0\r\n"
2499 "Contact: %s\r\n", tmp);
2501 g_free(tmp);
2502 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2503 g_free(to);
2504 g_free(hdr);
2507 /** Subscription for provisioning information to help with initial
2508 * configuration. This subscription is a one-time query (denoted by the Expires header,
2509 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2510 * configuration, meeting policies, and policy settings that Communicator must enforce.
2511 * TODO: for what we need this information.
2514 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
2516 gchar *to = sip_uri_self(sip);
2517 gchar *tmp = get_contact(sip);
2518 gchar *hdr = g_strdup_printf(
2519 "Event: vnd-microsoft-provisioning-v2\r\n"
2520 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2521 "Supported: com.microsoft.autoextend\r\n"
2522 "Supported: ms-benotify\r\n"
2523 "Proxy-Require: ms-benotify\r\n"
2524 "Supported: ms-piggyback-first-notify\r\n"
2525 "Expires: 0\r\n"
2526 "Contact: %s\r\n"
2527 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2528 gchar *body = g_strdup(
2529 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2530 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2531 "<provisioningGroup name=\"ucPolicy\"/>"
2532 "</provisioningGroupList>");
2534 g_free(tmp);
2535 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2536 g_free(body);
2537 g_free(to);
2538 g_free(hdr);
2541 /* IM Session (INVITE and MESSAGE methods) */
2543 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2544 static gchar *
2545 get_end_points (struct sipe_account_data *sip,
2546 struct sip_session *session)
2548 gchar *res;
2550 if (session == NULL) {
2551 return NULL;
2554 res = g_strdup_printf("<sip:%s>", sip->username);
2556 SIPE_DIALOG_FOREACH {
2557 gchar *tmp = res;
2558 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2559 g_free(tmp);
2561 if (dialog->theirepid) {
2562 tmp = res;
2563 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2564 g_free(tmp);
2566 } SIPE_DIALOG_FOREACH_END;
2568 return res;
2571 static gboolean
2572 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
2573 struct sipmsg *msg,
2574 SIPE_UNUSED_PARAMETER struct transaction *trans)
2576 gboolean ret = TRUE;
2578 if (msg->response != 200) {
2579 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2580 return FALSE;
2583 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2585 return ret;
2589 * Asks UA/proxy about its capabilities.
2591 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2593 gchar *to = sip_uri(who);
2594 gchar *contact = get_contact(sip);
2595 gchar *request = g_strdup_printf(
2596 "Accept: application/sdp\r\n"
2597 "Contact: %s\r\n", contact);
2598 g_free(contact);
2600 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2602 g_free(to);
2603 g_free(request);
2606 static void
2607 sipe_notify_user(struct sipe_account_data *sip,
2608 struct sip_session *session,
2609 PurpleMessageFlags flags,
2610 const gchar *message)
2612 PurpleConversation *conv;
2614 if (!session->conv) {
2615 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
2616 } else {
2617 conv = session->conv;
2619 purple_conversation_write(conv, NULL, message, flags, time(NULL));
2622 void
2623 sipe_present_info(struct sipe_account_data *sip,
2624 struct sip_session *session,
2625 const gchar *message)
2627 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
2630 static void
2631 sipe_present_err(struct sipe_account_data *sip,
2632 struct sip_session *session,
2633 const gchar *message)
2635 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
2638 void
2639 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
2640 struct sip_session *session,
2641 const gchar *who,
2642 const gchar *message)
2644 char *msg, *msg_tmp;
2646 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2647 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2648 g_free(msg_tmp);
2649 msg_tmp = g_strdup_printf( _("This message was not delivered to %s because one or more recipients are offline:\n%s") ,
2650 who ? who : "", msg ? msg : "");
2651 sipe_present_err(sip, session, msg_tmp);
2652 g_free(msg_tmp);
2653 g_free(msg);
2657 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
2659 static gboolean
2660 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
2661 SIPE_UNUSED_PARAMETER struct transaction *trans)
2663 gboolean ret = TRUE;
2664 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2665 struct sip_session *session = sipe_session_find_im(sip, with);
2666 struct sip_dialog *dialog;
2667 gchar *cseq;
2668 char *key;
2669 gchar *message;
2671 if (!session) {
2672 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2673 g_free(with);
2674 return FALSE;
2677 dialog = sipe_dialog_find(session, with);
2678 if (!dialog) {
2679 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2680 g_free(with);
2681 return FALSE;
2684 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2685 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2686 g_free(cseq);
2687 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2689 if (msg->response >= 400) {
2690 PurpleBuddy *pbuddy;
2691 gchar *alias = with;
2693 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
2695 if ((pbuddy = purple_find_buddy(sip->account, with))) {
2696 alias = (gchar *)purple_buddy_get_alias(pbuddy);
2699 sipe_present_message_undelivered_err(sip, session, alias, message);
2700 ret = FALSE;
2701 } else {
2702 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
2703 if (message_id) {
2704 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
2705 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
2706 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
2709 g_hash_table_remove(session->unconfirmed_messages, key);
2710 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2711 key, g_hash_table_size(session->unconfirmed_messages));
2714 g_free(key);
2715 g_free(with);
2717 if (ret) sipe_im_process_queue(sip, session);
2718 return ret;
2721 static gboolean
2722 sipe_is_election_finished(struct sip_session *session);
2724 static void
2725 sipe_election_result(struct sipe_account_data *sip,
2726 void *sess);
2728 static gboolean
2729 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
2730 SIPE_UNUSED_PARAMETER struct transaction *trans)
2732 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2733 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2734 struct sip_dialog *dialog;
2735 struct sip_session *session;
2737 session = sipe_session_find_chat_by_callid(sip, callid);
2738 if (!session) {
2739 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2740 return FALSE;
2743 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2744 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2745 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2746 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2748 if (xn_request_rm_response) {
2749 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2750 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2752 dialog = sipe_dialog_find(session, with);
2753 if (!dialog) {
2754 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2755 return FALSE;
2758 if (allow && !g_strcasecmp(allow, "true")) {
2759 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2760 dialog->election_vote = 1;
2761 } else if (allow && !g_strcasecmp(allow, "false")) {
2762 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2763 dialog->election_vote = -1;
2766 if (sipe_is_election_finished(session)) {
2767 sipe_election_result(sip, session);
2770 } else if (xn_set_rm_response) {
2773 xmlnode_free(xn_action);
2777 return TRUE;
2780 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2782 gchar *hdr;
2783 gchar *tmp;
2784 char *msgformat;
2785 char *msgtext;
2786 gchar *msgr_value;
2787 gchar *msgr;
2789 sipe_parse_html(msg, &msgformat, &msgtext);
2790 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2792 msgr_value = sipmsg_get_msgr_string(msgformat);
2793 g_free(msgformat);
2794 if (msgr_value) {
2795 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2796 g_free(msgr_value);
2797 } else {
2798 msgr = g_strdup("");
2801 tmp = get_contact(sip);
2802 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2803 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2804 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2805 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2806 g_free(tmp);
2807 g_free(msgr);
2809 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2810 g_free(msgtext);
2811 g_free(hdr);
2815 static void
2816 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
2818 GSList *entry2 = session->outgoing_message_queue;
2819 while (entry2) {
2820 char *queued_msg = entry2->data;
2822 /* for multiparty chat or conference */
2823 if (session->is_multiparty || session->focus_uri) {
2824 gchar *who = sip_uri_self(sip);
2825 serv_got_chat_in(sip->gc, session->chat_id, who,
2826 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2827 g_free(who);
2830 SIPE_DIALOG_FOREACH {
2831 char *key;
2833 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
2835 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2836 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2837 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2838 key, g_hash_table_size(session->unconfirmed_messages));
2839 g_free(key);
2841 sipe_send_message(sip, dialog, queued_msg);
2842 } SIPE_DIALOG_FOREACH_END;
2844 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2845 g_free(queued_msg);
2849 static void
2850 sipe_refer_notify(struct sipe_account_data *sip,
2851 struct sip_session *session,
2852 const gchar *who,
2853 int status,
2854 const gchar *desc)
2856 gchar *hdr;
2857 gchar *body;
2858 struct sip_dialog *dialog = sipe_dialog_find(session, who);
2860 hdr = g_strdup_printf(
2861 "Event: refer\r\n"
2862 "Subscription-State: %s\r\n"
2863 "Content-Type: message/sipfrag\r\n",
2864 status >= 200 ? "terminated" : "active");
2866 body = g_strdup_printf(
2867 "SIP/2.0 %d %s\r\n",
2868 status, desc);
2870 send_sip_request(sip->gc, "NOTIFY", who, who, hdr, body, dialog, NULL);
2872 g_free(hdr);
2873 g_free(body);
2876 static gboolean
2877 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2879 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2880 struct sip_session *session;
2881 struct sip_dialog *dialog;
2882 char *cseq;
2883 char *key;
2884 gchar *message;
2885 struct sipmsg *request_msg = trans->msg;
2887 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2888 gchar *referred_by;
2890 session = sipe_session_find_chat_by_callid(sip, callid);
2891 if (!session) {
2892 session = sipe_session_find_im(sip, with);
2894 if (!session) {
2895 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2896 g_free(with);
2897 return FALSE;
2900 dialog = sipe_dialog_find(session, with);
2901 if (!dialog) {
2902 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2903 g_free(with);
2904 return FALSE;
2907 sipe_dialog_parse(dialog, msg, TRUE);
2909 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2910 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2911 g_free(cseq);
2912 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2914 if (msg->response != 200) {
2915 PurpleBuddy *pbuddy;
2916 gchar *alias = with;
2918 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2920 if ((pbuddy = purple_find_buddy(sip->account, with))) {
2921 alias = (gchar *)purple_buddy_get_alias(pbuddy);
2924 if (message) {
2925 sipe_present_message_undelivered_err(sip, session, alias, message);
2926 } else {
2927 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
2928 sipe_present_err(sip, session, tmp_msg);
2929 g_free(tmp_msg);
2932 sipe_dialog_remove(session, with);
2934 g_free(key);
2935 g_free(with);
2936 return FALSE;
2939 dialog->cseq = 0;
2940 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
2941 dialog->outgoing_invite = NULL;
2942 dialog->is_established = TRUE;
2944 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
2945 if (referred_by) {
2946 sipe_refer_notify(sip, session, referred_by, 200, "OK");
2947 g_free(referred_by);
2950 /* add user to chat if it is a multiparty session */
2951 if (session->is_multiparty) {
2952 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
2953 with, NULL,
2954 PURPLE_CBFLAGS_NONE, TRUE);
2957 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2958 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2959 if (session->outgoing_message_queue) {
2960 char *queued_msg = session->outgoing_message_queue->data;
2961 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2962 g_free(queued_msg);
2966 sipe_im_process_queue(sip, session);
2968 g_hash_table_remove(session->unconfirmed_messages, key);
2969 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2970 key, g_hash_table_size(session->unconfirmed_messages));
2972 g_free(key);
2973 g_free(with);
2974 return TRUE;
2978 void
2979 sipe_invite(struct sipe_account_data *sip,
2980 struct sip_session *session,
2981 const gchar *who,
2982 const gchar *msg_body,
2983 const gchar *referred_by,
2984 const gboolean is_triggered)
2986 gchar *hdr;
2987 gchar *to;
2988 gchar *contact;
2989 gchar *body;
2990 gchar *self;
2991 char *ms_text_format = g_strdup("");
2992 gchar *roster_manager;
2993 gchar *end_points;
2994 gchar *referred_by_str;
2995 struct sip_dialog *dialog = sipe_dialog_find(session, who);
2997 if (dialog && dialog->is_established) {
2998 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
2999 return;
3002 if (!dialog) {
3003 dialog = sipe_dialog_add(session);
3004 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3005 dialog->with = g_strdup(who);
3008 if (!(dialog->ourtag)) {
3009 dialog->ourtag = gentag();
3012 to = sip_uri(who);
3014 if (msg_body) {
3015 char *msgformat;
3016 char *msgtext;
3017 char *base64_msg;
3018 gchar *msgr_value;
3019 gchar *msgr;
3020 char *key;
3022 sipe_parse_html(msg_body, &msgformat, &msgtext);
3023 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat);
3025 msgr_value = sipmsg_get_msgr_string(msgformat);
3026 g_free(msgformat);
3027 msgr = "";
3028 if (msgr_value) {
3029 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3030 g_free(msgr_value);
3033 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3034 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3035 g_free(msgtext);
3036 g_free(msgr);
3037 g_free(base64_msg);
3039 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3040 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3041 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
3042 key, g_hash_table_size(session->unconfirmed_messages));
3043 g_free(key);
3046 contact = get_contact(sip);
3047 end_points = get_end_points(sip, session);
3048 self = sip_uri_self(sip);
3049 roster_manager = g_strdup_printf(
3050 "Roster-Manager: %s\r\n"
3051 "EndPoints: %s\r\n",
3052 self,
3053 end_points);
3054 referred_by_str = referred_by ?
3055 g_strdup_printf(
3056 "Referred-By: %s\r\n",
3057 referred_by)
3058 : g_strdup("");
3059 hdr = g_strdup_printf(
3060 "Supported: ms-sender\r\n"
3061 "%s"
3062 "%s"
3063 "%s"
3064 "%s"
3065 "Contact: %s\r\n%s"
3066 "Content-Type: application/sdp\r\n",
3067 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3068 referred_by_str,
3069 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3070 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3071 contact,
3072 ms_text_format);
3073 g_free(ms_text_format);
3074 g_free(self);
3076 body = g_strdup_printf(
3077 "v=0\r\n"
3078 "o=- 0 0 IN IP4 %s\r\n"
3079 "s=session\r\n"
3080 "c=IN IP4 %s\r\n"
3081 "t=0 0\r\n"
3082 "m=message %d sip null\r\n"
3083 "a=accept-types:text/plain text/html image/gif "
3084 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3085 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3087 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3088 to, to, hdr, body, dialog, process_invite_response);
3090 g_free(to);
3091 g_free(roster_manager);
3092 g_free(end_points);
3093 g_free(referred_by_str);
3094 g_free(body);
3095 g_free(hdr);
3096 g_free(contact);
3099 static void
3100 sipe_refer(struct sipe_account_data *sip,
3101 struct sip_session *session,
3102 const gchar *who)
3104 gchar *hdr;
3105 gchar *contact;
3106 struct sip_dialog *dialog = sipe_dialog_find(session,
3107 session->roster_manager);
3109 contact = get_contact(sip);
3110 hdr = g_strdup_printf(
3111 "Contact: %s\r\n"
3112 "Refer-to: <%s>\r\n"
3113 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3114 "Require: com.microsoft.rtc-multiparty\r\n",
3115 contact,
3116 who,
3117 sip->username,
3118 dialog->ourtag ? ";tag=" : "",
3119 dialog->ourtag ? dialog->ourtag : "",
3120 get_epid(sip));
3122 send_sip_request(sip->gc, "REFER",
3123 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3125 g_free(hdr);
3126 g_free(contact);
3129 static void
3130 sipe_send_election_request_rm(struct sipe_account_data *sip,
3131 struct sip_dialog *dialog,
3132 int bid)
3134 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3136 gchar *body = g_strdup_printf(
3137 "<?xml version=\"1.0\"?>\r\n"
3138 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3139 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3140 sip->username, bid);
3142 send_sip_request(sip->gc, "INFO",
3143 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3145 g_free(body);
3148 static void
3149 sipe_send_election_set_rm(struct sipe_account_data *sip,
3150 struct sip_dialog *dialog)
3152 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3154 gchar *body = g_strdup_printf(
3155 "<?xml version=\"1.0\"?>\r\n"
3156 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3157 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3158 sip->username);
3160 send_sip_request(sip->gc, "INFO",
3161 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3163 g_free(body);
3166 static void
3167 sipe_session_close(struct sipe_account_data *sip,
3168 struct sip_session * session)
3170 if (session && session->focus_uri) {
3171 conf_session_close(sip, session);
3174 if (session) {
3175 SIPE_DIALOG_FOREACH {
3176 /* @TODO slow down BYE message sending rate */
3177 /* @see single subscription code */
3178 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3179 } SIPE_DIALOG_FOREACH_END;
3181 sipe_session_remove(sip, session);
3185 static void
3186 sipe_session_close_all(struct sipe_account_data *sip)
3188 GSList *entry;
3189 while ((entry = sip->sessions) != NULL) {
3190 sipe_session_close(sip, entry->data);
3194 static void
3195 sipe_convo_closed(PurpleConnection * gc, const char *who)
3197 struct sipe_account_data *sip = gc->proto_data;
3199 purple_debug_info("sipe", "conversation with %s closed\n", who);
3200 sipe_session_close(sip, sipe_session_find_im(sip, who));
3203 static void
3204 sipe_chat_leave (PurpleConnection *gc, int id)
3206 struct sipe_account_data *sip = gc->proto_data;
3207 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
3209 sipe_session_close(sip, session);
3212 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3213 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3215 struct sipe_account_data *sip = gc->proto_data;
3216 struct sip_session *session;
3217 struct sip_dialog *dialog;
3219 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3221 session = sipe_session_find_or_add_im(sip, who);
3222 dialog = sipe_dialog_find(session, who);
3224 // Queue the message
3225 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3227 if (dialog && dialog->callid) {
3228 sipe_im_process_queue(sip, session);
3229 } else if (!dialog || !dialog->outgoing_invite) {
3230 // Need to send the INVITE to get the outgoing dialog setup
3231 sipe_invite(sip, session, who, what, NULL, FALSE);
3234 return 1;
3237 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
3238 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3240 struct sipe_account_data *sip = gc->proto_data;
3241 struct sip_session *session;
3243 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3245 session = sipe_session_find_chat_by_id(sip, id);
3247 // Queue the message
3248 if (session && session->dialogs) {
3249 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3250 g_strdup(what));
3251 sipe_im_process_queue(sip, session);
3254 return 1;
3257 /* End IM Session (INVITE and MESSAGE methods) */
3259 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3261 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3262 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3263 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3264 struct sip_session *session;
3266 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg->body ? msg->body : "");
3268 session = sipe_session_find_chat_by_callid(sip, callid);
3269 if (!session) {
3270 session = sipe_session_find_im(sip, from);
3272 if (!session) {
3273 g_free(from);
3274 return;
3277 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3278 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3279 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3280 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3282 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
3284 if (xn_request_rm) {
3285 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3286 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3287 gchar *body = g_strdup_printf(
3288 "<?xml version=\"1.0\"?>\r\n"
3289 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3290 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3291 sip->username,
3292 session->bid < bid ? "true" : "false");
3293 send_sip_response(sip->gc, msg, 200, "OK", body);
3294 g_free(body);
3295 } else if (xn_set_rm) {
3296 gchar *body;
3297 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3298 g_free(session->roster_manager);
3299 session->roster_manager = g_strdup(rm);
3301 body = g_strdup_printf(
3302 "<?xml version=\"1.0\"?>\r\n"
3303 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3304 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3305 sip->username);
3306 send_sip_response(sip->gc, msg, 200, "OK", body);
3307 g_free(body);
3309 xmlnode_free(xn_action);
3311 } else {
3312 /* looks like purple lacks typing notification for chat */
3313 if (!session->is_multiparty && !session->focus_uri) {
3314 xmlnode *xn_keyboard_activity = xmlnode_from_str(msg->body, msg->bodylen);
3315 const char *status = xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity, "status"),
3316 "status");
3317 if (status && !strcmp(status, "type")) {
3318 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3319 } else if (status && !strcmp(status, "idle")) {
3320 serv_got_typing_stopped(sip->gc, from);
3322 xmlnode_free(xn_keyboard_activity);
3325 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3327 g_free(from);
3330 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3332 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3333 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3334 struct sip_session *session;
3336 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3338 session = sipe_session_find_chat_by_callid(sip, callid);
3339 if (!session) {
3340 session = sipe_session_find_im(sip, from);
3342 if (!session) {
3343 g_free(from);
3344 return;
3347 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3348 g_free(session->roster_manager);
3349 session->roster_manager = NULL;
3352 /* This what BYE is essentially for - terminating dialog */
3353 sipe_dialog_remove(session, from);
3354 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
3355 sipe_conf_immcu_closed(sip, session);
3356 } else if (session->is_multiparty) {
3357 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3360 g_free(from);
3363 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3365 gchar *self = sip_uri_self(sip);
3366 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3367 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3368 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3369 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3370 struct sip_session *session;
3371 struct sip_dialog *dialog;
3373 session = sipe_session_find_chat_by_callid(sip, callid);
3374 dialog = sipe_dialog_find(session, from);
3376 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3377 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3378 } else {
3379 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3381 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3384 g_free(self);
3385 g_free(from);
3386 g_free(refer_to);
3387 g_free(referred_by);
3390 static unsigned int
3391 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3393 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3394 struct sip_session *session;
3395 struct sip_dialog *dialog;
3397 if (state == PURPLE_NOT_TYPING)
3398 return 0;
3400 session = sipe_session_find_im(sip, who);
3401 dialog = sipe_dialog_find(session, who);
3403 if (session && dialog && dialog->is_established) {
3404 send_sip_request(gc, "INFO", who, who,
3405 "Content-Type: application/xml\r\n",
3406 SIPE_SEND_TYPING, dialog, NULL);
3408 return SIPE_TYPING_SEND_TIMEOUT;
3411 static gboolean resend_timeout(struct sipe_account_data *sip)
3413 GSList *tmp = sip->transactions;
3414 time_t currtime = time(NULL);
3415 while (tmp) {
3416 struct transaction *trans = tmp->data;
3417 tmp = tmp->next;
3418 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3419 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3420 /* TODO 408 */
3421 } else {
3422 if ((currtime - trans->time > 2) && trans->retries == 0) {
3423 trans->retries++;
3424 sendout_sipmsg(sip, trans->msg);
3428 return TRUE;
3431 static void do_reauthenticate_cb(struct sipe_account_data *sip,
3432 SIPE_UNUSED_PARAMETER void *unused)
3434 /* register again when security token expires */
3435 /* we have to start a new authentication as the security token
3436 * is almost expired by sending a not signed REGISTER message */
3437 purple_debug_info("sipe", "do a full reauthentication\n");
3438 sipe_auth_free(&sip->registrar);
3439 sipe_auth_free(&sip->proxy);
3440 sip->registerstatus = 0;
3441 do_register(sip);
3442 sip->reauthenticate_set = FALSE;
3445 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3447 gchar *from;
3448 gchar *contenttype;
3449 gboolean found = FALSE;
3451 from = parse_from(sipmsg_find_header(msg, "From"));
3453 if (!from) return;
3455 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3457 contenttype = sipmsg_find_header(msg, "Content-Type");
3458 if (!strncmp(contenttype, "text/plain", 10)
3459 || !strncmp(contenttype, "text/html", 9)
3460 || !strncmp(contenttype, "multipart/related", 21))
3462 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3463 gchar *html = get_html_message(contenttype, msg->body);
3465 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
3466 if (!session) {
3467 session = sipe_session_find_im(sip, from);
3470 if (session && session->focus_uri) { /* a conference */
3471 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
3472 gchar *sender = parse_from(tmp);
3473 g_free(tmp);
3474 serv_got_chat_in(sip->gc, session->chat_id, sender,
3475 PURPLE_MESSAGE_RECV, html, time(NULL));
3476 g_free(sender);
3477 } else if (session && session->is_multiparty) { /* a multiparty chat */
3478 serv_got_chat_in(sip->gc, session->chat_id, from,
3479 PURPLE_MESSAGE_RECV, html, time(NULL));
3480 } else {
3481 serv_got_im(sip->gc, from, html, 0, time(NULL));
3483 g_free(html);
3484 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3485 found = TRUE;
3487 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3488 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3489 xmlnode *state;
3490 gchar *statedata;
3492 if (!isc) {
3493 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3494 return;
3497 state = xmlnode_get_child(isc, "state");
3499 if (!state) {
3500 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3501 xmlnode_free(isc);
3502 return;
3505 statedata = xmlnode_get_data(state);
3506 if (statedata) {
3507 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3508 else serv_got_typing_stopped(sip->gc, from);
3510 g_free(statedata);
3512 xmlnode_free(isc);
3513 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3514 found = TRUE;
3516 if (!found) {
3517 purple_debug_info("sipe", "got unknown mime-type");
3518 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3520 g_free(from);
3523 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3525 gchar *ms_text_format;
3526 gchar *body;
3527 gchar *newTag;
3528 gchar *oldHeader;
3529 gchar *newHeader;
3530 gboolean is_multiparty = FALSE;
3531 gboolean is_triggered = FALSE;
3532 gboolean was_multiparty = TRUE;
3533 gboolean just_joined = FALSE;
3534 gchar *from;
3535 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3536 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
3537 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
3538 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
3539 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3540 GSList *end_points = NULL;
3541 struct sip_session *session;
3543 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3545 /* Invitation to join conference */
3546 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
3547 process_incoming_invite_conf(sip, msg);
3548 return;
3551 /* Only accept text invitations */
3552 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3553 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3554 return;
3557 // TODO There *must* be a better way to clean up the To header to add a tag...
3558 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3559 oldHeader = sipmsg_find_header(msg, "To");
3560 newTag = gentag();
3561 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3562 sipmsg_remove_header_now(msg, "To");
3563 sipmsg_add_header_now(msg, "To", newHeader);
3564 g_free(newHeader);
3566 if (end_points_hdr) {
3567 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
3569 if (g_slist_length(end_points) > 2) {
3570 is_multiparty = TRUE;
3573 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
3574 is_triggered = TRUE;
3575 is_multiparty = TRUE;
3578 session = sipe_session_find_chat_by_callid(sip, callid);
3579 /* Convert to multiparty */
3580 if (session && is_multiparty && !session->is_multiparty) {
3581 g_free(session->with);
3582 session->with = NULL;
3583 was_multiparty = FALSE;
3584 session->is_multiparty = TRUE;
3585 session->chat_id = rand();
3588 if (!session && is_multiparty) {
3589 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
3591 /* IM session */
3592 from = parse_from(sipmsg_find_header(msg, "From"));
3593 if (!session) {
3594 session = sipe_session_find_or_add_im(sip, from);
3597 g_free(session->callid);
3598 session->callid = g_strdup(callid);
3600 session->is_multiparty = is_multiparty;
3601 if (roster_manager) {
3602 session->roster_manager = g_strdup(roster_manager);
3605 if (is_multiparty && end_points) {
3606 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3607 GSList *entry = end_points;
3608 while (entry) {
3609 struct sip_dialog *dialog;
3610 struct sipendpoint *end_point = entry->data;
3611 entry = entry->next;
3613 if (!g_strcasecmp(from, end_point->contact) ||
3614 !g_strcasecmp(to, end_point->contact))
3615 continue;
3617 dialog = sipe_dialog_find(session, end_point->contact);
3618 if (dialog) {
3619 g_free(dialog->theirepid);
3620 dialog->theirepid = end_point->epid;
3621 end_point->epid = NULL;
3622 } else {
3623 dialog = sipe_dialog_add(session);
3625 dialog->callid = g_strdup(session->callid);
3626 dialog->with = end_point->contact;
3627 end_point->contact = NULL;
3628 dialog->theirepid = end_point->epid;
3629 end_point->epid = NULL;
3631 just_joined = TRUE;
3633 /* send triggered INVITE */
3634 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
3637 g_free(to);
3640 if (end_points) {
3641 GSList *entry = end_points;
3642 while (entry) {
3643 struct sipendpoint *end_point = entry->data;
3644 entry = entry->next;
3645 g_free(end_point->contact);
3646 g_free(end_point->epid);
3647 g_free(end_point);
3649 g_slist_free(end_points);
3652 if (session) {
3653 struct sip_dialog *dialog = sipe_dialog_find(session, from);
3654 if (dialog) {
3655 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3656 } else {
3657 dialog = sipe_dialog_add(session);
3659 dialog->callid = g_strdup(session->callid);
3660 dialog->with = g_strdup(from);
3661 sipe_dialog_parse(dialog, msg, FALSE);
3663 if (!dialog->ourtag) {
3664 dialog->ourtag = newTag;
3665 newTag = NULL;
3668 just_joined = TRUE;
3670 } else {
3671 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3673 g_free(newTag);
3675 if (is_multiparty && !session->conv) {
3676 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3677 gchar *self = sip_uri_self(sip);
3678 /* create prpl chat */
3679 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3680 session->chat_name = g_strdup(chat_name);
3681 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
3682 /* add self */
3683 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3684 self, NULL,
3685 PURPLE_CBFLAGS_NONE, FALSE);
3686 g_free(chat_name);
3687 g_free(self);
3690 if (is_multiparty && !was_multiparty) {
3691 /* add current IM counterparty to chat */
3692 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3693 sipe_dialog_first(session)->with, NULL,
3694 PURPLE_CBFLAGS_NONE, FALSE);
3697 /* add inviting party to chat */
3698 if (just_joined && session->conv) {
3699 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3700 from, NULL,
3701 PURPLE_CBFLAGS_NONE, TRUE);
3704 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3705 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3706 if (ms_text_format) {
3707 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3709 gchar *html = get_html_message(ms_text_format, NULL);
3710 if (html) {
3711 if (is_multiparty) {
3712 serv_got_chat_in(sip->gc, session->chat_id, from,
3713 PURPLE_MESSAGE_RECV, html, time(NULL));
3714 } else {
3715 serv_got_im(sip->gc, from, html, 0, time(NULL));
3717 g_free(html);
3718 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3722 g_free(from);
3724 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3725 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3726 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3728 body = g_strdup_printf(
3729 "v=0\r\n"
3730 "o=- 0 0 IN IP4 %s\r\n"
3731 "s=session\r\n"
3732 "c=IN IP4 %s\r\n"
3733 "t=0 0\r\n"
3734 "m=message %d sip sip:%s\r\n"
3735 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3736 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3737 sip->realport, sip->username);
3738 send_sip_response(sip->gc, msg, 200, "OK", body);
3739 g_free(body);
3742 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3744 gchar *body;
3746 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
3747 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3748 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3750 body = g_strdup_printf(
3751 "v=0\r\n"
3752 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3753 "s=session\r\n"
3754 "c=IN IP4 0.0.0.0\r\n"
3755 "t=0 0\r\n"
3756 "m=message %d sip sip:%s\r\n"
3757 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3758 sip->realport, sip->username);
3759 send_sip_response(sip->gc, msg, 200, "OK", body);
3760 g_free(body);
3763 static void sipe_connection_cleanup(struct sipe_account_data *);
3764 static void create_connection(struct sipe_account_data *, gchar *, int);
3766 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3768 gchar *tmp;
3769 const gchar *expires_header;
3770 int expires, i;
3771 GSList *hdr = msg->headers;
3772 struct siphdrelement *elem;
3774 expires_header = sipmsg_find_header(msg, "Expires");
3775 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3776 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3778 switch (msg->response) {
3779 case 200:
3780 if (expires == 0) {
3781 sip->registerstatus = 0;
3782 } else {
3783 gchar *contact_hdr = NULL;
3784 gchar *gruu = NULL;
3785 gchar *epid;
3786 gchar *uuid;
3787 gchar *timeout;
3789 if (!sip->reregister_set) {
3790 gchar *action_name = g_strdup_printf("<%s>", "registration");
3791 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3792 g_free(action_name);
3793 sip->reregister_set = TRUE;
3796 sip->registerstatus = 3;
3798 #ifdef USE_KERBEROS
3799 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3800 #endif
3801 tmp = sipmsg_find_auth_header(msg, "NTLM");
3802 #ifdef USE_KERBEROS
3803 } else {
3804 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3806 #endif
3807 if (tmp) {
3808 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3809 fill_auth(tmp, &sip->registrar);
3812 if (!sip->reauthenticate_set) {
3813 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3814 guint reauth_timeout;
3815 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3816 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3817 reauth_timeout = sip->registrar.expires - 300;
3818 } else {
3819 /* NTLM: we have to reauthenticate as our security token expires
3820 after eight hours (be five minutes early) */
3821 reauth_timeout = (8 * 3600) - 300;
3823 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3824 g_free(action_name);
3825 sip->reauthenticate_set = TRUE;
3828 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3830 epid = get_epid(sip);
3831 uuid = generateUUIDfromEPID(epid);
3832 g_free(epid);
3834 // There can be multiple Contact headers (one per location where the user is logged in) so
3835 // make sure to only get the one for this uuid
3836 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3837 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3838 if (valid_contact) {
3839 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3840 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3841 g_free(valid_contact);
3842 break;
3843 } else {
3844 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3847 g_free(uuid);
3849 g_free(sip->contact);
3850 if(gruu) {
3851 sip->contact = g_strdup_printf("<%s>", gruu);
3852 g_free(gruu);
3853 } else {
3854 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3855 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);
3857 sip->msrtc_event_categories = FALSE;
3858 sip->batched_support = FALSE;
3860 while(hdr)
3862 elem = hdr->data;
3863 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3864 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3865 sip->msrtc_event_categories = TRUE;
3866 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3868 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3869 sip->batched_support = TRUE;
3870 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3873 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3874 gchar **caps = g_strsplit(elem->value,",",0);
3875 i = 0;
3876 while (caps[i]) {
3877 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3878 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3879 i++;
3881 g_strfreev(caps);
3883 hdr = g_slist_next(hdr);
3886 /* subscriptions */
3887 if (!sip->subscribed) { //do it just once, not every re-register
3889 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
3890 (GCompareFunc)g_ascii_strcasecmp)) {
3891 sipe_subscribe_roaming_contacts(sip);
3894 /* For 2007+ it does not make sence to subscribe to:
3895 * vnd-microsoft-roaming-ACL
3896 * vnd-microsoft-provisioning (not v2)
3897 * presence.wpending
3898 * These are for backward compatibility.
3900 if (sip->msrtc_event_categories)
3902 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
3903 (GCompareFunc)g_ascii_strcasecmp)) {
3904 sipe_subscribe_roaming_self(sip);
3906 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
3907 (GCompareFunc)g_ascii_strcasecmp)) {
3908 sipe_subscribe_roaming_provisioning_v2(sip);
3911 /* For 2005- servers */
3912 else
3914 //sipe_options_request(sip, sip->sipdomain);
3916 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
3917 (GCompareFunc)g_ascii_strcasecmp)) {
3918 sipe_subscribe_roaming_acl(sip);
3920 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
3921 (GCompareFunc)g_ascii_strcasecmp)) {
3922 sipe_subscribe_roaming_provisioning(sip);
3924 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
3925 (GCompareFunc)g_ascii_strcasecmp)) {
3926 sipe_subscribe_presence_wpending(sip, msg);
3929 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3930 sip->subscribed = TRUE;
3933 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3934 "timeout=", ";", NULL);
3935 if (timeout != NULL) {
3936 sscanf(timeout, "%u", &sip->keepalive_timeout);
3937 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3938 sip->keepalive_timeout);
3939 g_free(timeout);
3942 // Should we remove the transaction here?
3943 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3944 transactions_remove(sip, tc);
3946 break;
3947 case 301:
3949 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3951 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3952 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3953 gchar **tmp;
3954 gchar *hostname;
3955 int port = 0;
3956 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3957 int i = 1;
3959 tmp = g_strsplit(parts[0], ":", 0);
3960 hostname = g_strdup(tmp[0]);
3961 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3962 g_strfreev(tmp);
3964 while (parts[i]) {
3965 tmp = g_strsplit(parts[i], "=", 0);
3966 if (tmp[1]) {
3967 if (g_strcasecmp("transport", tmp[0]) == 0) {
3968 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3969 transport = SIPE_TRANSPORT_TCP;
3970 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3971 transport = SIPE_TRANSPORT_UDP;
3975 g_strfreev(tmp);
3976 i++;
3978 g_strfreev(parts);
3980 /* Close old connection */
3981 sipe_connection_cleanup(sip);
3983 /* Create new connection */
3984 sip->transport = transport;
3985 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3986 hostname, port, TRANSPORT_DESCRIPTOR);
3987 create_connection(sip, hostname, port);
3989 g_free(redirect);
3991 break;
3992 case 401:
3993 if (sip->registerstatus != 2) {
3994 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3995 if (sip->registrar.retries > 3) {
3996 sip->gc->wants_to_die = TRUE;
3997 purple_connection_error(sip->gc, _("Wrong Password"));
3998 return TRUE;
4000 #ifdef USE_KERBEROS
4001 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4002 #endif
4003 tmp = sipmsg_find_auth_header(msg, "NTLM");
4004 #ifdef USE_KERBEROS
4005 } else {
4006 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4008 #endif
4009 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
4010 fill_auth(tmp, &sip->registrar);
4011 sip->registerstatus = 2;
4012 if (sip->account->disconnecting) {
4013 do_register_exp(sip, 0);
4014 } else {
4015 do_register(sip);
4018 break;
4019 case 403:
4021 gchar *warning = sipmsg_find_header(msg, "Warning");
4022 if (warning != NULL) {
4023 /* Example header:
4024 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4026 gchar **tmp = g_strsplit(warning, "\"", 0);
4027 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
4028 g_strfreev(tmp);
4029 } else {
4030 warning = g_strdup(_("You have been rejected by the server"));
4033 sip->gc->wants_to_die = TRUE;
4034 purple_connection_error(sip->gc, warning);
4035 g_free(warning);
4036 return TRUE;
4038 break;
4039 case 404:
4041 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4042 if (warning != NULL) {
4043 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4044 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
4045 g_free(reason);
4046 } else {
4047 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4050 sip->gc->wants_to_die = TRUE;
4051 purple_connection_error(sip->gc, warning);
4052 g_free(warning);
4053 return TRUE;
4055 break;
4056 case 503:
4058 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4059 if (warning != NULL) {
4060 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4061 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4062 g_free(reason);
4063 } else {
4064 warning = g_strdup(_("Service unavailable: no reason given"));
4067 sip->gc->wants_to_die = TRUE;
4068 purple_connection_error(sip->gc, warning);
4069 g_free(warning);
4070 return TRUE;
4072 break;
4074 return TRUE;
4077 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4079 const char *uri;
4080 xmlnode *xn_categories;
4081 xmlnode *xn_category;
4082 xmlnode *xn_node;
4083 const char *activity = NULL;
4085 xn_categories = xmlnode_from_str(data, len);
4086 uri = xmlnode_get_attrib(xn_categories, "uri");
4088 for (xn_category = xmlnode_get_child(xn_categories, "category");
4089 xn_category ;
4090 xn_category = xmlnode_get_next_twin(xn_category) )
4092 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4094 if (!strcmp(attrVar, "note"))
4096 if (uri) {
4097 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4099 if (sbuddy) {
4100 char *note;
4102 xn_node = xmlnode_get_child(xn_category, "note");
4103 if (!xn_node) continue;
4104 xn_node = xmlnode_get_child(xn_node, "body");
4105 if (!xn_node) continue;
4106 note = xmlnode_get_data(xn_node);
4107 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4108 g_free(sbuddy->annotation);
4109 sbuddy->annotation = NULL;
4110 if (note) sbuddy->annotation = g_strdup(note);
4111 g_free(note);
4116 else if(!strcmp(attrVar, "state"))
4118 char *data;
4119 int avail;
4120 xn_node = xmlnode_get_child(xn_category, "state");
4121 if (!xn_node) continue;
4122 xn_node = xmlnode_get_child(xn_node, "availability");
4123 if (!xn_node) continue;
4125 data = xmlnode_get_data(xn_node);
4126 avail = atoi(data);
4127 g_free(data);
4129 if (avail < 3000)
4130 activity = SIPE_STATUS_ID_UNKNOWN;
4131 else if (avail < 4500)
4132 activity = SIPE_STATUS_ID_AVAILABLE;
4133 else if (avail < 6000)
4134 activity = SIPE_STATUS_ID_BRB;
4135 else if (avail < 7500)
4136 activity = SIPE_STATUS_ID_ONPHONE;
4137 else if (avail < 9000)
4138 activity = SIPE_STATUS_ID_BUSY;
4139 else if (avail < 12000)
4140 activity = SIPE_STATUS_ID_DND;
4141 else if (avail < 18000)
4142 activity = SIPE_STATUS_ID_AWAY;
4143 else
4144 activity = SIPE_STATUS_ID_OFFLINE;
4147 if(activity) {
4148 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4149 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4152 xmlnode_free(xn_categories);
4155 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4157 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4158 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4159 payload->host = g_strdup(host);
4160 payload->buddies = server;
4161 sipe_subscribe_presence_batched_routed(sip, payload);
4162 sipe_subscribe_presence_batched_routed_free(payload);
4165 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4167 xmlnode *xn_list;
4168 xmlnode *xn_resource;
4169 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4170 g_free, NULL);
4171 GSList *server;
4172 gchar *host;
4174 xn_list = xmlnode_from_str(data, len);
4176 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4177 xn_resource;
4178 xn_resource = xmlnode_get_next_twin(xn_resource) )
4180 const char *uri, *state;
4181 xmlnode *xn_instance;
4183 xn_instance = xmlnode_get_child(xn_resource, "instance");
4184 if (!xn_instance) continue;
4186 uri = xmlnode_get_attrib(xn_resource, "uri");
4187 state = xmlnode_get_attrib(xn_instance, "state");
4188 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4190 if (strstr(state, "resubscribe")) {
4191 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4192 struct sipe_buddy *sbuddy;
4193 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4194 gchar *user = g_strdup(uri);
4195 host = g_strdup(poolFqdn);
4196 server = g_hash_table_lookup(servers, host);
4197 server = g_slist_append(server, user);
4198 g_hash_table_insert(servers, host, server);
4199 } else {
4200 sipe_subscribe_presence_single(sip, (void *) uri);
4202 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4203 if (sbuddy) {
4204 sbuddy->resubscribed = TRUE;
4209 /* Send out any deferred poolFqdn subscriptions */
4210 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4211 g_hash_table_destroy(servers);
4213 xmlnode_free(xn_list);
4216 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4218 const gchar *uri;
4219 gchar *getbasic;
4220 gchar *activity = NULL;
4221 xmlnode *pidf;
4222 xmlnode *basicstatus = NULL, *tuple, *status;
4223 gboolean isonline = FALSE;
4224 xmlnode *display_name_node;
4226 pidf = xmlnode_from_str(data, len);
4227 if (!pidf) {
4228 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4229 return;
4232 uri = xmlnode_get_attrib(pidf, "entity");
4234 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4236 if ((status = xmlnode_get_child(tuple, "status"))) {
4237 basicstatus = xmlnode_get_child(status, "basic");
4241 if (!basicstatus) {
4242 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4243 xmlnode_free(pidf);
4244 return;
4247 getbasic = xmlnode_get_data(basicstatus);
4248 if (!getbasic) {
4249 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4250 xmlnode_free(pidf);
4251 return;
4254 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4255 if (strstr(getbasic, "open")) {
4256 isonline = TRUE;
4258 g_free(getbasic);
4260 display_name_node = xmlnode_get_child(pidf, "display-name");
4261 // updating display name if alias was just URI
4262 if (display_name_node) {
4263 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4264 GSList *entry = buddies;
4265 PurpleBuddy *p_buddy;
4266 char * display_name = xmlnode_get_data(display_name_node);
4268 while (entry) {
4269 const char *server_alias;
4270 char *alias;
4272 p_buddy = entry->data;
4274 alias = (char *)purple_buddy_get_alias(p_buddy);
4275 alias = alias ? sip_uri_from_name(alias) : NULL;
4276 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4277 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4278 purple_blist_alias_buddy(p_buddy, display_name);
4280 g_free(alias);
4282 server_alias = purple_buddy_get_server_alias(p_buddy);
4283 if (display_name &&
4284 ( (server_alias && strcmp(display_name, server_alias))
4285 || !server_alias || strlen(server_alias) == 0 )
4287 purple_blist_server_alias_buddy(p_buddy, display_name);
4290 entry = entry->next;
4292 g_free(display_name);
4295 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4296 if ((status = xmlnode_get_child(tuple, "status"))) {
4297 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4298 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4299 activity = xmlnode_get_data(basicstatus);
4300 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4306 if (isonline) {
4307 const gchar * status_id = NULL;
4308 if (activity) {
4309 if (strstr(activity, "busy")) {
4310 status_id = SIPE_STATUS_ID_BUSY;
4311 } else if (strstr(activity, "away")) {
4312 status_id = SIPE_STATUS_ID_AWAY;
4316 if (!status_id) {
4317 status_id = SIPE_STATUS_ID_AVAILABLE;
4320 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4321 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4322 } else {
4323 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4326 g_free(activity);
4327 xmlnode_free(pidf);
4330 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4332 const char *availability;
4333 const char *activity;
4334 const char *display_name = NULL;
4335 const char *activity_name = NULL;
4336 const char *name;
4337 char *uri;
4338 int avl;
4339 int act;
4340 struct sipe_buddy *sbuddy;
4342 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4344 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4345 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4346 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4347 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4348 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4349 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4350 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4351 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4353 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4354 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4355 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4356 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4357 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4358 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4360 name = xmlnode_get_attrib(xn_presentity, "uri");
4361 uri = sip_uri_from_name(name);
4362 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4363 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4365 // updating display name if alias was just URI
4366 if (xn_display_name) {
4367 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4368 GSList *entry = buddies;
4369 PurpleBuddy *p_buddy;
4370 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4372 while (entry) {
4373 const char *email_str, *server_alias;
4375 p_buddy = entry->data;
4377 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4378 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4379 purple_blist_alias_buddy(p_buddy, display_name);
4382 server_alias = purple_buddy_get_server_alias(p_buddy);
4383 if (display_name &&
4384 ( (server_alias && strcmp(display_name, server_alias))
4385 || !server_alias || strlen(server_alias) == 0 )
4387 purple_blist_server_alias_buddy(p_buddy, display_name);
4390 if (email) {
4391 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4392 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4393 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4397 entry = entry->next;
4401 avl = atoi(availability);
4402 act = atoi(activity);
4404 if(sip->msrtc_event_categories){
4405 if (act == 100 && avl == 0)
4406 activity_name = SIPE_STATUS_ID_OFFLINE;
4407 else if (act == 100 && avl == 300)
4408 activity_name = SIPE_STATUS_ID_AWAY;
4409 else if (act == 300 && avl == 300)
4410 activity_name = SIPE_STATUS_ID_BRB;
4411 else if (act == 400 && avl == 300)
4412 activity_name = SIPE_STATUS_ID_AVAILABLE;
4413 else if (act == 500 && act == 300)
4414 activity_name = SIPE_STATUS_ID_ONPHONE;
4415 else if (act == 600 && avl == 300)
4416 activity_name = SIPE_STATUS_ID_BUSY;
4417 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4418 if(avail){ //Check for LegacyInterop elements
4419 avl = atoi(avail);
4420 if(avl == 18500)
4421 activity_name = SIPE_STATUS_ID_OFFLINE;
4422 else if (avl == 3500)
4423 activity_name = SIPE_STATUS_ID_AVAILABLE;
4424 else if (avl == 15500)
4425 activity_name = SIPE_STATUS_ID_AWAY;
4426 else if (avl == 6500)
4427 activity_name = SIPE_STATUS_ID_BUSY;
4428 else if (avl == 12500)
4429 activity_name = SIPE_STATUS_ID_BRB;
4434 if(activity_name == NULL){
4435 if (act <= 100)
4436 activity_name = SIPE_STATUS_ID_AWAY;
4437 else if (act <= 150)
4438 activity_name = SIPE_STATUS_ID_LUNCH;
4439 else if (act <= 300)
4440 activity_name = SIPE_STATUS_ID_BRB;
4441 else if (act <= 400)
4442 activity_name = SIPE_STATUS_ID_AVAILABLE;
4443 else if (act <= 500)
4444 activity_name = SIPE_STATUS_ID_ONPHONE;
4445 else if (act <= 600)
4446 activity_name = SIPE_STATUS_ID_BUSY;
4447 else
4448 activity_name = SIPE_STATUS_ID_AVAILABLE;
4450 if (avl == 0)
4451 activity_name = SIPE_STATUS_ID_OFFLINE;
4454 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4455 if (sbuddy)
4457 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4458 sbuddy->annotation = NULL;
4459 if (note) { sbuddy->annotation = g_strdup(note); }
4461 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4462 sbuddy->device_name = NULL;
4463 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4466 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4467 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4468 g_free(note);
4469 xmlnode_free(xn_presentity);
4470 g_free(uri);
4473 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4475 char *ctype = sipmsg_find_header(msg, "Content-Type");
4477 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4479 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4480 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4482 const char *content = msg->body;
4483 unsigned length = msg->bodylen;
4484 PurpleMimeDocument *mime = NULL;
4486 if (strstr(ctype, "multipart"))
4488 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4489 const char *content_type;
4490 GList* parts;
4491 mime = purple_mime_document_parse(doc);
4492 parts = purple_mime_document_get_parts(mime);
4493 while(parts) {
4494 content = purple_mime_part_get_data(parts->data);
4495 length = purple_mime_part_get_length(parts->data);
4496 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4497 if(content_type && strstr(content_type,"application/rlmi+xml"))
4499 process_incoming_notify_rlmi_resub(sip, content, length);
4501 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4503 process_incoming_notify_msrtc(sip, content, length);
4505 else
4507 process_incoming_notify_rlmi(sip, content, length);
4509 parts = parts->next;
4511 g_free(doc);
4513 if (mime)
4515 purple_mime_document_free(mime);
4518 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4520 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4522 else if(strstr(ctype, "application/rlmi+xml"))
4524 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4527 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4529 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4531 else
4533 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4537 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4539 char *ctype = sipmsg_find_header(msg, "Content-Type");
4540 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4542 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4544 if (ctype &&
4545 strstr(ctype, "multipart") &&
4546 (strstr(ctype, "application/rlmi+xml") ||
4547 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4548 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4549 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4550 GList *parts = purple_mime_document_get_parts(mime);
4551 GSList *buddies = NULL;
4552 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4554 while (parts) {
4555 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4556 purple_mime_part_get_length(parts->data));
4557 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
4559 buddies = g_slist_append(buddies, uri);
4560 xmlnode_free(xml);
4562 parts = parts->next;
4564 g_free(doc);
4565 if (mime) purple_mime_document_free(mime);
4567 payload->host = who;
4568 payload->buddies = buddies;
4569 sipe_schedule_action(action_name, timeout,
4570 sipe_subscribe_presence_batched_routed,
4571 sipe_subscribe_presence_batched_routed_free,
4572 sip, payload);
4573 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4575 } else {
4576 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4577 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4579 g_free(action_name);
4583 * Dispatcher for all incoming subscription information
4584 * whether it comes from NOTIFY, BENOTIFY requests or
4585 * piggy-backed to subscription's OK responce.
4587 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4588 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4590 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4592 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4593 gchar *event = sipmsg_find_header(msg, "Event");
4594 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4595 int timeout = 0;
4597 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4598 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n\n", subscription_state ? subscription_state : "");
4600 /* implicit subscriptions */
4601 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4602 sipe_process_imdn(sip, msg);
4605 if (!request)
4607 const gchar *expires_header;
4608 expires_header = sipmsg_find_header(msg, "Expires");
4609 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4610 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4611 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4614 /* for one off subscriptions (send with Expire: 0) */
4615 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
4617 sipe_process_provisioning_v2(sip, msg);
4620 if (!subscription_state || strstr(subscription_state, "active"))
4622 if (event && !g_ascii_strcasecmp(event, "presence"))
4624 sipe_process_presence(sip, msg);
4626 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4628 sipe_process_roaming_contacts(sip, msg);
4630 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
4632 sipe_process_roaming_self(sip, msg);
4634 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4636 sipe_process_roaming_acl(sip, msg);
4638 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4640 sipe_process_presence_wpending(sip, msg);
4642 else if (event && !g_ascii_strcasecmp(event, "conference"))
4644 sipe_process_conference(sip, msg);
4648 //The server sends a (BE)NOTIFY with the status 'terminated'
4649 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4650 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4651 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4652 g_free(from);
4655 if (timeout && event) {// For LSC 2005 and OCS 2007
4656 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4657 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4659 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4660 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4661 g_free(action_name);
4663 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4664 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4666 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4667 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4668 g_free(action_name);
4670 else*/
4671 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4672 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4674 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4675 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4676 g_free(action_name);
4678 else if (!g_ascii_strcasecmp(event, "presence") &&
4679 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4681 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4682 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4683 if(sip->batched_support) {
4684 gchar *my_self = sip_uri_self(sip);
4685 if(!g_ascii_strcasecmp(who, my_self)){
4686 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4687 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4688 g_free(who); /* unused */
4690 else {
4691 sipe_process_presence_timeout(sip, msg, who, timeout);
4693 g_free(my_self);
4695 else {
4696 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, who);
4697 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4699 g_free(action_name);
4700 /* "who" will be freed by the action we just scheduled */
4704 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4706 sipe_process_registration_notify(sip, msg);
4709 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4710 if (request && !benotify)
4712 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4717 * unused. Needed?
4719 static gchar* gen_xpidf(struct sipe_account_data *sip)
4721 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4722 "<presence>\r\n"
4723 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4724 "<display name=\"sip:%s\"/>\r\n"
4725 "<atom id=\"1234\">\r\n"
4726 "<address uri=\"sip:%s\">\r\n"
4727 "<status status=\"%s\"/>\r\n"
4728 "</address>\r\n"
4729 "</atom>\r\n"
4730 "</presence>\r\n",
4731 sip->username,
4732 sip->username,
4733 sip->username,
4734 sip->status);
4735 return doc;
4740 static gchar* gen_pidf(struct sipe_account_data *sip)
4742 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4743 "<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"
4744 "<tuple id=\"0\">\r\n"
4745 "<status>\r\n"
4746 "<basic>open</basic>\r\n"
4747 "<ep:activities>\r\n"
4748 " <ep:activity>%s</ep:activity>\r\n"
4749 "</ep:activities>"
4750 "</status>\r\n"
4751 "</tuple>\r\n"
4752 "<ci:display-name>%s</ci:display-name>\r\n"
4753 "</presence>",
4754 sip->username,
4755 sip->status,
4756 sip->username);
4757 return doc;
4761 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4763 int availability = 300; // online
4764 int activity = 400; // Available
4765 gchar *name;
4766 gchar *body;
4767 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4768 activity = 100;
4769 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4770 activity = 150;
4771 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4772 activity = 300;
4773 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4774 activity = 400;
4775 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4776 activity = 500;
4777 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4778 activity = 600;
4779 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4780 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4781 availability = 0; // offline
4782 activity = 100;
4783 } else {
4784 activity = 400; // available
4787 name = g_strdup_printf("sip: sip:%s", sip->username);
4788 //@TODO: send user data - state; add hostname in upper case
4789 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4790 send_soap_request_with_cb(sip, body, NULL , NULL);
4791 g_free(name);
4792 g_free(body);
4795 static gboolean
4796 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg,
4797 SIPE_UNUSED_PARAMETER struct transaction *tc)
4799 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4800 if (msg->response == 200) {
4801 sip->status_version = 0;
4802 send_presence_status(sip);
4804 return TRUE;
4807 static gboolean
4808 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg,
4809 SIPE_UNUSED_PARAMETER struct transaction *tc)
4811 if (msg->response == 409) {
4812 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4813 // TODO need to parse the version #'s?
4814 gchar *uri = sip_uri_self(sip);
4815 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4816 gchar *tmp;
4817 gchar *hdr;
4819 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4821 tmp = get_contact(sip);
4822 hdr = g_strdup_printf("Contact: %s\r\n"
4823 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4825 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4827 g_free(tmp);
4828 g_free(hdr);
4829 g_free(uri);
4830 g_free(doc);
4832 return TRUE;
4835 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4837 int code;
4838 gchar *uri;
4839 gchar *doc;
4840 gchar *tmp;
4841 gchar *hdr;
4842 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4843 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4844 code = 12000;
4845 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4846 code = 9000;
4847 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4848 code = 7500;
4849 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4850 code = 6000;
4851 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4852 code = 4500;
4853 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4854 code = 3000;
4855 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4856 code = 0;
4857 } else {
4858 // Offline or invisible
4859 code = 18000;
4862 uri = sip_uri_self(sip);
4863 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4864 sip->status_version, code,
4865 sip->status_version, code,
4866 sip->status_version, note ? note : "",
4867 sip->status_version, note ? note : "",
4868 sip->status_version, note ? note : ""
4870 sip->status_version++;
4872 tmp = get_contact(sip);
4873 hdr = g_strdup_printf("Contact: %s\r\n"
4874 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4876 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4878 g_free(tmp);
4879 g_free(hdr);
4880 g_free(uri);
4881 g_free(doc);
4884 static void send_presence_status(struct sipe_account_data *sip)
4886 PurpleStatus * status = purple_account_get_active_status(sip->account);
4887 const gchar *note;
4888 if (!status) return;
4890 note = purple_status_get_attr_string(status, "message");
4892 if(sip->msrtc_event_categories){
4893 send_presence_category_publish(sip, note);
4894 } else {
4895 send_presence_soap(sip, note);
4899 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4901 gboolean found = FALSE;
4902 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4903 if (msg->response == 0) { /* request */
4904 if (!strcmp(msg->method, "MESSAGE")) {
4905 process_incoming_message(sip, msg);
4906 found = TRUE;
4907 } else if (!strcmp(msg->method, "NOTIFY")) {
4908 purple_debug_info("sipe","send->process_incoming_notify\n");
4909 process_incoming_notify(sip, msg, TRUE, FALSE);
4910 found = TRUE;
4911 } else if (!strcmp(msg->method, "BENOTIFY")) {
4912 purple_debug_info("sipe","send->process_incoming_benotify\n");
4913 process_incoming_notify(sip, msg, TRUE, TRUE);
4914 found = TRUE;
4915 } else if (!strcmp(msg->method, "INVITE")) {
4916 process_incoming_invite(sip, msg);
4917 found = TRUE;
4918 } else if (!strcmp(msg->method, "REFER")) {
4919 process_incoming_refer(sip, msg);
4920 found = TRUE;
4921 } else if (!strcmp(msg->method, "OPTIONS")) {
4922 process_incoming_options(sip, msg);
4923 found = TRUE;
4924 } else if (!strcmp(msg->method, "INFO")) {
4925 process_incoming_info(sip, msg);
4926 found = TRUE;
4927 } else if (!strcmp(msg->method, "ACK")) {
4928 // ACK's don't need any response
4929 found = TRUE;
4930 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4931 // LCS 2005 sends us these - just respond 200 OK
4932 found = TRUE;
4933 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4934 } else if (!strcmp(msg->method, "BYE")) {
4935 process_incoming_bye(sip, msg);
4936 found = TRUE;
4937 } else {
4938 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4940 } else { /* response */
4941 struct transaction *trans = transactions_find(sip, msg);
4942 if (trans) {
4943 if (msg->response == 407) {
4944 gchar *resend, *auth, *ptmp;
4946 if (sip->proxy.retries > 30) return;
4947 sip->proxy.retries++;
4948 /* do proxy authentication */
4950 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4952 fill_auth(ptmp, &sip->proxy);
4953 auth = auth_header(sip, &sip->proxy, trans->msg);
4954 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4955 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4956 g_free(auth);
4957 resend = sipmsg_to_string(trans->msg);
4958 /* resend request */
4959 sendout_pkt(sip->gc, resend);
4960 g_free(resend);
4961 } else {
4962 if (msg->response == 100 || msg->response == 180) {
4963 /* ignore provisional response */
4964 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4965 } else {
4966 sip->proxy.retries = 0;
4967 if (!strcmp(trans->msg->method, "REGISTER")) {
4968 if (msg->response == 401)
4970 sip->registrar.retries++;
4972 else
4974 sip->registrar.retries = 0;
4976 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4977 } else {
4978 if (msg->response == 401) {
4979 gchar *resend, *auth, *ptmp;
4981 if (sip->registrar.retries > 4) return;
4982 sip->registrar.retries++;
4984 #ifdef USE_KERBEROS
4985 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4986 #endif
4987 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4988 #ifdef USE_KERBEROS
4989 } else {
4990 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4992 #endif
4994 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4996 fill_auth(ptmp, &sip->registrar);
4997 auth = auth_header(sip, &sip->registrar, trans->msg);
4998 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4999 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
5001 //sipmsg_remove_header_now(trans->msg, "Authorization");
5002 //sipmsg_add_header(trans->msg, "Authorization", auth);
5003 g_free(auth);
5004 resend = sipmsg_to_string(trans->msg);
5005 /* resend request */
5006 sendout_pkt(sip->gc, resend);
5007 g_free(resend);
5011 if (trans->callback) {
5012 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
5013 /* call the callback to process response*/
5014 (trans->callback)(sip, msg, trans);
5016 /* Not sure if this is needed or what needs to be done
5017 but transactions seem to be removed prematurely so
5018 this only removes them if the response is 200 OK */
5019 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
5020 /*Has a bug and it's unneccesary*/
5021 /*transactions_remove(sip, trans);*/
5025 found = TRUE;
5026 } else {
5027 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
5030 if (!found) {
5031 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
5035 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
5037 char *cur;
5038 char *dummy;
5039 struct sipmsg *msg;
5040 int restlen;
5041 cur = conn->inbuf;
5043 /* according to the RFC remove CRLF at the beginning */
5044 while (*cur == '\r' || *cur == '\n') {
5045 cur++;
5047 if (cur != conn->inbuf) {
5048 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
5049 conn->inbufused = strlen(conn->inbuf);
5052 /* Received a full Header? */
5053 sip->processing_input = TRUE;
5054 while (sip->processing_input &&
5055 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
5056 time_t currtime = time(NULL);
5057 cur += 2;
5058 cur[0] = '\0';
5059 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
5060 msg = sipmsg_parse_header(conn->inbuf);
5061 cur[0] = '\r';
5062 cur += 2;
5063 restlen = conn->inbufused - (cur - conn->inbuf);
5064 if (restlen >= msg->bodylen) {
5065 dummy = g_malloc(msg->bodylen + 1);
5066 memcpy(dummy, cur, msg->bodylen);
5067 dummy[msg->bodylen] = '\0';
5068 msg->body = dummy;
5069 cur += msg->bodylen;
5070 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
5071 conn->inbufused = strlen(conn->inbuf);
5072 } else {
5073 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5074 restlen, msg->bodylen, (int)strlen(conn->inbuf));
5075 sipmsg_free(msg);
5076 return;
5079 /*if (msg->body) {
5080 purple_debug_info("sipe", "body:\n%s", msg->body);
5083 // Verify the signature before processing it
5084 if (sip->registrar.gssapi_context) {
5085 struct sipmsg_breakdown msgbd;
5086 gchar *signature_input_str;
5087 gchar *rspauth;
5088 msgbd.msg = msg;
5089 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5090 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5092 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5094 if (rspauth != NULL) {
5095 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5096 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5097 process_input_message(sip, msg);
5098 } else {
5099 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5100 purple_connection_error(sip->gc, _("Invalid message signature received"));
5101 sip->gc->wants_to_die = TRUE;
5103 } else if (msg->response == 401) {
5104 purple_connection_error(sip->gc, _("Wrong Password"));
5105 sip->gc->wants_to_die = TRUE;
5107 g_free(signature_input_str);
5109 g_free(rspauth);
5110 sipmsg_breakdown_free(&msgbd);
5111 } else {
5112 process_input_message(sip, msg);
5115 sipmsg_free(msg);
5119 static void sipe_udp_process(gpointer data, gint source,
5120 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
5122 PurpleConnection *gc = data;
5123 struct sipe_account_data *sip = gc->proto_data;
5124 struct sipmsg *msg;
5125 int len;
5126 time_t currtime;
5128 static char buffer[65536];
5129 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5130 buffer[len] = '\0';
5131 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5132 msg = sipmsg_parse_msg(buffer);
5133 if (msg) process_input_message(sip, msg);
5137 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5139 struct sipe_account_data *sip = gc->proto_data;
5140 PurpleSslConnection *gsc = sip->gsc;
5142 purple_debug_error("sipe", "%s",debug);
5143 purple_connection_error(gc, msg);
5145 /* Invalidate this connection. Next send will open a new one */
5146 if (gsc) {
5147 connection_remove(sip, gsc->fd);
5148 purple_ssl_close(gsc);
5150 sip->gsc = NULL;
5151 sip->fd = -1;
5154 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
5155 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5157 PurpleConnection *gc = data;
5158 struct sipe_account_data *sip;
5159 struct sip_connection *conn;
5160 int readlen, len;
5161 gboolean firstread = TRUE;
5163 /* NOTE: This check *IS* necessary */
5164 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5165 purple_ssl_close(gsc);
5166 return;
5169 sip = gc->proto_data;
5170 conn = connection_find(sip, gsc->fd);
5171 if (conn == NULL) {
5172 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5173 gc->wants_to_die = TRUE;
5174 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5175 return;
5178 /* Read all available data from the SSL connection */
5179 do {
5180 /* Increase input buffer size as needed */
5181 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5182 conn->inbuflen += SIMPLE_BUF_INC;
5183 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5184 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5187 /* Try to read as much as there is space left in the buffer */
5188 readlen = conn->inbuflen - conn->inbufused - 1;
5189 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5191 if (len < 0 && errno == EAGAIN) {
5192 /* Try again later */
5193 return;
5194 } else if (len < 0) {
5195 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5196 return;
5197 } else if (firstread && (len == 0)) {
5198 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5199 return;
5202 conn->inbufused += len;
5203 firstread = FALSE;
5205 /* Equivalence indicates that there is possibly more data to read */
5206 } while (len == readlen);
5208 conn->inbuf[conn->inbufused] = '\0';
5209 process_input(sip, conn);
5213 static void sipe_input_cb(gpointer data, gint source,
5214 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5216 PurpleConnection *gc = data;
5217 struct sipe_account_data *sip = gc->proto_data;
5218 int len;
5219 struct sip_connection *conn = connection_find(sip, source);
5220 if (!conn) {
5221 purple_debug_error("sipe", "Connection not found!\n");
5222 return;
5225 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5226 conn->inbuflen += SIMPLE_BUF_INC;
5227 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5230 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5232 if (len < 0 && errno == EAGAIN)
5233 return;
5234 else if (len <= 0) {
5235 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5236 connection_remove(sip, source);
5237 if (sip->fd == source) sip->fd = -1;
5238 return;
5241 conn->inbufused += len;
5242 conn->inbuf[conn->inbufused] = '\0';
5244 process_input(sip, conn);
5247 /* Callback for new connections on incoming TCP port */
5248 static void sipe_newconn_cb(gpointer data, gint source,
5249 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5251 PurpleConnection *gc = data;
5252 struct sipe_account_data *sip = gc->proto_data;
5253 struct sip_connection *conn;
5255 int newfd = accept(source, NULL, NULL);
5257 conn = connection_create(sip, newfd);
5259 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5262 static void login_cb(gpointer data, gint source,
5263 SIPE_UNUSED_PARAMETER const gchar *error_message)
5265 PurpleConnection *gc = data;
5266 struct sipe_account_data *sip;
5267 struct sip_connection *conn;
5269 if (!PURPLE_CONNECTION_IS_VALID(gc))
5271 if (source >= 0)
5272 close(source);
5273 return;
5276 if (source < 0) {
5277 purple_connection_error(gc, _("Could not connect"));
5278 return;
5281 sip = gc->proto_data;
5282 sip->fd = source;
5283 sip->last_keepalive = time(NULL);
5285 conn = connection_create(sip, source);
5287 do_register(sip);
5289 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5292 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
5293 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5295 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5296 if (sip == NULL) return;
5298 do_register(sip);
5301 static guint sipe_ht_hash_nick(const char *nick)
5303 char *lc = g_utf8_strdown(nick, -1);
5304 guint bucket = g_str_hash(lc);
5305 g_free(lc);
5307 return bucket;
5310 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5312 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5315 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5317 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5319 sip->listen_data = NULL;
5321 if (listenfd == -1) {
5322 purple_connection_error(sip->gc, _("Could not create listen socket"));
5323 return;
5326 sip->fd = listenfd;
5328 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5329 sip->listenfd = sip->fd;
5331 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5333 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5334 do_register(sip);
5337 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
5338 SIPE_UNUSED_PARAMETER const char *error_message)
5340 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5342 sip->query_data = NULL;
5344 if (!hosts || !hosts->data) {
5345 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5346 return;
5349 hosts = g_slist_remove(hosts, hosts->data);
5350 g_free(sip->serveraddr);
5351 sip->serveraddr = hosts->data;
5352 hosts = g_slist_remove(hosts, hosts->data);
5353 while (hosts) {
5354 hosts = g_slist_remove(hosts, hosts->data);
5355 g_free(hosts->data);
5356 hosts = g_slist_remove(hosts, hosts->data);
5359 /* create socket for incoming connections */
5360 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5361 sipe_udp_host_resolved_listen_cb, sip);
5362 if (sip->listen_data == NULL) {
5363 purple_connection_error(sip->gc, _("Could not create listen socket"));
5364 return;
5368 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
5369 PurpleSslErrorType error,
5370 gpointer data)
5372 PurpleConnection *gc = data;
5373 struct sipe_account_data *sip;
5375 /* If the connection is already disconnected, we don't need to do anything else */
5376 if (!PURPLE_CONNECTION_IS_VALID(gc))
5377 return;
5379 sip = gc->proto_data;
5380 sip->fd = -1;
5381 sip->gsc = NULL;
5383 switch(error) {
5384 case PURPLE_SSL_CONNECT_FAILED:
5385 purple_connection_error(gc, _("Connection Failed"));
5386 break;
5387 case PURPLE_SSL_HANDSHAKE_FAILED:
5388 purple_connection_error(gc, _("SSL Handshake Failed"));
5389 break;
5390 case PURPLE_SSL_CERTIFICATE_INVALID:
5391 purple_connection_error(gc, _("SSL Certificate Invalid"));
5392 break;
5396 static void
5397 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5399 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5400 PurpleProxyConnectData *connect_data;
5402 sip->listen_data = NULL;
5404 sip->listenfd = listenfd;
5405 if (sip->listenfd == -1) {
5406 purple_connection_error(sip->gc, _("Could not create listen socket"));
5407 return;
5410 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5411 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5412 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5413 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5414 sipe_newconn_cb, sip->gc);
5415 purple_debug_info("sipe", "connecting to %s port %d\n",
5416 sip->realhostname, sip->realport);
5417 /* open tcp connection to the server */
5418 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5419 sip->realport, login_cb, sip->gc);
5421 if (connect_data == NULL) {
5422 purple_connection_error(sip->gc, _("Couldn't create socket"));
5426 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5428 PurpleAccount *account = sip->account;
5429 PurpleConnection *gc = sip->gc;
5431 if (port == 0) {
5432 port = (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5435 sip->realhostname = hostname;
5436 sip->realport = port;
5438 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5439 hostname, port);
5441 /* TODO: is there a good default grow size? */
5442 if (sip->transport != SIPE_TRANSPORT_UDP)
5443 sip->txbuf = purple_circ_buffer_new(0);
5445 if (sip->transport == SIPE_TRANSPORT_TLS) {
5446 /* SSL case */
5447 if (!purple_ssl_is_supported()) {
5448 gc->wants_to_die = TRUE;
5449 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5450 return;
5453 purple_debug_info("sipe", "using SSL\n");
5455 sip->gsc = purple_ssl_connect(account, hostname, port,
5456 login_cb_ssl, sipe_ssl_connect_failure, gc);
5457 if (sip->gsc == NULL) {
5458 purple_connection_error(gc, _("Could not create SSL context"));
5459 return;
5461 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5462 /* UDP case */
5463 purple_debug_info("sipe", "using UDP\n");
5465 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5466 if (sip->query_data == NULL) {
5467 purple_connection_error(gc, _("Could not resolve hostname"));
5469 } else {
5470 /* TCP case */
5471 purple_debug_info("sipe", "using TCP\n");
5472 /* create socket for incoming connections */
5473 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5474 sipe_tcp_connect_listen_cb, sip);
5475 if (sip->listen_data == NULL) {
5476 purple_connection_error(gc, _("Could not create listen socket"));
5477 return;
5482 /* Service list for autodection */
5483 static const struct sipe_service_data service_autodetect[] = {
5484 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5485 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5486 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5487 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5488 { NULL, NULL, 0 }
5491 /* Service list for SSL/TLS */
5492 static const struct sipe_service_data service_tls[] = {
5493 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5494 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5495 { NULL, NULL, 0 }
5498 /* Service list for TCP */
5499 static const struct sipe_service_data service_tcp[] = {
5500 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5501 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5502 { NULL, NULL, 0 }
5505 /* Service list for UDP */
5506 static const struct sipe_service_data service_udp[] = {
5507 { "sip", "udp", SIPE_TRANSPORT_UDP },
5508 { NULL, NULL, 0 }
5511 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5512 static void resolve_next_service(struct sipe_account_data *sip,
5513 const struct sipe_service_data *start)
5515 if (start) {
5516 sip->service_data = start;
5517 } else {
5518 sip->service_data++;
5519 if (sip->service_data->service == NULL) {
5520 gchar *hostname;
5521 /* Try connecting to the SIP hostname directly */
5522 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5523 if (sip->auto_transport) {
5524 // If SSL is supported, default to using it; OCS servers aren't configured
5525 // by default to accept TCP
5526 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5527 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5528 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5531 hostname = g_strdup(sip->sipdomain);
5532 create_connection(sip, hostname, 0);
5533 return;
5537 /* Try to resolve next service */
5538 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5539 sip->service_data->transport,
5540 sip->sipdomain,
5541 srvresolved, sip);
5544 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5546 struct sipe_account_data *sip = data;
5548 sip->srv_query_data = NULL;
5550 /* find the host to connect to */
5551 if (results) {
5552 gchar *hostname = g_strdup(resp->hostname);
5553 int port = resp->port;
5554 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5555 hostname, port);
5556 g_free(resp);
5558 sip->transport = sip->service_data->type;
5560 create_connection(sip, hostname, port);
5561 } else {
5562 resolve_next_service(sip, NULL);
5566 static void sipe_login(PurpleAccount *account)
5568 PurpleConnection *gc;
5569 struct sipe_account_data *sip;
5570 gchar **signinname_login, **userserver;
5571 const char *transport;
5573 const char *username = purple_account_get_username(account);
5574 gc = purple_account_get_connection(account);
5576 purple_debug_info("sipe", "sipe_login: username '%s'\n", username);
5578 if (strpbrk(username, "\t\v\r\n") != NULL) {
5579 gc->wants_to_die = TRUE;
5580 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5581 return;
5584 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5585 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5586 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5587 sip->gc = gc;
5588 sip->account = account;
5589 sip->reregister_set = FALSE;
5590 sip->reauthenticate_set = FALSE;
5591 sip->subscribed = FALSE;
5592 sip->subscribed_buddies = FALSE;
5594 signinname_login = g_strsplit(username, ",", 2);
5595 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login[0]);
5597 if (!strstr(signinname_login[0], "@") || g_str_has_prefix(signinname_login[0], "@") || g_str_has_suffix(signinname_login[0], "@")) {
5598 g_strfreev(signinname_login);
5599 gc->wants_to_die = TRUE;
5600 purple_connection_error(gc, _("Username should be valid SIP URI\nExample: user@company.com"));
5601 return;
5604 if (signinname_login[1] && strcmp(signinname_login[1], "")) {
5605 gchar **domain_user = g_strsplit(signinname_login[1], "\\", 2);
5606 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login[1]);
5607 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
5608 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) :
5609 (signinname_login ? g_strdup(signinname_login[1]) : NULL);
5610 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n", sip->authdomain, sip->authuser);
5611 g_strfreev(domain_user);
5614 userserver = g_strsplit(signinname_login[0], "@", 2);
5615 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver[0], userserver[1]);
5616 purple_connection_set_display_name(gc, userserver[0]);
5617 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
5618 sip->sipdomain = g_strdup(userserver[1]);
5619 g_strfreev(userserver);
5620 g_strfreev(signinname_login);
5622 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
5623 gc->wants_to_die = TRUE;
5624 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5625 return;
5628 sip->password = g_strdup(purple_connection_get_password(gc));
5630 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5632 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5634 /* TODO: Set the status correctly. */
5635 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5637 sip->auto_transport = FALSE;
5638 transport = purple_account_get_string(account, "transport", "auto");
5639 userserver = g_strsplit(purple_account_get_string(account, "server", ""), ":", 2);
5640 if (userserver[0]) {
5641 /* Use user specified server[:port] */
5642 int port = 0;
5644 if (userserver[1])
5645 port = atoi(userserver[1]);
5647 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login: user specified SIP server %s:%d\n",
5648 userserver[0], port);
5650 if (strcmp(transport, "auto") == 0) {
5651 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5652 } else if (strcmp(transport, "tls") == 0) {
5653 sip->transport = SIPE_TRANSPORT_TLS;
5654 } else if (strcmp(transport, "tcp") == 0) {
5655 sip->transport = SIPE_TRANSPORT_TCP;
5656 } else {
5657 sip->transport = SIPE_TRANSPORT_UDP;
5660 create_connection(sip, g_strdup(userserver[0]), port);
5661 } else {
5662 /* Server auto-discovery */
5663 if (strcmp(transport, "auto") == 0) {
5664 sip->auto_transport = TRUE;
5665 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5666 } else if (strcmp(transport, "tls") == 0) {
5667 resolve_next_service(sip, service_tls);
5668 } else if (strcmp(transport, "tcp") == 0) {
5669 resolve_next_service(sip, service_tcp);
5670 } else {
5671 resolve_next_service(sip, service_udp);
5674 g_strfreev(userserver);
5677 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5679 connection_free_all(sip);
5681 g_free(sip->epid);
5682 sip->epid = NULL;
5684 if (sip->query_data != NULL)
5685 purple_dnsquery_destroy(sip->query_data);
5686 sip->query_data = NULL;
5688 if (sip->srv_query_data != NULL)
5689 purple_srv_cancel(sip->srv_query_data);
5690 sip->srv_query_data = NULL;
5692 if (sip->listen_data != NULL)
5693 purple_network_listen_cancel(sip->listen_data);
5694 sip->listen_data = NULL;
5696 if (sip->gsc != NULL)
5697 purple_ssl_close(sip->gsc);
5698 sip->gsc = NULL;
5700 sipe_auth_free(&sip->registrar);
5701 sipe_auth_free(&sip->proxy);
5703 if (sip->txbuf)
5704 purple_circ_buffer_destroy(sip->txbuf);
5705 sip->txbuf = NULL;
5707 g_free(sip->realhostname);
5708 sip->realhostname = NULL;
5710 if (sip->listenpa)
5711 purple_input_remove(sip->listenpa);
5712 sip->listenpa = 0;
5713 if (sip->tx_handler)
5714 purple_input_remove(sip->tx_handler);
5715 sip->tx_handler = 0;
5716 if (sip->resendtimeout)
5717 purple_timeout_remove(sip->resendtimeout);
5718 sip->resendtimeout = 0;
5719 if (sip->timeouts) {
5720 GSList *entry = sip->timeouts;
5721 while (entry) {
5722 struct scheduled_action *sched_action = entry->data;
5723 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5724 purple_timeout_remove(sched_action->timeout_handler);
5725 if (sched_action->destroy) {
5726 (*sched_action->destroy)(sched_action->payload);
5728 g_free(sched_action->name);
5729 g_free(sched_action);
5730 entry = entry->next;
5733 g_slist_free(sip->timeouts);
5735 if (sip->allow_events) {
5736 GSList *entry = sip->allow_events;
5737 while (entry) {
5738 g_free(entry->data);
5739 entry = entry->next;
5742 g_slist_free(sip->allow_events);
5744 if (sip->containers) {
5745 GSList *entry = sip->containers;
5746 while (entry) {
5747 free_container((struct sipe_container *)entry->data);
5748 entry = entry->next;
5751 g_slist_free(sip->containers);
5753 if (sip->contact)
5754 g_free(sip->contact);
5755 sip->contact = NULL;
5756 if (sip->regcallid)
5757 g_free(sip->regcallid);
5758 sip->regcallid = NULL;
5760 if (sip->serveraddr)
5761 g_free(sip->serveraddr);
5762 sip->serveraddr = NULL;
5764 if (sip->focus_factory_uri)
5765 g_free(sip->focus_factory_uri);
5766 sip->focus_factory_uri = NULL;
5768 sip->fd = -1;
5769 sip->processing_input = FALSE;
5773 * A callback for g_hash_table_foreach_remove
5775 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5776 SIPE_UNUSED_PARAMETER gpointer user_data)
5778 sipe_free_buddy((struct sipe_buddy *) buddy);
5780 /* We must return TRUE as the key/value have already been deleted */
5781 return(TRUE);
5784 static void sipe_close(PurpleConnection *gc)
5786 struct sipe_account_data *sip = gc->proto_data;
5788 if (sip) {
5789 /* leave all conversations */
5790 sipe_session_close_all(sip);
5791 sipe_session_remove_all(sip);
5793 /* unregister */
5794 if (PURPLE_CONNECTION_IS_CONNECTED(sip->gc)) {
5795 do_register_exp(sip, 0);
5798 sipe_connection_cleanup(sip);
5799 g_free(sip->sipdomain);
5800 g_free(sip->username);
5801 g_free(sip->password);
5802 g_free(sip->authdomain);
5803 g_free(sip->authuser);
5804 g_free(sip->status);
5806 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
5807 g_hash_table_destroy(sip->buddies);
5809 g_free(gc->proto_data);
5810 gc->proto_data = NULL;
5813 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5814 SIPE_UNUSED_PARAMETER void *user_data)
5816 PurpleAccount *acct = purple_connection_get_account(gc);
5817 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5818 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5819 if (conv == NULL)
5820 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5821 purple_conversation_present(conv);
5822 g_free(id);
5825 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5826 SIPE_UNUSED_PARAMETER void *user_data)
5829 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5830 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5833 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
5834 SIPE_UNUSED_PARAMETER struct transaction *tc)
5836 PurpleNotifySearchResults *results;
5837 PurpleNotifySearchColumn *column;
5838 xmlnode *searchResults;
5839 xmlnode *mrow;
5840 int match_count = 0;
5841 gboolean more = FALSE;
5842 gchar *secondary;
5844 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5846 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5847 if (!searchResults) {
5848 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5849 return FALSE;
5852 results = purple_notify_searchresults_new();
5854 if (results == NULL) {
5855 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5856 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5858 xmlnode_free(searchResults);
5859 return FALSE;
5862 column = purple_notify_searchresults_column_new(_("User Name"));
5863 purple_notify_searchresults_column_add(results, column);
5865 column = purple_notify_searchresults_column_new(_("Name"));
5866 purple_notify_searchresults_column_add(results, column);
5868 column = purple_notify_searchresults_column_new(_("Company"));
5869 purple_notify_searchresults_column_add(results, column);
5871 column = purple_notify_searchresults_column_new(_("Country"));
5872 purple_notify_searchresults_column_add(results, column);
5874 column = purple_notify_searchresults_column_new(_("Email"));
5875 purple_notify_searchresults_column_add(results, column);
5877 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5878 GList *row = NULL;
5880 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5881 row = g_list_append(row, g_strdup(uri_parts[1]));
5882 g_strfreev(uri_parts);
5884 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5885 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5886 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5887 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5889 purple_notify_searchresults_row_add(results, row);
5890 match_count++;
5893 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5894 char *data = xmlnode_get_data_unescaped(mrow);
5895 more = (g_strcasecmp(data, "true") == 0);
5896 g_free(data);
5899 secondary = g_strdup_printf(
5900 dngettext(GETTEXT_PACKAGE,
5901 "Found %d contact%s:",
5902 "Found %d contacts%s:", match_count),
5903 match_count, more ? _(" (more matched your query)") : "");
5905 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5906 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5907 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5909 g_free(secondary);
5910 xmlnode_free(searchResults);
5911 return TRUE;
5914 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5916 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5917 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5918 unsigned i = 0;
5920 do {
5921 PurpleRequestField *field = entries->data;
5922 const char *id = purple_request_field_get_id(field);
5923 const char *value = purple_request_field_string_get_value(field);
5925 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5927 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5928 } while ((entries = g_list_next(entries)) != NULL);
5929 attrs[i] = NULL;
5931 if (i > 0) {
5932 gchar *query = g_strjoinv(NULL, attrs);
5933 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5934 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5935 send_soap_request_with_cb(gc->proto_data, body,
5936 (TransCallback) process_search_contact_response, NULL);
5937 g_free(body);
5938 g_free(query);
5941 g_strfreev(attrs);
5944 static void sipe_show_find_contact(PurplePluginAction *action)
5946 PurpleConnection *gc = (PurpleConnection *) action->context;
5947 PurpleRequestFields *fields;
5948 PurpleRequestFieldGroup *group;
5949 PurpleRequestField *field;
5951 fields = purple_request_fields_new();
5952 group = purple_request_field_group_new(NULL);
5953 purple_request_fields_add_group(fields, group);
5955 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5956 purple_request_field_group_add_field(group, field);
5957 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5958 purple_request_field_group_add_field(group, field);
5959 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5960 purple_request_field_group_add_field(group, field);
5961 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5962 purple_request_field_group_add_field(group, field);
5964 purple_request_fields(gc,
5965 _("Search"),
5966 _("Search for a Contact"),
5967 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5968 fields,
5969 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5970 _("_Cancel"), NULL,
5971 purple_connection_get_account(gc), NULL, NULL, gc);
5974 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
5975 SIPE_UNUSED_PARAMETER gpointer context)
5977 GList *menu = NULL;
5978 PurplePluginAction *act;
5980 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5981 menu = g_list_prepend(menu, act);
5983 menu = g_list_reverse(menu);
5985 return menu;
5988 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
5992 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
5994 return TRUE;
5998 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
6000 return TRUE;
6004 static char *sipe_status_text(PurpleBuddy *buddy)
6006 struct sipe_account_data *sip;
6007 struct sipe_buddy *sbuddy;
6008 char *text = NULL;
6010 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
6011 if (sip) //happens on pidgin exit
6013 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
6014 if (sbuddy && sbuddy->annotation)
6016 text = g_strdup(sbuddy->annotation);
6020 return text;
6023 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
6025 const PurplePresence *presence = purple_buddy_get_presence(buddy);
6026 const PurpleStatus *status = purple_presence_get_active_status(presence);
6027 struct sipe_account_data *sip;
6028 struct sipe_buddy *sbuddy;
6029 char *annotation = NULL;
6031 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
6032 if (sip) //happens on pidgin exit
6034 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
6035 if (sbuddy)
6037 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
6041 //Layout
6042 if (purple_presence_is_online(presence))
6044 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
6047 if (annotation)
6049 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
6050 g_free(annotation);
6055 static GHashTable *
6056 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
6058 GHashTable *table;
6059 table = g_hash_table_new(g_str_hash, g_str_equal);
6060 g_hash_table_insert(table, "login_label", (gpointer)_("user@company.com"));
6061 return table;
6064 static PurpleBuddy *
6065 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6067 PurpleBuddy *clone;
6068 const gchar *server_alias, *email;
6069 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6071 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6073 purple_blist_add_buddy(clone, NULL, group, NULL);
6075 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
6076 if (server_alias) {
6077 purple_blist_server_alias_buddy(clone, server_alias);
6080 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6081 if (email) {
6082 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
6085 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6086 //for UI to update;
6087 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6088 return clone;
6091 static void
6092 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6094 PurpleBuddy *buddy, *b;
6095 PurpleConnection *gc;
6096 PurpleGroup * group = purple_find_group(group_name);
6098 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6100 buddy = (PurpleBuddy *)node;
6102 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
6103 gc = purple_account_get_connection(buddy->account);
6105 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6106 if (!b){
6107 b = purple_blist_add_buddy_clone(group, buddy);
6110 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6113 static void
6114 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6116 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6118 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6120 /* 2007+ conference */
6121 if (sip->msrtc_event_categories)
6123 sipe_conf_add(sip, buddy->name);
6125 else /* 2005- multiparty chat */
6127 gchar *self = sip_uri_self(sip);
6128 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
6129 struct sip_session *session;
6131 session = sipe_session_add_chat(sip);
6132 session->roster_manager = g_strdup(self);
6134 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6135 session->chat_name = g_strdup(chat_name);
6136 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
6137 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6138 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6140 g_free(chat_name);
6141 g_free(self);
6145 static gboolean
6146 sipe_is_election_finished(struct sip_session *session)
6148 gboolean res = TRUE;
6150 SIPE_DIALOG_FOREACH {
6151 if (dialog->election_vote == 0) {
6152 res = FALSE;
6153 break;
6155 } SIPE_DIALOG_FOREACH_END;
6157 if (res) {
6158 session->is_voting_in_progress = FALSE;
6160 return res;
6163 static void
6164 sipe_election_start(struct sipe_account_data *sip,
6165 struct sip_session *session)
6167 int election_timeout;
6169 if (session->is_voting_in_progress) {
6170 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6171 return;
6172 } else {
6173 session->is_voting_in_progress = TRUE;
6175 session->bid = rand();
6177 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6179 SIPE_DIALOG_FOREACH {
6180 /* reset election_vote for each chat participant */
6181 dialog->election_vote = 0;
6183 /* send RequestRM to each chat participant*/
6184 sipe_send_election_request_rm(sip, dialog, session->bid);
6185 } SIPE_DIALOG_FOREACH_END;
6187 election_timeout = 15; /* sec */
6188 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6192 * @param who a URI to whom to invite to chat
6194 void
6195 sipe_invite_to_chat(struct sipe_account_data *sip,
6196 struct sip_session *session,
6197 const gchar *who)
6199 /* a conference */
6200 if (session->focus_uri)
6202 sipe_invite_conf(sip, session, who);
6204 else /* a multi-party chat */
6206 gchar *self = sip_uri_self(sip);
6207 if (session->roster_manager) {
6208 if (!strcmp(session->roster_manager, self)) {
6209 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6210 } else {
6211 sipe_refer(sip, session, who);
6213 } else {
6214 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6216 session->pending_invite_queue = slist_insert_unique_sorted(
6217 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6219 sipe_election_start(sip, session);
6221 g_free(self);
6225 void
6226 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6227 struct sip_session *session)
6229 gchar *invitee;
6230 GSList *entry = session->pending_invite_queue;
6232 while (entry) {
6233 invitee = entry->data;
6234 sipe_invite_to_chat(sip, session, invitee);
6235 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6236 g_free(invitee);
6240 static void
6241 sipe_election_result(struct sipe_account_data *sip,
6242 void *sess)
6244 struct sip_session *session = (struct sip_session *)sess;
6245 gchar *rival;
6246 gboolean has_won = TRUE;
6248 if (session->roster_manager) {
6249 purple_debug_info("sipe",
6250 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6251 return;
6254 session->is_voting_in_progress = FALSE;
6256 SIPE_DIALOG_FOREACH {
6257 if (dialog->election_vote < 0) {
6258 has_won = FALSE;
6259 rival = dialog->with;
6260 break;
6262 } SIPE_DIALOG_FOREACH_END;
6264 if (has_won) {
6265 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6267 session->roster_manager = sip_uri_self(sip);
6269 SIPE_DIALOG_FOREACH {
6270 /* send SetRM to each chat participant*/
6271 sipe_send_election_set_rm(sip, dialog);
6272 } SIPE_DIALOG_FOREACH_END;
6273 } else {
6274 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6276 session->bid = 0;
6278 sipe_process_pending_invite_queue(sip, session);
6282 * For 2007+ conference only.
6284 static void
6285 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_name)
6287 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6288 struct sip_session *session;
6290 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
6291 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_name=%s\n", chat_name);
6293 session = sipe_session_find_chat_by_name(sip, chat_name);
6295 sipe_conf_modify_user_role(sip, session, buddy->name);
6299 * For 2007+ conference only.
6301 static void
6302 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_name)
6304 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6305 struct sip_session *session;
6307 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
6308 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_name=%s\n", chat_name);
6310 session = sipe_session_find_chat_by_name(sip, chat_name);
6312 sipe_conf_delete_user(sip, session, buddy->name);
6315 static void
6316 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6318 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6319 struct sip_session *session;
6321 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6322 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6324 session = sipe_session_find_chat_by_name(sip, chat_name);
6326 sipe_invite_to_chat(sip, session, buddy->name);
6329 static void
6330 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6332 const gchar *email;
6333 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6335 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6336 if (email)
6338 char *mailto = g_strdup_printf("mailto:%s", email);
6339 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6340 #ifndef _WIN32
6342 pid_t pid;
6343 char *const parmList[] = {mailto, NULL};
6344 if ((pid = fork()) == -1)
6346 purple_debug_info("sipe", "fork() error\n");
6348 else if (pid == 0)
6350 execvp("xdg-email", parmList);
6351 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6354 #else
6356 BOOL ret;
6357 _flushall();
6358 errno = 0;
6359 //@TODO resolve env variable %WINDIR% first
6360 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6361 if (errno)
6363 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6366 #endif
6368 g_free(mailto);
6370 else
6372 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6377 * A menu which appear when right-clicking on buddy in contact list.
6379 static GList *
6380 sipe_buddy_menu(PurpleBuddy *buddy)
6382 PurpleBlistNode *g_node;
6383 PurpleGroup *group, *gr_parent;
6384 PurpleMenuAction *act;
6385 GList *menu = NULL;
6386 GList *menu_groups = NULL;
6387 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6388 gchar *self = sip_uri_self(sip);
6390 SIPE_SESSION_FOREACH {
6391 if (strcmp(self, buddy->name) && session->chat_name && session->conv)
6393 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
6395 PurpleConvChatBuddyFlags flags;
6396 PurpleConvChatBuddyFlags flags_us;
6398 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
6399 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
6400 if (session->focus_uri
6401 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
6402 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6404 gchar *label = g_strdup_printf(_("Make Leader of '%s'"), session->chat_name);
6405 act = purple_menu_action_new(label,
6406 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6407 g_strdup(session->chat_name), NULL);
6408 g_free(label);
6409 menu = g_list_prepend(menu, act);
6412 if (session->focus_uri
6413 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6415 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_name);
6416 act = purple_menu_action_new(label,
6417 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6418 g_strdup(session->chat_name), NULL);
6419 g_free(label);
6420 menu = g_list_prepend(menu, act);
6423 else
6425 if (!session->focus_uri
6426 || (session->focus_uri && !session->locked))
6428 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_name);
6429 act = purple_menu_action_new(label,
6430 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6431 g_strdup(session->chat_name), NULL);
6432 g_free(label);
6433 menu = g_list_prepend(menu, act);
6437 } SIPE_SESSION_FOREACH_END;
6439 act = purple_menu_action_new(_("New Chat"),
6440 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6441 NULL, NULL);
6442 menu = g_list_prepend(menu, act);
6444 act = purple_menu_action_new(_("Send Email..."),
6445 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6446 NULL, NULL);
6447 menu = g_list_prepend(menu, act);
6449 gr_parent = purple_buddy_get_group(buddy);
6450 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6451 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6452 continue;
6454 group = (PurpleGroup *)g_node;
6455 if (group == gr_parent)
6456 continue;
6458 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6459 continue;
6461 act = purple_menu_action_new(purple_group_get_name(group),
6462 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6463 group->name, NULL);
6464 menu_groups = g_list_prepend(menu_groups, act);
6466 menu_groups = g_list_reverse(menu_groups);
6468 act = purple_menu_action_new(_("Copy to"),
6469 NULL,
6470 NULL, menu_groups);
6471 menu = g_list_prepend(menu, act);
6472 menu = g_list_reverse(menu);
6474 g_free(self);
6475 return menu;
6478 static void
6479 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
6481 struct sipe_account_data *sip = chat->account->gc->proto_data;
6482 struct sip_session *session;
6484 session = sipe_session_find_chat_by_name(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
6485 sipe_conf_modify_conference_lock(sip, session, locked);
6488 static void
6489 sipe_chat_menu_unlock_cb(PurpleChat *chat)
6491 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
6492 sipe_conf_modify_lock(chat, FALSE);
6495 static void
6496 sipe_chat_menu_lock_cb(PurpleChat *chat)
6498 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
6499 sipe_conf_modify_lock(chat, TRUE);
6502 static GList *
6503 sipe_chat_menu(PurpleChat *chat)
6505 PurpleMenuAction *act;
6506 PurpleConvChatBuddyFlags flags_us;
6507 GList *menu = NULL;
6508 struct sipe_account_data *sip = chat->account->gc->proto_data;
6509 struct sip_session *session;
6510 gchar *self = sip_uri_self(sip);
6512 session = sipe_session_find_chat_by_name(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
6513 if (!session) return NULL;
6515 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
6517 if (session->focus_uri
6518 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6520 if (session->locked) {
6521 act = purple_menu_action_new(_("Unlock Conversation"),
6522 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
6523 NULL, NULL);
6524 menu = g_list_prepend(menu, act);
6525 } else {
6526 act = purple_menu_action_new(_("Lock Conversation"),
6527 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
6528 NULL, NULL);
6529 menu = g_list_prepend(menu, act);
6533 menu = g_list_reverse(menu);
6535 g_free(self);
6536 return menu;
6539 static GList *
6540 sipe_blist_node_menu(PurpleBlistNode *node)
6542 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6543 return sipe_buddy_menu((PurpleBuddy *) node);
6544 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
6545 return sipe_chat_menu((PurpleChat *)node);
6546 } else {
6547 return NULL;
6551 static gboolean
6552 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6554 gboolean ret = TRUE;
6555 char *username = (char *)trans->payload;
6557 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6558 PurpleBuddy *pbuddy;
6559 struct sipe_buddy *sbuddy;
6560 const char *alias;
6561 char *server_alias = NULL;
6562 char *email = NULL;
6563 const char *device_name = NULL;
6565 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6567 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6568 alias = purple_buddy_get_local_alias(pbuddy);
6570 if (sip)
6572 //will query buddy UA's capabilities and send answer to log
6573 sipe_options_request(sip, username);
6575 sbuddy = g_hash_table_lookup(sip->buddies, username);
6576 if (sbuddy)
6578 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6582 if (msg->response != 200) {
6583 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6584 } else {
6585 xmlnode *searchResults;
6586 xmlnode *mrow;
6588 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6589 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6590 if (!searchResults) {
6591 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6592 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6593 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6594 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6595 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6596 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6597 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6598 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6599 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6600 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6601 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6602 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6603 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6604 if (!email || strcmp("", email)) {
6605 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6606 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6610 xmlnode_free(searchResults);
6613 purple_notify_user_info_add_section_break(info);
6615 if (!server_alias || !strcmp("", server_alias)) {
6616 g_free(server_alias);
6617 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6618 if (server_alias) {
6619 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6623 // same as server alias, do not present
6624 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6625 if (alias)
6627 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6630 if (!email || !strcmp("", email)) {
6631 g_free(email);
6632 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6633 if (email) {
6634 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6638 if (device_name)
6640 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6643 /* show a buddy's user info in a nice dialog box */
6644 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6645 username, /* buddy's username */
6646 info, /* body */
6647 NULL, /* callback called when dialog closed */
6648 NULL); /* userdata for callback */
6650 return ret;
6654 * AD search first, LDAP based
6656 static void sipe_get_info(PurpleConnection *gc, const char *username)
6658 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6659 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6661 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6662 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6663 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6664 g_free(body);
6665 g_free(row);
6668 static PurplePlugin *my_protocol = NULL;
6670 static PurplePluginProtocolInfo prpl_info =
6672 OPT_PROTO_PASSWORD_OPTIONAL | OPT_PROTO_CHAT_TOPIC,
6673 NULL, /* user_splits */
6674 NULL, /* protocol_options */
6675 NO_BUDDY_ICONS, /* icon_spec */
6676 sipe_list_icon, /* list_icon */
6677 NULL, /* list_emblems */
6678 sipe_status_text, /* status_text */
6679 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6680 sipe_status_types, /* away_states */
6681 sipe_blist_node_menu, /* blist_node_menu */
6682 NULL, /* chat_info */
6683 NULL, /* chat_info_defaults */
6684 sipe_login, /* login */
6685 sipe_close, /* close */
6686 sipe_im_send, /* send_im */
6687 NULL, /* set_info */ // TODO maybe
6688 sipe_send_typing, /* send_typing */
6689 sipe_get_info, /* get_info */
6690 sipe_set_status, /* set_status */
6691 NULL, /* set_idle */
6692 NULL, /* change_passwd */
6693 sipe_add_buddy, /* add_buddy */
6694 NULL, /* add_buddies */
6695 sipe_remove_buddy, /* remove_buddy */
6696 NULL, /* remove_buddies */
6697 sipe_add_permit, /* add_permit */
6698 sipe_add_deny, /* add_deny */
6699 sipe_add_deny, /* rem_permit */
6700 sipe_add_permit, /* rem_deny */
6701 dummy_permit_deny, /* set_permit_deny */
6702 NULL, /* join_chat */
6703 NULL, /* reject_chat */
6704 NULL, /* get_chat_name */
6705 sipe_chat_invite, /* chat_invite */
6706 sipe_chat_leave, /* chat_leave */
6707 NULL, /* chat_whisper */
6708 sipe_chat_send, /* chat_send */
6709 sipe_keep_alive, /* keepalive */
6710 NULL, /* register_user */
6711 NULL, /* get_cb_info */ // deprecated
6712 NULL, /* get_cb_away */ // deprecated
6713 sipe_alias_buddy, /* alias_buddy */
6714 sipe_group_buddy, /* group_buddy */
6715 sipe_rename_group, /* rename_group */
6716 NULL, /* buddy_free */
6717 sipe_convo_closed, /* convo_closed */
6718 purple_normalize_nocase, /* normalize */
6719 NULL, /* set_buddy_icon */
6720 sipe_remove_group, /* remove_group */
6721 NULL, /* get_cb_real_name */ // TODO?
6722 NULL, /* set_chat_topic */
6723 NULL, /* find_blist_chat */
6724 NULL, /* roomlist_get_list */
6725 NULL, /* roomlist_cancel */
6726 NULL, /* roomlist_expand_category */
6727 NULL, /* can_receive_file */
6728 NULL, /* send_file */
6729 NULL, /* new_xfer */
6730 NULL, /* offline_message */
6731 NULL, /* whiteboard_prpl_ops */
6732 sipe_send_raw, /* send_raw */
6733 NULL, /* roomlist_room_serialize */
6734 NULL, /* unregister_user */
6735 NULL, /* send_attention */
6736 NULL, /* get_attention_types */
6738 sizeof(PurplePluginProtocolInfo), /* struct_size */
6739 sipe_get_account_text_table, /* get_account_text_table */
6743 static PurplePluginInfo info = {
6744 PURPLE_PLUGIN_MAGIC,
6745 PURPLE_MAJOR_VERSION,
6746 PURPLE_MINOR_VERSION,
6747 PURPLE_PLUGIN_PROTOCOL, /**< type */
6748 NULL, /**< ui_requirement */
6749 0, /**< flags */
6750 NULL, /**< dependencies */
6751 PURPLE_PRIORITY_DEFAULT, /**< priority */
6752 "prpl-sipe", /**< id */
6753 "Microsoft LCS/OCS", /**< name */
6754 VERSION, /**< version */
6755 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6756 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6757 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6758 "Gabriel Burt <gburt@novell.com>", /**< author */
6759 PURPLE_WEBSITE, /**< homepage */
6760 sipe_plugin_load, /**< load */
6761 sipe_plugin_unload, /**< unload */
6762 sipe_plugin_destroy, /**< destroy */
6763 NULL, /**< ui_info */
6764 &prpl_info, /**< extra_info */
6765 NULL,
6766 sipe_actions,
6767 NULL,
6768 NULL,
6769 NULL,
6770 NULL
6773 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
6775 GList *entry;
6777 entry = prpl_info.protocol_options;
6778 while (entry) {
6779 purple_account_option_destroy(entry->data);
6780 entry = g_list_delete_link(entry, entry);
6782 prpl_info.protocol_options = NULL;
6784 entry = prpl_info.user_splits;
6785 while (entry) {
6786 purple_account_user_split_destroy(entry->data);
6787 entry = g_list_delete_link(entry, entry);
6789 prpl_info.user_splits = NULL;
6792 static void init_plugin(PurplePlugin *plugin)
6794 PurpleAccountUserSplit *split;
6795 PurpleAccountOption *option;
6797 srand(time(NULL));
6799 #ifdef ENABLE_NLS
6800 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6801 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6802 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6803 textdomain(GETTEXT_PACKAGE);
6804 #endif
6806 purple_plugin_register(plugin);
6808 split = purple_account_user_split_new(_("Login \n DOMAIN\\user or\n user@company.com "), NULL, ',');
6809 purple_account_user_split_set_reverse(split, FALSE);
6810 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6812 option = purple_account_option_string_new(_("Server[:Port]\n(Leave empty for auto-discovery)"), "server", "");
6813 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6815 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6816 purple_account_option_add_list_item(option, _("Auto"), "auto");
6817 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6818 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6819 purple_account_option_add_list_item(option, _("UDP"), "udp");
6820 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6822 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6823 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6825 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6826 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6828 #ifdef USE_KERBEROS
6829 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6830 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6832 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6833 * No login/password is taken into account if this option present,
6834 * instead used default credentials stored in OS.
6836 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6837 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6838 #endif
6839 my_protocol = plugin;
6842 /* I had to redefined the function for it load, but works */
6843 gboolean purple_init_plugin(PurplePlugin *plugin){
6844 plugin->info = &(info);
6845 init_plugin((plugin));
6846 sipe_plugin_load((plugin));
6847 return purple_plugin_register(plugin);
6851 Local Variables:
6852 mode: c
6853 c-file-style: "bsd"
6854 indent-tabs-mode: t
6855 tab-width: 8
6856 End: