Fix function prototype for sipe_uuid_get_macaddr()
[siplcs.git] / src / sipe.c
blob543d79962620d9816ac69cbb8a55e0227ee91e0e
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 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
113 return g_strdup(sip->epid);
116 static char *genbranch()
118 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
119 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
120 rand() & 0xFFFF, rand() & 0xFFFF);
123 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount *a,
124 SIPE_UNUSED_PARAMETER PurpleBuddy *b)
126 return "sipe";
129 static void sipe_plugin_destroy(PurplePlugin *plugin);
131 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
133 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
134 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
135 gpointer data);
137 static void sipe_close(PurpleConnection *gc);
139 static void send_presence_status(struct sipe_account_data *sip);
141 static void sendout_pkt(PurpleConnection *gc, const char *buf);
143 static void sipe_keep_alive(PurpleConnection *gc)
145 struct sipe_account_data *sip = gc->proto_data;
146 if (sip->transport == SIPE_TRANSPORT_UDP) {
147 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
148 gchar buf[2] = {0, 0};
149 purple_debug_info("sipe", "sending keep alive\n");
150 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
151 } else {
152 time_t now = time(NULL);
153 if ((sip->keepalive_timeout > 0) &&
154 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
155 #if PURPLE_VERSION_CHECK(2,4,0)
156 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
157 #endif
159 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
160 sendout_pkt(gc, "\r\n\r\n");
161 sip->last_keepalive = now;
166 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
168 struct sip_connection *ret = NULL;
169 GSList *entry = sip->openconns;
170 while (entry) {
171 ret = entry->data;
172 if (ret->fd == fd) return ret;
173 entry = entry->next;
175 return NULL;
178 static void sipe_auth_free(struct sip_auth *auth)
180 g_free(auth->opaque);
181 auth->opaque = NULL;
182 g_free(auth->realm);
183 auth->realm = NULL;
184 g_free(auth->target);
185 auth->target = NULL;
186 auth->type = AUTH_TYPE_UNSET;
187 auth->retries = 0;
188 auth->expires = 0;
189 g_free(auth->gssapi_data);
190 auth->gssapi_data = NULL;
191 sip_sec_destroy_context(auth->gssapi_context);
192 auth->gssapi_context = NULL;
195 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
197 struct sip_connection *ret = g_new0(struct sip_connection, 1);
198 ret->fd = fd;
199 sip->openconns = g_slist_append(sip->openconns, ret);
200 return ret;
203 static void connection_remove(struct sipe_account_data *sip, int fd)
205 struct sip_connection *conn = connection_find(sip, fd);
206 if (conn) {
207 sip->openconns = g_slist_remove(sip->openconns, conn);
208 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
209 g_free(conn->inbuf);
210 g_free(conn);
214 static void connection_free_all(struct sipe_account_data *sip)
216 struct sip_connection *ret = NULL;
217 GSList *entry = sip->openconns;
218 while (entry) {
219 ret = entry->data;
220 connection_remove(sip, ret->fd);
221 entry = sip->openconns;
225 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
227 gchar noncecount[9];
228 const char *authuser = sip->authuser;
229 gchar *response;
230 gchar *ret;
232 if (!authuser || strlen(authuser) < 1) {
233 authuser = sip->username;
236 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
237 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
239 // If we have a signature for the message, include that
240 if (msg->signature) {
241 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);
244 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
245 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
246 gchar *gssapi_data;
247 gchar *opaque;
249 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
250 &(auth->expires),
251 auth->type,
252 purple_account_get_bool(sip->account, "sso", TRUE),
253 sip->authdomain ? sip->authdomain : "",
254 authuser,
255 sip->password,
256 auth->target,
257 auth->gssapi_data);
258 if (!gssapi_data || !auth->gssapi_context)
259 return NULL;
261 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
262 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
263 g_free(opaque);
264 g_free(gssapi_data);
265 return ret;
268 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
270 } else { /* Digest */
272 /* Calculate new session key */
273 if (!auth->opaque) {
274 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
275 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
276 authuser, auth->realm, sip->password,
277 auth->gssapi_data, NULL);
280 sprintf(noncecount, "%08d", auth->nc++);
281 response = purple_cipher_http_digest_calculate_response("md5",
282 msg->method, msg->target, NULL, NULL,
283 auth->gssapi_data, noncecount, NULL,
284 auth->opaque);
285 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
287 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);
288 g_free(response);
289 return ret;
293 static char *parse_attribute(const char *attrname, const char *source)
295 const char *tmp, *tmp2;
296 char *retval = NULL;
297 int len = strlen(attrname);
299 if (!strncmp(source, attrname, len)) {
300 tmp = source + len;
301 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
302 if (tmp2)
303 retval = g_strndup(tmp, tmp2 - tmp);
304 else
305 retval = g_strdup(tmp);
308 return retval;
311 static void fill_auth(gchar *hdr, struct sip_auth *auth)
313 int i;
314 gchar **parts;
316 if (!hdr) {
317 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
318 return;
321 if (!g_strncasecmp(hdr, "NTLM", 4)) {
322 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
323 auth->type = AUTH_TYPE_NTLM;
324 hdr += 5;
325 auth->nc = 1;
326 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
327 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
328 auth->type = AUTH_TYPE_KERBEROS;
329 hdr += 9;
330 auth->nc = 3;
331 } else {
332 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
333 auth->type = AUTH_TYPE_DIGEST;
334 hdr += 7;
337 parts = g_strsplit(hdr, "\", ", 0);
338 for (i = 0; parts[i]; i++) {
339 char *tmp;
341 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
343 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
344 g_free(auth->gssapi_data);
345 auth->gssapi_data = tmp;
347 if (auth->type == AUTH_TYPE_NTLM) {
348 /* NTLM module extracts nonce from gssapi-data */
349 auth->nc = 3;
352 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
353 /* Only used with AUTH_TYPE_DIGEST */
354 g_free(auth->gssapi_data);
355 auth->gssapi_data = tmp;
356 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
357 g_free(auth->opaque);
358 auth->opaque = tmp;
359 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
360 g_free(auth->realm);
361 auth->realm = tmp;
363 if (auth->type == AUTH_TYPE_DIGEST) {
364 /* Throw away old session key */
365 g_free(auth->opaque);
366 auth->opaque = NULL;
367 auth->nc = 1;
370 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
371 g_free(auth->target);
372 auth->target = tmp;
375 g_strfreev(parts);
377 return;
380 static void sipe_canwrite_cb(gpointer data,
381 SIPE_UNUSED_PARAMETER gint source,
382 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
384 PurpleConnection *gc = data;
385 struct sipe_account_data *sip = gc->proto_data;
386 gsize max_write;
387 gssize written;
389 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
391 if (max_write == 0) {
392 if (sip->tx_handler != 0){
393 purple_input_remove(sip->tx_handler);
394 sip->tx_handler = 0;
396 return;
399 written = write(sip->fd, sip->txbuf->outptr, max_write);
401 if (written < 0 && errno == EAGAIN)
402 written = 0;
403 else if (written <= 0) {
404 /*TODO: do we really want to disconnect on a failure to write?*/
405 purple_connection_error(gc, _("Could not write"));
406 return;
409 purple_circ_buffer_mark_read(sip->txbuf, written);
412 static void sipe_canwrite_cb_ssl(gpointer data,
413 SIPE_UNUSED_PARAMETER gint src,
414 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
416 PurpleConnection *gc = data;
417 struct sipe_account_data *sip = gc->proto_data;
418 gsize max_write;
419 gssize written;
421 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
423 if (max_write == 0) {
424 if (sip->tx_handler != 0) {
425 purple_input_remove(sip->tx_handler);
426 sip->tx_handler = 0;
427 return;
431 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
433 if (written < 0 && errno == EAGAIN)
434 written = 0;
435 else if (written <= 0) {
436 /*TODO: do we really want to disconnect on a failure to write?*/
437 purple_connection_error(gc, _("Could not write"));
438 return;
441 purple_circ_buffer_mark_read(sip->txbuf, written);
444 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
446 static void send_later_cb(gpointer data, gint source,
447 SIPE_UNUSED_PARAMETER const gchar *error)
449 PurpleConnection *gc = data;
450 struct sipe_account_data *sip;
451 struct sip_connection *conn;
453 if (!PURPLE_CONNECTION_IS_VALID(gc))
455 if (source >= 0)
456 close(source);
457 return;
460 if (source < 0) {
461 purple_connection_error(gc, _("Could not connect"));
462 return;
465 sip = gc->proto_data;
466 sip->fd = source;
467 sip->connecting = FALSE;
468 sip->last_keepalive = time(NULL);
470 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
472 /* If there is more to write now, we need to register a handler */
473 if (sip->txbuf->bufused > 0)
474 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
476 conn = connection_create(sip, source);
477 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
480 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
482 struct sipe_account_data *sip;
483 struct sip_connection *conn;
485 if (!PURPLE_CONNECTION_IS_VALID(gc))
487 if (gsc) purple_ssl_close(gsc);
488 return NULL;
491 sip = gc->proto_data;
492 sip->fd = gsc->fd;
493 sip->gsc = gsc;
494 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
495 sip->connecting = FALSE;
496 sip->last_keepalive = time(NULL);
498 conn = connection_create(sip, gsc->fd);
500 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
502 return sip;
505 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc,
506 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
508 PurpleConnection *gc = data;
509 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
510 if (sip == NULL) return;
512 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
514 /* If there is more to write now */
515 if (sip->txbuf->bufused > 0) {
516 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
521 static void sendlater(PurpleConnection *gc, const char *buf)
523 struct sipe_account_data *sip = gc->proto_data;
525 if (!sip->connecting) {
526 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
527 if (sip->transport == SIPE_TRANSPORT_TLS){
528 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
529 } else {
530 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
531 purple_connection_error(gc, _("Couldn't create socket"));
534 sip->connecting = TRUE;
537 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
538 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
540 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
543 static void sendout_pkt(PurpleConnection *gc, const char *buf)
545 struct sipe_account_data *sip = gc->proto_data;
546 time_t currtime = time(NULL);
547 int writelen = strlen(buf);
549 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
550 if (sip->transport == SIPE_TRANSPORT_UDP) {
551 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
552 purple_debug_info("sipe", "could not send packet\n");
554 } else {
555 int ret;
556 if (sip->fd < 0) {
557 sendlater(gc, buf);
558 return;
561 if (sip->tx_handler) {
562 ret = -1;
563 errno = EAGAIN;
564 } else{
565 if (sip->gsc){
566 ret = purple_ssl_write(sip->gsc, buf, writelen);
567 }else{
568 ret = write(sip->fd, buf, writelen);
572 if (ret < 0 && errno == EAGAIN)
573 ret = 0;
574 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
575 sendlater(gc, buf);
576 return;
579 if (ret < writelen) {
580 if (!sip->tx_handler){
581 if (sip->gsc){
582 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
584 else{
585 sip->tx_handler = purple_input_add(sip->fd,
586 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
587 gc);
591 /* XXX: is it OK to do this? You might get part of a request sent
592 with part of another. */
593 if (sip->txbuf->bufused > 0)
594 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
596 purple_circ_buffer_append(sip->txbuf, buf + ret,
597 writelen - ret);
602 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
604 sendout_pkt(gc, buf);
605 return len;
608 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
610 GSList *tmp = msg->headers;
611 gchar *name;
612 gchar *value;
613 GString *outstr = g_string_new("");
614 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
615 while (tmp) {
616 name = ((struct siphdrelement*) (tmp->data))->name;
617 value = ((struct siphdrelement*) (tmp->data))->value;
618 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
619 tmp = g_slist_next(tmp);
621 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
622 sendout_pkt(sip->gc, outstr->str);
623 g_string_free(outstr, TRUE);
626 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
628 gchar * buf;
630 if (sip->registrar.type == AUTH_TYPE_UNSET) {
631 return;
634 if (sip->registrar.gssapi_context) {
635 struct sipmsg_breakdown msgbd;
636 gchar *signature_input_str;
637 msgbd.msg = msg;
638 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
639 msgbd.rand = g_strdup_printf("%08x", g_random_int());
640 sip->registrar.ntlm_num++;
641 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
642 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
643 if (signature_input_str != NULL) {
644 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
645 msg->signature = signature_hex;
646 msg->rand = g_strdup(msgbd.rand);
647 msg->num = g_strdup(msgbd.num);
648 g_free(signature_input_str);
650 sipmsg_breakdown_free(&msgbd);
653 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
654 buf = auth_header(sip, &sip->registrar, msg);
655 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
656 g_free(buf);
657 } 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")) {
658 sip->registrar.nc = 3;
659 #ifdef USE_KERBEROS
660 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
661 #endif
662 sip->registrar.type = AUTH_TYPE_NTLM;
663 #ifdef USE_KERBEROS
664 } else {
665 sip->registrar.type = AUTH_TYPE_KERBEROS;
667 #endif
670 buf = auth_header(sip, &sip->registrar, msg);
671 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
672 g_free(buf);
673 } else {
674 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
679 * unused. Needed?
680 static char *get_contact_service(struct sipe_account_data *sip)
682 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()));
683 //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);
687 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
688 const char *text, const char *body)
690 gchar *name;
691 gchar *value;
692 GString *outstr = g_string_new("");
693 struct sipe_account_data *sip = gc->proto_data;
694 gchar *contact;
695 GSList *tmp;
696 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
698 contact = get_contact(sip);
699 sipmsg_add_header(msg, "Contact", contact);
700 g_free(contact);
702 if (body) {
703 gchar len[12];
704 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
705 sipmsg_add_header(msg, "Content-Length", len);
706 } else {
707 sipmsg_add_header(msg, "Content-Length", "0");
710 msg->response = code;
712 sipmsg_strip_headers(msg, keepers);
713 sipmsg_merge_new_headers(msg);
714 sign_outgoing_message(msg, sip, msg->method);
716 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
717 tmp = msg->headers;
718 while (tmp) {
719 name = ((struct siphdrelement*) (tmp->data))->name;
720 value = ((struct siphdrelement*) (tmp->data))->value;
722 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
723 tmp = g_slist_next(tmp);
725 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
726 sendout_pkt(gc, outstr->str);
727 g_string_free(outstr, TRUE);
730 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
732 sip->transactions = g_slist_remove(sip->transactions, trans);
733 if (trans->msg) sipmsg_free(trans->msg);
734 g_free(trans->key);
735 g_free(trans);
738 static struct transaction *
739 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
741 gchar *call_id = NULL;
742 gchar *cseq = NULL;
743 struct transaction *trans = g_new0(struct transaction, 1);
745 trans->time = time(NULL);
746 trans->msg = (struct sipmsg *)msg;
747 call_id = sipmsg_find_header(trans->msg, "Call-ID");
748 cseq = sipmsg_find_header(trans->msg, "CSeq");
749 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
750 trans->callback = callback;
751 sip->transactions = g_slist_append(sip->transactions, trans);
752 return trans;
755 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
757 struct transaction *trans;
758 GSList *transactions = sip->transactions;
759 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
760 gchar *cseq = sipmsg_find_header(msg, "CSeq");
761 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
763 while (transactions) {
764 trans = transactions->data;
765 if (!g_strcasecmp(trans->key, key)) {
766 g_free(key);
767 return trans;
769 transactions = transactions->next;
772 g_free(key);
773 return NULL;
776 struct transaction *
777 send_sip_request(PurpleConnection *gc, const gchar *method,
778 const gchar *url, const gchar *to, const gchar *addheaders,
779 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
781 struct sipe_account_data *sip = gc->proto_data;
782 const char *addh = "";
783 char *buf;
784 struct sipmsg *msg;
785 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
786 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
787 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
788 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
789 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
790 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
791 gchar *route = strdup("");
792 gchar *epid = get_epid(sip); // TODO generate one per account/login
793 int cseq = dialog ? ++dialog->cseq :
794 /* This breaks OCS2007: own presence, contact search, ?
795 1 .* as Call-Id is new in this case */
796 ++sip->cseq;
797 struct transaction *trans;
799 if (dialog && dialog->routes)
801 GSList *iter = dialog->routes;
803 while(iter)
805 char *tmp = route;
806 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
807 g_free(tmp);
808 iter = g_slist_next(iter);
812 if (!ourtag && !dialog) {
813 ourtag = gentag();
816 if (!strcmp(method, "REGISTER")) {
817 if (sip->regcallid) {
818 g_free(callid);
819 callid = g_strdup(sip->regcallid);
820 } else {
821 sip->regcallid = g_strdup(callid);
825 if (addheaders) addh = addheaders;
827 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
828 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
829 "From: <sip:%s>%s%s;epid=%s\r\n"
830 "To: <%s>%s%s%s%s\r\n"
831 "Max-Forwards: 70\r\n"
832 "CSeq: %d %s\r\n"
833 "User-Agent: %s\r\n"
834 "Call-ID: %s\r\n"
835 "%s%s"
836 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
837 method,
838 dialog && dialog->request ? dialog->request : url,
839 TRANSPORT_DESCRIPTOR,
840 purple_network_get_my_ip(-1),
841 sip->listenport,
842 branch ? ";branch=" : "",
843 branch ? branch : "",
844 sip->username,
845 ourtag ? ";tag=" : "",
846 ourtag ? ourtag : "",
847 epid,
849 theirtag ? ";tag=" : "",
850 theirtag ? theirtag : "",
851 theirepid ? ";epid=" : "",
852 theirepid ? theirepid : "",
853 cseq,
854 method,
855 useragent,
856 callid,
857 route,
858 addh,
859 body ? (gsize) strlen(body) : 0,
860 body ? body : "");
863 //printf ("parsing msg buf:\n%s\n\n", buf);
864 msg = sipmsg_parse_msg(buf);
866 g_free(buf);
867 g_free(ourtag);
868 g_free(theirtag);
869 g_free(theirepid);
870 g_free(branch);
871 g_free(callid);
872 g_free(route);
873 g_free(epid);
875 sign_outgoing_message (msg, sip, method);
877 buf = sipmsg_to_string (msg);
879 /* add to ongoing transactions */
880 trans = transactions_add_buf(sip, msg, tc);
881 sendout_pkt(gc, buf);
882 g_free(buf);
884 return trans;
887 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
889 gchar *from = sip_uri_self(sip);
890 gchar *contact = get_contact(sip);
891 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
892 "Content-Type: application/SOAP+xml\r\n",contact);
894 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
895 tr->payload = payload;
897 g_free(from);
898 g_free(contact);
899 g_free(hdr);
902 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
904 send_soap_request_with_cb(sip, body, NULL, NULL);
907 static char *get_contact_register(struct sipe_account_data *sip)
909 char *epid = get_epid(sip);
910 char *uuid = generateUUIDfromEPID(epid);
911 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);
912 g_free(uuid);
913 g_free(epid);
914 return(buf);
917 static void do_register_exp(struct sipe_account_data *sip, int expire)
919 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
920 char *uri = sip_uri_from_name(sip->sipdomain);
921 char *to = sip_uri_self(sip);
922 char *contact = get_contact_register(sip);
923 char *hdr = g_strdup_printf("Contact: %s\r\n"
924 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
925 "Event: registration\r\n"
926 "Allow-Events: presence\r\n"
927 "ms-keep-alive: UAC;hop-hop=yes\r\n"
928 "%s", contact, expires);
929 g_free(contact);
930 g_free(expires);
932 sip->registerstatus = 1;
934 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
935 process_register_response);
937 g_free(hdr);
938 g_free(uri);
939 g_free(to);
942 static void do_register_cb(struct sipe_account_data *sip,
943 SIPE_UNUSED_PARAMETER void *unused)
945 do_register_exp(sip, -1);
946 sip->reregister_set = FALSE;
949 static void do_register(struct sipe_account_data *sip)
951 do_register_exp(sip, -1);
954 static void
955 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
957 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
958 send_soap_request(sip, body);
959 g_free(body);
962 static void
963 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
965 if (allow) {
966 purple_debug_info("sipe", "Authorizing contact %s\n", who);
967 } else {
968 purple_debug_info("sipe", "Blocking contact %s\n", who);
971 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
974 static
975 void sipe_auth_user_cb(void * data)
977 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
978 if (!job) return;
980 sipe_contact_allow_deny (job->sip, job->who, TRUE);
981 g_free(job);
984 static
985 void sipe_deny_user_cb(void * data)
987 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
988 if (!job) return;
990 sipe_contact_allow_deny (job->sip, job->who, FALSE);
991 g_free(job);
994 static void
995 sipe_add_permit(PurpleConnection *gc, const char *name)
997 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
998 sipe_contact_allow_deny(sip, name, TRUE);
1001 static void
1002 sipe_add_deny(PurpleConnection *gc, const char *name)
1004 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1005 sipe_contact_allow_deny(sip, name, FALSE);
1008 /*static void
1009 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1011 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1012 sipe_contact_set_acl(sip, name, "");
1015 static void
1016 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1018 xmlnode *watchers;
1019 xmlnode *watcher;
1020 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1021 if (msg->response != 0 && msg->response != 200) return;
1023 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1025 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1026 if (!watchers) return;
1028 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1029 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1030 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1031 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1033 // TODO pull out optional displayName to pass as alias
1034 if (remote_user) {
1035 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1036 job->who = remote_user;
1037 job->sip = sip;
1038 purple_account_request_authorization(
1039 sip->account,
1040 remote_user,
1041 _("you"), /* id */
1042 alias,
1043 NULL, /* message */
1044 on_list,
1045 sipe_auth_user_cb,
1046 sipe_deny_user_cb,
1047 (void *) job);
1052 xmlnode_free(watchers);
1053 return;
1056 static void
1057 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1059 PurpleGroup * purple_group = purple_find_group(group->name);
1060 if (!purple_group) {
1061 purple_group = purple_group_new(group->name);
1062 purple_blist_add_group(purple_group, NULL);
1065 if (purple_group) {
1066 group->purple_group = purple_group;
1067 sip->groups = g_slist_append(sip->groups, group);
1068 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1069 } else {
1070 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1074 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1076 struct sipe_group *group;
1077 GSList *entry;
1078 if (sip == NULL) {
1079 return NULL;
1082 entry = sip->groups;
1083 while (entry) {
1084 group = entry->data;
1085 if (group->id == id) {
1086 return group;
1088 entry = entry->next;
1090 return NULL;
1093 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1095 struct sipe_group *group;
1096 GSList *entry;
1097 if (sip == NULL) {
1098 return NULL;
1101 entry = sip->groups;
1102 while (entry) {
1103 group = entry->data;
1104 if (!strcmp(group->name, name)) {
1105 return group;
1107 entry = entry->next;
1109 return NULL;
1112 static void
1113 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1115 gchar *body;
1116 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1117 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1118 send_soap_request(sip, body);
1119 g_free(body);
1120 g_free(group->name);
1121 group->name = g_strdup(name);
1125 * Only appends if no such value already stored.
1126 * Like Set in Java.
1128 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1129 GSList * res = list;
1130 if (!g_slist_find_custom(list, data, func)) {
1131 res = g_slist_insert_sorted(list, data, func);
1133 return res;
1136 static int
1137 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1138 return group1->id - group2->id;
1142 * Returns string like "2 4 7 8" - group ids buddy belong to.
1144 static gchar *
1145 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1146 int i = 0;
1147 gchar *res;
1148 //creating array from GList, converting int to gchar*
1149 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1150 GSList *entry = buddy->groups;
1151 while (entry) {
1152 struct sipe_group * group = entry->data;
1153 ids_arr[i] = g_strdup_printf("%d", group->id);
1154 entry = entry->next;
1155 i++;
1157 ids_arr[i] = NULL;
1158 res = g_strjoinv(" ", ids_arr);
1159 g_strfreev(ids_arr);
1160 return res;
1164 * Sends buddy update to server
1166 static void
1167 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1169 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1170 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1172 if (buddy && purple_buddy) {
1173 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1174 gchar *body;
1175 gchar *groups = sipe_get_buddy_groups_string(buddy);
1176 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1178 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1179 alias, groups, "true", buddy->name, sip->contacts_delta++
1181 send_soap_request(sip, body);
1182 g_free(groups);
1183 g_free(body);
1187 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1189 if (msg->response == 200) {
1190 struct sipe_group *group;
1191 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1192 xmlnode *xml;
1193 xmlnode *node;
1194 char *group_id;
1195 struct sipe_buddy *buddy;
1197 xml = xmlnode_from_str(msg->body, msg->bodylen);
1198 if (!xml) {
1199 g_free(ctx);
1200 return FALSE;
1203 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1204 if (!node) {
1205 g_free(ctx);
1206 xmlnode_free(xml);
1207 return FALSE;
1210 group_id = xmlnode_get_data(node);
1211 if (!group_id) {
1212 g_free(ctx);
1213 xmlnode_free(xml);
1214 return FALSE;
1217 group = g_new0(struct sipe_group, 1);
1218 group->id = (int)g_ascii_strtod(group_id, NULL);
1219 g_free(group_id);
1220 group->name = ctx->group_name;
1222 sipe_group_add(sip, group);
1224 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1225 if (buddy) {
1226 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1229 sipe_group_set_user(sip, ctx->user_name);
1231 g_free(ctx);
1232 xmlnode_free(xml);
1233 return TRUE;
1235 return FALSE;
1238 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1240 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1241 gchar *body;
1242 ctx->group_name = g_strdup(name);
1243 ctx->user_name = g_strdup(who);
1245 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1246 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1247 g_free(body);
1251 * Data structure for scheduled actions
1253 typedef void (*Action) (struct sipe_account_data *, void *);
1255 struct scheduled_action {
1257 * Name of action.
1258 * Format is <Event>[<Data>...]
1259 * Example: <presence><sip:user@domain.com> or <registration>
1261 gchar *name;
1262 guint timeout_handler;
1263 gboolean repetitive;
1264 Action action;
1265 GDestroyNotify destroy;
1266 struct sipe_account_data *sip;
1267 void *payload;
1271 * A timer callback
1272 * Should return FALSE if repetitive action is not needed
1274 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1276 gboolean ret;
1277 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1278 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1279 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1280 (sched_action->action)(sched_action->sip, sched_action->payload);
1281 ret = sched_action->repetitive;
1282 if (sched_action->destroy) {
1283 (*sched_action->destroy)(sched_action->payload);
1285 g_free(sched_action->name);
1286 g_free(sched_action);
1287 return ret;
1291 * Kills action timer effectively cancelling
1292 * scheduled action
1294 * @param name of action
1296 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1298 GSList *entry;
1300 if (!sip->timeouts || !name) return;
1302 entry = sip->timeouts;
1303 while (entry) {
1304 struct scheduled_action *sched_action = entry->data;
1305 if(!strcmp(sched_action->name, name)) {
1306 GSList *to_delete = entry;
1307 entry = entry->next;
1308 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1309 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1310 purple_timeout_remove(sched_action->timeout_handler);
1311 if (sched_action->destroy) {
1312 (*sched_action->destroy)(sched_action->payload);
1314 g_free(sched_action->name);
1315 g_free(sched_action);
1316 } else {
1317 entry = entry->next;
1322 static void
1323 sipe_schedule_action0(const gchar *name,
1324 int timeout,
1325 gboolean isSeconds,
1326 Action action,
1327 GDestroyNotify destroy,
1328 struct sipe_account_data *sip,
1329 void *payload)
1331 struct scheduled_action *sched_action;
1333 /* Make sure each action only exists once */
1334 sipe_cancel_scheduled_action(sip, name);
1336 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1337 sched_action = g_new0(struct scheduled_action, 1);
1338 sched_action->repetitive = FALSE;
1339 sched_action->name = g_strdup(name);
1340 sched_action->action = action;
1341 sched_action->destroy = destroy;
1342 sched_action->sip = sip;
1343 sched_action->payload = payload;
1344 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1345 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1346 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1347 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1351 * Do schedule action for execution in the future.
1352 * Non repetitive execution.
1354 * @param name of action (will be copied)
1355 * @param timeout in seconds
1356 * @action callback function
1357 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1359 static void
1360 sipe_schedule_action(const gchar *name,
1361 int timeout,
1362 Action action,
1363 GDestroyNotify destroy,
1364 struct sipe_account_data *sip,
1365 void *payload)
1367 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1371 * Same as sipe_schedule_action() but timeout is in milliseconds.
1373 static void
1374 sipe_schedule_action_msec(const gchar *name,
1375 int timeout,
1376 Action action,
1377 GDestroyNotify destroy,
1378 struct sipe_account_data *sip,
1379 void *payload)
1381 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1385 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1387 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg,
1388 SIPE_UNUSED_PARAMETER struct transaction *tc)
1390 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1392 process_incoming_notify(sip, msg, FALSE, FALSE);
1394 return TRUE;
1397 static void sipe_subscribe_resource_uri(const char *name,
1398 SIPE_UNUSED_PARAMETER gpointer value,
1399 gchar **resources_uri)
1401 gchar *tmp = *resources_uri;
1402 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1403 g_free(tmp);
1406 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1408 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1409 if (sbuddy && !sbuddy->resubscribed) { // Only not resubscribed contacts; the first time everybody are included
1410 gchar *tmp = *resources_uri;
1411 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1412 g_free(tmp);
1417 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1418 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1419 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1420 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1421 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1424 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1426 gchar *contact = get_contact(sip);
1427 gchar *request;
1428 gchar *content;
1429 gchar *require = "";
1430 gchar *accept = "";
1431 gchar *autoextend = "";
1432 gchar *content_type;
1434 if (sip->msrtc_event_categories) {
1435 require = ", categoryList";
1436 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1437 content_type = "application/msrtc-adrl-categorylist+xml";
1438 content = g_strdup_printf(
1439 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1440 "<action name=\"subscribe\" id=\"63792024\">\n"
1441 "<adhocList>\n%s</adhocList>\n"
1442 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1443 "<category name=\"note\"/>\n"
1444 "<category name=\"state\"/>\n"
1445 "</categoryList>\n"
1446 "</action>\n"
1447 "</batchSub>", sip->username, resources_uri);
1448 } else {
1449 autoextend = "Supported: com.microsoft.autoextend\r\n";
1450 content_type = "application/adrl+xml";
1451 content = g_strdup_printf(
1452 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1453 "<create xmlns=\"\">\n%s</create>\n"
1454 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1456 g_free(resources_uri);
1458 request = g_strdup_printf(
1459 "Require: adhoclist%s\r\n"
1460 "Supported: eventlist\r\n"
1461 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1462 "Supported: ms-piggyback-first-notify\r\n"
1463 "%sSupported: ms-benotify\r\n"
1464 "Proxy-Require: ms-benotify\r\n"
1465 "Event: presence\r\n"
1466 "Content-Type: %s\r\n"
1467 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1468 g_free(contact);
1470 /* subscribe to buddy presence */
1471 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1472 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1474 g_free(content);
1475 g_free(to);
1476 g_free(request);
1479 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip,
1480 SIPE_UNUSED_PARAMETER void *unused)
1482 gchar *to = sip_uri_self(sip);
1483 gchar *resources_uri = g_strdup("");
1484 if (sip->msrtc_event_categories) {
1485 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1486 } else {
1487 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1489 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1492 struct presence_batched_routed {
1493 gchar *host;
1494 GSList *buddies;
1497 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1499 struct presence_batched_routed *data = payload;
1500 GSList *buddies = data->buddies;
1501 while (buddies) {
1502 g_free(buddies->data);
1503 buddies = buddies->next;
1505 g_slist_free(data->buddies);
1506 g_free(data->host);
1507 g_free(payload);
1510 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1512 struct presence_batched_routed *data = payload;
1513 GSList *buddies = data->buddies;
1514 gchar *resources_uri = g_strdup("");
1515 while (buddies) {
1516 gchar *tmp = resources_uri;
1517 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1518 g_free(tmp);
1519 buddies = buddies->next;
1521 sipe_subscribe_presence_batched_to(sip, resources_uri,
1522 g_strdup(data->host));
1526 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1527 * The user sends a single SUBSCRIBE request to the subscribed contact.
1528 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1532 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1534 gchar *to = sip_uri((char *)buddy_name);
1535 gchar *tmp = get_contact(sip);
1536 gchar *request;
1537 gchar *content;
1538 gchar *autoextend = "";
1540 if (!sip->msrtc_event_categories)
1541 autoextend = "Supported: com.microsoft.autoextend\r\n";
1543 request = g_strdup_printf(
1544 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1545 "Supported: ms-piggyback-first-notify\r\n"
1546 "%sSupported: ms-benotify\r\n"
1547 "Proxy-Require: ms-benotify\r\n"
1548 "Event: presence\r\n"
1549 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1550 "Contact: %s\r\n", autoextend,tmp);
1552 content = g_strdup_printf(
1553 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1554 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1555 "<resource uri=\"%s\"/>\n"
1556 "</adhocList>\n"
1557 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1558 "<category name=\"note\"/>\n"
1559 "<category name=\"state\"/>\n"
1560 "</categoryList>\n"
1561 "</action>\n"
1562 "</batchSub>", sip->username, to
1565 g_free(tmp);
1567 /* subscribe to buddy presence */
1568 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1570 g_free(content);
1571 g_free(to);
1572 g_free(request);
1575 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1577 if (!purple_status_is_active(status))
1578 return;
1580 if (account->gc) {
1581 struct sipe_account_data *sip = account->gc->proto_data;
1583 if (sip) {
1584 g_free(sip->status);
1585 sip->status = g_strdup(purple_status_get_id(status));
1586 send_presence_status(sip);
1591 static void
1592 sipe_alias_buddy(PurpleConnection *gc, const char *name,
1593 SIPE_UNUSED_PARAMETER const char *alias)
1595 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1596 sipe_group_set_user(sip, name);
1599 static void
1600 sipe_group_buddy(PurpleConnection *gc,
1601 const char *who,
1602 const char *old_group_name,
1603 const char *new_group_name)
1605 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1606 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1607 struct sipe_group * old_group = NULL;
1608 struct sipe_group * new_group;
1610 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1611 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1613 if(!buddy) { // buddy not in roaming list
1614 return;
1617 if (old_group_name) {
1618 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1620 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1622 if (old_group) {
1623 buddy->groups = g_slist_remove(buddy->groups, old_group);
1624 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1627 if (!new_group) {
1628 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1629 } else {
1630 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1631 sipe_group_set_user(sip, who);
1635 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1637 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1638 struct sipe_buddy *b;
1640 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1642 // Prepend sip: if needed
1643 if (strncmp("sip:", buddy->name, 4)) {
1644 gchar *buf = sip_uri_from_name(buddy->name);
1645 purple_blist_rename_buddy(buddy, buf);
1646 g_free(buf);
1649 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1650 b = g_new0(struct sipe_buddy, 1);
1651 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1652 b->name = g_strdup(buddy->name);
1653 g_hash_table_insert(sip->buddies, b->name, b);
1654 sipe_group_buddy(gc, b->name, NULL, group->name);
1655 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1656 } else {
1657 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1661 static void sipe_free_buddy(struct sipe_buddy *buddy)
1663 #ifndef _WIN32
1665 * We are calling g_hash_table_foreach_steal(). That means that no
1666 * key/value deallocation functions are called. Therefore the glib
1667 * hash code does not touch the key (buddy->name) or value (buddy)
1668 * of the to-be-deleted hash node at all. It follows that we
1670 * - MUST free the memory for the key ourselves and
1671 * - ARE allowed to do it in this function
1673 * Conclusion: glib must be broken on the Windows platform if sipe
1674 * crashes with SIGTRAP when closing. You'll have to live
1675 * with the memory leak until this is fixed.
1677 g_free(buddy->name);
1678 #endif
1679 g_free(buddy->annotation);
1680 g_free(buddy->device_name);
1681 g_slist_free(buddy->groups);
1682 g_free(buddy);
1686 * Unassociates buddy from group first.
1687 * Then see if no groups left, removes buddy completely.
1688 * Otherwise updates buddy groups on server.
1690 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1692 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1693 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1694 struct sipe_group *g = NULL;
1696 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1698 if (!b) return;
1700 if (group) {
1701 g = sipe_group_find_by_name(sip, group->name);
1704 if (g) {
1705 b->groups = g_slist_remove(b->groups, g);
1706 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1709 if (g_slist_length(b->groups) < 1) {
1710 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1711 sipe_cancel_scheduled_action(sip, action_name);
1712 g_free(action_name);
1714 g_hash_table_remove(sip->buddies, buddy->name);
1716 if (b->name) {
1717 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1718 send_soap_request(sip, body);
1719 g_free(body);
1722 sipe_free_buddy(b);
1723 } else {
1724 //updates groups on server
1725 sipe_group_set_user(sip, b->name);
1730 static void
1731 sipe_rename_group(PurpleConnection *gc,
1732 const char *old_name,
1733 PurpleGroup *group,
1734 SIPE_UNUSED_PARAMETER GList *moved_buddies)
1736 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1737 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1738 if (group) {
1739 sipe_group_rename(sip, s_group, group->name);
1740 } else {
1741 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1745 static void
1746 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1748 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1749 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1750 if (s_group) {
1751 gchar *body;
1752 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1753 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1754 send_soap_request(sip, body);
1755 g_free(body);
1757 sip->groups = g_slist_remove(sip->groups, s_group);
1758 g_free(s_group->name);
1759 g_free(s_group);
1760 } else {
1761 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1765 static GList *sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount *acc)
1767 PurpleStatusType *type;
1768 GList *types = NULL;
1770 // Online
1771 type = purple_status_type_new_with_attrs(
1772 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1773 // Translators: noun
1774 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1775 NULL);
1776 types = g_list_append(types, type);
1778 // Busy
1779 type = purple_status_type_new_with_attrs(
1780 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1781 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1782 NULL);
1783 types = g_list_append(types, type);
1785 // Do Not Disturb (not user settable)
1786 type = purple_status_type_new_with_attrs(
1787 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1788 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1789 NULL);
1790 types = g_list_append(types, type);
1792 // Be Right Back
1793 type = purple_status_type_new_with_attrs(
1794 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1795 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1796 NULL);
1797 types = g_list_append(types, type);
1799 // Away
1800 type = purple_status_type_new_with_attrs(
1801 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1802 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1803 NULL);
1804 types = g_list_append(types, type);
1806 //On The Phone
1807 type = purple_status_type_new_with_attrs(
1808 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1809 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1810 NULL);
1811 types = g_list_append(types, type);
1813 //Out To Lunch
1814 type = purple_status_type_new_with_attrs(
1815 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1816 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1817 NULL);
1818 types = g_list_append(types, type);
1820 //Appear Offline
1821 type = purple_status_type_new_full(
1822 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1823 types = g_list_append(types, type);
1825 // Offline
1826 type = purple_status_type_new_full(
1827 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1828 types = g_list_append(types, type);
1830 return types;
1834 * A callback for g_hash_table_foreach
1836 static void sipe_buddy_subscribe_cb(SIPE_UNUSED_PARAMETER char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1838 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1839 int time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1840 int timeout = (time_range * rand()) / RAND_MAX; /* random period within the range */
1841 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy->name));
1845 * Removes entries from purple buddy list
1846 * that does not correspond ones in the roaming contact list.
1848 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1849 GSList *buddies = purple_find_buddies(sip->account, NULL);
1850 GSList *entry = buddies;
1851 struct sipe_buddy *buddy;
1852 PurpleBuddy *b;
1853 PurpleGroup *g;
1855 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1856 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1857 while (entry) {
1858 b = entry->data;
1859 g = purple_buddy_get_group(b);
1860 buddy = g_hash_table_lookup(sip->buddies, b->name);
1861 if(buddy) {
1862 gboolean in_sipe_groups = FALSE;
1863 GSList *entry2 = buddy->groups;
1864 while (entry2) {
1865 struct sipe_group *group = entry2->data;
1866 if (!strcmp(group->name, g->name)) {
1867 in_sipe_groups = TRUE;
1868 break;
1870 entry2 = entry2->next;
1872 if(!in_sipe_groups) {
1873 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1874 purple_blist_remove_buddy(b);
1876 } else {
1877 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1878 purple_blist_remove_buddy(b);
1880 entry = entry->next;
1884 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg)
1886 int len = msg->bodylen;
1888 gchar *tmp = sipmsg_find_header(msg, "Event");
1889 xmlnode *item;
1890 xmlnode *isc;
1891 const gchar *contacts_delta;
1892 xmlnode *group_node;
1893 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1894 return FALSE;
1897 /* Convert the contact from XML to Purple Buddies */
1898 isc = xmlnode_from_str(msg->body, len);
1899 if (!isc) {
1900 return FALSE;
1903 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1904 if (contacts_delta) {
1905 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1908 if (!strcmp(isc->name, "contactList")) {
1910 /* Parse groups */
1911 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1912 struct sipe_group * group = g_new0(struct sipe_group, 1);
1913 const char *name = xmlnode_get_attrib(group_node, "name");
1915 if (!strncmp(name, "~", 1)) {
1916 name = _("Other Contacts");
1918 group->name = g_strdup(name);
1919 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1921 sipe_group_add(sip, group);
1924 // Make sure we have at least one group
1925 if (g_slist_length(sip->groups) == 0) {
1926 struct sipe_group * group = g_new0(struct sipe_group, 1);
1927 PurpleGroup *purple_group;
1928 group->name = g_strdup(_("Other Contacts"));
1929 group->id = 1;
1930 purple_group = purple_group_new(group->name);
1931 purple_blist_add_group(purple_group, NULL);
1932 sip->groups = g_slist_append(sip->groups, group);
1935 /* Parse contacts */
1936 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1937 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1938 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1939 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1940 gchar * buddy_name = sip_uri_from_name(uri);
1941 gchar **item_groups;
1942 struct sipe_group *group = NULL;
1943 struct sipe_buddy *buddy = NULL;
1944 int i = 0;
1946 // assign to group Other Contacts if nothing else received
1947 if(!groups || !strcmp("", groups) ) {
1948 group = sipe_group_find_by_name(sip, _("Other Contacts"));
1949 groups = group ? g_strdup_printf("%d", group->id) : "1";
1952 item_groups = g_strsplit(groups, " ", 0);
1954 while (item_groups[i]) {
1955 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1957 // If couldn't find the right group for this contact, just put them in the first group we have
1958 if (group == NULL && g_slist_length(sip->groups) > 0) {
1959 group = sip->groups->data;
1962 if (group != NULL) {
1963 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1964 if (!b){
1965 b = purple_buddy_new(sip->account, buddy_name, uri);
1966 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1969 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1970 if (name != NULL && strlen(name) != 0) {
1971 purple_blist_alias_buddy(b, name);
1975 if (!buddy) {
1976 buddy = g_new0(struct sipe_buddy, 1);
1977 buddy->name = g_strdup(b->name);
1978 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1981 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1983 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
1984 } else {
1985 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1986 name);
1989 i++;
1990 } // while, contact groups
1991 g_strfreev(item_groups);
1992 g_free(groups);
1993 g_free(name);
1994 g_free(buddy_name);
1995 g_free(uri);
1997 } // for, contacts
1999 sipe_cleanup_local_blist(sip);
2001 xmlnode_free(isc);
2003 //subscribe to buddies
2004 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
2005 if(sip->batched_support){
2006 sipe_subscribe_presence_batched(sip, NULL);
2008 else{
2009 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2011 sip->subscribed_buddies = TRUE;
2014 return 0;
2018 * Subscribe roaming contacts
2020 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip)
2022 gchar *to = sip_uri_self(sip);
2023 gchar *tmp = get_contact(sip);
2024 gchar *hdr = g_strdup_printf(
2025 "Event: vnd-microsoft-roaming-contacts\r\n"
2026 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2027 "Supported: com.microsoft.autoextend\r\n"
2028 "Supported: ms-benotify\r\n"
2029 "Proxy-Require: ms-benotify\r\n"
2030 "Supported: ms-piggyback-first-notify\r\n"
2031 "Contact: %s\r\n", tmp);
2032 g_free(tmp);
2034 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2035 g_free(to);
2036 g_free(hdr);
2039 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip,
2040 SIPE_UNUSED_PARAMETER void *unused)
2042 gchar *to = sip_uri_self(sip);
2043 gchar *tmp = get_contact(sip);
2044 gchar *hdr = g_strdup_printf(
2045 "Event: presence.wpending\r\n"
2046 "Accept: text/xml+msrtc.wpending\r\n"
2047 "Supported: com.microsoft.autoextend\r\n"
2048 "Supported: ms-benotify\r\n"
2049 "Proxy-Require: ms-benotify\r\n"
2050 "Supported: ms-piggyback-first-notify\r\n"
2051 "Contact: %s\r\n", tmp);
2052 g_free(tmp);
2054 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2055 g_free(to);
2056 g_free(hdr);
2060 * Fires on deregistration event initiated by server.
2061 * [MS-SIPREGE] SIP extension.
2064 // 2007 Example
2066 // Content-Type: text/registration-event
2067 // subscription-state: terminated;expires=0
2068 // ms-diagnostics-public: 4141;reason="User disabled"
2070 // deregistered;event=rejected
2072 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2074 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2075 gchar *event = NULL;
2076 gchar *reason = NULL;
2077 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2079 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2080 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2082 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2083 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2084 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2085 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2086 } else {
2087 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2088 return;
2091 if (warning != NULL) {
2092 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2093 } else { // for LCS2005
2094 int error_id = 0;
2095 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2096 error_id = 4140; // [MS-SIPREGE]
2097 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2098 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2099 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2100 error_id = 4141;
2101 reason = g_strdup(_("User disabled")); // [MS-OCER]
2102 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2103 error_id = 4142;
2104 reason = g_strdup(_("User moved")); // [MS-OCER]
2107 g_free(event);
2108 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2109 g_free(reason);
2111 sip->gc->wants_to_die = TRUE;
2112 purple_connection_error(sip->gc, warning);
2113 g_free(warning);
2117 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2119 xmlnode *xn_provision_group_list;
2120 xmlnode *node;
2122 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2124 /* provisionGroup */
2125 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2126 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2127 g_free(sip->focus_factory_uri);
2128 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2129 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2130 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2131 break;
2134 xmlnode_free(xn_provision_group_list);
2137 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2139 const gchar *contacts_delta;
2140 xmlnode *xml;
2142 xml = xmlnode_from_str(msg->body, msg->bodylen);
2143 if (!xml)
2145 return;
2148 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2149 if (contacts_delta)
2151 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2154 xmlnode_free(xml);
2157 static void
2158 free_container(struct sipe_container *container)
2160 GSList *entry;
2162 if (!container) return;
2164 entry = container->members;
2165 while (entry) {
2166 g_free(entry->data);
2167 entry = g_slist_remove(entry, entry->data);
2169 g_free(container);
2173 * Finds locally stored MS-PRES container member
2175 static struct sipe_container_member *
2176 sipe_find_container_member(struct sipe_container *container,
2177 const gchar *type,
2178 const gchar *value)
2180 struct sipe_container_member *member;
2181 GSList *entry;
2183 if (container == NULL || type == NULL) {
2184 return NULL;
2187 entry = container->members;
2188 while (entry) {
2189 member = entry->data;
2190 if (!g_strcasecmp(member->type, type)
2191 && ((!member->value && !value)
2192 || (value && member->value && !g_strcasecmp(member->value, value)))
2194 return member;
2196 entry = entry->next;
2198 return NULL;
2202 * Finds locally stored MS-PRES container by id
2204 static struct sipe_container *
2205 sipe_find_container(struct sipe_account_data *sip,
2206 guint id)
2208 struct sipe_container *container;
2209 GSList *entry;
2211 if (sip == NULL) {
2212 return NULL;
2215 entry = sip->containers;
2216 while (entry) {
2217 container = entry->data;
2218 if (id == container->id) {
2219 return container;
2221 entry = entry->next;
2223 return NULL;
2227 * Access Levels
2228 * 32000 - Blocked
2229 * 400 - Personal
2230 * 300 - Team
2231 * 200 - Company
2232 * 100 - Public
2234 static int
2235 sipe_find_access_level(struct sipe_account_data *sip,
2236 const gchar *type,
2237 const gchar *value)
2239 guint containers[] = {32000, 400, 300, 200, 100};
2240 int i = 0;
2242 for (i = 0; i < 5; i++) {
2243 struct sipe_container_member *member;
2244 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2245 if (!container) continue;
2247 member = sipe_find_container_member(container, type, value);
2248 if (member) {
2249 return containers[i];
2253 return -1;
2256 static void
2257 sipe_send_set_container_members(struct sipe_account_data *sip,
2258 guint container_id,
2259 guint container_version,
2260 const gchar* action,
2261 const gchar* type,
2262 const gchar* value)
2264 gchar *self = sip_uri_self(sip);
2265 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2266 gchar *contact;
2267 gchar *hdr;
2268 gchar *body = g_strdup_printf(
2269 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2270 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2271 "</setContainerMembers>",
2272 container_id,
2273 container_version,
2274 action,
2275 type,
2276 value_str);
2277 g_free(value_str);
2279 contact = get_contact(sip);
2280 hdr = g_strdup_printf("Contact: %s\r\n"
2281 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2282 g_free(contact);
2284 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2286 g_free(hdr);
2287 g_free(body);
2288 g_free(self);
2293 * When we receive some self (BE) NOTIFY with a new subscriber
2294 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2297 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
2299 gchar *contact;
2300 gchar *to;
2301 xmlnode *xml;
2302 xmlnode *node;
2303 xmlnode *node2;
2304 char *display_name = NULL;
2305 PurpleBuddy *pbuddy;
2306 const char *alias;
2307 char *uri_alias;
2308 char *uri_user;
2310 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2312 xml = xmlnode_from_str(msg->body, msg->bodylen);
2313 if (!xml) return;
2315 contact = get_contact(sip);
2316 to = sip_uri_self(sip);
2318 /* containers */
2319 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
2320 guint id = atoi(xmlnode_get_attrib(node, "id"));
2321 struct sipe_container *container = sipe_find_container(sip, id);
2323 if (container) {
2324 sip->containers = g_slist_remove(sip->containers, container);
2325 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
2326 free_container(container);
2328 container = g_new0(struct sipe_container, 1);
2329 container->id = id;
2330 container->version = atoi(xmlnode_get_attrib(node, "version"));
2331 sip->containers = g_slist_append(sip->containers, container);
2332 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
2334 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
2335 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2336 member->type = xmlnode_get_attrib(node2, "type");
2337 member->value = xmlnode_get_attrib(node2, "value");
2338 container->members = g_slist_append(container->members, member);
2339 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
2340 member->type, member->value ? member->value : "");
2344 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
2345 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
2346 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
2347 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
2348 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
2349 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
2350 /* initial set-up to let counterparties see your status */
2351 if (sameEnterpriseAL < 0) {
2352 struct sipe_container *container = sipe_find_container(sip, 200);
2353 guint version = container ? container->version : 0;
2354 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
2356 if (federatedAL < 0) {
2357 struct sipe_container *container = sipe_find_container(sip, 100);
2358 guint version = container ? container->version : 0;
2359 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
2361 sip->access_level_set = TRUE;
2364 /* subscribers */
2365 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2366 const char *user;
2367 const char *acknowledged;
2368 gchar *hdr;
2369 gchar *body;
2371 user = xmlnode_get_attrib(node, "user");
2372 if (!user) continue;
2373 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2374 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2375 uri_user = sip_uri_from_name(user);
2376 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2377 if(pbuddy){
2378 alias = purple_buddy_get_local_alias(pbuddy);
2379 uri_alias = sip_uri_from_name(alias);
2380 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2381 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2382 purple_blist_alias_buddy(pbuddy, display_name);
2384 g_free(uri_alias);
2387 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2388 if(!g_ascii_strcasecmp(acknowledged,"false")){
2389 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2390 if (!purple_find_buddy(sip->account, uri_user)) {
2391 purple_account_request_add(sip->account, uri_user, _("you"), display_name, NULL);
2394 hdr = g_strdup_printf(
2395 "Contact: %s\r\n"
2396 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2398 body = g_strdup_printf(
2399 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2400 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2401 "</setSubscribers>", user);
2403 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2404 g_free(body);
2405 g_free(hdr);
2407 g_free(display_name);
2408 g_free(uri_user);
2411 g_free(to);
2412 g_free(contact);
2413 xmlnode_free(xml);
2416 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip)
2418 gchar *to = sip_uri_self(sip);
2419 gchar *tmp = get_contact(sip);
2420 gchar *hdr = g_strdup_printf(
2421 "Event: vnd-microsoft-roaming-ACL\r\n"
2422 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2423 "Supported: com.microsoft.autoextend\r\n"
2424 "Supported: ms-benotify\r\n"
2425 "Proxy-Require: ms-benotify\r\n"
2426 "Supported: ms-piggyback-first-notify\r\n"
2427 "Contact: %s\r\n", tmp);
2428 g_free(tmp);
2430 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2431 g_free(to);
2432 g_free(hdr);
2436 * To request for presence information about the user, access level settings that have already been configured by the user
2437 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2438 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2441 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip)
2443 gchar *to = sip_uri_self(sip);
2444 gchar *tmp = get_contact(sip);
2445 gchar *hdr = g_strdup_printf(
2446 "Event: vnd-microsoft-roaming-self\r\n"
2447 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2448 "Supported: ms-benotify\r\n"
2449 "Proxy-Require: ms-benotify\r\n"
2450 "Supported: ms-piggyback-first-notify\r\n"
2451 "Contact: %s\r\n"
2452 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2454 gchar *body=g_strdup(
2455 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2456 "<roaming type=\"categories\"/>"
2457 "<roaming type=\"containers\"/>"
2458 "<roaming type=\"subscribers\"/></roamingList>");
2460 g_free(tmp);
2461 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2462 g_free(body);
2463 g_free(to);
2464 g_free(hdr);
2468 * For 2005 version
2470 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip)
2472 gchar *to = sip_uri_self(sip);
2473 gchar *tmp = get_contact(sip);
2474 gchar *hdr = g_strdup_printf(
2475 "Event: vnd-microsoft-provisioning\r\n"
2476 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2477 "Supported: com.microsoft.autoextend\r\n"
2478 "Supported: ms-benotify\r\n"
2479 "Proxy-Require: ms-benotify\r\n"
2480 "Supported: ms-piggyback-first-notify\r\n"
2481 "Expires: 0\r\n"
2482 "Contact: %s\r\n", tmp);
2484 g_free(tmp);
2485 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2486 g_free(to);
2487 g_free(hdr);
2490 /** Subscription for provisioning information to help with initial
2491 * configuration. This subscription is a one-time query (denoted by the Expires header,
2492 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2493 * configuration, meeting policies, and policy settings that Communicator must enforce.
2494 * TODO: for what we need this information.
2497 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip)
2499 gchar *to = sip_uri_self(sip);
2500 gchar *tmp = get_contact(sip);
2501 gchar *hdr = g_strdup_printf(
2502 "Event: vnd-microsoft-provisioning-v2\r\n"
2503 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2504 "Supported: com.microsoft.autoextend\r\n"
2505 "Supported: ms-benotify\r\n"
2506 "Proxy-Require: ms-benotify\r\n"
2507 "Supported: ms-piggyback-first-notify\r\n"
2508 "Expires: 0\r\n"
2509 "Contact: %s\r\n"
2510 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2511 gchar *body = g_strdup(
2512 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2513 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2514 "<provisioningGroup name=\"ucPolicy\"/>"
2515 "</provisioningGroupList>");
2517 g_free(tmp);
2518 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2519 g_free(body);
2520 g_free(to);
2521 g_free(hdr);
2524 /* IM Session (INVITE and MESSAGE methods) */
2526 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2527 static gchar *
2528 get_end_points (struct sipe_account_data *sip,
2529 struct sip_session *session)
2531 gchar *res;
2533 if (session == NULL) {
2534 return NULL;
2537 res = g_strdup_printf("<sip:%s>", sip->username);
2539 SIPE_DIALOG_FOREACH {
2540 gchar *tmp = res;
2541 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2542 g_free(tmp);
2544 if (dialog->theirepid) {
2545 tmp = res;
2546 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2547 g_free(tmp);
2549 } SIPE_DIALOG_FOREACH_END;
2551 return res;
2554 static gboolean
2555 process_options_response(SIPE_UNUSED_PARAMETER struct sipe_account_data *sip,
2556 struct sipmsg *msg,
2557 SIPE_UNUSED_PARAMETER struct transaction *trans)
2559 gboolean ret = TRUE;
2561 if (msg->response != 200) {
2562 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2563 return FALSE;
2566 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2568 return ret;
2572 * Asks UA/proxy about its capabilities.
2574 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2576 gchar *to = sip_uri(who);
2577 gchar *contact = get_contact(sip);
2578 gchar *request = g_strdup_printf(
2579 "Accept: application/sdp\r\n"
2580 "Contact: %s\r\n", contact);
2581 g_free(contact);
2583 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2585 g_free(to);
2586 g_free(request);
2589 static void
2590 sipe_notify_user(struct sipe_account_data *sip,
2591 struct sip_session *session,
2592 PurpleMessageFlags flags,
2593 const gchar *message)
2595 PurpleConversation *conv;
2597 if (!session->conv) {
2598 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
2599 } else {
2600 conv = session->conv;
2602 purple_conversation_write(conv, NULL, message, flags, time(NULL));
2605 void
2606 sipe_present_info(struct sipe_account_data *sip,
2607 struct sip_session *session,
2608 const gchar *message)
2610 sipe_notify_user(sip, session, PURPLE_MESSAGE_SYSTEM, message);
2613 static void
2614 sipe_present_err(struct sipe_account_data *sip,
2615 struct sip_session *session,
2616 const gchar *message)
2618 sipe_notify_user(sip, session, PURPLE_MESSAGE_ERROR, message);
2621 void
2622 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
2623 struct sip_session *session,
2624 const gchar *who,
2625 const gchar *message)
2627 char *msg, *msg_tmp;
2629 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2630 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2631 g_free(msg_tmp);
2632 msg_tmp = g_strdup_printf( _("This message was not delivered to %s because one or more recipients are offline:\n%s") ,
2633 who ? who : "", msg ? msg : "");
2634 sipe_present_err(sip, session, msg_tmp);
2635 g_free(msg_tmp);
2636 g_free(msg);
2640 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
2642 static gboolean
2643 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg,
2644 SIPE_UNUSED_PARAMETER struct transaction *trans)
2646 gboolean ret = TRUE;
2647 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2648 struct sip_session *session = sipe_session_find_im(sip, with);
2649 struct sip_dialog *dialog;
2650 gchar *cseq;
2651 char *key;
2652 gchar *message;
2654 if (!session) {
2655 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2656 g_free(with);
2657 return FALSE;
2660 dialog = sipe_dialog_find(session, with);
2661 if (!dialog) {
2662 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2663 g_free(with);
2664 return FALSE;
2667 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2668 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2669 g_free(cseq);
2670 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2672 if (msg->response >= 400) {
2673 PurpleBuddy *pbuddy;
2674 gchar *alias = with;
2676 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
2678 if ((pbuddy = purple_find_buddy(sip->account, with))) {
2679 alias = (gchar *)purple_buddy_get_alias(pbuddy);
2682 sipe_present_message_undelivered_err(sip, session, alias, message);
2683 ret = FALSE;
2684 } else {
2685 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
2686 if (message_id) {
2687 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
2688 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
2689 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
2692 g_hash_table_remove(session->unconfirmed_messages, key);
2693 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2694 key, g_hash_table_size(session->unconfirmed_messages));
2697 g_free(key);
2698 g_free(with);
2700 if (ret) sipe_im_process_queue(sip, session);
2701 return ret;
2704 static gboolean
2705 sipe_is_election_finished(struct sip_session *session);
2707 static void
2708 sipe_election_result(struct sipe_account_data *sip,
2709 void *sess);
2711 static gboolean
2712 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg,
2713 SIPE_UNUSED_PARAMETER struct transaction *trans)
2715 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2716 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2717 struct sip_dialog *dialog;
2718 struct sip_session *session;
2720 session = sipe_session_find_chat_by_callid(sip, callid);
2721 if (!session) {
2722 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2723 return FALSE;
2726 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2727 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2728 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2729 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2731 if (xn_request_rm_response) {
2732 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2733 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2735 dialog = sipe_dialog_find(session, with);
2736 if (!dialog) {
2737 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2738 return FALSE;
2741 if (allow && !g_strcasecmp(allow, "true")) {
2742 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2743 dialog->election_vote = 1;
2744 } else if (allow && !g_strcasecmp(allow, "false")) {
2745 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2746 dialog->election_vote = -1;
2749 if (sipe_is_election_finished(session)) {
2750 sipe_election_result(sip, session);
2753 } else if (xn_set_rm_response) {
2756 xmlnode_free(xn_action);
2760 return TRUE;
2763 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2765 gchar *hdr;
2766 gchar *tmp;
2767 char *msgformat;
2768 char *msgtext;
2769 gchar *msgr_value;
2770 gchar *msgr;
2772 sipe_parse_html(msg, &msgformat, &msgtext);
2773 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2775 msgr_value = sipmsg_get_msgr_string(msgformat);
2776 g_free(msgformat);
2777 if (msgr_value) {
2778 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2779 g_free(msgr_value);
2780 } else {
2781 msgr = g_strdup("");
2784 tmp = get_contact(sip);
2785 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2786 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2787 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2788 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2789 g_free(tmp);
2790 g_free(msgr);
2792 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2793 g_free(msgtext);
2794 g_free(hdr);
2798 static void
2799 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
2801 GSList *entry2 = session->outgoing_message_queue;
2802 while (entry2) {
2803 char *queued_msg = entry2->data;
2805 /* for multiparty chat or conference */
2806 if (session->is_multiparty || session->focus_uri) {
2807 gchar *who = sip_uri_self(sip);
2808 serv_got_chat_in(sip->gc, session->chat_id, who,
2809 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2810 g_free(who);
2813 SIPE_DIALOG_FOREACH {
2814 char *key;
2816 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
2818 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2819 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2820 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2821 key, g_hash_table_size(session->unconfirmed_messages));
2822 g_free(key);
2824 sipe_send_message(sip, dialog, queued_msg);
2825 } SIPE_DIALOG_FOREACH_END;
2827 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2828 g_free(queued_msg);
2832 static void
2833 sipe_refer_notify(struct sipe_account_data *sip,
2834 struct sip_session *session,
2835 const gchar *who,
2836 int status,
2837 const gchar *desc)
2839 gchar *hdr;
2840 gchar *body;
2841 struct sip_dialog *dialog = sipe_dialog_find(session, who);
2843 hdr = g_strdup_printf(
2844 "Event: refer\r\n"
2845 "Subscription-State: %s\r\n"
2846 "Content-Type: message/sipfrag\r\n",
2847 status >= 200 ? "terminated" : "active");
2849 body = g_strdup_printf(
2850 "SIP/2.0 %d %s\r\n",
2851 status, desc);
2853 dialog->outgoing_invite = send_sip_request(sip->gc, "NOTIFY",
2854 who, who, hdr, body, dialog, NULL);
2856 g_free(hdr);
2857 g_free(body);
2860 static gboolean
2861 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2863 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2864 struct sip_session *session;
2865 struct sip_dialog *dialog;
2866 char *cseq;
2867 char *key;
2868 gchar *message;
2869 struct sipmsg *request_msg = trans->msg;
2871 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2872 gchar *referred_by;
2874 session = sipe_session_find_chat_by_callid(sip, callid);
2875 if (!session) {
2876 session = sipe_session_find_im(sip, with);
2878 if (!session) {
2879 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2880 g_free(with);
2881 return FALSE;
2884 dialog = sipe_dialog_find(session, with);
2885 if (!dialog) {
2886 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2887 g_free(with);
2888 return FALSE;
2891 sipe_dialog_parse(dialog, msg, TRUE);
2893 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2894 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2895 g_free(cseq);
2896 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2898 if (msg->response != 200) {
2899 PurpleBuddy *pbuddy;
2900 gchar *alias = with;
2902 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2904 if ((pbuddy = purple_find_buddy(sip->account, with))) {
2905 alias = (gchar *)purple_buddy_get_alias(pbuddy);
2908 if (message) {
2909 sipe_present_message_undelivered_err(sip, session, alias, message);
2910 } else {
2911 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
2912 sipe_present_err(sip, session, tmp_msg);
2913 g_free(tmp_msg);
2916 sipe_dialog_remove(session, with);
2918 g_free(key);
2919 g_free(with);
2920 return FALSE;
2923 dialog->cseq = 0;
2924 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
2925 dialog->outgoing_invite = NULL;
2926 dialog->is_established = TRUE;
2928 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
2929 if (referred_by) {
2930 sipe_refer_notify(sip, session, referred_by, 200, "OK");
2931 g_free(referred_by);
2934 /* add user to chat if it is a multiparty session */
2935 if (session->is_multiparty) {
2936 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
2937 with, NULL,
2938 PURPLE_CBFLAGS_NONE, TRUE);
2941 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2942 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2943 if (session->outgoing_message_queue) {
2944 char *queued_msg = session->outgoing_message_queue->data;
2945 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2946 g_free(queued_msg);
2950 sipe_im_process_queue(sip, session);
2952 g_hash_table_remove(session->unconfirmed_messages, key);
2953 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2954 key, g_hash_table_size(session->unconfirmed_messages));
2956 g_free(key);
2957 g_free(with);
2958 return TRUE;
2962 void
2963 sipe_invite(struct sipe_account_data *sip,
2964 struct sip_session *session,
2965 const gchar *who,
2966 const gchar *msg_body,
2967 const gchar *referred_by,
2968 const gboolean is_triggered)
2970 gchar *hdr;
2971 gchar *to;
2972 gchar *contact;
2973 gchar *body;
2974 gchar *self;
2975 char *ms_text_format = g_strdup("");
2976 gchar *roster_manager;
2977 gchar *end_points;
2978 gchar *referred_by_str;
2979 struct sip_dialog *dialog = sipe_dialog_find(session, who);
2981 if (dialog && dialog->is_established) {
2982 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
2983 return;
2986 if (!dialog) {
2987 dialog = sipe_dialog_add(session);
2988 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
2989 dialog->with = g_strdup(who);
2992 if (!(dialog->ourtag)) {
2993 dialog->ourtag = gentag();
2996 to = sip_uri(who);
2998 if (msg_body) {
2999 char *msgformat;
3000 char *msgtext;
3001 char *base64_msg;
3002 gchar *msgr_value;
3003 gchar *msgr;
3004 char *key;
3006 sipe_parse_html(msg_body, &msgformat, &msgtext);
3007 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
3009 msgr_value = sipmsg_get_msgr_string(msgformat);
3010 g_free(msgformat);
3011 msgr = "";
3012 if (msgr_value) {
3013 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3014 g_free(msgr_value);
3017 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3018 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3019 g_free(msgtext);
3020 g_free(msgr);
3021 g_free(base64_msg);
3023 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3024 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3025 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3026 key, g_hash_table_size(session->unconfirmed_messages));
3027 g_free(key);
3030 contact = get_contact(sip);
3031 end_points = get_end_points(sip, session);
3032 self = sip_uri_self(sip);
3033 roster_manager = g_strdup_printf(
3034 "Roster-Manager: %s\r\n"
3035 "EndPoints: %s\r\n",
3036 self,
3037 end_points);
3038 referred_by_str = referred_by ?
3039 g_strdup_printf(
3040 "Referred-By: %s\r\n",
3041 referred_by)
3042 : g_strdup("");
3043 hdr = g_strdup_printf(
3044 "Supported: ms-sender\r\n"
3045 "%s"
3046 "%s"
3047 "%s"
3048 "%s"
3049 "Contact: %s\r\n%s"
3050 "Content-Type: application/sdp\r\n",
3051 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3052 referred_by_str,
3053 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3054 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3055 contact,
3056 ms_text_format);
3057 g_free(ms_text_format);
3058 g_free(self);
3060 body = g_strdup_printf(
3061 "v=0\r\n"
3062 "o=- 0 0 IN IP4 %s\r\n"
3063 "s=session\r\n"
3064 "c=IN IP4 %s\r\n"
3065 "t=0 0\r\n"
3066 "m=message %d sip null\r\n"
3067 "a=accept-types:text/plain text/html image/gif "
3068 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3069 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3071 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3072 to, to, hdr, body, dialog, process_invite_response);
3074 g_free(to);
3075 g_free(roster_manager);
3076 g_free(end_points);
3077 g_free(referred_by_str);
3078 g_free(body);
3079 g_free(hdr);
3080 g_free(contact);
3083 static void
3084 sipe_refer(struct sipe_account_data *sip,
3085 struct sip_session *session,
3086 const gchar *who)
3088 gchar *hdr;
3089 gchar *contact;
3090 struct sip_dialog *dialog = sipe_dialog_find(session,
3091 session->roster_manager);
3093 contact = get_contact(sip);
3094 hdr = g_strdup_printf(
3095 "Contact: %s\r\n"
3096 "Refer-to: <%s>\r\n"
3097 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3098 "Require: com.microsoft.rtc-multiparty\r\n",
3099 contact,
3100 who,
3101 sip->username,
3102 dialog->ourtag ? ";tag=" : "",
3103 dialog->ourtag ? dialog->ourtag : "",
3104 get_epid(sip));
3106 send_sip_request(sip->gc, "REFER",
3107 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3109 g_free(hdr);
3110 g_free(contact);
3113 static void
3114 sipe_send_election_request_rm(struct sipe_account_data *sip,
3115 struct sip_dialog *dialog,
3116 int bid)
3118 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3120 gchar *body = g_strdup_printf(
3121 "<?xml version=\"1.0\"?>\r\n"
3122 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3123 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3124 sip->username, bid);
3126 send_sip_request(sip->gc, "INFO",
3127 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3129 g_free(body);
3132 static void
3133 sipe_send_election_set_rm(struct sipe_account_data *sip,
3134 struct sip_dialog *dialog)
3136 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3138 gchar *body = g_strdup_printf(
3139 "<?xml version=\"1.0\"?>\r\n"
3140 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3141 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3142 sip->username);
3144 send_sip_request(sip->gc, "INFO",
3145 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3147 g_free(body);
3150 static void
3151 sipe_session_close(struct sipe_account_data *sip,
3152 struct sip_session * session)
3154 if (session && session->focus_uri) {
3155 conf_session_close(sip, session);
3158 if (session) {
3159 SIPE_DIALOG_FOREACH {
3160 /* @TODO slow down BYE message sending rate */
3161 /* @see single subscription code */
3162 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3163 } SIPE_DIALOG_FOREACH_END;
3165 sipe_session_remove(sip, session);
3169 static void
3170 sipe_session_close_all(struct sipe_account_data *sip)
3172 GSList *entry;
3173 while ((entry = sip->sessions) != NULL) {
3174 sipe_session_close(sip, entry->data);
3178 static void
3179 sipe_convo_closed(PurpleConnection * gc, const char *who)
3181 struct sipe_account_data *sip = gc->proto_data;
3183 purple_debug_info("sipe", "conversation with %s closed\n", who);
3184 sipe_session_close(sip, sipe_session_find_im(sip, who));
3187 static void
3188 sipe_chat_leave (PurpleConnection *gc, int id)
3190 struct sipe_account_data *sip = gc->proto_data;
3191 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
3193 sipe_session_close(sip, session);
3196 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what,
3197 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3199 struct sipe_account_data *sip = gc->proto_data;
3200 struct sip_session *session;
3201 struct sip_dialog *dialog;
3203 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3205 session = sipe_session_find_or_add_im(sip, who);
3206 dialog = sipe_dialog_find(session, who);
3208 // Queue the message
3209 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3211 if (dialog && dialog->callid) {
3212 sipe_im_process_queue(sip, session);
3213 } else if (!dialog || !dialog->outgoing_invite) {
3214 // Need to send the INVITE to get the outgoing dialog setup
3215 sipe_invite(sip, session, who, what, NULL, FALSE);
3218 return 1;
3221 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what,
3222 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags)
3224 struct sipe_account_data *sip = gc->proto_data;
3225 struct sip_session *session;
3227 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3229 session = sipe_session_find_chat_by_id(sip, id);
3231 // Queue the message
3232 if (session && session->dialogs) {
3233 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3234 g_strdup(what));
3235 sipe_im_process_queue(sip, session);
3238 return 1;
3241 /* End IM Session (INVITE and MESSAGE methods) */
3243 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3245 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3246 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3247 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3249 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
3250 if (!session) {
3251 session = sipe_session_find_im(sip, from);
3253 if (!session) {
3254 g_free(from);
3255 return;
3258 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3259 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3260 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3261 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3263 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
3265 if (xn_request_rm) {
3266 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3267 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3268 gchar *body = g_strdup_printf(
3269 "<?xml version=\"1.0\"?>\r\n"
3270 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3271 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3272 sip->username,
3273 session->bid < bid ? "true" : "false");
3274 send_sip_response(sip->gc, msg, 200, "OK", body);
3275 g_free(body);
3276 } else if (xn_set_rm) {
3277 gchar *body;
3278 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3279 g_free(session->roster_manager);
3280 session->roster_manager = g_strdup(rm);
3282 body = g_strdup_printf(
3283 "<?xml version=\"1.0\"?>\r\n"
3284 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3285 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3286 sip->username);
3287 send_sip_response(sip->gc, msg, 200, "OK", body);
3288 g_free(body);
3290 xmlnode_free(xn_action);
3292 } else {
3293 /* looks like purple lacks typing notification for chat */
3294 if (!session->is_multiparty && !session->focus_uri) {
3295 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3298 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3300 g_free(from);
3303 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3305 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3306 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3307 struct sip_session *session;
3309 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3311 session = sipe_session_find_chat_by_callid(sip, callid);
3312 if (!session) {
3313 session = sipe_session_find_im(sip, from);
3315 if (!session) {
3316 g_free(from);
3317 return;
3320 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3321 g_free(session->roster_manager);
3322 session->roster_manager = NULL;
3325 /* This what BYE is essentially for - terminating dialog */
3326 sipe_dialog_remove(session, from);
3327 if (session->focus_uri && !g_strcasecmp(from, session->im_mcu_uri)) {
3328 sipe_conf_immcu_closed(sip, session);
3329 } else if (session->is_multiparty) {
3330 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3333 g_free(from);
3336 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3338 gchar *self = sip_uri_self(sip);
3339 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3340 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3341 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3342 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3343 struct sip_session *session;
3344 struct sip_dialog *dialog;
3346 session = sipe_session_find_chat_by_callid(sip, callid);
3347 dialog = sipe_dialog_find(session, from);
3349 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3350 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3351 } else {
3352 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3354 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3357 g_free(self);
3358 g_free(from);
3359 g_free(refer_to);
3360 g_free(referred_by);
3363 static unsigned int
3364 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3366 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3367 struct sip_session *session;
3368 struct sip_dialog *dialog;
3370 if (state == PURPLE_NOT_TYPING)
3371 return 0;
3373 session = sipe_session_find_im(sip, who);
3374 dialog = sipe_dialog_find(session, who);
3376 if (session && dialog && dialog->is_established) {
3377 send_sip_request(gc, "INFO", who, who,
3378 "Content-Type: application/xml\r\n",
3379 SIPE_SEND_TYPING, dialog, NULL);
3381 return SIPE_TYPING_SEND_TIMEOUT;
3384 static gboolean resend_timeout(struct sipe_account_data *sip)
3386 GSList *tmp = sip->transactions;
3387 time_t currtime = time(NULL);
3388 while (tmp) {
3389 struct transaction *trans = tmp->data;
3390 tmp = tmp->next;
3391 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3392 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3393 /* TODO 408 */
3394 } else {
3395 if ((currtime - trans->time > 2) && trans->retries == 0) {
3396 trans->retries++;
3397 sendout_sipmsg(sip, trans->msg);
3401 return TRUE;
3404 static void do_reauthenticate_cb(struct sipe_account_data *sip,
3405 SIPE_UNUSED_PARAMETER void *unused)
3407 /* register again when security token expires */
3408 /* we have to start a new authentication as the security token
3409 * is almost expired by sending a not signed REGISTER message */
3410 purple_debug_info("sipe", "do a full reauthentication\n");
3411 sipe_auth_free(&sip->registrar);
3412 sipe_auth_free(&sip->proxy);
3413 sip->registerstatus = 0;
3414 do_register(sip);
3415 sip->reauthenticate_set = FALSE;
3418 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3420 gchar *from;
3421 gchar *contenttype;
3422 gboolean found = FALSE;
3424 from = parse_from(sipmsg_find_header(msg, "From"));
3426 if (!from) return;
3428 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3430 contenttype = sipmsg_find_header(msg, "Content-Type");
3431 if (!strncmp(contenttype, "text/plain", 10)
3432 || !strncmp(contenttype, "text/html", 9)
3433 || !strncmp(contenttype, "multipart/related", 21))
3435 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3436 gchar *html = get_html_message(contenttype, msg->body);
3438 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
3439 if (!session) {
3440 session = sipe_session_find_im(sip, from);
3443 if (session && session->focus_uri) { /* a conference */
3444 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
3445 gchar *sender = parse_from(tmp);
3446 g_free(tmp);
3447 serv_got_chat_in(sip->gc, session->chat_id, sender,
3448 PURPLE_MESSAGE_RECV, html, time(NULL));
3449 g_free(sender);
3450 } else if (session && session->is_multiparty) { /* a multiparty chat */
3451 serv_got_chat_in(sip->gc, session->chat_id, from,
3452 PURPLE_MESSAGE_RECV, html, time(NULL));
3453 } else {
3454 serv_got_im(sip->gc, from, html, 0, time(NULL));
3456 g_free(html);
3457 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3458 found = TRUE;
3460 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3461 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3462 xmlnode *state;
3463 gchar *statedata;
3465 if (!isc) {
3466 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3467 return;
3470 state = xmlnode_get_child(isc, "state");
3472 if (!state) {
3473 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3474 xmlnode_free(isc);
3475 return;
3478 statedata = xmlnode_get_data(state);
3479 if (statedata) {
3480 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3481 else serv_got_typing_stopped(sip->gc, from);
3483 g_free(statedata);
3485 xmlnode_free(isc);
3486 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3487 found = TRUE;
3489 if (!found) {
3490 purple_debug_info("sipe", "got unknown mime-type");
3491 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3493 g_free(from);
3496 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3498 gchar *ms_text_format;
3499 gchar *body;
3500 gchar *newTag;
3501 gchar *oldHeader;
3502 gchar *newHeader;
3503 gboolean is_multiparty = FALSE;
3504 gboolean is_triggered = FALSE;
3505 gboolean was_multiparty = TRUE;
3506 gboolean just_joined = FALSE;
3507 gchar *from;
3508 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3509 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
3510 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
3511 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
3512 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3513 GSList *end_points = NULL;
3514 struct sip_session *session;
3516 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3518 /* Invitation to join conference */
3519 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
3520 process_incoming_invite_conf(sip, msg);
3521 return;
3524 /* Only accept text invitations */
3525 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3526 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3527 return;
3530 // TODO There *must* be a better way to clean up the To header to add a tag...
3531 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3532 oldHeader = sipmsg_find_header(msg, "To");
3533 newTag = gentag();
3534 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3535 sipmsg_remove_header_now(msg, "To");
3536 sipmsg_add_header_now(msg, "To", newHeader);
3537 g_free(newHeader);
3539 if (end_points_hdr) {
3540 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
3542 if (g_slist_length(end_points) > 2) {
3543 is_multiparty = TRUE;
3546 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
3547 is_triggered = TRUE;
3548 is_multiparty = TRUE;
3551 session = sipe_session_find_chat_by_callid(sip, callid);
3552 /* Convert to multiparty */
3553 if (session && is_multiparty && !session->is_multiparty) {
3554 g_free(session->with);
3555 session->with = NULL;
3556 was_multiparty = FALSE;
3557 session->is_multiparty = TRUE;
3558 session->chat_id = rand();
3561 if (!session && is_multiparty) {
3562 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
3564 /* IM session */
3565 from = parse_from(sipmsg_find_header(msg, "From"));
3566 if (!session) {
3567 session = sipe_session_find_or_add_im(sip, from);
3570 g_free(session->callid);
3571 session->callid = g_strdup(callid);
3573 session->is_multiparty = is_multiparty;
3574 if (roster_manager) {
3575 session->roster_manager = g_strdup(roster_manager);
3578 if (is_multiparty && end_points) {
3579 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3580 GSList *entry = end_points;
3581 while (entry) {
3582 struct sip_dialog *dialog;
3583 struct sipendpoint *end_point = entry->data;
3584 entry = entry->next;
3586 if (!g_strcasecmp(from, end_point->contact) ||
3587 !g_strcasecmp(to, end_point->contact))
3588 continue;
3590 dialog = sipe_dialog_find(session, end_point->contact);
3591 if (dialog) {
3592 g_free(dialog->theirepid);
3593 dialog->theirepid = end_point->epid;
3594 end_point->epid = NULL;
3595 } else {
3596 dialog = sipe_dialog_add(session);
3598 dialog->callid = g_strdup(session->callid);
3599 dialog->with = end_point->contact;
3600 end_point->contact = NULL;
3601 dialog->theirepid = end_point->epid;
3602 end_point->epid = NULL;
3604 just_joined = TRUE;
3606 /* send triggered INVITE */
3607 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
3610 g_free(to);
3613 if (end_points) {
3614 GSList *entry = end_points;
3615 while (entry) {
3616 struct sipendpoint *end_point = entry->data;
3617 entry = entry->next;
3618 g_free(end_point->contact);
3619 g_free(end_point->epid);
3620 g_free(end_point);
3622 g_slist_free(end_points);
3625 if (session) {
3626 struct sip_dialog *dialog = sipe_dialog_find(session, from);
3627 if (dialog) {
3628 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3629 } else {
3630 dialog = sipe_dialog_add(session);
3632 dialog->callid = g_strdup(session->callid);
3633 dialog->with = g_strdup(from);
3634 sipe_dialog_parse(dialog, msg, FALSE);
3636 if (!dialog->ourtag) {
3637 dialog->ourtag = newTag;
3638 newTag = NULL;
3641 just_joined = TRUE;
3643 } else {
3644 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3646 g_free(newTag);
3648 if (is_multiparty && !session->conv) {
3649 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3650 gchar *self = sip_uri_self(sip);
3651 /* create prpl chat */
3652 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3653 session->chat_name = g_strdup(chat_name);
3654 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
3655 /* add self */
3656 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3657 self, NULL,
3658 PURPLE_CBFLAGS_NONE, FALSE);
3659 g_free(chat_name);
3660 g_free(self);
3663 if (is_multiparty && !was_multiparty) {
3664 /* add current IM counterparty to chat */
3665 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3666 sipe_dialog_first(session)->with, NULL,
3667 PURPLE_CBFLAGS_NONE, FALSE);
3670 /* add inviting party */
3671 if (just_joined) {
3672 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3673 from, NULL,
3674 PURPLE_CBFLAGS_NONE, TRUE);
3677 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3678 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3679 if (ms_text_format) {
3680 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3682 gchar *html = get_html_message(ms_text_format, NULL);
3683 if (html) {
3684 if (is_multiparty) {
3685 serv_got_chat_in(sip->gc, session->chat_id, from,
3686 PURPLE_MESSAGE_RECV, html, time(NULL));
3687 } else {
3688 serv_got_im(sip->gc, from, html, 0, time(NULL));
3690 g_free(html);
3691 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3695 g_free(from);
3697 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3698 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3699 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3701 body = g_strdup_printf(
3702 "v=0\r\n"
3703 "o=- 0 0 IN IP4 %s\r\n"
3704 "s=session\r\n"
3705 "c=IN IP4 %s\r\n"
3706 "t=0 0\r\n"
3707 "m=message %d sip sip:%s\r\n"
3708 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3709 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3710 sip->realport, sip->username);
3711 send_sip_response(sip->gc, msg, 200, "OK", body);
3712 g_free(body);
3715 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3717 gchar *body;
3719 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
3720 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3721 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3723 body = g_strdup_printf(
3724 "v=0\r\n"
3725 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3726 "s=session\r\n"
3727 "c=IN IP4 0.0.0.0\r\n"
3728 "t=0 0\r\n"
3729 "m=message %d sip sip:%s\r\n"
3730 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3731 sip->realport, sip->username);
3732 send_sip_response(sip->gc, msg, 200, "OK", body);
3733 g_free(body);
3736 static void sipe_connection_cleanup(struct sipe_account_data *);
3737 static void create_connection(struct sipe_account_data *, gchar *, int);
3739 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3741 gchar *tmp;
3742 const gchar *expires_header;
3743 int expires, i;
3744 GSList *hdr = msg->headers;
3745 struct siphdrelement *elem;
3747 expires_header = sipmsg_find_header(msg, "Expires");
3748 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3749 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3751 switch (msg->response) {
3752 case 200:
3753 if (expires == 0) {
3754 sip->registerstatus = 0;
3755 } else {
3756 gchar *contact_hdr = NULL;
3757 gchar *gruu = NULL;
3758 gchar *epid;
3759 gchar *uuid;
3760 gchar *timeout;
3762 if (!sip->reregister_set) {
3763 gchar *action_name = g_strdup_printf("<%s>", "registration");
3764 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3765 g_free(action_name);
3766 sip->reregister_set = TRUE;
3769 sip->registerstatus = 3;
3771 #ifdef USE_KERBEROS
3772 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3773 #endif
3774 tmp = sipmsg_find_auth_header(msg, "NTLM");
3775 #ifdef USE_KERBEROS
3776 } else {
3777 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3779 #endif
3780 if (tmp) {
3781 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3782 fill_auth(tmp, &sip->registrar);
3785 if (!sip->reauthenticate_set) {
3786 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3787 guint reauth_timeout;
3788 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3789 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3790 reauth_timeout = sip->registrar.expires - 300;
3791 } else {
3792 /* NTLM: we have to reauthenticate as our security token expires
3793 after eight hours (be five minutes early) */
3794 reauth_timeout = (8 * 3600) - 300;
3796 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3797 g_free(action_name);
3798 sip->reauthenticate_set = TRUE;
3801 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3803 epid = get_epid(sip);
3804 uuid = generateUUIDfromEPID(epid);
3805 g_free(epid);
3807 // There can be multiple Contact headers (one per location where the user is logged in) so
3808 // make sure to only get the one for this uuid
3809 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3810 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3811 if (valid_contact) {
3812 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3813 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3814 g_free(valid_contact);
3815 break;
3816 } else {
3817 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3820 g_free(uuid);
3822 g_free(sip->contact);
3823 if(gruu) {
3824 sip->contact = g_strdup_printf("<%s>", gruu);
3825 g_free(gruu);
3826 } else {
3827 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3828 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);
3830 sip->msrtc_event_categories = FALSE;
3831 sip->batched_support = FALSE;
3833 while(hdr)
3835 elem = hdr->data;
3836 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3837 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3838 sip->msrtc_event_categories = TRUE;
3839 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3841 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3842 sip->batched_support = TRUE;
3843 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3846 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3847 gchar **caps = g_strsplit(elem->value,",",0);
3848 i = 0;
3849 while (caps[i]) {
3850 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3851 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3852 i++;
3854 g_strfreev(caps);
3856 hdr = g_slist_next(hdr);
3859 /* subscriptions */
3860 if (!sip->subscribed) { //do it just once, not every re-register
3862 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
3863 (GCompareFunc)g_ascii_strcasecmp)) {
3864 sipe_subscribe_roaming_contacts(sip);
3867 /* For 2007+ it does not make sence to subscribe to:
3868 * vnd-microsoft-roaming-ACL
3869 * vnd-microsoft-provisioning (not v2)
3870 * presence.wpending
3871 * These are for backward compatibility.
3873 if (sip->msrtc_event_categories)
3875 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
3876 (GCompareFunc)g_ascii_strcasecmp)) {
3877 sipe_subscribe_roaming_self(sip);
3879 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
3880 (GCompareFunc)g_ascii_strcasecmp)) {
3881 sipe_subscribe_roaming_provisioning_v2(sip);
3884 /* For 2005- servers */
3885 else
3887 //sipe_options_request(sip, sip->sipdomain);
3889 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
3890 (GCompareFunc)g_ascii_strcasecmp)) {
3891 sipe_subscribe_roaming_acl(sip);
3893 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
3894 (GCompareFunc)g_ascii_strcasecmp)) {
3895 sipe_subscribe_roaming_provisioning(sip);
3897 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
3898 (GCompareFunc)g_ascii_strcasecmp)) {
3899 sipe_subscribe_presence_wpending(sip, msg);
3902 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3903 sip->subscribed = TRUE;
3906 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3907 "timeout=", ";", NULL);
3908 if (timeout != NULL) {
3909 sscanf(timeout, "%u", &sip->keepalive_timeout);
3910 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3911 sip->keepalive_timeout);
3912 g_free(timeout);
3915 // Should we remove the transaction here?
3916 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3917 transactions_remove(sip, tc);
3919 break;
3920 case 301:
3922 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3924 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3925 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3926 gchar **tmp;
3927 gchar *hostname;
3928 int port = 0;
3929 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3930 int i = 1;
3932 tmp = g_strsplit(parts[0], ":", 0);
3933 hostname = g_strdup(tmp[0]);
3934 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3935 g_strfreev(tmp);
3937 while (parts[i]) {
3938 tmp = g_strsplit(parts[i], "=", 0);
3939 if (tmp[1]) {
3940 if (g_strcasecmp("transport", tmp[0]) == 0) {
3941 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3942 transport = SIPE_TRANSPORT_TCP;
3943 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3944 transport = SIPE_TRANSPORT_UDP;
3948 g_strfreev(tmp);
3949 i++;
3951 g_strfreev(parts);
3953 /* Close old connection */
3954 sipe_connection_cleanup(sip);
3956 /* Create new connection */
3957 sip->transport = transport;
3958 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3959 hostname, port, TRANSPORT_DESCRIPTOR);
3960 create_connection(sip, hostname, port);
3962 g_free(redirect);
3964 break;
3965 case 401:
3966 if (sip->registerstatus != 2) {
3967 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3968 if (sip->registrar.retries > 3) {
3969 sip->gc->wants_to_die = TRUE;
3970 purple_connection_error(sip->gc, _("Wrong Password"));
3971 return TRUE;
3973 #ifdef USE_KERBEROS
3974 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3975 #endif
3976 tmp = sipmsg_find_auth_header(msg, "NTLM");
3977 #ifdef USE_KERBEROS
3978 } else {
3979 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3981 #endif
3982 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3983 fill_auth(tmp, &sip->registrar);
3984 sip->registerstatus = 2;
3985 if (sip->account->disconnecting) {
3986 do_register_exp(sip, 0);
3987 } else {
3988 do_register(sip);
3991 break;
3992 case 403:
3994 gchar *warning = sipmsg_find_header(msg, "Warning");
3995 if (warning != NULL) {
3996 /* Example header:
3997 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3999 gchar **tmp = g_strsplit(warning, "\"", 0);
4000 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
4001 g_strfreev(tmp);
4002 } else {
4003 warning = g_strdup(_("You have been rejected by the server"));
4006 sip->gc->wants_to_die = TRUE;
4007 purple_connection_error(sip->gc, warning);
4008 g_free(warning);
4009 return TRUE;
4011 break;
4012 case 404:
4014 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4015 if (warning != NULL) {
4016 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4017 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
4018 g_free(reason);
4019 } else {
4020 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4023 sip->gc->wants_to_die = TRUE;
4024 purple_connection_error(sip->gc, warning);
4025 g_free(warning);
4026 return TRUE;
4028 break;
4029 case 503:
4031 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4032 if (warning != NULL) {
4033 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4034 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4035 g_free(reason);
4036 } else {
4037 warning = g_strdup(_("Service unavailable: no reason given"));
4040 sip->gc->wants_to_die = TRUE;
4041 purple_connection_error(sip->gc, warning);
4042 g_free(warning);
4043 return TRUE;
4045 break;
4047 return TRUE;
4050 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4052 const char *uri;
4053 xmlnode *xn_categories;
4054 xmlnode *xn_category;
4055 xmlnode *xn_node;
4056 const char *activity = NULL;
4058 xn_categories = xmlnode_from_str(data, len);
4059 uri = xmlnode_get_attrib(xn_categories, "uri");
4061 for (xn_category = xmlnode_get_child(xn_categories, "category");
4062 xn_category ;
4063 xn_category = xmlnode_get_next_twin(xn_category) )
4065 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4067 if (!strcmp(attrVar, "note"))
4069 if (uri) {
4070 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4072 if (sbuddy) {
4073 char *note;
4075 xn_node = xmlnode_get_child(xn_category, "note");
4076 if (!xn_node) continue;
4077 xn_node = xmlnode_get_child(xn_node, "body");
4078 if (!xn_node) continue;
4079 note = xmlnode_get_data(xn_node);
4080 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4081 g_free(sbuddy->annotation);
4082 sbuddy->annotation = NULL;
4083 if (note) sbuddy->annotation = g_strdup(note);
4084 g_free(note);
4089 else if(!strcmp(attrVar, "state"))
4091 char *data;
4092 int avail;
4093 xn_node = xmlnode_get_child(xn_category, "state");
4094 if (!xn_node) continue;
4095 xn_node = xmlnode_get_child(xn_node, "availability");
4096 if (!xn_node) continue;
4098 data = xmlnode_get_data(xn_node);
4099 avail = atoi(data);
4100 g_free(data);
4102 if (avail < 3000)
4103 activity = SIPE_STATUS_ID_UNKNOWN;
4104 else if (avail < 4500)
4105 activity = SIPE_STATUS_ID_AVAILABLE;
4106 else if (avail < 6000)
4107 activity = SIPE_STATUS_ID_BRB;
4108 else if (avail < 7500)
4109 activity = SIPE_STATUS_ID_ONPHONE;
4110 else if (avail < 9000)
4111 activity = SIPE_STATUS_ID_BUSY;
4112 else if (avail < 12000)
4113 activity = SIPE_STATUS_ID_DND;
4114 else if (avail < 18000)
4115 activity = SIPE_STATUS_ID_AWAY;
4116 else
4117 activity = SIPE_STATUS_ID_OFFLINE;
4120 if(activity) {
4121 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4122 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4125 xmlnode_free(xn_categories);
4128 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4130 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4131 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4132 payload->host = g_strdup(host);
4133 payload->buddies = server;
4134 sipe_subscribe_presence_batched_routed(sip, payload);
4135 sipe_subscribe_presence_batched_routed_free(payload);
4138 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4140 xmlnode *xn_list;
4141 xmlnode *xn_resource;
4142 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4143 g_free, NULL);
4144 GSList *server;
4145 gchar *host;
4147 xn_list = xmlnode_from_str(data, len);
4149 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4150 xn_resource;
4151 xn_resource = xmlnode_get_next_twin(xn_resource) )
4153 const char *uri, *state;
4154 xmlnode *xn_instance;
4156 xn_instance = xmlnode_get_child(xn_resource, "instance");
4157 if (!xn_instance) continue;
4159 uri = xmlnode_get_attrib(xn_resource, "uri");
4160 state = xmlnode_get_attrib(xn_instance, "state");
4161 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4163 if (strstr(state, "resubscribe")) {
4164 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4165 struct sipe_buddy *sbuddy;
4166 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4167 gchar *user = g_strdup(uri);
4168 host = g_strdup(poolFqdn);
4169 server = g_hash_table_lookup(servers, host);
4170 server = g_slist_append(server, user);
4171 g_hash_table_insert(servers, host, server);
4172 } else {
4173 sipe_subscribe_presence_single(sip, (void *) uri);
4175 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4176 if (sbuddy) {
4177 sbuddy->resubscribed = TRUE;
4182 /* Send out any deferred poolFqdn subscriptions */
4183 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4184 g_hash_table_destroy(servers);
4186 xmlnode_free(xn_list);
4189 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4191 const gchar *uri;
4192 gchar *getbasic;
4193 gchar *activity = NULL;
4194 xmlnode *pidf;
4195 xmlnode *basicstatus = NULL, *tuple, *status;
4196 gboolean isonline = FALSE;
4197 xmlnode *display_name_node;
4199 pidf = xmlnode_from_str(data, len);
4200 if (!pidf) {
4201 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4202 return;
4205 uri = xmlnode_get_attrib(pidf, "entity");
4207 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4209 if ((status = xmlnode_get_child(tuple, "status"))) {
4210 basicstatus = xmlnode_get_child(status, "basic");
4214 if (!basicstatus) {
4215 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4216 xmlnode_free(pidf);
4217 return;
4220 getbasic = xmlnode_get_data(basicstatus);
4221 if (!getbasic) {
4222 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4223 xmlnode_free(pidf);
4224 return;
4227 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4228 if (strstr(getbasic, "open")) {
4229 isonline = TRUE;
4231 g_free(getbasic);
4233 display_name_node = xmlnode_get_child(pidf, "display-name");
4234 // updating display name if alias was just URI
4235 if (display_name_node) {
4236 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4237 GSList *entry = buddies;
4238 PurpleBuddy *p_buddy;
4239 char * display_name = xmlnode_get_data(display_name_node);
4241 while (entry) {
4242 const char *server_alias;
4243 char *alias;
4245 p_buddy = entry->data;
4247 alias = (char *)purple_buddy_get_alias(p_buddy);
4248 alias = alias ? sip_uri_from_name(alias) : NULL;
4249 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4250 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4251 purple_blist_alias_buddy(p_buddy, display_name);
4253 g_free(alias);
4255 server_alias = purple_buddy_get_server_alias(p_buddy);
4256 if (display_name &&
4257 ( (server_alias && strcmp(display_name, server_alias))
4258 || !server_alias || strlen(server_alias) == 0 )
4260 purple_blist_server_alias_buddy(p_buddy, display_name);
4263 entry = entry->next;
4265 g_free(display_name);
4268 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4269 if ((status = xmlnode_get_child(tuple, "status"))) {
4270 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4271 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4272 activity = xmlnode_get_data(basicstatus);
4273 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4279 if (isonline) {
4280 const gchar * status_id = NULL;
4281 if (activity) {
4282 if (strstr(activity, "busy")) {
4283 status_id = SIPE_STATUS_ID_BUSY;
4284 } else if (strstr(activity, "away")) {
4285 status_id = SIPE_STATUS_ID_AWAY;
4289 if (!status_id) {
4290 status_id = SIPE_STATUS_ID_AVAILABLE;
4293 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4294 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4295 } else {
4296 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4299 g_free(activity);
4300 xmlnode_free(pidf);
4303 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4305 const char *availability;
4306 const char *activity;
4307 const char *display_name = NULL;
4308 const char *activity_name = NULL;
4309 const char *name;
4310 char *uri;
4311 int avl;
4312 int act;
4313 struct sipe_buddy *sbuddy;
4315 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4317 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4318 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4319 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4320 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4321 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4322 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4323 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4324 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4326 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4327 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4328 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4329 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4330 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4331 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4333 name = xmlnode_get_attrib(xn_presentity, "uri");
4334 uri = sip_uri_from_name(name);
4335 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4336 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4338 // updating display name if alias was just URI
4339 if (xn_display_name) {
4340 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4341 GSList *entry = buddies;
4342 PurpleBuddy *p_buddy;
4343 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4345 while (entry) {
4346 const char *email_str, *server_alias;
4348 p_buddy = entry->data;
4350 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4351 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4352 purple_blist_alias_buddy(p_buddy, display_name);
4355 server_alias = purple_buddy_get_server_alias(p_buddy);
4356 if (display_name &&
4357 ( (server_alias && strcmp(display_name, server_alias))
4358 || !server_alias || strlen(server_alias) == 0 )
4360 purple_blist_server_alias_buddy(p_buddy, display_name);
4363 if (email) {
4364 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4365 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4366 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4370 entry = entry->next;
4374 avl = atoi(availability);
4375 act = atoi(activity);
4377 if(sip->msrtc_event_categories){
4378 if (act == 100 && avl == 0)
4379 activity_name = SIPE_STATUS_ID_OFFLINE;
4380 else if (act == 100 && avl == 300)
4381 activity_name = SIPE_STATUS_ID_AWAY;
4382 else if (act == 300 && avl == 300)
4383 activity_name = SIPE_STATUS_ID_BRB;
4384 else if (act == 400 && avl == 300)
4385 activity_name = SIPE_STATUS_ID_AVAILABLE;
4386 else if (act == 500 && act == 300)
4387 activity_name = SIPE_STATUS_ID_ONPHONE;
4388 else if (act == 600 && avl == 300)
4389 activity_name = SIPE_STATUS_ID_BUSY;
4390 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4391 if(avail){ //Check for LegacyInterop elements
4392 avl = atoi(avail);
4393 if(avl == 18500)
4394 activity_name = SIPE_STATUS_ID_OFFLINE;
4395 else if (avl == 3500)
4396 activity_name = SIPE_STATUS_ID_AVAILABLE;
4397 else if (avl == 15500)
4398 activity_name = SIPE_STATUS_ID_AWAY;
4399 else if (avl == 6500)
4400 activity_name = SIPE_STATUS_ID_BUSY;
4401 else if (avl == 12500)
4402 activity_name = SIPE_STATUS_ID_BRB;
4407 if(activity_name == NULL){
4408 if (act <= 100)
4409 activity_name = SIPE_STATUS_ID_AWAY;
4410 else if (act <= 150)
4411 activity_name = SIPE_STATUS_ID_LUNCH;
4412 else if (act <= 300)
4413 activity_name = SIPE_STATUS_ID_BRB;
4414 else if (act <= 400)
4415 activity_name = SIPE_STATUS_ID_AVAILABLE;
4416 else if (act <= 500)
4417 activity_name = SIPE_STATUS_ID_ONPHONE;
4418 else if (act <= 600)
4419 activity_name = SIPE_STATUS_ID_BUSY;
4420 else
4421 activity_name = SIPE_STATUS_ID_AVAILABLE;
4423 if (avl == 0)
4424 activity_name = SIPE_STATUS_ID_OFFLINE;
4427 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4428 if (sbuddy)
4430 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4431 sbuddy->annotation = NULL;
4432 if (note) { sbuddy->annotation = g_strdup(note); }
4434 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4435 sbuddy->device_name = NULL;
4436 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4439 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4440 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4441 g_free(note);
4442 xmlnode_free(xn_presentity);
4443 g_free(uri);
4446 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4448 char *ctype = sipmsg_find_header(msg, "Content-Type");
4450 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4452 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4453 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4455 const char *content = msg->body;
4456 unsigned length = msg->bodylen;
4457 PurpleMimeDocument *mime = NULL;
4459 if (strstr(ctype, "multipart"))
4461 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4462 const char *content_type;
4463 GList* parts;
4464 mime = purple_mime_document_parse(doc);
4465 parts = purple_mime_document_get_parts(mime);
4466 while(parts) {
4467 content = purple_mime_part_get_data(parts->data);
4468 length = purple_mime_part_get_length(parts->data);
4469 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4470 if(content_type && strstr(content_type,"application/rlmi+xml"))
4472 process_incoming_notify_rlmi_resub(sip, content, length);
4474 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4476 process_incoming_notify_msrtc(sip, content, length);
4478 else
4480 process_incoming_notify_rlmi(sip, content, length);
4482 parts = parts->next;
4484 g_free(doc);
4486 if (mime)
4488 purple_mime_document_free(mime);
4491 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4493 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4495 else if(strstr(ctype, "application/rlmi+xml"))
4497 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4500 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4502 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4504 else
4506 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4510 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4512 char *ctype = sipmsg_find_header(msg, "Content-Type");
4513 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4515 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4517 if (ctype &&
4518 strstr(ctype, "multipart") &&
4519 (strstr(ctype, "application/rlmi+xml") ||
4520 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4521 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4522 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4523 GList *parts = purple_mime_document_get_parts(mime);
4524 GSList *buddies = NULL;
4525 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4527 while (parts) {
4528 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4529 purple_mime_part_get_length(parts->data));
4530 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
4532 buddies = g_slist_append(buddies, uri);
4533 xmlnode_free(xml);
4535 parts = parts->next;
4537 g_free(doc);
4538 if (mime) purple_mime_document_free(mime);
4540 payload->host = who;
4541 payload->buddies = buddies;
4542 sipe_schedule_action(action_name, timeout,
4543 sipe_subscribe_presence_batched_routed,
4544 sipe_subscribe_presence_batched_routed_free,
4545 sip, payload);
4546 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4548 } else {
4549 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4550 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4552 g_free(action_name);
4556 * Dispatcher for all incoming subscription information
4557 * whether it comes from NOTIFY, BENOTIFY requests or
4558 * piggy-backed to subscription's OK responce.
4560 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4561 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4563 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4565 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4566 gchar *event = sipmsg_find_header(msg, "Event");
4567 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4568 int timeout = 0;
4570 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4571 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n\n", subscription_state ? subscription_state : "");
4573 /* implicit subscriptions */
4574 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4575 sipe_process_imdn(sip, msg);
4578 if (!request)
4580 const gchar *expires_header;
4581 expires_header = sipmsg_find_header(msg, "Expires");
4582 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4583 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4584 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4587 /* for one off subscriptions (send with Expire: 0) */
4588 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
4590 sipe_process_provisioning_v2(sip, msg);
4593 if (!subscription_state || strstr(subscription_state, "active"))
4595 if (event && !g_ascii_strcasecmp(event, "presence"))
4597 sipe_process_presence(sip, msg);
4599 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4601 sipe_process_roaming_contacts(sip, msg);
4603 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
4605 sipe_process_roaming_self(sip, msg);
4607 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4609 sipe_process_roaming_acl(sip, msg);
4611 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4613 sipe_process_presence_wpending(sip, msg);
4615 else if (event && !g_ascii_strcasecmp(event, "conference"))
4617 sipe_process_conference(sip, msg);
4621 //The server sends a (BE)NOTIFY with the status 'terminated'
4622 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4623 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4624 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4625 g_free(from);
4628 if (timeout && event) {// For LSC 2005 and OCS 2007
4629 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4630 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4632 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4633 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4634 g_free(action_name);
4636 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4637 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4639 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4640 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4641 g_free(action_name);
4643 else*/
4644 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4645 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4647 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4648 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4649 g_free(action_name);
4651 else if (!g_ascii_strcasecmp(event, "presence") &&
4652 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4654 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4655 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4656 if(sip->batched_support) {
4657 gchar *my_self = sip_uri_self(sip);
4658 if(!g_ascii_strcasecmp(who, my_self)){
4659 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4660 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4661 g_free(who); /* unused */
4663 else {
4664 sipe_process_presence_timeout(sip, msg, who, timeout);
4666 g_free(my_self);
4668 else {
4669 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, who);
4670 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4672 g_free(action_name);
4673 /* "who" will be freed by the action we just scheduled */
4677 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4679 sipe_process_registration_notify(sip, msg);
4682 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4683 if (request && !benotify)
4685 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4690 * unused. Needed?
4692 static gchar* gen_xpidf(struct sipe_account_data *sip)
4694 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4695 "<presence>\r\n"
4696 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4697 "<display name=\"sip:%s\"/>\r\n"
4698 "<atom id=\"1234\">\r\n"
4699 "<address uri=\"sip:%s\">\r\n"
4700 "<status status=\"%s\"/>\r\n"
4701 "</address>\r\n"
4702 "</atom>\r\n"
4703 "</presence>\r\n",
4704 sip->username,
4705 sip->username,
4706 sip->username,
4707 sip->status);
4708 return doc;
4713 static gchar* gen_pidf(struct sipe_account_data *sip)
4715 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4716 "<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"
4717 "<tuple id=\"0\">\r\n"
4718 "<status>\r\n"
4719 "<basic>open</basic>\r\n"
4720 "<ep:activities>\r\n"
4721 " <ep:activity>%s</ep:activity>\r\n"
4722 "</ep:activities>"
4723 "</status>\r\n"
4724 "</tuple>\r\n"
4725 "<ci:display-name>%s</ci:display-name>\r\n"
4726 "</presence>",
4727 sip->username,
4728 sip->status,
4729 sip->username);
4730 return doc;
4734 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4736 int availability = 300; // online
4737 int activity = 400; // Available
4738 gchar *name;
4739 gchar *body;
4740 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4741 activity = 100;
4742 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4743 activity = 150;
4744 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4745 activity = 300;
4746 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4747 activity = 400;
4748 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4749 activity = 500;
4750 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4751 activity = 600;
4752 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4753 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4754 availability = 0; // offline
4755 activity = 100;
4756 } else {
4757 activity = 400; // available
4760 name = g_strdup_printf("sip: sip:%s", sip->username);
4761 //@TODO: send user data - state; add hostname in upper case
4762 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4763 send_soap_request_with_cb(sip, body, NULL , NULL);
4764 g_free(name);
4765 g_free(body);
4768 static gboolean
4769 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg,
4770 SIPE_UNUSED_PARAMETER struct transaction *tc)
4772 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4773 if (msg->response == 200) {
4774 sip->status_version = 0;
4775 send_presence_status(sip);
4777 return TRUE;
4780 static gboolean
4781 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg,
4782 SIPE_UNUSED_PARAMETER struct transaction *tc)
4784 if (msg->response == 409) {
4785 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4786 // TODO need to parse the version #'s?
4787 gchar *uri = sip_uri_self(sip);
4788 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4789 gchar *tmp;
4790 gchar *hdr;
4792 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4794 tmp = get_contact(sip);
4795 hdr = g_strdup_printf("Contact: %s\r\n"
4796 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4798 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4800 g_free(tmp);
4801 g_free(hdr);
4802 g_free(uri);
4803 g_free(doc);
4805 return TRUE;
4808 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4810 int code;
4811 gchar *uri;
4812 gchar *doc;
4813 gchar *tmp;
4814 gchar *hdr;
4815 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4816 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4817 code = 12000;
4818 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4819 code = 9000;
4820 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4821 code = 7500;
4822 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4823 code = 6000;
4824 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4825 code = 4500;
4826 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4827 code = 3000;
4828 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4829 code = 0;
4830 } else {
4831 // Offline or invisible
4832 code = 18000;
4835 uri = sip_uri_self(sip);
4836 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4837 sip->status_version, code,
4838 sip->status_version, code,
4839 sip->status_version, note ? note : "",
4840 sip->status_version, note ? note : "",
4841 sip->status_version, note ? note : ""
4843 sip->status_version++;
4845 tmp = get_contact(sip);
4846 hdr = g_strdup_printf("Contact: %s\r\n"
4847 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4849 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4851 g_free(tmp);
4852 g_free(hdr);
4853 g_free(uri);
4854 g_free(doc);
4857 static void send_presence_status(struct sipe_account_data *sip)
4859 PurpleStatus * status = purple_account_get_active_status(sip->account);
4860 const gchar *note;
4861 if (!status) return;
4863 note = purple_status_get_attr_string(status, "message");
4865 if(sip->msrtc_event_categories){
4866 send_presence_category_publish(sip, note);
4867 } else {
4868 send_presence_soap(sip, note);
4872 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4874 gboolean found = FALSE;
4875 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4876 if (msg->response == 0) { /* request */
4877 if (!strcmp(msg->method, "MESSAGE")) {
4878 process_incoming_message(sip, msg);
4879 found = TRUE;
4880 } else if (!strcmp(msg->method, "NOTIFY")) {
4881 purple_debug_info("sipe","send->process_incoming_notify\n");
4882 process_incoming_notify(sip, msg, TRUE, FALSE);
4883 found = TRUE;
4884 } else if (!strcmp(msg->method, "BENOTIFY")) {
4885 purple_debug_info("sipe","send->process_incoming_benotify\n");
4886 process_incoming_notify(sip, msg, TRUE, TRUE);
4887 found = TRUE;
4888 } else if (!strcmp(msg->method, "INVITE")) {
4889 process_incoming_invite(sip, msg);
4890 found = TRUE;
4891 } else if (!strcmp(msg->method, "REFER")) {
4892 process_incoming_refer(sip, msg);
4893 found = TRUE;
4894 } else if (!strcmp(msg->method, "OPTIONS")) {
4895 process_incoming_options(sip, msg);
4896 found = TRUE;
4897 } else if (!strcmp(msg->method, "INFO")) {
4898 process_incoming_info(sip, msg);
4899 found = TRUE;
4900 } else if (!strcmp(msg->method, "ACK")) {
4901 // ACK's don't need any response
4902 found = TRUE;
4903 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4904 // LCS 2005 sends us these - just respond 200 OK
4905 found = TRUE;
4906 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4907 } else if (!strcmp(msg->method, "BYE")) {
4908 process_incoming_bye(sip, msg);
4909 found = TRUE;
4910 } else {
4911 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4913 } else { /* response */
4914 struct transaction *trans = transactions_find(sip, msg);
4915 if (trans) {
4916 if (msg->response == 407) {
4917 gchar *resend, *auth, *ptmp;
4919 if (sip->proxy.retries > 30) return;
4920 sip->proxy.retries++;
4921 /* do proxy authentication */
4923 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4925 fill_auth(ptmp, &sip->proxy);
4926 auth = auth_header(sip, &sip->proxy, trans->msg);
4927 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4928 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4929 g_free(auth);
4930 resend = sipmsg_to_string(trans->msg);
4931 /* resend request */
4932 sendout_pkt(sip->gc, resend);
4933 g_free(resend);
4934 } else {
4935 if (msg->response == 100 || msg->response == 180) {
4936 /* ignore provisional response */
4937 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4938 } else {
4939 sip->proxy.retries = 0;
4940 if (!strcmp(trans->msg->method, "REGISTER")) {
4941 if (msg->response == 401)
4943 sip->registrar.retries++;
4945 else
4947 sip->registrar.retries = 0;
4949 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4950 } else {
4951 if (msg->response == 401) {
4952 gchar *resend, *auth, *ptmp;
4954 if (sip->registrar.retries > 4) return;
4955 sip->registrar.retries++;
4957 #ifdef USE_KERBEROS
4958 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4959 #endif
4960 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4961 #ifdef USE_KERBEROS
4962 } else {
4963 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4965 #endif
4967 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4969 fill_auth(ptmp, &sip->registrar);
4970 auth = auth_header(sip, &sip->registrar, trans->msg);
4971 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4972 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4974 //sipmsg_remove_header_now(trans->msg, "Authorization");
4975 //sipmsg_add_header(trans->msg, "Authorization", auth);
4976 g_free(auth);
4977 resend = sipmsg_to_string(trans->msg);
4978 /* resend request */
4979 sendout_pkt(sip->gc, resend);
4980 g_free(resend);
4984 if (trans->callback) {
4985 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4986 /* call the callback to process response*/
4987 (trans->callback)(sip, msg, trans);
4989 /* Not sure if this is needed or what needs to be done
4990 but transactions seem to be removed prematurely so
4991 this only removes them if the response is 200 OK */
4992 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4993 /*Has a bug and it's unneccesary*/
4994 /*transactions_remove(sip, trans);*/
4998 found = TRUE;
4999 } else {
5000 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
5003 if (!found) {
5004 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
5008 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
5010 char *cur;
5011 char *dummy;
5012 struct sipmsg *msg;
5013 int restlen;
5014 cur = conn->inbuf;
5016 /* according to the RFC remove CRLF at the beginning */
5017 while (*cur == '\r' || *cur == '\n') {
5018 cur++;
5020 if (cur != conn->inbuf) {
5021 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
5022 conn->inbufused = strlen(conn->inbuf);
5025 /* Received a full Header? */
5026 sip->processing_input = TRUE;
5027 while (sip->processing_input &&
5028 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
5029 time_t currtime = time(NULL);
5030 cur += 2;
5031 cur[0] = '\0';
5032 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
5033 msg = sipmsg_parse_header(conn->inbuf);
5034 cur[0] = '\r';
5035 cur += 2;
5036 restlen = conn->inbufused - (cur - conn->inbuf);
5037 if (restlen >= msg->bodylen) {
5038 dummy = g_malloc(msg->bodylen + 1);
5039 memcpy(dummy, cur, msg->bodylen);
5040 dummy[msg->bodylen] = '\0';
5041 msg->body = dummy;
5042 cur += msg->bodylen;
5043 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
5044 conn->inbufused = strlen(conn->inbuf);
5045 } else {
5046 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5047 restlen, msg->bodylen, (int)strlen(conn->inbuf));
5048 sipmsg_free(msg);
5049 return;
5052 /*if (msg->body) {
5053 purple_debug_info("sipe", "body:\n%s", msg->body);
5056 // Verify the signature before processing it
5057 if (sip->registrar.gssapi_context) {
5058 struct sipmsg_breakdown msgbd;
5059 gchar *signature_input_str;
5060 gchar *rspauth;
5061 msgbd.msg = msg;
5062 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5063 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5065 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5067 if (rspauth != NULL) {
5068 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5069 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5070 process_input_message(sip, msg);
5071 } else {
5072 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5073 purple_connection_error(sip->gc, _("Invalid message signature received"));
5074 sip->gc->wants_to_die = TRUE;
5076 } else if (msg->response == 401) {
5077 purple_connection_error(sip->gc, _("Wrong Password"));
5078 sip->gc->wants_to_die = TRUE;
5080 g_free(signature_input_str);
5082 g_free(rspauth);
5083 sipmsg_breakdown_free(&msgbd);
5084 } else {
5085 process_input_message(sip, msg);
5088 sipmsg_free(msg);
5092 static void sipe_udp_process(gpointer data, gint source,
5093 SIPE_UNUSED_PARAMETER PurpleInputCondition con)
5095 PurpleConnection *gc = data;
5096 struct sipe_account_data *sip = gc->proto_data;
5097 struct sipmsg *msg;
5098 int len;
5099 time_t currtime;
5101 static char buffer[65536];
5102 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5103 buffer[len] = '\0';
5104 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5105 msg = sipmsg_parse_msg(buffer);
5106 if (msg) process_input_message(sip, msg);
5110 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5112 struct sipe_account_data *sip = gc->proto_data;
5113 PurpleSslConnection *gsc = sip->gsc;
5115 purple_debug_error("sipe", "%s",debug);
5116 purple_connection_error(gc, msg);
5118 /* Invalidate this connection. Next send will open a new one */
5119 if (gsc) {
5120 connection_remove(sip, gsc->fd);
5121 purple_ssl_close(gsc);
5123 sip->gsc = NULL;
5124 sip->fd = -1;
5127 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc,
5128 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5130 PurpleConnection *gc = data;
5131 struct sipe_account_data *sip;
5132 struct sip_connection *conn;
5133 int readlen, len;
5134 gboolean firstread = TRUE;
5136 /* NOTE: This check *IS* necessary */
5137 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5138 purple_ssl_close(gsc);
5139 return;
5142 sip = gc->proto_data;
5143 conn = connection_find(sip, gsc->fd);
5144 if (conn == NULL) {
5145 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5146 gc->wants_to_die = TRUE;
5147 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5148 return;
5151 /* Read all available data from the SSL connection */
5152 do {
5153 /* Increase input buffer size as needed */
5154 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5155 conn->inbuflen += SIMPLE_BUF_INC;
5156 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5157 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5160 /* Try to read as much as there is space left in the buffer */
5161 readlen = conn->inbuflen - conn->inbufused - 1;
5162 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5164 if (len < 0 && errno == EAGAIN) {
5165 /* Try again later */
5166 return;
5167 } else if (len < 0) {
5168 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5169 return;
5170 } else if (firstread && (len == 0)) {
5171 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5172 return;
5175 conn->inbufused += len;
5176 firstread = FALSE;
5178 /* Equivalence indicates that there is possibly more data to read */
5179 } while (len == readlen);
5181 conn->inbuf[conn->inbufused] = '\0';
5182 process_input(sip, conn);
5186 static void sipe_input_cb(gpointer data, gint source,
5187 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5189 PurpleConnection *gc = data;
5190 struct sipe_account_data *sip = gc->proto_data;
5191 int len;
5192 struct sip_connection *conn = connection_find(sip, source);
5193 if (!conn) {
5194 purple_debug_error("sipe", "Connection not found!\n");
5195 return;
5198 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5199 conn->inbuflen += SIMPLE_BUF_INC;
5200 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5203 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5205 if (len < 0 && errno == EAGAIN)
5206 return;
5207 else if (len <= 0) {
5208 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5209 connection_remove(sip, source);
5210 if (sip->fd == source) sip->fd = -1;
5211 return;
5214 conn->inbufused += len;
5215 conn->inbuf[conn->inbufused] = '\0';
5217 process_input(sip, conn);
5220 /* Callback for new connections on incoming TCP port */
5221 static void sipe_newconn_cb(gpointer data, gint source,
5222 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5224 PurpleConnection *gc = data;
5225 struct sipe_account_data *sip = gc->proto_data;
5226 struct sip_connection *conn;
5228 int newfd = accept(source, NULL, NULL);
5230 conn = connection_create(sip, newfd);
5232 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5235 static void login_cb(gpointer data, gint source,
5236 SIPE_UNUSED_PARAMETER const gchar *error_message)
5238 PurpleConnection *gc = data;
5239 struct sipe_account_data *sip;
5240 struct sip_connection *conn;
5242 if (!PURPLE_CONNECTION_IS_VALID(gc))
5244 if (source >= 0)
5245 close(source);
5246 return;
5249 if (source < 0) {
5250 purple_connection_error(gc, _("Could not connect"));
5251 return;
5254 sip = gc->proto_data;
5255 sip->fd = source;
5256 sip->last_keepalive = time(NULL);
5258 conn = connection_create(sip, source);
5260 do_register(sip);
5262 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5265 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc,
5266 SIPE_UNUSED_PARAMETER PurpleInputCondition cond)
5268 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5269 if (sip == NULL) return;
5271 do_register(sip);
5274 static guint sipe_ht_hash_nick(const char *nick)
5276 char *lc = g_utf8_strdown(nick, -1);
5277 guint bucket = g_str_hash(lc);
5278 g_free(lc);
5280 return bucket;
5283 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5285 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5288 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5290 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5292 sip->listen_data = NULL;
5294 if (listenfd == -1) {
5295 purple_connection_error(sip->gc, _("Could not create listen socket"));
5296 return;
5299 sip->fd = listenfd;
5301 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5302 sip->listenfd = sip->fd;
5304 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5306 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5307 do_register(sip);
5310 static void sipe_udp_host_resolved(GSList *hosts, gpointer data,
5311 SIPE_UNUSED_PARAMETER const char *error_message)
5313 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5315 sip->query_data = NULL;
5317 if (!hosts || !hosts->data) {
5318 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5319 return;
5322 hosts = g_slist_remove(hosts, hosts->data);
5323 g_free(sip->serveraddr);
5324 sip->serveraddr = hosts->data;
5325 hosts = g_slist_remove(hosts, hosts->data);
5326 while (hosts) {
5327 hosts = g_slist_remove(hosts, hosts->data);
5328 g_free(hosts->data);
5329 hosts = g_slist_remove(hosts, hosts->data);
5332 /* create socket for incoming connections */
5333 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5334 sipe_udp_host_resolved_listen_cb, sip);
5335 if (sip->listen_data == NULL) {
5336 purple_connection_error(sip->gc, _("Could not create listen socket"));
5337 return;
5341 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection *gsc,
5342 PurpleSslErrorType error,
5343 gpointer data)
5345 PurpleConnection *gc = data;
5346 struct sipe_account_data *sip;
5348 /* If the connection is already disconnected, we don't need to do anything else */
5349 if (!PURPLE_CONNECTION_IS_VALID(gc))
5350 return;
5352 sip = gc->proto_data;
5353 sip->fd = -1;
5354 sip->gsc = NULL;
5356 switch(error) {
5357 case PURPLE_SSL_CONNECT_FAILED:
5358 purple_connection_error(gc, _("Connection Failed"));
5359 break;
5360 case PURPLE_SSL_HANDSHAKE_FAILED:
5361 purple_connection_error(gc, _("SSL Handshake Failed"));
5362 break;
5363 case PURPLE_SSL_CERTIFICATE_INVALID:
5364 purple_connection_error(gc, _("SSL Certificate Invalid"));
5365 break;
5369 static void
5370 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5372 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5373 PurpleProxyConnectData *connect_data;
5375 sip->listen_data = NULL;
5377 sip->listenfd = listenfd;
5378 if (sip->listenfd == -1) {
5379 purple_connection_error(sip->gc, _("Could not create listen socket"));
5380 return;
5383 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5384 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5385 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5386 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5387 sipe_newconn_cb, sip->gc);
5388 purple_debug_info("sipe", "connecting to %s port %d\n",
5389 sip->realhostname, sip->realport);
5390 /* open tcp connection to the server */
5391 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5392 sip->realport, login_cb, sip->gc);
5394 if (connect_data == NULL) {
5395 purple_connection_error(sip->gc, _("Couldn't create socket"));
5400 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5402 PurpleAccount *account = sip->account;
5403 PurpleConnection *gc = sip->gc;
5405 if (purple_account_get_bool(account, "useport", FALSE)) {
5406 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
5407 port = purple_account_get_int(account, "port", 0);
5408 } else {
5409 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5412 sip->realhostname = hostname;
5413 sip->realport = port;
5415 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5416 hostname, port);
5418 /* TODO: is there a good default grow size? */
5419 if (sip->transport != SIPE_TRANSPORT_UDP)
5420 sip->txbuf = purple_circ_buffer_new(0);
5422 if (sip->transport == SIPE_TRANSPORT_TLS) {
5423 /* SSL case */
5424 if (!purple_ssl_is_supported()) {
5425 gc->wants_to_die = TRUE;
5426 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5427 return;
5430 purple_debug_info("sipe", "using SSL\n");
5432 sip->gsc = purple_ssl_connect(account, hostname, port,
5433 login_cb_ssl, sipe_ssl_connect_failure, gc);
5434 if (sip->gsc == NULL) {
5435 purple_connection_error(gc, _("Could not create SSL context"));
5436 return;
5438 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5439 /* UDP case */
5440 purple_debug_info("sipe", "using UDP\n");
5442 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5443 if (sip->query_data == NULL) {
5444 purple_connection_error(gc, _("Could not resolve hostname"));
5446 } else {
5447 /* TCP case */
5448 purple_debug_info("sipe", "using TCP\n");
5449 /* create socket for incoming connections */
5450 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5451 sipe_tcp_connect_listen_cb, sip);
5452 if (sip->listen_data == NULL) {
5453 purple_connection_error(gc, _("Could not create listen socket"));
5454 return;
5459 /* Service list for autodection */
5460 static const struct sipe_service_data service_autodetect[] = {
5461 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5462 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5463 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5464 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5465 { NULL, NULL, 0 }
5468 /* Service list for SSL/TLS */
5469 static const struct sipe_service_data service_tls[] = {
5470 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5471 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5472 { NULL, NULL, 0 }
5475 /* Service list for TCP */
5476 static const struct sipe_service_data service_tcp[] = {
5477 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5478 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5479 { NULL, NULL, 0 }
5482 /* Service list for UDP */
5483 static const struct sipe_service_data service_udp[] = {
5484 { "sip", "udp", SIPE_TRANSPORT_UDP },
5485 { NULL, NULL, 0 }
5488 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5489 static void resolve_next_service(struct sipe_account_data *sip,
5490 const struct sipe_service_data *start)
5492 if (start) {
5493 sip->service_data = start;
5494 } else {
5495 sip->service_data++;
5496 if (sip->service_data->service == NULL) {
5497 gchar *hostname;
5498 /* Try connecting to the SIP hostname directly */
5499 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5500 if (sip->auto_transport) {
5501 // If SSL is supported, default to using it; OCS servers aren't configured
5502 // by default to accept TCP
5503 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5504 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5505 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5508 hostname = g_strdup(sip->sipdomain);
5509 create_connection(sip, hostname, 0);
5510 return;
5514 /* Try to resolve next service */
5515 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5516 sip->service_data->transport,
5517 sip->sipdomain,
5518 srvresolved, sip);
5521 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5523 struct sipe_account_data *sip = data;
5525 sip->srv_query_data = NULL;
5527 /* find the host to connect to */
5528 if (results) {
5529 gchar *hostname = g_strdup(resp->hostname);
5530 int port = resp->port;
5531 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5532 hostname, port);
5533 g_free(resp);
5535 sip->transport = sip->service_data->type;
5537 create_connection(sip, hostname, port);
5538 } else {
5539 resolve_next_service(sip, NULL);
5543 static void sipe_login(PurpleAccount *account)
5545 PurpleConnection *gc;
5546 struct sipe_account_data *sip;
5547 gchar **signinname_login, **userserver, **domain_user;
5548 const char *transport;
5550 const char *username = purple_account_get_username(account);
5551 gc = purple_account_get_connection(account);
5553 if (strpbrk(username, "\t\v\r\n") != NULL) {
5554 gc->wants_to_die = TRUE;
5555 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5556 return;
5559 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5560 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5561 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5562 sip->gc = gc;
5563 sip->account = account;
5564 sip->reregister_set = FALSE;
5565 sip->reauthenticate_set = FALSE;
5566 sip->subscribed = FALSE;
5567 sip->subscribed_buddies = FALSE;
5569 signinname_login = g_strsplit(username, ",", 2);
5571 userserver = g_strsplit(signinname_login[0], "@", 2);
5572 purple_connection_set_display_name(gc, userserver[0]);
5573 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
5574 sip->sipdomain = g_strdup(userserver[1]);
5576 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
5577 gc->wants_to_die = TRUE;
5578 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5579 return;
5582 domain_user = g_strsplit(signinname_login[1], "\\", 2);
5583 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
5584 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
5586 sip->password = g_strdup(purple_connection_get_password(gc));
5588 g_strfreev(userserver);
5589 g_strfreev(domain_user);
5590 g_strfreev(signinname_login);
5592 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5594 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5596 /* TODO: Set the status correctly. */
5597 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5599 transport = purple_account_get_string(account, "transport", "auto");
5600 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
5601 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
5602 SIPE_TRANSPORT_UDP;
5604 if (purple_account_get_bool(account, "useproxy", FALSE)) {
5605 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
5606 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
5607 } else if (strcmp(transport, "auto") == 0) {
5608 sip->auto_transport = TRUE;
5609 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5610 } else if (strcmp(transport, "tls") == 0) {
5611 resolve_next_service(sip, service_tls);
5612 } else if (strcmp(transport, "tcp") == 0) {
5613 resolve_next_service(sip, service_tcp);
5614 } else {
5615 resolve_next_service(sip, service_udp);
5619 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5621 connection_free_all(sip);
5623 g_free(sip->epid);
5624 sip->epid = NULL;
5626 if (sip->query_data != NULL)
5627 purple_dnsquery_destroy(sip->query_data);
5628 sip->query_data = NULL;
5630 if (sip->srv_query_data != NULL)
5631 purple_srv_cancel(sip->srv_query_data);
5632 sip->srv_query_data = NULL;
5634 if (sip->listen_data != NULL)
5635 purple_network_listen_cancel(sip->listen_data);
5636 sip->listen_data = NULL;
5638 if (sip->gsc != NULL)
5639 purple_ssl_close(sip->gsc);
5640 sip->gsc = NULL;
5642 sipe_auth_free(&sip->registrar);
5643 sipe_auth_free(&sip->proxy);
5645 if (sip->txbuf)
5646 purple_circ_buffer_destroy(sip->txbuf);
5647 sip->txbuf = NULL;
5649 g_free(sip->realhostname);
5650 sip->realhostname = NULL;
5652 if (sip->listenpa)
5653 purple_input_remove(sip->listenpa);
5654 sip->listenpa = 0;
5655 if (sip->tx_handler)
5656 purple_input_remove(sip->tx_handler);
5657 sip->tx_handler = 0;
5658 if (sip->resendtimeout)
5659 purple_timeout_remove(sip->resendtimeout);
5660 sip->resendtimeout = 0;
5661 if (sip->timeouts) {
5662 GSList *entry = sip->timeouts;
5663 while (entry) {
5664 struct scheduled_action *sched_action = entry->data;
5665 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5666 purple_timeout_remove(sched_action->timeout_handler);
5667 if (sched_action->destroy) {
5668 (*sched_action->destroy)(sched_action->payload);
5670 g_free(sched_action->name);
5671 g_free(sched_action);
5672 entry = entry->next;
5675 g_slist_free(sip->timeouts);
5677 if (sip->allow_events) {
5678 GSList *entry = sip->allow_events;
5679 while (entry) {
5680 g_free(entry->data);
5681 entry = entry->next;
5684 g_slist_free(sip->allow_events);
5686 if (sip->containers) {
5687 GSList *entry = sip->containers;
5688 while (entry) {
5689 free_container((struct sipe_container *)entry->data);
5690 entry = entry->next;
5693 g_slist_free(sip->containers);
5695 if (sip->contact)
5696 g_free(sip->contact);
5697 sip->contact = NULL;
5698 if (sip->regcallid)
5699 g_free(sip->regcallid);
5700 sip->regcallid = NULL;
5702 if (sip->serveraddr)
5703 g_free(sip->serveraddr);
5704 sip->serveraddr = NULL;
5706 if (sip->focus_factory_uri)
5707 g_free(sip->focus_factory_uri);
5708 sip->focus_factory_uri = NULL;
5710 sip->fd = -1;
5711 sip->processing_input = FALSE;
5715 * A callback for g_hash_table_foreach_remove
5717 static gboolean sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key, gpointer buddy,
5718 SIPE_UNUSED_PARAMETER gpointer user_data)
5720 sipe_free_buddy((struct sipe_buddy *) buddy);
5722 /* We must return TRUE as the key/value have already been deleted */
5723 return(TRUE);
5726 static void sipe_close(PurpleConnection *gc)
5728 struct sipe_account_data *sip = gc->proto_data;
5730 if (sip) {
5731 /* leave all conversations */
5732 sipe_session_close_all(sip);
5733 sipe_session_remove_all(sip);
5735 /* unregister */
5736 do_register_exp(sip, 0);
5738 sipe_connection_cleanup(sip);
5739 g_free(sip->sipdomain);
5740 g_free(sip->username);
5741 g_free(sip->password);
5742 g_free(sip->authdomain);
5743 g_free(sip->authuser);
5744 g_free(sip->status);
5746 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
5747 g_hash_table_destroy(sip->buddies);
5749 g_free(gc->proto_data);
5750 gc->proto_data = NULL;
5753 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row,
5754 SIPE_UNUSED_PARAMETER void *user_data)
5756 PurpleAccount *acct = purple_connection_get_account(gc);
5757 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5758 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5759 if (conv == NULL)
5760 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5761 purple_conversation_present(conv);
5762 g_free(id);
5765 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row,
5766 SIPE_UNUSED_PARAMETER void *user_data)
5769 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5770 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5773 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,
5774 SIPE_UNUSED_PARAMETER struct transaction *tc)
5776 PurpleNotifySearchResults *results;
5777 PurpleNotifySearchColumn *column;
5778 xmlnode *searchResults;
5779 xmlnode *mrow;
5780 int match_count = 0;
5781 gboolean more = FALSE;
5782 gchar *secondary;
5784 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5786 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5787 if (!searchResults) {
5788 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5789 return FALSE;
5792 results = purple_notify_searchresults_new();
5794 if (results == NULL) {
5795 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5796 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5798 xmlnode_free(searchResults);
5799 return FALSE;
5802 column = purple_notify_searchresults_column_new(_("User Name"));
5803 purple_notify_searchresults_column_add(results, column);
5805 column = purple_notify_searchresults_column_new(_("Name"));
5806 purple_notify_searchresults_column_add(results, column);
5808 column = purple_notify_searchresults_column_new(_("Company"));
5809 purple_notify_searchresults_column_add(results, column);
5811 column = purple_notify_searchresults_column_new(_("Country"));
5812 purple_notify_searchresults_column_add(results, column);
5814 column = purple_notify_searchresults_column_new(_("Email"));
5815 purple_notify_searchresults_column_add(results, column);
5817 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5818 GList *row = NULL;
5820 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5821 row = g_list_append(row, g_strdup(uri_parts[1]));
5822 g_strfreev(uri_parts);
5824 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5825 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5826 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5827 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5829 purple_notify_searchresults_row_add(results, row);
5830 match_count++;
5833 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5834 char *data = xmlnode_get_data_unescaped(mrow);
5835 more = (g_strcasecmp(data, "true") == 0);
5836 g_free(data);
5839 secondary = g_strdup_printf(
5840 dngettext(GETTEXT_PACKAGE,
5841 "Found %d contact%s:",
5842 "Found %d contacts%s:", match_count),
5843 match_count, more ? _(" (more matched your query)") : "");
5845 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5846 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5847 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5849 g_free(secondary);
5850 xmlnode_free(searchResults);
5851 return TRUE;
5854 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5856 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5857 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5858 unsigned i = 0;
5860 do {
5861 PurpleRequestField *field = entries->data;
5862 const char *id = purple_request_field_get_id(field);
5863 const char *value = purple_request_field_string_get_value(field);
5865 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5867 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5868 } while ((entries = g_list_next(entries)) != NULL);
5869 attrs[i] = NULL;
5871 if (i > 0) {
5872 gchar *query = g_strjoinv(NULL, attrs);
5873 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5874 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5875 send_soap_request_with_cb(gc->proto_data, body,
5876 (TransCallback) process_search_contact_response, NULL);
5877 g_free(body);
5878 g_free(query);
5881 g_strfreev(attrs);
5884 static void sipe_show_find_contact(PurplePluginAction *action)
5886 PurpleConnection *gc = (PurpleConnection *) action->context;
5887 PurpleRequestFields *fields;
5888 PurpleRequestFieldGroup *group;
5889 PurpleRequestField *field;
5891 fields = purple_request_fields_new();
5892 group = purple_request_field_group_new(NULL);
5893 purple_request_fields_add_group(fields, group);
5895 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5896 purple_request_field_group_add_field(group, field);
5897 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5898 purple_request_field_group_add_field(group, field);
5899 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5900 purple_request_field_group_add_field(group, field);
5901 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5902 purple_request_field_group_add_field(group, field);
5904 purple_request_fields(gc,
5905 _("Search"),
5906 _("Search for a Contact"),
5907 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5908 fields,
5909 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5910 _("_Cancel"), NULL,
5911 purple_connection_get_account(gc), NULL, NULL, gc);
5914 GList *sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin *plugin,
5915 SIPE_UNUSED_PARAMETER gpointer context)
5917 GList *menu = NULL;
5918 PurplePluginAction *act;
5920 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5921 menu = g_list_prepend(menu, act);
5923 menu = g_list_reverse(menu);
5925 return menu;
5928 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection *gc)
5932 static gboolean sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
5934 return TRUE;
5938 static gboolean sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
5940 return TRUE;
5944 static char *sipe_status_text(PurpleBuddy *buddy)
5946 struct sipe_account_data *sip;
5947 struct sipe_buddy *sbuddy;
5948 char *text = NULL;
5950 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5951 if (sip) //happens on pidgin exit
5953 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5954 if (sbuddy && sbuddy->annotation)
5956 text = g_strdup(sbuddy->annotation);
5960 return text;
5963 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, SIPE_UNUSED_PARAMETER gboolean full)
5965 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5966 const PurpleStatus *status = purple_presence_get_active_status(presence);
5967 struct sipe_account_data *sip;
5968 struct sipe_buddy *sbuddy;
5969 char *annotation = NULL;
5971 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5972 if (sip) //happens on pidgin exit
5974 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5975 if (sbuddy)
5977 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5981 //Layout
5982 if (purple_presence_is_online(presence))
5984 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5987 if (annotation)
5989 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5990 g_free(annotation);
5995 static GHashTable *
5996 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount *account)
5998 GHashTable *table;
5999 table = g_hash_table_new(g_str_hash, g_str_equal);
6000 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
6001 return table;
6004 static PurpleBuddy *
6005 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6007 PurpleBuddy *clone;
6008 const gchar *server_alias, *email;
6009 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6011 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6013 purple_blist_add_buddy(clone, NULL, group, NULL);
6015 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
6016 if (server_alias) {
6017 purple_blist_server_alias_buddy(clone, server_alias);
6020 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6021 if (email) {
6022 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
6025 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6026 //for UI to update;
6027 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6028 return clone;
6031 static void
6032 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6034 PurpleBuddy *buddy, *b;
6035 PurpleConnection *gc;
6036 PurpleGroup * group = purple_find_group(group_name);
6038 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6040 buddy = (PurpleBuddy *)node;
6042 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
6043 gc = purple_account_get_connection(buddy->account);
6045 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6046 if (!b){
6047 b = purple_blist_add_buddy_clone(group, buddy);
6050 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6053 static void
6054 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6056 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6058 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6060 /* 2007+ conference */
6061 if (sip->msrtc_event_categories)
6063 sipe_conf_add(sip, buddy->name);
6065 else /* 2005- multiparty chat */
6067 gchar *self = sip_uri_self(sip);
6068 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
6069 struct sip_session *session;
6071 session = sipe_session_add_chat(sip);
6072 session->roster_manager = g_strdup(self);
6074 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6075 session->chat_name = g_strdup(chat_name);
6076 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
6077 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6078 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6080 g_free(chat_name);
6081 g_free(self);
6085 static gboolean
6086 sipe_is_election_finished(struct sip_session *session)
6088 gboolean res = TRUE;
6090 SIPE_DIALOG_FOREACH {
6091 if (dialog->election_vote == 0) {
6092 res = FALSE;
6093 break;
6095 } SIPE_DIALOG_FOREACH_END;
6097 if (res) {
6098 session->is_voting_in_progress = FALSE;
6100 return res;
6103 static void
6104 sipe_election_start(struct sipe_account_data *sip,
6105 struct sip_session *session)
6107 int election_timeout;
6109 if (session->is_voting_in_progress) {
6110 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6111 return;
6112 } else {
6113 session->is_voting_in_progress = TRUE;
6115 session->bid = rand();
6117 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6119 SIPE_DIALOG_FOREACH {
6120 /* reset election_vote for each chat participant */
6121 dialog->election_vote = 0;
6123 /* send RequestRM to each chat participant*/
6124 sipe_send_election_request_rm(sip, dialog, session->bid);
6125 } SIPE_DIALOG_FOREACH_END;
6127 election_timeout = 15; /* sec */
6128 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6132 * @param who a URI to whom to invite to chat
6134 void
6135 sipe_invite_to_chat(struct sipe_account_data *sip,
6136 struct sip_session *session,
6137 const gchar *who)
6139 /* a conference */
6140 if (session->focus_uri)
6142 sipe_invite_conf(sip, session, who);
6144 else /* a multi-party chat */
6146 gchar *self = sip_uri_self(sip);
6147 if (session->roster_manager) {
6148 if (!strcmp(session->roster_manager, self)) {
6149 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6150 } else {
6151 sipe_refer(sip, session, who);
6153 } else {
6154 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6156 session->pending_invite_queue = slist_insert_unique_sorted(
6157 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6159 sipe_election_start(sip, session);
6161 g_free(self);
6165 void
6166 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6167 struct sip_session *session)
6169 gchar *invitee;
6170 GSList *entry = session->pending_invite_queue;
6172 while (entry) {
6173 invitee = entry->data;
6174 sipe_invite_to_chat(sip, session, invitee);
6175 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6176 g_free(invitee);
6180 static void
6181 sipe_election_result(struct sipe_account_data *sip,
6182 void *sess)
6184 struct sip_session *session = (struct sip_session *)sess;
6185 gchar *rival;
6186 gboolean has_won = TRUE;
6188 if (session->roster_manager) {
6189 purple_debug_info("sipe",
6190 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6191 return;
6194 session->is_voting_in_progress = FALSE;
6196 SIPE_DIALOG_FOREACH {
6197 if (dialog->election_vote < 0) {
6198 has_won = FALSE;
6199 rival = dialog->with;
6200 break;
6202 } SIPE_DIALOG_FOREACH_END;
6204 if (has_won) {
6205 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6207 session->roster_manager = sip_uri_self(sip);
6209 SIPE_DIALOG_FOREACH {
6210 /* send SetRM to each chat participant*/
6211 sipe_send_election_set_rm(sip, dialog);
6212 } SIPE_DIALOG_FOREACH_END;
6213 } else {
6214 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6216 session->bid = 0;
6218 sipe_process_pending_invite_queue(sip, session);
6222 * For 2007+ conference only.
6224 static void
6225 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_name)
6227 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6228 struct sip_session *session;
6230 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
6231 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_name=%s\n", chat_name);
6233 session = sipe_session_find_chat_by_name(sip, chat_name);
6235 sipe_conf_modify_user_role(sip, session, buddy->name);
6239 * For 2007+ conference only.
6241 static void
6242 sipe_buddy_menu_chat_remove_cb(PurpleBuddy *buddy, const char *chat_name)
6244 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6245 struct sip_session *session;
6247 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
6248 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_name=%s\n", chat_name);
6250 session = sipe_session_find_chat_by_name(sip, chat_name);
6252 sipe_conf_delete_user(sip, session, buddy->name);
6255 static void
6256 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6258 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6259 struct sip_session *session;
6261 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6262 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6264 session = sipe_session_find_chat_by_name(sip, chat_name);
6266 sipe_invite_to_chat(sip, session, buddy->name);
6269 static void
6270 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6272 const gchar *email;
6273 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6275 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6276 if (email)
6278 char *mailto = g_strdup_printf("mailto:%s", email);
6279 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6280 #ifndef _WIN32
6282 pid_t pid;
6283 char *const parmList[] = {mailto, NULL};
6284 if ((pid = fork()) == -1)
6286 purple_debug_info("sipe", "fork() error\n");
6288 else if (pid == 0)
6290 execvp("xdg-email", parmList);
6291 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6294 #else
6296 BOOL ret;
6297 _flushall();
6298 errno = 0;
6299 //@TODO resolve env variable %WINDIR% first
6300 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6301 if (errno)
6303 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6306 #endif
6308 g_free(mailto);
6310 else
6312 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6317 * A menu which appear when right-clicking on buddy in contact list.
6319 static GList *
6320 sipe_buddy_menu(PurpleBuddy *buddy)
6322 PurpleBlistNode *g_node;
6323 PurpleGroup *group, *gr_parent;
6324 PurpleMenuAction *act;
6325 GList *menu = NULL;
6326 GList *menu_groups = NULL;
6327 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6328 gchar *self = sip_uri_self(sip);
6330 SIPE_SESSION_FOREACH {
6331 if (strcmp(self, buddy->name) && session->chat_name && session->conv)
6333 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
6335 PurpleConvChatBuddyFlags flags;
6336 PurpleConvChatBuddyFlags flags_us;
6338 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
6339 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
6340 if (session->focus_uri
6341 && PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) /* Not conf OP */
6342 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6344 gchar *label = g_strdup_printf(_("Make Leader of '%s'"), session->chat_name);
6345 act = purple_menu_action_new(label,
6346 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6347 g_strdup(session->chat_name), NULL);
6348 g_free(label);
6349 menu = g_list_prepend(menu, act);
6352 if (session->focus_uri
6353 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6355 gchar *label = g_strdup_printf(_("Remove from '%s'"), session->chat_name);
6356 act = purple_menu_action_new(label,
6357 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb),
6358 g_strdup(session->chat_name), NULL);
6359 g_free(label);
6360 menu = g_list_prepend(menu, act);
6363 else
6365 if (!session->focus_uri
6366 || (session->focus_uri && !session->locked))
6368 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_name);
6369 act = purple_menu_action_new(label,
6370 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6371 g_strdup(session->chat_name), NULL);
6372 g_free(label);
6373 menu = g_list_prepend(menu, act);
6377 } SIPE_SESSION_FOREACH_END;
6379 act = purple_menu_action_new(_("New Chat"),
6380 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6381 NULL, NULL);
6382 menu = g_list_prepend(menu, act);
6384 act = purple_menu_action_new(_("Send Email..."),
6385 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6386 NULL, NULL);
6387 menu = g_list_prepend(menu, act);
6389 gr_parent = purple_buddy_get_group(buddy);
6390 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6391 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6392 continue;
6394 group = (PurpleGroup *)g_node;
6395 if (group == gr_parent)
6396 continue;
6398 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6399 continue;
6401 act = purple_menu_action_new(purple_group_get_name(group),
6402 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6403 group->name, NULL);
6404 menu_groups = g_list_prepend(menu_groups, act);
6406 menu_groups = g_list_reverse(menu_groups);
6408 act = purple_menu_action_new(_("Copy to"),
6409 NULL,
6410 NULL, menu_groups);
6411 menu = g_list_prepend(menu, act);
6412 menu = g_list_reverse(menu);
6414 g_free(self);
6415 return menu;
6418 static void
6419 sipe_conf_modify_lock(PurpleChat *chat, gboolean locked)
6421 struct sipe_account_data *sip = chat->account->gc->proto_data;
6422 struct sip_session *session;
6424 session = sipe_session_find_chat_by_name(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
6425 sipe_conf_modify_conference_lock(sip, session, locked);
6428 static void
6429 sipe_chat_menu_unlock_cb(PurpleChat *chat)
6431 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
6432 sipe_conf_modify_lock(chat, FALSE);
6435 static void
6436 sipe_chat_menu_lock_cb(PurpleChat *chat)
6438 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
6439 sipe_conf_modify_lock(chat, TRUE);
6442 static GList *
6443 sipe_chat_menu(PurpleChat *chat)
6445 PurpleMenuAction *act;
6446 PurpleConvChatBuddyFlags flags_us;
6447 GList *menu = NULL;
6448 struct sipe_account_data *sip = chat->account->gc->proto_data;
6449 struct sip_session *session;
6450 gchar *self = sip_uri_self(sip);
6452 session = sipe_session_find_chat_by_name(sip, (gchar *)g_hash_table_lookup(chat->components, "channel"));
6453 if (!session) return NULL;
6455 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
6457 if (session->focus_uri
6458 && PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6460 if (session->locked) {
6461 act = purple_menu_action_new(_("Unlock Conversation"),
6462 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb),
6463 NULL, NULL);
6464 menu = g_list_prepend(menu, act);
6465 } else {
6466 act = purple_menu_action_new(_("Lock Conversation"),
6467 PURPLE_CALLBACK(sipe_chat_menu_lock_cb),
6468 NULL, NULL);
6469 menu = g_list_prepend(menu, act);
6473 menu = g_list_reverse(menu);
6475 g_free(self);
6476 return menu;
6479 static GList *
6480 sipe_blist_node_menu(PurpleBlistNode *node)
6482 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6483 return sipe_buddy_menu((PurpleBuddy *) node);
6484 } else if(PURPLE_BLIST_NODE_IS_CHAT(node)) {
6485 return sipe_chat_menu((PurpleChat *)node);
6486 } else {
6487 return NULL;
6491 static gboolean
6492 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6494 gboolean ret = TRUE;
6495 char *username = (char *)trans->payload;
6497 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6498 PurpleBuddy *pbuddy;
6499 struct sipe_buddy *sbuddy;
6500 const char *alias;
6501 char *server_alias = NULL;
6502 char *email = NULL;
6503 const char *device_name = NULL;
6505 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6507 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6508 alias = purple_buddy_get_local_alias(pbuddy);
6510 if (sip)
6512 //will query buddy UA's capabilities and send answer to log
6513 sipe_options_request(sip, username);
6515 sbuddy = g_hash_table_lookup(sip->buddies, username);
6516 if (sbuddy)
6518 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6522 if (msg->response != 200) {
6523 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6524 } else {
6525 xmlnode *searchResults;
6526 xmlnode *mrow;
6528 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6529 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6530 if (!searchResults) {
6531 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6532 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6533 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6534 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6535 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6536 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6537 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6538 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6539 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6540 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6541 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6542 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6543 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6544 if (!email || strcmp("", email)) {
6545 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6546 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6550 xmlnode_free(searchResults);
6553 purple_notify_user_info_add_section_break(info);
6555 if (!server_alias || !strcmp("", server_alias)) {
6556 g_free(server_alias);
6557 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6558 if (server_alias) {
6559 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6563 // same as server alias, do not present
6564 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6565 if (alias)
6567 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6570 if (!email || !strcmp("", email)) {
6571 g_free(email);
6572 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6573 if (email) {
6574 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6578 if (device_name)
6580 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6583 /* show a buddy's user info in a nice dialog box */
6584 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6585 username, /* buddy's username */
6586 info, /* body */
6587 NULL, /* callback called when dialog closed */
6588 NULL); /* userdata for callback */
6590 return ret;
6594 * AD search first, LDAP based
6596 static void sipe_get_info(PurpleConnection *gc, const char *username)
6598 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6599 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6601 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6602 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6603 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6604 g_free(body);
6605 g_free(row);
6608 static PurplePlugin *my_protocol = NULL;
6610 static PurplePluginProtocolInfo prpl_info =
6612 OPT_PROTO_CHAT_TOPIC,
6613 NULL, /* user_splits */
6614 NULL, /* protocol_options */
6615 NO_BUDDY_ICONS, /* icon_spec */
6616 sipe_list_icon, /* list_icon */
6617 NULL, /* list_emblems */
6618 sipe_status_text, /* status_text */
6619 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6620 sipe_status_types, /* away_states */
6621 sipe_blist_node_menu, /* blist_node_menu */
6622 NULL, /* chat_info */
6623 NULL, /* chat_info_defaults */
6624 sipe_login, /* login */
6625 sipe_close, /* close */
6626 sipe_im_send, /* send_im */
6627 NULL, /* set_info */ // TODO maybe
6628 sipe_send_typing, /* send_typing */
6629 sipe_get_info, /* get_info */
6630 sipe_set_status, /* set_status */
6631 NULL, /* set_idle */
6632 NULL, /* change_passwd */
6633 sipe_add_buddy, /* add_buddy */
6634 NULL, /* add_buddies */
6635 sipe_remove_buddy, /* remove_buddy */
6636 NULL, /* remove_buddies */
6637 sipe_add_permit, /* add_permit */
6638 sipe_add_deny, /* add_deny */
6639 sipe_add_deny, /* rem_permit */
6640 sipe_add_permit, /* rem_deny */
6641 dummy_permit_deny, /* set_permit_deny */
6642 NULL, /* join_chat */
6643 NULL, /* reject_chat */
6644 NULL, /* get_chat_name */
6645 sipe_chat_invite, /* chat_invite */
6646 sipe_chat_leave, /* chat_leave */
6647 NULL, /* chat_whisper */
6648 sipe_chat_send, /* chat_send */
6649 sipe_keep_alive, /* keepalive */
6650 NULL, /* register_user */
6651 NULL, /* get_cb_info */ // deprecated
6652 NULL, /* get_cb_away */ // deprecated
6653 sipe_alias_buddy, /* alias_buddy */
6654 sipe_group_buddy, /* group_buddy */
6655 sipe_rename_group, /* rename_group */
6656 NULL, /* buddy_free */
6657 sipe_convo_closed, /* convo_closed */
6658 purple_normalize_nocase, /* normalize */
6659 NULL, /* set_buddy_icon */
6660 sipe_remove_group, /* remove_group */
6661 NULL, /* get_cb_real_name */ // TODO?
6662 NULL, /* set_chat_topic */
6663 NULL, /* find_blist_chat */
6664 NULL, /* roomlist_get_list */
6665 NULL, /* roomlist_cancel */
6666 NULL, /* roomlist_expand_category */
6667 NULL, /* can_receive_file */
6668 NULL, /* send_file */
6669 NULL, /* new_xfer */
6670 NULL, /* offline_message */
6671 NULL, /* whiteboard_prpl_ops */
6672 sipe_send_raw, /* send_raw */
6673 NULL, /* roomlist_room_serialize */
6674 NULL, /* unregister_user */
6675 NULL, /* send_attention */
6676 NULL, /* get_attention_types */
6678 sizeof(PurplePluginProtocolInfo), /* struct_size */
6679 sipe_get_account_text_table, /* get_account_text_table */
6683 static PurplePluginInfo info = {
6684 PURPLE_PLUGIN_MAGIC,
6685 PURPLE_MAJOR_VERSION,
6686 PURPLE_MINOR_VERSION,
6687 PURPLE_PLUGIN_PROTOCOL, /**< type */
6688 NULL, /**< ui_requirement */
6689 0, /**< flags */
6690 NULL, /**< dependencies */
6691 PURPLE_PRIORITY_DEFAULT, /**< priority */
6692 "prpl-sipe", /**< id */
6693 "Microsoft LCS/OCS", /**< name */
6694 VERSION, /**< version */
6695 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6696 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6697 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6698 "Gabriel Burt <gburt@novell.com>", /**< author */
6699 PURPLE_WEBSITE, /**< homepage */
6700 sipe_plugin_load, /**< load */
6701 sipe_plugin_unload, /**< unload */
6702 sipe_plugin_destroy, /**< destroy */
6703 NULL, /**< ui_info */
6704 &prpl_info, /**< extra_info */
6705 NULL,
6706 sipe_actions,
6707 NULL,
6708 NULL,
6709 NULL,
6710 NULL
6713 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin *plugin)
6715 GList *entry;
6717 entry = prpl_info.protocol_options;
6718 while (entry) {
6719 purple_account_option_destroy(entry->data);
6720 entry = g_list_delete_link(entry, entry);
6722 prpl_info.protocol_options = NULL;
6724 entry = prpl_info.user_splits;
6725 while (entry) {
6726 purple_account_user_split_destroy(entry->data);
6727 entry = g_list_delete_link(entry, entry);
6729 prpl_info.user_splits = NULL;
6732 static void init_plugin(PurplePlugin *plugin)
6734 PurpleAccountUserSplit *split;
6735 PurpleAccountOption *option;
6737 srand(time(NULL));
6739 #ifdef ENABLE_NLS
6740 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6741 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6742 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6743 textdomain(GETTEXT_PACKAGE);
6744 #endif
6746 purple_plugin_register(plugin);
6748 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
6749 purple_account_user_split_set_reverse(split, FALSE);
6750 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6752 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
6753 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6754 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6755 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6757 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
6758 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6759 // Translators: noun (networking port)
6760 option = purple_account_option_int_new(_("Port"), "port", 5061);
6761 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6763 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6764 purple_account_option_add_list_item(option, _("Auto"), "auto");
6765 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6766 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6767 purple_account_option_add_list_item(option, _("UDP"), "udp");
6768 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6770 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6771 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6773 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6774 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6776 #ifdef USE_KERBEROS
6777 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6778 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6780 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6781 * No login/password is taken into account if this option present,
6782 * instead used default credentials stored in OS.
6784 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6785 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6786 #endif
6787 my_protocol = plugin;
6790 /* I had to redefined the function for it load, but works */
6791 gboolean purple_init_plugin(PurplePlugin *plugin){
6792 plugin->info = &(info);
6793 init_plugin((plugin));
6794 sipe_plugin_load((plugin));
6795 return purple_plugin_register(plugin);
6799 Local Variables:
6800 mode: c
6801 c-file-style: "bsd"
6802 indent-tabs-mode: t
6803 tab-width: 8
6804 End: