Refactor SIP session dialog API
[siplcs.git] / src / sipe.c
blob3727848646a9ecb0eb3f24b7401dc66a3639a296
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 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2481 static gchar *
2482 get_end_points (struct sipe_account_data *sip,
2483 struct sip_im_session *session)
2485 gchar *res;
2487 if (session == NULL) {
2488 return NULL;
2491 res = g_strdup_printf("<sip:%s>", sip->username);
2493 SIPE_DIALOG_FOREACH {
2494 gchar *tmp = res;
2495 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2496 g_free(tmp);
2498 if (dialog->theirepid) {
2499 tmp = res;
2500 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2501 g_free(tmp);
2503 } SIPE_DIALOG_FOREACH_END;
2505 return res;
2508 static struct sip_im_session *
2509 find_chat_session_by_id (struct sipe_account_data *sip,
2510 int id)
2512 struct sip_im_session *session;
2513 GSList *entry;
2514 if (sip == NULL) {
2515 return NULL;
2518 entry = sip->im_sessions;
2519 while (entry) {
2520 session = entry->data;
2521 if (id == session->chat_id) {
2522 return session;
2524 entry = entry->next;
2526 return NULL;
2529 static struct sip_im_session *
2530 find_chat_session_by_name (struct sipe_account_data *sip,
2531 const char *chat_name)
2533 struct sip_im_session *session;
2534 GSList *entry;
2535 if (sip == NULL || chat_name == NULL) {
2536 return NULL;
2539 entry = sip->im_sessions;
2540 while (entry) {
2541 session = entry->data;
2542 if (session->chat_name && !g_strcasecmp(chat_name, session->chat_name)) {
2543 return session;
2545 entry = entry->next;
2547 return NULL;
2550 static struct sip_im_session *
2551 find_chat_session (struct sipe_account_data *sip,
2552 const char *callid)
2554 struct sip_im_session *session;
2555 GSList *entry;
2556 if (sip == NULL || callid == NULL) {
2557 return NULL;
2560 entry = sip->im_sessions;
2561 while (entry) {
2562 session = entry->data;
2563 if (session->callid && !g_strcasecmp(callid, session->callid)) {
2564 return session;
2566 entry = entry->next;
2568 return NULL;
2571 static struct sip_im_session * find_im_session (struct sipe_account_data *sip, const char *who)
2573 struct sip_im_session *session;
2574 GSList *entry;
2575 if (sip == NULL || who == NULL) {
2576 return NULL;
2579 entry = sip->im_sessions;
2580 while (entry) {
2581 session = entry->data;
2582 if (session->with && !strcmp(who, session->with)) {
2583 return session;
2585 entry = entry->next;
2587 return NULL;
2590 struct sip_im_session *
2591 create_chat_session (struct sipe_account_data *sip)
2593 struct sip_im_session *session = g_new0(struct sip_im_session, 1);
2594 session->callid = gencallid();
2595 session->is_multiparty = TRUE;
2596 session->chat_id = rand();
2597 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2598 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2599 return session;
2602 static struct sip_im_session * find_or_create_chat_session (struct sipe_account_data *sip, const char *callid)
2604 struct sip_im_session *session = find_chat_session(sip, callid);
2605 if (!session) {
2606 session = create_chat_session(sip);
2607 session->callid = g_strdup(callid);
2609 return session;
2612 static struct sip_im_session * find_or_create_im_session (struct sipe_account_data *sip, const char *who)
2614 struct sip_im_session *session = find_im_session(sip, who);
2615 if (!session) {
2616 session = g_new0(struct sip_im_session, 1);
2617 session->is_multiparty = FALSE;
2618 session->with = g_strdup(who);
2619 session->unconfirmed_messages = g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free);
2620 sip->im_sessions = g_slist_append(sip->im_sessions, session);
2622 return session;
2625 void im_session_destroy(struct sipe_account_data *sip, struct sip_im_session * session)
2627 GSList *entry;
2629 sip->im_sessions = g_slist_remove(sip->im_sessions, session);
2631 sipe_dialog_remove_all(session);
2633 entry = session->outgoing_message_queue;
2634 while (entry) {
2635 g_free(entry->data);
2636 entry = g_slist_remove(entry, entry->data);
2639 entry = session->pending_invite_queue;
2640 while (entry) {
2641 g_free(entry->data);
2642 entry = g_slist_remove(entry, entry->data);
2645 g_hash_table_destroy(session->unconfirmed_messages);
2647 g_free(session->with);
2648 g_free(session->chat_name);
2649 g_free(session->callid);
2650 g_free(session->roster_manager);
2651 g_free(session->focus_uri);
2652 g_free(session->im_mcu_uri);
2653 g_free(session);
2656 static gboolean
2657 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2659 gboolean ret = TRUE;
2661 if (msg->response != 200) {
2662 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2663 return FALSE;
2666 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2668 return ret;
2672 * Asks UA/proxy about its capabilities.
2674 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2676 gchar *to = strstr(who, "sip:") ? g_strdup(who) : g_strdup_printf("sip:%s", who);
2677 gchar *contact = get_contact(sip);
2678 gchar *request;
2679 request = g_strdup_printf(
2680 "Accept: application/sdp\r\n"
2681 "Contact: %s\r\n", contact);
2683 g_free(contact);
2685 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2687 g_free(to);
2688 g_free(request);
2691 static void sipe_present_message_undelivered_err(gchar *with, struct sipe_account_data *sip, gchar *message)
2693 char *msg, *msg_tmp;
2694 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2695 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2696 g_free(msg_tmp);
2697 msg_tmp = g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2698 "possibly because one or more persons are offline:\n%s") ,
2699 msg ? msg : "");
2700 purple_conv_present_error(with, sip->account, msg_tmp);
2701 g_free(msg);
2702 g_free(msg_tmp);
2705 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session);
2707 static gboolean
2708 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2710 gboolean ret = TRUE;
2711 gchar * with = parse_from(sipmsg_find_header(msg, "To"));
2712 struct sip_im_session * session = find_im_session(sip, with);
2713 struct sip_dialog *dialog;
2714 gchar *cseq;
2715 char *key;
2716 gchar *message;
2718 if (!session) {
2719 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2720 g_free(with);
2721 return FALSE;
2724 dialog = sipe_dialog_find(session, with);
2725 if (!dialog) {
2726 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2727 g_free(with);
2728 return FALSE;
2731 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2732 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2733 g_free(cseq);
2734 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2736 if (msg->response != 200) {
2737 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2739 sipe_present_message_undelivered_err(with, sip, message);
2740 im_session_destroy(sip, session);
2741 ret = FALSE;
2742 } else {
2743 g_hash_table_remove(session->unconfirmed_messages, key);
2744 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2745 key, g_hash_table_size(session->unconfirmed_messages));
2748 g_free(key);
2749 g_free(with);
2751 if (ret) sipe_im_process_queue(sip, session);
2752 return ret;
2755 static gboolean
2756 sipe_is_election_finished(struct sipe_account_data *sip,
2757 struct sip_im_session *session);
2759 static void
2760 sipe_election_result(struct sipe_account_data *sip,
2761 void *sess);
2763 static gboolean
2764 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2766 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2767 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2768 struct sip_dialog *dialog;
2769 struct sip_im_session *session;
2771 session = find_chat_session(sip, callid);
2772 if (!session) {
2773 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2774 return FALSE;
2777 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2778 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2779 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2780 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2782 if (xn_request_rm_response) {
2783 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2784 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2786 dialog = sipe_dialog_find(session, with);
2787 if (!dialog) {
2788 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2789 return FALSE;
2792 if (allow && !g_strcasecmp(allow, "true")) {
2793 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2794 dialog->election_vote = 1;
2795 } else if (allow && !g_strcasecmp(allow, "false")) {
2796 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2797 dialog->election_vote = -1;
2800 if (sipe_is_election_finished(sip, session)) {
2801 sipe_election_result(sip, session);
2804 } else if (xn_set_rm_response) {
2807 xmlnode_free(xn_action);
2811 return TRUE;
2814 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2816 gchar *hdr;
2817 gchar *tmp;
2818 char *msgformat;
2819 char *msgtext;
2820 gchar *msgr_value;
2821 gchar *msgr;
2823 sipe_parse_html(msg, &msgformat, &msgtext);
2824 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2826 msgr_value = sipmsg_get_msgr_string(msgformat);
2827 g_free(msgformat);
2828 if (msgr_value) {
2829 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2830 g_free(msgr_value);
2831 } else {
2832 msgr = g_strdup("");
2835 tmp = get_contact(sip);
2836 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2837 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2838 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2839 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2840 g_free(tmp);
2841 g_free(msgr);
2843 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2844 g_free(msgtext);
2845 g_free(hdr);
2849 static void
2850 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_im_session * session)
2852 GSList *entry2 = session->outgoing_message_queue;
2853 while (entry2) {
2854 char *queued_msg = entry2->data;
2856 if (session->is_multiparty) {
2857 gchar *who = g_strdup_printf("sip:%s", sip->username);
2858 serv_got_chat_in(sip->gc, session->chat_id, who,
2859 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2860 g_free(who);
2863 SIPE_DIALOG_FOREACH {
2864 char *key;
2866 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
2868 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2869 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2870 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2871 key, g_hash_table_size(session->unconfirmed_messages));
2872 g_free(key);
2874 sipe_send_message(sip, dialog, queued_msg);
2875 } SIPE_DIALOG_FOREACH_END;
2877 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2878 g_free(queued_msg);
2882 static void
2883 sipe_refer_notify(struct sipe_account_data *sip,
2884 struct sip_im_session *session,
2885 const gchar *who,
2886 int status,
2887 const gchar *desc)
2889 gchar *hdr;
2890 gchar *body;
2891 struct sip_dialog *dialog = sipe_dialog_find(session, who);
2893 hdr = g_strdup_printf(
2894 "Event: refer\r\n"
2895 "Subscription-State: %s\r\n"
2896 "Content-Type: message/sipfrag\r\n",
2897 status >= 200 ? "terminated" : "active");
2899 body = g_strdup_printf(
2900 "SIP/2.0 %d %s\r\n",
2901 status, desc);
2903 dialog->outgoing_invite = send_sip_request(sip->gc, "NOTIFY",
2904 who, who, hdr, body, dialog, NULL);
2906 g_free(hdr);
2907 g_free(body);
2910 static gboolean
2911 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2913 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2914 struct sip_im_session *session;
2915 struct sip_dialog *dialog;
2916 char *cseq;
2917 char *key;
2918 gchar *message;
2919 struct sipmsg *request_msg = trans->msg;
2921 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2922 gchar *referred_by;
2924 session = find_chat_session(sip, callid);
2925 if (!session) {
2926 session = find_im_session(sip, with);
2928 if (!session) {
2929 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2930 g_free(with);
2931 return FALSE;
2934 dialog = sipe_dialog_find(session, with);
2935 if (!dialog) {
2936 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2937 g_free(with);
2938 return FALSE;
2941 sipe_dialog_parse(dialog, msg, TRUE);
2943 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2944 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2945 g_free(cseq);
2946 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2948 if (msg->response != 200) {
2949 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2951 sipe_present_message_undelivered_err(with, sip, message);
2952 im_session_destroy(sip, session);
2953 g_free(key);
2954 g_free(with);
2955 return FALSE;
2958 dialog->cseq = 0;
2959 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
2960 dialog->outgoing_invite = NULL;
2961 dialog->is_established = TRUE;
2963 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
2964 if (referred_by) {
2965 sipe_refer_notify(sip, session, referred_by, 200, "OK");
2966 g_free(referred_by);
2969 /* add user to chat if it is a multiparty session */
2970 if (session->is_multiparty) {
2971 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
2972 with, NULL,
2973 PURPLE_CBFLAGS_NONE, TRUE);
2976 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2977 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2978 if (session->outgoing_message_queue) {
2979 char *queued_msg = session->outgoing_message_queue->data;
2980 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2981 g_free(queued_msg);
2985 sipe_im_process_queue(sip, session);
2987 g_hash_table_remove(session->unconfirmed_messages, key);
2988 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2989 key, g_hash_table_size(session->unconfirmed_messages));
2991 g_free(key);
2992 g_free(with);
2993 return TRUE;
2997 void
2998 sipe_invite(struct sipe_account_data *sip,
2999 struct sip_im_session *session,
3000 const gchar *who,
3001 const gchar *msg_body,
3002 const gchar *referred_by,
3003 const gboolean is_triggered)
3005 gchar *hdr;
3006 gchar *to;
3007 gchar *contact;
3008 gchar *body;
3009 gchar *self;
3010 char *ms_text_format = g_strdup("");
3011 gchar *roster_manager;
3012 gchar *end_points;
3013 gchar *referred_by_str;
3014 struct sip_dialog *dialog = sipe_dialog_find(session, who);
3016 if (dialog && dialog->is_established) {
3017 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
3018 return;
3021 if (!dialog) {
3022 dialog = sipe_dialog_add(session);
3023 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
3024 dialog->with = g_strdup(who);
3027 if (!(dialog->ourtag)) {
3028 dialog->ourtag = gentag();
3032 if (strstr(who, "sip:")) {
3033 to = g_strdup(who);
3034 } else {
3035 to = g_strdup_printf("sip:%s", who);
3038 if (msg_body) {
3039 char *msgformat;
3040 char *msgtext;
3041 char *base64_msg;
3042 gchar *msgr_value;
3043 gchar *msgr;
3044 char *key;
3046 sipe_parse_html(msg_body, &msgformat, &msgtext);
3047 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
3049 msgr_value = sipmsg_get_msgr_string(msgformat);
3050 g_free(msgformat);
3051 msgr = "";
3052 if (msgr_value) {
3053 msgr = g_strdup_printf(";msgr=%s", msgr_value);
3054 g_free(msgr_value);
3057 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
3058 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
3059 g_free(msgtext);
3060 g_free(msgr);
3061 g_free(base64_msg);
3063 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
3064 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
3065 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3066 key, g_hash_table_size(session->unconfirmed_messages));
3067 g_free(key);
3070 contact = get_contact(sip);
3071 end_points = get_end_points(sip, session);
3072 self = g_strdup_printf("sip:%s", sip->username);
3073 roster_manager = g_strdup_printf(
3074 "Roster-Manager: %s\r\n"
3075 "EndPoints: %s\r\n",
3076 self,
3077 end_points);
3078 referred_by_str = referred_by ?
3079 g_strdup_printf(
3080 "Referred-By: %s\r\n",
3081 referred_by)
3082 : g_strdup("");
3083 hdr = g_strdup_printf(
3084 "%s"
3085 "%s"
3086 "%s"
3087 "%s"
3088 "Contact: %s\r\n%s"
3089 "Content-Type: application/sdp\r\n",
3090 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3091 referred_by_str,
3092 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3093 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3094 contact,
3095 ms_text_format);
3096 g_free(ms_text_format);
3097 g_free(self);
3099 body = g_strdup_printf(
3100 "v=0\r\n"
3101 "o=- 0 0 IN IP4 %s\r\n"
3102 "s=session\r\n"
3103 "c=IN IP4 %s\r\n"
3104 "t=0 0\r\n"
3105 "m=message %d sip null\r\n"
3106 "a=accept-types:text/plain text/html image/gif "
3107 "multipart/alternative application/im-iscomposing+xml\r\n",
3108 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3110 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3111 to, to, hdr, body, dialog, process_invite_response);
3113 g_free(to);
3114 g_free(roster_manager);
3115 g_free(end_points);
3116 g_free(referred_by_str);
3117 g_free(body);
3118 g_free(hdr);
3119 g_free(contact);
3122 static void
3123 sipe_refer(struct sipe_account_data *sip,
3124 struct sip_im_session *session,
3125 const gchar *who)
3127 gchar *hdr;
3128 gchar *contact;
3129 struct sip_dialog *dialog = sipe_dialog_find(session,
3130 session->roster_manager);
3132 contact = get_contact(sip);
3133 hdr = g_strdup_printf(
3134 "Contact: %s\r\n"
3135 "Refer-to: <%s>\r\n"
3136 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3137 "Require: com.microsoft.rtc-multiparty\r\n",
3138 contact,
3139 who,
3140 sip->username,
3141 dialog->ourtag ? ";tag=" : "",
3142 dialog->ourtag ? dialog->ourtag : "",
3143 get_epid(sip));
3145 send_sip_request(sip->gc, "REFER",
3146 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3148 g_free(hdr);
3149 g_free(contact);
3152 static void
3153 sipe_send_election_request_rm(struct sipe_account_data *sip,
3154 struct sip_dialog *dialog,
3155 int bid)
3157 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3159 gchar *body = g_strdup_printf(
3160 "<?xml version=\"1.0\"?>\r\n"
3161 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3162 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3163 sip->username, bid);
3165 send_sip_request(sip->gc, "INFO",
3166 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3168 g_free(body);
3171 static void
3172 sipe_send_election_set_rm(struct sipe_account_data *sip,
3173 struct sip_dialog *dialog)
3175 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3177 gchar *body = g_strdup_printf(
3178 "<?xml version=\"1.0\"?>\r\n"
3179 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3180 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3181 sip->username);
3183 send_sip_request(sip->gc, "INFO",
3184 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3186 g_free(body);
3189 static void
3190 im_session_close (struct sipe_account_data *sip, struct sip_im_session * session)
3192 if (session) {
3193 SIPE_DIALOG_FOREACH {
3194 /* @TODO slow down BYE message sending rate */
3195 /* @see single subscription code */
3196 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3197 } SIPE_DIALOG_FOREACH_END;
3199 im_session_destroy(sip, session);
3203 static void
3204 sipe_convo_closed(PurpleConnection * gc, const char *who)
3206 struct sipe_account_data *sip = gc->proto_data;
3208 purple_debug_info("sipe", "conversation with %s closed\n", who);
3209 im_session_close(sip, find_im_session(sip, who));
3212 static void
3213 sipe_chat_leave (PurpleConnection *gc, int id)
3215 struct sipe_account_data *sip = gc->proto_data;
3216 struct sip_im_session * session = find_chat_session_by_id(sip, id);
3217 im_session_close(sip, session);
3220 static void
3221 im_session_close_all (struct sipe_account_data *sip)
3223 GSList *entry = sip->im_sessions;
3224 while (entry) {
3225 im_session_close (sip, entry->data);
3226 entry = sip->im_sessions;
3230 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
3232 struct sipe_account_data *sip = gc->proto_data;
3233 struct sip_im_session *session;
3234 struct sip_dialog *dialog;
3236 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3238 session = find_or_create_im_session(sip, who);
3239 dialog = sipe_dialog_find(session, who);
3241 // Queue the message
3242 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3244 if (dialog && dialog->callid) {
3245 sipe_im_process_queue(sip, session);
3246 } else if (!dialog || !dialog->outgoing_invite) {
3247 // Need to send the INVITE to get the outgoing dialog setup
3248 sipe_invite(sip, session, who, what, NULL, FALSE);
3251 return 1;
3254 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags)
3256 struct sipe_account_data *sip = gc->proto_data;
3257 struct sip_im_session *session;
3259 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3261 session = find_chat_session_by_id(sip, id);
3263 // Queue the message
3264 if (session) {
3265 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3266 g_strdup(what));
3267 sipe_im_process_queue(sip, session);
3270 return 1;
3273 /* End IM Session (INVITE and MESSAGE methods) */
3275 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3277 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3278 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3279 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3281 struct sip_im_session *session = find_chat_session(sip, callid);
3282 if (!session) {
3283 session = find_im_session(sip, from);
3285 if (!session) {
3286 g_free(from);
3287 return;
3290 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3291 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3292 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3293 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3295 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
3297 if (xn_request_rm) {
3298 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3299 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3300 gchar *body = g_strdup_printf(
3301 "<?xml version=\"1.0\"?>\r\n"
3302 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3303 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3304 sip->username,
3305 session->bid < bid ? "true" : "false");
3306 send_sip_response(sip->gc, msg, 200, "OK", body);
3307 g_free(body);
3308 } else if (xn_set_rm) {
3309 gchar *body;
3310 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3311 g_free(session->roster_manager);
3312 session->roster_manager = g_strdup(rm);
3314 body = g_strdup_printf(
3315 "<?xml version=\"1.0\"?>\r\n"
3316 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3317 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3318 sip->username);
3319 send_sip_response(sip->gc, msg, 200, "OK", body);
3320 g_free(body);
3322 xmlnode_free(xn_action);
3324 } else {
3325 /* looks like purple lacks typing notification for chat */
3326 if (!session->is_multiparty) {
3327 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3330 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3332 g_free(from);
3335 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3337 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3338 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3339 struct sip_im_session *session;
3341 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3343 session = find_chat_session(sip, callid);
3344 if (!session) {
3345 session = find_im_session(sip, from);
3347 if (!session) {
3348 g_free(from);
3349 return;
3352 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3353 g_free(session->roster_manager);
3354 session->roster_manager = NULL;
3357 if (!session->is_multiparty) {
3358 // TODO Let the user know the other user left the conversation?
3359 im_session_destroy(sip, session);
3360 } else {
3361 sipe_dialog_remove(session, from);
3363 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3365 if (!sipe_dialog_any(session)) {
3366 im_session_destroy(sip, session);
3370 g_free(from);
3373 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3375 gchar *self = g_strdup_printf("sip:%s", sip->username);
3376 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3377 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3378 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3379 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3380 struct sip_im_session *session;
3381 struct sip_dialog *dialog;
3383 session = find_chat_session(sip, callid);
3384 dialog = sipe_dialog_find(session, from);
3386 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3387 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3388 } else {
3389 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3391 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3394 g_free(self);
3395 g_free(from);
3396 g_free(refer_to);
3397 g_free(referred_by);
3400 static unsigned int
3401 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3403 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3404 struct sip_im_session *session;
3405 struct sip_dialog *dialog;
3407 if (state == PURPLE_NOT_TYPING)
3408 return 0;
3410 session = find_im_session(sip, who);
3411 dialog = sipe_dialog_find(session, who);
3413 if (session && dialog) {
3414 send_sip_request(gc, "INFO", who, who,
3415 "Content-Type: application/xml\r\n",
3416 SIPE_SEND_TYPING, dialog, NULL);
3418 return SIPE_TYPING_SEND_TIMEOUT;
3421 static gboolean resend_timeout(struct sipe_account_data *sip)
3423 GSList *tmp = sip->transactions;
3424 time_t currtime = time(NULL);
3425 while (tmp) {
3426 struct transaction *trans = tmp->data;
3427 tmp = tmp->next;
3428 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3429 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3430 /* TODO 408 */
3431 } else {
3432 if ((currtime - trans->time > 2) && trans->retries == 0) {
3433 trans->retries++;
3434 sendout_sipmsg(sip, trans->msg);
3438 return TRUE;
3441 static void do_reauthenticate_cb(struct sipe_account_data *sip, void *unused)
3443 /* register again when security token expires */
3444 /* we have to start a new authentication as the security token
3445 * is almost expired by sending a not signed REGISTER message */
3446 purple_debug_info("sipe", "do a full reauthentication\n");
3447 sipe_auth_free(&sip->registrar);
3448 sipe_auth_free(&sip->proxy);
3449 sip->registerstatus = 0;
3450 do_register(sip);
3451 sip->reauthenticate_set = FALSE;
3454 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3456 gchar *from;
3457 gchar *contenttype;
3458 gboolean found = FALSE;
3460 from = parse_from(sipmsg_find_header(msg, "From"));
3462 if (!from) return;
3464 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3466 contenttype = sipmsg_find_header(msg, "Content-Type");
3467 if (!strncmp(contenttype, "text/plain", 10) || !strncmp(contenttype, "text/html", 9)) {
3469 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3470 gchar *html = get_html_message(contenttype, msg->body);
3472 struct sip_im_session *session = find_chat_session(sip, callid);
3473 if (!session) {
3474 session = find_im_session(sip, from);
3477 if (session && session->is_multiparty) {
3478 serv_got_chat_in(sip->gc, session->chat_id, from,
3479 PURPLE_MESSAGE_RECV, html, time(NULL));
3480 } else {
3481 serv_got_im(sip->gc, from, html, 0, time(NULL));
3483 g_free(html);
3484 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3485 found = TRUE;
3487 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3488 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3489 xmlnode *state;
3490 gchar *statedata;
3492 if (!isc) {
3493 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3494 return;
3497 state = xmlnode_get_child(isc, "state");
3499 if (!state) {
3500 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3501 xmlnode_free(isc);
3502 return;
3505 statedata = xmlnode_get_data(state);
3506 if (statedata) {
3507 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3508 else serv_got_typing_stopped(sip->gc, from);
3510 g_free(statedata);
3512 xmlnode_free(isc);
3513 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3514 found = TRUE;
3516 if (!found) {
3517 purple_debug_info("sipe", "got unknown mime-type");
3518 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3520 g_free(from);
3523 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3525 gchar *ms_text_format;
3526 gchar *body;
3527 gchar *newTag = gentag();
3528 gchar *oldHeader;
3529 gchar *newHeader;
3530 gboolean is_multiparty = FALSE;
3531 gboolean is_triggered = FALSE;
3532 gboolean was_multiparty = TRUE;
3533 gboolean just_joined = FALSE;
3534 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3535 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3536 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3537 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
3538 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
3539 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
3540 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3541 GSList *end_points = NULL;
3542 struct sip_im_session *session;
3544 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3546 /* Invitation to join conference */
3547 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
3548 process_incoming_invite_conf(sip, msg);
3549 return;
3552 /* Only accept text invitations */
3553 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3554 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3555 return;
3558 // TODO There *must* be a better way to clean up the To header to add a tag...
3559 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3560 oldHeader = sipmsg_find_header(msg, "To");
3561 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3562 sipmsg_remove_header_now(msg, "To");
3563 sipmsg_add_header_now(msg, "To", newHeader);
3564 g_free(newHeader);
3566 if (end_points_hdr) {
3567 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
3569 if (g_slist_length(end_points) > 2) {
3570 is_multiparty = TRUE;
3573 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
3574 is_triggered = TRUE;
3575 is_multiparty = TRUE;
3578 session = find_chat_session(sip, callid);
3579 /* Convert to multiparty */
3580 if (session && is_multiparty && !session->is_multiparty) {
3581 g_free(session->with);
3582 session->with = NULL;
3583 was_multiparty = FALSE;
3584 session->is_multiparty = TRUE;
3585 session->chat_id = rand();
3588 if (!session && is_multiparty) {
3589 session = find_or_create_chat_session(sip, callid);
3591 /* IM session */
3592 if (!session) {
3593 session = find_or_create_im_session(sip, from);
3596 if (!session->callid) {
3597 session->callid = g_strdup(callid);
3600 session->is_multiparty = is_multiparty;
3601 if (roster_manager) {
3602 session->roster_manager = g_strdup(roster_manager);
3605 if (is_multiparty && end_points) {
3606 GSList *entry = end_points;
3607 while (entry) {
3608 struct sip_dialog *dialog;
3609 struct sipendpoint *end_point = entry->data;
3610 entry = entry->next;
3612 if (!g_strcasecmp(from, end_point->contact) ||
3613 !g_strcasecmp(to, end_point->contact))
3614 continue;
3616 dialog = sipe_dialog_find(session, end_point->contact);
3617 if (dialog) {
3618 g_free(dialog->theirepid);
3619 dialog->theirepid = end_point->epid;
3620 end_point->epid = NULL;
3621 } else {
3622 dialog = sipe_dialog_add(session);
3624 dialog->callid = g_strdup(session->callid);
3625 dialog->with = end_point->contact;
3626 end_point->contact = NULL;
3627 dialog->theirepid = end_point->epid;
3628 end_point->epid = NULL;
3630 just_joined = TRUE;
3632 /* send triggered INVITE */
3633 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
3638 if (end_points) {
3639 GSList *entry = end_points;
3640 while (entry) {
3641 struct sipendpoint *end_point = entry->data;
3642 entry = entry->next;
3643 g_free(end_point->contact);
3644 g_free(end_point->epid);
3645 g_free(end_point);
3647 g_slist_free(end_points);
3650 if (session) {
3651 struct sip_dialog *dialog = sipe_dialog_find(session, from);
3652 if (dialog) {
3653 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3654 } else {
3655 dialog = sipe_dialog_add(session);
3657 dialog->callid = g_strdup(session->callid);
3658 dialog->with = g_strdup(from);
3659 sipe_dialog_parse(dialog, msg, FALSE);
3661 if (!dialog->ourtag) {
3662 dialog->ourtag = newTag;
3663 newTag = NULL;
3666 just_joined = TRUE;
3668 } else {
3669 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3671 g_free(newTag);
3673 if (is_multiparty && !session->conv) {
3674 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3675 gchar *self = g_strdup_printf("sip:%s", sip->username);
3676 /* create prpl chat */
3677 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3678 session->chat_name = g_strdup(chat_name);
3679 /* add self */
3680 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3681 self, NULL,
3682 PURPLE_CBFLAGS_NONE, FALSE);
3683 g_free(chat_name);
3684 g_free(self);
3687 if (is_multiparty && !was_multiparty) {
3688 /* add current IM counterparty to chat */
3689 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3690 sipe_dialog_first(session)->with, NULL,
3691 PURPLE_CBFLAGS_NONE, FALSE);
3694 /* add inviting party */
3695 if (just_joined) {
3696 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3697 from, NULL,
3698 PURPLE_CBFLAGS_NONE, TRUE);
3701 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3702 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3703 if (ms_text_format) {
3704 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3706 gchar *html = get_html_message(ms_text_format, NULL);
3707 if (html) {
3708 if (is_multiparty) {
3709 serv_got_chat_in(sip->gc, session->chat_id, from,
3710 PURPLE_MESSAGE_RECV, html, time(NULL));
3711 } else {
3712 serv_got_im(sip->gc, from, html, 0, time(NULL));
3714 g_free(html);
3715 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3719 g_free(from);
3721 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3722 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3723 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3725 body = g_strdup_printf(
3726 "v=0\r\n"
3727 "o=- 0 0 IN IP4 %s\r\n"
3728 "s=session\r\n"
3729 "c=IN IP4 %s\r\n"
3730 "t=0 0\r\n"
3731 "m=message %d sip sip:%s\r\n"
3732 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3733 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3734 sip->realport, sip->username);
3735 send_sip_response(sip->gc, msg, 200, "OK", body);
3736 g_free(body);
3739 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3741 gchar *body;
3743 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
3744 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3745 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3747 body = g_strdup_printf(
3748 "v=0\r\n"
3749 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3750 "s=session\r\n"
3751 "c=IN IP4 0.0.0.0\r\n"
3752 "t=0 0\r\n"
3753 "m=message %d sip sip:%s\r\n"
3754 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3755 sip->realport, sip->username);
3756 send_sip_response(sip->gc, msg, 200, "OK", body);
3757 g_free(body);
3760 static void sipe_connection_cleanup(struct sipe_account_data *);
3761 static void create_connection(struct sipe_account_data *, gchar *, int);
3763 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3765 gchar *tmp;
3766 const gchar *expires_header;
3767 int expires, i;
3768 GSList *hdr = msg->headers;
3769 GSList *entry;
3770 struct siphdrelement *elem;
3772 expires_header = sipmsg_find_header(msg, "Expires");
3773 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3774 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3776 switch (msg->response) {
3777 case 200:
3778 if (expires == 0) {
3779 sip->registerstatus = 0;
3780 } else {
3781 gchar *contact_hdr = NULL;
3782 gchar *gruu = NULL;
3783 gchar *epid;
3784 gchar *uuid;
3785 gchar *timeout;
3787 if (!sip->reregister_set) {
3788 gchar *action_name = g_strdup_printf("<%s>", "registration");
3789 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3790 g_free(action_name);
3791 sip->reregister_set = TRUE;
3794 sip->registerstatus = 3;
3796 #ifdef USE_KERBEROS
3797 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3798 #endif
3799 tmp = sipmsg_find_auth_header(msg, "NTLM");
3800 #ifdef USE_KERBEROS
3801 } else {
3802 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3804 #endif
3805 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3806 fill_auth(sip, tmp, &sip->registrar);
3808 if (!sip->reauthenticate_set) {
3809 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3810 guint reauth_timeout;
3811 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3812 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3813 reauth_timeout = sip->registrar.expires - 300;
3814 } else {
3815 /* NTLM: we have to reauthenticate as our security token expires
3816 after eight hours (be five minutes early) */
3817 reauth_timeout = (8 * 3600) - 300;
3819 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3820 g_free(action_name);
3821 sip->reauthenticate_set = TRUE;
3824 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3826 epid = get_epid(sip);
3827 uuid = generateUUIDfromEPID(epid);
3828 g_free(epid);
3830 // There can be multiple Contact headers (one per location where the user is logged in) so
3831 // make sure to only get the one for this uuid
3832 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3833 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3834 if (valid_contact) {
3835 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3836 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3837 g_free(valid_contact);
3838 break;
3839 } else {
3840 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3843 g_free(uuid);
3845 g_free(sip->contact);
3846 if(gruu) {
3847 sip->contact = g_strdup_printf("<%s>", gruu);
3848 g_free(gruu);
3849 } else {
3850 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3851 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);
3853 sip->msrtc_event_categories = FALSE;
3854 sip->batched_support = FALSE;
3856 while(hdr)
3858 elem = hdr->data;
3859 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3860 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3861 sip->msrtc_event_categories = TRUE;
3862 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3864 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3865 sip->batched_support = TRUE;
3866 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3869 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3870 gchar **caps = g_strsplit(elem->value,",",0);
3871 i = 0;
3872 while (caps[i]) {
3873 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3874 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3875 i++;
3877 g_strfreev(caps);
3879 hdr = g_slist_next(hdr);
3882 if (!sip->subscribed) { //do it just once, not every re-register
3883 if(!sip->msrtc_event_categories){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3884 //sipe_options_request(sip, sip->sipdomain);
3886 entry = sip->allow_events;
3887 while (entry) {
3888 tmp = entry->data;
3889 if (tmp && !g_ascii_strcasecmp(tmp, "vnd-microsoft-roaming-contacts")) {
3890 sipe_subscribe_roaming_contacts(sip, msg);
3892 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-ACL")) {
3893 sipe_subscribe_roaming_acl(sip, msg);
3895 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-roaming-self")) {
3896 sipe_subscribe_roaming_self(sip, msg);
3898 if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning-v2")) {
3899 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3900 } else if (tmp && !g_ascii_strcasecmp(tmp,"vnd-microsoft-provisioning")) { // LSC2005
3901 sipe_subscribe_roaming_provisioning(sip, msg);
3903 if (tmp && !g_ascii_strcasecmp(tmp,"presence.wpending")) {
3904 sipe_subscribe_presence_wpending(sip, msg);
3906 entry = entry->next;
3908 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3909 sip->subscribed = TRUE;
3912 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3913 "timeout=", ";", NULL);
3914 if (timeout != NULL) {
3915 sscanf(timeout, "%u", &sip->keepalive_timeout);
3916 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3917 sip->keepalive_timeout);
3918 g_free(timeout);
3921 // Should we remove the transaction here?
3922 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3923 transactions_remove(sip, tc);
3925 break;
3926 case 301:
3928 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3930 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3931 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3932 gchar **tmp;
3933 gchar *hostname;
3934 int port = 0;
3935 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3936 int i = 1;
3938 tmp = g_strsplit(parts[0], ":", 0);
3939 hostname = g_strdup(tmp[0]);
3940 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3941 g_strfreev(tmp);
3943 while (parts[i]) {
3944 tmp = g_strsplit(parts[i], "=", 0);
3945 if (tmp[1]) {
3946 if (g_strcasecmp("transport", tmp[0]) == 0) {
3947 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3948 transport = SIPE_TRANSPORT_TCP;
3949 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3950 transport = SIPE_TRANSPORT_UDP;
3954 g_strfreev(tmp);
3955 i++;
3957 g_strfreev(parts);
3959 /* Close old connection */
3960 sipe_connection_cleanup(sip);
3962 /* Create new connection */
3963 sip->transport = transport;
3964 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3965 hostname, port, TRANSPORT_DESCRIPTOR);
3966 create_connection(sip, hostname, port);
3968 g_free(redirect);
3970 break;
3971 case 401:
3972 if (sip->registerstatus != 2) {
3973 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3974 if (sip->registrar.retries > 3) {
3975 sip->gc->wants_to_die = TRUE;
3976 purple_connection_error(sip->gc, _("Wrong Password"));
3977 return TRUE;
3979 #ifdef USE_KERBEROS
3980 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3981 #endif
3982 tmp = sipmsg_find_auth_header(msg, "NTLM");
3983 #ifdef USE_KERBEROS
3984 } else {
3985 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3987 #endif
3988 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3989 fill_auth(sip, tmp, &sip->registrar);
3990 sip->registerstatus = 2;
3991 if (sip->account->disconnecting) {
3992 do_register_exp(sip, 0);
3993 } else {
3994 do_register(sip);
3997 break;
3998 case 403:
4000 gchar *warning = sipmsg_find_header(msg, "Warning");
4001 if (warning != NULL) {
4002 /* Example header:
4003 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4005 gchar **tmp = g_strsplit(warning, "\"", 0);
4006 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
4007 g_strfreev(tmp);
4008 } else {
4009 warning = g_strdup(_("You have been rejected by the server"));
4012 sip->gc->wants_to_die = TRUE;
4013 purple_connection_error(sip->gc, warning);
4014 g_free(warning);
4015 return TRUE;
4017 break;
4018 case 404:
4020 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4021 if (warning != NULL) {
4022 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4023 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
4024 g_free(reason);
4025 } else {
4026 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4029 sip->gc->wants_to_die = TRUE;
4030 purple_connection_error(sip->gc, warning);
4031 g_free(warning);
4032 return TRUE;
4034 break;
4035 case 503:
4037 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
4038 if (warning != NULL) {
4039 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
4040 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
4041 g_free(reason);
4042 } else {
4043 warning = g_strdup(_("Service unavailable: no reason given"));
4046 sip->gc->wants_to_die = TRUE;
4047 purple_connection_error(sip->gc, warning);
4048 g_free(warning);
4049 return TRUE;
4051 break;
4053 return TRUE;
4056 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
4058 const char *uri;
4059 xmlnode *xn_categories;
4060 xmlnode *xn_category;
4061 xmlnode *xn_node;
4062 const char *activity = NULL;
4064 xn_categories = xmlnode_from_str(data, len);
4065 uri = xmlnode_get_attrib(xn_categories, "uri");
4067 for (xn_category = xmlnode_get_child(xn_categories, "category");
4068 xn_category ;
4069 xn_category = xmlnode_get_next_twin(xn_category) )
4071 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4073 if (!strcmp(attrVar, "note"))
4075 if (uri) {
4076 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4078 if (sbuddy) {
4079 char *note;
4081 xn_node = xmlnode_get_child(xn_category, "note");
4082 if (!xn_node) continue;
4083 xn_node = xmlnode_get_child(xn_node, "body");
4084 if (!xn_node) continue;
4085 note = xmlnode_get_data(xn_node);
4086 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4087 g_free(sbuddy->annotation);
4088 sbuddy->annotation = NULL;
4089 if (note) sbuddy->annotation = g_strdup(note);
4090 g_free(note);
4095 else if(!strcmp(attrVar, "state"))
4097 char *data;
4098 int avail;
4099 xn_node = xmlnode_get_child(xn_category, "state");
4100 if (!xn_node) continue;
4101 xn_node = xmlnode_get_child(xn_node, "availability");
4102 if (!xn_node) continue;
4104 data = xmlnode_get_data(xn_node);
4105 avail = atoi(data);
4106 g_free(data);
4108 if (avail < 3000)
4109 activity = SIPE_STATUS_ID_UNKNOWN;
4110 else if (avail < 4500)
4111 activity = SIPE_STATUS_ID_AVAILABLE;
4112 else if (avail < 6000)
4113 activity = SIPE_STATUS_ID_BRB;
4114 else if (avail < 7500)
4115 activity = SIPE_STATUS_ID_ONPHONE;
4116 else if (avail < 9000)
4117 activity = SIPE_STATUS_ID_BUSY;
4118 else if (avail < 12000)
4119 activity = SIPE_STATUS_ID_DND;
4120 else if (avail < 18000)
4121 activity = SIPE_STATUS_ID_AWAY;
4122 else
4123 activity = SIPE_STATUS_ID_OFFLINE;
4126 if(activity) {
4127 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4128 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4131 xmlnode_free(xn_categories);
4134 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4136 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4137 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4138 payload->host = g_strdup(host);
4139 payload->buddies = server;
4140 sipe_subscribe_presence_batched_routed(sip, payload);
4141 sipe_subscribe_presence_batched_routed_free(payload);
4144 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4146 xmlnode *xn_list;
4147 xmlnode *xn_resource;
4148 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4149 g_free, NULL);
4150 GSList *server;
4151 gchar *host;
4153 xn_list = xmlnode_from_str(data, len);
4155 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4156 xn_resource;
4157 xn_resource = xmlnode_get_next_twin(xn_resource) )
4159 const char *uri, *state;
4160 xmlnode *xn_instance;
4162 xn_instance = xmlnode_get_child(xn_resource, "instance");
4163 if (!xn_instance) continue;
4165 uri = xmlnode_get_attrib(xn_resource, "uri");
4166 state = xmlnode_get_attrib(xn_instance, "state");
4167 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4169 if (strstr(state, "resubscribe")) {
4170 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4171 struct sipe_buddy *sbuddy;
4172 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4173 gchar *user = g_strdup(uri);
4174 host = g_strdup(poolFqdn);
4175 server = g_hash_table_lookup(servers, host);
4176 server = g_slist_append(server, user);
4177 g_hash_table_insert(servers, host, server);
4178 } else {
4179 sipe_subscribe_presence_single(sip, (void *) uri);
4181 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4182 if (sbuddy) {
4183 sbuddy->resubscribed = TRUE;
4188 /* Send out any deferred poolFqdn subscriptions */
4189 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4190 g_hash_table_destroy(servers);
4192 xmlnode_free(xn_list);
4195 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4197 const gchar *uri;
4198 gchar *getbasic;
4199 gchar *activity = NULL;
4200 xmlnode *pidf;
4201 xmlnode *basicstatus = NULL, *tuple, *status;
4202 gboolean isonline = FALSE;
4203 xmlnode *display_name_node;
4205 pidf = xmlnode_from_str(data, len);
4206 if (!pidf) {
4207 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4208 return;
4211 uri = xmlnode_get_attrib(pidf, "entity");
4213 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4215 if ((status = xmlnode_get_child(tuple, "status"))) {
4216 basicstatus = xmlnode_get_child(status, "basic");
4220 if (!basicstatus) {
4221 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4222 xmlnode_free(pidf);
4223 return;
4226 getbasic = xmlnode_get_data(basicstatus);
4227 if (!getbasic) {
4228 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4229 xmlnode_free(pidf);
4230 return;
4233 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4234 if (strstr(getbasic, "open")) {
4235 isonline = TRUE;
4237 g_free(getbasic);
4239 display_name_node = xmlnode_get_child(pidf, "display-name");
4240 // updating display name if alias was just URI
4241 if (display_name_node) {
4242 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4243 GSList *entry = buddies;
4244 PurpleBuddy *p_buddy;
4245 char * display_name = xmlnode_get_data(display_name_node);
4247 while (entry) {
4248 const char *server_alias;
4249 char *alias;
4251 p_buddy = entry->data;
4253 alias = (char *)purple_buddy_get_alias(p_buddy);
4254 alias = alias ? g_strdup_printf("sip:%s", alias) : NULL;
4255 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4256 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4257 purple_blist_alias_buddy(p_buddy, display_name);
4259 g_free(alias);
4261 server_alias = purple_buddy_get_server_alias(p_buddy);
4262 if (display_name &&
4263 ( (server_alias && strcmp(display_name, server_alias))
4264 || !server_alias || strlen(server_alias) == 0 )
4266 purple_blist_server_alias_buddy(p_buddy, display_name);
4269 entry = entry->next;
4271 g_free(display_name);
4274 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4275 if ((status = xmlnode_get_child(tuple, "status"))) {
4276 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4277 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4278 activity = xmlnode_get_data(basicstatus);
4279 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4285 if (isonline) {
4286 const gchar * status_id = NULL;
4287 if (activity) {
4288 if (strstr(activity, "busy")) {
4289 status_id = SIPE_STATUS_ID_BUSY;
4290 } else if (strstr(activity, "away")) {
4291 status_id = SIPE_STATUS_ID_AWAY;
4295 if (!status_id) {
4296 status_id = SIPE_STATUS_ID_AVAILABLE;
4299 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4300 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4301 } else {
4302 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4305 g_free(activity);
4306 xmlnode_free(pidf);
4309 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4311 const char *availability;
4312 const char *activity;
4313 const char *display_name = NULL;
4314 const char *activity_name = NULL;
4315 const char *name;
4316 char *uri;
4317 int avl;
4318 int act;
4319 struct sipe_buddy *sbuddy;
4321 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4323 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4324 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4325 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4326 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4327 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4328 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4329 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4330 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4332 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4333 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4334 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4335 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4336 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4337 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4339 name = xmlnode_get_attrib(xn_presentity, "uri");
4340 uri = g_strdup_printf("sip:%s", name);
4341 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4342 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4344 // updating display name if alias was just URI
4345 if (xn_display_name) {
4346 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4347 GSList *entry = buddies;
4348 PurpleBuddy *p_buddy;
4349 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4351 while (entry) {
4352 const char *email_str, *server_alias;
4354 p_buddy = entry->data;
4356 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4357 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4358 purple_blist_alias_buddy(p_buddy, display_name);
4361 server_alias = purple_buddy_get_server_alias(p_buddy);
4362 if (display_name &&
4363 ( (server_alias && strcmp(display_name, server_alias))
4364 || !server_alias || strlen(server_alias) == 0 )
4366 purple_blist_server_alias_buddy(p_buddy, display_name);
4369 if (email) {
4370 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4371 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4372 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4376 entry = entry->next;
4380 avl = atoi(availability);
4381 act = atoi(activity);
4383 if(sip->msrtc_event_categories){
4384 if (act == 100 && avl == 0)
4385 activity_name = SIPE_STATUS_ID_OFFLINE;
4386 else if (act == 100 && avl == 300)
4387 activity_name = SIPE_STATUS_ID_AWAY;
4388 else if (act == 300 && avl == 300)
4389 activity_name = SIPE_STATUS_ID_BRB;
4390 else if (act == 400 && avl == 300)
4391 activity_name = SIPE_STATUS_ID_AVAILABLE;
4392 else if (act == 500 && act == 300)
4393 activity_name = SIPE_STATUS_ID_ONPHONE;
4394 else if (act == 600 && avl == 300)
4395 activity_name = SIPE_STATUS_ID_BUSY;
4396 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4397 if(avail){ //Check for LegacyInterop elements
4398 avl = atoi(avail);
4399 if(avl == 18500)
4400 activity_name = SIPE_STATUS_ID_OFFLINE;
4401 else if (avl == 3500)
4402 activity_name = SIPE_STATUS_ID_AVAILABLE;
4403 else if (avl == 15500)
4404 activity_name = SIPE_STATUS_ID_AWAY;
4405 else if (avl == 6500)
4406 activity_name = SIPE_STATUS_ID_BUSY;
4407 else if (avl == 12500)
4408 activity_name = SIPE_STATUS_ID_BRB;
4413 if(activity_name == NULL){
4414 if (act <= 100)
4415 activity_name = SIPE_STATUS_ID_AWAY;
4416 else if (act <= 150)
4417 activity_name = SIPE_STATUS_ID_LUNCH;
4418 else if (act <= 300)
4419 activity_name = SIPE_STATUS_ID_BRB;
4420 else if (act <= 400)
4421 activity_name = SIPE_STATUS_ID_AVAILABLE;
4422 else if (act <= 500)
4423 activity_name = SIPE_STATUS_ID_ONPHONE;
4424 else if (act <= 600)
4425 activity_name = SIPE_STATUS_ID_BUSY;
4426 else
4427 activity_name = SIPE_STATUS_ID_AVAILABLE;
4429 if (avl == 0)
4430 activity_name = SIPE_STATUS_ID_OFFLINE;
4433 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4434 if (sbuddy)
4436 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4437 sbuddy->annotation = NULL;
4438 if (note) { sbuddy->annotation = g_strdup(note); }
4440 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4441 sbuddy->device_name = NULL;
4442 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4445 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4446 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4447 g_free(note);
4448 xmlnode_free(xn_presentity);
4449 g_free(uri);
4452 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4454 char *ctype = sipmsg_find_header(msg, "Content-Type");
4456 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4458 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4459 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4461 const char *content = msg->body;
4462 unsigned length = msg->bodylen;
4463 PurpleMimeDocument *mime = NULL;
4465 if (strstr(ctype, "multipart"))
4467 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4468 const char *content_type;
4469 GList* parts;
4470 mime = purple_mime_document_parse(doc);
4471 parts = purple_mime_document_get_parts(mime);
4472 while(parts) {
4473 content = purple_mime_part_get_data(parts->data);
4474 length = purple_mime_part_get_length(parts->data);
4475 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4476 if(content_type && strstr(content_type,"application/rlmi+xml"))
4478 process_incoming_notify_rlmi_resub(sip, content, length);
4480 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4482 process_incoming_notify_msrtc(sip, content, length);
4484 else
4486 process_incoming_notify_rlmi(sip, content, length);
4488 parts = parts->next;
4490 g_free(doc);
4492 if (mime)
4494 purple_mime_document_free(mime);
4497 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4499 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4501 else if(strstr(ctype, "application/rlmi+xml"))
4503 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4506 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4508 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4510 else
4512 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4516 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4518 char *ctype = sipmsg_find_header(msg, "Content-Type");
4519 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4521 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4523 if (ctype &&
4524 strstr(ctype, "multipart") &&
4525 (strstr(ctype, "application/rlmi+xml") ||
4526 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4527 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4528 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4529 GList *parts = purple_mime_document_get_parts(mime);
4530 GSList *buddies = NULL;
4531 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4533 while (parts) {
4534 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4535 purple_mime_part_get_length(parts->data));
4536 gchar *uri = g_strdup(xmlnode_get_attrib(xml, "uri"));
4538 if (strstr(uri, "sip:") == NULL) {
4539 gchar *tmp = uri;
4540 uri = g_strdup_printf("sip:%s", tmp);
4541 g_free(tmp);
4543 buddies = g_slist_append(buddies, uri);
4544 xmlnode_free(xml);
4546 parts = parts->next;
4548 g_free(doc);
4549 if (mime) purple_mime_document_free(mime);
4551 payload->host = who;
4552 payload->buddies = buddies;
4553 sipe_schedule_action(action_name, timeout,
4554 sipe_subscribe_presence_batched_routed,
4555 sipe_subscribe_presence_batched_routed_free,
4556 sip, payload);
4557 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4559 } else {
4560 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4561 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4563 g_free(action_name);
4567 * Dispatcher for all incoming subscription information
4568 * whether it comes from NOTIFY, BENOTIFY requests or
4569 * piggy-backed to subscription's OK responce.
4571 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4572 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4574 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4576 gchar *event = sipmsg_find_header(msg, "Event");
4577 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4578 int timeout = 0;
4580 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4581 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state);
4583 if (!request)
4585 const gchar *expires_header;
4586 expires_header = sipmsg_find_header(msg, "Expires");
4587 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4588 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4589 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4592 if (!subscription_state || strstr(subscription_state, "active"))
4594 if (event && !g_ascii_strcasecmp(event, "presence"))
4596 sipe_process_presence(sip, msg);
4598 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4600 sipe_process_roaming_contacts(sip, msg, NULL);
4602 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self") )
4604 sipe_process_roaming_self(sip, msg);
4606 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4608 sipe_process_roaming_acl(sip, msg);
4610 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4612 sipe_process_presence_wpending(sip, msg);
4614 else if (event && !g_ascii_strcasecmp(event, "conference"))
4616 sipe_process_conference(sip, msg);
4618 else
4620 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event ? event : "");
4624 //The server sends a (BE)NOTIFY with the status 'terminated'
4625 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4626 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4627 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4628 g_free(from);
4631 if (timeout && event) {// For LSC 2005 and OCS 2007
4632 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4633 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4635 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4636 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4637 g_free(action_name);
4639 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4640 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4642 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4643 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4644 g_free(action_name);
4646 else*/
4647 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4648 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4650 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4651 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4652 g_free(action_name);
4654 else if (!g_ascii_strcasecmp(event, "presence") &&
4655 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4657 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4658 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4659 if(sip->batched_support) {
4660 gchar *my_self = g_strdup_printf("sip:%s",sip->username);
4661 if(!g_ascii_strcasecmp(who, my_self)){
4662 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4663 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4664 g_free(who); /* unused */
4666 else {
4667 sipe_process_presence_timeout(sip, msg, who, timeout);
4669 g_free(my_self);
4671 else {
4672 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4673 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4675 g_free(action_name);
4676 /* "who" will be freed by the action we just scheduled */
4680 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4682 sipe_process_registration_notify(sip, msg);
4685 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4686 if (request && !benotify)
4688 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4693 * unused. Needed?
4695 static gchar* gen_xpidf(struct sipe_account_data *sip)
4697 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4698 "<presence>\r\n"
4699 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4700 "<display name=\"sip:%s\"/>\r\n"
4701 "<atom id=\"1234\">\r\n"
4702 "<address uri=\"sip:%s\">\r\n"
4703 "<status status=\"%s\"/>\r\n"
4704 "</address>\r\n"
4705 "</atom>\r\n"
4706 "</presence>\r\n",
4707 sip->username,
4708 sip->username,
4709 sip->username,
4710 sip->status);
4711 return doc;
4716 static gchar* gen_pidf(struct sipe_account_data *sip)
4718 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4719 "<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"
4720 "<tuple id=\"0\">\r\n"
4721 "<status>\r\n"
4722 "<basic>open</basic>\r\n"
4723 "<ep:activities>\r\n"
4724 " <ep:activity>%s</ep:activity>\r\n"
4725 "</ep:activities>"
4726 "</status>\r\n"
4727 "</tuple>\r\n"
4728 "<ci:display-name>%s</ci:display-name>\r\n"
4729 "</presence>",
4730 sip->username,
4731 sip->status,
4732 sip->username);
4733 return doc;
4737 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4739 int availability = 300; // online
4740 int activity = 400; // Available
4741 gchar *name;
4742 gchar *body;
4743 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4744 activity = 100;
4745 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4746 activity = 150;
4747 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4748 activity = 300;
4749 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4750 activity = 400;
4751 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4752 activity = 500;
4753 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4754 activity = 600;
4755 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4756 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4757 availability = 0; // offline
4758 activity = 100;
4759 } else {
4760 activity = 400; // available
4763 name = g_strdup_printf("sip: sip:%s", sip->username);
4764 //@TODO: send user data - state; add hostname in upper case
4765 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4766 send_soap_request_with_cb(sip, body, NULL , NULL);
4767 g_free(name);
4768 g_free(body);
4771 static gboolean
4772 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4774 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4775 if (msg->response == 200) {
4776 sip->status_version = 0;
4777 send_presence_status(sip);
4779 return TRUE;
4782 static gboolean
4783 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4785 if (msg->response == 409) {
4786 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4787 // TODO need to parse the version #'s?
4788 gchar *uri = g_strdup_printf("sip:%s", sip->username);
4789 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4790 gchar *tmp;
4791 gchar *hdr;
4793 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4795 tmp = get_contact(sip);
4796 hdr = g_strdup_printf("Contact: %s\r\n"
4797 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4799 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4801 g_free(tmp);
4802 g_free(hdr);
4803 g_free(uri);
4804 g_free(doc);
4806 return TRUE;
4809 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4811 int code;
4812 gchar *uri;
4813 gchar *doc;
4814 gchar *tmp;
4815 gchar *hdr;
4816 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4817 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4818 code = 12000;
4819 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4820 code = 9000;
4821 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4822 code = 7500;
4823 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4824 code = 6000;
4825 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4826 code = 4500;
4827 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4828 code = 3000;
4829 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4830 code = 0;
4831 } else {
4832 // Offline or invisible
4833 code = 18000;
4836 uri = g_strdup_printf("sip:%s", sip->username);
4837 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4838 sip->status_version, code,
4839 sip->status_version, code,
4840 sip->status_version, note ? note : "",
4841 sip->status_version, note ? note : "",
4842 sip->status_version, note ? note : ""
4844 sip->status_version++;
4846 tmp = get_contact(sip);
4847 hdr = g_strdup_printf("Contact: %s\r\n"
4848 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4850 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4852 g_free(tmp);
4853 g_free(hdr);
4854 g_free(uri);
4855 g_free(doc);
4858 static void send_presence_status(struct sipe_account_data *sip)
4860 PurpleStatus * status = purple_account_get_active_status(sip->account);
4861 const gchar *note;
4862 if (!status) return;
4864 note = purple_status_get_attr_string(status, "message");
4866 if(sip->msrtc_event_categories){
4867 send_presence_category_publish(sip, note);
4868 } else {
4869 send_presence_soap(sip, note);
4873 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4875 gboolean found = FALSE;
4876 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4877 if (msg->response == 0) { /* request */
4878 if (!strcmp(msg->method, "MESSAGE")) {
4879 process_incoming_message(sip, msg);
4880 found = TRUE;
4881 } else if (!strcmp(msg->method, "NOTIFY")) {
4882 purple_debug_info("sipe","send->process_incoming_notify\n");
4883 process_incoming_notify(sip, msg, TRUE, FALSE);
4884 found = TRUE;
4885 } else if (!strcmp(msg->method, "BENOTIFY")) {
4886 purple_debug_info("sipe","send->process_incoming_benotify\n");
4887 process_incoming_notify(sip, msg, TRUE, TRUE);
4888 found = TRUE;
4889 } else if (!strcmp(msg->method, "INVITE")) {
4890 process_incoming_invite(sip, msg);
4891 found = TRUE;
4892 } else if (!strcmp(msg->method, "REFER")) {
4893 process_incoming_refer(sip, msg);
4894 found = TRUE;
4895 } else if (!strcmp(msg->method, "OPTIONS")) {
4896 process_incoming_options(sip, msg);
4897 found = TRUE;
4898 } else if (!strcmp(msg->method, "INFO")) {
4899 process_incoming_info(sip, msg);
4900 found = TRUE;
4901 } else if (!strcmp(msg->method, "ACK")) {
4902 // ACK's don't need any response
4903 found = TRUE;
4904 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4905 // LCS 2005 sends us these - just respond 200 OK
4906 found = TRUE;
4907 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4908 } else if (!strcmp(msg->method, "BYE")) {
4909 process_incoming_bye(sip, msg);
4910 found = TRUE;
4911 } else {
4912 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4914 } else { /* response */
4915 struct transaction *trans = transactions_find(sip, msg);
4916 if (trans) {
4917 if (msg->response == 407) {
4918 gchar *resend, *auth, *ptmp;
4920 if (sip->proxy.retries > 30) return;
4921 sip->proxy.retries++;
4922 /* do proxy authentication */
4924 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4926 fill_auth(sip, ptmp, &sip->proxy);
4927 auth = auth_header(sip, &sip->proxy, trans->msg);
4928 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4929 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4930 g_free(auth);
4931 resend = sipmsg_to_string(trans->msg);
4932 /* resend request */
4933 sendout_pkt(sip->gc, resend);
4934 g_free(resend);
4935 } else {
4936 if (msg->response == 100 || msg->response == 180) {
4937 /* ignore provisional response */
4938 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4939 } else {
4940 sip->proxy.retries = 0;
4941 if (!strcmp(trans->msg->method, "REGISTER")) {
4942 if (msg->response == 401)
4944 sip->registrar.retries++;
4946 else
4948 sip->registrar.retries = 0;
4950 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4951 } else {
4952 if (msg->response == 401) {
4953 gchar *resend, *auth, *ptmp;
4955 if (sip->registrar.retries > 4) return;
4956 sip->registrar.retries++;
4958 #ifdef USE_KERBEROS
4959 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4960 #endif
4961 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4962 #ifdef USE_KERBEROS
4963 } else {
4964 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4966 #endif
4968 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4970 fill_auth(sip, ptmp, &sip->registrar);
4971 auth = auth_header(sip, &sip->registrar, trans->msg);
4972 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4973 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4975 //sipmsg_remove_header_now(trans->msg, "Authorization");
4976 //sipmsg_add_header(trans->msg, "Authorization", auth);
4977 g_free(auth);
4978 resend = sipmsg_to_string(trans->msg);
4979 /* resend request */
4980 sendout_pkt(sip->gc, resend);
4981 g_free(resend);
4985 if (trans->callback) {
4986 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4987 /* call the callback to process response*/
4988 (trans->callback)(sip, msg, trans);
4990 /* Not sure if this is needed or what needs to be done
4991 but transactions seem to be removed prematurely so
4992 this only removes them if the response is 200 OK */
4993 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4994 /*Has a bug and it's unneccesary*/
4995 /*transactions_remove(sip, trans);*/
4999 found = TRUE;
5000 } else {
5001 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
5004 if (!found) {
5005 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
5009 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
5011 char *cur;
5012 char *dummy;
5013 struct sipmsg *msg;
5014 int restlen;
5015 cur = conn->inbuf;
5017 /* according to the RFC remove CRLF at the beginning */
5018 while (*cur == '\r' || *cur == '\n') {
5019 cur++;
5021 if (cur != conn->inbuf) {
5022 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
5023 conn->inbufused = strlen(conn->inbuf);
5026 /* Received a full Header? */
5027 sip->processing_input = TRUE;
5028 while (sip->processing_input &&
5029 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
5030 time_t currtime = time(NULL);
5031 cur += 2;
5032 cur[0] = '\0';
5033 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
5034 msg = sipmsg_parse_header(conn->inbuf);
5035 cur[0] = '\r';
5036 cur += 2;
5037 restlen = conn->inbufused - (cur - conn->inbuf);
5038 if (restlen >= msg->bodylen) {
5039 dummy = g_malloc(msg->bodylen + 1);
5040 memcpy(dummy, cur, msg->bodylen);
5041 dummy[msg->bodylen] = '\0';
5042 msg->body = dummy;
5043 cur += msg->bodylen;
5044 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
5045 conn->inbufused = strlen(conn->inbuf);
5046 } else {
5047 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5048 restlen, msg->bodylen, (int)strlen(conn->inbuf));
5049 sipmsg_free(msg);
5050 return;
5053 /*if (msg->body) {
5054 purple_debug_info("sipe", "body:\n%s", msg->body);
5057 // Verify the signature before processing it
5058 if (sip->registrar.gssapi_context) {
5059 struct sipmsg_breakdown msgbd;
5060 gchar *signature_input_str;
5061 gchar *rspauth;
5062 msgbd.msg = msg;
5063 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5064 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5066 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5068 if (rspauth != NULL) {
5069 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5070 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5071 process_input_message(sip, msg);
5072 } else {
5073 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5074 purple_connection_error(sip->gc, _("Invalid message signature received"));
5075 sip->gc->wants_to_die = TRUE;
5077 } else if (msg->response == 401) {
5078 purple_connection_error(sip->gc, _("Wrong Password"));
5079 sip->gc->wants_to_die = TRUE;
5081 g_free(signature_input_str);
5083 g_free(rspauth);
5084 sipmsg_breakdown_free(&msgbd);
5085 } else {
5086 process_input_message(sip, msg);
5089 sipmsg_free(msg);
5093 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
5095 PurpleConnection *gc = data;
5096 struct sipe_account_data *sip = gc->proto_data;
5097 struct sipmsg *msg;
5098 int len;
5099 time_t currtime;
5101 static char buffer[65536];
5102 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5103 buffer[len] = '\0';
5104 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5105 msg = sipmsg_parse_msg(buffer);
5106 if (msg) process_input_message(sip, msg);
5110 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5112 struct sipe_account_data *sip = gc->proto_data;
5113 PurpleSslConnection *gsc = sip->gsc;
5115 purple_debug_error("sipe", "%s",debug);
5116 purple_connection_error(gc, msg);
5118 /* Invalidate this connection. Next send will open a new one */
5119 if (gsc) {
5120 connection_remove(sip, gsc->fd);
5121 purple_ssl_close(gsc);
5123 sip->gsc = NULL;
5124 sip->fd = -1;
5127 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5129 PurpleConnection *gc = data;
5130 struct sipe_account_data *sip;
5131 struct sip_connection *conn;
5132 int readlen, len;
5133 gboolean firstread = TRUE;
5135 /* NOTE: This check *IS* necessary */
5136 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5137 purple_ssl_close(gsc);
5138 return;
5141 sip = gc->proto_data;
5142 conn = connection_find(sip, gsc->fd);
5143 if (conn == NULL) {
5144 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5145 gc->wants_to_die = TRUE;
5146 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5147 return;
5150 /* Read all available data from the SSL connection */
5151 do {
5152 /* Increase input buffer size as needed */
5153 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5154 conn->inbuflen += SIMPLE_BUF_INC;
5155 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5156 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5159 /* Try to read as much as there is space left in the buffer */
5160 readlen = conn->inbuflen - conn->inbufused - 1;
5161 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5163 if (len < 0 && errno == EAGAIN) {
5164 /* Try again later */
5165 return;
5166 } else if (len < 0) {
5167 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5168 return;
5169 } else if (firstread && (len == 0)) {
5170 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5171 return;
5174 conn->inbufused += len;
5175 firstread = FALSE;
5177 /* Equivalence indicates that there is possibly more data to read */
5178 } while (len == readlen);
5180 conn->inbuf[conn->inbufused] = '\0';
5181 process_input(sip, conn);
5185 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
5187 PurpleConnection *gc = data;
5188 struct sipe_account_data *sip = gc->proto_data;
5189 int len;
5190 struct sip_connection *conn = connection_find(sip, source);
5191 if (!conn) {
5192 purple_debug_error("sipe", "Connection not found!\n");
5193 return;
5196 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5197 conn->inbuflen += SIMPLE_BUF_INC;
5198 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5201 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5203 if (len < 0 && errno == EAGAIN)
5204 return;
5205 else if (len <= 0) {
5206 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5207 connection_remove(sip, source);
5208 if (sip->fd == source) sip->fd = -1;
5209 return;
5212 conn->inbufused += len;
5213 conn->inbuf[conn->inbufused] = '\0';
5215 process_input(sip, conn);
5218 /* Callback for new connections on incoming TCP port */
5219 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
5221 PurpleConnection *gc = data;
5222 struct sipe_account_data *sip = gc->proto_data;
5223 struct sip_connection *conn;
5225 int newfd = accept(source, NULL, NULL);
5227 conn = connection_create(sip, newfd);
5229 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5232 static void login_cb(gpointer data, gint source, const gchar *error_message)
5234 PurpleConnection *gc = data;
5235 struct sipe_account_data *sip;
5236 struct sip_connection *conn;
5238 if (!PURPLE_CONNECTION_IS_VALID(gc))
5240 if (source >= 0)
5241 close(source);
5242 return;
5245 if (source < 0) {
5246 purple_connection_error(gc, _("Could not connect"));
5247 return;
5250 sip = gc->proto_data;
5251 sip->fd = source;
5252 sip->last_keepalive = time(NULL);
5254 conn = connection_create(sip, source);
5256 do_register(sip);
5258 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5261 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5263 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5264 if (sip == NULL) return;
5266 do_register(sip);
5269 static guint sipe_ht_hash_nick(const char *nick)
5271 char *lc = g_utf8_strdown(nick, -1);
5272 guint bucket = g_str_hash(lc);
5273 g_free(lc);
5275 return bucket;
5278 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5280 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5283 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5285 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5287 sip->listen_data = NULL;
5289 if (listenfd == -1) {
5290 purple_connection_error(sip->gc, _("Could not create listen socket"));
5291 return;
5294 sip->fd = listenfd;
5296 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5297 sip->listenfd = sip->fd;
5299 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5301 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5302 do_register(sip);
5305 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
5307 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5309 sip->query_data = NULL;
5311 if (!hosts || !hosts->data) {
5312 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5313 return;
5316 hosts = g_slist_remove(hosts, hosts->data);
5317 g_free(sip->serveraddr);
5318 sip->serveraddr = hosts->data;
5319 hosts = g_slist_remove(hosts, hosts->data);
5320 while (hosts) {
5321 hosts = g_slist_remove(hosts, hosts->data);
5322 g_free(hosts->data);
5323 hosts = g_slist_remove(hosts, hosts->data);
5326 /* create socket for incoming connections */
5327 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5328 sipe_udp_host_resolved_listen_cb, sip);
5329 if (sip->listen_data == NULL) {
5330 purple_connection_error(sip->gc, _("Could not create listen socket"));
5331 return;
5335 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
5336 gpointer data)
5338 PurpleConnection *gc = data;
5339 struct sipe_account_data *sip;
5341 /* If the connection is already disconnected, we don't need to do anything else */
5342 if (!PURPLE_CONNECTION_IS_VALID(gc))
5343 return;
5345 sip = gc->proto_data;
5346 sip->fd = -1;
5347 sip->gsc = NULL;
5349 switch(error) {
5350 case PURPLE_SSL_CONNECT_FAILED:
5351 purple_connection_error(gc, _("Connection Failed"));
5352 break;
5353 case PURPLE_SSL_HANDSHAKE_FAILED:
5354 purple_connection_error(gc, _("SSL Handshake Failed"));
5355 break;
5356 case PURPLE_SSL_CERTIFICATE_INVALID:
5357 purple_connection_error(gc, _("SSL Certificate Invalid"));
5358 break;
5362 static void
5363 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5365 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5366 PurpleProxyConnectData *connect_data;
5368 sip->listen_data = NULL;
5370 sip->listenfd = listenfd;
5371 if (sip->listenfd == -1) {
5372 purple_connection_error(sip->gc, _("Could not create listen socket"));
5373 return;
5376 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5377 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5378 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5379 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5380 sipe_newconn_cb, sip->gc);
5381 purple_debug_info("sipe", "connecting to %s port %d\n",
5382 sip->realhostname, sip->realport);
5383 /* open tcp connection to the server */
5384 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5385 sip->realport, login_cb, sip->gc);
5387 if (connect_data == NULL) {
5388 purple_connection_error(sip->gc, _("Couldn't create socket"));
5393 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5395 PurpleAccount *account = sip->account;
5396 PurpleConnection *gc = sip->gc;
5398 if (purple_account_get_bool(account, "useport", FALSE)) {
5399 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
5400 port = purple_account_get_int(account, "port", 0);
5401 } else {
5402 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5405 sip->realhostname = hostname;
5406 sip->realport = port;
5408 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5409 hostname, port);
5411 /* TODO: is there a good default grow size? */
5412 if (sip->transport != SIPE_TRANSPORT_UDP)
5413 sip->txbuf = purple_circ_buffer_new(0);
5415 if (sip->transport == SIPE_TRANSPORT_TLS) {
5416 /* SSL case */
5417 if (!purple_ssl_is_supported()) {
5418 gc->wants_to_die = TRUE;
5419 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5420 return;
5423 purple_debug_info("sipe", "using SSL\n");
5425 sip->gsc = purple_ssl_connect(account, hostname, port,
5426 login_cb_ssl, sipe_ssl_connect_failure, gc);
5427 if (sip->gsc == NULL) {
5428 purple_connection_error(gc, _("Could not create SSL context"));
5429 return;
5431 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5432 /* UDP case */
5433 purple_debug_info("sipe", "using UDP\n");
5435 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5436 if (sip->query_data == NULL) {
5437 purple_connection_error(gc, _("Could not resolve hostname"));
5439 } else {
5440 /* TCP case */
5441 purple_debug_info("sipe", "using TCP\n");
5442 /* create socket for incoming connections */
5443 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5444 sipe_tcp_connect_listen_cb, sip);
5445 if (sip->listen_data == NULL) {
5446 purple_connection_error(gc, _("Could not create listen socket"));
5447 return;
5452 /* Service list for autodection */
5453 static const struct sipe_service_data service_autodetect[] = {
5454 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5455 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5456 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5457 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5458 { NULL, NULL, 0 }
5461 /* Service list for SSL/TLS */
5462 static const struct sipe_service_data service_tls[] = {
5463 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5464 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5465 { NULL, NULL, 0 }
5468 /* Service list for TCP */
5469 static const struct sipe_service_data service_tcp[] = {
5470 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5471 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5472 { NULL, NULL, 0 }
5475 /* Service list for UDP */
5476 static const struct sipe_service_data service_udp[] = {
5477 { "sip", "udp", SIPE_TRANSPORT_UDP },
5478 { NULL, NULL, 0 }
5481 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5482 static void resolve_next_service(struct sipe_account_data *sip,
5483 const struct sipe_service_data *start)
5485 if (start) {
5486 sip->service_data = start;
5487 } else {
5488 sip->service_data++;
5489 if (sip->service_data->service == NULL) {
5490 gchar *hostname;
5491 /* Try connecting to the SIP hostname directly */
5492 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5493 if (sip->auto_transport) {
5494 // If SSL is supported, default to using it; OCS servers aren't configured
5495 // by default to accept TCP
5496 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5497 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5498 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5501 hostname = g_strdup(sip->sipdomain);
5502 create_connection(sip, hostname, 0);
5503 return;
5507 /* Try to resolve next service */
5508 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5509 sip->service_data->transport,
5510 sip->sipdomain,
5511 srvresolved, sip);
5514 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5516 struct sipe_account_data *sip = data;
5518 sip->srv_query_data = NULL;
5520 /* find the host to connect to */
5521 if (results) {
5522 gchar *hostname = g_strdup(resp->hostname);
5523 int port = resp->port;
5524 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5525 hostname, port);
5526 g_free(resp);
5528 sip->transport = sip->service_data->type;
5530 create_connection(sip, hostname, port);
5531 } else {
5532 resolve_next_service(sip, NULL);
5536 static void sipe_login(PurpleAccount *account)
5538 PurpleConnection *gc;
5539 struct sipe_account_data *sip;
5540 gchar **signinname_login, **userserver, **domain_user;
5541 const char *transport;
5543 const char *username = purple_account_get_username(account);
5544 gc = purple_account_get_connection(account);
5546 if (strpbrk(username, "\t\v\r\n") != NULL) {
5547 gc->wants_to_die = TRUE;
5548 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5549 return;
5552 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5553 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5554 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5555 sip->gc = gc;
5556 sip->account = account;
5557 sip->reregister_set = FALSE;
5558 sip->reauthenticate_set = FALSE;
5559 sip->subscribed = FALSE;
5560 sip->subscribed_buddies = FALSE;
5562 signinname_login = g_strsplit(username, ",", 2);
5564 userserver = g_strsplit(signinname_login[0], "@", 2);
5565 purple_connection_set_display_name(gc, userserver[0]);
5566 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
5567 sip->sipdomain = g_strdup(userserver[1]);
5569 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
5570 gc->wants_to_die = TRUE;
5571 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5572 return;
5575 domain_user = g_strsplit(signinname_login[1], "\\", 2);
5576 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
5577 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
5579 sip->password = g_strdup(purple_connection_get_password(gc));
5581 g_strfreev(userserver);
5582 g_strfreev(domain_user);
5583 g_strfreev(signinname_login);
5585 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5587 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5589 /* TODO: Set the status correctly. */
5590 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5592 transport = purple_account_get_string(account, "transport", "auto");
5593 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
5594 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
5595 SIPE_TRANSPORT_UDP;
5597 if (purple_account_get_bool(account, "useproxy", FALSE)) {
5598 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
5599 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
5600 } else if (strcmp(transport, "auto") == 0) {
5601 sip->auto_transport = TRUE;
5602 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5603 } else if (strcmp(transport, "tls") == 0) {
5604 resolve_next_service(sip, service_tls);
5605 } else if (strcmp(transport, "tcp") == 0) {
5606 resolve_next_service(sip, service_tcp);
5607 } else {
5608 resolve_next_service(sip, service_udp);
5612 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5614 connection_free_all(sip);
5616 g_free(sip->epid);
5617 sip->epid = NULL;
5619 if (sip->query_data != NULL)
5620 purple_dnsquery_destroy(sip->query_data);
5621 sip->query_data = NULL;
5623 if (sip->srv_query_data != NULL)
5624 purple_srv_cancel(sip->srv_query_data);
5625 sip->srv_query_data = NULL;
5627 if (sip->listen_data != NULL)
5628 purple_network_listen_cancel(sip->listen_data);
5629 sip->listen_data = NULL;
5631 if (sip->gsc != NULL)
5632 purple_ssl_close(sip->gsc);
5633 sip->gsc = NULL;
5635 sipe_auth_free(&sip->registrar);
5636 sipe_auth_free(&sip->proxy);
5638 if (sip->txbuf)
5639 purple_circ_buffer_destroy(sip->txbuf);
5640 sip->txbuf = NULL;
5642 g_free(sip->realhostname);
5643 sip->realhostname = NULL;
5645 if (sip->listenpa)
5646 purple_input_remove(sip->listenpa);
5647 sip->listenpa = 0;
5648 if (sip->tx_handler)
5649 purple_input_remove(sip->tx_handler);
5650 sip->tx_handler = 0;
5651 if (sip->resendtimeout)
5652 purple_timeout_remove(sip->resendtimeout);
5653 sip->resendtimeout = 0;
5654 if (sip->timeouts) {
5655 GSList *entry = sip->timeouts;
5656 while (entry) {
5657 struct scheduled_action *sched_action = entry->data;
5658 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5659 purple_timeout_remove(sched_action->timeout_handler);
5660 (*sched_action->destroy)(sched_action->payload);
5661 g_free(sched_action->name);
5662 g_free(sched_action);
5663 entry = entry->next;
5666 g_slist_free(sip->timeouts);
5668 if (sip->allow_events) {
5669 GSList *entry = sip->allow_events;
5670 while (entry) {
5671 g_free(entry->data);
5672 entry = entry->next;
5675 g_slist_free(sip->allow_events);
5677 if (sip->containers) {
5678 GSList *entry = sip->containers;
5679 while (entry) {
5680 free_container((struct sipe_container *)entry->data);
5681 entry = entry->next;
5684 g_slist_free(sip->containers);
5686 if (sip->contact)
5687 g_free(sip->contact);
5688 sip->contact = NULL;
5689 if (sip->regcallid)
5690 g_free(sip->regcallid);
5691 sip->regcallid = NULL;
5693 if (sip->serveraddr)
5694 g_free(sip->serveraddr);
5695 sip->serveraddr = NULL;
5697 sip->fd = -1;
5698 sip->processing_input = FALSE;
5702 * A callback for g_hash_table_foreach_remove
5704 static gboolean sipe_buddy_remove(gpointer key, gpointer buddy, gpointer user_data)
5706 sipe_free_buddy((struct sipe_buddy *) buddy);
5708 /* We must return TRUE as the key/value have already been deleted */
5709 return(TRUE);
5712 static void sipe_close(PurpleConnection *gc)
5714 struct sipe_account_data *sip = gc->proto_data;
5716 if (sip) {
5717 /* leave all conversations */
5718 im_session_close_all(sip);
5720 /* unregister */
5721 do_register_exp(sip, 0);
5723 sipe_connection_cleanup(sip);
5724 g_free(sip->sipdomain);
5725 g_free(sip->username);
5726 g_free(sip->password);
5727 g_free(sip->authdomain);
5728 g_free(sip->authuser);
5729 g_free(sip->status);
5731 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
5732 g_hash_table_destroy(sip->buddies);
5734 g_free(gc->proto_data);
5735 gc->proto_data = NULL;
5738 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
5740 PurpleAccount *acct = purple_connection_get_account(gc);
5741 char *id = g_strdup_printf("sip:%s", (char *)g_list_nth_data(row, 0));
5742 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5743 if (conv == NULL)
5744 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5745 purple_conversation_present(conv);
5746 g_free(id);
5749 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5752 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5753 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5756 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5758 PurpleNotifySearchResults *results;
5759 PurpleNotifySearchColumn *column;
5760 xmlnode *searchResults;
5761 xmlnode *mrow;
5762 int match_count = 0;
5763 gboolean more = FALSE;
5764 gchar *secondary;
5766 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5768 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5769 if (!searchResults) {
5770 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5771 return FALSE;
5774 results = purple_notify_searchresults_new();
5776 if (results == NULL) {
5777 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5778 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5780 xmlnode_free(searchResults);
5781 return FALSE;
5784 column = purple_notify_searchresults_column_new(_("User Name"));
5785 purple_notify_searchresults_column_add(results, column);
5787 column = purple_notify_searchresults_column_new(_("Name"));
5788 purple_notify_searchresults_column_add(results, column);
5790 column = purple_notify_searchresults_column_new(_("Company"));
5791 purple_notify_searchresults_column_add(results, column);
5793 column = purple_notify_searchresults_column_new(_("Country"));
5794 purple_notify_searchresults_column_add(results, column);
5796 column = purple_notify_searchresults_column_new(_("Email"));
5797 purple_notify_searchresults_column_add(results, column);
5799 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5800 GList *row = NULL;
5802 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5803 row = g_list_append(row, g_strdup(uri_parts[1]));
5804 g_strfreev(uri_parts);
5806 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5807 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5808 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5809 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5811 purple_notify_searchresults_row_add(results, row);
5812 match_count++;
5815 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5816 char *data = xmlnode_get_data_unescaped(mrow);
5817 more = (g_strcasecmp(data, "true") == 0);
5818 g_free(data);
5821 secondary = g_strdup_printf(
5822 dngettext(GETTEXT_PACKAGE,
5823 "Found %d contact%s:",
5824 "Found %d contacts%s:", match_count),
5825 match_count, more ? _(" (more matched your query)") : "");
5827 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5828 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5829 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5831 g_free(secondary);
5832 xmlnode_free(searchResults);
5833 return TRUE;
5836 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5838 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5839 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5840 unsigned i = 0;
5842 do {
5843 PurpleRequestField *field = entries->data;
5844 const char *id = purple_request_field_get_id(field);
5845 const char *value = purple_request_field_string_get_value(field);
5847 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5849 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5850 } while ((entries = g_list_next(entries)) != NULL);
5851 attrs[i] = NULL;
5853 if (i > 0) {
5854 gchar *query = g_strjoinv(NULL, attrs);
5855 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5856 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5857 send_soap_request_with_cb(gc->proto_data, body,
5858 (TransCallback) process_search_contact_response, NULL);
5859 g_free(body);
5860 g_free(query);
5863 g_strfreev(attrs);
5866 static void sipe_show_find_contact(PurplePluginAction *action)
5868 PurpleConnection *gc = (PurpleConnection *) action->context;
5869 PurpleRequestFields *fields;
5870 PurpleRequestFieldGroup *group;
5871 PurpleRequestField *field;
5873 fields = purple_request_fields_new();
5874 group = purple_request_field_group_new(NULL);
5875 purple_request_fields_add_group(fields, group);
5877 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5878 purple_request_field_group_add_field(group, field);
5879 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5880 purple_request_field_group_add_field(group, field);
5881 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5882 purple_request_field_group_add_field(group, field);
5883 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5884 purple_request_field_group_add_field(group, field);
5886 purple_request_fields(gc,
5887 _("Search"),
5888 _("Search for a Contact"),
5889 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5890 fields,
5891 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5892 _("_Cancel"), NULL,
5893 purple_connection_get_account(gc), NULL, NULL, gc);
5896 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5898 GList *menu = NULL;
5899 PurplePluginAction *act;
5901 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5902 menu = g_list_prepend(menu, act);
5904 menu = g_list_reverse(menu);
5906 return menu;
5909 static void dummy_permit_deny(PurpleConnection *gc)
5913 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5915 return TRUE;
5919 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5921 return TRUE;
5925 static char *sipe_status_text(PurpleBuddy *buddy)
5927 struct sipe_account_data *sip;
5928 struct sipe_buddy *sbuddy;
5929 char *text = NULL;
5931 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5932 if (sip) //happens on pidgin exit
5934 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5935 if (sbuddy && sbuddy->annotation)
5937 text = g_strdup(sbuddy->annotation);
5941 return text;
5944 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5946 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5947 const PurpleStatus *status = purple_presence_get_active_status(presence);
5948 struct sipe_account_data *sip;
5949 struct sipe_buddy *sbuddy;
5950 char *annotation = NULL;
5952 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5953 if (sip) //happens on pidgin exit
5955 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5956 if (sbuddy)
5958 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5962 //Layout
5963 if (purple_presence_is_online(presence))
5965 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5968 if (annotation)
5970 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5971 g_free(annotation);
5976 static GHashTable *
5977 sipe_get_account_text_table(PurpleAccount *account)
5979 GHashTable *table;
5980 table = g_hash_table_new(g_str_hash, g_str_equal);
5981 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5982 return table;
5985 static PurpleBuddy *
5986 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5988 PurpleBuddy *clone;
5989 const gchar *server_alias, *email;
5990 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5992 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5994 purple_blist_add_buddy(clone, NULL, group, NULL);
5996 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5997 if (server_alias) {
5998 purple_blist_server_alias_buddy(clone, server_alias);
6001 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6002 if (email) {
6003 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
6006 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
6007 //for UI to update;
6008 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
6009 return clone;
6012 static void
6013 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
6015 PurpleBuddy *buddy, *b;
6016 PurpleConnection *gc;
6017 PurpleGroup * group = purple_find_group(group_name);
6019 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
6021 buddy = (PurpleBuddy *)node;
6023 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
6024 gc = purple_account_get_connection(buddy->account);
6026 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
6027 if (!b){
6028 b = purple_blist_add_buddy_clone(group, buddy);
6031 sipe_group_buddy(gc, buddy->name, NULL, group_name);
6034 static void
6035 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
6037 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6038 gchar *self = g_strdup_printf("sip:%s", sip->username);
6039 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
6040 struct sip_im_session *session;
6042 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6044 session = create_chat_session(sip);
6045 session->roster_manager = g_strdup(self);
6047 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6048 session->chat_name = g_strdup(chat_name);
6049 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6050 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6052 g_free(chat_name);
6053 g_free(self);
6056 static gboolean
6057 sipe_is_election_finished(struct sipe_account_data *sip,
6058 struct sip_im_session *session)
6060 gboolean res = TRUE;
6062 SIPE_DIALOG_FOREACH {
6063 if (dialog->election_vote == 0) {
6064 res = FALSE;
6065 break;
6067 } SIPE_DIALOG_FOREACH_END;
6069 if (res) {
6070 session->is_voting_in_progress = FALSE;
6072 return res;
6075 static void
6076 sipe_election_start(struct sipe_account_data *sip,
6077 struct sip_im_session *session)
6079 int election_timeout;
6081 if (session->is_voting_in_progress) {
6082 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6083 return;
6084 } else {
6085 session->is_voting_in_progress = TRUE;
6087 session->bid = rand();
6089 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6091 SIPE_DIALOG_FOREACH {
6092 /* reset election_vote for each chat participant */
6093 dialog->election_vote = 0;
6095 /* send RequestRM to each chat participant*/
6096 sipe_send_election_request_rm(sip, dialog, session->bid);
6097 } SIPE_DIALOG_FOREACH_END;
6099 election_timeout = 15; /* sec */
6100 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6104 * @param who a URI to whom to invite to chat
6106 static void
6107 sipe_invite_to_chat(struct sipe_account_data *sip,
6108 struct sip_im_session *session,
6109 const char *who)
6111 gchar *self = g_strdup_printf("sip:%s", sip->username);
6113 if (session->roster_manager) {
6114 if (!strcmp(session->roster_manager, self)) {
6115 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6116 } else {
6117 sipe_refer(sip, session, who);
6119 } else {
6120 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6122 session->pending_invite_queue = slist_insert_unique_sorted(
6123 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6125 sipe_election_start(sip, session);
6128 g_free(self);
6131 static void
6132 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6133 struct sip_im_session *session)
6135 gchar *invitee;
6136 GSList *entry = session->pending_invite_queue;
6138 while (entry) {
6139 invitee = entry->data;
6140 sipe_invite_to_chat(sip, session, invitee);
6141 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6142 g_free(invitee);
6146 static void
6147 sipe_election_result(struct sipe_account_data *sip,
6148 void *sess)
6150 struct sip_im_session *session = (struct sip_im_session *)sess;
6151 gchar *rival;
6152 gboolean has_won = TRUE;
6154 if (session->roster_manager) {
6155 purple_debug_info("sipe",
6156 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6157 return;
6160 session->is_voting_in_progress = FALSE;
6162 SIPE_DIALOG_FOREACH {
6163 if (dialog->election_vote < 0) {
6164 has_won = FALSE;
6165 rival = dialog->with;
6166 break;
6168 } SIPE_DIALOG_FOREACH_END;
6170 if (has_won) {
6171 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6173 session->roster_manager = g_strdup_printf("sip:%s", sip->username);
6175 SIPE_DIALOG_FOREACH {
6176 /* send SetRM to each chat participant*/
6177 sipe_send_election_set_rm(sip, dialog);
6178 } SIPE_DIALOG_FOREACH_END;
6179 } else {
6180 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6182 session->bid = 0;
6184 sipe_process_pending_invite_queue(sip, session);
6187 static void
6188 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6190 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6191 struct sip_im_session *session;
6193 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6194 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6196 session = find_chat_session_by_name(sip, chat_name);
6198 sipe_invite_to_chat(sip, session, buddy->name);
6201 static void
6202 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6204 const gchar *email;
6205 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6207 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6208 if (email)
6210 char *mailto = g_strdup_printf("mailto:%s", email);
6211 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6212 #ifndef _WIN32
6214 pid_t pid;
6215 char *const parmList[] = {mailto, NULL};
6216 if ((pid = fork()) == -1)
6218 purple_debug_info("sipe", "fork() error\n");
6220 else if (pid == 0)
6222 execvp("xdg-email", parmList);
6223 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6226 #else
6228 BOOL ret;
6229 _flushall();
6230 errno = 0;
6231 //@TODO resolve env variable %WINDIR% first
6232 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6233 if (errno)
6235 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6238 #endif
6240 g_free(mailto);
6242 else
6244 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6249 * A menu which appear when right-clicking on buddy in contact list.
6251 static GList *
6252 sipe_buddy_menu(PurpleBuddy *buddy)
6254 PurpleBlistNode *g_node;
6255 PurpleGroup *group, *gr_parent;
6256 PurpleMenuAction *act;
6257 GList *menu = NULL;
6258 GList *menu_groups = NULL;
6259 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6260 struct sip_im_session *session;
6261 GSList *entry;
6262 gchar *self = g_strdup_printf("sip:%s", sip->username);
6264 act = purple_menu_action_new(_("New Chat"),
6265 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6266 NULL, NULL);
6267 menu = g_list_prepend(menu, act);
6269 entry = sip->im_sessions;
6270 while (entry) {
6271 session = entry->data;
6272 if (strcmp(self, buddy->name) && session->chat_name &&
6273 !sipe_dialog_find(session, buddy->name)) {
6274 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_name);
6275 act = purple_menu_action_new(label,
6276 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6277 g_strdup(session->chat_name), NULL);
6278 g_free(label);
6279 menu = g_list_prepend(menu, act);
6281 entry = entry->next;
6284 act = purple_menu_action_new(_("Send Email..."),
6285 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6286 NULL, NULL);
6287 menu = g_list_prepend(menu, act);
6289 gr_parent = purple_buddy_get_group(buddy);
6290 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6291 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6292 continue;
6294 group = (PurpleGroup *)g_node;
6295 if (group == gr_parent)
6296 continue;
6298 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6299 continue;
6301 act = purple_menu_action_new(purple_group_get_name(group),
6302 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6303 group->name, NULL);
6304 menu_groups = g_list_prepend(menu_groups, act);
6306 menu_groups = g_list_reverse(menu_groups);
6308 act = purple_menu_action_new(_("Copy to"),
6309 NULL,
6310 NULL, menu_groups);
6311 menu = g_list_prepend(menu, act);
6312 menu = g_list_reverse(menu);
6314 g_free(self);
6315 return menu;
6318 static GList *
6319 sipe_blist_node_menu(PurpleBlistNode *node)
6321 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6322 return sipe_buddy_menu((PurpleBuddy *) node);
6323 } else {
6324 return NULL;
6328 static gboolean
6329 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6331 gboolean ret = TRUE;
6332 char *username = (char *)trans->payload;
6334 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6335 PurpleBuddy *pbuddy;
6336 struct sipe_buddy *sbuddy;
6337 const char *alias;
6338 char *server_alias = NULL;
6339 char *email = NULL;
6340 const char *device_name = NULL;
6342 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6344 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6345 alias = purple_buddy_get_local_alias(pbuddy);
6347 if (sip)
6349 //will query buddy UA's capabilities and send answer to log
6350 sipe_options_request(sip, username);
6352 sbuddy = g_hash_table_lookup(sip->buddies, username);
6353 if (sbuddy)
6355 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6359 if (msg->response != 200) {
6360 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6361 } else {
6362 xmlnode *searchResults;
6363 xmlnode *mrow;
6365 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6366 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6367 if (!searchResults) {
6368 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6369 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6370 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6371 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6372 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6373 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6374 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6375 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6376 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6377 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6378 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6379 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6380 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6381 if (!email || strcmp("", email)) {
6382 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6383 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6387 xmlnode_free(searchResults);
6390 purple_notify_user_info_add_section_break(info);
6392 if (!server_alias || !strcmp("", server_alias)) {
6393 g_free(server_alias);
6394 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6395 if (server_alias) {
6396 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6400 // same as server alias, do not present
6401 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6402 if (alias)
6404 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6407 if (!email || !strcmp("", email)) {
6408 g_free(email);
6409 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6410 if (email) {
6411 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6415 if (device_name)
6417 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6420 /* show a buddy's user info in a nice dialog box */
6421 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6422 username, /* buddy's username */
6423 info, /* body */
6424 NULL, /* callback called when dialog closed */
6425 NULL); /* userdata for callback */
6427 return ret;
6431 * AD search first, LDAP based
6433 static void sipe_get_info(PurpleConnection *gc, const char *username)
6435 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6436 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6438 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6439 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6440 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6441 g_free(body);
6442 g_free(row);
6445 static PurplePlugin *my_protocol = NULL;
6447 static PurplePluginProtocolInfo prpl_info =
6450 NULL, /* user_splits */
6451 NULL, /* protocol_options */
6452 NO_BUDDY_ICONS, /* icon_spec */
6453 sipe_list_icon, /* list_icon */
6454 NULL, /* list_emblems */
6455 sipe_status_text, /* status_text */
6456 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6457 sipe_status_types, /* away_states */
6458 sipe_blist_node_menu, /* blist_node_menu */
6459 NULL, /* chat_info */
6460 NULL, /* chat_info_defaults */
6461 sipe_login, /* login */
6462 sipe_close, /* close */
6463 sipe_im_send, /* send_im */
6464 NULL, /* set_info */ // TODO maybe
6465 sipe_send_typing, /* send_typing */
6466 sipe_get_info, /* get_info */
6467 sipe_set_status, /* set_status */
6468 NULL, /* set_idle */
6469 NULL, /* change_passwd */
6470 sipe_add_buddy, /* add_buddy */
6471 NULL, /* add_buddies */
6472 sipe_remove_buddy, /* remove_buddy */
6473 NULL, /* remove_buddies */
6474 sipe_add_permit, /* add_permit */
6475 sipe_add_deny, /* add_deny */
6476 sipe_add_deny, /* rem_permit */
6477 sipe_add_permit, /* rem_deny */
6478 dummy_permit_deny, /* set_permit_deny */
6479 NULL, /* join_chat */
6480 NULL, /* reject_chat */
6481 NULL, /* get_chat_name */
6482 NULL, /* chat_invite */
6483 sipe_chat_leave, /* chat_leave */
6484 NULL, /* chat_whisper */
6485 sipe_chat_send, /* chat_send */
6486 sipe_keep_alive, /* keepalive */
6487 NULL, /* register_user */
6488 NULL, /* get_cb_info */ // deprecated
6489 NULL, /* get_cb_away */ // deprecated
6490 sipe_alias_buddy, /* alias_buddy */
6491 sipe_group_buddy, /* group_buddy */
6492 sipe_rename_group, /* rename_group */
6493 NULL, /* buddy_free */
6494 sipe_convo_closed, /* convo_closed */
6495 purple_normalize_nocase, /* normalize */
6496 NULL, /* set_buddy_icon */
6497 sipe_remove_group, /* remove_group */
6498 NULL, /* get_cb_real_name */ // TODO?
6499 NULL, /* set_chat_topic */
6500 NULL, /* find_blist_chat */
6501 NULL, /* roomlist_get_list */
6502 NULL, /* roomlist_cancel */
6503 NULL, /* roomlist_expand_category */
6504 NULL, /* can_receive_file */
6505 NULL, /* send_file */
6506 NULL, /* new_xfer */
6507 NULL, /* offline_message */
6508 NULL, /* whiteboard_prpl_ops */
6509 sipe_send_raw, /* send_raw */
6510 NULL, /* roomlist_room_serialize */
6511 NULL, /* unregister_user */
6512 NULL, /* send_attention */
6513 NULL, /* get_attention_types */
6515 sizeof(PurplePluginProtocolInfo), /* struct_size */
6516 sipe_get_account_text_table, /* get_account_text_table */
6520 static PurplePluginInfo info = {
6521 PURPLE_PLUGIN_MAGIC,
6522 PURPLE_MAJOR_VERSION,
6523 PURPLE_MINOR_VERSION,
6524 PURPLE_PLUGIN_PROTOCOL, /**< type */
6525 NULL, /**< ui_requirement */
6526 0, /**< flags */
6527 NULL, /**< dependencies */
6528 PURPLE_PRIORITY_DEFAULT, /**< priority */
6529 "prpl-sipe", /**< id */
6530 "Microsoft LCS/OCS", /**< name */
6531 VERSION, /**< version */
6532 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6533 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6534 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6535 "Gabriel Burt <gburt@novell.com>", /**< author */
6536 PURPLE_WEBSITE, /**< homepage */
6537 sipe_plugin_load, /**< load */
6538 sipe_plugin_unload, /**< unload */
6539 sipe_plugin_destroy, /**< destroy */
6540 NULL, /**< ui_info */
6541 &prpl_info, /**< extra_info */
6542 NULL,
6543 sipe_actions,
6544 NULL,
6545 NULL,
6546 NULL,
6547 NULL
6550 static void sipe_plugin_destroy(PurplePlugin *plugin)
6552 GList *entry;
6554 entry = prpl_info.protocol_options;
6555 while (entry) {
6556 purple_account_option_destroy(entry->data);
6557 entry = g_list_delete_link(entry, entry);
6559 prpl_info.protocol_options = NULL;
6561 entry = prpl_info.user_splits;
6562 while (entry) {
6563 purple_account_user_split_destroy(entry->data);
6564 entry = g_list_delete_link(entry, entry);
6566 prpl_info.user_splits = NULL;
6569 static void init_plugin(PurplePlugin *plugin)
6571 PurpleAccountUserSplit *split;
6572 PurpleAccountOption *option;
6574 srand(time(NULL));
6576 #ifdef ENABLE_NLS
6577 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6578 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6579 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6580 textdomain(GETTEXT_PACKAGE);
6581 #endif
6583 purple_plugin_register(plugin);
6585 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
6586 purple_account_user_split_set_reverse(split, FALSE);
6587 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6589 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
6590 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6591 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6592 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6594 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
6595 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6596 // Translators: noun (networking port)
6597 option = purple_account_option_int_new(_("Port"), "port", 5061);
6598 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6600 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6601 purple_account_option_add_list_item(option, _("Auto"), "auto");
6602 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6603 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6604 purple_account_option_add_list_item(option, _("UDP"), "udp");
6605 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6607 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6608 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6610 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6611 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6613 #ifdef USE_KERBEROS
6614 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6615 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6617 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6618 * No login/password is taken into account if this option present,
6619 * instead used default credentials stored in OS.
6621 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6622 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6623 #endif
6624 my_protocol = plugin;
6627 /* I had to redefined the function for it load, but works */
6628 gboolean purple_init_plugin(PurplePlugin *plugin){
6629 plugin->info = &(info);
6630 init_plugin((plugin));
6631 sipe_plugin_load((plugin));
6632 return purple_plugin_register(plugin);
6636 Local Variables:
6637 mode: c
6638 c-file-style: "bsd"
6639 indent-tabs-mode: t
6640 tab-width: 8
6641 End: