conference: incoming message and leave the conference
[siplcs.git] / src / sipe.c
blob8b7f1d5c5c18a8f5605936117f6d6ffdd875c079
1 /**
2 * @file sipe.c
4 * pidgin-sipe
5 *
6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
11 * ***
12 * Thanks to Google's Summer of Code Program and the helpful mentors
13 * ***
15 * Session-based SIP MESSAGE documentation:
16 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #ifndef _WIN32
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <netinet/in.h>
38 #include <net/if.h>
39 #else
40 #ifdef _DLL
41 #define _WS2TCPIP_H_
42 #define _WINSOCK2API_
43 #define _LIBC_INTERNAL_
44 #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-conf.h"
73 #include "sipe-dialog.h"
74 #include "sipe-nls.h"
75 #include "sipe-utils.h"
76 #include "sipmsg.h"
77 #include "sipe-sign.h"
78 #include "dnssrv.h"
79 #include "request.h"
81 /* Keep in sync with sipe_transport_type! */
82 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
83 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
85 /* Status identifiers (see also: sipe_status_types()) */
86 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
87 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
88 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
89 /* PURPLE_STATUS_UNAVAILABLE: */
90 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
91 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
92 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
93 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
94 /* PURPLE_STATUS_AWAY: */
95 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
96 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
97 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
98 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
99 /* ??? PURPLE_STATUS_MOBILE */
100 /* ??? PURPLE_STATUS_TUNE */
102 /* Action name templates */
103 #define ACTION_NAME_PRESENCE "<presence><%s>"
105 static gchar *get_epid(struct sipe_account_data *sip)
107 if (!sip->epid) {
108 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
110 return g_strdup(sip->epid);
113 static char *genbranch()
115 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
116 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
117 rand() & 0xFFFF, rand() & 0xFFFF);
120 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
122 return "sipe";
125 static void sipe_plugin_destroy(PurplePlugin *plugin);
127 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
129 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
130 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
131 gpointer data);
133 static void sipe_close(PurpleConnection *gc);
135 static void send_presence_status(struct sipe_account_data *sip);
137 static void sendout_pkt(PurpleConnection *gc, const char *buf);
139 static void sipe_keep_alive(PurpleConnection *gc)
141 struct sipe_account_data *sip = gc->proto_data;
142 if (sip->transport == SIPE_TRANSPORT_UDP) {
143 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
144 gchar buf[2] = {0, 0};
145 purple_debug_info("sipe", "sending keep alive\n");
146 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
147 } else {
148 time_t now = time(NULL);
149 if ((sip->keepalive_timeout > 0) &&
150 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
151 #if PURPLE_VERSION_CHECK(2,4,0)
152 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
153 #endif
155 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
156 sendout_pkt(gc, "\r\n\r\n");
157 sip->last_keepalive = now;
162 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
164 struct sip_connection *ret = NULL;
165 GSList *entry = sip->openconns;
166 while (entry) {
167 ret = entry->data;
168 if (ret->fd == fd) return ret;
169 entry = entry->next;
171 return NULL;
174 static void sipe_auth_free(struct sip_auth *auth)
176 g_free(auth->opaque);
177 auth->opaque = NULL;
178 g_free(auth->realm);
179 auth->realm = NULL;
180 g_free(auth->target);
181 auth->target = NULL;
182 auth->type = AUTH_TYPE_UNSET;
183 auth->retries = 0;
184 auth->expires = 0;
185 g_free(auth->gssapi_data);
186 auth->gssapi_data = NULL;
187 sip_sec_destroy_context(auth->gssapi_context);
188 auth->gssapi_context = NULL;
191 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
193 struct sip_connection *ret = g_new0(struct sip_connection, 1);
194 ret->fd = fd;
195 sip->openconns = g_slist_append(sip->openconns, ret);
196 return ret;
199 static void connection_remove(struct sipe_account_data *sip, int fd)
201 struct sip_connection *conn = connection_find(sip, fd);
202 if (conn) {
203 sip->openconns = g_slist_remove(sip->openconns, conn);
204 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
205 g_free(conn->inbuf);
206 g_free(conn);
210 static void connection_free_all(struct sipe_account_data *sip)
212 struct sip_connection *ret = NULL;
213 GSList *entry = sip->openconns;
214 while (entry) {
215 ret = entry->data;
216 connection_remove(sip, ret->fd);
217 entry = sip->openconns;
221 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
223 gchar noncecount[9];
224 const char *authuser = sip->authuser;
225 gchar *response;
226 gchar *ret;
228 if (!authuser || strlen(authuser) < 1) {
229 authuser = sip->username;
232 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
233 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
235 // If we have a signature for the message, include that
236 if (msg->signature) {
237 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);
240 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
241 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
242 gchar *gssapi_data;
243 gchar *opaque;
245 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
246 &(auth->expires),
247 auth->type,
248 purple_account_get_bool(sip->account, "sso", TRUE),
249 sip->authdomain ? sip->authdomain : "",
250 authuser,
251 sip->password,
252 auth->target,
253 auth->gssapi_data);
254 if (!gssapi_data || !auth->gssapi_context)
255 return NULL;
257 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
258 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
259 g_free(opaque);
260 g_free(gssapi_data);
261 return ret;
264 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
266 } else { /* Digest */
268 /* Calculate new session key */
269 if (!auth->opaque) {
270 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
271 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
272 authuser, auth->realm, sip->password,
273 auth->gssapi_data, NULL);
276 sprintf(noncecount, "%08d", auth->nc++);
277 response = purple_cipher_http_digest_calculate_response("md5",
278 msg->method, msg->target, NULL, NULL,
279 auth->gssapi_data, noncecount, NULL,
280 auth->opaque);
281 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
283 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);
284 g_free(response);
285 return ret;
289 static char *parse_attribute(const char *attrname, const char *source)
291 const char *tmp, *tmp2;
292 char *retval = NULL;
293 int len = strlen(attrname);
295 if (!strncmp(source, attrname, len)) {
296 tmp = source + len;
297 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
298 if (tmp2)
299 retval = g_strndup(tmp, tmp2 - tmp);
300 else
301 retval = g_strdup(tmp);
304 return retval;
307 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
309 int i;
310 gchar **parts;
312 if (!hdr) {
313 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
314 return;
317 if (!g_strncasecmp(hdr, "NTLM", 4)) {
318 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
319 auth->type = AUTH_TYPE_NTLM;
320 hdr += 5;
321 auth->nc = 1;
322 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
323 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
324 auth->type = AUTH_TYPE_KERBEROS;
325 hdr += 9;
326 auth->nc = 3;
327 } else {
328 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
329 auth->type = AUTH_TYPE_DIGEST;
330 hdr += 7;
333 parts = g_strsplit(hdr, "\", ", 0);
334 for (i = 0; parts[i]; i++) {
335 char *tmp;
337 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
339 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
340 g_free(auth->gssapi_data);
341 auth->gssapi_data = tmp;
343 if (auth->type == AUTH_TYPE_NTLM) {
344 /* NTLM module extracts nonce from gssapi-data */
345 auth->nc = 3;
348 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
349 /* Only used with AUTH_TYPE_DIGEST */
350 g_free(auth->gssapi_data);
351 auth->gssapi_data = tmp;
352 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
353 g_free(auth->opaque);
354 auth->opaque = tmp;
355 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
356 g_free(auth->realm);
357 auth->realm = tmp;
359 if (auth->type == AUTH_TYPE_DIGEST) {
360 /* Throw away old session key */
361 g_free(auth->opaque);
362 auth->opaque = NULL;
363 auth->nc = 1;
366 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
367 g_free(auth->target);
368 auth->target = tmp;
371 g_strfreev(parts);
373 return;
376 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
378 PurpleConnection *gc = data;
379 struct sipe_account_data *sip = gc->proto_data;
380 gsize max_write;
381 gssize written;
383 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
385 if (max_write == 0) {
386 if (sip->tx_handler != 0){
387 purple_input_remove(sip->tx_handler);
388 sip->tx_handler = 0;
390 return;
393 written = write(sip->fd, sip->txbuf->outptr, max_write);
395 if (written < 0 && errno == EAGAIN)
396 written = 0;
397 else if (written <= 0) {
398 /*TODO: do we really want to disconnect on a failure to write?*/
399 purple_connection_error(gc, _("Could not write"));
400 return;
403 purple_circ_buffer_mark_read(sip->txbuf, written);
406 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
408 PurpleConnection *gc = data;
409 struct sipe_account_data *sip = gc->proto_data;
410 gsize max_write;
411 gssize written;
413 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
415 if (max_write == 0) {
416 if (sip->tx_handler != 0) {
417 purple_input_remove(sip->tx_handler);
418 sip->tx_handler = 0;
419 return;
423 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
425 if (written < 0 && errno == EAGAIN)
426 written = 0;
427 else if (written <= 0) {
428 /*TODO: do we really want to disconnect on a failure to write?*/
429 purple_connection_error(gc, _("Could not write"));
430 return;
433 purple_circ_buffer_mark_read(sip->txbuf, written);
436 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
438 static void send_later_cb(gpointer data, gint source, const gchar *error)
440 PurpleConnection *gc = data;
441 struct sipe_account_data *sip;
442 struct sip_connection *conn;
444 if (!PURPLE_CONNECTION_IS_VALID(gc))
446 if (source >= 0)
447 close(source);
448 return;
451 if (source < 0) {
452 purple_connection_error(gc, _("Could not connect"));
453 return;
456 sip = gc->proto_data;
457 sip->fd = source;
458 sip->connecting = FALSE;
459 sip->last_keepalive = time(NULL);
461 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
463 /* If there is more to write now, we need to register a handler */
464 if (sip->txbuf->bufused > 0)
465 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
467 conn = connection_create(sip, source);
468 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
471 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
473 struct sipe_account_data *sip;
474 struct sip_connection *conn;
476 if (!PURPLE_CONNECTION_IS_VALID(gc))
478 if (gsc) purple_ssl_close(gsc);
479 return NULL;
482 sip = gc->proto_data;
483 sip->fd = gsc->fd;
484 sip->gsc = gsc;
485 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
486 sip->connecting = FALSE;
487 sip->last_keepalive = time(NULL);
489 conn = connection_create(sip, gsc->fd);
491 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
493 return sip;
496 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
498 PurpleConnection *gc = data;
499 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
500 if (sip == NULL) return;
502 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
504 /* If there is more to write now */
505 if (sip->txbuf->bufused > 0) {
506 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
511 static void sendlater(PurpleConnection *gc, const char *buf)
513 struct sipe_account_data *sip = gc->proto_data;
515 if (!sip->connecting) {
516 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
517 if (sip->transport == SIPE_TRANSPORT_TLS){
518 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
519 } else {
520 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
521 purple_connection_error(gc, _("Couldn't create socket"));
524 sip->connecting = TRUE;
527 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
528 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
530 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
533 static void sendout_pkt(PurpleConnection *gc, const char *buf)
535 struct sipe_account_data *sip = gc->proto_data;
536 time_t currtime = time(NULL);
537 int writelen = strlen(buf);
539 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
540 if (sip->transport == SIPE_TRANSPORT_UDP) {
541 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
542 purple_debug_info("sipe", "could not send packet\n");
544 } else {
545 int ret;
546 if (sip->fd < 0) {
547 sendlater(gc, buf);
548 return;
551 if (sip->tx_handler) {
552 ret = -1;
553 errno = EAGAIN;
554 } else{
555 if (sip->gsc){
556 ret = purple_ssl_write(sip->gsc, buf, writelen);
557 }else{
558 ret = write(sip->fd, buf, writelen);
562 if (ret < 0 && errno == EAGAIN)
563 ret = 0;
564 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
565 sendlater(gc, buf);
566 return;
569 if (ret < writelen) {
570 if (!sip->tx_handler){
571 if (sip->gsc){
572 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
574 else{
575 sip->tx_handler = purple_input_add(sip->fd,
576 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
577 gc);
581 /* XXX: is it OK to do this? You might get part of a request sent
582 with part of another. */
583 if (sip->txbuf->bufused > 0)
584 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
586 purple_circ_buffer_append(sip->txbuf, buf + ret,
587 writelen - ret);
592 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
594 sendout_pkt(gc, buf);
595 return len;
598 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
600 GSList *tmp = msg->headers;
601 gchar *name;
602 gchar *value;
603 GString *outstr = g_string_new("");
604 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
605 while (tmp) {
606 name = ((struct siphdrelement*) (tmp->data))->name;
607 value = ((struct siphdrelement*) (tmp->data))->value;
608 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
609 tmp = g_slist_next(tmp);
611 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
612 sendout_pkt(sip->gc, outstr->str);
613 g_string_free(outstr, TRUE);
616 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
618 gchar * buf;
619 if (sip->registrar.gssapi_context) {
620 struct sipmsg_breakdown msgbd;
621 gchar *signature_input_str;
622 msgbd.msg = msg;
623 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
624 msgbd.rand = g_strdup_printf("%08x", g_random_int());
625 sip->registrar.ntlm_num++;
626 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
627 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
628 if (signature_input_str != NULL) {
629 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
630 msg->signature = signature_hex;
631 msg->rand = g_strdup(msgbd.rand);
632 msg->num = g_strdup(msgbd.num);
633 g_free(signature_input_str);
635 sipmsg_breakdown_free(&msgbd);
638 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
639 buf = auth_header(sip, &sip->registrar, msg);
640 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
641 g_free(buf);
642 } 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")) {
643 sip->registrar.nc = 3;
644 #ifdef USE_KERBEROS
645 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
646 #endif
647 sip->registrar.type = AUTH_TYPE_NTLM;
648 #ifdef USE_KERBEROS
649 } else {
650 sip->registrar.type = AUTH_TYPE_KERBEROS;
652 #endif
655 buf = auth_header(sip, &sip->registrar, msg);
656 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
657 g_free(buf);
658 } else {
659 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
664 * unused. Needed?
665 static char *get_contact_service(struct sipe_account_data *sip)
667 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()));
668 //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);
672 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
673 const char *text, const char *body)
675 gchar *name;
676 gchar *value;
677 GString *outstr = g_string_new("");
678 struct sipe_account_data *sip = gc->proto_data;
679 gchar *contact;
680 GSList *tmp;
681 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
683 contact = get_contact(sip);
684 sipmsg_add_header(msg, "Contact", contact);
685 g_free(contact);
687 if (body) {
688 gchar len[12];
689 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
690 sipmsg_add_header(msg, "Content-Length", len);
691 } else {
692 sipmsg_add_header(msg, "Content-Length", "0");
695 msg->response = code;
697 sipmsg_strip_headers(msg, keepers);
698 sipmsg_merge_new_headers(msg);
699 sign_outgoing_message(msg, sip, msg->method);
701 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
702 tmp = msg->headers;
703 while (tmp) {
704 name = ((struct siphdrelement*) (tmp->data))->name;
705 value = ((struct siphdrelement*) (tmp->data))->value;
707 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
708 tmp = g_slist_next(tmp);
710 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
711 sendout_pkt(gc, outstr->str);
712 g_string_free(outstr, TRUE);
715 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
717 sip->transactions = g_slist_remove(sip->transactions, trans);
718 if (trans->msg) sipmsg_free(trans->msg);
719 g_free(trans->key);
720 g_free(trans);
723 static struct transaction *
724 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
726 gchar *call_id = NULL;
727 gchar *cseq = NULL;
728 struct transaction *trans = g_new0(struct transaction, 1);
730 trans->time = time(NULL);
731 trans->msg = (struct sipmsg *)msg;
732 call_id = sipmsg_find_header(trans->msg, "Call-ID");
733 cseq = sipmsg_find_header(trans->msg, "CSeq");
734 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
735 trans->callback = callback;
736 sip->transactions = g_slist_append(sip->transactions, trans);
737 return trans;
740 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
742 struct transaction *trans;
743 GSList *transactions = sip->transactions;
744 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
745 gchar *cseq = sipmsg_find_header(msg, "CSeq");
746 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
748 while (transactions) {
749 trans = transactions->data;
750 if (!g_strcasecmp(trans->key, key)) {
751 g_free(key);
752 return trans;
754 transactions = transactions->next;
757 g_free(key);
758 return NULL;
761 struct transaction *
762 send_sip_request(PurpleConnection *gc, const gchar *method,
763 const gchar *url, const gchar *to, const gchar *addheaders,
764 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
766 struct sipe_account_data *sip = gc->proto_data;
767 const char *addh = "";
768 char *buf;
769 struct sipmsg *msg;
770 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
771 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
772 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
773 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
774 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
775 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
776 gchar *route = strdup("");
777 gchar *epid = get_epid(sip); // TODO generate one per account/login
778 int cseq = dialog ? ++dialog->cseq :
779 /* This breaks OCS2007: own presence, contact search, ?
780 1 .* as Call-Id is new in this case */
781 ++sip->cseq;
782 struct transaction *trans;
784 if (dialog && dialog->routes)
786 GSList *iter = dialog->routes;
788 while(iter)
790 char *tmp = route;
791 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
792 g_free(tmp);
793 iter = g_slist_next(iter);
797 if (!ourtag && !dialog) {
798 ourtag = gentag();
801 if (!strcmp(method, "REGISTER")) {
802 if (sip->regcallid) {
803 g_free(callid);
804 callid = g_strdup(sip->regcallid);
805 } else {
806 sip->regcallid = g_strdup(callid);
810 if (addheaders) addh = addheaders;
812 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
813 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
814 "From: <sip:%s>%s%s;epid=%s\r\n"
815 "To: <%s>%s%s%s%s\r\n"
816 "Max-Forwards: 70\r\n"
817 "CSeq: %d %s\r\n"
818 "User-Agent: %s\r\n"
819 "Call-ID: %s\r\n"
820 "%s%s"
821 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
822 method,
823 dialog && dialog->request ? dialog->request : url,
824 TRANSPORT_DESCRIPTOR,
825 purple_network_get_my_ip(-1),
826 sip->listenport,
827 branch ? ";branch=" : "",
828 branch ? branch : "",
829 sip->username,
830 ourtag ? ";tag=" : "",
831 ourtag ? ourtag : "",
832 epid,
834 theirtag ? ";tag=" : "",
835 theirtag ? theirtag : "",
836 theirepid ? ";epid=" : "",
837 theirepid ? theirepid : "",
838 cseq,
839 method,
840 useragent,
841 callid,
842 route,
843 addh,
844 body ? (gsize) strlen(body) : 0,
845 body ? body : "");
848 //printf ("parsing msg buf:\n%s\n\n", buf);
849 msg = sipmsg_parse_msg(buf);
851 g_free(buf);
852 g_free(ourtag);
853 g_free(theirtag);
854 g_free(theirepid);
855 g_free(branch);
856 g_free(callid);
857 g_free(route);
858 g_free(epid);
860 sign_outgoing_message (msg, sip, method);
862 buf = sipmsg_to_string (msg);
864 /* add to ongoing transactions */
865 trans = transactions_add_buf(sip, msg, tc);
866 sendout_pkt(gc, buf);
867 g_free(buf);
869 return trans;
872 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
874 gchar *from = g_strdup_printf("sip:%s", sip->username);
875 gchar *contact = get_contact(sip);
876 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
877 "Content-Type: application/SOAP+xml\r\n",contact);
879 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
880 tr->payload = payload;
882 g_free(from);
883 g_free(contact);
884 g_free(hdr);
887 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
889 send_soap_request_with_cb(sip, body, NULL, NULL);
892 static char *get_contact_register(struct sipe_account_data *sip)
894 char *epid = get_epid(sip);
895 char *uuid = generateUUIDfromEPID(epid);
896 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);
897 g_free(uuid);
898 g_free(epid);
899 return(buf);
902 static void do_register_exp(struct sipe_account_data *sip, int expire)
904 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
905 char *uri = g_strdup_printf("sip:%s", sip->sipdomain);
906 char *to = g_strdup_printf("sip:%s", sip->username);
907 char *contact = get_contact_register(sip);
908 char *hdr = g_strdup_printf("Contact: %s\r\n"
909 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
910 "Event: registration\r\n"
911 "Allow-Events: presence\r\n"
912 "ms-keep-alive: UAC;hop-hop=yes\r\n"
913 "%s", contact, expires);
914 g_free(contact);
915 g_free(expires);
917 sip->registerstatus = 1;
919 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
920 process_register_response);
922 g_free(hdr);
923 g_free(uri);
924 g_free(to);
927 static void do_register_cb(struct sipe_account_data *sip, void *unused)
929 do_register_exp(sip, -1);
930 sip->reregister_set = FALSE;
933 static void do_register(struct sipe_account_data *sip)
935 do_register_exp(sip, -1);
938 static void
939 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
941 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
942 send_soap_request(sip, body);
943 g_free(body);
946 static void
947 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
949 if (allow) {
950 purple_debug_info("sipe", "Authorizing contact %s\n", who);
951 } else {
952 purple_debug_info("sipe", "Blocking contact %s\n", who);
955 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
958 static
959 void sipe_auth_user_cb(void * data)
961 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
962 if (!job) return;
964 sipe_contact_allow_deny (job->sip, job->who, TRUE);
965 g_free(job);
968 static
969 void sipe_deny_user_cb(void * data)
971 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
972 if (!job) return;
974 sipe_contact_allow_deny (job->sip, job->who, FALSE);
975 g_free(job);
978 static void
979 sipe_add_permit(PurpleConnection *gc, const char *name)
981 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
982 sipe_contact_allow_deny(sip, name, TRUE);
985 static void
986 sipe_add_deny(PurpleConnection *gc, const char *name)
988 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
989 sipe_contact_allow_deny(sip, name, FALSE);
992 /*static void
993 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
995 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
996 sipe_contact_set_acl(sip, name, "");
999 static void
1000 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1002 xmlnode *watchers;
1003 xmlnode *watcher;
1004 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1005 if (msg->response != 0 && msg->response != 200) return;
1007 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1009 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1010 if (!watchers) return;
1012 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1013 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1014 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1015 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1017 // TODO pull out optional displayName to pass as alias
1018 if (remote_user) {
1019 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1020 job->who = remote_user;
1021 job->sip = sip;
1022 purple_account_request_authorization(
1023 sip->account,
1024 remote_user,
1025 _("you"), /* id */
1026 alias,
1027 NULL, /* message */
1028 on_list,
1029 sipe_auth_user_cb,
1030 sipe_deny_user_cb,
1031 (void *) job);
1036 xmlnode_free(watchers);
1037 return;
1040 static void
1041 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1043 PurpleGroup * purple_group = purple_find_group(group->name);
1044 if (!purple_group) {
1045 purple_group = purple_group_new(group->name);
1046 purple_blist_add_group(purple_group, NULL);
1049 if (purple_group) {
1050 group->purple_group = purple_group;
1051 sip->groups = g_slist_append(sip->groups, group);
1052 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1053 } else {
1054 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1058 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1060 struct sipe_group *group;
1061 GSList *entry;
1062 if (sip == NULL) {
1063 return NULL;
1066 entry = sip->groups;
1067 while (entry) {
1068 group = entry->data;
1069 if (group->id == id) {
1070 return group;
1072 entry = entry->next;
1074 return NULL;
1077 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1079 struct sipe_group *group;
1080 GSList *entry;
1081 if (sip == NULL) {
1082 return NULL;
1085 entry = sip->groups;
1086 while (entry) {
1087 group = entry->data;
1088 if (!strcmp(group->name, name)) {
1089 return group;
1091 entry = entry->next;
1093 return NULL;
1096 static void
1097 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1099 gchar *body;
1100 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1101 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1102 send_soap_request(sip, body);
1103 g_free(body);
1104 g_free(group->name);
1105 group->name = g_strdup(name);
1109 * Only appends if no such value already stored.
1110 * Like Set in Java.
1112 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1113 GSList * res = list;
1114 if (!g_slist_find_custom(list, data, func)) {
1115 res = g_slist_insert_sorted(list, data, func);
1117 return res;
1120 static int
1121 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1122 return group1->id - group2->id;
1126 * Returns string like "2 4 7 8" - group ids buddy belong to.
1128 static gchar *
1129 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1130 int i = 0;
1131 gchar *res;
1132 //creating array from GList, converting int to gchar*
1133 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1134 GSList *entry = buddy->groups;
1135 while (entry) {
1136 struct sipe_group * group = entry->data;
1137 ids_arr[i] = g_strdup_printf("%d", group->id);
1138 entry = entry->next;
1139 i++;
1141 ids_arr[i] = NULL;
1142 res = g_strjoinv(" ", ids_arr);
1143 g_strfreev(ids_arr);
1144 return res;
1148 * Sends buddy update to server
1150 static void
1151 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1153 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1154 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1156 if (buddy && purple_buddy) {
1157 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1158 gchar *body;
1159 gchar *groups = sipe_get_buddy_groups_string(buddy);
1160 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1162 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1163 alias, groups, "true", buddy->name, sip->contacts_delta++
1165 send_soap_request(sip, body);
1166 g_free(groups);
1167 g_free(body);
1171 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1173 if (msg->response == 200) {
1174 struct sipe_group *group;
1175 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1176 xmlnode *xml;
1177 xmlnode *node;
1178 char *group_id;
1179 struct sipe_buddy *buddy;
1181 xml = xmlnode_from_str(msg->body, msg->bodylen);
1182 if (!xml) {
1183 g_free(ctx);
1184 return FALSE;
1187 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1188 if (!node) {
1189 g_free(ctx);
1190 xmlnode_free(xml);
1191 return FALSE;
1194 group_id = xmlnode_get_data(node);
1195 if (!group_id) {
1196 g_free(ctx);
1197 xmlnode_free(xml);
1198 return FALSE;
1201 group = g_new0(struct sipe_group, 1);
1202 group->id = (int)g_ascii_strtod(group_id, NULL);
1203 g_free(group_id);
1204 group->name = ctx->group_name;
1206 sipe_group_add(sip, group);
1208 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1209 if (buddy) {
1210 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1213 sipe_group_set_user(sip, ctx->user_name);
1215 g_free(ctx);
1216 xmlnode_free(xml);
1217 return TRUE;
1219 return FALSE;
1222 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1224 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1225 gchar *body;
1226 ctx->group_name = g_strdup(name);
1227 ctx->user_name = g_strdup(who);
1229 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1230 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1231 g_free(body);
1235 * Data structure for scheduled actions
1237 typedef void (*Action) (struct sipe_account_data *, void *);
1239 struct scheduled_action {
1240 /**
1241 * Name of action.
1242 * Format is <Event>[<Data>...]
1243 * Example: <presence><sip:user@domain.com> or <registration>
1245 gchar *name;
1246 guint timeout_handler;
1247 gboolean repetitive;
1248 Action action;
1249 GDestroyNotify destroy;
1250 struct sipe_account_data *sip;
1251 void *payload;
1255 * A timer callback
1256 * Should return FALSE if repetitive action is not needed
1258 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1260 gboolean ret;
1261 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1262 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1263 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1264 (sched_action->action)(sched_action->sip, sched_action->payload);
1265 ret = sched_action->repetitive;
1266 (*sched_action->destroy)(sched_action->payload);
1267 g_free(sched_action->name);
1268 g_free(sched_action);
1269 return ret;
1273 * Kills action timer effectively cancelling
1274 * scheduled action
1276 * @param name of action
1278 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1280 GSList *entry;
1282 if (!sip->timeouts || !name) return;
1284 entry = sip->timeouts;
1285 while (entry) {
1286 struct scheduled_action *sched_action = entry->data;
1287 if(!strcmp(sched_action->name, name)) {
1288 GSList *to_delete = entry;
1289 entry = entry->next;
1290 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1291 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1292 purple_timeout_remove(sched_action->timeout_handler);
1293 (*sched_action->destroy)(sched_action->payload);
1294 g_free(sched_action->name);
1295 g_free(sched_action);
1296 } else {
1297 entry = entry->next;
1302 static void
1303 sipe_schedule_action0(const gchar *name,
1304 int timeout,
1305 gboolean isSeconds,
1306 Action action,
1307 GDestroyNotify destroy,
1308 struct sipe_account_data *sip,
1309 void *payload)
1311 struct scheduled_action *sched_action;
1313 /* Make sure each action only exists once */
1314 sipe_cancel_scheduled_action(sip, name);
1316 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1317 sched_action = g_new0(struct scheduled_action, 1);
1318 sched_action->repetitive = FALSE;
1319 sched_action->name = g_strdup(name);
1320 sched_action->action = action;
1321 sched_action->destroy = destroy ? destroy : g_free;
1322 sched_action->sip = sip;
1323 sched_action->payload = payload;
1324 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1325 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1326 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1327 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1331 * Do schedule action for execution in the future.
1332 * Non repetitive execution.
1334 * @param name of action (will be copied)
1335 * @param timeout in seconds
1336 * @action callback function
1337 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1339 static void
1340 sipe_schedule_action(const gchar *name,
1341 int timeout,
1342 Action action,
1343 GDestroyNotify destroy,
1344 struct sipe_account_data *sip,
1345 void *payload)
1347 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1351 * Same as sipe_schedule_action() but timeout is in milliseconds.
1353 static void
1354 sipe_schedule_action_msec(const gchar *name,
1355 int timeout,
1356 Action action,
1357 GDestroyNotify destroy,
1358 struct sipe_account_data *sip,
1359 void *payload)
1361 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1365 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1367 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1369 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1371 process_incoming_notify(sip, msg, FALSE, FALSE);
1373 return TRUE;
1376 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1378 gchar *tmp = *resources_uri;
1379 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1380 g_free(tmp);
1383 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1385 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1386 if (sbuddy && !sbuddy->resubscribed) { // Only not resubscribed contacts; the first time everybody are included
1387 gchar *tmp = *resources_uri;
1388 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1389 g_free(tmp);
1394 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1395 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1396 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1397 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1398 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1401 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1403 gchar *contact = get_contact(sip);
1404 gchar *request;
1405 gchar *content;
1406 gchar *require = "";
1407 gchar *accept = "";
1408 gchar *autoextend = "";
1409 gchar *content_type;
1411 if (sip->msrtc_event_categories) {
1412 require = ", categoryList";
1413 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1414 content_type = "application/msrtc-adrl-categorylist+xml";
1415 content = g_strdup_printf(
1416 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1417 "<action name=\"subscribe\" id=\"63792024\">\n"
1418 "<adhocList>\n%s</adhocList>\n"
1419 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1420 "<category name=\"note\"/>\n"
1421 "<category name=\"state\"/>\n"
1422 "</categoryList>\n"
1423 "</action>\n"
1424 "</batchSub>", sip->username, resources_uri);
1425 } else {
1426 autoextend = "Supported: com.microsoft.autoextend\r\n";
1427 content_type = "application/adrl+xml";
1428 content = g_strdup_printf(
1429 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1430 "<create xmlns=\"\">\n%s</create>\n"
1431 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1433 g_free(resources_uri);
1435 request = g_strdup_printf(
1436 "Require: adhoclist%s\r\n"
1437 "Supported: eventlist\r\n"
1438 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1439 "Supported: ms-piggyback-first-notify\r\n"
1440 "%sSupported: ms-benotify\r\n"
1441 "Proxy-Require: ms-benotify\r\n"
1442 "Event: presence\r\n"
1443 "Content-Type: %s\r\n"
1444 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1445 g_free(contact);
1447 /* subscribe to buddy presence */
1448 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1449 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1451 g_free(content);
1452 g_free(to);
1453 g_free(request);
1456 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip, void *unused)
1458 gchar *to = g_strdup_printf("sip:%s", sip->username);
1459 gchar *resources_uri = g_strdup("");
1460 if (sip->msrtc_event_categories) {
1461 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1462 } else {
1463 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1465 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1468 struct presence_batched_routed {
1469 gchar *host;
1470 GSList *buddies;
1473 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1475 struct presence_batched_routed *data = payload;
1476 GSList *buddies = data->buddies;
1477 while (buddies) {
1478 g_free(buddies->data);
1479 buddies = buddies->next;
1481 g_slist_free(data->buddies);
1482 g_free(data->host);
1483 g_free(payload);
1486 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1488 struct presence_batched_routed *data = payload;
1489 GSList *buddies = data->buddies;
1490 gchar *resources_uri = g_strdup("");
1491 while (buddies) {
1492 gchar *tmp = resources_uri;
1493 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1494 g_free(tmp);
1495 buddies = buddies->next;
1497 sipe_subscribe_presence_batched_to(sip, resources_uri,
1498 g_strdup(data->host));
1502 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1503 * The user sends a single SUBSCRIBE request to the subscribed contact.
1504 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1508 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1510 gchar *to = strstr((char *)buddy_name, "sip:") ? g_strdup((char *)buddy_name) : g_strdup_printf("sip:%s", (char *)buddy_name);
1511 gchar *tmp = get_contact(sip);
1512 gchar *request;
1513 gchar *content;
1514 gchar *autoextend = "";
1516 if (!sip->msrtc_event_categories)
1517 autoextend = "Supported: com.microsoft.autoextend\r\n";
1519 request = g_strdup_printf(
1520 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1521 "Supported: ms-piggyback-first-notify\r\n"
1522 "%sSupported: ms-benotify\r\n"
1523 "Proxy-Require: ms-benotify\r\n"
1524 "Event: presence\r\n"
1525 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1526 "Contact: %s\r\n", autoextend,tmp);
1528 content = g_strdup_printf(
1529 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1530 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1531 "<resource uri=\"%s\"/>\n"
1532 "</adhocList>\n"
1533 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1534 "<category name=\"note\"/>\n"
1535 "<category name=\"state\"/>\n"
1536 "</categoryList>\n"
1537 "</action>\n"
1538 "</batchSub>", sip->username, to
1541 g_free(tmp);
1543 /* subscribe to buddy presence */
1544 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1546 g_free(content);
1547 g_free(to);
1548 g_free(request);
1551 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1553 if (!purple_status_is_active(status))
1554 return;
1556 if (account->gc) {
1557 struct sipe_account_data *sip = account->gc->proto_data;
1559 if (sip) {
1560 g_free(sip->status);
1561 sip->status = g_strdup(purple_status_get_id(status));
1562 send_presence_status(sip);
1567 static void
1568 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1570 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1571 sipe_group_set_user(sip, name);
1574 static void
1575 sipe_group_buddy(PurpleConnection *gc,
1576 const char *who,
1577 const char *old_group_name,
1578 const char *new_group_name)
1580 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1581 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1582 struct sipe_group * old_group = NULL;
1583 struct sipe_group * new_group;
1585 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1586 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1588 if(!buddy) { // buddy not in roaming list
1589 return;
1592 if (old_group_name) {
1593 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1595 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1597 if (old_group) {
1598 buddy->groups = g_slist_remove(buddy->groups, old_group);
1599 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1602 if (!new_group) {
1603 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1604 } else {
1605 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1606 sipe_group_set_user(sip, who);
1610 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1612 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1613 struct sipe_buddy *b;
1615 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1617 // Prepend sip: if needed
1618 if (strncmp("sip:", buddy->name, 4)) {
1619 gchar *buf = g_strdup_printf("sip:%s", buddy->name);
1620 purple_blist_rename_buddy(buddy, buf);
1621 g_free(buf);
1624 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1625 b = g_new0(struct sipe_buddy, 1);
1626 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1627 b->name = g_strdup(buddy->name);
1628 g_hash_table_insert(sip->buddies, b->name, b);
1629 sipe_group_buddy(gc, b->name, NULL, group->name);
1630 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1631 } else {
1632 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1636 static void sipe_free_buddy(struct sipe_buddy *buddy)
1638 #ifndef _WIN32
1640 * We are calling g_hash_table_foreach_steal(). That means that no
1641 * key/value deallocation functions are called. Therefore the glib
1642 * hash code does not touch the key (buddy->name) or value (buddy)
1643 * of the to-be-deleted hash node at all. It follows that we
1645 * - MUST free the memory for the key ourselves and
1646 * - ARE allowed to do it in this function
1648 * Conclusion: glib must be broken on the Windows platform if sipe
1649 * crashes with SIGTRAP when closing. You'll have to live
1650 * with the memory leak until this is fixed.
1652 g_free(buddy->name);
1653 #endif
1654 g_free(buddy->annotation);
1655 g_free(buddy->device_name);
1656 g_slist_free(buddy->groups);
1657 g_free(buddy);
1661 * Unassociates buddy from group first.
1662 * Then see if no groups left, removes buddy completely.
1663 * Otherwise updates buddy groups on server.
1665 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1667 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1668 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1669 struct sipe_group *g = NULL;
1671 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1673 if (!b) return;
1675 if (group) {
1676 g = sipe_group_find_by_name(sip, group->name);
1679 if (g) {
1680 b->groups = g_slist_remove(b->groups, g);
1681 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1684 if (g_slist_length(b->groups) < 1) {
1685 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1686 sipe_cancel_scheduled_action(sip, action_name);
1687 g_free(action_name);
1689 g_hash_table_remove(sip->buddies, buddy->name);
1691 if (b->name) {
1692 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1693 send_soap_request(sip, body);
1694 g_free(body);
1697 sipe_free_buddy(b);
1698 } else {
1699 //updates groups on server
1700 sipe_group_set_user(sip, b->name);
1705 static void
1706 sipe_rename_group(PurpleConnection *gc,
1707 const char *old_name,
1708 PurpleGroup *group,
1709 GList *moved_buddies)
1711 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1712 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1713 if (group) {
1714 sipe_group_rename(sip, s_group, group->name);
1715 } else {
1716 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1720 static void
1721 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1723 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1724 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1725 if (s_group) {
1726 gchar *body;
1727 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1728 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1729 send_soap_request(sip, body);
1730 g_free(body);
1732 sip->groups = g_slist_remove(sip->groups, s_group);
1733 g_free(s_group->name);
1734 g_free(s_group);
1735 } else {
1736 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1740 static GList *sipe_status_types(PurpleAccount *acc)
1742 PurpleStatusType *type;
1743 GList *types = NULL;
1745 // Online
1746 type = purple_status_type_new_with_attrs(
1747 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1748 // Translators: noun
1749 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1750 NULL);
1751 types = g_list_append(types, type);
1753 // Busy
1754 type = purple_status_type_new_with_attrs(
1755 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1756 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1757 NULL);
1758 types = g_list_append(types, type);
1760 // Do Not Disturb (not user settable)
1761 type = purple_status_type_new_with_attrs(
1762 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1763 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1764 NULL);
1765 types = g_list_append(types, type);
1767 // Be Right Back
1768 type = purple_status_type_new_with_attrs(
1769 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1770 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1771 NULL);
1772 types = g_list_append(types, type);
1774 // Away
1775 type = purple_status_type_new_with_attrs(
1776 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1777 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1778 NULL);
1779 types = g_list_append(types, type);
1781 //On The Phone
1782 type = purple_status_type_new_with_attrs(
1783 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1784 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1785 NULL);
1786 types = g_list_append(types, type);
1788 //Out To Lunch
1789 type = purple_status_type_new_with_attrs(
1790 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1791 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1792 NULL);
1793 types = g_list_append(types, type);
1795 //Appear Offline
1796 type = purple_status_type_new_full(
1797 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1798 types = g_list_append(types, type);
1800 // Offline
1801 type = purple_status_type_new_full(
1802 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1803 types = g_list_append(types, type);
1805 return types;
1809 * A callback for g_hash_table_foreach
1811 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1813 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1814 int time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1815 int timeout = (time_range * rand()) / RAND_MAX; /* random period within the range */
1816 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, buddy->name);
1820 * Removes entries from purple buddy list
1821 * that does not correspond ones in the roaming contact list.
1823 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1824 GSList *buddies = purple_find_buddies(sip->account, NULL);
1825 GSList *entry = buddies;
1826 struct sipe_buddy *buddy;
1827 PurpleBuddy *b;
1828 PurpleGroup *g;
1830 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1831 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1832 while (entry) {
1833 b = entry->data;
1834 g = purple_buddy_get_group(b);
1835 buddy = g_hash_table_lookup(sip->buddies, b->name);
1836 if(buddy) {
1837 gboolean in_sipe_groups = FALSE;
1838 GSList *entry2 = buddy->groups;
1839 while (entry2) {
1840 struct sipe_group *group = entry2->data;
1841 if (!strcmp(group->name, g->name)) {
1842 in_sipe_groups = TRUE;
1843 break;
1845 entry2 = entry2->next;
1847 if(!in_sipe_groups) {
1848 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1849 purple_blist_remove_buddy(b);
1851 } else {
1852 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1853 purple_blist_remove_buddy(b);
1855 entry = entry->next;
1859 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1861 int len = msg->bodylen;
1863 gchar *tmp = sipmsg_find_header(msg, "Event");
1864 xmlnode *item;
1865 xmlnode *isc;
1866 const gchar *contacts_delta;
1867 xmlnode *group_node;
1868 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1869 return FALSE;
1872 /* Convert the contact from XML to Purple Buddies */
1873 isc = xmlnode_from_str(msg->body, len);
1874 if (!isc) {
1875 return FALSE;
1878 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1879 if (contacts_delta) {
1880 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1883 if (!strcmp(isc->name, "contactList")) {
1885 /* Parse groups */
1886 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1887 struct sipe_group * group = g_new0(struct sipe_group, 1);
1888 const char *name = xmlnode_get_attrib(group_node, "name");
1890 if (!strncmp(name, "~", 1)) {
1891 name = _("Other Contacts");
1893 group->name = g_strdup(name);
1894 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1896 sipe_group_add(sip, group);
1899 // Make sure we have at least one group
1900 if (g_slist_length(sip->groups) == 0) {
1901 struct sipe_group * group = g_new0(struct sipe_group, 1);
1902 PurpleGroup *purple_group;
1903 group->name = g_strdup(_("Other Contacts"));
1904 group->id = 1;
1905 purple_group = purple_group_new(group->name);
1906 purple_blist_add_group(purple_group, NULL);
1907 sip->groups = g_slist_append(sip->groups, group);
1910 /* Parse contacts */
1911 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1912 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1913 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1914 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1915 gchar * buddy_name = g_strdup_printf("sip:%s", uri);
1916 gchar **item_groups;
1917 struct sipe_group *group = NULL;
1918 struct sipe_buddy *buddy = NULL;
1919 int i = 0;
1921 // assign to group Other Contacts if nothing else received
1922 if(!groups || !strcmp("", groups) ) {
1923 group = sipe_group_find_by_name(sip, _("Other Contacts"));
1924 groups = group ? g_strdup_printf("%d", group->id) : "1";
1927 item_groups = g_strsplit(groups, " ", 0);
1929 while (item_groups[i]) {
1930 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1932 // If couldn't find the right group for this contact, just put them in the first group we have
1933 if (group == NULL && g_slist_length(sip->groups) > 0) {
1934 group = sip->groups->data;
1937 if (group != NULL) {
1938 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1939 if (!b){
1940 b = purple_buddy_new(sip->account, buddy_name, uri);
1941 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1944 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1945 if (name != NULL && strlen(name) != 0) {
1946 purple_blist_alias_buddy(b, name);
1950 if (!buddy) {
1951 buddy = g_new0(struct sipe_buddy, 1);
1952 buddy->name = g_strdup(b->name);
1953 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1956 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1958 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
1959 } else {
1960 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1961 name);
1964 i++;
1965 } // while, contact groups
1966 g_strfreev(item_groups);
1967 g_free(groups);
1968 g_free(name);
1969 g_free(buddy_name);
1970 g_free(uri);
1972 } // for, contacts
1974 sipe_cleanup_local_blist(sip);
1976 xmlnode_free(isc);
1978 //subscribe to buddies
1979 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1980 if(sip->batched_support){
1981 sipe_subscribe_presence_batched(sip, NULL);
1983 else{
1984 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
1986 sip->subscribed_buddies = TRUE;
1989 return 0;
1993 * Subscribe roaming contacts
1995 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
1997 gchar *to = g_strdup_printf("sip:%s", sip->username);
1998 gchar *tmp = get_contact(sip);
1999 gchar *hdr = g_strdup_printf(
2000 "Event: vnd-microsoft-roaming-contacts\r\n"
2001 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2002 "Supported: com.microsoft.autoextend\r\n"
2003 "Supported: ms-benotify\r\n"
2004 "Proxy-Require: ms-benotify\r\n"
2005 "Supported: ms-piggyback-first-notify\r\n"
2006 "Contact: %s\r\n", tmp);
2007 g_free(tmp);
2009 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2010 g_free(to);
2011 g_free(hdr);
2014 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, void *unused)
2016 gchar *to = g_strdup_printf("sip:%s", sip->username);
2017 gchar *tmp = get_contact(sip);
2018 gchar *hdr = g_strdup_printf(
2019 "Event: presence.wpending\r\n"
2020 "Accept: text/xml+msrtc.wpending\r\n"
2021 "Supported: com.microsoft.autoextend\r\n"
2022 "Supported: ms-benotify\r\n"
2023 "Proxy-Require: ms-benotify\r\n"
2024 "Supported: ms-piggyback-first-notify\r\n"
2025 "Contact: %s\r\n", tmp);
2026 g_free(tmp);
2028 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2029 g_free(to);
2030 g_free(hdr);
2034 * Fires on deregistration event initiated by server.
2035 * [MS-SIPREGE] SIP extension.
2038 // 2007 Example
2040 // Content-Type: text/registration-event
2041 // subscription-state: terminated;expires=0
2042 // ms-diagnostics-public: 4141;reason="User disabled"
2044 // deregistered;event=rejected
2046 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2048 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2049 gchar *event = NULL;
2050 gchar *reason = NULL;
2051 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2053 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2054 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2056 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2057 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2058 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2059 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2060 } else {
2061 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2062 return;
2065 if (warning != NULL) {
2066 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2067 } else { // for LCS2005
2068 int error_id = 0;
2069 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2070 error_id = 4140; // [MS-SIPREGE]
2071 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2072 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2073 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2074 error_id = 4141;
2075 reason = g_strdup(_("User disabled")); // [MS-OCER]
2076 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2077 error_id = 4142;
2078 reason = g_strdup(_("User moved")); // [MS-OCER]
2081 g_free(event);
2082 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2083 g_free(reason);
2085 sip->gc->wants_to_die = TRUE;
2086 purple_connection_error(sip->gc, warning);
2087 g_free(warning);
2091 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2093 const gchar *contacts_delta;
2094 xmlnode *xml;
2096 xml = xmlnode_from_str(msg->body, msg->bodylen);
2097 if (!xml)
2099 return;
2102 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2103 if (contacts_delta)
2105 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2108 xmlnode_free(xml);
2111 static void
2112 free_container(struct sipe_container *container)
2114 GSList *entry;
2116 if (!container) return;
2118 entry = container->members;
2119 while (entry) {
2120 g_free(entry->data);
2121 entry = g_slist_remove(entry, entry->data);
2123 g_free(container);
2127 * Finds locally stored MS-PRES container member
2129 static struct sipe_container_member *
2130 sipe_find_container_member(struct sipe_container *container,
2131 const gchar *type,
2132 const gchar *value)
2134 struct sipe_container_member *member;
2135 GSList *entry;
2137 if (container == NULL || type == NULL) {
2138 return NULL;
2141 entry = container->members;
2142 while (entry) {
2143 member = entry->data;
2144 if (!g_strcasecmp(member->type, type)
2145 && ((!member->value && !value)
2146 || (value && member->value && !g_strcasecmp(member->value, value)))
2147 ) {
2148 return member;
2150 entry = entry->next;
2152 return NULL;
2156 * Finds locally stored MS-PRES container by id
2158 static struct sipe_container *
2159 sipe_find_container(struct sipe_account_data *sip,
2160 guint id)
2162 struct sipe_container *container;
2163 GSList *entry;
2165 if (sip == NULL) {
2166 return NULL;
2169 entry = sip->containers;
2170 while (entry) {
2171 container = entry->data;
2172 if (id == container->id) {
2173 return container;
2175 entry = entry->next;
2177 return NULL;
2181 * Access Levels
2182 * 32000 - Blocked
2183 * 400 - Personal
2184 * 300 - Team
2185 * 200 - Company
2186 * 100 - Public
2188 static int
2189 sipe_find_access_level(struct sipe_account_data *sip,
2190 const gchar *type,
2191 const gchar *value)
2193 guint containers[] = {32000, 400, 300, 200, 100};
2194 int i = 0;
2196 for (i = 0; i < 5; i++) {
2197 struct sipe_container_member *member;
2198 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2199 if (!container) continue;
2201 member = sipe_find_container_member(container, type, value);
2202 if (member) {
2203 return containers[i];
2207 return -1;
2210 static void
2211 sipe_send_set_container_members(struct sipe_account_data *sip,
2212 guint container_id,
2213 guint container_version,
2214 const gchar* action,
2215 const gchar* type,
2216 const gchar* value)
2218 gchar *self = g_strdup_printf("sip:%s", sip->username);
2219 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2220 gchar *contact;
2221 gchar *hdr;
2222 gchar *body = g_strdup_printf(
2223 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2224 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2225 "</setContainerMembers>",
2226 container_id,
2227 container_version,
2228 action,
2229 type,
2230 value_str);
2231 g_free(value_str);
2233 contact = get_contact(sip);
2234 hdr = g_strdup_printf("Contact: %s\r\n"
2235 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2236 g_free(contact);
2238 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2240 g_free(hdr);
2241 g_free(body);
2242 g_free(self);
2247 * When we receive some self (BE) NOTIFY with a new subscriber
2248 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2251 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
2253 gchar *contact;
2254 gchar *to;
2255 xmlnode *xml;
2256 xmlnode *node;
2257 xmlnode *node2;
2258 char *display_name = NULL;
2259 PurpleBuddy *pbuddy;
2260 const char *alias;
2261 char *uri_alias;
2262 char *uri_user;
2264 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2266 xml = xmlnode_from_str(msg->body, msg->bodylen);
2267 if (!xml) return;
2269 contact = get_contact(sip);
2270 to = g_strdup_printf("sip:%s", sip->username);
2272 /* containers */
2273 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
2274 guint id = atoi(xmlnode_get_attrib(node, "id"));
2275 struct sipe_container *container = sipe_find_container(sip, id);
2277 if (container) {
2278 sip->containers = g_slist_remove(sip->containers, container);
2279 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
2280 free_container(container);
2282 container = g_new0(struct sipe_container, 1);
2283 container->id = id;
2284 container->version = atoi(xmlnode_get_attrib(node, "version"));
2285 sip->containers = g_slist_append(sip->containers, container);
2286 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
2288 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
2289 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2290 member->type = xmlnode_get_attrib(node2, "type");
2291 member->value = xmlnode_get_attrib(node2, "value");
2292 container->members = g_slist_append(container->members, member);
2293 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
2294 member->type, member->value ? member->value : "");
2298 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
2299 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
2300 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
2301 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
2302 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
2303 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
2304 /* initial set-up to let counterparties see your status */
2305 if (sameEnterpriseAL < 0) {
2306 struct sipe_container *container = sipe_find_container(sip, 200);
2307 guint version = container ? container->version : 0;
2308 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
2310 if (federatedAL < 0) {
2311 struct sipe_container *container = sipe_find_container(sip, 100);
2312 guint version = container ? container->version : 0;
2313 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
2315 sip->access_level_set = TRUE;
2318 /* subscribers */
2319 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2320 const char *user;
2321 const char *acknowledged;
2322 gchar *hdr;
2323 gchar *body;
2325 user = xmlnode_get_attrib(node, "user");
2326 if (!user) continue;
2327 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2328 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2329 uri_user = g_strdup_printf("sip:%s", user);
2330 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2331 if(pbuddy){
2332 alias = purple_buddy_get_local_alias(pbuddy);
2333 uri_alias = g_strdup_printf("sip:%s", alias);
2334 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2335 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2336 purple_blist_alias_buddy(pbuddy, display_name);
2338 g_free(uri_alias);
2341 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2342 if(!g_ascii_strcasecmp(acknowledged,"false")){
2343 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2344 if (!purple_find_buddy(sip->account, uri_user)) {
2345 purple_account_request_add(sip->account, uri_user, _("you"), display_name, NULL);
2348 hdr = g_strdup_printf(
2349 "Contact: %s\r\n"
2350 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2352 body = g_strdup_printf(
2353 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2354 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2355 "</setSubscribers>", user);
2357 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2358 g_free(body);
2359 g_free(hdr);
2361 g_free(display_name);
2362 g_free(uri_user);
2365 g_free(to);
2366 g_free(contact);
2367 xmlnode_free(xml);
2370 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2372 gchar *to = g_strdup_printf("sip:%s", sip->username);
2373 gchar *tmp = get_contact(sip);
2374 gchar *hdr = g_strdup_printf(
2375 "Event: vnd-microsoft-roaming-ACL\r\n"
2376 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2377 "Supported: com.microsoft.autoextend\r\n"
2378 "Supported: ms-benotify\r\n"
2379 "Proxy-Require: ms-benotify\r\n"
2380 "Supported: ms-piggyback-first-notify\r\n"
2381 "Contact: %s\r\n", tmp);
2382 g_free(tmp);
2384 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2385 g_free(to);
2386 g_free(hdr);
2390 * To request for presence information about the user, access level settings that have already been configured by the user
2391 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2392 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2395 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2397 gchar *to = g_strdup_printf("sip:%s", sip->username);
2398 gchar *tmp = get_contact(sip);
2399 gchar *hdr = g_strdup_printf(
2400 "Event: vnd-microsoft-roaming-self\r\n"
2401 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2402 "Supported: ms-benotify\r\n"
2403 "Proxy-Require: ms-benotify\r\n"
2404 "Supported: ms-piggyback-first-notify\r\n"
2405 "Contact: %s\r\n"
2406 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2408 gchar *body=g_strdup(
2409 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2410 "<roaming type=\"categories\"/>"
2411 "<roaming type=\"containers\"/>"
2412 "<roaming type=\"subscribers\"/></roamingList>");
2414 g_free(tmp);
2415 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2416 g_free(body);
2417 g_free(to);
2418 g_free(hdr);
2422 * For 2005 version
2424 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2426 gchar *to = g_strdup_printf("sip:%s", sip->username);
2427 gchar *tmp = get_contact(sip);
2428 gchar *hdr = g_strdup_printf(
2429 "Event: vnd-microsoft-provisioning\r\n"
2430 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2431 "Supported: com.microsoft.autoextend\r\n"
2432 "Supported: ms-benotify\r\n"
2433 "Proxy-Require: ms-benotify\r\n"
2434 "Supported: ms-piggyback-first-notify\r\n"
2435 "Expires: 0\r\n"
2436 "Contact: %s\r\n", tmp);
2438 g_free(tmp);
2439 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2440 g_free(to);
2441 g_free(hdr);
2444 /** Subscription for provisioning information to help with initial
2445 * configuration. This subscription is a one-time query (denoted by the Expires header,
2446 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2447 * configuration, meeting policies, and policy settings that Communicator must enforce.
2448 * TODO: for what we need this information.
2451 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2453 gchar *to = g_strdup_printf("sip:%s", sip->username);
2454 gchar *tmp = get_contact(sip);
2455 gchar *hdr = g_strdup_printf(
2456 "Event: vnd-microsoft-provisioning-v2\r\n"
2457 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2458 "Supported: com.microsoft.autoextend\r\n"
2459 "Supported: ms-benotify\r\n"
2460 "Proxy-Require: ms-benotify\r\n"
2461 "Supported: ms-piggyback-first-notify\r\n"
2462 "Expires: 0\r\n"
2463 "Contact: %s\r\n"
2464 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2465 gchar *body = g_strdup(
2466 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2467 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2468 "<provisioningGroup name=\"ucPolicy\"/>"
2469 "</provisioningGroupList>");
2471 g_free(tmp);
2472 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2473 g_free(body);
2474 g_free(to);
2475 g_free(hdr);
2478 /* IM Session (INVITE and MESSAGE methods) */
2480 struct sip_dialog *
2481 get_dialog (struct sip_im_session *session,
2482 const gchar *who)
2484 struct sip_dialog *dialog;
2485 GSList *entry;
2486 if (session == NULL || who == NULL) {
2487 return NULL;
2490 entry = session->dialogs;
2491 while (entry) {
2492 dialog = entry->data;
2493 if (dialog->with && !strcmp(who, dialog->with)) {
2494 return dialog;
2496 entry = entry->next;
2498 return NULL;
2501 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2502 static gchar *
2503 get_end_points (struct sipe_account_data *sip,
2504 struct sip_im_session *session)
2506 gchar *tmp = NULL;
2507 gchar *res = g_strdup_printf("<sip:%s>", sip->username);
2508 struct sip_dialog *dialog;
2509 GSList *entry;
2510 if (session == NULL) {
2511 return NULL;
2514 entry = session->dialogs;
2515 while (entry) {
2516 dialog = entry->data;
2518 tmp = res;
2519 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2520 g_free(tmp);
2522 if (dialog->theirepid) {
2523 tmp = res;
2524 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2525 g_free(tmp);
2528 entry = entry->next;
2530 return res;
2533 static struct sip_im_session *
2534 find_chat_session_by_id (struct sipe_account_data *sip,
2535 int id)
2537 struct sip_im_session *session;
2538 GSList *entry;
2539 if (sip == NULL) {
2540 return NULL;
2543 entry = sip->im_sessions;
2544 while (entry) {
2545 session = entry->data;
2546 if (id == session->chat_id) {
2547 return session;
2549 entry = entry->next;
2551 return NULL;
2554 static struct sip_im_session *
2555 find_chat_session_by_name (struct sipe_account_data *sip,
2556 const char *chat_name)
2558 struct sip_im_session *session;
2559 GSList *entry;
2560 if (sip == NULL || chat_name == NULL) {
2561 return NULL;
2564 entry = sip->im_sessions;
2565 while (entry) {
2566 session = entry->data;
2567 if (session->chat_name && !g_strcasecmp(chat_name, session->chat_name)) {
2568 return session;
2570 entry = entry->next;
2572 return NULL;
2575 static struct sip_im_session *
2576 find_chat_session (struct sipe_account_data *sip,
2577 const char *callid)
2579 struct sip_im_session *session;
2580 GSList *entry;
2581 if (sip == NULL || callid == NULL) {
2582 return NULL;
2585 entry = sip->im_sessions;
2586 while (entry) {
2587 session = entry->data;
2588 if (session->callid && !g_strcasecmp(callid, session->callid)) {
2589 return session;
2591 entry = entry->next;
2593 return NULL;
2596 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2598 struct sip_im_session *session;
2599 GSList *entry;
2600 if (sip == NULL || who == NULL) {
2601 return NULL;
2604 entry = sip->im_sessions;
2605 while (entry) {
2606 session = entry->data;
2607 if (session->with && !strcmp(who, session->with)) {
2608 return session;
2610 entry = entry->next;
2612 return NULL;
2615 struct sip_im_session *
2616 create_chat_session (struct sipe_account_data *sip)
2618 struct sip_im_session *session = g_new0(struct sip_im_session, 1);
2619 session->callid = gencallid();
2620 session->is_multiparty = TRUE;
2621 session->chat_id = rand();
2622 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2623 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2624 return session;
2627 static struct sip_im_session * find_or_create_chat_session (struct sipe_account_data *sip, const char *callid)
2629 struct sip_im_session *session = find_chat_session(sip, callid);
2630 if (!session) {
2631 session = create_chat_session(sip);
2632 session->callid = g_strdup(callid);
2634 return session;
2637 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2639 struct sip_im_session *session = find_im_session(sip, who);
2640 if (!session) {
2641 session = g_new0(struct sip_im_session, 1);
2642 session->is_multiparty = FALSE;
2643 session->with = g_strdup(who);
2644 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2645 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2647 return session;
2650 void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2652 GSList *entry;
2654 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2656 entry = session->dialogs;
2657 while (entry) {
2658 free_dialog(entry->data);
2659 entry = g_slist_remove(entry, entry->data);
2662 entry = session->outgoing_message_queue;
2663 while (entry) {
2664 g_free(entry->data);
2665 entry = g_slist_remove(entry, entry->data);
2668 entry = session->pending_invite_queue;
2669 while (entry) {
2670 g_free(entry->data);
2671 entry = g_slist_remove(entry, entry->data);
2674 g_hash_table_destroy(session->unconfirmed_messages);
2676 g_free(session->with);
2677 g_free(session->chat_name);
2678 g_free(session->callid);
2679 g_free(session->roster_manager);
2680 g_free(session->focus_uri);
2681 g_free(session->im_mcu_uri);
2682 g_free(session);
2685 static gboolean
2686 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2688 gboolean ret = TRUE;
2690 if (msg->response != 200) {
2691 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2692 return FALSE;
2695 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2697 return ret;
2701 * Asks UA/proxy about its capabilities.
2703 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2705 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2706 gchar *contact = get_contact(sip);
2707 gchar *request;
2708 request = g_strdup_printf(
2709 "Accept: application/sdp\r\n"
2710 "Contact: %s\r\n", contact);
2712 g_free(contact);
2714 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2716 g_free(to);
2717 g_free(request);
2720 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2722 char *msg, *msg_tmp;
2723 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2724 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2725 g_free(msg_tmp);
2726 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2727 "possibly because one or more persons are offline:\n%s") ,
2728 msg ? msg : "");
2729 purple_conv_present_error(with, sip->account, msg_tmp);
2730 g_free(msg);
2731 g_free(msg_tmp);
2734 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2736 static gboolean
2737 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2739 gboolean ret = TRUE;
2740 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2741 struct sip_im_session * session = find_im_session(sip, with);
2742 struct sip_dialog *dialog;
2743 gchar *cseq;
2744 char *key;
2745 gchar *message;
2747 if (!session) {
2748 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2749 g_free(with);
2750 return FALSE;
2753 dialog = get_dialog(session, with);
2755 if (!dialog) {
2756 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2757 g_free(with);
2758 return FALSE;
2761 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2762 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2763 g_free(cseq);
2764 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2766 if (msg->response != 200) {
2767 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2769 sipe_present_message_undelivered_err(with, sip, message);
2770 im_session_destroy(sip, session);
2771 ret = FALSE;
2772 } else {
2773 g_hash_table_remove(session->unconfirmed_messages, key);
2774 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2775 key, g_hash_table_size(session->unconfirmed_messages));
2778 g_free(key);
2779 g_free(with);
2781 if (ret) sipe_im_process_queue(sip, session);
2782 return ret;
2785 static gboolean
2786 sipe_is_election_finished(struct sipe_account_data *sip,
2787 struct sip_im_session *session);
2789 static void
2790 sipe_election_result(struct sipe_account_data *sip,
2791 void *sess);
2793 static gboolean
2794 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2796 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2797 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2798 struct sip_dialog *dialog;
2799 struct sip_im_session *session;
2801 session = find_chat_session(sip, callid);
2802 if (!session) {
2803 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2804 return FALSE;
2807 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2808 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2809 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2810 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2812 if (xn_request_rm_response) {
2813 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2814 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2816 dialog = get_dialog(session, with);
2817 if (!dialog) {
2818 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2819 return FALSE;
2822 if (allow && !g_strcasecmp(allow, "true")) {
2823 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2824 dialog->election_vote = 1;
2825 } else if (allow && !g_strcasecmp(allow, "false")) {
2826 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2827 dialog->election_vote = -1;
2830 if (sipe_is_election_finished(sip, session)) {
2831 sipe_election_result(sip, session);
2834 } else if (xn_set_rm_response) {
2837 xmlnode_free(xn_action);
2841 return TRUE;
2844 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2846 gchar *hdr;
2847 gchar *tmp;
2848 char *msgformat;
2849 char *msgtext;
2850 gchar *msgr_value;
2851 gchar *msgr;
2853 sipe_parse_html(msg, &msgformat, &msgtext);
2854 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2856 msgr_value = sipmsg_get_msgr_string(msgformat);
2857 g_free(msgformat);
2858 if (msgr_value) {
2859 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2860 g_free(msgr_value);
2861 } else {
2862 msgr = g_strdup("");
2865 tmp = get_contact(sip);
2866 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2867 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2868 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2869 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2870 g_free(tmp);
2871 g_free(msgr);
2873 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2874 g_free(msgtext);
2875 g_free(hdr);
2879 static void
2880 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2882 GSList *entry2 = session->outgoing_message_queue;
2883 while (entry2) {
2884 char *queued_msg = entry2->data;
2885 struct sip_dialog *dialog;
2886 GSList *entry = session->dialogs;
2888 if (session->is_multiparty) {
2889 gchar *who = g_strdup_printf("sip:%s", sip->username);
2890 serv_got_chat_in(sip->gc, session->chat_id, who,
2891 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2892 g_free(who);
2895 while (entry) {
2896 char *key;
2898 dialog = entry->data;
2899 entry = entry->next;
2900 if (dialog->outgoing_invite) continue; //do not send messages as INVITE is not responded.
2902 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2903 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2904 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2905 key, g_hash_table_size(session->unconfirmed_messages));
2906 g_free(key);
2907 sipe_send_message(sip, dialog, queued_msg);
2910 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2911 g_free(queued_msg);
2915 static void
2916 sipe_refer_notify(struct sipe_account_data *sip,
2917 struct sip_im_session *session,
2918 const gchar *who,
2919 int status,
2920 const gchar *desc)
2922 gchar *hdr;
2923 gchar *body;
2924 struct sip_dialog *dialog = get_dialog(session, who);
2926 hdr = g_strdup_printf(
2927 "Event: refer\r\n"
2928 "Subscription-State: %s\r\n"
2929 "Content-Type: message/sipfrag\r\n",
2930 status >= 200 ? "terminated" : "active");
2932 body = g_strdup_printf(
2933 "SIP/2.0 %d %s\r\n",
2934 status, desc);
2936 dialog->outgoing_invite = send_sip_request(sip->gc, "NOTIFY",
2937 who, who, hdr, body, dialog, NULL);
2939 g_free(hdr);
2940 g_free(body);
2943 static gboolean
2944 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2946 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2947 struct sip_im_session *session;
2948 struct sip_dialog *dialog;
2949 char *cseq;
2950 char *key;
2951 gchar *message;
2952 struct sipmsg *request_msg = trans->msg;
2954 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2955 gchar *referred_by;
2957 session = find_chat_session(sip, callid);
2958 if (!session) {
2959 session = find_im_session(sip, with);
2962 if (!session) {
2963 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2964 g_free(with);
2965 return FALSE;
2968 dialog = get_dialog(session, with);
2969 if (!dialog) {
2970 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2971 g_free(with);
2972 return FALSE;
2975 sipe_parse_dialog(msg, dialog, TRUE);
2977 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2978 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2979 g_free(cseq);
2980 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2982 if (msg->response != 200) {
2983 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2985 sipe_present_message_undelivered_err(with, sip, message);
2986 im_session_destroy(sip, session);
2987 g_free(key);
2988 g_free(with);
2989 return FALSE;
2992 dialog->cseq = 0;
2993 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
2994 dialog->outgoing_invite = NULL;
2995 dialog->is_established = TRUE;
2997 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
2998 if (referred_by) {
2999 sipe_refer_notify(sip, session, referred_by, 200, "OK");
3000 g_free(referred_by);
3003 /* add user to chat if it is a multiparty session */
3004 if (session->is_multiparty) {
3005 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3006 with, NULL,
3007 PURPLE_CBFLAGS_NONE, TRUE);
3010 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
3011 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3012 if (session->outgoing_message_queue) {
3013 char *queued_msg = session->outgoing_message_queue->data;
3014 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
3015 g_free(queued_msg);
3019 sipe_im_process_queue(sip, session);
3021 g_hash_table_remove(session->unconfirmed_messages, key);
3022 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3023 key, g_hash_table_size(session->unconfirmed_messages));
3025 g_free(key);
3026 g_free(with);
3027 return TRUE;
3031 void
3032 sipe_invite(struct sipe_account_data *sip,
3033 struct sip_im_session *session,
3034 const gchar *who,
3035 const gchar *msg_body,
3036 const gchar *referred_by,
3037 const gboolean is_triggered)
3039 gchar *hdr;
3040 gchar *to;
3041 gchar *contact;
3042 gchar *body;
3043 gchar *self;
3044 char *ms_text_format = g_strdup("");
3045 gchar *roster_manager;
3046 gchar *end_points;
3047 gchar *referred_by_str;
3048 struct sip_dialog *dialog = get_dialog(session, who);
3050 if (dialog && dialog->is_established) {
3051 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
3052 return;
3055 if (!dialog) {
3056 dialog = g_new0(struct sip_dialog, 1);
3057 session->dialogs = g_slist_append(session->dialogs, dialog);
3059 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3060 dialog->with = g_strdup(who);
3063 if (!(dialog->ourtag)) {
3064 dialog->ourtag = gentag();
3068 if (strstr(who, "sip:")) {
3069 to = g_strdup(who);
3070 } else {
3071 to = g_strdup_printf("sip:%s", who);
3074 if (msg_body) {
3075 char *msgformat;
3076 char *msgtext;
3077 char *base64_msg;
3078 gchar *msgr_value;
3079 gchar *msgr;
3080 char *key;
3082 sipe_parse_html(msg_body, &msgformat, &msgtext);
3083 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
3085 msgr_value = sipmsg_get_msgr_string(msgformat);
3086 g_free(msgformat);
3087 msgr = "";
3088 if (msgr_value) {
3089 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3090 g_free(msgr_value);
3093 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3094 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3095 g_free(msgtext);
3096 g_free(msgr);
3097 g_free(base64_msg);
3099 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3100 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3101 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3102 key, g_hash_table_size(session->unconfirmed_messages));
3103 g_free(key);
3106 contact = get_contact(sip);
3107 end_points = get_end_points(sip, session);
3108 self = g_strdup_printf("sip:%s", sip->username);
3109 roster_manager = g_strdup_printf(
3110 "Roster-Manager: %s\r\n"
3111 "EndPoints: %s\r\n",
3112 self,
3113 end_points);
3114 referred_by_str = referred_by ?
3115 g_strdup_printf(
3116 "Referred-By: %s\r\n",
3117 referred_by)
3118 : g_strdup("");
3119 hdr = g_strdup_printf(
3120 "Supported: ms-sender\r\n"
3121 "%s"
3122 "%s"
3123 "%s"
3124 "%s"
3125 "Contact: %s\r\n%s"
3126 "Content-Type: application/sdp\r\n",
3127 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3128 referred_by_str,
3129 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3130 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3131 contact,
3132 ms_text_format);
3133 g_free(ms_text_format);
3134 g_free(self);
3136 body = g_strdup_printf(
3137 "v=0\r\n"
3138 "o=- 0 0 IN IP4 %s\r\n"
3139 "s=session\r\n"
3140 "c=IN IP4 %s\r\n"
3141 "t=0 0\r\n"
3142 "m=message %d sip null\r\n"
3143 "a=accept-types:text/plain text/html image/gif "
3144 "multipart/related application/im-iscomposing+xml\r\n",
3145 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3147 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3148 to, to, hdr, body, dialog, process_invite_response);
3150 g_free(to);
3151 g_free(roster_manager);
3152 g_free(end_points);
3153 g_free(referred_by_str);
3154 g_free(body);
3155 g_free(hdr);
3156 g_free(contact);
3159 static void
3160 sipe_refer(struct sipe_account_data *sip,
3161 struct sip_im_session *session,
3162 const gchar *who)
3164 gchar *hdr;
3165 gchar *contact;
3166 struct sip_dialog *dialog = get_dialog(session, session->roster_manager);
3168 contact = get_contact(sip);
3169 hdr = g_strdup_printf(
3170 "Contact: %s\r\n"
3171 "Refer-to: <%s>\r\n"
3172 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3173 "Require: com.microsoft.rtc-multiparty\r\n",
3174 contact,
3175 who,
3176 sip->username,
3177 dialog->ourtag ? ";tag=" : "",
3178 dialog->ourtag ? dialog->ourtag : "",
3179 get_epid(sip));
3181 send_sip_request(sip->gc, "REFER",
3182 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3184 g_free(hdr);
3185 g_free(contact);
3188 static void
3189 sipe_send_election_request_rm(struct sipe_account_data *sip,
3190 struct sip_im_session *session,
3191 const gchar *who,
3192 int bid)
3194 gchar *hdr;
3195 gchar *body;
3196 struct sip_dialog *dialog = get_dialog(session, who);
3198 hdr = "Content-Type: application/x-ms-mim\r\n";
3200 body = g_strdup_printf(
3201 "<?xml version=\"1.0\"?>\r\n"
3202 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3203 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3204 sip->username, bid);
3206 send_sip_request(sip->gc, "INFO",
3207 who, who, hdr, body, dialog, process_info_response);
3209 g_free(body);
3212 static void
3213 sipe_send_election_set_rm(struct sipe_account_data *sip,
3214 struct sip_im_session *session,
3215 const gchar *who)
3217 gchar *hdr;
3218 gchar *body;
3219 struct sip_dialog *dialog = get_dialog(session, who);
3221 hdr = "Content-Type: application/x-ms-mim\r\n";
3223 body = g_strdup_printf(
3224 "<?xml version=\"1.0\"?>\r\n"
3225 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3226 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3227 sip->username);
3229 send_sip_request(sip->gc, "INFO",
3230 who, who, hdr, body, dialog, process_info_response);
3232 g_free(body);
3235 static void
3236 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
3238 if (session) {
3239 struct sip_dialog *dialog;
3240 GSList *entry;
3241 entry = session->dialogs;
3242 while (entry) {
3243 dialog = entry->data;
3244 /* @TODO slow down BYE message sending rate */
3245 /* @see single subscription code */
3246 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3247 entry = entry->next;
3250 im_session_destroy(sip, session);
3254 static void
3255 sipe_convo_closed(PurpleConnection * gc, const char *who)
3257 struct sipe_account_data *sip = gc->proto_data;
3259 purple_debug_info("sipe", "conversation with %s closed\n", who);
3260 im_session_close(sip, find_im_session(sip, who));
3263 static void
3264 sipe_chat_leave (PurpleConnection *gc, int id)
3266 struct sipe_account_data *sip = gc->proto_data;
3267 struct sip_im_session * session = find_chat_session_by_id(sip, id);
3268 if (session->focus_uri) {
3269 conf_session_close(sip, session);
3271 im_session_close(sip, session);
3274 static void
3275 im_session_close_all (struct sipe_account_data *sip)
3277 GSList *entry = sip->im_sessions;
3278 while (entry) {
3279 im_session_close (sip, entry->data);
3280 entry = sip->im_sessions;
3284 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
3286 struct sipe_account_data *sip = gc->proto_data;
3287 struct sip_im_session *session;
3288 struct sip_dialog *dialog;
3290 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3292 session = find_or_create_im_session(sip, who);
3293 dialog = get_dialog(session, who);
3295 // Queue the message
3296 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3298 if (dialog && dialog->callid) {
3299 sipe_im_process_queue(sip, session);
3300 } else if (!dialog || !dialog->outgoing_invite) {
3301 // Need to send the INVITE to get the outgoing dialog setup
3302 sipe_invite(sip, session, who, what, NULL, FALSE);
3305 return 1;
3308 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags)
3310 struct sipe_account_data *sip = gc->proto_data;
3311 struct sip_im_session *session;
3313 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3315 session = find_chat_session_by_id(sip, id);
3317 // Queue the message
3318 if (session) {
3319 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3320 g_strdup(what));
3321 sipe_im_process_queue(sip, session);
3324 return 1;
3327 /* End IM Session (INVITE and MESSAGE methods) */
3329 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3331 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3332 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3333 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3335 struct sip_im_session *session = find_chat_session(sip, callid);
3336 if (!session) {
3337 session = find_im_session(sip, from);
3340 if (!session) {
3341 return;
3344 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3345 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3346 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3347 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3349 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
3351 if (xn_request_rm) {
3352 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3353 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3354 gchar *body = g_strdup_printf(
3355 "<?xml version=\"1.0\"?>\r\n"
3356 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3357 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3358 sip->username,
3359 session->bid < bid ? "true" : "false");
3360 send_sip_response(sip->gc, msg, 200, "OK", body);
3361 g_free(body);
3362 } else if (xn_set_rm) {
3363 gchar *body;
3364 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3365 g_free(session->roster_manager);
3366 session->roster_manager = g_strdup(rm);
3368 body = g_strdup_printf(
3369 "<?xml version=\"1.0\"?>\r\n"
3370 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3371 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3372 sip->username);
3373 send_sip_response(sip->gc, msg, 200, "OK", body);
3374 g_free(body);
3376 xmlnode_free(xn_action);
3378 } else {
3379 /* looks like purple lacks typing notification for chat */
3380 if (!session->is_multiparty && !session->focus_uri) {
3381 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3384 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3386 g_free(from);
3389 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3391 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3392 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3393 struct sip_im_session *session;
3394 struct sip_dialog *dialog;
3396 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3398 session = find_chat_session(sip, callid);
3399 if (!session) {
3400 session = find_im_session(sip, from);
3403 if (!session) {
3404 return;
3407 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3408 g_free(session->roster_manager);
3409 session->roster_manager = NULL;
3412 if (!session->is_multiparty) {
3413 // TODO Let the user know the other user left the conversation?
3414 im_session_destroy(sip, session);
3415 } else {
3416 dialog = get_dialog(session, from);
3417 session->dialogs = g_slist_remove(session->dialogs, dialog);
3418 free_dialog(dialog);
3420 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3422 if (!session->dialogs) {
3423 im_session_destroy(sip, session);
3427 g_free(from);
3430 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3432 gchar *self = g_strdup_printf("sip:%s", sip->username);
3433 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3434 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3435 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3436 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3437 struct sip_im_session *session;
3438 struct sip_dialog *dialog;
3440 session = find_chat_session(sip, callid);
3441 dialog = get_dialog(session, from);
3443 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3444 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3445 } else {
3446 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3448 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3451 g_free(self);
3452 g_free(from);
3453 g_free(refer_to);
3454 g_free(referred_by);
3457 static unsigned int
3458 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3460 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3461 struct sip_im_session *session;
3462 struct sip_dialog *dialog;
3464 if (state == PURPLE_NOT_TYPING)
3465 return 0;
3467 session = find_im_session(sip, who);
3468 dialog = get_dialog(session, who);
3470 if (session && dialog) {
3471 send_sip_request(gc, "INFO", who, who,
3472 "Content-Type: application/xml\r\n",
3473 SIPE_SEND_TYPING, dialog, NULL);
3475 return SIPE_TYPING_SEND_TIMEOUT;
3478 static gboolean resend_timeout(struct sipe_account_data *sip)
3480 GSList *tmp = sip->transactions;
3481 time_t currtime = time(NULL);
3482 while (tmp) {
3483 struct transaction *trans = tmp->data;
3484 tmp = tmp->next;
3485 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3486 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3487 /* TODO 408 */
3488 } else {
3489 if ((currtime - trans->time > 2) && trans->retries == 0) {
3490 trans->retries++;
3491 sendout_sipmsg(sip, trans->msg);
3495 return TRUE;
3498 static void do_reauthenticate_cb(struct sipe_account_data *sip, void *unused)
3500 /* register again when security token expires */
3501 /* we have to start a new authentication as the security token
3502 * is almost expired by sending a not signed REGISTER message */
3503 purple_debug_info("sipe", "do a full reauthentication\n");
3504 sipe_auth_free(&sip->registrar);
3505 sipe_auth_free(&sip->proxy);
3506 sip->registerstatus = 0;
3507 do_register(sip);
3508 sip->reauthenticate_set = FALSE;
3511 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3513 gchar *from;
3514 gchar *contenttype;
3515 gboolean found = FALSE;
3517 from = parse_from(sipmsg_find_header(msg, "From"));
3519 if (!from) return;
3521 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3523 contenttype = sipmsg_find_header(msg, "Content-Type");
3524 if (!strncmp(contenttype, "text/plain", 10)
3525 || !strncmp(contenttype, "text/html", 9)
3526 || !strncmp(contenttype, "multipart/related", 21))
3528 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3529 gchar *html = get_html_message(contenttype, msg->body);
3531 struct sip_im_session *session = find_chat_session(sip, callid);
3532 if (!session) {
3533 session = find_im_session(sip, from);
3536 if (session && session->focus_uri) { /* a conference */
3537 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
3538 gchar *sender = parse_from(tmp);
3539 g_free(tmp);
3540 serv_got_chat_in(sip->gc, session->chat_id, sender,
3541 PURPLE_MESSAGE_RECV, html, time(NULL));
3542 g_free(sender);
3543 } else if (session && session->is_multiparty) { /* a multiparty chat */
3544 serv_got_chat_in(sip->gc, session->chat_id, from,
3545 PURPLE_MESSAGE_RECV, html, time(NULL));
3546 } else {
3547 serv_got_im(sip->gc, from, html, 0, time(NULL));
3549 g_free(html);
3550 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3551 found = TRUE;
3553 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3554 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3555 xmlnode *state;
3556 gchar *statedata;
3558 if (!isc) {
3559 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3560 return;
3563 state = xmlnode_get_child(isc, "state");
3565 if (!state) {
3566 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3567 xmlnode_free(isc);
3568 return;
3571 statedata = xmlnode_get_data(state);
3572 if (statedata) {
3573 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3574 else serv_got_typing_stopped(sip->gc, from);
3576 g_free(statedata);
3578 xmlnode_free(isc);
3579 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3580 found = TRUE;
3582 if (!found) {
3583 purple_debug_info("sipe", "got unknown mime-type");
3584 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3586 g_free(from);
3589 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3591 gchar *ms_text_format;
3592 gchar *body;
3593 gchar *newTag = gentag();
3594 gchar *oldHeader;
3595 gchar *newHeader;
3596 gboolean is_multiparty = FALSE;
3597 gboolean is_triggered = FALSE;
3598 gboolean was_multiparty = TRUE;
3599 gboolean just_joined = FALSE;
3600 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3601 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3602 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3603 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
3604 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
3605 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
3606 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3607 GSList *end_points = NULL;
3608 struct sip_im_session *session;
3609 struct sip_dialog *dialog;
3611 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3613 /* Invitation to join conference */
3614 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
3615 process_incoming_invite_conf(sip, msg);
3616 return;
3619 /* Only accept text invitations */
3620 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3621 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3622 return;
3625 // TODO There *must* be a better way to clean up the To header to add a tag...
3626 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3627 oldHeader = sipmsg_find_header(msg, "To");
3628 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3629 sipmsg_remove_header_now(msg, "To");
3630 sipmsg_add_header_now(msg, "To", newHeader);
3631 g_free(newHeader);
3633 if (end_points_hdr) {
3634 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
3636 if (g_slist_length(end_points) > 2) {
3637 is_multiparty = TRUE;
3640 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
3641 is_triggered = TRUE;
3642 is_multiparty = TRUE;
3645 session = find_chat_session(sip, callid);
3646 /* Convert to multiparty */
3647 if (session && is_multiparty && !session->is_multiparty) {
3648 g_free(session->with);
3649 session->with = NULL;
3650 was_multiparty = FALSE;
3651 session->is_multiparty = TRUE;
3652 session->chat_id = rand();
3655 if (!session && is_multiparty) {
3656 session = find_or_create_chat_session(sip, callid);
3658 /* IM session */
3659 if (!session) {
3660 session = find_or_create_im_session(sip, from);
3663 if (!session->callid) {
3664 session->callid = g_strdup(callid);
3667 session->is_multiparty = is_multiparty;
3668 if (roster_manager) {
3669 session->roster_manager = g_strdup(roster_manager);
3672 if (is_multiparty && end_points) {
3673 GSList *entry = end_points;
3674 while (entry) {
3675 struct sipendpoint *end_point = entry->data;
3676 entry = entry->next;
3678 if (!g_strcasecmp(from, end_point->contact) ||
3679 !g_strcasecmp(to, end_point->contact))
3680 continue;
3682 dialog = get_dialog(session, end_point->contact);
3683 if (dialog) {
3684 g_free(dialog->theirepid);
3685 dialog->theirepid = end_point->epid;
3686 end_point->epid = NULL;
3687 } else {
3688 dialog = g_new0(struct sip_dialog, 1);
3689 session->dialogs = g_slist_append(session->dialogs, dialog);
3691 dialog->callid = g_strdup(session->callid);
3692 dialog->with = end_point->contact;
3693 end_point->contact = NULL;
3694 dialog->theirepid = end_point->epid;
3695 end_point->epid = NULL;
3697 just_joined = TRUE;
3699 /* send triggered INVITE */
3700 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
3705 if (end_points) {
3706 GSList *entry = end_points;
3707 while (entry) {
3708 struct sipendpoint *end_point = entry->data;
3709 entry = entry->next;
3710 g_free(end_point->contact);
3711 g_free(end_point->epid);
3712 g_free(end_point);
3714 g_slist_free(end_points);
3717 if (session) {
3718 dialog = get_dialog(session, from);
3719 if (dialog) {
3720 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3721 } else {
3722 dialog = g_new0(struct sip_dialog, 1);
3723 session->dialogs = g_slist_append(session->dialogs, dialog);
3725 dialog->callid = g_strdup(session->callid);
3726 dialog->with = g_strdup(from);
3727 sipe_parse_dialog(msg, dialog, FALSE);
3729 if (!dialog->ourtag) {
3730 dialog->ourtag = newTag;
3731 newTag = NULL;
3734 just_joined = TRUE;
3736 } else {
3737 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3739 g_free(newTag);
3741 if (is_multiparty && !session->conv) {
3742 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3743 gchar *self = g_strdup_printf("sip:%s", sip->username);
3744 /* create prpl chat */
3745 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3746 session->chat_name = g_strdup(chat_name);
3747 /* add self */
3748 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3749 self, NULL,
3750 PURPLE_CBFLAGS_NONE, FALSE);
3751 g_free(chat_name);
3752 g_free(self);
3755 if (is_multiparty && !was_multiparty) {
3756 /* add current IM counterparty to chat */
3757 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3758 ((struct sip_dialog *)session->dialogs->data)->with, NULL,
3759 PURPLE_CBFLAGS_NONE, FALSE);
3763 /* add inviting party */
3764 if (just_joined) {
3765 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3766 from, NULL,
3767 PURPLE_CBFLAGS_NONE, TRUE);
3770 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3771 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3772 if (ms_text_format) {
3773 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3775 gchar *html = get_html_message(ms_text_format, NULL);
3776 if (html) {
3777 if (is_multiparty) {
3778 serv_got_chat_in(sip->gc, session->chat_id, from,
3779 PURPLE_MESSAGE_RECV, html, time(NULL));
3780 } else {
3781 serv_got_im(sip->gc, from, html, 0, time(NULL));
3783 g_free(html);
3784 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3788 g_free(from);
3790 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3791 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3792 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3794 body = g_strdup_printf(
3795 "v=0\r\n"
3796 "o=- 0 0 IN IP4 %s\r\n"
3797 "s=session\r\n"
3798 "c=IN IP4 %s\r\n"
3799 "t=0 0\r\n"
3800 "m=message %d sip sip:%s\r\n"
3801 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml\r\n",
3802 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3803 sip->realport, sip->username);
3804 send_sip_response(sip->gc, msg, 200, "OK", body);
3805 g_free(body);
3808 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3810 gchar *body;
3812 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
3813 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3814 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3816 body = g_strdup_printf(
3817 "v=0\r\n"
3818 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3819 "s=session\r\n"
3820 "c=IN IP4 0.0.0.0\r\n"
3821 "t=0 0\r\n"
3822 "m=message %d sip sip:%s\r\n"
3823 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml\r\n",
3824 sip->realport, sip->username);
3825 send_sip_response(sip->gc, msg, 200, "OK", body);
3826 g_free(body);
3829 static void sipe_connection_cleanup(struct sipe_account_data *);
3830 static void create_connection(struct sipe_account_data *, gchar *, int);
3832 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3834 gchar *tmp;
3835 const gchar *expires_header;
3836 int expires, i;
3837 GSList *hdr = msg->headers;
3838 GSList *entry;
3839 struct siphdrelement *elem;
3841 expires_header = sipmsg_find_header(msg, "Expires");
3842 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3843 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3845 switch (msg->response) {
3846 case 200:
3847 if (expires == 0) {
3848 sip->registerstatus = 0;
3849 } else {
3850 gchar *contact_hdr = NULL;
3851 gchar *gruu = NULL;
3852 gchar *epid;
3853 gchar *uuid;
3854 gchar *timeout;
3856 if (!sip->reregister_set) {
3857 gchar *action_name = g_strdup_printf("<%s>", "registration");
3858 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3859 g_free(action_name);
3860 sip->reregister_set = TRUE;
3863 sip->registerstatus = 3;
3865 #ifdef USE_KERBEROS
3866 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3867 #endif
3868 tmp = sipmsg_find_auth_header(msg, "NTLM");
3869 #ifdef USE_KERBEROS
3870 } else {
3871 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3873 #endif
3874 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3875 fill_auth(sip, tmp, &sip->registrar);
3877 if (!sip->reauthenticate_set) {
3878 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3879 guint reauth_timeout;
3880 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3881 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3882 reauth_timeout = sip->registrar.expires - 300;
3883 } else {
3884 /* NTLM: we have to reauthenticate as our security token expires
3885 after eight hours (be five minutes early) */
3886 reauth_timeout = (8 * 3600) - 300;
3888 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3889 g_free(action_name);
3890 sip->reauthenticate_set = TRUE;
3893 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3895 epid = get_epid(sip);
3896 uuid = generateUUIDfromEPID(epid);
3897 g_free(epid);
3899 // There can be multiple Contact headers (one per location where the user is logged in) so
3900 // make sure to only get the one for this uuid
3901 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3902 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3903 if (valid_contact) {
3904 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3905 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3906 g_free(valid_contact);
3907 break;
3908 } else {
3909 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3912 g_free(uuid);
3914 g_free(sip->contact);
3915 if(gruu) {
3916 sip->contact = g_strdup_printf("<%s>", gruu);
3917 g_free(gruu);
3918 } else {
3919 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3920 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);
3922 sip->msrtc_event_categories = FALSE;
3923 sip->batched_support = FALSE;
3925 while(hdr)
3927 elem = hdr->data;
3928 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3929 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3930 sip->msrtc_event_categories = TRUE;
3931 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3933 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3934 sip->batched_support = TRUE;
3935 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3938 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3939 gchar **caps = g_strsplit(elem->value,",",0);
3940 i = 0;
3941 while (caps[i]) {
3942 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3943 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3944 i++;
3946 g_strfreev(caps);
3948 hdr = g_slist_next(hdr);
3951 if (!sip->subscribed) { //do it just once, not every re-register
3952 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3953 //sipe_options_request(sip, sip->sipdomain);
3955 entry = sip->allow_events;
3956 while (entry) {
3957 tmp = entry->data;
3958 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3959 sipe_subscribe_roaming_contacts(sip, msg);
3961 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3962 sipe_subscribe_roaming_acl(sip, msg);
3964 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3965 sipe_subscribe_roaming_self(sip, msg);
3967 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3968 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3969 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3970 sipe_subscribe_roaming_provisioning(sip, msg);
3972 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3973 sipe_subscribe_presence_wpending(sip, msg);
3975 entry = entry->next;
3977 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3978 sip->subscribed = TRUE;
3981 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3982 "timeout=", ";", NULL);
3983 if (timeout != NULL) {
3984 sscanf(timeout, "%u", &sip->keepalive_timeout);
3985 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3986 sip->keepalive_timeout);
3987 g_free(timeout);
3990 // Should we remove the transaction here?
3991 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3992 transactions_remove(sip, tc);
3994 break;
3995 case 301:
3997 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3999 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
4000 gchar **parts = g_strsplit(redirect + 4, ";", 0);
4001 gchar **tmp;
4002 gchar *hostname;
4003 int port = 0;
4004 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
4005 int i = 1;
4007 tmp = g_strsplit(parts[0], ":", 0);
4008 hostname = g_strdup(tmp[0]);
4009 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
4010 g_strfreev(tmp);
4012 while (parts[i]) {
4013 tmp = g_strsplit(parts[i], "=", 0);
4014 if (tmp[1]) {
4015 if (g_strcasecmp("transport", tmp[0]) == 0) {
4016 if (g_strcasecmp("tcp", tmp[1]) == 0) {
4017 transport = SIPE_TRANSPORT_TCP;
4018 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
4019 transport = SIPE_TRANSPORT_UDP;
4023 g_strfreev(tmp);
4024 i++;
4026 g_strfreev(parts);
4028 /* Close old connection */
4029 sipe_connection_cleanup(sip);
4031 /* Create new connection */
4032 sip->transport = transport;
4033 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4034 hostname, port, TRANSPORT_DESCRIPTOR);
4035 create_connection(sip, hostname, port);
4037 g_free(redirect);
4039 break;
4040 case 401:
4041 if (sip->registerstatus != 2) {
4042 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
4043 if (sip->registrar.retries > 3) {
4044 sip->gc->wants_to_die = TRUE;
4045 purple_connection_error(sip->gc, _("Wrong Password"));
4046 return TRUE;
4048 #ifdef USE_KERBEROS
4049 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4050 #endif
4051 tmp = sipmsg_find_auth_header(msg, "NTLM");
4052 #ifdef USE_KERBEROS
4053 } else {
4054 tmp = sipmsg_find_auth_header(msg, "Kerberos");
4056 #endif
4057 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
4058 fill_auth(sip, tmp, &sip->registrar);
4059 sip->registerstatus = 2;
4060 if (sip->account->disconnecting) {
4061 do_register_exp(sip, 0);
4062 } else {
4063 do_register(sip);
4066 break;
4067 case 403:
4069 gchar *warning = sipmsg_find_header(msg, "Warning");
4070 if (warning != NULL) {
4071 /* Example header:
4072 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4074 gchar **tmp = g_strsplit(warning, "\"", 0);
4075 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
4076 g_strfreev(tmp);
4077 } else {
4078 warning = g_strdup(_("You have been rejected by the server"));
4081 sip->gc->wants_to_die = TRUE;
4082 purple_connection_error(sip->gc, warning);
4083 g_free(warning);
4084 return TRUE;
4086 break;
4087 case 404:
4089 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4090 if (warning != NULL) {
4091 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4092 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
4093 g_free(reason);
4094 } else {
4095 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4098 sip->gc->wants_to_die = TRUE;
4099 purple_connection_error(sip->gc, warning);
4100 g_free(warning);
4101 return TRUE;
4103 break;
4104 case 503:
4106 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4107 if (warning != NULL) {
4108 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4109 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4110 g_free(reason);
4111 } else {
4112 warning = g_strdup(_("Service unavailable: no reason given"));
4115 sip->gc->wants_to_die = TRUE;
4116 purple_connection_error(sip->gc, warning);
4117 g_free(warning);
4118 return TRUE;
4120 break;
4122 return TRUE;
4125 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4127 const char *uri;
4128 xmlnode *xn_categories;
4129 xmlnode *xn_category;
4130 xmlnode *xn_node;
4131 const char *activity = NULL;
4133 xn_categories = xmlnode_from_str(data, len);
4134 uri = xmlnode_get_attrib(xn_categories, "uri");
4136 for (xn_category = xmlnode_get_child(xn_categories, "category");
4137 xn_category ;
4138 xn_category = xmlnode_get_next_twin(xn_category) )
4140 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4142 if (!strcmp(attrVar, "note"))
4144 if (uri) {
4145 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4147 if (sbuddy) {
4148 char *note;
4150 xn_node = xmlnode_get_child(xn_category, "note");
4151 if (!xn_node) continue;
4152 xn_node = xmlnode_get_child(xn_node, "body");
4153 if (!xn_node) continue;
4154 note = xmlnode_get_data(xn_node);
4155 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4156 g_free(sbuddy->annotation);
4157 sbuddy->annotation = NULL;
4158 if (note) sbuddy->annotation = g_strdup(note);
4159 g_free(note);
4164 else if(!strcmp(attrVar, "state"))
4166 char *data;
4167 int avail;
4168 xn_node = xmlnode_get_child(xn_category, "state");
4169 if (!xn_node) continue;
4170 xn_node = xmlnode_get_child(xn_node, "availability");
4171 if (!xn_node) continue;
4173 data = xmlnode_get_data(xn_node);
4174 avail = atoi(data);
4175 g_free(data);
4177 if (avail < 3000)
4178 activity = SIPE_STATUS_ID_UNKNOWN;
4179 else if (avail < 4500)
4180 activity = SIPE_STATUS_ID_AVAILABLE;
4181 else if (avail < 6000)
4182 activity = SIPE_STATUS_ID_BRB;
4183 else if (avail < 7500)
4184 activity = SIPE_STATUS_ID_ONPHONE;
4185 else if (avail < 9000)
4186 activity = SIPE_STATUS_ID_BUSY;
4187 else if (avail < 12000)
4188 activity = SIPE_STATUS_ID_DND;
4189 else if (avail < 18000)
4190 activity = SIPE_STATUS_ID_AWAY;
4191 else
4192 activity = SIPE_STATUS_ID_OFFLINE;
4195 if(activity) {
4196 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4197 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4200 xmlnode_free(xn_categories);
4203 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4205 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4206 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4207 payload->host = g_strdup(host);
4208 payload->buddies = server;
4209 sipe_subscribe_presence_batched_routed(sip, payload);
4210 sipe_subscribe_presence_batched_routed_free(payload);
4213 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4215 xmlnode *xn_list;
4216 xmlnode *xn_resource;
4217 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4218 g_free, NULL);
4219 GSList *server;
4220 gchar *host;
4222 xn_list = xmlnode_from_str(data, len);
4224 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4225 xn_resource;
4226 xn_resource = xmlnode_get_next_twin(xn_resource) )
4228 const char *uri, *state;
4229 xmlnode *xn_instance;
4231 xn_instance = xmlnode_get_child(xn_resource, "instance");
4232 if (!xn_instance) continue;
4234 uri = xmlnode_get_attrib(xn_resource, "uri");
4235 state = xmlnode_get_attrib(xn_instance, "state");
4236 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4238 if (strstr(state, "resubscribe")) {
4239 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4240 struct sipe_buddy *sbuddy;
4241 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4242 gchar *user = g_strdup(uri);
4243 host = g_strdup(poolFqdn);
4244 server = g_hash_table_lookup(servers, host);
4245 server = g_slist_append(server, user);
4246 g_hash_table_insert(servers, host, server);
4247 } else {
4248 sipe_subscribe_presence_single(sip, (void *) uri);
4250 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4251 if (sbuddy) {
4252 sbuddy->resubscribed = TRUE;
4257 /* Send out any deferred poolFqdn subscriptions */
4258 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4259 g_hash_table_destroy(servers);
4261 xmlnode_free(xn_list);
4264 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4266 const gchar *uri;
4267 gchar *getbasic;
4268 gchar *activity = NULL;
4269 xmlnode *pidf;
4270 xmlnode *basicstatus = NULL, *tuple, *status;
4271 gboolean isonline = FALSE;
4272 xmlnode *display_name_node;
4274 pidf = xmlnode_from_str(data, len);
4275 if (!pidf) {
4276 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4277 return;
4280 uri = xmlnode_get_attrib(pidf, "entity");
4282 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4284 if ((status = xmlnode_get_child(tuple, "status"))) {
4285 basicstatus = xmlnode_get_child(status, "basic");
4289 if (!basicstatus) {
4290 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4291 xmlnode_free(pidf);
4292 return;
4295 getbasic = xmlnode_get_data(basicstatus);
4296 if (!getbasic) {
4297 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4298 xmlnode_free(pidf);
4299 return;
4302 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4303 if (strstr(getbasic, "open")) {
4304 isonline = TRUE;
4306 g_free(getbasic);
4308 display_name_node = xmlnode_get_child(pidf, "display-name");
4309 // updating display name if alias was just URI
4310 if (display_name_node) {
4311 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4312 GSList *entry = buddies;
4313 PurpleBuddy *p_buddy;
4314 char * display_name = xmlnode_get_data(display_name_node);
4316 while (entry) {
4317 const char *server_alias;
4318 char *alias;
4320 p_buddy = entry->data;
4322 alias = (char *)purple_buddy_get_alias(p_buddy);
4323 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
4324 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4325 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4326 purple_blist_alias_buddy(p_buddy, display_name);
4328 g_free(alias);
4330 server_alias = purple_buddy_get_server_alias(p_buddy);
4331 if (display_name &&
4332 ( (server_alias && strcmp(display_name, server_alias))
4333 || !server_alias || strlen(server_alias) == 0 )
4335 purple_blist_server_alias_buddy(p_buddy, display_name);
4338 entry = entry->next;
4340 g_free(display_name);
4343 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4344 if ((status = xmlnode_get_child(tuple, "status"))) {
4345 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4346 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4347 activity = xmlnode_get_data(basicstatus);
4348 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4354 if (isonline) {
4355 const gchar * status_id = NULL;
4356 if (activity) {
4357 if (strstr(activity, "busy")) {
4358 status_id = SIPE_STATUS_ID_BUSY;
4359 } else if (strstr(activity, "away")) {
4360 status_id = SIPE_STATUS_ID_AWAY;
4364 if (!status_id) {
4365 status_id = SIPE_STATUS_ID_AVAILABLE;
4368 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4369 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4370 } else {
4371 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4374 g_free(activity);
4375 xmlnode_free(pidf);
4378 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4380 const char *availability;
4381 const char *activity;
4382 const char *display_name = NULL;
4383 const char *activity_name = NULL;
4384 const char *name;
4385 char *uri;
4386 int avl;
4387 int act;
4388 struct sipe_buddy *sbuddy;
4390 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4392 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4393 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4394 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4395 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4396 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4397 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4398 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4399 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4401 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4402 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4403 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4404 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4405 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4406 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4408 name = xmlnode_get_attrib(xn_presentity, "uri");
4409 uri = g_strdup_printf("sip:%s", name);
4410 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4411 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4413 // updating display name if alias was just URI
4414 if (xn_display_name) {
4415 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4416 GSList *entry = buddies;
4417 PurpleBuddy *p_buddy;
4418 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4420 while (entry) {
4421 const char *email_str, *server_alias;
4423 p_buddy = entry->data;
4425 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4426 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4427 purple_blist_alias_buddy(p_buddy, display_name);
4430 server_alias = purple_buddy_get_server_alias(p_buddy);
4431 if (display_name &&
4432 ( (server_alias && strcmp(display_name, server_alias))
4433 || !server_alias || strlen(server_alias) == 0 )
4435 purple_blist_server_alias_buddy(p_buddy, display_name);
4438 if (email) {
4439 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4440 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4441 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4445 entry = entry->next;
4449 avl = atoi(availability);
4450 act = atoi(activity);
4452 if(sip->msrtc_event_categories){
4453 if (act == 100 && avl == 0)
4454 activity_name = SIPE_STATUS_ID_OFFLINE;
4455 else if (act == 100 && avl == 300)
4456 activity_name = SIPE_STATUS_ID_AWAY;
4457 else if (act == 300 && avl == 300)
4458 activity_name = SIPE_STATUS_ID_BRB;
4459 else if (act == 400 && avl == 300)
4460 activity_name = SIPE_STATUS_ID_AVAILABLE;
4461 else if (act == 500 && act == 300)
4462 activity_name = SIPE_STATUS_ID_ONPHONE;
4463 else if (act == 600 && avl == 300)
4464 activity_name = SIPE_STATUS_ID_BUSY;
4465 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4466 if(avail){ //Check for LegacyInterop elements
4467 avl = atoi(avail);
4468 if(avl == 18500)
4469 activity_name = SIPE_STATUS_ID_OFFLINE;
4470 else if (avl == 3500)
4471 activity_name = SIPE_STATUS_ID_AVAILABLE;
4472 else if (avl == 15500)
4473 activity_name = SIPE_STATUS_ID_AWAY;
4474 else if (avl == 6500)
4475 activity_name = SIPE_STATUS_ID_BUSY;
4476 else if (avl == 12500)
4477 activity_name = SIPE_STATUS_ID_BRB;
4482 if(activity_name == NULL){
4483 if (act <= 100)
4484 activity_name = SIPE_STATUS_ID_AWAY;
4485 else if (act <= 150)
4486 activity_name = SIPE_STATUS_ID_LUNCH;
4487 else if (act <= 300)
4488 activity_name = SIPE_STATUS_ID_BRB;
4489 else if (act <= 400)
4490 activity_name = SIPE_STATUS_ID_AVAILABLE;
4491 else if (act <= 500)
4492 activity_name = SIPE_STATUS_ID_ONPHONE;
4493 else if (act <= 600)
4494 activity_name = SIPE_STATUS_ID_BUSY;
4495 else
4496 activity_name = SIPE_STATUS_ID_AVAILABLE;
4498 if (avl == 0)
4499 activity_name = SIPE_STATUS_ID_OFFLINE;
4502 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4503 if (sbuddy)
4505 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4506 sbuddy->annotation = NULL;
4507 if (note) { sbuddy->annotation = g_strdup(note); }
4509 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4510 sbuddy->device_name = NULL;
4511 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4514 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4515 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4516 g_free(note);
4517 xmlnode_free(xn_presentity);
4518 g_free(uri);
4521 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4523 char *ctype = sipmsg_find_header(msg, "Content-Type");
4525 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4527 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4528 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4530 const char *content = msg->body;
4531 unsigned length = msg->bodylen;
4532 PurpleMimeDocument *mime = NULL;
4534 if (strstr(ctype, "multipart"))
4536 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4537 const char *content_type;
4538 GList* parts;
4539 mime = purple_mime_document_parse(doc);
4540 parts = purple_mime_document_get_parts(mime);
4541 while(parts) {
4542 content = purple_mime_part_get_data(parts->data);
4543 length = purple_mime_part_get_length(parts->data);
4544 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4545 if(content_type && strstr(content_type,"application/rlmi+xml"))
4547 process_incoming_notify_rlmi_resub(sip, content, length);
4549 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4551 process_incoming_notify_msrtc(sip, content, length);
4553 else
4555 process_incoming_notify_rlmi(sip, content, length);
4557 parts = parts->next;
4559 g_free(doc);
4561 if (mime)
4563 purple_mime_document_free(mime);
4566 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4568 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4570 else if(strstr(ctype, "application/rlmi+xml"))
4572 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4575 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4577 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4579 else
4581 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4585 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4587 char *ctype = sipmsg_find_header(msg, "Content-Type");
4588 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4590 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4592 if (ctype &&
4593 strstr(ctype, "multipart") &&
4594 (strstr(ctype, "application/rlmi+xml") ||
4595 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4596 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4597 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4598 GList *parts = purple_mime_document_get_parts(mime);
4599 GSList *buddies = NULL;
4600 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4602 while (parts) {
4603 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4604 purple_mime_part_get_length(parts->data));
4605 gchar *uri = g_strdup(xmlnode_get_attrib(xml, "uri"));
4607 if (strstr(uri, "sip:") == NULL) {
4608 gchar *tmp = uri;
4609 uri = g_strdup_printf("sip:%s", tmp);
4610 g_free(tmp);
4612 buddies = g_slist_append(buddies, uri);
4613 xmlnode_free(xml);
4615 parts = parts->next;
4617 g_free(doc);
4618 if (mime) purple_mime_document_free(mime);
4620 payload->host = who;
4621 payload->buddies = buddies;
4622 sipe_schedule_action(action_name, timeout,
4623 sipe_subscribe_presence_batched_routed,
4624 sipe_subscribe_presence_batched_routed_free,
4625 sip, payload);
4626 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4628 } else {
4629 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4630 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4632 g_free(action_name);
4636 * Dispatcher for all incoming subscription information
4637 * whether it comes from NOTIFY, BENOTIFY requests or
4638 * piggy-backed to subscription's OK responce.
4640 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4641 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4643 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4645 gchar *event = sipmsg_find_header(msg, "Event");
4646 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4647 int timeout = 0;
4649 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4650 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
4652 if (!request)
4654 const gchar *expires_header;
4655 expires_header = sipmsg_find_header(msg, "Expires");
4656 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4657 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4658 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4661 if (!subscription_state || strstr(subscription_state, "active"))
4663 if (event && !g_ascii_strcasecmp(event, "presence"))
4665 sipe_process_presence(sip, msg);
4667 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4669 sipe_process_roaming_contacts(sip, msg, NULL);
4671 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
4673 sipe_process_roaming_self(sip, msg);
4675 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4677 sipe_process_roaming_acl(sip, msg);
4679 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4681 sipe_process_presence_wpending(sip, msg);
4683 else if (event && !g_ascii_strcasecmp(event, "conference"))
4685 sipe_process_conference(sip, msg);
4687 else
4689 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
4693 //The server sends a (BE)NOTIFY with the status 'terminated'
4694 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4695 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4696 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4697 g_free(from);
4700 if (timeout && event) {// For LSC 2005 and OCS 2007
4701 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4702 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4704 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4705 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4706 g_free(action_name);
4708 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4709 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4711 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4712 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4713 g_free(action_name);
4715 else*/
4716 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4717 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4719 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4720 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4721 g_free(action_name);
4723 else if (!g_ascii_strcasecmp(event, "presence") &&
4724 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4726 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4727 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4728 if(sip->batched_support) {
4729 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
4730 if(!g_ascii_strcasecmp(who, my_self)){
4731 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4732 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4733 g_free(who); /* unused */
4735 else {
4736 sipe_process_presence_timeout(sip, msg, who, timeout);
4738 g_free(my_self);
4740 else {
4741 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4742 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4744 g_free(action_name);
4745 /* "who" will be freed by the action we just scheduled */
4749 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4751 sipe_process_registration_notify(sip, msg);
4754 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4755 if (request && !benotify)
4757 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4762 * unused. Needed?
4764 static gchar* gen_xpidf(struct sipe_account_data *sip)
4766 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4767 "<presence>\r\n"
4768 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4769 "<display name=\"sip:%s\"/>\r\n"
4770 "<atom id=\"1234\">\r\n"
4771 "<address uri=\"sip:%s\">\r\n"
4772 "<status status=\"%s\"/>\r\n"
4773 "</address>\r\n"
4774 "</atom>\r\n"
4775 "</presence>\r\n",
4776 sip->username,
4777 sip->username,
4778 sip->username,
4779 sip->status);
4780 return doc;
4785 static gchar* gen_pidf(struct sipe_account_data *sip)
4787 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4788 "<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"
4789 "<tuple id=\"0\">\r\n"
4790 "<status>\r\n"
4791 "<basic>open</basic>\r\n"
4792 "<ep:activities>\r\n"
4793 " <ep:activity>%s</ep:activity>\r\n"
4794 "</ep:activities>"
4795 "</status>\r\n"
4796 "</tuple>\r\n"
4797 "<ci:display-name>%s</ci:display-name>\r\n"
4798 "</presence>",
4799 sip->username,
4800 sip->status,
4801 sip->username);
4802 return doc;
4806 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4808 int availability = 300; // online
4809 int activity = 400; // Available
4810 gchar *name;
4811 gchar *body;
4812 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4813 activity = 100;
4814 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4815 activity = 150;
4816 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4817 activity = 300;
4818 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4819 activity = 400;
4820 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4821 activity = 500;
4822 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4823 activity = 600;
4824 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4825 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4826 availability = 0; // offline
4827 activity = 100;
4828 } else {
4829 activity = 400; // available
4832 name = g_strdup_printf("sip: sip:%s", sip->username);
4833 //@TODO: send user data - state; add hostname in upper case
4834 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4835 send_soap_request_with_cb(sip, body, NULL , NULL);
4836 g_free(name);
4837 g_free(body);
4840 static gboolean
4841 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4843 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4844 if (msg->response == 200) {
4845 sip->status_version = 0;
4846 send_presence_status(sip);
4848 return TRUE;
4851 static gboolean
4852 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4854 if (msg->response == 409) {
4855 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4856 // TODO need to parse the version #'s?
4857 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4858 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4859 gchar *tmp;
4860 gchar *hdr;
4862 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4864 tmp = get_contact(sip);
4865 hdr = g_strdup_printf("Contact: %s\r\n"
4866 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4868 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4870 g_free(tmp);
4871 g_free(hdr);
4872 g_free(uri);
4873 g_free(doc);
4875 return TRUE;
4878 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4880 int code;
4881 gchar *uri;
4882 gchar *doc;
4883 gchar *tmp;
4884 gchar *hdr;
4885 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4886 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4887 code = 12000;
4888 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4889 code = 9000;
4890 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4891 code = 7500;
4892 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4893 code = 6000;
4894 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4895 code = 4500;
4896 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4897 code = 3000;
4898 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4899 code = 0;
4900 } else {
4901 // Offline or invisible
4902 code = 18000;
4905 uri = g_strdup_printf("sip:%s", sip->username);
4906 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4907 sip->status_version, code,
4908 sip->status_version, code,
4909 sip->status_version, note ? note : "",
4910 sip->status_version, note ? note : "",
4911 sip->status_version, note ? note : ""
4913 sip->status_version++;
4915 tmp = get_contact(sip);
4916 hdr = g_strdup_printf("Contact: %s\r\n"
4917 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4919 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4921 g_free(tmp);
4922 g_free(hdr);
4923 g_free(uri);
4924 g_free(doc);
4927 static void send_presence_status(struct sipe_account_data *sip)
4929 PurpleStatus * status = purple_account_get_active_status(sip->account);
4930 const gchar *note;
4931 if (!status) return;
4933 note = purple_status_get_attr_string(status, "message");
4935 if(sip->msrtc_event_categories){
4936 send_presence_category_publish(sip, note);
4937 } else {
4938 send_presence_soap(sip, note);
4942 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4944 gboolean found = FALSE;
4945 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4946 if (msg->response == 0) { /* request */
4947 if (!strcmp(msg->method, "MESSAGE")) {
4948 process_incoming_message(sip, msg);
4949 found = TRUE;
4950 } else if (!strcmp(msg->method, "NOTIFY")) {
4951 purple_debug_info("sipe","send->process_incoming_notify\n");
4952 process_incoming_notify(sip, msg, TRUE, FALSE);
4953 found = TRUE;
4954 } else if (!strcmp(msg->method, "BENOTIFY")) {
4955 purple_debug_info("sipe","send->process_incoming_benotify\n");
4956 process_incoming_notify(sip, msg, TRUE, TRUE);
4957 found = TRUE;
4958 } else if (!strcmp(msg->method, "INVITE")) {
4959 process_incoming_invite(sip, msg);
4960 found = TRUE;
4961 } else if (!strcmp(msg->method, "REFER")) {
4962 process_incoming_refer(sip, msg);
4963 found = TRUE;
4964 } else if (!strcmp(msg->method, "OPTIONS")) {
4965 process_incoming_options(sip, msg);
4966 found = TRUE;
4967 } else if (!strcmp(msg->method, "INFO")) {
4968 process_incoming_info(sip, msg);
4969 found = TRUE;
4970 } else if (!strcmp(msg->method, "ACK")) {
4971 // ACK's don't need any response
4972 found = TRUE;
4973 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4974 // LCS 2005 sends us these - just respond 200 OK
4975 found = TRUE;
4976 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4977 } else if (!strcmp(msg->method, "BYE")) {
4978 process_incoming_bye(sip, msg);
4979 found = TRUE;
4980 } else {
4981 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4983 } else { /* response */
4984 struct transaction *trans = transactions_find(sip, msg);
4985 if (trans) {
4986 if (msg->response == 407) {
4987 gchar *resend, *auth, *ptmp;
4989 if (sip->proxy.retries > 30) return;
4990 sip->proxy.retries++;
4991 /* do proxy authentication */
4993 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4995 fill_auth(sip, ptmp, &sip->proxy);
4996 auth = auth_header(sip, &sip->proxy, trans->msg);
4997 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4998 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4999 g_free(auth);
5000 resend = sipmsg_to_string(trans->msg);
5001 /* resend request */
5002 sendout_pkt(sip->gc, resend);
5003 g_free(resend);
5004 } else {
5005 if (msg->response == 100 || msg->response == 180) {
5006 /* ignore provisional response */
5007 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
5008 } else {
5009 sip->proxy.retries = 0;
5010 if (!strcmp(trans->msg->method, "REGISTER")) {
5011 if (msg->response == 401)
5013 sip->registrar.retries++;
5015 else
5017 sip->registrar.retries = 0;
5019 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
5020 } else {
5021 if (msg->response == 401) {
5022 gchar *resend, *auth, *ptmp;
5024 if (sip->registrar.retries > 4) return;
5025 sip->registrar.retries++;
5027 #ifdef USE_KERBEROS
5028 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
5029 #endif
5030 ptmp = sipmsg_find_auth_header(msg, "NTLM");
5031 #ifdef USE_KERBEROS
5032 } else {
5033 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
5035 #endif
5037 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
5039 fill_auth(sip, ptmp, &sip->registrar);
5040 auth = auth_header(sip, &sip->registrar, trans->msg);
5041 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
5042 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
5044 //sipmsg_remove_header_now(trans->msg, "Authorization");
5045 //sipmsg_add_header(trans->msg, "Authorization", auth);
5046 g_free(auth);
5047 resend = sipmsg_to_string(trans->msg);
5048 /* resend request */
5049 sendout_pkt(sip->gc, resend);
5050 g_free(resend);
5054 if (trans->callback) {
5055 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
5056 /* call the callback to process response*/
5057 (trans->callback)(sip, msg, trans);
5059 /* Not sure if this is needed or what needs to be done
5060 but transactions seem to be removed prematurely so
5061 this only removes them if the response is 200 OK */
5062 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
5063 /*Has a bug and it's unneccesary*/
5064 /*transactions_remove(sip, trans);*/
5068 found = TRUE;
5069 } else {
5070 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
5073 if (!found) {
5074 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
5078 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
5080 char *cur;
5081 char *dummy;
5082 struct sipmsg *msg;
5083 int restlen;
5084 cur = conn->inbuf;
5086 /* according to the RFC remove CRLF at the beginning */
5087 while (*cur == '\r' || *cur == '\n') {
5088 cur++;
5090 if (cur != conn->inbuf) {
5091 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
5092 conn->inbufused = strlen(conn->inbuf);
5095 /* Received a full Header? */
5096 sip->processing_input = TRUE;
5097 while (sip->processing_input &&
5098 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
5099 time_t currtime = time(NULL);
5100 cur += 2;
5101 cur[0] = '\0';
5102 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
5103 msg = sipmsg_parse_header(conn->inbuf);
5104 cur[0] = '\r';
5105 cur += 2;
5106 restlen = conn->inbufused - (cur - conn->inbuf);
5107 if (restlen >= msg->bodylen) {
5108 dummy = g_malloc(msg->bodylen + 1);
5109 memcpy(dummy, cur, msg->bodylen);
5110 dummy[msg->bodylen] = '\0';
5111 msg->body = dummy;
5112 cur += msg->bodylen;
5113 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
5114 conn->inbufused = strlen(conn->inbuf);
5115 } else {
5116 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5117 restlen, msg->bodylen, (int)strlen(conn->inbuf));
5118 sipmsg_free(msg);
5119 return;
5122 /*if (msg->body) {
5123 purple_debug_info("sipe", "body:\n%s", msg->body);
5126 // Verify the signature before processing it
5127 if (sip->registrar.gssapi_context) {
5128 struct sipmsg_breakdown msgbd;
5129 gchar *signature_input_str;
5130 gchar *rspauth;
5131 msgbd.msg = msg;
5132 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5133 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5135 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5137 if (rspauth != NULL) {
5138 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5139 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5140 process_input_message(sip, msg);
5141 } else {
5142 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5143 purple_connection_error(sip->gc, _("Invalid message signature received"));
5144 sip->gc->wants_to_die = TRUE;
5146 } else if (msg->response == 401) {
5147 purple_connection_error(sip->gc, _("Wrong Password"));
5148 sip->gc->wants_to_die = TRUE;
5150 g_free(signature_input_str);
5152 g_free(rspauth);
5153 sipmsg_breakdown_free(&msgbd);
5154 } else {
5155 process_input_message(sip, msg);
5158 sipmsg_free(msg);
5162 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
5164 PurpleConnection *gc = data;
5165 struct sipe_account_data *sip = gc->proto_data;
5166 struct sipmsg *msg;
5167 int len;
5168 time_t currtime;
5170 static char buffer[65536];
5171 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5172 buffer[len] = '\0';
5173 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5174 msg = sipmsg_parse_msg(buffer);
5175 if (msg) process_input_message(sip, msg);
5179 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5181 struct sipe_account_data *sip = gc->proto_data;
5182 PurpleSslConnection *gsc = sip->gsc;
5184 purple_debug_error("sipe", "%s",debug);
5185 purple_connection_error(gc, msg);
5187 /* Invalidate this connection. Next send will open a new one */
5188 if (gsc) {
5189 connection_remove(sip, gsc->fd);
5190 purple_ssl_close(gsc);
5192 sip->gsc = NULL;
5193 sip->fd = -1;
5196 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5198 PurpleConnection *gc = data;
5199 struct sipe_account_data *sip;
5200 struct sip_connection *conn;
5201 int readlen, len;
5202 gboolean firstread = TRUE;
5204 /* NOTE: This check *IS* necessary */
5205 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5206 purple_ssl_close(gsc);
5207 return;
5210 sip = gc->proto_data;
5211 conn = connection_find(sip, gsc->fd);
5212 if (conn == NULL) {
5213 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5214 gc->wants_to_die = TRUE;
5215 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5216 return;
5219 /* Read all available data from the SSL connection */
5220 do {
5221 /* Increase input buffer size as needed */
5222 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5223 conn->inbuflen += SIMPLE_BUF_INC;
5224 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5225 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5228 /* Try to read as much as there is space left in the buffer */
5229 readlen = conn->inbuflen - conn->inbufused - 1;
5230 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5232 if (len < 0 && errno == EAGAIN) {
5233 /* Try again later */
5234 return;
5235 } else if (len < 0) {
5236 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5237 return;
5238 } else if (firstread && (len == 0)) {
5239 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5240 return;
5243 conn->inbufused += len;
5244 firstread = FALSE;
5246 /* Equivalence indicates that there is possibly more data to read */
5247 } while (len == readlen);
5249 conn->inbuf[conn->inbufused] = '\0';
5250 process_input(sip, conn);
5254 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
5256 PurpleConnection *gc = data;
5257 struct sipe_account_data *sip = gc->proto_data;
5258 int len;
5259 struct sip_connection *conn = connection_find(sip, source);
5260 if (!conn) {
5261 purple_debug_error("sipe", "Connection not found!\n");
5262 return;
5265 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5266 conn->inbuflen += SIMPLE_BUF_INC;
5267 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5270 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5272 if (len < 0 && errno == EAGAIN)
5273 return;
5274 else if (len <= 0) {
5275 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5276 connection_remove(sip, source);
5277 if (sip->fd == source) sip->fd = -1;
5278 return;
5281 conn->inbufused += len;
5282 conn->inbuf[conn->inbufused] = '\0';
5284 process_input(sip, conn);
5287 /* Callback for new connections on incoming TCP port */
5288 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
5290 PurpleConnection *gc = data;
5291 struct sipe_account_data *sip = gc->proto_data;
5292 struct sip_connection *conn;
5294 int newfd = accept(source, NULL, NULL);
5296 conn = connection_create(sip, newfd);
5298 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5301 static void login_cb(gpointer data, gint source, const gchar *error_message)
5303 PurpleConnection *gc = data;
5304 struct sipe_account_data *sip;
5305 struct sip_connection *conn;
5307 if (!PURPLE_CONNECTION_IS_VALID(gc))
5309 if (source >= 0)
5310 close(source);
5311 return;
5314 if (source < 0) {
5315 purple_connection_error(gc, _("Could not connect"));
5316 return;
5319 sip = gc->proto_data;
5320 sip->fd = source;
5321 sip->last_keepalive = time(NULL);
5323 conn = connection_create(sip, source);
5325 do_register(sip);
5327 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5330 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5332 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5333 if (sip == NULL) return;
5335 do_register(sip);
5338 static guint sipe_ht_hash_nick(const char *nick)
5340 char *lc = g_utf8_strdown(nick, -1);
5341 guint bucket = g_str_hash(lc);
5342 g_free(lc);
5344 return bucket;
5347 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5349 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5352 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5354 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5356 sip->listen_data = NULL;
5358 if (listenfd == -1) {
5359 purple_connection_error(sip->gc, _("Could not create listen socket"));
5360 return;
5363 sip->fd = listenfd;
5365 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5366 sip->listenfd = sip->fd;
5368 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5370 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5371 do_register(sip);
5374 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
5376 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5378 sip->query_data = NULL;
5380 if (!hosts || !hosts->data) {
5381 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5382 return;
5385 hosts = g_slist_remove(hosts, hosts->data);
5386 g_free(sip->serveraddr);
5387 sip->serveraddr = hosts->data;
5388 hosts = g_slist_remove(hosts, hosts->data);
5389 while (hosts) {
5390 hosts = g_slist_remove(hosts, hosts->data);
5391 g_free(hosts->data);
5392 hosts = g_slist_remove(hosts, hosts->data);
5395 /* create socket for incoming connections */
5396 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5397 sipe_udp_host_resolved_listen_cb, sip);
5398 if (sip->listen_data == NULL) {
5399 purple_connection_error(sip->gc, _("Could not create listen socket"));
5400 return;
5404 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
5405 gpointer data)
5407 PurpleConnection *gc = data;
5408 struct sipe_account_data *sip;
5410 /* If the connection is already disconnected, we don't need to do anything else */
5411 if (!PURPLE_CONNECTION_IS_VALID(gc))
5412 return;
5414 sip = gc->proto_data;
5415 sip->fd = -1;
5416 sip->gsc = NULL;
5418 switch(error) {
5419 case PURPLE_SSL_CONNECT_FAILED:
5420 purple_connection_error(gc, _("Connection Failed"));
5421 break;
5422 case PURPLE_SSL_HANDSHAKE_FAILED:
5423 purple_connection_error(gc, _("SSL Handshake Failed"));
5424 break;
5425 case PURPLE_SSL_CERTIFICATE_INVALID:
5426 purple_connection_error(gc, _("SSL Certificate Invalid"));
5427 break;
5431 static void
5432 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5434 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5435 PurpleProxyConnectData *connect_data;
5437 sip->listen_data = NULL;
5439 sip->listenfd = listenfd;
5440 if (sip->listenfd == -1) {
5441 purple_connection_error(sip->gc, _("Could not create listen socket"));
5442 return;
5445 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5446 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5447 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5448 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5449 sipe_newconn_cb, sip->gc);
5450 purple_debug_info("sipe", "connecting to %s port %d\n",
5451 sip->realhostname, sip->realport);
5452 /* open tcp connection to the server */
5453 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5454 sip->realport, login_cb, sip->gc);
5456 if (connect_data == NULL) {
5457 purple_connection_error(sip->gc, _("Couldn't create socket"));
5462 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5464 PurpleAccount *account = sip->account;
5465 PurpleConnection *gc = sip->gc;
5467 if (purple_account_get_bool(account, "useport", FALSE)) {
5468 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
5469 port = purple_account_get_int(account, "port", 0);
5470 } else {
5471 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5474 sip->realhostname = hostname;
5475 sip->realport = port;
5477 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5478 hostname, port);
5480 /* TODO: is there a good default grow size? */
5481 if (sip->transport != SIPE_TRANSPORT_UDP)
5482 sip->txbuf = purple_circ_buffer_new(0);
5484 if (sip->transport == SIPE_TRANSPORT_TLS) {
5485 /* SSL case */
5486 if (!purple_ssl_is_supported()) {
5487 gc->wants_to_die = TRUE;
5488 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5489 return;
5492 purple_debug_info("sipe", "using SSL\n");
5494 sip->gsc = purple_ssl_connect(account, hostname, port,
5495 login_cb_ssl, sipe_ssl_connect_failure, gc);
5496 if (sip->gsc == NULL) {
5497 purple_connection_error(gc, _("Could not create SSL context"));
5498 return;
5500 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5501 /* UDP case */
5502 purple_debug_info("sipe", "using UDP\n");
5504 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5505 if (sip->query_data == NULL) {
5506 purple_connection_error(gc, _("Could not resolve hostname"));
5508 } else {
5509 /* TCP case */
5510 purple_debug_info("sipe", "using TCP\n");
5511 /* create socket for incoming connections */
5512 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5513 sipe_tcp_connect_listen_cb, sip);
5514 if (sip->listen_data == NULL) {
5515 purple_connection_error(gc, _("Could not create listen socket"));
5516 return;
5521 /* Service list for autodection */
5522 static const struct sipe_service_data service_autodetect[] = {
5523 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5524 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5525 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5526 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5527 { NULL, NULL, 0 }
5530 /* Service list for SSL/TLS */
5531 static const struct sipe_service_data service_tls[] = {
5532 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5533 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5534 { NULL, NULL, 0 }
5537 /* Service list for TCP */
5538 static const struct sipe_service_data service_tcp[] = {
5539 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5540 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5541 { NULL, NULL, 0 }
5544 /* Service list for UDP */
5545 static const struct sipe_service_data service_udp[] = {
5546 { "sip", "udp", SIPE_TRANSPORT_UDP },
5547 { NULL, NULL, 0 }
5550 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5551 static void resolve_next_service(struct sipe_account_data *sip,
5552 const struct sipe_service_data *start)
5554 if (start) {
5555 sip->service_data = start;
5556 } else {
5557 sip->service_data++;
5558 if (sip->service_data->service == NULL) {
5559 gchar *hostname;
5560 /* Try connecting to the SIP hostname directly */
5561 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5562 if (sip->auto_transport) {
5563 // If SSL is supported, default to using it; OCS servers aren't configured
5564 // by default to accept TCP
5565 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5566 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5567 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5570 hostname = g_strdup(sip->sipdomain);
5571 create_connection(sip, hostname, 0);
5572 return;
5576 /* Try to resolve next service */
5577 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5578 sip->service_data->transport,
5579 sip->sipdomain,
5580 srvresolved, sip);
5583 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5585 struct sipe_account_data *sip = data;
5587 sip->srv_query_data = NULL;
5589 /* find the host to connect to */
5590 if (results) {
5591 gchar *hostname = g_strdup(resp->hostname);
5592 int port = resp->port;
5593 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5594 hostname, port);
5595 g_free(resp);
5597 sip->transport = sip->service_data->type;
5599 create_connection(sip, hostname, port);
5600 } else {
5601 resolve_next_service(sip, NULL);
5605 static void sipe_login(PurpleAccount *account)
5607 PurpleConnection *gc;
5608 struct sipe_account_data *sip;
5609 gchar **signinname_login, **userserver, **domain_user;
5610 const char *transport;
5612 const char *username = purple_account_get_username(account);
5613 gc = purple_account_get_connection(account);
5615 if (strpbrk(username, "\t\v\r\n") != NULL) {
5616 gc->wants_to_die = TRUE;
5617 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5618 return;
5621 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5622 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5623 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5624 sip->gc = gc;
5625 sip->account = account;
5626 sip->reregister_set = FALSE;
5627 sip->reauthenticate_set = FALSE;
5628 sip->subscribed = FALSE;
5629 sip->subscribed_buddies = FALSE;
5631 signinname_login = g_strsplit(username, ",", 2);
5633 userserver = g_strsplit(signinname_login[0], "@", 2);
5634 purple_connection_set_display_name(gc, userserver[0]);
5635 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
5636 sip->sipdomain = g_strdup(userserver[1]);
5638 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
5639 gc->wants_to_die = TRUE;
5640 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5641 return;
5644 domain_user = g_strsplit(signinname_login[1], "\\", 2);
5645 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
5646 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
5648 sip->password = g_strdup(purple_connection_get_password(gc));
5650 g_strfreev(userserver);
5651 g_strfreev(domain_user);
5652 g_strfreev(signinname_login);
5654 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5656 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5658 /* TODO: Set the status correctly. */
5659 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5661 transport = purple_account_get_string(account, "transport", "auto");
5662 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
5663 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
5664 SIPE_TRANSPORT_UDP;
5666 if (purple_account_get_bool(account, "useproxy", FALSE)) {
5667 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
5668 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
5669 } else if (strcmp(transport, "auto") == 0) {
5670 sip->auto_transport = TRUE;
5671 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5672 } else if (strcmp(transport, "tls") == 0) {
5673 resolve_next_service(sip, service_tls);
5674 } else if (strcmp(transport, "tcp") == 0) {
5675 resolve_next_service(sip, service_tcp);
5676 } else {
5677 resolve_next_service(sip, service_udp);
5681 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5683 connection_free_all(sip);
5685 g_free(sip->epid);
5686 sip->epid = NULL;
5688 if (sip->query_data != NULL)
5689 purple_dnsquery_destroy(sip->query_data);
5690 sip->query_data = NULL;
5692 if (sip->srv_query_data != NULL)
5693 purple_srv_cancel(sip->srv_query_data);
5694 sip->srv_query_data = NULL;
5696 if (sip->listen_data != NULL)
5697 purple_network_listen_cancel(sip->listen_data);
5698 sip->listen_data = NULL;
5700 if (sip->gsc != NULL)
5701 purple_ssl_close(sip->gsc);
5702 sip->gsc = NULL;
5704 sipe_auth_free(&sip->registrar);
5705 sipe_auth_free(&sip->proxy);
5707 if (sip->txbuf)
5708 purple_circ_buffer_destroy(sip->txbuf);
5709 sip->txbuf = NULL;
5711 g_free(sip->realhostname);
5712 sip->realhostname = NULL;
5714 if (sip->listenpa)
5715 purple_input_remove(sip->listenpa);
5716 sip->listenpa = 0;
5717 if (sip->tx_handler)
5718 purple_input_remove(sip->tx_handler);
5719 sip->tx_handler = 0;
5720 if (sip->resendtimeout)
5721 purple_timeout_remove(sip->resendtimeout);
5722 sip->resendtimeout = 0;
5723 if (sip->timeouts) {
5724 GSList *entry = sip->timeouts;
5725 while (entry) {
5726 struct scheduled_action *sched_action = entry->data;
5727 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5728 purple_timeout_remove(sched_action->timeout_handler);
5729 (*sched_action->destroy)(sched_action->payload);
5730 g_free(sched_action->name);
5731 g_free(sched_action);
5732 entry = entry->next;
5735 g_slist_free(sip->timeouts);
5737 if (sip->allow_events) {
5738 GSList *entry = sip->allow_events;
5739 while (entry) {
5740 g_free(entry->data);
5741 entry = entry->next;
5744 g_slist_free(sip->allow_events);
5746 if (sip->containers) {
5747 GSList *entry = sip->containers;
5748 while (entry) {
5749 free_container((struct sipe_container *)entry->data);
5750 entry = entry->next;
5753 g_slist_free(sip->containers);
5755 if (sip->contact)
5756 g_free(sip->contact);
5757 sip->contact = NULL;
5758 if (sip->regcallid)
5759 g_free(sip->regcallid);
5760 sip->regcallid = NULL;
5762 if (sip->serveraddr)
5763 g_free(sip->serveraddr);
5764 sip->serveraddr = NULL;
5766 sip->fd = -1;
5767 sip->processing_input = FALSE;
5771 * A callback for g_hash_table_foreach_remove
5773 static gboolean sipe_buddy_remove(gpointer key, gpointer buddy, gpointer user_data)
5775 sipe_free_buddy((struct sipe_buddy *) buddy);
5777 /* We must return TRUE as the key/value have already been deleted */
5778 return(TRUE);
5781 static void sipe_close(PurpleConnection *gc)
5783 struct sipe_account_data *sip = gc->proto_data;
5785 if (sip) {
5786 /* leave all conversations */
5787 im_session_close_all(sip);
5789 /* unregister */
5790 do_register_exp(sip, 0);
5792 sipe_connection_cleanup(sip);
5793 g_free(sip->sipdomain);
5794 g_free(sip->username);
5795 g_free(sip->password);
5796 g_free(sip->authdomain);
5797 g_free(sip->authuser);
5798 g_free(sip->status);
5800 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
5801 g_hash_table_destroy(sip->buddies);
5803 g_free(gc->proto_data);
5804 gc->proto_data = NULL;
5807 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
5809 PurpleAccount *acct = purple_connection_get_account(gc);
5810 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
5811 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5812 if (conv == NULL)
5813 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5814 purple_conversation_present(conv);
5815 g_free(id);
5818 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5821 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5822 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5825 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5827 PurpleNotifySearchResults *results;
5828 PurpleNotifySearchColumn *column;
5829 xmlnode *searchResults;
5830 xmlnode *mrow;
5831 int match_count = 0;
5832 gboolean more = FALSE;
5833 gchar *secondary;
5835 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5837 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5838 if (!searchResults) {
5839 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5840 return FALSE;
5843 results = purple_notify_searchresults_new();
5845 if (results == NULL) {
5846 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5847 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5849 xmlnode_free(searchResults);
5850 return FALSE;
5853 column = purple_notify_searchresults_column_new(_("User Name"));
5854 purple_notify_searchresults_column_add(results, column);
5856 column = purple_notify_searchresults_column_new(_("Name"));
5857 purple_notify_searchresults_column_add(results, column);
5859 column = purple_notify_searchresults_column_new(_("Company"));
5860 purple_notify_searchresults_column_add(results, column);
5862 column = purple_notify_searchresults_column_new(_("Country"));
5863 purple_notify_searchresults_column_add(results, column);
5865 column = purple_notify_searchresults_column_new(_("Email"));
5866 purple_notify_searchresults_column_add(results, column);
5868 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5869 GList *row = NULL;
5871 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5872 row = g_list_append(row, g_strdup(uri_parts[1]));
5873 g_strfreev(uri_parts);
5875 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5876 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5877 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5878 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5880 purple_notify_searchresults_row_add(results, row);
5881 match_count++;
5884 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5885 char *data = xmlnode_get_data_unescaped(mrow);
5886 more = (g_strcasecmp(data, "true") == 0);
5887 g_free(data);
5890 secondary = g_strdup_printf(
5891 dngettext(GETTEXT_PACKAGE,
5892 "Found %d contact%s:",
5893 "Found %d contacts%s:", match_count),
5894 match_count, more ? _(" (more matched your query)") : "");
5896 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5897 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5898 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5900 g_free(secondary);
5901 xmlnode_free(searchResults);
5902 return TRUE;
5905 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5907 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5908 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5909 unsigned i = 0;
5911 do {
5912 PurpleRequestField *field = entries->data;
5913 const char *id = purple_request_field_get_id(field);
5914 const char *value = purple_request_field_string_get_value(field);
5916 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5918 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5919 } while ((entries = g_list_next(entries)) != NULL);
5920 attrs[i] = NULL;
5922 if (i > 0) {
5923 gchar *query = g_strjoinv(NULL, attrs);
5924 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5925 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5926 send_soap_request_with_cb(gc->proto_data, body,
5927 (TransCallback) process_search_contact_response, NULL);
5928 g_free(body);
5929 g_free(query);
5932 g_strfreev(attrs);
5935 static void sipe_show_find_contact(PurplePluginAction *action)
5937 PurpleConnection *gc = (PurpleConnection *) action->context;
5938 PurpleRequestFields *fields;
5939 PurpleRequestFieldGroup *group;
5940 PurpleRequestField *field;
5942 fields = purple_request_fields_new();
5943 group = purple_request_field_group_new(NULL);
5944 purple_request_fields_add_group(fields, group);
5946 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5947 purple_request_field_group_add_field(group, field);
5948 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5949 purple_request_field_group_add_field(group, field);
5950 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5951 purple_request_field_group_add_field(group, field);
5952 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5953 purple_request_field_group_add_field(group, field);
5955 purple_request_fields(gc,
5956 _("Search"),
5957 _("Search for a Contact"),
5958 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5959 fields,
5960 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5961 _("_Cancel"), NULL,
5962 purple_connection_get_account(gc), NULL, NULL, gc);
5965 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5967 GList *menu = NULL;
5968 PurplePluginAction *act;
5970 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5971 menu = g_list_prepend(menu, act);
5973 menu = g_list_reverse(menu);
5975 return menu;
5978 static void dummy_permit_deny(PurpleConnection *gc)
5982 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5984 return TRUE;
5988 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5990 return TRUE;
5994 static char *sipe_status_text(PurpleBuddy *buddy)
5996 struct sipe_account_data *sip;
5997 struct sipe_buddy *sbuddy;
5998 char *text = NULL;
6000 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
6001 if (sip) //happens on pidgin exit
6003 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
6004 if (sbuddy && sbuddy->annotation)
6006 text = g_strdup(sbuddy->annotation);
6010 return text;
6013 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
6015 const PurplePresence *presence = purple_buddy_get_presence(buddy);
6016 const PurpleStatus *status = purple_presence_get_active_status(presence);
6017 struct sipe_account_data *sip;
6018 struct sipe_buddy *sbuddy;
6019 char *annotation = NULL;
6021 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
6022 if (sip) //happens on pidgin exit
6024 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
6025 if (sbuddy)
6027 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
6031 //Layout
6032 if (purple_presence_is_online(presence))
6034 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
6037 if (annotation)
6039 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
6040 g_free(annotation);
6045 static GHashTable *
6046 sipe_get_account_text_table(PurpleAccount *account)
6048 GHashTable *table;
6049 table = g_hash_table_new(g_str_hash, g_str_equal);
6050 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
6051 return table;
6054 static PurpleBuddy *
6055 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
6057 PurpleBuddy *clone;
6058 const gchar *server_alias, *email;
6059 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
6061 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
6063 purple_blist_add_buddy(clone, NULL, group, NULL);
6065 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
6066 if (server_alias) {
6067 purple_blist_server_alias_buddy(clone, server_alias);
6070 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6071 if (email) {
6072 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
6075 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6076 //for UI to update;
6077 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6078 return clone;
6081 static void
6082 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6084 PurpleBuddy *buddy, *b;
6085 PurpleConnection *gc;
6086 PurpleGroup * group = purple_find_group(group_name);
6088 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6090 buddy = (PurpleBuddy *)node;
6092 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
6093 gc = purple_account_get_connection(buddy->account);
6095 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6096 if (!b){
6097 b = purple_blist_add_buddy_clone(group, buddy);
6100 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6103 static void
6104 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6106 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6107 gchar *self = g_strdup_printf("sip:%s", sip->username);
6108 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
6109 struct sip_im_session *session;
6111 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6113 session = create_chat_session(sip);
6114 session->roster_manager = g_strdup(self);
6116 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6117 session->chat_name = g_strdup(chat_name);
6118 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6119 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6121 g_free(chat_name);
6122 g_free(self);
6125 static gboolean
6126 sipe_is_election_finished(struct sipe_account_data *sip,
6127 struct sip_im_session *session)
6129 struct sip_dialog *dialog;
6130 GSList *entry;
6131 gboolean res = TRUE;
6133 entry = session->dialogs;
6134 while (entry) {
6135 dialog = entry->data;
6136 if (dialog->election_vote == 0) {
6137 res = FALSE;
6138 break;
6140 entry = entry->next;
6143 if (res) {
6144 session->is_voting_in_progress = FALSE;
6146 return res;
6149 static void
6150 sipe_election_start(struct sipe_account_data *sip,
6151 struct sip_im_session *session)
6153 struct sip_dialog *dialog;
6154 GSList *entry;
6155 int election_timeout;
6157 if (session->is_voting_in_progress) {
6158 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6159 return;
6160 } else {
6161 session->is_voting_in_progress = TRUE;
6163 session->bid = rand();
6165 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6167 /* reset election_vote for each chat participant */
6168 entry = session->dialogs;
6169 while (entry) {
6170 dialog = entry->data;
6171 dialog->election_vote = 0;
6172 entry = entry->next;
6175 entry = session->dialogs;
6176 while (entry) {
6177 dialog = entry->data;
6178 /* send RequestRM to each chat participant*/
6179 sipe_send_election_request_rm(sip, session, dialog->with, session->bid);
6180 entry = entry->next;
6183 election_timeout = 15; /* sec */
6184 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6188 * @param who a URI to whom to invite to chat
6190 static void
6191 sipe_invite_to_chat(struct sipe_account_data *sip,
6192 struct sip_im_session *session,
6193 const char *who)
6195 gchar *self = g_strdup_printf("sip:%s", sip->username);
6197 if (session->roster_manager) {
6198 if (!strcmp(session->roster_manager, self)) {
6199 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6200 } else {
6201 sipe_refer(sip, session, who);
6203 } else {
6204 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6206 session->pending_invite_queue = slist_insert_unique_sorted(
6207 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6209 sipe_election_start(sip, session);
6212 g_free(self);
6215 static void
6216 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6217 struct sip_im_session *session)
6219 gchar *invitee;
6220 GSList *entry = session->pending_invite_queue;
6222 while (entry) {
6223 invitee = entry->data;
6224 sipe_invite_to_chat(sip, session, invitee);
6225 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6226 g_free(invitee);
6230 static void
6231 sipe_election_result(struct sipe_account_data *sip,
6232 void *sess)
6234 struct sip_im_session *session = (struct sip_im_session *)sess;
6235 struct sip_dialog *dialog;
6236 GSList *entry;
6237 gchar * rival;
6238 gboolean has_won = TRUE;
6240 if (session->roster_manager) {
6241 purple_debug_info("sipe",
6242 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6243 return;
6246 session->is_voting_in_progress = FALSE;
6248 entry = session->dialogs;
6249 while (entry) {
6250 dialog = entry->data;
6251 if (dialog->election_vote < 0) {
6252 has_won = FALSE;
6253 rival = dialog->with;
6254 break;
6256 entry = entry->next;
6259 if (has_won) {
6260 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6262 session->roster_manager = g_strdup_printf("sip:%s", sip->username);
6264 entry = session->dialogs;
6265 while (entry) {
6266 dialog = entry->data;
6267 /* send SetRM to each chat participant*/
6268 sipe_send_election_set_rm(sip, session, dialog->with);
6269 entry = entry->next;
6271 } else {
6272 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6274 session->bid = 0;
6276 sipe_process_pending_invite_queue(sip, session);
6279 static void
6280 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6282 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6283 struct sip_im_session *session;
6285 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6286 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6288 session = find_chat_session_by_name(sip, chat_name);
6290 sipe_invite_to_chat(sip, session, buddy->name);
6293 static void
6294 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6296 const gchar *email;
6297 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6299 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6300 if (email)
6302 char *mailto = g_strdup_printf("mailto:%s", email);
6303 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6304 #ifndef _WIN32
6306 pid_t pid;
6307 char *const parmList[] = {mailto, NULL};
6308 if ((pid = fork()) == -1)
6310 purple_debug_info("sipe", "fork() error\n");
6312 else if (pid == 0)
6314 execvp("xdg-email", parmList);
6315 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6318 #else
6320 BOOL ret;
6321 _flushall();
6322 errno = 0;
6323 //@TODO resolve env variable %WINDIR% first
6324 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6325 if (errno)
6327 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6330 #endif
6332 g_free(mailto);
6334 else
6336 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6341 * A menu which appear when right-clicking on buddy in contact list.
6343 static GList *
6344 sipe_buddy_menu(PurpleBuddy *buddy)
6346 PurpleBlistNode *g_node;
6347 PurpleGroup *group, *gr_parent;
6348 PurpleMenuAction *act;
6349 GList *menu = NULL;
6350 GList *menu_groups = NULL;
6351 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6352 struct sip_im_session *session;
6353 GSList *entry;
6354 gchar *self = g_strdup_printf("sip:%s", sip->username);
6356 act = purple_menu_action_new(_("New Chat"),
6357 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6358 NULL, NULL);
6359 menu = g_list_prepend(menu, act);
6361 entry = sip->im_sessions;
6362 while (entry) {
6363 session = entry->data;
6364 if (strcmp(self, buddy->name) && session->chat_name && !get_dialog(session, buddy->name)) {
6365 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_name);
6366 act = purple_menu_action_new(label,
6367 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6368 g_strdup(session->chat_name), NULL);
6369 g_free(label);
6370 menu = g_list_prepend(menu, act);
6372 entry = entry->next;
6375 act = purple_menu_action_new(_("Send Email..."),
6376 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6377 NULL, NULL);
6378 menu = g_list_prepend(menu, act);
6380 gr_parent = purple_buddy_get_group(buddy);
6381 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6382 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6383 continue;
6385 group = (PurpleGroup *)g_node;
6386 if (group == gr_parent)
6387 continue;
6389 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6390 continue;
6392 act = purple_menu_action_new(purple_group_get_name(group),
6393 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6394 group->name, NULL);
6395 menu_groups = g_list_prepend(menu_groups, act);
6397 menu_groups = g_list_reverse(menu_groups);
6399 act = purple_menu_action_new(_("Copy to"),
6400 NULL,
6401 NULL, menu_groups);
6402 menu = g_list_prepend(menu, act);
6403 menu = g_list_reverse(menu);
6405 g_free(self);
6406 return menu;
6409 static GList *
6410 sipe_blist_node_menu(PurpleBlistNode *node)
6412 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6413 return sipe_buddy_menu((PurpleBuddy *) node);
6414 } else {
6415 return NULL;
6419 static gboolean
6420 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6422 gboolean ret = TRUE;
6423 char *username = (char *)trans->payload;
6425 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6426 PurpleBuddy *pbuddy;
6427 struct sipe_buddy *sbuddy;
6428 const char *alias;
6429 char *server_alias = NULL;
6430 char *email = NULL;
6431 const char *device_name = NULL;
6433 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6435 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6436 alias = purple_buddy_get_local_alias(pbuddy);
6438 if (sip)
6440 //will query buddy UA's capabilities and send answer to log
6441 sipe_options_request(sip, username);
6443 sbuddy = g_hash_table_lookup(sip->buddies, username);
6444 if (sbuddy)
6446 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6450 if (msg->response != 200) {
6451 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6452 } else {
6453 xmlnode *searchResults;
6454 xmlnode *mrow;
6456 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6457 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6458 if (!searchResults) {
6459 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6460 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6461 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6462 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6463 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6464 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6465 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6466 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6467 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6468 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6469 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6470 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6471 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6472 if (!email || strcmp("", email)) {
6473 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6474 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6478 xmlnode_free(searchResults);
6481 purple_notify_user_info_add_section_break(info);
6483 if (!server_alias || !strcmp("", server_alias)) {
6484 g_free(server_alias);
6485 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6486 if (server_alias) {
6487 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6491 // same as server alias, do not present
6492 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6493 if (alias)
6495 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6498 if (!email || !strcmp("", email)) {
6499 g_free(email);
6500 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6501 if (email) {
6502 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6506 if (device_name)
6508 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6511 /* show a buddy's user info in a nice dialog box */
6512 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6513 username, /* buddy's username */
6514 info, /* body */
6515 NULL, /* callback called when dialog closed */
6516 NULL); /* userdata for callback */
6518 return ret;
6522 * AD search first, LDAP based
6524 static void sipe_get_info(PurpleConnection *gc, const char *username)
6526 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6527 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6529 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6530 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6531 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6532 g_free(body);
6533 g_free(row);
6536 static PurplePlugin *my_protocol = NULL;
6538 static PurplePluginProtocolInfo prpl_info =
6541 NULL, /* user_splits */
6542 NULL, /* protocol_options */
6543 NO_BUDDY_ICONS, /* icon_spec */
6544 sipe_list_icon, /* list_icon */
6545 NULL, /* list_emblems */
6546 sipe_status_text, /* status_text */
6547 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6548 sipe_status_types, /* away_states */
6549 sipe_blist_node_menu, /* blist_node_menu */
6550 NULL, /* chat_info */
6551 NULL, /* chat_info_defaults */
6552 sipe_login, /* login */
6553 sipe_close, /* close */
6554 sipe_im_send, /* send_im */
6555 NULL, /* set_info */ // TODO maybe
6556 sipe_send_typing, /* send_typing */
6557 sipe_get_info, /* get_info */
6558 sipe_set_status, /* set_status */
6559 NULL, /* set_idle */
6560 NULL, /* change_passwd */
6561 sipe_add_buddy, /* add_buddy */
6562 NULL, /* add_buddies */
6563 sipe_remove_buddy, /* remove_buddy */
6564 NULL, /* remove_buddies */
6565 sipe_add_permit, /* add_permit */
6566 sipe_add_deny, /* add_deny */
6567 sipe_add_deny, /* rem_permit */
6568 sipe_add_permit, /* rem_deny */
6569 dummy_permit_deny, /* set_permit_deny */
6570 NULL, /* join_chat */
6571 NULL, /* reject_chat */
6572 NULL, /* get_chat_name */
6573 NULL, /* chat_invite */
6574 sipe_chat_leave, /* chat_leave */
6575 NULL, /* chat_whisper */
6576 sipe_chat_send, /* chat_send */
6577 sipe_keep_alive, /* keepalive */
6578 NULL, /* register_user */
6579 NULL, /* get_cb_info */ // deprecated
6580 NULL, /* get_cb_away */ // deprecated
6581 sipe_alias_buddy, /* alias_buddy */
6582 sipe_group_buddy, /* group_buddy */
6583 sipe_rename_group, /* rename_group */
6584 NULL, /* buddy_free */
6585 sipe_convo_closed, /* convo_closed */
6586 purple_normalize_nocase, /* normalize */
6587 NULL, /* set_buddy_icon */
6588 sipe_remove_group, /* remove_group */
6589 NULL, /* get_cb_real_name */ // TODO?
6590 NULL, /* set_chat_topic */
6591 NULL, /* find_blist_chat */
6592 NULL, /* roomlist_get_list */
6593 NULL, /* roomlist_cancel */
6594 NULL, /* roomlist_expand_category */
6595 NULL, /* can_receive_file */
6596 NULL, /* send_file */
6597 NULL, /* new_xfer */
6598 NULL, /* offline_message */
6599 NULL, /* whiteboard_prpl_ops */
6600 sipe_send_raw, /* send_raw */
6601 NULL, /* roomlist_room_serialize */
6602 NULL, /* unregister_user */
6603 NULL, /* send_attention */
6604 NULL, /* get_attention_types */
6606 sizeof(PurplePluginProtocolInfo), /* struct_size */
6607 sipe_get_account_text_table, /* get_account_text_table */
6611 static PurplePluginInfo info = {
6612 PURPLE_PLUGIN_MAGIC,
6613 PURPLE_MAJOR_VERSION,
6614 PURPLE_MINOR_VERSION,
6615 PURPLE_PLUGIN_PROTOCOL, /**< type */
6616 NULL, /**< ui_requirement */
6617 0, /**< flags */
6618 NULL, /**< dependencies */
6619 PURPLE_PRIORITY_DEFAULT, /**< priority */
6620 "prpl-sipe", /**< id */
6621 "Microsoft LCS/OCS", /**< name */
6622 VERSION, /**< version */
6623 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6624 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6625 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6626 "Gabriel Burt <gburt@novell.com>", /**< author */
6627 PURPLE_WEBSITE, /**< homepage */
6628 sipe_plugin_load, /**< load */
6629 sipe_plugin_unload, /**< unload */
6630 sipe_plugin_destroy, /**< destroy */
6631 NULL, /**< ui_info */
6632 &prpl_info, /**< extra_info */
6633 NULL,
6634 sipe_actions,
6635 NULL,
6636 NULL,
6637 NULL,
6638 NULL
6641 static void sipe_plugin_destroy(PurplePlugin *plugin)
6643 GList *entry;
6645 entry = prpl_info.protocol_options;
6646 while (entry) {
6647 purple_account_option_destroy(entry->data);
6648 entry = g_list_delete_link(entry, entry);
6650 prpl_info.protocol_options = NULL;
6652 entry = prpl_info.user_splits;
6653 while (entry) {
6654 purple_account_user_split_destroy(entry->data);
6655 entry = g_list_delete_link(entry, entry);
6657 prpl_info.user_splits = NULL;
6660 static void init_plugin(PurplePlugin *plugin)
6662 PurpleAccountUserSplit *split;
6663 PurpleAccountOption *option;
6665 srand(time(NULL));
6667 #ifdef ENABLE_NLS
6668 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6669 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6670 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6671 textdomain(GETTEXT_PACKAGE);
6672 #endif
6674 purple_plugin_register(plugin);
6676 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
6677 purple_account_user_split_set_reverse(split, FALSE);
6678 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6680 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
6681 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6682 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6683 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6685 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
6686 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6687 // Translators: noun (networking port)
6688 option = purple_account_option_int_new(_("Port"), "port", 5061);
6689 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6691 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6692 purple_account_option_add_list_item(option, _("Auto"), "auto");
6693 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6694 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6695 purple_account_option_add_list_item(option, _("UDP"), "udp");
6696 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6698 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6699 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6701 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6702 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6704 #ifdef USE_KERBEROS
6705 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6706 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6708 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6709 * No login/password is taken into account if this option present,
6710 * instead used default credentials stored in OS.
6712 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6713 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6714 #endif
6715 my_protocol = plugin;
6718 /* I had to redefined the function for it load, but works */
6719 gboolean purple_init_plugin(PurplePlugin *plugin){
6720 plugin->info = &(info);
6721 init_plugin((plugin));
6722 sipe_plugin_load((plugin));
6723 return purple_plugin_register(plugin);
6727 Local Variables:
6728 mode: c
6729 c-file-style: "bsd"
6730 indent-tabs-mode: t
6731 tab-width: 8
6732 End: