conference: display conference subject
[siplcs.git] / src / sipe.c
blob8a9672660722369fa9611e23e7d8542fb7543d34
1 /**
2 * @file sipe.c
4 * pidgin-sipe
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 */
45 #include "internal.h"
46 #endif /* _WIN32 */
48 #include <time.h>
49 #include <stdio.h>
50 #include <errno.h>
51 #include <string.h>
52 #include <glib.h>
55 #include "accountopt.h"
56 #include "blist.h"
57 #include "conversation.h"
58 #include "dnsquery.h"
59 #include "debug.h"
60 #include "notify.h"
61 #include "privacy.h"
62 #include "prpl.h"
63 #include "plugin.h"
64 #include "util.h"
65 #include "version.h"
66 #include "network.h"
67 #include "xmlnode.h"
68 #include "mime.h"
70 #include "sipe.h"
71 #include "sipe-chat.h"
72 #include "sipe-conf.h"
73 #include "sipe-dialog.h"
74 #include "sipe-nls.h"
75 #include "sipe-session.h"
76 #include "sipe-utils.h"
77 #include "sipmsg.h"
78 #include "sipe-sign.h"
79 #include "dnssrv.h"
80 #include "request.h"
82 /* Keep in sync with sipe_transport_type! */
83 static const char *transport_descriptor[] = { "tls", "tcp", "udp" };
84 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
86 /* Status identifiers (see also: sipe_status_types()) */
87 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
88 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
89 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
90 /* PURPLE_STATUS_UNAVAILABLE: */
91 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
92 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
93 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
94 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
95 /* PURPLE_STATUS_AWAY: */
96 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
97 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
98 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
99 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
100 /* ??? PURPLE_STATUS_MOBILE */
101 /* ??? PURPLE_STATUS_TUNE */
103 /* Action name templates */
104 #define ACTION_NAME_PRESENCE "<presence><%s>"
107 static gchar *get_epid(struct sipe_account_data *sip)
109 if (!sip->epid) {
110 sip->epid = sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
112 return g_strdup(sip->epid);
115 static char *genbranch()
117 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
118 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
119 rand() & 0xFFFF, rand() & 0xFFFF);
122 static const char *sipe_list_icon(PurpleAccount *a, PurpleBuddy *b)
124 return "sipe";
127 static void sipe_plugin_destroy(PurplePlugin *plugin);
129 static gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc);
131 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond);
132 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
133 gpointer data);
135 static void sipe_close(PurpleConnection *gc);
137 static void send_presence_status(struct sipe_account_data *sip);
139 static void sendout_pkt(PurpleConnection *gc, const char *buf);
141 static void sipe_keep_alive(PurpleConnection *gc)
143 struct sipe_account_data *sip = gc->proto_data;
144 if (sip->transport == SIPE_TRANSPORT_UDP) {
145 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
146 gchar buf[2] = {0, 0};
147 purple_debug_info("sipe", "sending keep alive\n");
148 sendto(sip->fd, buf, 1, 0, sip->serveraddr, sizeof(struct sockaddr_in));
149 } else {
150 time_t now = time(NULL);
151 if ((sip->keepalive_timeout > 0) &&
152 ((guint) (now - sip->last_keepalive) >= sip->keepalive_timeout)
153 #if PURPLE_VERSION_CHECK(2,4,0)
154 && ((guint) (now - gc->last_received) >= sip->keepalive_timeout)
155 #endif
157 purple_debug_info("sipe", "sending keep alive %d\n",sip->keepalive_timeout);
158 sendout_pkt(gc, "\r\n\r\n");
159 sip->last_keepalive = now;
164 static struct sip_connection *connection_find(struct sipe_account_data *sip, int fd)
166 struct sip_connection *ret = NULL;
167 GSList *entry = sip->openconns;
168 while (entry) {
169 ret = entry->data;
170 if (ret->fd == fd) return ret;
171 entry = entry->next;
173 return NULL;
176 static void sipe_auth_free(struct sip_auth *auth)
178 g_free(auth->opaque);
179 auth->opaque = NULL;
180 g_free(auth->realm);
181 auth->realm = NULL;
182 g_free(auth->target);
183 auth->target = NULL;
184 auth->type = AUTH_TYPE_UNSET;
185 auth->retries = 0;
186 auth->expires = 0;
187 g_free(auth->gssapi_data);
188 auth->gssapi_data = NULL;
189 sip_sec_destroy_context(auth->gssapi_context);
190 auth->gssapi_context = NULL;
193 static struct sip_connection *connection_create(struct sipe_account_data *sip, int fd)
195 struct sip_connection *ret = g_new0(struct sip_connection, 1);
196 ret->fd = fd;
197 sip->openconns = g_slist_append(sip->openconns, ret);
198 return ret;
201 static void connection_remove(struct sipe_account_data *sip, int fd)
203 struct sip_connection *conn = connection_find(sip, fd);
204 if (conn) {
205 sip->openconns = g_slist_remove(sip->openconns, conn);
206 if (conn->inputhandler) purple_input_remove(conn->inputhandler);
207 g_free(conn->inbuf);
208 g_free(conn);
212 static void connection_free_all(struct sipe_account_data *sip)
214 struct sip_connection *ret = NULL;
215 GSList *entry = sip->openconns;
216 while (entry) {
217 ret = entry->data;
218 connection_remove(sip, ret->fd);
219 entry = sip->openconns;
223 static gchar *auth_header(struct sipe_account_data *sip, struct sip_auth *auth, struct sipmsg * msg)
225 gchar noncecount[9];
226 const char *authuser = sip->authuser;
227 gchar *response;
228 gchar *ret;
230 if (!authuser || strlen(authuser) < 1) {
231 authuser = sip->username;
234 if (auth->type == AUTH_TYPE_NTLM || auth->type == AUTH_TYPE_KERBEROS) { /* NTLM or Kerberos */
235 gchar *auth_protocol = (auth->type == AUTH_TYPE_NTLM ? "NTLM" : "Kerberos");
237 // If we have a signature for the message, include that
238 if (msg->signature) {
239 return g_strdup_printf("%s qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth_protocol, auth->opaque, auth->realm, auth->target, msg->rand, msg->num, msg->signature);
242 if ((auth->type == AUTH_TYPE_NTLM && auth->nc == 3 && auth->gssapi_data && auth->gssapi_context == NULL)
243 || (auth->type == AUTH_TYPE_KERBEROS && auth->nc == 3)) {
244 gchar *gssapi_data;
245 gchar *opaque;
247 gssapi_data = sip_sec_init_context(&(auth->gssapi_context),
248 &(auth->expires),
249 auth->type,
250 purple_account_get_bool(sip->account, "sso", TRUE),
251 sip->authdomain ? sip->authdomain : "",
252 authuser,
253 sip->password,
254 auth->target,
255 auth->gssapi_data);
256 if (!gssapi_data || !auth->gssapi_context)
257 return NULL;
259 opaque = (auth->type == AUTH_TYPE_NTLM ? g_strdup_printf(", opaque=\"%s\"", auth->opaque) : g_strdup(""));
260 ret = g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol, opaque, auth->realm, auth->target, gssapi_data);
261 g_free(opaque);
262 g_free(gssapi_data);
263 return ret;
266 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol, auth->realm, auth->target);
268 } else { /* Digest */
270 /* Calculate new session key */
271 if (!auth->opaque) {
272 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest nonce: %s realm: %s\n", auth->gssapi_data, auth->realm);
273 auth->opaque = purple_cipher_http_digest_calculate_session_key("md5",
274 authuser, auth->realm, sip->password,
275 auth->gssapi_data, NULL);
278 sprintf(noncecount, "%08d", auth->nc++);
279 response = purple_cipher_http_digest_calculate_response("md5",
280 msg->method, msg->target, NULL, NULL,
281 auth->gssapi_data, noncecount, NULL,
282 auth->opaque);
283 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Digest response %s\n", response);
285 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);
286 g_free(response);
287 return ret;
291 static char *parse_attribute(const char *attrname, const char *source)
293 const char *tmp, *tmp2;
294 char *retval = NULL;
295 int len = strlen(attrname);
297 if (!strncmp(source, attrname, len)) {
298 tmp = source + len;
299 tmp2 = g_strstr_len(tmp, strlen(tmp), "\"");
300 if (tmp2)
301 retval = g_strndup(tmp, tmp2 - tmp);
302 else
303 retval = g_strdup(tmp);
306 return retval;
309 static void fill_auth(struct sipe_account_data *sip, gchar *hdr, struct sip_auth *auth)
311 int i;
312 gchar **parts;
314 if (!hdr) {
315 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
316 return;
319 if (!g_strncasecmp(hdr, "NTLM", 4)) {
320 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type NTLM\n");
321 auth->type = AUTH_TYPE_NTLM;
322 hdr += 5;
323 auth->nc = 1;
324 } else if (!g_strncasecmp(hdr, "Kerberos", 8)) {
325 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Kerberos\n");
326 auth->type = AUTH_TYPE_KERBEROS;
327 hdr += 9;
328 auth->nc = 3;
329 } else {
330 purple_debug(PURPLE_DEBUG_MISC, "sipe", "fill_auth: type Digest\n");
331 auth->type = AUTH_TYPE_DIGEST;
332 hdr += 7;
335 parts = g_strsplit(hdr, "\", ", 0);
336 for (i = 0; parts[i]; i++) {
337 char *tmp;
339 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
341 if ((tmp = parse_attribute("gssapi-data=\"", parts[i]))) {
342 g_free(auth->gssapi_data);
343 auth->gssapi_data = tmp;
345 if (auth->type == AUTH_TYPE_NTLM) {
346 /* NTLM module extracts nonce from gssapi-data */
347 auth->nc = 3;
350 } else if ((tmp = parse_attribute("nonce=\"", parts[i]))) {
351 /* Only used with AUTH_TYPE_DIGEST */
352 g_free(auth->gssapi_data);
353 auth->gssapi_data = tmp;
354 } else if ((tmp = parse_attribute("opaque=\"", parts[i]))) {
355 g_free(auth->opaque);
356 auth->opaque = tmp;
357 } else if ((tmp = parse_attribute("realm=\"", parts[i]))) {
358 g_free(auth->realm);
359 auth->realm = tmp;
361 if (auth->type == AUTH_TYPE_DIGEST) {
362 /* Throw away old session key */
363 g_free(auth->opaque);
364 auth->opaque = NULL;
365 auth->nc = 1;
368 } else if ((tmp = parse_attribute("targetname=\"", parts[i]))) {
369 g_free(auth->target);
370 auth->target = tmp;
373 g_strfreev(parts);
375 return;
378 static void sipe_canwrite_cb(gpointer data, gint source, PurpleInputCondition cond)
380 PurpleConnection *gc = data;
381 struct sipe_account_data *sip = gc->proto_data;
382 gsize max_write;
383 gssize written;
385 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
387 if (max_write == 0) {
388 if (sip->tx_handler != 0){
389 purple_input_remove(sip->tx_handler);
390 sip->tx_handler = 0;
392 return;
395 written = write(sip->fd, sip->txbuf->outptr, max_write);
397 if (written < 0 && errno == EAGAIN)
398 written = 0;
399 else if (written <= 0) {
400 /*TODO: do we really want to disconnect on a failure to write?*/
401 purple_connection_error(gc, _("Could not write"));
402 return;
405 purple_circ_buffer_mark_read(sip->txbuf, written);
408 static void sipe_canwrite_cb_ssl(gpointer data, gint src, PurpleInputCondition cond)
410 PurpleConnection *gc = data;
411 struct sipe_account_data *sip = gc->proto_data;
412 gsize max_write;
413 gssize written;
415 max_write = purple_circ_buffer_get_max_read(sip->txbuf);
417 if (max_write == 0) {
418 if (sip->tx_handler != 0) {
419 purple_input_remove(sip->tx_handler);
420 sip->tx_handler = 0;
421 return;
425 written = purple_ssl_write(sip->gsc, sip->txbuf->outptr, max_write);
427 if (written < 0 && errno == EAGAIN)
428 written = 0;
429 else if (written <= 0) {
430 /*TODO: do we really want to disconnect on a failure to write?*/
431 purple_connection_error(gc, _("Could not write"));
432 return;
435 purple_circ_buffer_mark_read(sip->txbuf, written);
438 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond);
440 static void send_later_cb(gpointer data, gint source, const gchar *error)
442 PurpleConnection *gc = data;
443 struct sipe_account_data *sip;
444 struct sip_connection *conn;
446 if (!PURPLE_CONNECTION_IS_VALID(gc))
448 if (source >= 0)
449 close(source);
450 return;
453 if (source < 0) {
454 purple_connection_error(gc, _("Could not connect"));
455 return;
458 sip = gc->proto_data;
459 sip->fd = source;
460 sip->connecting = FALSE;
461 sip->last_keepalive = time(NULL);
463 sipe_canwrite_cb(gc, sip->fd, PURPLE_INPUT_WRITE);
465 /* If there is more to write now, we need to register a handler */
466 if (sip->txbuf->bufused > 0)
467 sip->tx_handler = purple_input_add(sip->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb, gc);
469 conn = connection_create(sip, source);
470 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
473 static struct sipe_account_data *sipe_setup_ssl(PurpleConnection *gc, PurpleSslConnection *gsc)
475 struct sipe_account_data *sip;
476 struct sip_connection *conn;
478 if (!PURPLE_CONNECTION_IS_VALID(gc))
480 if (gsc) purple_ssl_close(gsc);
481 return NULL;
484 sip = gc->proto_data;
485 sip->fd = gsc->fd;
486 sip->gsc = gsc;
487 sip->listenport = purple_network_get_port_from_fd(gsc->fd);
488 sip->connecting = FALSE;
489 sip->last_keepalive = time(NULL);
491 conn = connection_create(sip, gsc->fd);
493 purple_ssl_input_add(gsc, sipe_input_cb_ssl, gc);
495 return sip;
498 static void send_later_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
500 PurpleConnection *gc = data;
501 struct sipe_account_data *sip = sipe_setup_ssl(gc, gsc);
502 if (sip == NULL) return;
504 sipe_canwrite_cb_ssl(gc, gsc->fd, PURPLE_INPUT_WRITE);
506 /* If there is more to write now */
507 if (sip->txbuf->bufused > 0) {
508 sip->tx_handler = purple_input_add(gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
513 static void sendlater(PurpleConnection *gc, const char *buf)
515 struct sipe_account_data *sip = gc->proto_data;
517 if (!sip->connecting) {
518 purple_debug_info("sipe", "connecting to %s port %d\n", sip->realhostname ? sip->realhostname : "{NULL}", sip->realport);
519 if (sip->transport == SIPE_TRANSPORT_TLS){
520 sip->gsc = purple_ssl_connect(sip->account,sip->realhostname, sip->realport, send_later_cb_ssl, sipe_ssl_connect_failure, sip->gc);
521 } else {
522 if (purple_proxy_connect(gc, sip->account, sip->realhostname, sip->realport, send_later_cb, gc) == NULL) {
523 purple_connection_error(gc, _("Couldn't create socket"));
526 sip->connecting = TRUE;
529 if (purple_circ_buffer_get_max_read(sip->txbuf) > 0)
530 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
532 purple_circ_buffer_append(sip->txbuf, buf, strlen(buf));
535 static void sendout_pkt(PurpleConnection *gc, const char *buf)
537 struct sipe_account_data *sip = gc->proto_data;
538 time_t currtime = time(NULL);
539 int writelen = strlen(buf);
541 purple_debug(PURPLE_DEBUG_MISC, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime), buf);
542 if (sip->transport == SIPE_TRANSPORT_UDP) {
543 if (sendto(sip->fd, buf, writelen, 0, sip->serveraddr, sizeof(struct sockaddr_in)) < writelen) {
544 purple_debug_info("sipe", "could not send packet\n");
546 } else {
547 int ret;
548 if (sip->fd < 0) {
549 sendlater(gc, buf);
550 return;
553 if (sip->tx_handler) {
554 ret = -1;
555 errno = EAGAIN;
556 } else{
557 if (sip->gsc){
558 ret = purple_ssl_write(sip->gsc, buf, writelen);
559 }else{
560 ret = write(sip->fd, buf, writelen);
564 if (ret < 0 && errno == EAGAIN)
565 ret = 0;
566 else if (ret <= 0) { /* XXX: When does this happen legitimately? */
567 sendlater(gc, buf);
568 return;
571 if (ret < writelen) {
572 if (!sip->tx_handler){
573 if (sip->gsc){
574 sip->tx_handler = purple_input_add(sip->gsc->fd, PURPLE_INPUT_WRITE, sipe_canwrite_cb_ssl, gc);
576 else{
577 sip->tx_handler = purple_input_add(sip->fd,
578 PURPLE_INPUT_WRITE, sipe_canwrite_cb,
579 gc);
583 /* XXX: is it OK to do this? You might get part of a request sent
584 with part of another. */
585 if (sip->txbuf->bufused > 0)
586 purple_circ_buffer_append(sip->txbuf, "\r\n", 2);
588 purple_circ_buffer_append(sip->txbuf, buf + ret,
589 writelen - ret);
594 static int sipe_send_raw(PurpleConnection *gc, const char *buf, int len)
596 sendout_pkt(gc, buf);
597 return len;
600 static void sendout_sipmsg(struct sipe_account_data *sip, struct sipmsg *msg)
602 GSList *tmp = msg->headers;
603 gchar *name;
604 gchar *value;
605 GString *outstr = g_string_new("");
606 g_string_append_printf(outstr, "%s %s SIP/2.0\r\n", msg->method, msg->target);
607 while (tmp) {
608 name = ((struct siphdrelement*) (tmp->data))->name;
609 value = ((struct siphdrelement*) (tmp->data))->value;
610 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
611 tmp = g_slist_next(tmp);
613 g_string_append_printf(outstr, "\r\n%s", msg->body ? msg->body : "");
614 sendout_pkt(sip->gc, outstr->str);
615 g_string_free(outstr, TRUE);
618 static void sign_outgoing_message (struct sipmsg * msg, struct sipe_account_data *sip, const gchar *method)
620 gchar * buf;
621 if (sip->registrar.gssapi_context) {
622 struct sipmsg_breakdown msgbd;
623 gchar *signature_input_str;
624 msgbd.msg = msg;
625 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
626 msgbd.rand = g_strdup_printf("%08x", g_random_int());
627 sip->registrar.ntlm_num++;
628 msgbd.num = g_strdup_printf("%d", sip->registrar.ntlm_num);
629 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
630 if (signature_input_str != NULL) {
631 char *signature_hex = sip_sec_make_signature(sip->registrar.gssapi_context, signature_input_str);
632 msg->signature = signature_hex;
633 msg->rand = g_strdup(msgbd.rand);
634 msg->num = g_strdup(msgbd.num);
635 g_free(signature_input_str);
637 sipmsg_breakdown_free(&msgbd);
640 if (sip->registrar.type && !strcmp(method, "REGISTER")) {
641 buf = auth_header(sip, &sip->registrar, msg);
642 sipmsg_add_header_now_pos(msg, "Authorization", buf, 5);
643 g_free(buf);
644 } 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")) {
645 sip->registrar.nc = 3;
646 #ifdef USE_KERBEROS
647 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
648 #endif
649 sip->registrar.type = AUTH_TYPE_NTLM;
650 #ifdef USE_KERBEROS
651 } else {
652 sip->registrar.type = AUTH_TYPE_KERBEROS;
654 #endif
657 buf = auth_header(sip, &sip->registrar, msg);
658 sipmsg_add_header_now_pos(msg, "Proxy-Authorization", buf, 5);
659 g_free(buf);
660 } else {
661 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method);
666 * unused. Needed?
667 static char *get_contact_service(struct sipe_account_data *sip)
669 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()));
670 //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);
674 void send_sip_response(PurpleConnection *gc, struct sipmsg *msg, int code,
675 const char *text, const char *body)
677 gchar *name;
678 gchar *value;
679 GString *outstr = g_string_new("");
680 struct sipe_account_data *sip = gc->proto_data;
681 gchar *contact;
682 GSList *tmp;
683 const gchar *keepers[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL };
685 contact = get_contact(sip);
686 sipmsg_add_header(msg, "Contact", contact);
687 g_free(contact);
689 if (body) {
690 gchar len[12];
691 sprintf(len, "%" G_GSIZE_FORMAT , (gsize) strlen(body));
692 sipmsg_add_header(msg, "Content-Length", len);
693 } else {
694 sipmsg_add_header(msg, "Content-Length", "0");
697 msg->response = code;
699 sipmsg_strip_headers(msg, keepers);
700 sipmsg_merge_new_headers(msg);
701 sign_outgoing_message(msg, sip, msg->method);
703 g_string_append_printf(outstr, "SIP/2.0 %d %s\r\n", code, text);
704 tmp = msg->headers;
705 while (tmp) {
706 name = ((struct siphdrelement*) (tmp->data))->name;
707 value = ((struct siphdrelement*) (tmp->data))->value;
709 g_string_append_printf(outstr, "%s: %s\r\n", name, value);
710 tmp = g_slist_next(tmp);
712 g_string_append_printf(outstr, "\r\n%s", body ? body : "");
713 sendout_pkt(gc, outstr->str);
714 g_string_free(outstr, TRUE);
717 static void transactions_remove(struct sipe_account_data *sip, struct transaction *trans)
719 sip->transactions = g_slist_remove(sip->transactions, trans);
720 if (trans->msg) sipmsg_free(trans->msg);
721 g_free(trans->key);
722 g_free(trans);
725 static struct transaction *
726 transactions_add_buf(struct sipe_account_data *sip, const struct sipmsg *msg, void *callback)
728 gchar *call_id = NULL;
729 gchar *cseq = NULL;
730 struct transaction *trans = g_new0(struct transaction, 1);
732 trans->time = time(NULL);
733 trans->msg = (struct sipmsg *)msg;
734 call_id = sipmsg_find_header(trans->msg, "Call-ID");
735 cseq = sipmsg_find_header(trans->msg, "CSeq");
736 trans->key = g_strdup_printf("<%s><%s>", call_id, cseq);
737 trans->callback = callback;
738 sip->transactions = g_slist_append(sip->transactions, trans);
739 return trans;
742 static struct transaction *transactions_find(struct sipe_account_data *sip, struct sipmsg *msg)
744 struct transaction *trans;
745 GSList *transactions = sip->transactions;
746 gchar *call_id = sipmsg_find_header(msg, "Call-ID");
747 gchar *cseq = sipmsg_find_header(msg, "CSeq");
748 gchar *key = g_strdup_printf("<%s><%s>", call_id, cseq);
750 while (transactions) {
751 trans = transactions->data;
752 if (!g_strcasecmp(trans->key, key)) {
753 g_free(key);
754 return trans;
756 transactions = transactions->next;
759 g_free(key);
760 return NULL;
763 struct transaction *
764 send_sip_request(PurpleConnection *gc, const gchar *method,
765 const gchar *url, const gchar *to, const gchar *addheaders,
766 const gchar *body, struct sip_dialog *dialog, TransCallback tc)
768 struct sipe_account_data *sip = gc->proto_data;
769 const char *addh = "";
770 char *buf;
771 struct sipmsg *msg;
772 gchar *ourtag = dialog && dialog->ourtag ? g_strdup(dialog->ourtag) : NULL;
773 gchar *theirtag = dialog && dialog->theirtag ? g_strdup(dialog->theirtag) : NULL;
774 gchar *theirepid = dialog && dialog->theirepid ? g_strdup(dialog->theirepid) : NULL;
775 gchar *callid = dialog && dialog->callid ? g_strdup(dialog->callid) : gencallid();
776 gchar *branch = dialog && dialog->callid ? NULL : genbranch();
777 gchar *useragent = (gchar *)purple_account_get_string(sip->account, "useragent", "Purple/" VERSION);
778 gchar *route = strdup("");
779 gchar *epid = get_epid(sip); // TODO generate one per account/login
780 int cseq = dialog ? ++dialog->cseq :
781 /* This breaks OCS2007: own presence, contact search, ?
782 1 .* as Call-Id is new in this case */
783 ++sip->cseq;
784 struct transaction *trans;
786 if (dialog && dialog->routes)
788 GSList *iter = dialog->routes;
790 while(iter)
792 char *tmp = route;
793 route = g_strdup_printf("%sRoute: <%s>\r\n", route, (char *)iter->data);
794 g_free(tmp);
795 iter = g_slist_next(iter);
799 if (!ourtag && !dialog) {
800 ourtag = gentag();
803 if (!strcmp(method, "REGISTER")) {
804 if (sip->regcallid) {
805 g_free(callid);
806 callid = g_strdup(sip->regcallid);
807 } else {
808 sip->regcallid = g_strdup(callid);
812 if (addheaders) addh = addheaders;
814 buf = g_strdup_printf("%s %s SIP/2.0\r\n"
815 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
816 "From: <sip:%s>%s%s;epid=%s\r\n"
817 "To: <%s>%s%s%s%s\r\n"
818 "Max-Forwards: 70\r\n"
819 "CSeq: %d %s\r\n"
820 "User-Agent: %s\r\n"
821 "Call-ID: %s\r\n"
822 "%s%s"
823 "Content-Length: %" G_GSIZE_FORMAT "\r\n\r\n%s",
824 method,
825 dialog && dialog->request ? dialog->request : url,
826 TRANSPORT_DESCRIPTOR,
827 purple_network_get_my_ip(-1),
828 sip->listenport,
829 branch ? ";branch=" : "",
830 branch ? branch : "",
831 sip->username,
832 ourtag ? ";tag=" : "",
833 ourtag ? ourtag : "",
834 epid,
836 theirtag ? ";tag=" : "",
837 theirtag ? theirtag : "",
838 theirepid ? ";epid=" : "",
839 theirepid ? theirepid : "",
840 cseq,
841 method,
842 useragent,
843 callid,
844 route,
845 addh,
846 body ? (gsize) strlen(body) : 0,
847 body ? body : "");
850 //printf ("parsing msg buf:\n%s\n\n", buf);
851 msg = sipmsg_parse_msg(buf);
853 g_free(buf);
854 g_free(ourtag);
855 g_free(theirtag);
856 g_free(theirepid);
857 g_free(branch);
858 g_free(callid);
859 g_free(route);
860 g_free(epid);
862 sign_outgoing_message (msg, sip, method);
864 buf = sipmsg_to_string (msg);
866 /* add to ongoing transactions */
867 trans = transactions_add_buf(sip, msg, tc);
868 sendout_pkt(gc, buf);
869 g_free(buf);
871 return trans;
874 static void send_soap_request_with_cb(struct sipe_account_data *sip, gchar *body, TransCallback callback, void * payload)
876 gchar *from = sip_uri_self(sip);
877 gchar *contact = get_contact(sip);
878 gchar *hdr = g_strdup_printf("Contact: %s\r\n"
879 "Content-Type: application/SOAP+xml\r\n",contact);
881 struct transaction * tr = send_sip_request(sip->gc, "SERVICE", from, from, hdr, body, NULL, callback);
882 tr->payload = payload;
884 g_free(from);
885 g_free(contact);
886 g_free(hdr);
889 static void send_soap_request(struct sipe_account_data *sip, gchar *body)
891 send_soap_request_with_cb(sip, body, NULL, NULL);
894 static char *get_contact_register(struct sipe_account_data *sip)
896 char *epid = get_epid(sip);
897 char *uuid = generateUUIDfromEPID(epid);
898 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);
899 g_free(uuid);
900 g_free(epid);
901 return(buf);
904 static void do_register_exp(struct sipe_account_data *sip, int expire)
906 char *expires = expire >= 0 ? g_strdup_printf("Expires: %d\r\n", expire) : g_strdup("");
907 char *uri = sip_uri_from_name(sip->sipdomain);
908 char *to = sip_uri_self(sip);
909 char *contact = get_contact_register(sip);
910 char *hdr = g_strdup_printf("Contact: %s\r\n"
911 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
912 "Event: registration\r\n"
913 "Allow-Events: presence\r\n"
914 "ms-keep-alive: UAC;hop-hop=yes\r\n"
915 "%s", contact, expires);
916 g_free(contact);
917 g_free(expires);
919 sip->registerstatus = 1;
921 send_sip_request(sip->gc, "REGISTER", uri, to, hdr, "", NULL,
922 process_register_response);
924 g_free(hdr);
925 g_free(uri);
926 g_free(to);
929 static void do_register_cb(struct sipe_account_data *sip, void *unused)
931 do_register_exp(sip, -1);
932 sip->reregister_set = FALSE;
935 static void do_register(struct sipe_account_data *sip)
937 do_register_exp(sip, -1);
940 static void
941 sipe_contact_set_acl (struct sipe_account_data *sip, const gchar * who, gchar * rights)
943 gchar * body = g_strdup_printf(SIPE_SOAP_ALLOW_DENY, who, rights, sip->acl_delta++);
944 send_soap_request(sip, body);
945 g_free(body);
948 static void
949 sipe_contact_allow_deny (struct sipe_account_data *sip, const gchar * who, gboolean allow)
951 if (allow) {
952 purple_debug_info("sipe", "Authorizing contact %s\n", who);
953 } else {
954 purple_debug_info("sipe", "Blocking contact %s\n", who);
957 sipe_contact_set_acl (sip, who, allow ? "AA" : "BD");
960 static
961 void sipe_auth_user_cb(void * data)
963 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
964 if (!job) return;
966 sipe_contact_allow_deny (job->sip, job->who, TRUE);
967 g_free(job);
970 static
971 void sipe_deny_user_cb(void * data)
973 struct sipe_auth_job * job = (struct sipe_auth_job *) data;
974 if (!job) return;
976 sipe_contact_allow_deny (job->sip, job->who, FALSE);
977 g_free(job);
980 static void
981 sipe_add_permit(PurpleConnection *gc, const char *name)
983 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
984 sipe_contact_allow_deny(sip, name, TRUE);
987 static void
988 sipe_add_deny(PurpleConnection *gc, const char *name)
990 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
991 sipe_contact_allow_deny(sip, name, FALSE);
994 /*static void
995 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
997 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
998 sipe_contact_set_acl(sip, name, "");
1001 static void
1002 sipe_process_presence_wpending (struct sipe_account_data *sip, struct sipmsg * msg)
1004 xmlnode *watchers;
1005 xmlnode *watcher;
1006 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1007 if (msg->response != 0 && msg->response != 200) return;
1009 if (msg->bodylen == 0 || msg->body == NULL || !strcmp(sipmsg_find_header(msg, "Event"), "msrtc.wpending")) return;
1011 watchers = xmlnode_from_str(msg->body, msg->bodylen);
1012 if (!watchers) return;
1014 for (watcher = xmlnode_get_child(watchers, "watcher"); watcher; watcher = xmlnode_get_next_twin(watcher)) {
1015 gchar * remote_user = g_strdup(xmlnode_get_attrib(watcher, "uri"));
1016 gchar * alias = g_strdup(xmlnode_get_attrib(watcher, "displayName"));
1017 gboolean on_list = g_hash_table_lookup(sip->buddies, remote_user) != NULL;
1019 // TODO pull out optional displayName to pass as alias
1020 if (remote_user) {
1021 struct sipe_auth_job * job = g_new0(struct sipe_auth_job, 1);
1022 job->who = remote_user;
1023 job->sip = sip;
1024 purple_account_request_authorization(
1025 sip->account,
1026 remote_user,
1027 _("you"), /* id */
1028 alias,
1029 NULL, /* message */
1030 on_list,
1031 sipe_auth_user_cb,
1032 sipe_deny_user_cb,
1033 (void *) job);
1038 xmlnode_free(watchers);
1039 return;
1042 static void
1043 sipe_group_add (struct sipe_account_data *sip, struct sipe_group * group)
1045 PurpleGroup * purple_group = purple_find_group(group->name);
1046 if (!purple_group) {
1047 purple_group = purple_group_new(group->name);
1048 purple_blist_add_group(purple_group, NULL);
1051 if (purple_group) {
1052 group->purple_group = purple_group;
1053 sip->groups = g_slist_append(sip->groups, group);
1054 purple_debug_info("sipe", "added group %s (id %d)\n", group->name, group->id);
1055 } else {
1056 purple_debug_info("sipe", "did not add group %s\n", group->name ? group->name : "");
1060 static struct sipe_group * sipe_group_find_by_id (struct sipe_account_data *sip, int id)
1062 struct sipe_group *group;
1063 GSList *entry;
1064 if (sip == NULL) {
1065 return NULL;
1068 entry = sip->groups;
1069 while (entry) {
1070 group = entry->data;
1071 if (group->id == id) {
1072 return group;
1074 entry = entry->next;
1076 return NULL;
1079 static struct sipe_group * sipe_group_find_by_name (struct sipe_account_data *sip, const gchar * name)
1081 struct sipe_group *group;
1082 GSList *entry;
1083 if (sip == NULL) {
1084 return NULL;
1087 entry = sip->groups;
1088 while (entry) {
1089 group = entry->data;
1090 if (!strcmp(group->name, name)) {
1091 return group;
1093 entry = entry->next;
1095 return NULL;
1098 static void
1099 sipe_group_rename (struct sipe_account_data *sip, struct sipe_group * group, gchar * name)
1101 gchar *body;
1102 purple_debug_info("sipe", "Renaming group %s to %s\n", group->name, name);
1103 body = g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP, group->id, name, sip->contacts_delta++);
1104 send_soap_request(sip, body);
1105 g_free(body);
1106 g_free(group->name);
1107 group->name = g_strdup(name);
1111 * Only appends if no such value already stored.
1112 * Like Set in Java.
1114 GSList * slist_insert_unique_sorted(GSList *list, gpointer data, GCompareFunc func) {
1115 GSList * res = list;
1116 if (!g_slist_find_custom(list, data, func)) {
1117 res = g_slist_insert_sorted(list, data, func);
1119 return res;
1122 static int
1123 sipe_group_compare(struct sipe_group *group1, struct sipe_group *group2) {
1124 return group1->id - group2->id;
1128 * Returns string like "2 4 7 8" - group ids buddy belong to.
1130 static gchar *
1131 sipe_get_buddy_groups_string (struct sipe_buddy *buddy) {
1132 int i = 0;
1133 gchar *res;
1134 //creating array from GList, converting int to gchar*
1135 gchar **ids_arr = g_new(gchar *, g_slist_length(buddy->groups) + 1);
1136 GSList *entry = buddy->groups;
1137 while (entry) {
1138 struct sipe_group * group = entry->data;
1139 ids_arr[i] = g_strdup_printf("%d", group->id);
1140 entry = entry->next;
1141 i++;
1143 ids_arr[i] = NULL;
1144 res = g_strjoinv(" ", ids_arr);
1145 g_strfreev(ids_arr);
1146 return res;
1150 * Sends buddy update to server
1152 static void
1153 sipe_group_set_user (struct sipe_account_data *sip, const gchar * who)
1155 struct sipe_buddy *buddy = g_hash_table_lookup(sip->buddies, who);
1156 PurpleBuddy *purple_buddy = purple_find_buddy (sip->account, who);
1158 if (buddy && purple_buddy) {
1159 gchar *alias = (gchar *)purple_buddy_get_alias(purple_buddy);
1160 gchar *body;
1161 gchar *groups = sipe_get_buddy_groups_string(buddy);
1162 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who, alias, groups);
1164 body = g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT,
1165 alias, groups, "true", buddy->name, sip->contacts_delta++
1167 send_soap_request(sip, body);
1168 g_free(groups);
1169 g_free(body);
1173 static gboolean process_add_group_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1175 if (msg->response == 200) {
1176 struct sipe_group *group;
1177 struct group_user_context *ctx = (struct group_user_context*)tc->payload;
1178 xmlnode *xml;
1179 xmlnode *node;
1180 char *group_id;
1181 struct sipe_buddy *buddy;
1183 xml = xmlnode_from_str(msg->body, msg->bodylen);
1184 if (!xml) {
1185 g_free(ctx);
1186 return FALSE;
1189 node = xmlnode_get_descendant(xml, "Body", "addGroup", "groupID", NULL);
1190 if (!node) {
1191 g_free(ctx);
1192 xmlnode_free(xml);
1193 return FALSE;
1196 group_id = xmlnode_get_data(node);
1197 if (!group_id) {
1198 g_free(ctx);
1199 xmlnode_free(xml);
1200 return FALSE;
1203 group = g_new0(struct sipe_group, 1);
1204 group->id = (int)g_ascii_strtod(group_id, NULL);
1205 g_free(group_id);
1206 group->name = ctx->group_name;
1208 sipe_group_add(sip, group);
1210 buddy = g_hash_table_lookup(sip->buddies, ctx->user_name);
1211 if (buddy) {
1212 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1215 sipe_group_set_user(sip, ctx->user_name);
1217 g_free(ctx);
1218 xmlnode_free(xml);
1219 return TRUE;
1221 return FALSE;
1224 static void sipe_group_create (struct sipe_account_data *sip, gchar *name, gchar * who)
1226 struct group_user_context * ctx = g_new0(struct group_user_context, 1);
1227 gchar *body;
1228 ctx->group_name = g_strdup(name);
1229 ctx->user_name = g_strdup(who);
1231 body = g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP, name, sip->contacts_delta++);
1232 send_soap_request_with_cb(sip, body, process_add_group_response, ctx);
1233 g_free(body);
1237 * Data structure for scheduled actions
1239 typedef void (*Action) (struct sipe_account_data *, void *);
1241 struct scheduled_action {
1243 * Name of action.
1244 * Format is <Event>[<Data>...]
1245 * Example: <presence><sip:user@domain.com> or <registration>
1247 gchar *name;
1248 guint timeout_handler;
1249 gboolean repetitive;
1250 Action action;
1251 GDestroyNotify destroy;
1252 struct sipe_account_data *sip;
1253 void *payload;
1257 * A timer callback
1258 * Should return FALSE if repetitive action is not needed
1260 static gboolean sipe_scheduled_exec(struct scheduled_action *sched_action)
1262 gboolean ret;
1263 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1264 sched_action->sip->timeouts = g_slist_remove(sched_action->sip->timeouts, sched_action);
1265 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action->sip->timeouts));
1266 (sched_action->action)(sched_action->sip, sched_action->payload);
1267 ret = sched_action->repetitive;
1268 if (sched_action->destroy) {
1269 (*sched_action->destroy)(sched_action->payload);
1271 g_free(sched_action->name);
1272 g_free(sched_action);
1273 return ret;
1277 * Kills action timer effectively cancelling
1278 * scheduled action
1280 * @param name of action
1282 static void sipe_cancel_scheduled_action(struct sipe_account_data *sip, const gchar *name)
1284 GSList *entry;
1286 if (!sip->timeouts || !name) return;
1288 entry = sip->timeouts;
1289 while (entry) {
1290 struct scheduled_action *sched_action = entry->data;
1291 if(!strcmp(sched_action->name, name)) {
1292 GSList *to_delete = entry;
1293 entry = entry->next;
1294 sip->timeouts = g_slist_delete_link(sip->timeouts, to_delete);
1295 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
1296 purple_timeout_remove(sched_action->timeout_handler);
1297 if (sched_action->destroy) {
1298 (*sched_action->destroy)(sched_action->payload);
1300 g_free(sched_action->name);
1301 g_free(sched_action);
1302 } else {
1303 entry = entry->next;
1308 static void
1309 sipe_schedule_action0(const gchar *name,
1310 int timeout,
1311 gboolean isSeconds,
1312 Action action,
1313 GDestroyNotify destroy,
1314 struct sipe_account_data *sip,
1315 void *payload)
1317 struct scheduled_action *sched_action;
1319 /* Make sure each action only exists once */
1320 sipe_cancel_scheduled_action(sip, name);
1322 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name, timeout, isSeconds ? "sec" : "msec");
1323 sched_action = g_new0(struct scheduled_action, 1);
1324 sched_action->repetitive = FALSE;
1325 sched_action->name = g_strdup(name);
1326 sched_action->action = action;
1327 sched_action->destroy = destroy;
1328 sched_action->sip = sip;
1329 sched_action->payload = payload;
1330 sched_action->timeout_handler = isSeconds ? purple_timeout_add_seconds(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action) :
1331 purple_timeout_add(timeout, (GSourceFunc) sipe_scheduled_exec, sched_action);
1332 sip->timeouts = g_slist_append(sip->timeouts, sched_action);
1333 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip->timeouts));
1337 * Do schedule action for execution in the future.
1338 * Non repetitive execution.
1340 * @param name of action (will be copied)
1341 * @param timeout in seconds
1342 * @action callback function
1343 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1345 static void
1346 sipe_schedule_action(const gchar *name,
1347 int timeout,
1348 Action action,
1349 GDestroyNotify destroy,
1350 struct sipe_account_data *sip,
1351 void *payload)
1353 sipe_schedule_action0(name, timeout, TRUE, action, destroy, sip, payload);
1357 * Same as sipe_schedule_action() but timeout is in milliseconds.
1359 static void
1360 sipe_schedule_action_msec(const gchar *name,
1361 int timeout,
1362 Action action,
1363 GDestroyNotify destroy,
1364 struct sipe_account_data *sip,
1365 void *payload)
1367 sipe_schedule_action0(name, timeout, FALSE, action, destroy, sip, payload);
1371 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify);
1373 gboolean process_subscribe_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1375 if (sipmsg_find_header(msg, "ms-piggyback-cseq"))
1377 process_incoming_notify(sip, msg, FALSE, FALSE);
1379 return TRUE;
1382 static void sipe_subscribe_resource_uri(const char *name, gpointer value, gchar **resources_uri)
1384 gchar *tmp = *resources_uri;
1385 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, name);
1386 g_free(tmp);
1389 static void sipe_subscribe_resource_uri_with_context(const char *name, gpointer value, gchar **resources_uri)
1391 struct sipe_buddy *sbuddy = (struct sipe_buddy *)value;
1392 if (sbuddy && !sbuddy->resubscribed) { // Only not resubscribed contacts; the first time everybody are included
1393 gchar *tmp = *resources_uri;
1394 *resources_uri = g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp, name);
1395 g_free(tmp);
1400 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1401 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1402 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1403 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1404 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1407 static void sipe_subscribe_presence_batched_to(struct sipe_account_data *sip, gchar *resources_uri, gchar *to)
1409 gchar *contact = get_contact(sip);
1410 gchar *request;
1411 gchar *content;
1412 gchar *require = "";
1413 gchar *accept = "";
1414 gchar *autoextend = "";
1415 gchar *content_type;
1417 if (sip->msrtc_event_categories) {
1418 require = ", categoryList";
1419 accept = ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1420 content_type = "application/msrtc-adrl-categorylist+xml";
1421 content = g_strdup_printf(
1422 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1423 "<action name=\"subscribe\" id=\"63792024\">\n"
1424 "<adhocList>\n%s</adhocList>\n"
1425 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1426 "<category name=\"note\"/>\n"
1427 "<category name=\"state\"/>\n"
1428 "</categoryList>\n"
1429 "</action>\n"
1430 "</batchSub>", sip->username, resources_uri);
1431 } else {
1432 autoextend = "Supported: com.microsoft.autoextend\r\n";
1433 content_type = "application/adrl+xml";
1434 content = g_strdup_printf(
1435 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1436 "<create xmlns=\"\">\n%s</create>\n"
1437 "</adhoclist>\n", sip->username, sip->username, resources_uri);
1439 g_free(resources_uri);
1441 request = g_strdup_printf(
1442 "Require: adhoclist%s\r\n"
1443 "Supported: eventlist\r\n"
1444 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1445 "Supported: ms-piggyback-first-notify\r\n"
1446 "%sSupported: ms-benotify\r\n"
1447 "Proxy-Require: ms-benotify\r\n"
1448 "Event: presence\r\n"
1449 "Content-Type: %s\r\n"
1450 "Contact: %s\r\n", require, accept, autoextend, content_type, contact);
1451 g_free(contact);
1453 /* subscribe to buddy presence */
1454 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1455 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1457 g_free(content);
1458 g_free(to);
1459 g_free(request);
1462 static void sipe_subscribe_presence_batched(struct sipe_account_data *sip, void *unused)
1464 gchar *to = sip_uri_self(sip);
1465 gchar *resources_uri = g_strdup("");
1466 if (sip->msrtc_event_categories) {
1467 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri_with_context , &resources_uri);
1468 } else {
1469 g_hash_table_foreach(sip->buddies, (GHFunc) sipe_subscribe_resource_uri, &resources_uri);
1471 sipe_subscribe_presence_batched_to(sip, resources_uri, to);
1474 struct presence_batched_routed {
1475 gchar *host;
1476 GSList *buddies;
1479 static void sipe_subscribe_presence_batched_routed_free(void *payload)
1481 struct presence_batched_routed *data = payload;
1482 GSList *buddies = data->buddies;
1483 while (buddies) {
1484 g_free(buddies->data);
1485 buddies = buddies->next;
1487 g_slist_free(data->buddies);
1488 g_free(data->host);
1489 g_free(payload);
1492 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data *sip, void *payload)
1494 struct presence_batched_routed *data = payload;
1495 GSList *buddies = data->buddies;
1496 gchar *resources_uri = g_strdup("");
1497 while (buddies) {
1498 gchar *tmp = resources_uri;
1499 resources_uri = g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp, (char *) buddies->data);
1500 g_free(tmp);
1501 buddies = buddies->next;
1503 sipe_subscribe_presence_batched_to(sip, resources_uri,
1504 g_strdup(data->host));
1508 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1509 * The user sends a single SUBSCRIBE request to the subscribed contact.
1510 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1514 static void sipe_subscribe_presence_single(struct sipe_account_data *sip, void *buddy_name)
1516 gchar *to = sip_uri((char *)buddy_name);
1517 gchar *tmp = get_contact(sip);
1518 gchar *request;
1519 gchar *content;
1520 gchar *autoextend = "";
1522 if (!sip->msrtc_event_categories)
1523 autoextend = "Supported: com.microsoft.autoextend\r\n";
1525 request = g_strdup_printf(
1526 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1527 "Supported: ms-piggyback-first-notify\r\n"
1528 "%sSupported: ms-benotify\r\n"
1529 "Proxy-Require: ms-benotify\r\n"
1530 "Event: presence\r\n"
1531 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1532 "Contact: %s\r\n", autoextend,tmp);
1534 content = g_strdup_printf(
1535 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1536 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1537 "<resource uri=\"%s\"/>\n"
1538 "</adhocList>\n"
1539 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1540 "<category name=\"note\"/>\n"
1541 "<category name=\"state\"/>\n"
1542 "</categoryList>\n"
1543 "</action>\n"
1544 "</batchSub>", sip->username, to
1547 g_free(tmp);
1549 /* subscribe to buddy presence */
1550 send_sip_request(sip->gc, "SUBSCRIBE", to, to, request, content, NULL, process_subscribe_response);
1552 g_free(content);
1553 g_free(to);
1554 g_free(request);
1557 static void sipe_set_status(PurpleAccount *account, PurpleStatus *status)
1559 if (!purple_status_is_active(status))
1560 return;
1562 if (account->gc) {
1563 struct sipe_account_data *sip = account->gc->proto_data;
1565 if (sip) {
1566 g_free(sip->status);
1567 sip->status = g_strdup(purple_status_get_id(status));
1568 send_presence_status(sip);
1573 static void
1574 sipe_alias_buddy(PurpleConnection *gc, const char *name, const char *alias)
1576 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1577 sipe_group_set_user(sip, name);
1580 static void
1581 sipe_group_buddy(PurpleConnection *gc,
1582 const char *who,
1583 const char *old_group_name,
1584 const char *new_group_name)
1586 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1587 struct sipe_buddy * buddy = g_hash_table_lookup(sip->buddies, who);
1588 struct sipe_group * old_group = NULL;
1589 struct sipe_group * new_group;
1591 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1592 who ? who : "", old_group_name ? old_group_name : "", new_group_name ? new_group_name : "");
1594 if(!buddy) { // buddy not in roaming list
1595 return;
1598 if (old_group_name) {
1599 old_group = sipe_group_find_by_name(sip, g_strdup(old_group_name));
1601 new_group = sipe_group_find_by_name(sip, g_strdup(new_group_name));
1603 if (old_group) {
1604 buddy->groups = g_slist_remove(buddy->groups, old_group);
1605 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who, old_group_name);
1608 if (!new_group) {
1609 sipe_group_create(sip, g_strdup(new_group_name), g_strdup(who));
1610 } else {
1611 buddy->groups = slist_insert_unique_sorted(buddy->groups, new_group, (GCompareFunc)sipe_group_compare);
1612 sipe_group_set_user(sip, who);
1616 static void sipe_add_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1618 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1619 struct sipe_buddy *b;
1621 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1623 // Prepend sip: if needed
1624 if (strncmp("sip:", buddy->name, 4)) {
1625 gchar *buf = sip_uri_from_name(buddy->name);
1626 purple_blist_rename_buddy(buddy, buf);
1627 g_free(buf);
1630 if (!g_hash_table_lookup(sip->buddies, buddy->name)) {
1631 b = g_new0(struct sipe_buddy, 1);
1632 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy->name);
1633 b->name = g_strdup(buddy->name);
1634 g_hash_table_insert(sip->buddies, b->name, b);
1635 sipe_group_buddy(gc, b->name, NULL, group->name);
1636 sipe_subscribe_presence_single(sip, b->name); //@TODO should go to callback
1637 } else {
1638 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy->name);
1642 static void sipe_free_buddy(struct sipe_buddy *buddy)
1644 #ifndef _WIN32
1646 * We are calling g_hash_table_foreach_steal(). That means that no
1647 * key/value deallocation functions are called. Therefore the glib
1648 * hash code does not touch the key (buddy->name) or value (buddy)
1649 * of the to-be-deleted hash node at all. It follows that we
1651 * - MUST free the memory for the key ourselves and
1652 * - ARE allowed to do it in this function
1654 * Conclusion: glib must be broken on the Windows platform if sipe
1655 * crashes with SIGTRAP when closing. You'll have to live
1656 * with the memory leak until this is fixed.
1658 g_free(buddy->name);
1659 #endif
1660 g_free(buddy->annotation);
1661 g_free(buddy->device_name);
1662 g_slist_free(buddy->groups);
1663 g_free(buddy);
1667 * Unassociates buddy from group first.
1668 * Then see if no groups left, removes buddy completely.
1669 * Otherwise updates buddy groups on server.
1671 static void sipe_remove_buddy(PurpleConnection *gc, PurpleBuddy *buddy, PurpleGroup *group)
1673 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1674 struct sipe_buddy *b = g_hash_table_lookup(sip->buddies, buddy->name);
1675 struct sipe_group *g = NULL;
1677 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy ? buddy->name : "", group ? group->name : "");
1679 if (!b) return;
1681 if (group) {
1682 g = sipe_group_find_by_name(sip, group->name);
1685 if (g) {
1686 b->groups = g_slist_remove(b->groups, g);
1687 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy->name, g->name);
1690 if (g_slist_length(b->groups) < 1) {
1691 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1692 sipe_cancel_scheduled_action(sip, action_name);
1693 g_free(action_name);
1695 g_hash_table_remove(sip->buddies, buddy->name);
1697 if (b->name) {
1698 gchar * body = g_strdup_printf(SIPE_SOAP_DEL_CONTACT, b->name, sip->contacts_delta++);
1699 send_soap_request(sip, body);
1700 g_free(body);
1703 sipe_free_buddy(b);
1704 } else {
1705 //updates groups on server
1706 sipe_group_set_user(sip, b->name);
1711 static void
1712 sipe_rename_group(PurpleConnection *gc,
1713 const char *old_name,
1714 PurpleGroup *group,
1715 GList *moved_buddies)
1717 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1718 struct sipe_group * s_group = sipe_group_find_by_name(sip, g_strdup(old_name));
1719 if (group) {
1720 sipe_group_rename(sip, s_group, group->name);
1721 } else {
1722 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name);
1726 static void
1727 sipe_remove_group(PurpleConnection *gc, PurpleGroup *group)
1729 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1730 struct sipe_group * s_group = sipe_group_find_by_name(sip, group->name);
1731 if (s_group) {
1732 gchar *body;
1733 purple_debug_info("sipe", "Deleting group %s\n", group->name);
1734 body = g_strdup_printf(SIPE_SOAP_DEL_GROUP, s_group->id, sip->contacts_delta++);
1735 send_soap_request(sip, body);
1736 g_free(body);
1738 sip->groups = g_slist_remove(sip->groups, s_group);
1739 g_free(s_group->name);
1740 g_free(s_group);
1741 } else {
1742 purple_debug_info("sipe", "Cannot find group %s to delete\n", group->name);
1746 static GList *sipe_status_types(PurpleAccount *acc)
1748 PurpleStatusType *type;
1749 GList *types = NULL;
1751 // Online
1752 type = purple_status_type_new_with_attrs(
1753 PURPLE_STATUS_AVAILABLE, NULL, _("Online"), TRUE, TRUE, FALSE,
1754 // Translators: noun
1755 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1756 NULL);
1757 types = g_list_append(types, type);
1759 // Busy
1760 type = purple_status_type_new_with_attrs(
1761 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_BUSY, _("Busy"), TRUE, TRUE, FALSE,
1762 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1763 NULL);
1764 types = g_list_append(types, type);
1766 // Do Not Disturb (not user settable)
1767 type = purple_status_type_new_with_attrs(
1768 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_DND, _("Do Not Disturb"), TRUE, FALSE, FALSE,
1769 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1770 NULL);
1771 types = g_list_append(types, type);
1773 // Be Right Back
1774 type = purple_status_type_new_with_attrs(
1775 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_BRB, _("Be Right Back"), TRUE, TRUE, FALSE,
1776 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1777 NULL);
1778 types = g_list_append(types, type);
1780 // Away
1781 type = purple_status_type_new_with_attrs(
1782 PURPLE_STATUS_AWAY, NULL, NULL, TRUE, TRUE, FALSE,
1783 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1784 NULL);
1785 types = g_list_append(types, type);
1787 //On The Phone
1788 type = purple_status_type_new_with_attrs(
1789 PURPLE_STATUS_UNAVAILABLE, SIPE_STATUS_ID_ONPHONE, _("On The Phone"), TRUE, TRUE, FALSE,
1790 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1791 NULL);
1792 types = g_list_append(types, type);
1794 //Out To Lunch
1795 type = purple_status_type_new_with_attrs(
1796 PURPLE_STATUS_AWAY, SIPE_STATUS_ID_LUNCH, _("Out To Lunch"), TRUE, TRUE, FALSE,
1797 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING),
1798 NULL);
1799 types = g_list_append(types, type);
1801 //Appear Offline
1802 type = purple_status_type_new_full(
1803 PURPLE_STATUS_INVISIBLE, NULL, _("Appear Offline"), TRUE, TRUE, FALSE);
1804 types = g_list_append(types, type);
1806 // Offline
1807 type = purple_status_type_new_full(
1808 PURPLE_STATUS_OFFLINE, NULL, NULL, TRUE, TRUE, FALSE);
1809 types = g_list_append(types, type);
1811 return types;
1815 * A callback for g_hash_table_foreach
1817 static void sipe_buddy_subscribe_cb(char *name, struct sipe_buddy *buddy, struct sipe_account_data *sip)
1819 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, buddy->name);
1820 int time_range = (g_hash_table_size(sip->buddies) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1821 int timeout = (time_range * rand()) / RAND_MAX; /* random period within the range */
1822 sipe_schedule_action_msec(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, g_strdup(buddy->name));
1826 * Removes entries from purple buddy list
1827 * that does not correspond ones in the roaming contact list.
1829 static void sipe_cleanup_local_blist(struct sipe_account_data *sip) {
1830 GSList *buddies = purple_find_buddies(sip->account, NULL);
1831 GSList *entry = buddies;
1832 struct sipe_buddy *buddy;
1833 PurpleBuddy *b;
1834 PurpleGroup *g;
1836 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies));
1837 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip->buddies));
1838 while (entry) {
1839 b = entry->data;
1840 g = purple_buddy_get_group(b);
1841 buddy = g_hash_table_lookup(sip->buddies, b->name);
1842 if(buddy) {
1843 gboolean in_sipe_groups = FALSE;
1844 GSList *entry2 = buddy->groups;
1845 while (entry2) {
1846 struct sipe_group *group = entry2->data;
1847 if (!strcmp(group->name, g->name)) {
1848 in_sipe_groups = TRUE;
1849 break;
1851 entry2 = entry2->next;
1853 if(!in_sipe_groups) {
1854 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b->name, g->name);
1855 purple_blist_remove_buddy(b);
1857 } else {
1858 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b->name, g->name);
1859 purple_blist_remove_buddy(b);
1861 entry = entry->next;
1865 static gboolean sipe_process_roaming_contacts(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
1867 int len = msg->bodylen;
1869 gchar *tmp = sipmsg_find_header(msg, "Event");
1870 xmlnode *item;
1871 xmlnode *isc;
1872 const gchar *contacts_delta;
1873 xmlnode *group_node;
1874 if (!tmp || strncmp(tmp, "vnd-microsoft-roaming-contacts", 30)) {
1875 return FALSE;
1878 /* Convert the contact from XML to Purple Buddies */
1879 isc = xmlnode_from_str(msg->body, len);
1880 if (!isc) {
1881 return FALSE;
1884 contacts_delta = xmlnode_get_attrib(isc, "deltaNum");
1885 if (contacts_delta) {
1886 sip->contacts_delta = (int)g_ascii_strtod(contacts_delta, NULL);
1889 if (!strcmp(isc->name, "contactList")) {
1891 /* Parse groups */
1892 for (group_node = xmlnode_get_child(isc, "group"); group_node; group_node = xmlnode_get_next_twin(group_node)) {
1893 struct sipe_group * group = g_new0(struct sipe_group, 1);
1894 const char *name = xmlnode_get_attrib(group_node, "name");
1896 if (!strncmp(name, "~", 1)) {
1897 name = _("Other Contacts");
1899 group->name = g_strdup(name);
1900 group->id = (int)g_ascii_strtod(xmlnode_get_attrib(group_node, "id"), NULL);
1902 sipe_group_add(sip, group);
1905 // Make sure we have at least one group
1906 if (g_slist_length(sip->groups) == 0) {
1907 struct sipe_group * group = g_new0(struct sipe_group, 1);
1908 PurpleGroup *purple_group;
1909 group->name = g_strdup(_("Other Contacts"));
1910 group->id = 1;
1911 purple_group = purple_group_new(group->name);
1912 purple_blist_add_group(purple_group, NULL);
1913 sip->groups = g_slist_append(sip->groups, group);
1916 /* Parse contacts */
1917 for (item = xmlnode_get_child(isc, "contact"); item; item = xmlnode_get_next_twin(item)) {
1918 gchar * uri = g_strdup(xmlnode_get_attrib(item, "uri"));
1919 gchar * name = g_strdup(xmlnode_get_attrib(item, "name"));
1920 gchar * groups = g_strdup(xmlnode_get_attrib(item, "groups"));
1921 gchar * buddy_name = sip_uri_from_name(uri);
1922 gchar **item_groups;
1923 struct sipe_group *group = NULL;
1924 struct sipe_buddy *buddy = NULL;
1925 int i = 0;
1927 // assign to group Other Contacts if nothing else received
1928 if(!groups || !strcmp("", groups) ) {
1929 group = sipe_group_find_by_name(sip, _("Other Contacts"));
1930 groups = group ? g_strdup_printf("%d", group->id) : "1";
1933 item_groups = g_strsplit(groups, " ", 0);
1935 while (item_groups[i]) {
1936 group = sipe_group_find_by_id(sip, g_ascii_strtod(item_groups[i], NULL));
1938 // If couldn't find the right group for this contact, just put them in the first group we have
1939 if (group == NULL && g_slist_length(sip->groups) > 0) {
1940 group = sip->groups->data;
1943 if (group != NULL) {
1944 PurpleBuddy *b = purple_find_buddy_in_group(sip->account, buddy_name, group->purple_group);
1945 if (!b){
1946 b = purple_buddy_new(sip->account, buddy_name, uri);
1947 purple_blist_add_buddy(b, NULL, group->purple_group, NULL);
1950 if (!g_ascii_strcasecmp(uri, purple_buddy_get_alias(b))) {
1951 if (name != NULL && strlen(name) != 0) {
1952 purple_blist_alias_buddy(b, name);
1956 if (!buddy) {
1957 buddy = g_new0(struct sipe_buddy, 1);
1958 buddy->name = g_strdup(b->name);
1959 g_hash_table_insert(sip->buddies, buddy->name, buddy);
1962 buddy->groups = slist_insert_unique_sorted(buddy->groups, group, (GCompareFunc)sipe_group_compare);
1964 purple_debug_info("sipe", "Added buddy %s to group %s\n", b->name, group->name);
1965 } else {
1966 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1967 name);
1970 i++;
1971 } // while, contact groups
1972 g_strfreev(item_groups);
1973 g_free(groups);
1974 g_free(name);
1975 g_free(buddy_name);
1976 g_free(uri);
1978 } // for, contacts
1980 sipe_cleanup_local_blist(sip);
1982 xmlnode_free(isc);
1984 //subscribe to buddies
1985 if (!sip->subscribed_buddies) { //do it once, then count Expire field to schedule resubscribe.
1986 if(sip->batched_support){
1987 sipe_subscribe_presence_batched(sip, NULL);
1989 else{
1990 g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
1992 sip->subscribed_buddies = TRUE;
1995 return 0;
1999 * Subscribe roaming contacts
2001 static void sipe_subscribe_roaming_contacts(struct sipe_account_data *sip,struct sipmsg *msg)
2003 gchar *to = sip_uri_self(sip);
2004 gchar *tmp = get_contact(sip);
2005 gchar *hdr = g_strdup_printf(
2006 "Event: vnd-microsoft-roaming-contacts\r\n"
2007 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2008 "Supported: com.microsoft.autoextend\r\n"
2009 "Supported: ms-benotify\r\n"
2010 "Proxy-Require: ms-benotify\r\n"
2011 "Supported: ms-piggyback-first-notify\r\n"
2012 "Contact: %s\r\n", tmp);
2013 g_free(tmp);
2015 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2016 g_free(to);
2017 g_free(hdr);
2020 static void sipe_subscribe_presence_wpending(struct sipe_account_data *sip, void *unused)
2022 gchar *to = sip_uri_self(sip);
2023 gchar *tmp = get_contact(sip);
2024 gchar *hdr = g_strdup_printf(
2025 "Event: presence.wpending\r\n"
2026 "Accept: text/xml+msrtc.wpending\r\n"
2027 "Supported: com.microsoft.autoextend\r\n"
2028 "Supported: ms-benotify\r\n"
2029 "Proxy-Require: ms-benotify\r\n"
2030 "Supported: ms-piggyback-first-notify\r\n"
2031 "Contact: %s\r\n", tmp);
2032 g_free(tmp);
2034 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2035 g_free(to);
2036 g_free(hdr);
2040 * Fires on deregistration event initiated by server.
2041 * [MS-SIPREGE] SIP extension.
2044 // 2007 Example
2046 // Content-Type: text/registration-event
2047 // subscription-state: terminated;expires=0
2048 // ms-diagnostics-public: 4141;reason="User disabled"
2050 // deregistered;event=rejected
2052 static void sipe_process_registration_notify(struct sipe_account_data *sip, struct sipmsg *msg)
2054 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2055 gchar *event = NULL;
2056 gchar *reason = NULL;
2057 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
2059 warning = warning ? warning : sipmsg_find_header(msg, "ms-diagnostics-public");
2060 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2062 if (!g_ascii_strncasecmp(contenttype, "text/registration-event", 23)) {
2063 event = sipmsg_find_part_of_header(msg->body, "event=", NULL, NULL);
2064 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2065 event = event ? event : sipmsg_find_part_of_header(msg->body, "event=", ";", NULL);
2066 } else {
2067 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2068 return;
2071 if (warning != NULL) {
2072 reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
2073 } else { // for LCS2005
2074 int error_id = 0;
2075 if (event && !g_ascii_strcasecmp(event, "unregistered")) {
2076 error_id = 4140; // [MS-SIPREGE]
2077 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2078 reason = g_strdup(_("You have been signed off because you've signed in at another location"));
2079 } else if (event && !g_ascii_strcasecmp(event, "rejected")) {
2080 error_id = 4141;
2081 reason = g_strdup(_("User disabled")); // [MS-OCER]
2082 } else if (event && !g_ascii_strcasecmp(event, "deactivated")) {
2083 error_id = 4142;
2084 reason = g_strdup(_("User moved")); // [MS-OCER]
2087 g_free(event);
2088 warning = g_strdup_printf(_("Unregistered by Server: %s."), reason ? reason : _("no reason given"));
2089 g_free(reason);
2091 sip->gc->wants_to_die = TRUE;
2092 purple_connection_error(sip->gc, warning);
2093 g_free(warning);
2097 static void sipe_process_provisioning_v2(struct sipe_account_data *sip, struct sipmsg *msg)
2099 xmlnode *xn_provision_group_list;
2100 xmlnode *node;
2102 xn_provision_group_list = xmlnode_from_str(msg->body, msg->bodylen);
2104 /* provisionGroup */
2105 for (node = xmlnode_get_child(xn_provision_group_list, "provisionGroup"); node; node = xmlnode_get_next_twin(node)) {
2106 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node, "name"))) {
2107 g_free(sip->focus_factory_uri);
2108 sip->focus_factory_uri = xmlnode_get_data(xmlnode_get_child(node, "focusFactoryUri"));
2109 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2110 sip->focus_factory_uri ? sip->focus_factory_uri : "");
2111 break;
2114 xmlnode_free(xn_provision_group_list);
2117 static void sipe_process_roaming_acl(struct sipe_account_data *sip, struct sipmsg *msg)
2119 const gchar *contacts_delta;
2120 xmlnode *xml;
2122 xml = xmlnode_from_str(msg->body, msg->bodylen);
2123 if (!xml)
2125 return;
2128 contacts_delta = xmlnode_get_attrib(xml, "deltaNum");
2129 if (contacts_delta)
2131 sip->acl_delta = (int)g_ascii_strtod(contacts_delta, NULL);
2134 xmlnode_free(xml);
2137 static void
2138 free_container(struct sipe_container *container)
2140 GSList *entry;
2142 if (!container) return;
2144 entry = container->members;
2145 while (entry) {
2146 g_free(entry->data);
2147 entry = g_slist_remove(entry, entry->data);
2149 g_free(container);
2153 * Finds locally stored MS-PRES container member
2155 static struct sipe_container_member *
2156 sipe_find_container_member(struct sipe_container *container,
2157 const gchar *type,
2158 const gchar *value)
2160 struct sipe_container_member *member;
2161 GSList *entry;
2163 if (container == NULL || type == NULL) {
2164 return NULL;
2167 entry = container->members;
2168 while (entry) {
2169 member = entry->data;
2170 if (!g_strcasecmp(member->type, type)
2171 && ((!member->value && !value)
2172 || (value && member->value && !g_strcasecmp(member->value, value)))
2174 return member;
2176 entry = entry->next;
2178 return NULL;
2182 * Finds locally stored MS-PRES container by id
2184 static struct sipe_container *
2185 sipe_find_container(struct sipe_account_data *sip,
2186 guint id)
2188 struct sipe_container *container;
2189 GSList *entry;
2191 if (sip == NULL) {
2192 return NULL;
2195 entry = sip->containers;
2196 while (entry) {
2197 container = entry->data;
2198 if (id == container->id) {
2199 return container;
2201 entry = entry->next;
2203 return NULL;
2207 * Access Levels
2208 * 32000 - Blocked
2209 * 400 - Personal
2210 * 300 - Team
2211 * 200 - Company
2212 * 100 - Public
2214 static int
2215 sipe_find_access_level(struct sipe_account_data *sip,
2216 const gchar *type,
2217 const gchar *value)
2219 guint containers[] = {32000, 400, 300, 200, 100};
2220 int i = 0;
2222 for (i = 0; i < 5; i++) {
2223 struct sipe_container_member *member;
2224 struct sipe_container *container = sipe_find_container(sip, containers[i]);
2225 if (!container) continue;
2227 member = sipe_find_container_member(container, type, value);
2228 if (member) {
2229 return containers[i];
2233 return -1;
2236 static void
2237 sipe_send_set_container_members(struct sipe_account_data *sip,
2238 guint container_id,
2239 guint container_version,
2240 const gchar* action,
2241 const gchar* type,
2242 const gchar* value)
2244 gchar *self = sip_uri_self(sip);
2245 gchar *value_str = value ? g_strdup_printf(" value=\"%s\"", value) : g_strdup("");
2246 gchar *contact;
2247 gchar *hdr;
2248 gchar *body = g_strdup_printf(
2249 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2250 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2251 "</setContainerMembers>",
2252 container_id,
2253 container_version,
2254 action,
2255 type,
2256 value_str);
2257 g_free(value_str);
2259 contact = get_contact(sip);
2260 hdr = g_strdup_printf("Contact: %s\r\n"
2261 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact);
2262 g_free(contact);
2264 send_sip_request(sip->gc, "SERVICE", self, self, hdr, body, NULL, NULL);
2266 g_free(hdr);
2267 g_free(body);
2268 g_free(self);
2273 * When we receive some self (BE) NOTIFY with a new subscriber
2274 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2277 static void sipe_process_roaming_self(struct sipe_account_data *sip, struct sipmsg *msg)
2279 gchar *contact;
2280 gchar *to;
2281 xmlnode *xml;
2282 xmlnode *node;
2283 xmlnode *node2;
2284 char *display_name = NULL;
2285 PurpleBuddy *pbuddy;
2286 const char *alias;
2287 char *uri_alias;
2288 char *uri_user;
2290 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2292 xml = xmlnode_from_str(msg->body, msg->bodylen);
2293 if (!xml) return;
2295 contact = get_contact(sip);
2296 to = sip_uri_self(sip);
2298 /* containers */
2299 for (node = xmlnode_get_descendant(xml, "containers", "container", NULL); node; node = xmlnode_get_next_twin(node)) {
2300 guint id = atoi(xmlnode_get_attrib(node, "id"));
2301 struct sipe_container *container = sipe_find_container(sip, id);
2303 if (container) {
2304 sip->containers = g_slist_remove(sip->containers, container);
2305 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container->id, container->version);
2306 free_container(container);
2308 container = g_new0(struct sipe_container, 1);
2309 container->id = id;
2310 container->version = atoi(xmlnode_get_attrib(node, "version"));
2311 sip->containers = g_slist_append(sip->containers, container);
2312 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container->id, container->version);
2314 for (node2 = xmlnode_get_child(node, "member"); node2; node2 = xmlnode_get_next_twin(node2)) {
2315 struct sipe_container_member *member = g_new0(struct sipe_container_member, 1);
2316 member->type = xmlnode_get_attrib(node2, "type");
2317 member->value = xmlnode_get_attrib(node2, "value");
2318 container->members = g_slist_append(container->members, member);
2319 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
2320 member->type, member->value ? member->value : "");
2324 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip->access_level_set ? "TRUE" : "FALSE");
2325 if (!sip->access_level_set && xmlnode_get_child(xml, "containers")) {
2326 int sameEnterpriseAL = sipe_find_access_level(sip, "sameEnterprise", NULL);
2327 int federatedAL = sipe_find_access_level(sip, "federated", NULL);
2328 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL);
2329 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL);
2330 /* initial set-up to let counterparties see your status */
2331 if (sameEnterpriseAL < 0) {
2332 struct sipe_container *container = sipe_find_container(sip, 200);
2333 guint version = container ? container->version : 0;
2334 sipe_send_set_container_members(sip, 200, version, "add", "sameEnterprise", NULL);
2336 if (federatedAL < 0) {
2337 struct sipe_container *container = sipe_find_container(sip, 100);
2338 guint version = container ? container->version : 0;
2339 sipe_send_set_container_members(sip, 100, version, "add", "federated", NULL);
2341 sip->access_level_set = TRUE;
2344 /* subscribers */
2345 for (node = xmlnode_get_descendant(xml, "subscribers", "subscriber", NULL); node; node = xmlnode_get_next_twin(node)) {
2346 const char *user;
2347 const char *acknowledged;
2348 gchar *hdr;
2349 gchar *body;
2351 user = xmlnode_get_attrib(node, "user");
2352 if (!user) continue;
2353 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user);
2354 display_name = g_strdup(xmlnode_get_attrib(node, "displayName"));
2355 uri_user = sip_uri_from_name(user);
2356 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, uri_user);
2357 if(pbuddy){
2358 alias = purple_buddy_get_local_alias(pbuddy);
2359 uri_alias = sip_uri_from_name(alias);
2360 if (display_name && !g_ascii_strcasecmp(uri_user, uri_alias)) { // 'bad' alias
2361 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user, display_name);
2362 purple_blist_alias_buddy(pbuddy, display_name);
2364 g_free(uri_alias);
2367 acknowledged= xmlnode_get_attrib(node, "acknowledged");
2368 if(!g_ascii_strcasecmp(acknowledged,"false")){
2369 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user);
2370 if (!purple_find_buddy(sip->account, uri_user)) {
2371 purple_account_request_add(sip->account, uri_user, _("you"), display_name, NULL);
2374 hdr = g_strdup_printf(
2375 "Contact: %s\r\n"
2376 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact);
2378 body = g_strdup_printf(
2379 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2380 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2381 "</setSubscribers>", user);
2383 send_sip_request(sip->gc, "SERVICE", to, to, hdr, body, NULL, NULL);
2384 g_free(body);
2385 g_free(hdr);
2387 g_free(display_name);
2388 g_free(uri_user);
2391 g_free(to);
2392 g_free(contact);
2393 xmlnode_free(xml);
2396 static void sipe_subscribe_roaming_acl(struct sipe_account_data *sip,struct sipmsg *msg)
2398 gchar *to = sip_uri_self(sip);
2399 gchar *tmp = get_contact(sip);
2400 gchar *hdr = g_strdup_printf(
2401 "Event: vnd-microsoft-roaming-ACL\r\n"
2402 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2403 "Supported: com.microsoft.autoextend\r\n"
2404 "Supported: ms-benotify\r\n"
2405 "Proxy-Require: ms-benotify\r\n"
2406 "Supported: ms-piggyback-first-notify\r\n"
2407 "Contact: %s\r\n", tmp);
2408 g_free(tmp);
2410 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, "", NULL, process_subscribe_response);
2411 g_free(to);
2412 g_free(hdr);
2416 * To request for presence information about the user, access level settings that have already been configured by the user
2417 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2418 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2421 static void sipe_subscribe_roaming_self(struct sipe_account_data *sip,struct sipmsg *msg)
2423 gchar *to = sip_uri_self(sip);
2424 gchar *tmp = get_contact(sip);
2425 gchar *hdr = g_strdup_printf(
2426 "Event: vnd-microsoft-roaming-self\r\n"
2427 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2428 "Supported: ms-benotify\r\n"
2429 "Proxy-Require: ms-benotify\r\n"
2430 "Supported: ms-piggyback-first-notify\r\n"
2431 "Contact: %s\r\n"
2432 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp);
2434 gchar *body=g_strdup(
2435 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2436 "<roaming type=\"categories\"/>"
2437 "<roaming type=\"containers\"/>"
2438 "<roaming type=\"subscribers\"/></roamingList>");
2440 g_free(tmp);
2441 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2442 g_free(body);
2443 g_free(to);
2444 g_free(hdr);
2448 * For 2005 version
2450 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data *sip,struct sipmsg *msg)
2452 gchar *to = sip_uri_self(sip);
2453 gchar *tmp = get_contact(sip);
2454 gchar *hdr = g_strdup_printf(
2455 "Event: vnd-microsoft-provisioning\r\n"
2456 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2457 "Supported: com.microsoft.autoextend\r\n"
2458 "Supported: ms-benotify\r\n"
2459 "Proxy-Require: ms-benotify\r\n"
2460 "Supported: ms-piggyback-first-notify\r\n"
2461 "Expires: 0\r\n"
2462 "Contact: %s\r\n", tmp);
2464 g_free(tmp);
2465 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, NULL, NULL, process_subscribe_response);
2466 g_free(to);
2467 g_free(hdr);
2470 /** Subscription for provisioning information to help with initial
2471 * configuration. This subscription is a one-time query (denoted by the Expires header,
2472 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2473 * configuration, meeting policies, and policy settings that Communicator must enforce.
2474 * TODO: for what we need this information.
2477 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data *sip,struct sipmsg *msg)
2479 gchar *to = sip_uri_self(sip);
2480 gchar *tmp = get_contact(sip);
2481 gchar *hdr = g_strdup_printf(
2482 "Event: vnd-microsoft-provisioning-v2\r\n"
2483 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2484 "Supported: com.microsoft.autoextend\r\n"
2485 "Supported: ms-benotify\r\n"
2486 "Proxy-Require: ms-benotify\r\n"
2487 "Supported: ms-piggyback-first-notify\r\n"
2488 "Expires: 0\r\n"
2489 "Contact: %s\r\n"
2490 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp);
2491 gchar *body = g_strdup(
2492 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2493 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2494 "<provisioningGroup name=\"ucPolicy\"/>"
2495 "</provisioningGroupList>");
2497 g_free(tmp);
2498 send_sip_request(sip->gc, "SUBSCRIBE", to, to, hdr, body, NULL, process_subscribe_response);
2499 g_free(body);
2500 g_free(to);
2501 g_free(hdr);
2504 /* IM Session (INVITE and MESSAGE methods) */
2506 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2507 static gchar *
2508 get_end_points (struct sipe_account_data *sip,
2509 struct sip_session *session)
2511 gchar *res;
2513 if (session == NULL) {
2514 return NULL;
2517 res = g_strdup_printf("<sip:%s>", sip->username);
2519 SIPE_DIALOG_FOREACH {
2520 gchar *tmp = res;
2521 res = g_strdup_printf("%s, <%s>", res, dialog->with);
2522 g_free(tmp);
2524 if (dialog->theirepid) {
2525 tmp = res;
2526 res = g_strdup_printf("%s;epid=%s", res, dialog->theirepid);
2527 g_free(tmp);
2529 } SIPE_DIALOG_FOREACH_END;
2531 return res;
2534 static gboolean
2535 process_options_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2537 gboolean ret = TRUE;
2539 if (msg->response != 200) {
2540 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg->response);
2541 return FALSE;
2544 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
2546 return ret;
2550 * Asks UA/proxy about its capabilities.
2552 static void sipe_options_request(struct sipe_account_data *sip, const char *who)
2554 gchar *to = sip_uri(who);
2555 gchar *contact = get_contact(sip);
2556 gchar *request = g_strdup_printf(
2557 "Accept: application/sdp\r\n"
2558 "Contact: %s\r\n", contact);
2559 g_free(contact);
2561 send_sip_request(sip->gc, "OPTIONS", to, to, request, NULL, NULL, process_options_response);
2563 g_free(to);
2564 g_free(request);
2567 static void
2568 sipe_present_err(struct sipe_account_data *sip,
2569 struct sip_session *session,
2570 const gchar *message)
2572 PurpleConversation *conv;
2574 if (!session->conv) {
2575 conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY, session->with, sip->account);
2576 } else {
2577 conv = session->conv;
2579 purple_conversation_write(conv, NULL, message, PURPLE_MESSAGE_ERROR, time(NULL));
2582 void
2583 sipe_present_message_undelivered_err(struct sipe_account_data *sip,
2584 struct sip_session *session,
2585 const gchar *who,
2586 const gchar *message)
2588 char *msg, *msg_tmp;
2590 msg_tmp = message ? purple_markup_strip_html(message) : NULL;
2591 msg = msg_tmp ? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp) : NULL;
2592 g_free(msg_tmp);
2593 msg_tmp = g_strdup_printf( _("This message was not delivered to %s because one or more recipients are offline:\n%s") ,
2594 who ? who : "", msg ? msg : "");
2595 sipe_present_err(sip, session, msg_tmp);
2596 g_free(msg_tmp);
2597 g_free(msg);
2601 static void sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session);
2603 static gboolean
2604 process_message_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2606 gboolean ret = TRUE;
2607 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2608 struct sip_session *session = sipe_session_find_im(sip, with);
2609 struct sip_dialog *dialog;
2610 gchar *cseq;
2611 char *key;
2612 gchar *message;
2614 if (!session) {
2615 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2616 g_free(with);
2617 return FALSE;
2620 dialog = sipe_dialog_find(session, with);
2621 if (!dialog) {
2622 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2623 g_free(with);
2624 return FALSE;
2627 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2628 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg, "Call-ID"), atoi(cseq), with);
2629 g_free(cseq);
2630 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2632 if (msg->response >= 400) {
2633 PurpleBuddy *pbuddy;
2634 gchar *alias = with;
2636 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
2638 if ((pbuddy = purple_find_buddy(sip->account, with))) {
2639 alias = (gchar *)purple_buddy_get_alias(pbuddy);
2642 sipe_present_message_undelivered_err(sip, session, alias, message);
2643 ret = FALSE;
2644 } else {
2645 gchar *message_id = sipmsg_find_header(msg, "Message-Id");
2646 if (message_id) {
2647 g_hash_table_insert(session->conf_unconfirmed_messages, g_strdup(message_id), g_strdup(message));
2648 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
2649 message_id, g_hash_table_size(session->conf_unconfirmed_messages));
2652 g_hash_table_remove(session->unconfirmed_messages, key);
2653 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2654 key, g_hash_table_size(session->unconfirmed_messages));
2657 g_free(key);
2658 g_free(with);
2660 if (ret) sipe_im_process_queue(sip, session);
2661 return ret;
2664 static gboolean
2665 sipe_is_election_finished(struct sipe_account_data *sip,
2666 struct sip_session *session);
2668 static void
2669 sipe_election_result(struct sipe_account_data *sip,
2670 void *sess);
2672 static gboolean
2673 process_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2675 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
2676 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2677 struct sip_dialog *dialog;
2678 struct sip_session *session;
2680 session = sipe_session_find_chat_by_callid(sip, callid);
2681 if (!session) {
2682 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid);
2683 return FALSE;
2686 if (msg->response == 200 && !strncmp(contenttype, "application/x-ms-mim", 20)) {
2687 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
2688 xmlnode *xn_request_rm_response = xmlnode_get_child(xn_action, "RequestRMResponse");
2689 xmlnode *xn_set_rm_response = xmlnode_get_child(xn_action, "SetRMResponse");
2691 if (xn_request_rm_response) {
2692 const char *with = xmlnode_get_attrib(xn_request_rm_response, "uri");
2693 const char *allow = xmlnode_get_attrib(xn_request_rm_response, "allow");
2695 dialog = sipe_dialog_find(session, with);
2696 if (!dialog) {
2697 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with);
2698 return FALSE;
2701 if (allow && !g_strcasecmp(allow, "true")) {
2702 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with);
2703 dialog->election_vote = 1;
2704 } else if (allow && !g_strcasecmp(allow, "false")) {
2705 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with);
2706 dialog->election_vote = -1;
2709 if (sipe_is_election_finished(sip, session)) {
2710 sipe_election_result(sip, session);
2713 } else if (xn_set_rm_response) {
2716 xmlnode_free(xn_action);
2720 return TRUE;
2723 static void sipe_send_message(struct sipe_account_data *sip, struct sip_dialog *dialog, const char *msg)
2725 gchar *hdr;
2726 gchar *tmp;
2727 char *msgformat;
2728 char *msgtext;
2729 gchar *msgr_value;
2730 gchar *msgr;
2732 sipe_parse_html(msg, &msgformat, &msgtext);
2733 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat);
2735 msgr_value = sipmsg_get_msgr_string(msgformat);
2736 g_free(msgformat);
2737 if (msgr_value) {
2738 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2739 g_free(msgr_value);
2740 } else {
2741 msgr = g_strdup("");
2744 tmp = get_contact(sip);
2745 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2746 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2747 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2748 hdr = g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp, msgr);
2749 g_free(tmp);
2750 g_free(msgr);
2752 send_sip_request(sip->gc, "MESSAGE", dialog->with, dialog->with, hdr, msgtext, dialog, process_message_response);
2753 g_free(msgtext);
2754 g_free(hdr);
2758 static void
2759 sipe_im_process_queue (struct sipe_account_data * sip, struct sip_session * session)
2761 GSList *entry2 = session->outgoing_message_queue;
2762 while (entry2) {
2763 char *queued_msg = entry2->data;
2765 /* for multiparty chat or conference */
2766 if (session->is_multiparty || session->focus_uri) {
2767 gchar *who = sip_uri_self(sip);
2768 serv_got_chat_in(sip->gc, session->chat_id, who,
2769 PURPLE_MESSAGE_SEND, queued_msg, time(NULL));
2770 g_free(who);
2773 SIPE_DIALOG_FOREACH {
2774 char *key;
2776 if (dialog->outgoing_invite) continue; /* do not send messages as INVITE is not responded. */
2778 key = g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog->callid, (dialog->cseq) + 1, dialog->with);
2779 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(queued_msg));
2780 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2781 key, g_hash_table_size(session->unconfirmed_messages));
2782 g_free(key);
2784 sipe_send_message(sip, dialog, queued_msg);
2785 } SIPE_DIALOG_FOREACH_END;
2787 entry2 = session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2788 g_free(queued_msg);
2792 static void
2793 sipe_refer_notify(struct sipe_account_data *sip,
2794 struct sip_session *session,
2795 const gchar *who,
2796 int status,
2797 const gchar *desc)
2799 gchar *hdr;
2800 gchar *body;
2801 struct sip_dialog *dialog = sipe_dialog_find(session, who);
2803 hdr = g_strdup_printf(
2804 "Event: refer\r\n"
2805 "Subscription-State: %s\r\n"
2806 "Content-Type: message/sipfrag\r\n",
2807 status >= 200 ? "terminated" : "active");
2809 body = g_strdup_printf(
2810 "SIP/2.0 %d %s\r\n",
2811 status, desc);
2813 dialog->outgoing_invite = send_sip_request(sip->gc, "NOTIFY",
2814 who, who, hdr, body, dialog, NULL);
2816 g_free(hdr);
2817 g_free(body);
2820 static gboolean
2821 process_invite_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
2823 gchar *with = parse_from(sipmsg_find_header(msg, "To"));
2824 struct sip_session *session;
2825 struct sip_dialog *dialog;
2826 char *cseq;
2827 char *key;
2828 gchar *message;
2829 struct sipmsg *request_msg = trans->msg;
2831 gchar *callid = sipmsg_find_header(msg, "Call-ID");
2832 gchar *referred_by;
2834 session = sipe_session_find_chat_by_callid(sip, callid);
2835 if (!session) {
2836 session = sipe_session_find_im(sip, with);
2838 if (!session) {
2839 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2840 g_free(with);
2841 return FALSE;
2844 dialog = sipe_dialog_find(session, with);
2845 if (!dialog) {
2846 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2847 g_free(with);
2848 return FALSE;
2851 sipe_dialog_parse(dialog, msg, TRUE);
2853 cseq = sipmsg_find_part_of_header(sipmsg_find_header(msg, "CSeq"), NULL, " ", NULL);
2854 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, atoi(cseq));
2855 g_free(cseq);
2856 message = g_hash_table_lookup(session->unconfirmed_messages, key);
2858 if (msg->response != 200) {
2859 PurpleBuddy *pbuddy;
2860 gchar *alias = with;
2862 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2864 if ((pbuddy = purple_find_buddy(sip->account, with))) {
2865 alias = (gchar *)purple_buddy_get_alias(pbuddy);
2868 if (message) {
2869 sipe_present_message_undelivered_err(sip, session, alias, message);
2870 } else {
2871 gchar *tmp_msg = g_strdup_printf(_("Failed to invite %s"), alias);
2872 sipe_present_err(sip, session, tmp_msg);
2873 g_free(tmp_msg);
2876 sipe_dialog_remove(session, with);
2878 g_free(key);
2879 g_free(with);
2880 return FALSE;
2883 dialog->cseq = 0;
2884 send_sip_request(sip->gc, "ACK", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
2885 dialog->outgoing_invite = NULL;
2886 dialog->is_established = TRUE;
2888 referred_by = parse_from(sipmsg_find_header(request_msg, "Referred-By"));
2889 if (referred_by) {
2890 sipe_refer_notify(sip, session, referred_by, 200, "OK");
2891 g_free(referred_by);
2894 /* add user to chat if it is a multiparty session */
2895 if (session->is_multiparty) {
2896 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
2897 with, NULL,
2898 PURPLE_CBFLAGS_NONE, TRUE);
2901 if(g_slist_find_custom(dialog->supported, "ms-text-format", (GCompareFunc)g_ascii_strcasecmp)) {
2902 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2903 if (session->outgoing_message_queue) {
2904 char *queued_msg = session->outgoing_message_queue->data;
2905 session->outgoing_message_queue = g_slist_remove(session->outgoing_message_queue, queued_msg);
2906 g_free(queued_msg);
2910 sipe_im_process_queue(sip, session);
2912 g_hash_table_remove(session->unconfirmed_messages, key);
2913 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2914 key, g_hash_table_size(session->unconfirmed_messages));
2916 g_free(key);
2917 g_free(with);
2918 return TRUE;
2922 void
2923 sipe_invite(struct sipe_account_data *sip,
2924 struct sip_session *session,
2925 const gchar *who,
2926 const gchar *msg_body,
2927 const gchar *referred_by,
2928 const gboolean is_triggered)
2930 gchar *hdr;
2931 gchar *to;
2932 gchar *contact;
2933 gchar *body;
2934 gchar *self;
2935 char *ms_text_format = g_strdup("");
2936 gchar *roster_manager;
2937 gchar *end_points;
2938 gchar *referred_by_str;
2939 struct sip_dialog *dialog = sipe_dialog_find(session, who);
2941 if (dialog && dialog->is_established) {
2942 purple_debug_info("sipe", "session with %s already has a dialog open\n", who);
2943 return;
2946 if (!dialog) {
2947 dialog = sipe_dialog_add(session);
2948 dialog->callid = session->callid ? g_strdup(session->callid) : gencallid();
2949 dialog->with = g_strdup(who);
2952 if (!(dialog->ourtag)) {
2953 dialog->ourtag = gentag();
2956 to = sip_uri(who);
2958 if (msg_body) {
2959 char *msgformat;
2960 char *msgtext;
2961 char *base64_msg;
2962 gchar *msgr_value;
2963 gchar *msgr;
2964 char *key;
2966 sipe_parse_html(msg_body, &msgformat, &msgtext);
2967 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat);
2969 msgr_value = sipmsg_get_msgr_string(msgformat);
2970 g_free(msgformat);
2971 msgr = "";
2972 if (msgr_value) {
2973 msgr = g_strdup_printf(";msgr=%s", msgr_value);
2974 g_free(msgr_value);
2977 base64_msg = purple_base64_encode((guchar*) msgtext, strlen(msgtext));
2978 ms_text_format = g_strdup_printf(SIPE_INVITE_TEXT, msgr, base64_msg);
2979 g_free(msgtext);
2980 g_free(msgr);
2981 g_free(base64_msg);
2983 key = g_strdup_printf("<%s><%d><INVITE>", dialog->callid, (dialog->cseq) + 1);
2984 g_hash_table_insert(session->unconfirmed_messages, g_strdup(key), g_strdup(msg_body));
2985 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2986 key, g_hash_table_size(session->unconfirmed_messages));
2987 g_free(key);
2990 contact = get_contact(sip);
2991 end_points = get_end_points(sip, session);
2992 self = sip_uri_self(sip);
2993 roster_manager = g_strdup_printf(
2994 "Roster-Manager: %s\r\n"
2995 "EndPoints: %s\r\n",
2996 self,
2997 end_points);
2998 referred_by_str = referred_by ?
2999 g_strdup_printf(
3000 "Referred-By: %s\r\n",
3001 referred_by)
3002 : g_strdup("");
3003 hdr = g_strdup_printf(
3004 "Supported: ms-sender\r\n"
3005 "%s"
3006 "%s"
3007 "%s"
3008 "%s"
3009 "Contact: %s\r\n%s"
3010 "Content-Type: application/sdp\r\n",
3011 (session->roster_manager && !strcmp(session->roster_manager, self)) ? roster_manager : "",
3012 referred_by_str,
3013 is_triggered ? "TriggeredInvite: TRUE\r\n" : "",
3014 is_triggered || session->is_multiparty ? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3015 contact,
3016 ms_text_format);
3017 g_free(ms_text_format);
3018 g_free(self);
3020 body = g_strdup_printf(
3021 "v=0\r\n"
3022 "o=- 0 0 IN IP4 %s\r\n"
3023 "s=session\r\n"
3024 "c=IN IP4 %s\r\n"
3025 "t=0 0\r\n"
3026 "m=message %d sip null\r\n"
3027 "a=accept-types:text/plain text/html image/gif "
3028 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3029 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip->realport);
3031 dialog->outgoing_invite = send_sip_request(sip->gc, "INVITE",
3032 to, to, hdr, body, dialog, process_invite_response);
3034 g_free(to);
3035 g_free(roster_manager);
3036 g_free(end_points);
3037 g_free(referred_by_str);
3038 g_free(body);
3039 g_free(hdr);
3040 g_free(contact);
3043 static void
3044 sipe_refer(struct sipe_account_data *sip,
3045 struct sip_session *session,
3046 const gchar *who)
3048 gchar *hdr;
3049 gchar *contact;
3050 struct sip_dialog *dialog = sipe_dialog_find(session,
3051 session->roster_manager);
3053 contact = get_contact(sip);
3054 hdr = g_strdup_printf(
3055 "Contact: %s\r\n"
3056 "Refer-to: <%s>\r\n"
3057 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3058 "Require: com.microsoft.rtc-multiparty\r\n",
3059 contact,
3060 who,
3061 sip->username,
3062 dialog->ourtag ? ";tag=" : "",
3063 dialog->ourtag ? dialog->ourtag : "",
3064 get_epid(sip));
3066 send_sip_request(sip->gc, "REFER",
3067 session->roster_manager, session->roster_manager, hdr, NULL, dialog, NULL);
3069 g_free(hdr);
3070 g_free(contact);
3073 static void
3074 sipe_send_election_request_rm(struct sipe_account_data *sip,
3075 struct sip_dialog *dialog,
3076 int bid)
3078 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3080 gchar *body = g_strdup_printf(
3081 "<?xml version=\"1.0\"?>\r\n"
3082 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3083 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3084 sip->username, bid);
3086 send_sip_request(sip->gc, "INFO",
3087 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3089 g_free(body);
3092 static void
3093 sipe_send_election_set_rm(struct sipe_account_data *sip,
3094 struct sip_dialog *dialog)
3096 const gchar *hdr = "Content-Type: application/x-ms-mim\r\n";
3098 gchar *body = g_strdup_printf(
3099 "<?xml version=\"1.0\"?>\r\n"
3100 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3101 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3102 sip->username);
3104 send_sip_request(sip->gc, "INFO",
3105 dialog->with, dialog->with, hdr, body, dialog, process_info_response);
3107 g_free(body);
3110 static void
3111 im_session_close (struct sipe_account_data *sip, struct sip_session * session)
3113 if (session) {
3114 SIPE_DIALOG_FOREACH {
3115 /* @TODO slow down BYE message sending rate */
3116 /* @see single subscription code */
3117 send_sip_request(sip->gc, "BYE", dialog->with, dialog->with, NULL, NULL, dialog, NULL);
3118 } SIPE_DIALOG_FOREACH_END;
3120 sipe_session_remove(sip, session);
3124 static void
3125 sipe_convo_closed(PurpleConnection * gc, const char *who)
3127 struct sipe_account_data *sip = gc->proto_data;
3129 purple_debug_info("sipe", "conversation with %s closed\n", who);
3130 im_session_close(sip, sipe_session_find_im(sip, who));
3133 static void
3134 sipe_chat_leave (PurpleConnection *gc, int id)
3136 struct sipe_account_data *sip = gc->proto_data;
3137 struct sip_session *session = sipe_session_find_chat_by_id(sip, id);
3138 if (session && session->focus_uri) {
3139 conf_session_close(sip, session);
3141 im_session_close(sip, session);
3144 static int sipe_im_send(PurpleConnection *gc, const char *who, const char *what, PurpleMessageFlags flags)
3146 struct sipe_account_data *sip = gc->proto_data;
3147 struct sip_session *session;
3148 struct sip_dialog *dialog;
3150 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what);
3152 session = sipe_session_find_or_add_im(sip, who);
3153 dialog = sipe_dialog_find(session, who);
3155 // Queue the message
3156 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue, g_strdup(what));
3158 if (dialog && dialog->callid) {
3159 sipe_im_process_queue(sip, session);
3160 } else if (!dialog || !dialog->outgoing_invite) {
3161 // Need to send the INVITE to get the outgoing dialog setup
3162 sipe_invite(sip, session, who, what, NULL, FALSE);
3165 return 1;
3168 static int sipe_chat_send(PurpleConnection *gc, int id, const char *what, PurpleMessageFlags flags)
3170 struct sipe_account_data *sip = gc->proto_data;
3171 struct sip_session *session;
3173 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what);
3175 session = sipe_session_find_chat_by_id(sip, id);
3177 // Queue the message
3178 if (session) {
3179 session->outgoing_message_queue = g_slist_append(session->outgoing_message_queue,
3180 g_strdup(what));
3181 sipe_im_process_queue(sip, session);
3184 return 1;
3187 /* End IM Session (INVITE and MESSAGE methods) */
3189 static void process_incoming_info(struct sipe_account_data *sip, struct sipmsg *msg)
3191 gchar *contenttype = sipmsg_find_header(msg, "Content-Type");
3192 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3193 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3195 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
3196 if (!session) {
3197 session = sipe_session_find_im(sip, from);
3199 if (!session) {
3200 g_free(from);
3201 return;
3204 if (!strncmp(contenttype, "application/x-ms-mim", 20)) {
3205 xmlnode *xn_action = xmlnode_from_str(msg->body, msg->bodylen);
3206 xmlnode *xn_request_rm = xmlnode_get_child(xn_action, "RequestRM");
3207 xmlnode *xn_set_rm = xmlnode_get_child(xn_action, "SetRM");
3209 sipmsg_add_header(msg, "Content-Type", "application/x-ms-mim");
3211 if (xn_request_rm) {
3212 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3213 int bid = atoi(xmlnode_get_attrib(xn_request_rm, "bid"));
3214 gchar *body = g_strdup_printf(
3215 "<?xml version=\"1.0\"?>\r\n"
3216 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3217 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3218 sip->username,
3219 session->bid < bid ? "true" : "false");
3220 send_sip_response(sip->gc, msg, 200, "OK", body);
3221 g_free(body);
3222 } else if (xn_set_rm) {
3223 gchar *body;
3224 const char *rm = xmlnode_get_attrib(xn_set_rm, "uri");
3225 g_free(session->roster_manager);
3226 session->roster_manager = g_strdup(rm);
3228 body = g_strdup_printf(
3229 "<?xml version=\"1.0\"?>\r\n"
3230 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3231 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3232 sip->username);
3233 send_sip_response(sip->gc, msg, 200, "OK", body);
3234 g_free(body);
3236 xmlnode_free(xn_action);
3238 } else {
3239 /* looks like purple lacks typing notification for chat */
3240 if (!session->is_multiparty && !session->focus_uri) {
3241 serv_got_typing(sip->gc, from, SIPE_TYPING_RECV_TIMEOUT, PURPLE_TYPING);
3244 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3246 g_free(from);
3249 static void process_incoming_bye(struct sipe_account_data *sip, struct sipmsg *msg)
3251 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3252 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3253 struct sip_session *session;
3255 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3257 session = sipe_session_find_chat_by_callid(sip, callid);
3258 if (!session) {
3259 session = sipe_session_find_im(sip, from);
3261 if (!session) {
3262 g_free(from);
3263 return;
3266 if (session->roster_manager && !g_strcasecmp(from, session->roster_manager)) {
3267 g_free(session->roster_manager);
3268 session->roster_manager = NULL;
3271 /* This what BYE is essentially for - terminating dialog */
3272 sipe_dialog_remove(session, from);
3273 if (session->is_multiparty) {
3274 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session->conv), from, NULL);
3277 g_free(from);
3280 static void process_incoming_refer(struct sipe_account_data *sip, struct sipmsg *msg)
3282 gchar *self = sip_uri_self(sip);
3283 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3284 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
3285 gchar *refer_to = parse_from(sipmsg_find_header(msg, "Refer-to"));
3286 gchar *referred_by = g_strdup(sipmsg_find_header(msg, "Referred-By"));
3287 struct sip_session *session;
3288 struct sip_dialog *dialog;
3290 session = sipe_session_find_chat_by_callid(sip, callid);
3291 dialog = sipe_dialog_find(session, from);
3293 if (!session || !dialog || !session->roster_manager || strcmp(session->roster_manager, self)) {
3294 send_sip_response(sip->gc, msg, 500, "Server Internal Error", NULL);
3295 } else {
3296 send_sip_response(sip->gc, msg, 202, "Accepted", NULL);
3298 sipe_invite(sip, session, refer_to, NULL, referred_by, FALSE);
3301 g_free(self);
3302 g_free(from);
3303 g_free(refer_to);
3304 g_free(referred_by);
3307 static unsigned int
3308 sipe_send_typing(PurpleConnection *gc, const char *who, PurpleTypingState state)
3310 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
3311 struct sip_session *session;
3312 struct sip_dialog *dialog;
3314 if (state == PURPLE_NOT_TYPING)
3315 return 0;
3317 session = sipe_session_find_im(sip, who);
3318 dialog = sipe_dialog_find(session, who);
3320 if (session && dialog) {
3321 send_sip_request(gc, "INFO", who, who,
3322 "Content-Type: application/xml\r\n",
3323 SIPE_SEND_TYPING, dialog, NULL);
3325 return SIPE_TYPING_SEND_TIMEOUT;
3328 static gboolean resend_timeout(struct sipe_account_data *sip)
3330 GSList *tmp = sip->transactions;
3331 time_t currtime = time(NULL);
3332 while (tmp) {
3333 struct transaction *trans = tmp->data;
3334 tmp = tmp->next;
3335 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime-trans->time);
3336 if ((currtime - trans->time > 5) && trans->retries >= 1) {
3337 /* TODO 408 */
3338 } else {
3339 if ((currtime - trans->time > 2) && trans->retries == 0) {
3340 trans->retries++;
3341 sendout_sipmsg(sip, trans->msg);
3345 return TRUE;
3348 static void do_reauthenticate_cb(struct sipe_account_data *sip, void *unused)
3350 /* register again when security token expires */
3351 /* we have to start a new authentication as the security token
3352 * is almost expired by sending a not signed REGISTER message */
3353 purple_debug_info("sipe", "do a full reauthentication\n");
3354 sipe_auth_free(&sip->registrar);
3355 sipe_auth_free(&sip->proxy);
3356 sip->registerstatus = 0;
3357 do_register(sip);
3358 sip->reauthenticate_set = FALSE;
3361 static void process_incoming_message(struct sipe_account_data *sip, struct sipmsg *msg)
3363 gchar *from;
3364 gchar *contenttype;
3365 gboolean found = FALSE;
3367 from = parse_from(sipmsg_find_header(msg, "From"));
3369 if (!from) return;
3371 purple_debug_info("sipe", "got message from %s: %s\n", from, msg->body);
3373 contenttype = sipmsg_find_header(msg, "Content-Type");
3374 if (!strncmp(contenttype, "text/plain", 10)
3375 || !strncmp(contenttype, "text/html", 9)
3376 || !strncmp(contenttype, "multipart/related", 21))
3378 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3379 gchar *html = get_html_message(contenttype, msg->body);
3381 struct sip_session *session = sipe_session_find_chat_by_callid(sip, callid);
3382 if (!session) {
3383 session = sipe_session_find_im(sip, from);
3386 if (session && session->focus_uri) { /* a conference */
3387 gchar *tmp = parse_from(sipmsg_find_header(msg, "Ms-Sender"));
3388 gchar *sender = parse_from(tmp);
3389 g_free(tmp);
3390 serv_got_chat_in(sip->gc, session->chat_id, sender,
3391 PURPLE_MESSAGE_RECV, html, time(NULL));
3392 g_free(sender);
3393 } else if (session && session->is_multiparty) { /* a multiparty chat */
3394 serv_got_chat_in(sip->gc, session->chat_id, from,
3395 PURPLE_MESSAGE_RECV, html, time(NULL));
3396 } else {
3397 serv_got_im(sip->gc, from, html, 0, time(NULL));
3399 g_free(html);
3400 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3401 found = TRUE;
3403 } else if (!strncmp(contenttype, "application/im-iscomposing+xml", 30)) {
3404 xmlnode *isc = xmlnode_from_str(msg->body, msg->bodylen);
3405 xmlnode *state;
3406 gchar *statedata;
3408 if (!isc) {
3409 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3410 return;
3413 state = xmlnode_get_child(isc, "state");
3415 if (!state) {
3416 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3417 xmlnode_free(isc);
3418 return;
3421 statedata = xmlnode_get_data(state);
3422 if (statedata) {
3423 if (strstr(statedata, "active")) serv_got_typing(sip->gc, from, 0, PURPLE_TYPING);
3424 else serv_got_typing_stopped(sip->gc, from);
3426 g_free(statedata);
3428 xmlnode_free(isc);
3429 send_sip_response(sip->gc, msg, 200, "OK", NULL);
3430 found = TRUE;
3432 if (!found) {
3433 purple_debug_info("sipe", "got unknown mime-type");
3434 send_sip_response(sip->gc, msg, 415, "Unsupported media type", NULL);
3436 g_free(from);
3439 static void process_incoming_invite(struct sipe_account_data *sip, struct sipmsg *msg)
3441 gchar *ms_text_format;
3442 gchar *body;
3443 gchar *newTag;
3444 gchar *oldHeader;
3445 gchar *newHeader;
3446 gboolean is_multiparty = FALSE;
3447 gboolean is_triggered = FALSE;
3448 gboolean was_multiparty = TRUE;
3449 gboolean just_joined = FALSE;
3450 gchar *from;
3451 gchar *callid = sipmsg_find_header(msg, "Call-ID");
3452 gchar *roster_manager = sipmsg_find_header(msg, "Roster-Manager");
3453 gchar *end_points_hdr = sipmsg_find_header(msg, "EndPoints");
3454 gchar *trig_invite = sipmsg_find_header(msg, "TriggeredInvite");
3455 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
3456 GSList *end_points = NULL;
3457 struct sip_session *session;
3459 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg->body ? msg->body : "");
3461 /* Invitation to join conference */
3462 if (!strncmp(content_type, "application/ms-conf-invite+xml", 30)) {
3463 process_incoming_invite_conf(sip, msg);
3464 return;
3467 /* Only accept text invitations */
3468 if (msg->body && !(strstr(msg->body, "m=message") || strstr(msg->body, "m=x-ms-message"))) {
3469 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
3470 return;
3473 // TODO There *must* be a better way to clean up the To header to add a tag...
3474 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3475 oldHeader = sipmsg_find_header(msg, "To");
3476 newTag = gentag();
3477 newHeader = g_strdup_printf("%s;tag=%s", oldHeader, newTag);
3478 sipmsg_remove_header_now(msg, "To");
3479 sipmsg_add_header_now(msg, "To", newHeader);
3480 g_free(newHeader);
3482 if (end_points_hdr) {
3483 end_points = sipmsg_parse_endpoints_header(end_points_hdr);
3485 if (g_slist_length(end_points) > 2) {
3486 is_multiparty = TRUE;
3489 if (trig_invite && !g_strcasecmp(trig_invite, "TRUE")) {
3490 is_triggered = TRUE;
3491 is_multiparty = TRUE;
3494 session = sipe_session_find_chat_by_callid(sip, callid);
3495 /* Convert to multiparty */
3496 if (session && is_multiparty && !session->is_multiparty) {
3497 g_free(session->with);
3498 session->with = NULL;
3499 was_multiparty = FALSE;
3500 session->is_multiparty = TRUE;
3501 session->chat_id = rand();
3504 if (!session && is_multiparty) {
3505 session = sipe_session_find_or_add_chat_by_callid(sip, callid);
3507 /* IM session */
3508 from = parse_from(sipmsg_find_header(msg, "From"));
3509 if (!session) {
3510 session = sipe_session_find_or_add_im(sip, from);
3513 if (!session->callid) {
3514 session->callid = g_strdup(callid);
3517 session->is_multiparty = is_multiparty;
3518 if (roster_manager) {
3519 session->roster_manager = g_strdup(roster_manager);
3522 if (is_multiparty && end_points) {
3523 gchar *to = parse_from(sipmsg_find_header(msg, "To"));
3524 GSList *entry = end_points;
3525 while (entry) {
3526 struct sip_dialog *dialog;
3527 struct sipendpoint *end_point = entry->data;
3528 entry = entry->next;
3530 if (!g_strcasecmp(from, end_point->contact) ||
3531 !g_strcasecmp(to, end_point->contact))
3532 continue;
3534 dialog = sipe_dialog_find(session, end_point->contact);
3535 if (dialog) {
3536 g_free(dialog->theirepid);
3537 dialog->theirepid = end_point->epid;
3538 end_point->epid = NULL;
3539 } else {
3540 dialog = sipe_dialog_add(session);
3542 dialog->callid = g_strdup(session->callid);
3543 dialog->with = end_point->contact;
3544 end_point->contact = NULL;
3545 dialog->theirepid = end_point->epid;
3546 end_point->epid = NULL;
3548 just_joined = TRUE;
3550 /* send triggered INVITE */
3551 sipe_invite(sip, session, dialog->with, NULL, NULL, TRUE);
3554 g_free(to);
3557 if (end_points) {
3558 GSList *entry = end_points;
3559 while (entry) {
3560 struct sipendpoint *end_point = entry->data;
3561 entry = entry->next;
3562 g_free(end_point->contact);
3563 g_free(end_point->epid);
3564 g_free(end_point);
3566 g_slist_free(end_points);
3569 if (session) {
3570 struct sip_dialog *dialog = sipe_dialog_find(session, from);
3571 if (dialog) {
3572 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3573 } else {
3574 dialog = sipe_dialog_add(session);
3576 dialog->callid = g_strdup(session->callid);
3577 dialog->with = g_strdup(from);
3578 sipe_dialog_parse(dialog, msg, FALSE);
3580 if (!dialog->ourtag) {
3581 dialog->ourtag = newTag;
3582 newTag = NULL;
3585 just_joined = TRUE;
3587 } else {
3588 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3590 g_free(newTag);
3592 if (is_multiparty && !session->conv) {
3593 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
3594 gchar *self = sip_uri_self(sip);
3595 /* create prpl chat */
3596 session->conv = serv_got_joined_chat(sip->gc, session->chat_id, chat_name);
3597 session->chat_name = g_strdup(chat_name);
3598 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
3599 /* add self */
3600 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3601 self, NULL,
3602 PURPLE_CBFLAGS_NONE, FALSE);
3603 g_free(chat_name);
3604 g_free(self);
3607 if (is_multiparty && !was_multiparty) {
3608 /* add current IM counterparty to chat */
3609 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3610 sipe_dialog_first(session)->with, NULL,
3611 PURPLE_CBFLAGS_NONE, FALSE);
3614 /* add inviting party */
3615 if (just_joined) {
3616 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv),
3617 from, NULL,
3618 PURPLE_CBFLAGS_NONE, TRUE);
3621 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3622 ms_text_format = sipmsg_find_header(msg, "ms-text-format");
3623 if (ms_text_format) {
3624 if (!strncmp(ms_text_format, "text/plain", 10) || !strncmp(ms_text_format, "text/html", 9)) {
3626 gchar *html = get_html_message(ms_text_format, NULL);
3627 if (html) {
3628 if (is_multiparty) {
3629 serv_got_chat_in(sip->gc, session->chat_id, from,
3630 PURPLE_MESSAGE_RECV, html, time(NULL));
3631 } else {
3632 serv_got_im(sip->gc, from, html, 0, time(NULL));
3634 g_free(html);
3635 sipmsg_add_header(msg, "Supported", "ms-text-format"); // accepts message received
3639 g_free(from);
3641 sipmsg_add_header(msg, "Supported", "com.microsoft.rtc-multiparty");
3642 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3643 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3645 body = g_strdup_printf(
3646 "v=0\r\n"
3647 "o=- 0 0 IN IP4 %s\r\n"
3648 "s=session\r\n"
3649 "c=IN IP4 %s\r\n"
3650 "t=0 0\r\n"
3651 "m=message %d sip sip:%s\r\n"
3652 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3653 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3654 sip->realport, sip->username);
3655 send_sip_response(sip->gc, msg, 200, "OK", body);
3656 g_free(body);
3659 static void process_incoming_options(struct sipe_account_data *sip, struct sipmsg *msg)
3661 gchar *body;
3663 sipmsg_add_header(msg, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
3664 sipmsg_add_header(msg, "User-Agent", purple_account_get_string(sip->account, "useragent", "Purple/" VERSION));
3665 sipmsg_add_header(msg, "Content-Type", "application/sdp");
3667 body = g_strdup_printf(
3668 "v=0\r\n"
3669 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3670 "s=session\r\n"
3671 "c=IN IP4 0.0.0.0\r\n"
3672 "t=0 0\r\n"
3673 "m=message %d sip sip:%s\r\n"
3674 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
3675 sip->realport, sip->username);
3676 send_sip_response(sip->gc, msg, 200, "OK", body);
3677 g_free(body);
3680 static void sipe_connection_cleanup(struct sipe_account_data *);
3681 static void create_connection(struct sipe_account_data *, gchar *, int);
3683 gboolean process_register_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
3685 gchar *tmp;
3686 const gchar *expires_header;
3687 int expires, i;
3688 GSList *hdr = msg->headers;
3689 struct siphdrelement *elem;
3691 expires_header = sipmsg_find_header(msg, "Expires");
3692 expires = expires_header != NULL ? strtol(expires_header, NULL, 10) : 0;
3693 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires);
3695 switch (msg->response) {
3696 case 200:
3697 if (expires == 0) {
3698 sip->registerstatus = 0;
3699 } else {
3700 gchar *contact_hdr = NULL;
3701 gchar *gruu = NULL;
3702 gchar *epid;
3703 gchar *uuid;
3704 gchar *timeout;
3706 if (!sip->reregister_set) {
3707 gchar *action_name = g_strdup_printf("<%s>", "registration");
3708 sipe_schedule_action(action_name, expires, do_register_cb, NULL, sip, NULL);
3709 g_free(action_name);
3710 sip->reregister_set = TRUE;
3713 sip->registerstatus = 3;
3715 #ifdef USE_KERBEROS
3716 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3717 #endif
3718 tmp = sipmsg_find_auth_header(msg, "NTLM");
3719 #ifdef USE_KERBEROS
3720 } else {
3721 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3723 #endif
3724 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3725 fill_auth(sip, tmp, &sip->registrar);
3727 if (!sip->reauthenticate_set) {
3728 gchar *action_name = g_strdup_printf("<%s>", "+reauthentication");
3729 guint reauth_timeout;
3730 if (sip->registrar.type == AUTH_TYPE_KERBEROS && sip->registrar.expires > 0) {
3731 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3732 reauth_timeout = sip->registrar.expires - 300;
3733 } else {
3734 /* NTLM: we have to reauthenticate as our security token expires
3735 after eight hours (be five minutes early) */
3736 reauth_timeout = (8 * 3600) - 300;
3738 sipe_schedule_action(action_name, reauth_timeout, do_reauthenticate_cb, NULL, sip, NULL);
3739 g_free(action_name);
3740 sip->reauthenticate_set = TRUE;
3743 purple_connection_set_state(sip->gc, PURPLE_CONNECTED);
3745 epid = get_epid(sip);
3746 uuid = generateUUIDfromEPID(epid);
3747 g_free(epid);
3749 // There can be multiple Contact headers (one per location where the user is logged in) so
3750 // make sure to only get the one for this uuid
3751 for (i = 0; (contact_hdr = sipmsg_find_header_instance (msg, "Contact", i)); i++) {
3752 gchar * valid_contact = sipmsg_find_part_of_header (contact_hdr, uuid, NULL, NULL);
3753 if (valid_contact) {
3754 gruu = sipmsg_find_part_of_header(contact_hdr, "gruu=\"", "\"", NULL);
3755 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3756 g_free(valid_contact);
3757 break;
3758 } else {
3759 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3762 g_free(uuid);
3764 g_free(sip->contact);
3765 if(gruu) {
3766 sip->contact = g_strdup_printf("<%s>", gruu);
3767 g_free(gruu);
3768 } else {
3769 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3770 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);
3772 sip->msrtc_event_categories = FALSE;
3773 sip->batched_support = FALSE;
3775 while(hdr)
3777 elem = hdr->data;
3778 if (!g_ascii_strcasecmp(elem->name, "Supported")) {
3779 if (!g_ascii_strcasecmp(elem->value, "msrtc-event-categories")) {
3780 sip->msrtc_event_categories = TRUE;
3781 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->msrtc_event_categories);
3783 if (!g_ascii_strcasecmp(elem->value, "adhoclist")) {
3784 sip->batched_support = TRUE;
3785 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Supported: %s: %d\n", elem->value,sip->batched_support);
3788 if (!g_ascii_strcasecmp(elem->name, "Allow-Events")){
3789 gchar **caps = g_strsplit(elem->value,",",0);
3790 i = 0;
3791 while (caps[i]) {
3792 sip->allow_events = g_slist_append(sip->allow_events, g_strdup(caps[i]));
3793 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Allow-Events: %s\n", caps[i]);
3794 i++;
3796 g_strfreev(caps);
3798 hdr = g_slist_next(hdr);
3801 /* subscriptions */
3802 if (!sip->subscribed) { //do it just once, not every re-register
3804 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts",
3805 (GCompareFunc)g_ascii_strcasecmp)) {
3806 sipe_subscribe_roaming_contacts(sip, msg);
3809 /* For 2007+ it does not make sence to subscribe to:
3810 * vnd-microsoft-roaming-ACL
3811 * vnd-microsoft-provisioning (not v2)
3812 * presence.wpending
3813 * These are for backward compatibility.
3815 if (sip->msrtc_event_categories)
3817 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-self",
3818 (GCompareFunc)g_ascii_strcasecmp)) {
3819 sipe_subscribe_roaming_self(sip, msg);
3821 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning-v2",
3822 (GCompareFunc)g_ascii_strcasecmp)) {
3823 sipe_subscribe_roaming_provisioning_v2(sip, msg);
3826 /* For 2005- servers */
3827 else
3829 //sipe_options_request(sip, sip->sipdomain);
3831 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL",
3832 (GCompareFunc)g_ascii_strcasecmp)) {
3833 sipe_subscribe_roaming_acl(sip, msg);
3835 if (g_slist_find_custom(sip->allow_events, "vnd-microsoft-provisioning",
3836 (GCompareFunc)g_ascii_strcasecmp)) {
3837 sipe_subscribe_roaming_provisioning(sip, msg);
3839 if (g_slist_find_custom(sip->allow_events, "presence.wpending",
3840 (GCompareFunc)g_ascii_strcasecmp)) {
3841 sipe_subscribe_presence_wpending(sip, msg);
3844 sipe_set_status(sip->account, purple_account_get_active_status(sip->account));
3845 sip->subscribed = TRUE;
3848 timeout = sipmsg_find_part_of_header(sipmsg_find_header(msg, "ms-keep-alive"),
3849 "timeout=", ";", NULL);
3850 if (timeout != NULL) {
3851 sscanf(timeout, "%u", &sip->keepalive_timeout);
3852 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3853 sip->keepalive_timeout);
3854 g_free(timeout);
3857 // Should we remove the transaction here?
3858 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip->cseq);
3859 transactions_remove(sip, tc);
3861 break;
3862 case 301:
3864 gchar *redirect = parse_from(sipmsg_find_header(msg, "Contact"));
3866 if (redirect && (g_strncasecmp("sip:", redirect, 4) == 0)) {
3867 gchar **parts = g_strsplit(redirect + 4, ";", 0);
3868 gchar **tmp;
3869 gchar *hostname;
3870 int port = 0;
3871 sipe_transport_type transport = SIPE_TRANSPORT_TLS;
3872 int i = 1;
3874 tmp = g_strsplit(parts[0], ":", 0);
3875 hostname = g_strdup(tmp[0]);
3876 if (tmp[1]) port = strtoul(tmp[1], NULL, 10);
3877 g_strfreev(tmp);
3879 while (parts[i]) {
3880 tmp = g_strsplit(parts[i], "=", 0);
3881 if (tmp[1]) {
3882 if (g_strcasecmp("transport", tmp[0]) == 0) {
3883 if (g_strcasecmp("tcp", tmp[1]) == 0) {
3884 transport = SIPE_TRANSPORT_TCP;
3885 } else if (g_strcasecmp("udp", tmp[1]) == 0) {
3886 transport = SIPE_TRANSPORT_UDP;
3890 g_strfreev(tmp);
3891 i++;
3893 g_strfreev(parts);
3895 /* Close old connection */
3896 sipe_connection_cleanup(sip);
3898 /* Create new connection */
3899 sip->transport = transport;
3900 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3901 hostname, port, TRANSPORT_DESCRIPTOR);
3902 create_connection(sip, hostname, port);
3904 g_free(redirect);
3906 break;
3907 case 401:
3908 if (sip->registerstatus != 2) {
3909 purple_debug_info("sipe", "REGISTER retries %d\n", sip->registrar.retries);
3910 if (sip->registrar.retries > 3) {
3911 sip->gc->wants_to_die = TRUE;
3912 purple_connection_error(sip->gc, _("Wrong Password"));
3913 return TRUE;
3915 #ifdef USE_KERBEROS
3916 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
3917 #endif
3918 tmp = sipmsg_find_auth_header(msg, "NTLM");
3919 #ifdef USE_KERBEROS
3920 } else {
3921 tmp = sipmsg_find_auth_header(msg, "Kerberos");
3923 #endif
3924 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_register_response - Auth header: %s\r\n", tmp);
3925 fill_auth(sip, tmp, &sip->registrar);
3926 sip->registerstatus = 2;
3927 if (sip->account->disconnecting) {
3928 do_register_exp(sip, 0);
3929 } else {
3930 do_register(sip);
3933 break;
3934 case 403:
3936 gchar *warning = sipmsg_find_header(msg, "Warning");
3937 if (warning != NULL) {
3938 /* Example header:
3939 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3941 gchar **tmp = g_strsplit(warning, "\"", 0);
3942 warning = g_strdup_printf(_("You have been rejected by the server: %s"), tmp[1] ? tmp[1] : _("no reason given"));
3943 g_strfreev(tmp);
3944 } else {
3945 warning = g_strdup(_("You have been rejected by the server"));
3948 sip->gc->wants_to_die = TRUE;
3949 purple_connection_error(sip->gc, warning);
3950 g_free(warning);
3951 return TRUE;
3953 break;
3954 case 404:
3956 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3957 if (warning != NULL) {
3958 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3959 warning = g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason ? reason : _("no reason given"));
3960 g_free(reason);
3961 } else {
3962 warning = g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3965 sip->gc->wants_to_die = TRUE;
3966 purple_connection_error(sip->gc, warning);
3967 g_free(warning);
3968 return TRUE;
3970 break;
3971 case 503:
3973 gchar *warning = sipmsg_find_header(msg, "ms-diagnostics");
3974 if (warning != NULL) {
3975 gchar *reason = sipmsg_find_part_of_header(warning, "reason=\"", "\"", NULL);
3976 warning = g_strdup_printf(_("Service unavailable: %s"), reason ? reason : _("no reason given"));
3977 g_free(reason);
3978 } else {
3979 warning = g_strdup(_("Service unavailable: no reason given"));
3982 sip->gc->wants_to_die = TRUE;
3983 purple_connection_error(sip->gc, warning);
3984 g_free(warning);
3985 return TRUE;
3987 break;
3989 return TRUE;
3992 static void process_incoming_notify_rlmi(struct sipe_account_data *sip, const gchar *data, unsigned len)
3994 const char *uri;
3995 xmlnode *xn_categories;
3996 xmlnode *xn_category;
3997 xmlnode *xn_node;
3998 const char *activity = NULL;
4000 xn_categories = xmlnode_from_str(data, len);
4001 uri = xmlnode_get_attrib(xn_categories, "uri");
4003 for (xn_category = xmlnode_get_child(xn_categories, "category");
4004 xn_category ;
4005 xn_category = xmlnode_get_next_twin(xn_category) )
4007 const char *attrVar = xmlnode_get_attrib(xn_category, "name");
4009 if (!strcmp(attrVar, "note"))
4011 if (uri) {
4012 struct sipe_buddy *sbuddy = g_hash_table_lookup(sip->buddies, uri);
4014 if (sbuddy) {
4015 char *note;
4017 xn_node = xmlnode_get_child(xn_category, "note");
4018 if (!xn_node) continue;
4019 xn_node = xmlnode_get_child(xn_node, "body");
4020 if (!xn_node) continue;
4021 note = xmlnode_get_data(xn_node);
4022 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri,note ? note : "");
4023 g_free(sbuddy->annotation);
4024 sbuddy->annotation = NULL;
4025 if (note) sbuddy->annotation = g_strdup(note);
4026 g_free(note);
4031 else if(!strcmp(attrVar, "state"))
4033 char *data;
4034 int avail;
4035 xn_node = xmlnode_get_child(xn_category, "state");
4036 if (!xn_node) continue;
4037 xn_node = xmlnode_get_child(xn_node, "availability");
4038 if (!xn_node) continue;
4040 data = xmlnode_get_data(xn_node);
4041 avail = atoi(data);
4042 g_free(data);
4044 if (avail < 3000)
4045 activity = SIPE_STATUS_ID_UNKNOWN;
4046 else if (avail < 4500)
4047 activity = SIPE_STATUS_ID_AVAILABLE;
4048 else if (avail < 6000)
4049 activity = SIPE_STATUS_ID_BRB;
4050 else if (avail < 7500)
4051 activity = SIPE_STATUS_ID_ONPHONE;
4052 else if (avail < 9000)
4053 activity = SIPE_STATUS_ID_BUSY;
4054 else if (avail < 12000)
4055 activity = SIPE_STATUS_ID_DND;
4056 else if (avail < 18000)
4057 activity = SIPE_STATUS_ID_AWAY;
4058 else
4059 activity = SIPE_STATUS_ID_OFFLINE;
4062 if(activity) {
4063 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity);
4064 purple_prpl_got_user_status(sip->account, uri, activity, NULL);
4067 xmlnode_free(xn_categories);
4070 static void sipe_subscribe_poolfqdn_resource_uri(const char *host, GSList *server, struct sipe_account_data *sip)
4072 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4073 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host);
4074 payload->host = g_strdup(host);
4075 payload->buddies = server;
4076 sipe_subscribe_presence_batched_routed(sip, payload);
4077 sipe_subscribe_presence_batched_routed_free(payload);
4080 static void process_incoming_notify_rlmi_resub(struct sipe_account_data *sip, const gchar *data, unsigned len)
4082 xmlnode *xn_list;
4083 xmlnode *xn_resource;
4084 GHashTable *servers = g_hash_table_new_full(g_str_hash, g_str_equal,
4085 g_free, NULL);
4086 GSList *server;
4087 gchar *host;
4089 xn_list = xmlnode_from_str(data, len);
4091 for (xn_resource = xmlnode_get_child(xn_list, "resource");
4092 xn_resource;
4093 xn_resource = xmlnode_get_next_twin(xn_resource) )
4095 const char *uri, *state;
4096 xmlnode *xn_instance;
4098 xn_instance = xmlnode_get_child(xn_resource, "instance");
4099 if (!xn_instance) continue;
4101 uri = xmlnode_get_attrib(xn_resource, "uri");
4102 state = xmlnode_get_attrib(xn_instance, "state");
4103 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri, state);
4105 if (strstr(state, "resubscribe")) {
4106 const char *poolFqdn = xmlnode_get_attrib(xn_instance, "poolFqdn");
4107 struct sipe_buddy *sbuddy;
4108 if (poolFqdn) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4109 gchar *user = g_strdup(uri);
4110 host = g_strdup(poolFqdn);
4111 server = g_hash_table_lookup(servers, host);
4112 server = g_slist_append(server, user);
4113 g_hash_table_insert(servers, host, server);
4114 } else {
4115 sipe_subscribe_presence_single(sip, (void *) uri);
4117 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4118 if (sbuddy) {
4119 sbuddy->resubscribed = TRUE;
4124 /* Send out any deferred poolFqdn subscriptions */
4125 g_hash_table_foreach(servers, (GHFunc) sipe_subscribe_poolfqdn_resource_uri, sip);
4126 g_hash_table_destroy(servers);
4128 xmlnode_free(xn_list);
4131 static void process_incoming_notify_pidf(struct sipe_account_data *sip, const gchar *data, unsigned len)
4133 const gchar *uri;
4134 gchar *getbasic;
4135 gchar *activity = NULL;
4136 xmlnode *pidf;
4137 xmlnode *basicstatus = NULL, *tuple, *status;
4138 gboolean isonline = FALSE;
4139 xmlnode *display_name_node;
4141 pidf = xmlnode_from_str(data, len);
4142 if (!pidf) {
4143 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data);
4144 return;
4147 uri = xmlnode_get_attrib(pidf, "entity");
4149 if ((tuple = xmlnode_get_child(pidf, "tuple")))
4151 if ((status = xmlnode_get_child(tuple, "status"))) {
4152 basicstatus = xmlnode_get_child(status, "basic");
4156 if (!basicstatus) {
4157 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4158 xmlnode_free(pidf);
4159 return;
4162 getbasic = xmlnode_get_data(basicstatus);
4163 if (!getbasic) {
4164 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4165 xmlnode_free(pidf);
4166 return;
4169 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic);
4170 if (strstr(getbasic, "open")) {
4171 isonline = TRUE;
4173 g_free(getbasic);
4175 display_name_node = xmlnode_get_child(pidf, "display-name");
4176 // updating display name if alias was just URI
4177 if (display_name_node) {
4178 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4179 GSList *entry = buddies;
4180 PurpleBuddy *p_buddy;
4181 char * display_name = xmlnode_get_data(display_name_node);
4183 while (entry) {
4184 const char *server_alias;
4185 char *alias;
4187 p_buddy = entry->data;
4189 alias = (char *)purple_buddy_get_alias(p_buddy);
4190 alias = alias ? sip_uri_from_name(alias) : NULL;
4191 if (!alias || !g_ascii_strcasecmp(uri, alias)) {
4192 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4193 purple_blist_alias_buddy(p_buddy, display_name);
4195 g_free(alias);
4197 server_alias = purple_buddy_get_server_alias(p_buddy);
4198 if (display_name &&
4199 ( (server_alias && strcmp(display_name, server_alias))
4200 || !server_alias || strlen(server_alias) == 0 )
4202 purple_blist_server_alias_buddy(p_buddy, display_name);
4205 entry = entry->next;
4207 g_free(display_name);
4210 if ((tuple = xmlnode_get_child(pidf, "tuple"))) {
4211 if ((status = xmlnode_get_child(tuple, "status"))) {
4212 if ((basicstatus = xmlnode_get_child(status, "activities"))) {
4213 if ((basicstatus = xmlnode_get_child(basicstatus, "activity"))) {
4214 activity = xmlnode_get_data(basicstatus);
4215 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity);
4221 if (isonline) {
4222 const gchar * status_id = NULL;
4223 if (activity) {
4224 if (strstr(activity, "busy")) {
4225 status_id = SIPE_STATUS_ID_BUSY;
4226 } else if (strstr(activity, "away")) {
4227 status_id = SIPE_STATUS_ID_AWAY;
4231 if (!status_id) {
4232 status_id = SIPE_STATUS_ID_AVAILABLE;
4235 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id);
4236 purple_prpl_got_user_status(sip->account, uri, status_id, NULL);
4237 } else {
4238 purple_prpl_got_user_status(sip->account, uri, SIPE_STATUS_ID_OFFLINE, NULL);
4241 g_free(activity);
4242 xmlnode_free(pidf);
4245 static void process_incoming_notify_msrtc(struct sipe_account_data *sip, const gchar *data, unsigned len)
4247 const char *availability;
4248 const char *activity;
4249 const char *display_name = NULL;
4250 const char *activity_name = NULL;
4251 const char *name;
4252 char *uri;
4253 int avl;
4254 int act;
4255 struct sipe_buddy *sbuddy;
4257 xmlnode *xn_presentity = xmlnode_from_str(data, len);
4259 xmlnode *xn_availability = xmlnode_get_child(xn_presentity, "availability");
4260 xmlnode *xn_activity = xmlnode_get_child(xn_presentity, "activity");
4261 xmlnode *xn_display_name = xmlnode_get_child(xn_presentity, "displayName");
4262 xmlnode *xn_email = xmlnode_get_child(xn_presentity, "email");
4263 const char *email = xn_email ? xmlnode_get_attrib(xn_email, "email") : NULL;
4264 xmlnode *xn_userinfo = xmlnode_get_child(xn_presentity, "userInfo");
4265 xmlnode *xn_state = xn_userinfo ? xmlnode_get_descendant(xn_userinfo, "states", "state", NULL):NULL;
4266 const char *avail = xn_state ? xmlnode_get_attrib(xn_state, "avail") : NULL;
4268 xmlnode *xn_note = xn_userinfo ? xmlnode_get_child(xn_userinfo, "note") : NULL;
4269 char *note = xn_note ? xmlnode_get_data(xn_note) : NULL;
4270 xmlnode *xn_devices = xmlnode_get_child(xn_presentity, "devices");
4271 xmlnode *xn_device_presence = xn_devices ? xmlnode_get_child(xn_devices, "devicePresence") : NULL;
4272 xmlnode *xn_device_name = xn_device_presence ? xmlnode_get_child(xn_device_presence, "deviceName") : NULL;
4273 const char *device_name = xn_device_name ? xmlnode_get_attrib(xn_device_name, "name") : NULL;
4275 name = xmlnode_get_attrib(xn_presentity, "uri");
4276 uri = sip_uri_from_name(name);
4277 availability = xmlnode_get_attrib(xn_availability, "aggregate");
4278 activity = xmlnode_get_attrib(xn_activity, "aggregate");
4280 // updating display name if alias was just URI
4281 if (xn_display_name) {
4282 GSList *buddies = purple_find_buddies(sip->account, uri); //all buddies in different groups
4283 GSList *entry = buddies;
4284 PurpleBuddy *p_buddy;
4285 display_name = xmlnode_get_attrib(xn_display_name, "displayName");
4287 while (entry) {
4288 const char *email_str, *server_alias;
4290 p_buddy = entry->data;
4292 if (!g_ascii_strcasecmp(name, purple_buddy_get_alias(p_buddy))) {
4293 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri, display_name);
4294 purple_blist_alias_buddy(p_buddy, display_name);
4297 server_alias = purple_buddy_get_server_alias(p_buddy);
4298 if (display_name &&
4299 ( (server_alias && strcmp(display_name, server_alias))
4300 || !server_alias || strlen(server_alias) == 0 )
4302 purple_blist_server_alias_buddy(p_buddy, display_name);
4305 if (email) {
4306 email_str = purple_blist_node_get_string((PurpleBlistNode *)p_buddy, "email");
4307 if (!email_str || g_ascii_strcasecmp(email_str, email)) {
4308 purple_blist_node_set_string((PurpleBlistNode *)p_buddy, "email", email);
4312 entry = entry->next;
4316 avl = atoi(availability);
4317 act = atoi(activity);
4319 if(sip->msrtc_event_categories){
4320 if (act == 100 && avl == 0)
4321 activity_name = SIPE_STATUS_ID_OFFLINE;
4322 else if (act == 100 && avl == 300)
4323 activity_name = SIPE_STATUS_ID_AWAY;
4324 else if (act == 300 && avl == 300)
4325 activity_name = SIPE_STATUS_ID_BRB;
4326 else if (act == 400 && avl == 300)
4327 activity_name = SIPE_STATUS_ID_AVAILABLE;
4328 else if (act == 500 && act == 300)
4329 activity_name = SIPE_STATUS_ID_ONPHONE;
4330 else if (act == 600 && avl == 300)
4331 activity_name = SIPE_STATUS_ID_BUSY;
4332 else if (act == 0 && avl == 0){ //MSRTC elements are zero
4333 if(avail){ //Check for LegacyInterop elements
4334 avl = atoi(avail);
4335 if(avl == 18500)
4336 activity_name = SIPE_STATUS_ID_OFFLINE;
4337 else if (avl == 3500)
4338 activity_name = SIPE_STATUS_ID_AVAILABLE;
4339 else if (avl == 15500)
4340 activity_name = SIPE_STATUS_ID_AWAY;
4341 else if (avl == 6500)
4342 activity_name = SIPE_STATUS_ID_BUSY;
4343 else if (avl == 12500)
4344 activity_name = SIPE_STATUS_ID_BRB;
4349 if(activity_name == NULL){
4350 if (act <= 100)
4351 activity_name = SIPE_STATUS_ID_AWAY;
4352 else if (act <= 150)
4353 activity_name = SIPE_STATUS_ID_LUNCH;
4354 else if (act <= 300)
4355 activity_name = SIPE_STATUS_ID_BRB;
4356 else if (act <= 400)
4357 activity_name = SIPE_STATUS_ID_AVAILABLE;
4358 else if (act <= 500)
4359 activity_name = SIPE_STATUS_ID_ONPHONE;
4360 else if (act <= 600)
4361 activity_name = SIPE_STATUS_ID_BUSY;
4362 else
4363 activity_name = SIPE_STATUS_ID_AVAILABLE;
4365 if (avl == 0)
4366 activity_name = SIPE_STATUS_ID_OFFLINE;
4369 sbuddy = g_hash_table_lookup(sip->buddies, uri);
4370 if (sbuddy)
4372 if (sbuddy->annotation) { g_free(sbuddy->annotation); }
4373 sbuddy->annotation = NULL;
4374 if (note) { sbuddy->annotation = g_strdup(note); }
4376 if (sbuddy->device_name) { g_free(sbuddy->device_name); }
4377 sbuddy->device_name = NULL;
4378 if (device_name) { sbuddy->device_name = g_strdup(device_name); }
4381 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name);
4382 purple_prpl_got_user_status(sip->account, uri, activity_name, NULL);
4383 g_free(note);
4384 xmlnode_free(xn_presentity);
4385 g_free(uri);
4388 static void sipe_process_presence(struct sipe_account_data *sip, struct sipmsg *msg)
4390 char *ctype = sipmsg_find_header(msg, "Content-Type");
4392 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype ? ctype : "");
4394 if ( ctype && ( strstr(ctype, "application/rlmi+xml")
4395 || strstr(ctype, "application/msrtc-event-categories+xml") ) )
4397 const char *content = msg->body;
4398 unsigned length = msg->bodylen;
4399 PurpleMimeDocument *mime = NULL;
4401 if (strstr(ctype, "multipart"))
4403 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4404 const char *content_type;
4405 GList* parts;
4406 mime = purple_mime_document_parse(doc);
4407 parts = purple_mime_document_get_parts(mime);
4408 while(parts) {
4409 content = purple_mime_part_get_data(parts->data);
4410 length = purple_mime_part_get_length(parts->data);
4411 content_type =purple_mime_part_get_field(parts->data,"Content-Type");
4412 if(content_type && strstr(content_type,"application/rlmi+xml"))
4414 process_incoming_notify_rlmi_resub(sip, content, length);
4416 else if(content_type && strstr(content_type, "text/xml+msrtc.pidf"))
4418 process_incoming_notify_msrtc(sip, content, length);
4420 else
4422 process_incoming_notify_rlmi(sip, content, length);
4424 parts = parts->next;
4426 g_free(doc);
4428 if (mime)
4430 purple_mime_document_free(mime);
4433 else if(strstr(ctype, "application/msrtc-event-categories+xml") )
4435 process_incoming_notify_rlmi(sip, msg->body, msg->bodylen);
4437 else if(strstr(ctype, "application/rlmi+xml"))
4439 process_incoming_notify_rlmi_resub(sip, msg->body, msg->bodylen);
4442 else if(ctype && strstr(ctype, "text/xml+msrtc.pidf"))
4444 process_incoming_notify_msrtc(sip, msg->body, msg->bodylen);
4446 else
4448 process_incoming_notify_pidf(sip, msg->body, msg->bodylen);
4452 static void sipe_process_presence_timeout(struct sipe_account_data *sip, struct sipmsg *msg, gchar *who, int timeout)
4454 char *ctype = sipmsg_find_header(msg, "Content-Type");
4455 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4457 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype ? ctype : "");
4459 if (ctype &&
4460 strstr(ctype, "multipart") &&
4461 (strstr(ctype, "application/rlmi+xml") ||
4462 strstr(ctype, "application/msrtc-event-categories+xml"))) {
4463 char *doc = g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype, msg->body);
4464 PurpleMimeDocument *mime = purple_mime_document_parse(doc);
4465 GList *parts = purple_mime_document_get_parts(mime);
4466 GSList *buddies = NULL;
4467 struct presence_batched_routed *payload = g_malloc(sizeof(struct presence_batched_routed));
4469 while (parts) {
4470 xmlnode *xml = xmlnode_from_str(purple_mime_part_get_data(parts->data),
4471 purple_mime_part_get_length(parts->data));
4472 gchar *uri = sip_uri(xmlnode_get_attrib(xml, "uri"));
4474 buddies = g_slist_append(buddies, uri);
4475 xmlnode_free(xml);
4477 parts = parts->next;
4479 g_free(doc);
4480 if (mime) purple_mime_document_free(mime);
4482 payload->host = who;
4483 payload->buddies = buddies;
4484 sipe_schedule_action(action_name, timeout,
4485 sipe_subscribe_presence_batched_routed,
4486 sipe_subscribe_presence_batched_routed_free,
4487 sip, payload);
4488 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who, timeout);
4490 } else {
4491 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, NULL, sip, who);
4492 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who, timeout);
4494 g_free(action_name);
4498 * Dispatcher for all incoming subscription information
4499 * whether it comes from NOTIFY, BENOTIFY requests or
4500 * piggy-backed to subscription's OK responce.
4502 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4503 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4505 static void process_incoming_notify(struct sipe_account_data *sip, struct sipmsg *msg, gboolean request, gboolean benotify)
4507 gchar *content_type = sipmsg_find_header(msg, "Content-Type");
4508 gchar *event = sipmsg_find_header(msg, "Event");
4509 gchar *subscription_state = sipmsg_find_header(msg, "subscription-state");
4510 int timeout = 0;
4512 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event ? event : "", msg->body);
4513 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n\n", subscription_state ? subscription_state : "");
4515 /* implicit subscriptions */
4516 if (content_type && purple_str_has_prefix(content_type, "application/ms-imdn+xml")) {
4517 sipe_process_imdn(sip, msg);
4520 if (!request)
4522 const gchar *expires_header;
4523 expires_header = sipmsg_find_header(msg, "Expires");
4524 timeout = expires_header ? strtol(expires_header, NULL, 10) : 0;
4525 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout);
4526 timeout = (timeout - 60) > 60 ? (timeout - 60) : timeout; // 1 min ahead of expiration
4529 /* for one off subscriptions (send with Expire: 0) */
4530 if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-provisioning-v2"))
4532 sipe_process_provisioning_v2(sip, msg);
4535 if (!subscription_state || strstr(subscription_state, "active"))
4537 if (event && !g_ascii_strcasecmp(event, "presence"))
4539 sipe_process_presence(sip, msg);
4541 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts"))
4543 sipe_process_roaming_contacts(sip, msg, NULL);
4545 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-self"))
4547 sipe_process_roaming_self(sip, msg);
4549 else if (event && !g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL"))
4551 sipe_process_roaming_acl(sip, msg);
4553 else if (event && !g_ascii_strcasecmp(event, "presence.wpending"))
4555 sipe_process_presence_wpending(sip, msg);
4557 else if (event && !g_ascii_strcasecmp(event, "conference"))
4559 sipe_process_conference(sip, msg);
4563 //The server sends a (BE)NOTIFY with the status 'terminated'
4564 if (request && subscription_state && strstr(subscription_state, "terminated") ) {
4565 gchar *from = parse_from(sipmsg_find_header(msg, "From"));
4566 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from);
4567 g_free(from);
4570 if (timeout && event) {// For LSC 2005 and OCS 2007
4571 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4572 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4574 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4575 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4576 g_free(action_name);
4578 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4579 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4581 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4582 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4583 g_free(action_name);
4585 else*/
4586 if (!g_ascii_strcasecmp(event, "presence.wpending") &&
4587 g_slist_find_custom(sip->allow_events, "presence.wpending", (GCompareFunc)g_ascii_strcasecmp))
4589 gchar *action_name = g_strdup_printf("<%s>", "presence.wpending");
4590 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_wpending, NULL, sip, NULL);
4591 g_free(action_name);
4593 else if (!g_ascii_strcasecmp(event, "presence") &&
4594 g_slist_find_custom(sip->allow_events, "presence", (GCompareFunc)g_ascii_strcasecmp))
4596 gchar *who = parse_from(sipmsg_find_header(msg, request ? "From" : "To"));
4597 gchar *action_name = g_strdup_printf(ACTION_NAME_PRESENCE, who);
4598 if(sip->batched_support) {
4599 gchar *my_self = sip_uri_self(sip);
4600 if(!g_ascii_strcasecmp(who, my_self)){
4601 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_batched, NULL, sip, NULL);
4602 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout);
4603 g_free(who); /* unused */
4605 else {
4606 sipe_process_presence_timeout(sip, msg, who, timeout);
4608 g_free(my_self);
4610 else {
4611 sipe_schedule_action(action_name, timeout, sipe_subscribe_presence_single, g_free, sip, who);
4612 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who,timeout);
4614 g_free(action_name);
4615 /* "who" will be freed by the action we just scheduled */
4619 if (event && !g_ascii_strcasecmp(event, "registration-notify"))
4621 sipe_process_registration_notify(sip, msg);
4624 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4625 if (request && !benotify)
4627 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4632 * unused. Needed?
4634 static gchar* gen_xpidf(struct sipe_account_data *sip)
4636 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4637 "<presence>\r\n"
4638 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4639 "<display name=\"sip:%s\"/>\r\n"
4640 "<atom id=\"1234\">\r\n"
4641 "<address uri=\"sip:%s\">\r\n"
4642 "<status status=\"%s\"/>\r\n"
4643 "</address>\r\n"
4644 "</atom>\r\n"
4645 "</presence>\r\n",
4646 sip->username,
4647 sip->username,
4648 sip->username,
4649 sip->status);
4650 return doc;
4655 static gchar* gen_pidf(struct sipe_account_data *sip)
4657 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4658 "<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"
4659 "<tuple id=\"0\">\r\n"
4660 "<status>\r\n"
4661 "<basic>open</basic>\r\n"
4662 "<ep:activities>\r\n"
4663 " <ep:activity>%s</ep:activity>\r\n"
4664 "</ep:activities>"
4665 "</status>\r\n"
4666 "</tuple>\r\n"
4667 "<ci:display-name>%s</ci:display-name>\r\n"
4668 "</presence>",
4669 sip->username,
4670 sip->status,
4671 sip->username);
4672 return doc;
4676 static void send_presence_soap(struct sipe_account_data *sip, const char * note)
4678 int availability = 300; // online
4679 int activity = 400; // Available
4680 gchar *name;
4681 gchar *body;
4682 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY)) {
4683 activity = 100;
4684 } else if (!strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4685 activity = 150;
4686 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4687 activity = 300;
4688 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4689 activity = 400;
4690 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4691 activity = 500;
4692 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4693 activity = 600;
4694 } else if (!strcmp(sip->status, SIPE_STATUS_ID_INVISIBLE) ||
4695 !strcmp(sip->status, SIPE_STATUS_ID_OFFLINE)) {
4696 availability = 0; // offline
4697 activity = 100;
4698 } else {
4699 activity = 400; // available
4702 name = g_strdup_printf("sip: sip:%s", sip->username);
4703 //@TODO: send user data - state; add hostname in upper case
4704 body = g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE, name, availability, activity, note ? note : "");
4705 send_soap_request_with_cb(sip, body, NULL , NULL);
4706 g_free(name);
4707 g_free(body);
4710 static gboolean
4711 process_clear_presence_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4713 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4714 if (msg->response == 200) {
4715 sip->status_version = 0;
4716 send_presence_status(sip);
4718 return TRUE;
4721 static gboolean
4722 process_send_presence_category_publish_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *tc)
4724 if (msg->response == 409) {
4725 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4726 // TODO need to parse the version #'s?
4727 gchar *uri = sip_uri_self(sip);
4728 gchar *doc = g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE, uri);
4729 gchar *tmp;
4730 gchar *hdr;
4732 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg->body);
4734 tmp = get_contact(sip);
4735 hdr = g_strdup_printf("Contact: %s\r\n"
4736 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4738 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_clear_presence_response);
4740 g_free(tmp);
4741 g_free(hdr);
4742 g_free(uri);
4743 g_free(doc);
4745 return TRUE;
4748 static void send_presence_category_publish(struct sipe_account_data *sip, const char * note)
4750 int code;
4751 gchar *uri;
4752 gchar *doc;
4753 gchar *tmp;
4754 gchar *hdr;
4755 if (!strcmp(sip->status, SIPE_STATUS_ID_AWAY) ||
4756 !strcmp(sip->status, SIPE_STATUS_ID_LUNCH)) {
4757 code = 12000;
4758 } else if (!strcmp(sip->status, SIPE_STATUS_ID_DND)) {
4759 code = 9000;
4760 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BUSY)) {
4761 code = 7500;
4762 } else if (!strcmp(sip->status, SIPE_STATUS_ID_ONPHONE)) {
4763 code = 6000;
4764 } else if (!strcmp(sip->status, SIPE_STATUS_ID_BRB)) {
4765 code = 4500;
4766 } else if (!strcmp(sip->status, SIPE_STATUS_ID_AVAILABLE)) {
4767 code = 3000;
4768 } else if (!strcmp(sip->status, SIPE_STATUS_ID_UNKNOWN)) {
4769 code = 0;
4770 } else {
4771 // Offline or invisible
4772 code = 18000;
4775 uri = sip_uri_self(sip);
4776 doc = g_strdup_printf(SIPE_SEND_PRESENCE, uri,
4777 sip->status_version, code,
4778 sip->status_version, code,
4779 sip->status_version, note ? note : "",
4780 sip->status_version, note ? note : "",
4781 sip->status_version, note ? note : ""
4783 sip->status_version++;
4785 tmp = get_contact(sip);
4786 hdr = g_strdup_printf("Contact: %s\r\n"
4787 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp);
4789 send_sip_request(sip->gc, "SERVICE", uri, uri, hdr, doc, NULL, process_send_presence_category_publish_response);
4791 g_free(tmp);
4792 g_free(hdr);
4793 g_free(uri);
4794 g_free(doc);
4797 static void send_presence_status(struct sipe_account_data *sip)
4799 PurpleStatus * status = purple_account_get_active_status(sip->account);
4800 const gchar *note;
4801 if (!status) return;
4803 note = purple_status_get_attr_string(status, "message");
4805 if(sip->msrtc_event_categories){
4806 send_presence_category_publish(sip, note);
4807 } else {
4808 send_presence_soap(sip, note);
4812 static void process_input_message(struct sipe_account_data *sip,struct sipmsg *msg)
4814 gboolean found = FALSE;
4815 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg->response,msg->method);
4816 if (msg->response == 0) { /* request */
4817 if (!strcmp(msg->method, "MESSAGE")) {
4818 process_incoming_message(sip, msg);
4819 found = TRUE;
4820 } else if (!strcmp(msg->method, "NOTIFY")) {
4821 purple_debug_info("sipe","send->process_incoming_notify\n");
4822 process_incoming_notify(sip, msg, TRUE, FALSE);
4823 found = TRUE;
4824 } else if (!strcmp(msg->method, "BENOTIFY")) {
4825 purple_debug_info("sipe","send->process_incoming_benotify\n");
4826 process_incoming_notify(sip, msg, TRUE, TRUE);
4827 found = TRUE;
4828 } else if (!strcmp(msg->method, "INVITE")) {
4829 process_incoming_invite(sip, msg);
4830 found = TRUE;
4831 } else if (!strcmp(msg->method, "REFER")) {
4832 process_incoming_refer(sip, msg);
4833 found = TRUE;
4834 } else if (!strcmp(msg->method, "OPTIONS")) {
4835 process_incoming_options(sip, msg);
4836 found = TRUE;
4837 } else if (!strcmp(msg->method, "INFO")) {
4838 process_incoming_info(sip, msg);
4839 found = TRUE;
4840 } else if (!strcmp(msg->method, "ACK")) {
4841 // ACK's don't need any response
4842 found = TRUE;
4843 } else if (!strcmp(msg->method, "SUBSCRIBE")) {
4844 // LCS 2005 sends us these - just respond 200 OK
4845 found = TRUE;
4846 send_sip_response(sip->gc, msg, 200, "OK", NULL);
4847 } else if (!strcmp(msg->method, "BYE")) {
4848 process_incoming_bye(sip, msg);
4849 found = TRUE;
4850 } else {
4851 send_sip_response(sip->gc, msg, 501, "Not implemented", NULL);
4853 } else { /* response */
4854 struct transaction *trans = transactions_find(sip, msg);
4855 if (trans) {
4856 if (msg->response == 407) {
4857 gchar *resend, *auth, *ptmp;
4859 if (sip->proxy.retries > 30) return;
4860 sip->proxy.retries++;
4861 /* do proxy authentication */
4863 ptmp = sipmsg_find_header(msg, "Proxy-Authenticate");
4865 fill_auth(sip, ptmp, &sip->proxy);
4866 auth = auth_header(sip, &sip->proxy, trans->msg);
4867 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4868 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4869 g_free(auth);
4870 resend = sipmsg_to_string(trans->msg);
4871 /* resend request */
4872 sendout_pkt(sip->gc, resend);
4873 g_free(resend);
4874 } else {
4875 if (msg->response == 100 || msg->response == 180) {
4876 /* ignore provisional response */
4877 purple_debug_info("sipe", "got trying (%d) response\n", msg->response);
4878 } else {
4879 sip->proxy.retries = 0;
4880 if (!strcmp(trans->msg->method, "REGISTER")) {
4881 if (msg->response == 401)
4883 sip->registrar.retries++;
4885 else
4887 sip->registrar.retries = 0;
4889 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip->cseq);
4890 } else {
4891 if (msg->response == 401) {
4892 gchar *resend, *auth, *ptmp;
4894 if (sip->registrar.retries > 4) return;
4895 sip->registrar.retries++;
4897 #ifdef USE_KERBEROS
4898 if (!purple_account_get_bool(sip->account, "krb5", FALSE)) {
4899 #endif
4900 ptmp = sipmsg_find_auth_header(msg, "NTLM");
4901 #ifdef USE_KERBEROS
4902 } else {
4903 ptmp = sipmsg_find_auth_header(msg, "Kerberos");
4905 #endif
4907 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - Auth header: %s\r\n", ptmp);
4909 fill_auth(sip, ptmp, &sip->registrar);
4910 auth = auth_header(sip, &sip->registrar, trans->msg);
4911 sipmsg_remove_header_now(trans->msg, "Proxy-Authorization");
4912 sipmsg_add_header_now_pos(trans->msg, "Proxy-Authorization", auth, 5);
4914 //sipmsg_remove_header_now(trans->msg, "Authorization");
4915 //sipmsg_add_header(trans->msg, "Authorization", auth);
4916 g_free(auth);
4917 resend = sipmsg_to_string(trans->msg);
4918 /* resend request */
4919 sendout_pkt(sip->gc, resend);
4920 g_free(resend);
4924 if (trans->callback) {
4925 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - we have a transaction callback\r\n");
4926 /* call the callback to process response*/
4927 (trans->callback)(sip, msg, trans);
4929 /* Not sure if this is needed or what needs to be done
4930 but transactions seem to be removed prematurely so
4931 this only removes them if the response is 200 OK */
4932 purple_debug(PURPLE_DEBUG_MISC, "sipe", "process_input_message - removing CSeq %d\r\n", sip->cseq);
4933 /*Has a bug and it's unneccesary*/
4934 /*transactions_remove(sip, trans);*/
4938 found = TRUE;
4939 } else {
4940 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received response to unknown transaction\n");
4943 if (!found) {
4944 purple_debug(PURPLE_DEBUG_MISC, "sipe", "received a unknown sip message with method %s and response %d\n", msg->method, msg->response);
4948 static void process_input(struct sipe_account_data *sip, struct sip_connection *conn)
4950 char *cur;
4951 char *dummy;
4952 struct sipmsg *msg;
4953 int restlen;
4954 cur = conn->inbuf;
4956 /* according to the RFC remove CRLF at the beginning */
4957 while (*cur == '\r' || *cur == '\n') {
4958 cur++;
4960 if (cur != conn->inbuf) {
4961 memmove(conn->inbuf, cur, conn->inbufused - (cur - conn->inbuf));
4962 conn->inbufused = strlen(conn->inbuf);
4965 /* Received a full Header? */
4966 sip->processing_input = TRUE;
4967 while (sip->processing_input &&
4968 ((cur = strstr(conn->inbuf, "\r\n\r\n")) != NULL)) {
4969 time_t currtime = time(NULL);
4970 cur += 2;
4971 cur[0] = '\0';
4972 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), conn->inbuf);
4973 msg = sipmsg_parse_header(conn->inbuf);
4974 cur[0] = '\r';
4975 cur += 2;
4976 restlen = conn->inbufused - (cur - conn->inbuf);
4977 if (restlen >= msg->bodylen) {
4978 dummy = g_malloc(msg->bodylen + 1);
4979 memcpy(dummy, cur, msg->bodylen);
4980 dummy[msg->bodylen] = '\0';
4981 msg->body = dummy;
4982 cur += msg->bodylen;
4983 memmove(conn->inbuf, cur, conn->inbuflen - (cur - conn->inbuf));
4984 conn->inbufused = strlen(conn->inbuf);
4985 } else {
4986 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4987 restlen, msg->bodylen, (int)strlen(conn->inbuf));
4988 sipmsg_free(msg);
4989 return;
4992 /*if (msg->body) {
4993 purple_debug_info("sipe", "body:\n%s", msg->body);
4996 // Verify the signature before processing it
4997 if (sip->registrar.gssapi_context) {
4998 struct sipmsg_breakdown msgbd;
4999 gchar *signature_input_str;
5000 gchar *rspauth;
5001 msgbd.msg = msg;
5002 sipmsg_breakdown_parse(&msgbd, sip->registrar.realm, sip->registrar.target);
5003 signature_input_str = sipmsg_breakdown_get_string(&msgbd);
5005 rspauth = sipmsg_find_part_of_header(sipmsg_find_header(msg, "Authentication-Info"), "rspauth=\"", "\"", NULL);
5007 if (rspauth != NULL) {
5008 if (!sip_sec_verify_signature(sip->registrar.gssapi_context, signature_input_str, rspauth)) {
5009 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature validated\n");
5010 process_input_message(sip, msg);
5011 } else {
5012 purple_debug(PURPLE_DEBUG_MISC, "sipe", "incoming message's signature is invalid.\n");
5013 purple_connection_error(sip->gc, _("Invalid message signature received"));
5014 sip->gc->wants_to_die = TRUE;
5016 } else if (msg->response == 401) {
5017 purple_connection_error(sip->gc, _("Wrong Password"));
5018 sip->gc->wants_to_die = TRUE;
5020 g_free(signature_input_str);
5022 g_free(rspauth);
5023 sipmsg_breakdown_free(&msgbd);
5024 } else {
5025 process_input_message(sip, msg);
5028 sipmsg_free(msg);
5032 static void sipe_udp_process(gpointer data, gint source, PurpleInputCondition con)
5034 PurpleConnection *gc = data;
5035 struct sipe_account_data *sip = gc->proto_data;
5036 struct sipmsg *msg;
5037 int len;
5038 time_t currtime;
5040 static char buffer[65536];
5041 if ((len = recv(source, buffer, sizeof(buffer) - 1, 0)) > 0) {
5042 buffer[len] = '\0';
5043 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime), buffer);
5044 msg = sipmsg_parse_msg(buffer);
5045 if (msg) process_input_message(sip, msg);
5049 static void sipe_invalidate_ssl_connection(PurpleConnection *gc, const char *msg, const char *debug)
5051 struct sipe_account_data *sip = gc->proto_data;
5052 PurpleSslConnection *gsc = sip->gsc;
5054 purple_debug_error("sipe", "%s",debug);
5055 purple_connection_error(gc, msg);
5057 /* Invalidate this connection. Next send will open a new one */
5058 if (gsc) {
5059 connection_remove(sip, gsc->fd);
5060 purple_ssl_close(gsc);
5062 sip->gsc = NULL;
5063 sip->fd = -1;
5066 static void sipe_input_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5068 PurpleConnection *gc = data;
5069 struct sipe_account_data *sip;
5070 struct sip_connection *conn;
5071 int readlen, len;
5072 gboolean firstread = TRUE;
5074 /* NOTE: This check *IS* necessary */
5075 if (!PURPLE_CONNECTION_IS_VALID(gc)) {
5076 purple_ssl_close(gsc);
5077 return;
5080 sip = gc->proto_data;
5081 conn = connection_find(sip, gsc->fd);
5082 if (conn == NULL) {
5083 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5084 gc->wants_to_die = TRUE;
5085 purple_connection_error(gc, _("Connection not found; Please try to connect again.\n"));
5086 return;
5089 /* Read all available data from the SSL connection */
5090 do {
5091 /* Increase input buffer size as needed */
5092 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5093 conn->inbuflen += SIMPLE_BUF_INC;
5094 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5095 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn->inbuflen);
5098 /* Try to read as much as there is space left in the buffer */
5099 readlen = conn->inbuflen - conn->inbufused - 1;
5100 len = purple_ssl_read(gsc, conn->inbuf + conn->inbufused, readlen);
5102 if (len < 0 && errno == EAGAIN) {
5103 /* Try again later */
5104 return;
5105 } else if (len < 0) {
5106 sipe_invalidate_ssl_connection(gc, _("SSL read error"), "SSL read error\n");
5107 return;
5108 } else if (firstread && (len == 0)) {
5109 sipe_invalidate_ssl_connection(gc, _("Server has disconnected"), "Server has disconnected\n");
5110 return;
5113 conn->inbufused += len;
5114 firstread = FALSE;
5116 /* Equivalence indicates that there is possibly more data to read */
5117 } while (len == readlen);
5119 conn->inbuf[conn->inbufused] = '\0';
5120 process_input(sip, conn);
5124 static void sipe_input_cb(gpointer data, gint source, PurpleInputCondition cond)
5126 PurpleConnection *gc = data;
5127 struct sipe_account_data *sip = gc->proto_data;
5128 int len;
5129 struct sip_connection *conn = connection_find(sip, source);
5130 if (!conn) {
5131 purple_debug_error("sipe", "Connection not found!\n");
5132 return;
5135 if (conn->inbuflen < conn->inbufused + SIMPLE_BUF_INC) {
5136 conn->inbuflen += SIMPLE_BUF_INC;
5137 conn->inbuf = g_realloc(conn->inbuf, conn->inbuflen);
5140 len = read(source, conn->inbuf + conn->inbufused, SIMPLE_BUF_INC - 1);
5142 if (len < 0 && errno == EAGAIN)
5143 return;
5144 else if (len <= 0) {
5145 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5146 connection_remove(sip, source);
5147 if (sip->fd == source) sip->fd = -1;
5148 return;
5151 conn->inbufused += len;
5152 conn->inbuf[conn->inbufused] = '\0';
5154 process_input(sip, conn);
5157 /* Callback for new connections on incoming TCP port */
5158 static void sipe_newconn_cb(gpointer data, gint source, PurpleInputCondition cond)
5160 PurpleConnection *gc = data;
5161 struct sipe_account_data *sip = gc->proto_data;
5162 struct sip_connection *conn;
5164 int newfd = accept(source, NULL, NULL);
5166 conn = connection_create(sip, newfd);
5168 conn->inputhandler = purple_input_add(newfd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5171 static void login_cb(gpointer data, gint source, const gchar *error_message)
5173 PurpleConnection *gc = data;
5174 struct sipe_account_data *sip;
5175 struct sip_connection *conn;
5177 if (!PURPLE_CONNECTION_IS_VALID(gc))
5179 if (source >= 0)
5180 close(source);
5181 return;
5184 if (source < 0) {
5185 purple_connection_error(gc, _("Could not connect"));
5186 return;
5189 sip = gc->proto_data;
5190 sip->fd = source;
5191 sip->last_keepalive = time(NULL);
5193 conn = connection_create(sip, source);
5195 do_register(sip);
5197 conn->inputhandler = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_input_cb, gc);
5200 static void login_cb_ssl(gpointer data, PurpleSslConnection *gsc, PurpleInputCondition cond)
5202 struct sipe_account_data *sip = sipe_setup_ssl(data, gsc);
5203 if (sip == NULL) return;
5205 do_register(sip);
5208 static guint sipe_ht_hash_nick(const char *nick)
5210 char *lc = g_utf8_strdown(nick, -1);
5211 guint bucket = g_str_hash(lc);
5212 g_free(lc);
5214 return bucket;
5217 static gboolean sipe_ht_equals_nick(const char *nick1, const char *nick2)
5219 return (purple_utf8_strcasecmp(nick1, nick2) == 0);
5222 static void sipe_udp_host_resolved_listen_cb(int listenfd, gpointer data)
5224 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5226 sip->listen_data = NULL;
5228 if (listenfd == -1) {
5229 purple_connection_error(sip->gc, _("Could not create listen socket"));
5230 return;
5233 sip->fd = listenfd;
5235 sip->listenport = purple_network_get_port_from_fd(sip->fd);
5236 sip->listenfd = sip->fd;
5238 sip->listenpa = purple_input_add(sip->fd, PURPLE_INPUT_READ, sipe_udp_process, sip->gc);
5240 sip->resendtimeout = purple_timeout_add(2500, (GSourceFunc) resend_timeout, sip);
5241 do_register(sip);
5244 static void sipe_udp_host_resolved(GSList *hosts, gpointer data, const char *error_message)
5246 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5248 sip->query_data = NULL;
5250 if (!hosts || !hosts->data) {
5251 purple_connection_error(sip->gc, _("Couldn't resolve host"));
5252 return;
5255 hosts = g_slist_remove(hosts, hosts->data);
5256 g_free(sip->serveraddr);
5257 sip->serveraddr = hosts->data;
5258 hosts = g_slist_remove(hosts, hosts->data);
5259 while (hosts) {
5260 hosts = g_slist_remove(hosts, hosts->data);
5261 g_free(hosts->data);
5262 hosts = g_slist_remove(hosts, hosts->data);
5265 /* create socket for incoming connections */
5266 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_DGRAM,
5267 sipe_udp_host_resolved_listen_cb, sip);
5268 if (sip->listen_data == NULL) {
5269 purple_connection_error(sip->gc, _("Could not create listen socket"));
5270 return;
5274 static void sipe_ssl_connect_failure(PurpleSslConnection *gsc, PurpleSslErrorType error,
5275 gpointer data)
5277 PurpleConnection *gc = data;
5278 struct sipe_account_data *sip;
5280 /* If the connection is already disconnected, we don't need to do anything else */
5281 if (!PURPLE_CONNECTION_IS_VALID(gc))
5282 return;
5284 sip = gc->proto_data;
5285 sip->fd = -1;
5286 sip->gsc = NULL;
5288 switch(error) {
5289 case PURPLE_SSL_CONNECT_FAILED:
5290 purple_connection_error(gc, _("Connection Failed"));
5291 break;
5292 case PURPLE_SSL_HANDSHAKE_FAILED:
5293 purple_connection_error(gc, _("SSL Handshake Failed"));
5294 break;
5295 case PURPLE_SSL_CERTIFICATE_INVALID:
5296 purple_connection_error(gc, _("SSL Certificate Invalid"));
5297 break;
5301 static void
5302 sipe_tcp_connect_listen_cb(int listenfd, gpointer data)
5304 struct sipe_account_data *sip = (struct sipe_account_data*) data;
5305 PurpleProxyConnectData *connect_data;
5307 sip->listen_data = NULL;
5309 sip->listenfd = listenfd;
5310 if (sip->listenfd == -1) {
5311 purple_connection_error(sip->gc, _("Could not create listen socket"));
5312 return;
5315 purple_debug_info("sipe", "listenfd: %d\n", sip->listenfd);
5316 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5317 sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5318 sip->listenpa = purple_input_add(sip->listenfd, PURPLE_INPUT_READ,
5319 sipe_newconn_cb, sip->gc);
5320 purple_debug_info("sipe", "connecting to %s port %d\n",
5321 sip->realhostname, sip->realport);
5322 /* open tcp connection to the server */
5323 connect_data = purple_proxy_connect(sip->gc, sip->account, sip->realhostname,
5324 sip->realport, login_cb, sip->gc);
5326 if (connect_data == NULL) {
5327 purple_connection_error(sip->gc, _("Couldn't create socket"));
5332 static void create_connection(struct sipe_account_data *sip, gchar *hostname, int port)
5334 PurpleAccount *account = sip->account;
5335 PurpleConnection *gc = sip->gc;
5337 if (purple_account_get_bool(account, "useport", FALSE)) {
5338 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - using specified SIP port\n");
5339 port = purple_account_get_int(account, "port", 0);
5340 } else {
5341 port = port ? port : (sip->transport == SIPE_TRANSPORT_TLS) ? 5061 : 5060;
5344 sip->realhostname = hostname;
5345 sip->realport = port;
5347 purple_debug(PURPLE_DEBUG_MISC, "sipe", "create_connection - hostname: %s port: %d\n",
5348 hostname, port);
5350 /* TODO: is there a good default grow size? */
5351 if (sip->transport != SIPE_TRANSPORT_UDP)
5352 sip->txbuf = purple_circ_buffer_new(0);
5354 if (sip->transport == SIPE_TRANSPORT_TLS) {
5355 /* SSL case */
5356 if (!purple_ssl_is_supported()) {
5357 gc->wants_to_die = TRUE;
5358 purple_connection_error(gc, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5359 return;
5362 purple_debug_info("sipe", "using SSL\n");
5364 sip->gsc = purple_ssl_connect(account, hostname, port,
5365 login_cb_ssl, sipe_ssl_connect_failure, gc);
5366 if (sip->gsc == NULL) {
5367 purple_connection_error(gc, _("Could not create SSL context"));
5368 return;
5370 } else if (sip->transport == SIPE_TRANSPORT_UDP) {
5371 /* UDP case */
5372 purple_debug_info("sipe", "using UDP\n");
5374 sip->query_data = purple_dnsquery_a(hostname, port, sipe_udp_host_resolved, sip);
5375 if (sip->query_data == NULL) {
5376 purple_connection_error(gc, _("Could not resolve hostname"));
5378 } else {
5379 /* TCP case */
5380 purple_debug_info("sipe", "using TCP\n");
5381 /* create socket for incoming connections */
5382 sip->listen_data = purple_network_listen_range(5060, 5160, SOCK_STREAM,
5383 sipe_tcp_connect_listen_cb, sip);
5384 if (sip->listen_data == NULL) {
5385 purple_connection_error(gc, _("Could not create listen socket"));
5386 return;
5391 /* Service list for autodection */
5392 static const struct sipe_service_data service_autodetect[] = {
5393 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5394 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5395 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5396 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5397 { NULL, NULL, 0 }
5400 /* Service list for SSL/TLS */
5401 static const struct sipe_service_data service_tls[] = {
5402 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS }, /* for internal TLS connections */
5403 { "sip", "tls", SIPE_TRANSPORT_TLS }, /* for external TLS connections */
5404 { NULL, NULL, 0 }
5407 /* Service list for TCP */
5408 static const struct sipe_service_data service_tcp[] = {
5409 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP }, /* for internal TCP connections */
5410 { "sip", "tcp", SIPE_TRANSPORT_TCP }, /*.for external TCP connections */
5411 { NULL, NULL, 0 }
5414 /* Service list for UDP */
5415 static const struct sipe_service_data service_udp[] = {
5416 { "sip", "udp", SIPE_TRANSPORT_UDP },
5417 { NULL, NULL, 0 }
5420 static void srvresolved(PurpleSrvResponse *, int, gpointer);
5421 static void resolve_next_service(struct sipe_account_data *sip,
5422 const struct sipe_service_data *start)
5424 if (start) {
5425 sip->service_data = start;
5426 } else {
5427 sip->service_data++;
5428 if (sip->service_data->service == NULL) {
5429 gchar *hostname;
5430 /* Try connecting to the SIP hostname directly */
5431 purple_debug(PURPLE_DEBUG_MISC, "sipe", "no SRV records found; using SIP domain as fallback\n");
5432 if (sip->auto_transport) {
5433 // If SSL is supported, default to using it; OCS servers aren't configured
5434 // by default to accept TCP
5435 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5436 sip->transport = purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS : SIPE_TRANSPORT_TCP;
5437 purple_debug(PURPLE_DEBUG_MISC, "sipe", "set transport type..\n");
5440 hostname = g_strdup(sip->sipdomain);
5441 create_connection(sip, hostname, 0);
5442 return;
5446 /* Try to resolve next service */
5447 sip->srv_query_data = purple_srv_resolve(sip->service_data->service,
5448 sip->service_data->transport,
5449 sip->sipdomain,
5450 srvresolved, sip);
5453 static void srvresolved(PurpleSrvResponse *resp, int results, gpointer data)
5455 struct sipe_account_data *sip = data;
5457 sip->srv_query_data = NULL;
5459 /* find the host to connect to */
5460 if (results) {
5461 gchar *hostname = g_strdup(resp->hostname);
5462 int port = resp->port;
5463 purple_debug(PURPLE_DEBUG_MISC, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5464 hostname, port);
5465 g_free(resp);
5467 sip->transport = sip->service_data->type;
5469 create_connection(sip, hostname, port);
5470 } else {
5471 resolve_next_service(sip, NULL);
5475 static void sipe_login(PurpleAccount *account)
5477 PurpleConnection *gc;
5478 struct sipe_account_data *sip;
5479 gchar **signinname_login, **userserver, **domain_user;
5480 const char *transport;
5482 const char *username = purple_account_get_username(account);
5483 gc = purple_account_get_connection(account);
5485 if (strpbrk(username, "\t\v\r\n") != NULL) {
5486 gc->wants_to_die = TRUE;
5487 purple_connection_error(gc, _("SIP Exchange username contains invalid characters"));
5488 return;
5491 gc->proto_data = sip = g_new0(struct sipe_account_data, 1);
5492 gc->flags |= PURPLE_CONNECTION_HTML | PURPLE_CONNECTION_FORMATTING_WBFO | PURPLE_CONNECTION_NO_BGCOLOR |
5493 PURPLE_CONNECTION_NO_FONTSIZE | PURPLE_CONNECTION_NO_URLDESC | PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY;
5494 sip->gc = gc;
5495 sip->account = account;
5496 sip->reregister_set = FALSE;
5497 sip->reauthenticate_set = FALSE;
5498 sip->subscribed = FALSE;
5499 sip->subscribed_buddies = FALSE;
5501 signinname_login = g_strsplit(username, ",", 2);
5503 userserver = g_strsplit(signinname_login[0], "@", 2);
5504 purple_connection_set_display_name(gc, userserver[0]);
5505 sip->username = g_strjoin("@", userserver[0], userserver[1], NULL);
5506 sip->sipdomain = g_strdup(userserver[1]);
5508 if (strpbrk(sip->username, " \t\v\r\n") != NULL) {
5509 gc->wants_to_die = TRUE;
5510 purple_connection_error(gc, _("SIP Exchange usernames may not contain whitespaces"));
5511 return;
5514 domain_user = g_strsplit(signinname_login[1], "\\", 2);
5515 sip->authdomain = (domain_user && domain_user[1]) ? g_strdup(domain_user[0]) : NULL;
5516 sip->authuser = (domain_user && domain_user[1]) ? g_strdup(domain_user[1]) : (signinname_login ? g_strdup(signinname_login[1]) : NULL);
5518 sip->password = g_strdup(purple_connection_get_password(gc));
5520 g_strfreev(userserver);
5521 g_strfreev(domain_user);
5522 g_strfreev(signinname_login);
5524 sip->buddies = g_hash_table_new((GHashFunc)sipe_ht_hash_nick, (GEqualFunc)sipe_ht_equals_nick);
5526 purple_connection_update_progress(gc, _("Connecting"), 1, 2);
5528 /* TODO: Set the status correctly. */
5529 sip->status = g_strdup(SIPE_STATUS_ID_AVAILABLE);
5531 transport = purple_account_get_string(account, "transport", "auto");
5532 sip->transport = (strcmp(transport, "tls") == 0) ? SIPE_TRANSPORT_TLS :
5533 (strcmp(transport, "tcp") == 0) ? SIPE_TRANSPORT_TCP :
5534 SIPE_TRANSPORT_UDP;
5536 if (purple_account_get_bool(account, "useproxy", FALSE)) {
5537 purple_debug(PURPLE_DEBUG_MISC, "sipe", "sipe_login - using specified SIP proxy\n");
5538 create_connection(sip, g_strdup(purple_account_get_string(account, "proxy", sip->sipdomain)), 0);
5539 } else if (strcmp(transport, "auto") == 0) {
5540 sip->auto_transport = TRUE;
5541 resolve_next_service(sip, purple_ssl_is_supported() ? service_autodetect : service_tcp);
5542 } else if (strcmp(transport, "tls") == 0) {
5543 resolve_next_service(sip, service_tls);
5544 } else if (strcmp(transport, "tcp") == 0) {
5545 resolve_next_service(sip, service_tcp);
5546 } else {
5547 resolve_next_service(sip, service_udp);
5551 static void sipe_connection_cleanup(struct sipe_account_data *sip)
5553 connection_free_all(sip);
5555 g_free(sip->epid);
5556 sip->epid = NULL;
5558 if (sip->query_data != NULL)
5559 purple_dnsquery_destroy(sip->query_data);
5560 sip->query_data = NULL;
5562 if (sip->srv_query_data != NULL)
5563 purple_srv_cancel(sip->srv_query_data);
5564 sip->srv_query_data = NULL;
5566 if (sip->listen_data != NULL)
5567 purple_network_listen_cancel(sip->listen_data);
5568 sip->listen_data = NULL;
5570 if (sip->gsc != NULL)
5571 purple_ssl_close(sip->gsc);
5572 sip->gsc = NULL;
5574 sipe_auth_free(&sip->registrar);
5575 sipe_auth_free(&sip->proxy);
5577 if (sip->txbuf)
5578 purple_circ_buffer_destroy(sip->txbuf);
5579 sip->txbuf = NULL;
5581 g_free(sip->realhostname);
5582 sip->realhostname = NULL;
5584 if (sip->listenpa)
5585 purple_input_remove(sip->listenpa);
5586 sip->listenpa = 0;
5587 if (sip->tx_handler)
5588 purple_input_remove(sip->tx_handler);
5589 sip->tx_handler = 0;
5590 if (sip->resendtimeout)
5591 purple_timeout_remove(sip->resendtimeout);
5592 sip->resendtimeout = 0;
5593 if (sip->timeouts) {
5594 GSList *entry = sip->timeouts;
5595 while (entry) {
5596 struct scheduled_action *sched_action = entry->data;
5597 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action->name);
5598 purple_timeout_remove(sched_action->timeout_handler);
5599 if (sched_action->destroy) {
5600 (*sched_action->destroy)(sched_action->payload);
5602 g_free(sched_action->name);
5603 g_free(sched_action);
5604 entry = entry->next;
5607 g_slist_free(sip->timeouts);
5609 if (sip->allow_events) {
5610 GSList *entry = sip->allow_events;
5611 while (entry) {
5612 g_free(entry->data);
5613 entry = entry->next;
5616 g_slist_free(sip->allow_events);
5618 if (sip->containers) {
5619 GSList *entry = sip->containers;
5620 while (entry) {
5621 free_container((struct sipe_container *)entry->data);
5622 entry = entry->next;
5625 g_slist_free(sip->containers);
5627 if (sip->contact)
5628 g_free(sip->contact);
5629 sip->contact = NULL;
5630 if (sip->regcallid)
5631 g_free(sip->regcallid);
5632 sip->regcallid = NULL;
5634 if (sip->serveraddr)
5635 g_free(sip->serveraddr);
5636 sip->serveraddr = NULL;
5638 if (sip->focus_factory_uri)
5639 g_free(sip->focus_factory_uri);
5640 sip->focus_factory_uri = NULL;
5642 sip->fd = -1;
5643 sip->processing_input = FALSE;
5647 * A callback for g_hash_table_foreach_remove
5649 static gboolean sipe_buddy_remove(gpointer key, gpointer buddy, gpointer user_data)
5651 sipe_free_buddy((struct sipe_buddy *) buddy);
5653 /* We must return TRUE as the key/value have already been deleted */
5654 return(TRUE);
5657 static void sipe_close(PurpleConnection *gc)
5659 struct sipe_account_data *sip = gc->proto_data;
5661 if (sip) {
5662 /* leave all conversations */
5663 sipe_session_remove_all(sip);
5665 /* unregister */
5666 do_register_exp(sip, 0);
5668 sipe_connection_cleanup(sip);
5669 g_free(sip->sipdomain);
5670 g_free(sip->username);
5671 g_free(sip->password);
5672 g_free(sip->authdomain);
5673 g_free(sip->authuser);
5674 g_free(sip->status);
5676 g_hash_table_foreach_steal(sip->buddies, sipe_buddy_remove, NULL);
5677 g_hash_table_destroy(sip->buddies);
5679 g_free(gc->proto_data);
5680 gc->proto_data = NULL;
5683 static void sipe_searchresults_im_buddy(PurpleConnection *gc, GList *row, void *user_data)
5685 PurpleAccount *acct = purple_connection_get_account(gc);
5686 char *id = sip_uri_from_name((gchar *)g_list_nth_data(row, 0));
5687 PurpleConversation *conv = purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM, id, acct);
5688 if (conv == NULL)
5689 conv = purple_conversation_new(PURPLE_CONV_TYPE_IM, acct, id);
5690 purple_conversation_present(conv);
5691 g_free(id);
5694 static void sipe_searchresults_add_buddy(PurpleConnection *gc, GList *row, void *user_data)
5697 purple_blist_request_add_buddy(purple_connection_get_account(gc),
5698 g_list_nth_data(row, 0), _("Other Contacts"), g_list_nth_data(row, 1));
5701 static gboolean process_search_contact_response(struct sipe_account_data *sip, struct sipmsg *msg,struct transaction *tc)
5703 PurpleNotifySearchResults *results;
5704 PurpleNotifySearchColumn *column;
5705 xmlnode *searchResults;
5706 xmlnode *mrow;
5707 int match_count = 0;
5708 gboolean more = FALSE;
5709 gchar *secondary;
5711 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg->body ? msg->body : "");
5713 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
5714 if (!searchResults) {
5715 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5716 return FALSE;
5719 results = purple_notify_searchresults_new();
5721 if (results == NULL) {
5722 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5723 purple_notify_error(sip->gc, NULL, _("Unable to display the search results."), NULL);
5725 xmlnode_free(searchResults);
5726 return FALSE;
5729 column = purple_notify_searchresults_column_new(_("User Name"));
5730 purple_notify_searchresults_column_add(results, column);
5732 column = purple_notify_searchresults_column_new(_("Name"));
5733 purple_notify_searchresults_column_add(results, column);
5735 column = purple_notify_searchresults_column_new(_("Company"));
5736 purple_notify_searchresults_column_add(results, column);
5738 column = purple_notify_searchresults_column_new(_("Country"));
5739 purple_notify_searchresults_column_add(results, column);
5741 column = purple_notify_searchresults_column_new(_("Email"));
5742 purple_notify_searchresults_column_add(results, column);
5744 for (mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL); mrow; mrow = xmlnode_get_next_twin(mrow)) {
5745 GList *row = NULL;
5747 gchar **uri_parts = g_strsplit(xmlnode_get_attrib(mrow, "uri"), ":", 2);
5748 row = g_list_append(row, g_strdup(uri_parts[1]));
5749 g_strfreev(uri_parts);
5751 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "displayName")));
5752 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "company")));
5753 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "country")));
5754 row = g_list_append(row, g_strdup(xmlnode_get_attrib(mrow, "email")));
5756 purple_notify_searchresults_row_add(results, row);
5757 match_count++;
5760 if ((mrow = xmlnode_get_descendant(searchResults, "Body", "directorySearch", "moreAvailable", NULL)) != NULL) {
5761 char *data = xmlnode_get_data_unescaped(mrow);
5762 more = (g_strcasecmp(data, "true") == 0);
5763 g_free(data);
5766 secondary = g_strdup_printf(
5767 dngettext(GETTEXT_PACKAGE,
5768 "Found %d contact%s:",
5769 "Found %d contacts%s:", match_count),
5770 match_count, more ? _(" (more matched your query)") : "");
5772 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_IM, sipe_searchresults_im_buddy);
5773 purple_notify_searchresults_button_add(results, PURPLE_NOTIFY_BUTTON_ADD, sipe_searchresults_add_buddy);
5774 purple_notify_searchresults(sip->gc, NULL, NULL, secondary, results, NULL, NULL);
5776 g_free(secondary);
5777 xmlnode_free(searchResults);
5778 return TRUE;
5781 static void sipe_search_contact_with_cb(PurpleConnection *gc, PurpleRequestFields *fields)
5783 GList *entries = purple_request_field_group_get_fields(purple_request_fields_get_groups(fields)->data);
5784 gchar **attrs = g_new(gchar *, g_list_length(entries) + 1);
5785 unsigned i = 0;
5787 do {
5788 PurpleRequestField *field = entries->data;
5789 const char *id = purple_request_field_get_id(field);
5790 const char *value = purple_request_field_string_get_value(field);
5792 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id, value ? value : "");
5794 if (value != NULL) attrs[i++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, id, value);
5795 } while ((entries = g_list_next(entries)) != NULL);
5796 attrs[i] = NULL;
5798 if (i > 0) {
5799 gchar *query = g_strjoinv(NULL, attrs);
5800 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 100, query);
5801 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body ? body : "");
5802 send_soap_request_with_cb(gc->proto_data, body,
5803 (TransCallback) process_search_contact_response, NULL);
5804 g_free(body);
5805 g_free(query);
5808 g_strfreev(attrs);
5811 static void sipe_show_find_contact(PurplePluginAction *action)
5813 PurpleConnection *gc = (PurpleConnection *) action->context;
5814 PurpleRequestFields *fields;
5815 PurpleRequestFieldGroup *group;
5816 PurpleRequestField *field;
5818 fields = purple_request_fields_new();
5819 group = purple_request_field_group_new(NULL);
5820 purple_request_fields_add_group(fields, group);
5822 field = purple_request_field_string_new("givenName", _("First Name"), NULL, FALSE);
5823 purple_request_field_group_add_field(group, field);
5824 field = purple_request_field_string_new("sn", _("Last Name"), NULL, FALSE);
5825 purple_request_field_group_add_field(group, field);
5826 field = purple_request_field_string_new("company", _("Company"), NULL, FALSE);
5827 purple_request_field_group_add_field(group, field);
5828 field = purple_request_field_string_new("c", _("Country"), NULL, FALSE);
5829 purple_request_field_group_add_field(group, field);
5831 purple_request_fields(gc,
5832 _("Search"),
5833 _("Search for a Contact"),
5834 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5835 fields,
5836 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb),
5837 _("_Cancel"), NULL,
5838 purple_connection_get_account(gc), NULL, NULL, gc);
5841 GList *sipe_actions(PurplePlugin *plugin, gpointer context)
5843 GList *menu = NULL;
5844 PurplePluginAction *act;
5846 act = purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact);
5847 menu = g_list_prepend(menu, act);
5849 menu = g_list_reverse(menu);
5851 return menu;
5854 static void dummy_permit_deny(PurpleConnection *gc)
5858 static gboolean sipe_plugin_load(PurplePlugin *plugin)
5860 return TRUE;
5864 static gboolean sipe_plugin_unload(PurplePlugin *plugin)
5866 return TRUE;
5870 static char *sipe_status_text(PurpleBuddy *buddy)
5872 struct sipe_account_data *sip;
5873 struct sipe_buddy *sbuddy;
5874 char *text = NULL;
5876 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5877 if (sip) //happens on pidgin exit
5879 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5880 if (sbuddy && sbuddy->annotation)
5882 text = g_strdup(sbuddy->annotation);
5886 return text;
5889 static void sipe_tooltip_text(PurpleBuddy *buddy, PurpleNotifyUserInfo *user_info, gboolean full)
5891 const PurplePresence *presence = purple_buddy_get_presence(buddy);
5892 const PurpleStatus *status = purple_presence_get_active_status(presence);
5893 struct sipe_account_data *sip;
5894 struct sipe_buddy *sbuddy;
5895 char *annotation = NULL;
5897 sip = (struct sipe_account_data *) buddy->account->gc->proto_data;
5898 if (sip) //happens on pidgin exit
5900 sbuddy = g_hash_table_lookup(sip->buddies, buddy->name);
5901 if (sbuddy)
5903 annotation = sbuddy->annotation ? g_strdup(sbuddy->annotation) : NULL;
5907 //Layout
5908 if (purple_presence_is_online(presence))
5910 purple_notify_user_info_add_pair(user_info, _("Status"), purple_status_get_name(status));
5913 if (annotation)
5915 purple_notify_user_info_add_pair( user_info, _("Note"), annotation );
5916 g_free(annotation);
5921 static GHashTable *
5922 sipe_get_account_text_table(PurpleAccount *account)
5924 GHashTable *table;
5925 table = g_hash_table_new(g_str_hash, g_str_equal);
5926 g_hash_table_insert(table, "login_label", (gpointer)_("Sign-In Name..."));
5927 return table;
5930 static PurpleBuddy *
5931 purple_blist_add_buddy_clone(PurpleGroup * group, PurpleBuddy * buddy)
5933 PurpleBuddy *clone;
5934 const gchar *server_alias, *email;
5935 const PurpleStatus *status = purple_presence_get_active_status(purple_buddy_get_presence(buddy));
5937 clone = purple_buddy_new(buddy->account, buddy->name, buddy->alias);
5939 purple_blist_add_buddy(clone, NULL, group, NULL);
5941 server_alias = g_strdup(purple_buddy_get_server_alias(buddy));
5942 if (server_alias) {
5943 purple_blist_server_alias_buddy(clone, server_alias);
5946 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
5947 if (email) {
5948 purple_blist_node_set_string((PurpleBlistNode *)clone, "email", email);
5951 purple_presence_set_status_active(purple_buddy_get_presence(clone), purple_status_get_id(status), TRUE);
5952 //for UI to update;
5953 purple_prpl_got_user_status(clone->account, clone->name, purple_status_get_id(status), NULL);
5954 return clone;
5957 static void
5958 sipe_buddy_menu_copy_to_cb(PurpleBlistNode *node, const char *group_name)
5960 PurpleBuddy *buddy, *b;
5961 PurpleConnection *gc;
5962 PurpleGroup * group = purple_find_group(group_name);
5964 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node));
5966 buddy = (PurpleBuddy *)node;
5968 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy->name, group_name);
5969 gc = purple_account_get_connection(buddy->account);
5971 b = purple_find_buddy_in_group(buddy->account, buddy->name, group);
5972 if (!b){
5973 b = purple_blist_add_buddy_clone(group, buddy);
5976 sipe_group_buddy(gc, buddy->name, NULL, group_name);
5979 static void
5980 sipe_buddy_menu_chat_new_cb(PurpleBuddy *buddy)
5982 struct sipe_account_data *sip = buddy->account->gc->proto_data;
5984 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
5986 /* 2007+ conference */
5987 if (sip->msrtc_event_categories)
5989 sipe_conf_add(sip, buddy->name);
5991 else /* 2005- multiparty chat */
5993 gchar *self = sip_uri_self(sip);
5994 gchar *chat_name = g_strdup_printf(_("Chat #%d"), ++sip->chat_seq);
5995 struct sip_session *session;
5997 session = sipe_session_add_chat(sip);
5998 session->roster_manager = g_strdup(self);
6000 session->conv = serv_got_joined_chat(buddy->account->gc, session->chat_id, g_strdup(chat_name));
6001 session->chat_name = g_strdup(chat_name);
6002 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session->conv), self);
6003 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session->conv), self, NULL, PURPLE_CBFLAGS_NONE, FALSE);
6004 sipe_invite(sip, session, buddy->name, NULL, NULL, FALSE);
6006 g_free(chat_name);
6007 g_free(self);
6011 static gboolean
6012 sipe_is_election_finished(struct sipe_account_data *sip,
6013 struct sip_session *session)
6015 gboolean res = TRUE;
6017 SIPE_DIALOG_FOREACH {
6018 if (dialog->election_vote == 0) {
6019 res = FALSE;
6020 break;
6022 } SIPE_DIALOG_FOREACH_END;
6024 if (res) {
6025 session->is_voting_in_progress = FALSE;
6027 return res;
6030 static void
6031 sipe_election_start(struct sipe_account_data *sip,
6032 struct sip_session *session)
6034 int election_timeout;
6036 if (session->is_voting_in_progress) {
6037 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6038 return;
6039 } else {
6040 session->is_voting_in_progress = TRUE;
6042 session->bid = rand();
6044 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session->bid);
6046 SIPE_DIALOG_FOREACH {
6047 /* reset election_vote for each chat participant */
6048 dialog->election_vote = 0;
6050 /* send RequestRM to each chat participant*/
6051 sipe_send_election_request_rm(sip, dialog, session->bid);
6052 } SIPE_DIALOG_FOREACH_END;
6054 election_timeout = 15; /* sec */
6055 sipe_schedule_action("<+election-result>", election_timeout, sipe_election_result, NULL, sip, session);
6059 * @param who a URI to whom to invite to chat
6061 void
6062 sipe_invite_to_chat(struct sipe_account_data *sip,
6063 struct sip_session *session,
6064 const gchar *who)
6066 /* a conference */
6067 if (session->focus_uri)
6069 sipe_invite_conf(sip, session, who);
6071 else /* a multi-party chat */
6073 gchar *self = sip_uri_self(sip);
6074 if (session->roster_manager) {
6075 if (!strcmp(session->roster_manager, self)) {
6076 sipe_invite(sip, session, who, NULL, NULL, FALSE);
6077 } else {
6078 sipe_refer(sip, session, who);
6080 } else {
6081 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6083 session->pending_invite_queue = slist_insert_unique_sorted(
6084 session->pending_invite_queue, g_strdup(who), (GCompareFunc)strcmp);
6086 sipe_election_start(sip, session);
6088 g_free(self);
6092 void
6093 sipe_process_pending_invite_queue(struct sipe_account_data *sip,
6094 struct sip_session *session)
6096 gchar *invitee;
6097 GSList *entry = session->pending_invite_queue;
6099 while (entry) {
6100 invitee = entry->data;
6101 sipe_invite_to_chat(sip, session, invitee);
6102 entry = session->pending_invite_queue = g_slist_remove(session->pending_invite_queue, invitee);
6103 g_free(invitee);
6107 static void
6108 sipe_election_result(struct sipe_account_data *sip,
6109 void *sess)
6111 struct sip_session *session = (struct sip_session *)sess;
6112 gchar *rival;
6113 gboolean has_won = TRUE;
6115 if (session->roster_manager) {
6116 purple_debug_info("sipe",
6117 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session->roster_manager);
6118 return;
6121 session->is_voting_in_progress = FALSE;
6123 SIPE_DIALOG_FOREACH {
6124 if (dialog->election_vote < 0) {
6125 has_won = FALSE;
6126 rival = dialog->with;
6127 break;
6129 } SIPE_DIALOG_FOREACH_END;
6131 if (has_won) {
6132 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6134 session->roster_manager = sip_uri_self(sip);
6136 SIPE_DIALOG_FOREACH {
6137 /* send SetRM to each chat participant*/
6138 sipe_send_election_set_rm(sip, dialog);
6139 } SIPE_DIALOG_FOREACH_END;
6140 } else {
6141 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival);
6143 session->bid = 0;
6145 sipe_process_pending_invite_queue(sip, session);
6149 * For 2007+ conference only.
6151 static void
6152 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy *buddy, const char *chat_name)
6154 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6155 struct sip_session *session;
6157 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy->name);
6158 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_name=%s\n", chat_name);
6160 session = sipe_session_find_chat_by_name(sip, chat_name);
6162 sipe_conf_modify_user_role(sip, session, buddy->name);
6165 static void
6166 sipe_buddy_menu_chat_invite_cb(PurpleBuddy *buddy, const char *chat_name)
6168 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6169 struct sip_session *session;
6171 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy->name);
6172 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name);
6174 session = sipe_session_find_chat_by_name(sip, chat_name);
6176 sipe_invite_to_chat(sip, session, buddy->name);
6179 static void
6180 sipe_buddy_menu_send_email_cb(PurpleBuddy *buddy)
6182 const gchar *email;
6183 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy->name);
6185 email = purple_blist_node_get_string((PurpleBlistNode *)buddy, "email");
6186 if (email)
6188 char *mailto = g_strdup_printf("mailto:%s", email);
6189 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email);
6190 #ifndef _WIN32
6192 pid_t pid;
6193 char *const parmList[] = {mailto, NULL};
6194 if ((pid = fork()) == -1)
6196 purple_debug_info("sipe", "fork() error\n");
6198 else if (pid == 0)
6200 execvp("xdg-email", parmList);
6201 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6204 #else
6206 BOOL ret;
6207 _flushall();
6208 errno = 0;
6209 //@TODO resolve env variable %WINDIR% first
6210 ret = spawnl(_P_NOWAIT, "c:/WINDOWS/system32/cmd", "/c", "start", mailto, NULL);
6211 if (errno)
6213 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno));
6216 #endif
6218 g_free(mailto);
6220 else
6222 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy->name);
6227 * A menu which appear when right-clicking on buddy in contact list.
6229 static GList *
6230 sipe_buddy_menu(PurpleBuddy *buddy)
6232 PurpleBlistNode *g_node;
6233 PurpleGroup *group, *gr_parent;
6234 PurpleMenuAction *act;
6235 GList *menu = NULL;
6236 GList *menu_groups = NULL;
6237 struct sipe_account_data *sip = buddy->account->gc->proto_data;
6238 gchar *self = sip_uri_self(sip);
6240 SIPE_SESSION_FOREACH {
6241 if (strcmp(self, buddy->name) && session->chat_name && session->conv)
6243 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session->conv), buddy->name))
6245 PurpleConvChatBuddyFlags flags;
6246 PurpleConvChatBuddyFlags flags_us;
6248 flags = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), buddy->name);
6249 flags_us = purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session->conv), self);
6250 if (session->focus_uri &&
6251 PURPLE_CBFLAGS_OP != (flags & PURPLE_CBFLAGS_OP) && /* Not conf OP */
6252 PURPLE_CBFLAGS_OP == (flags_us & PURPLE_CBFLAGS_OP)) /* We are a conf OP */
6254 gchar *label = g_strdup_printf(_("Make Leader of '%s'"), session->chat_name);
6255 act = purple_menu_action_new(label,
6256 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb),
6257 g_strdup(session->chat_name), NULL);
6258 g_free(label);
6259 menu = g_list_prepend(menu, act);
6262 else
6264 gchar *label = g_strdup_printf(_("Invite to '%s'"), session->chat_name);
6265 act = purple_menu_action_new(label,
6266 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb),
6267 g_strdup(session->chat_name), NULL);
6268 g_free(label);
6269 menu = g_list_prepend(menu, act);
6272 } SIPE_SESSION_FOREACH_END;
6274 act = purple_menu_action_new(_("New Chat"),
6275 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb),
6276 NULL, NULL);
6277 menu = g_list_prepend(menu, act);
6279 act = purple_menu_action_new(_("Send Email..."),
6280 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb),
6281 NULL, NULL);
6282 menu = g_list_prepend(menu, act);
6284 gr_parent = purple_buddy_get_group(buddy);
6285 for (g_node = purple_blist_get_root(); g_node; g_node = g_node->next) {
6286 if (g_node->type != PURPLE_BLIST_GROUP_NODE)
6287 continue;
6289 group = (PurpleGroup *)g_node;
6290 if (group == gr_parent)
6291 continue;
6293 if (purple_find_buddy_in_group(buddy->account, buddy->name, group))
6294 continue;
6296 act = purple_menu_action_new(purple_group_get_name(group),
6297 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb),
6298 group->name, NULL);
6299 menu_groups = g_list_prepend(menu_groups, act);
6301 menu_groups = g_list_reverse(menu_groups);
6303 act = purple_menu_action_new(_("Copy to"),
6304 NULL,
6305 NULL, menu_groups);
6306 menu = g_list_prepend(menu, act);
6307 menu = g_list_reverse(menu);
6309 g_free(self);
6310 return menu;
6313 static GList *
6314 sipe_blist_node_menu(PurpleBlistNode *node)
6316 if(PURPLE_BLIST_NODE_IS_BUDDY(node)) {
6317 return sipe_buddy_menu((PurpleBuddy *) node);
6318 } else {
6319 return NULL;
6323 static gboolean
6324 process_get_info_response(struct sipe_account_data *sip, struct sipmsg *msg, struct transaction *trans)
6326 gboolean ret = TRUE;
6327 char *username = (char *)trans->payload;
6329 PurpleNotifyUserInfo *info = purple_notify_user_info_new();
6330 PurpleBuddy *pbuddy;
6331 struct sipe_buddy *sbuddy;
6332 const char *alias;
6333 char *server_alias = NULL;
6334 char *email = NULL;
6335 const char *device_name = NULL;
6337 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username, sip->username);
6339 pbuddy = purple_find_buddy((PurpleAccount *)sip->account, username);
6340 alias = purple_buddy_get_local_alias(pbuddy);
6342 if (sip)
6344 //will query buddy UA's capabilities and send answer to log
6345 sipe_options_request(sip, username);
6347 sbuddy = g_hash_table_lookup(sip->buddies, username);
6348 if (sbuddy)
6350 device_name = sbuddy->device_name ? g_strdup(sbuddy->device_name) : NULL;
6354 if (msg->response != 200) {
6355 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg->response);
6356 } else {
6357 xmlnode *searchResults;
6358 xmlnode *mrow;
6360 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg->body ? msg->body : "");
6361 searchResults = xmlnode_from_str(msg->body, msg->bodylen);
6362 if (!searchResults) {
6363 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6364 } else if ((mrow = xmlnode_get_descendant(searchResults, "Body", "Array", "row", NULL))) {
6365 server_alias = g_strdup(xmlnode_get_attrib(mrow, "displayName"));
6366 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6367 purple_notify_user_info_add_pair(info, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow, "title")));
6368 purple_notify_user_info_add_pair(info, _("Office"), g_strdup(xmlnode_get_attrib(mrow, "office")));
6369 purple_notify_user_info_add_pair(info, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow, "phone")));
6370 purple_notify_user_info_add_pair(info, _("Company"), g_strdup(xmlnode_get_attrib(mrow, "company")));
6371 purple_notify_user_info_add_pair(info, _("City"), g_strdup(xmlnode_get_attrib(mrow, "city")));
6372 purple_notify_user_info_add_pair(info, _("State"), g_strdup(xmlnode_get_attrib(mrow, "state")));
6373 purple_notify_user_info_add_pair(info, _("Country"), g_strdup(xmlnode_get_attrib(mrow, "country")));
6374 email = g_strdup(xmlnode_get_attrib(mrow, "email"));
6375 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6376 if (!email || strcmp("", email)) {
6377 if (!purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email")) {
6378 purple_blist_node_set_string((PurpleBlistNode *)pbuddy, "email", email);
6382 xmlnode_free(searchResults);
6385 purple_notify_user_info_add_section_break(info);
6387 if (!server_alias || !strcmp("", server_alias)) {
6388 g_free(server_alias);
6389 server_alias = g_strdup(purple_buddy_get_server_alias(pbuddy));
6390 if (server_alias) {
6391 purple_notify_user_info_add_pair(info, _("Display Name"), server_alias);
6395 // same as server alias, do not present
6396 alias = (alias && server_alias && !strcmp(alias, server_alias)) ? NULL : alias;
6397 if (alias)
6399 purple_notify_user_info_add_pair(info, _("Alias"), alias);
6402 if (!email || !strcmp("", email)) {
6403 g_free(email);
6404 email = g_strdup(purple_blist_node_get_string((PurpleBlistNode *)pbuddy, "email"));
6405 if (email) {
6406 purple_notify_user_info_add_pair(info, _("E-Mail Address"), email);
6410 if (device_name)
6412 purple_notify_user_info_add_pair(info, _("Device"), device_name);
6415 /* show a buddy's user info in a nice dialog box */
6416 purple_notify_userinfo(sip->gc, /* connection the buddy info came through */
6417 username, /* buddy's username */
6418 info, /* body */
6419 NULL, /* callback called when dialog closed */
6420 NULL); /* userdata for callback */
6422 return ret;
6426 * AD search first, LDAP based
6428 static void sipe_get_info(PurpleConnection *gc, const char *username)
6430 char *row = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW, "msRTCSIP-PrimaryUserAddress", username);
6431 gchar *body = g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT, 1, row);
6433 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body ? body : "");
6434 send_soap_request_with_cb((struct sipe_account_data *)gc->proto_data, body,
6435 (TransCallback) process_get_info_response, (gpointer)g_strdup(username));
6436 g_free(body);
6437 g_free(row);
6440 static PurplePlugin *my_protocol = NULL;
6442 static PurplePluginProtocolInfo prpl_info =
6444 OPT_PROTO_CHAT_TOPIC,
6445 NULL, /* user_splits */
6446 NULL, /* protocol_options */
6447 NO_BUDDY_ICONS, /* icon_spec */
6448 sipe_list_icon, /* list_icon */
6449 NULL, /* list_emblems */
6450 sipe_status_text, /* status_text */
6451 sipe_tooltip_text, /* tooltip_text */ // add custom info to contact tooltip
6452 sipe_status_types, /* away_states */
6453 sipe_blist_node_menu, /* blist_node_menu */
6454 NULL, /* chat_info */
6455 NULL, /* chat_info_defaults */
6456 sipe_login, /* login */
6457 sipe_close, /* close */
6458 sipe_im_send, /* send_im */
6459 NULL, /* set_info */ // TODO maybe
6460 sipe_send_typing, /* send_typing */
6461 sipe_get_info, /* get_info */
6462 sipe_set_status, /* set_status */
6463 NULL, /* set_idle */
6464 NULL, /* change_passwd */
6465 sipe_add_buddy, /* add_buddy */
6466 NULL, /* add_buddies */
6467 sipe_remove_buddy, /* remove_buddy */
6468 NULL, /* remove_buddies */
6469 sipe_add_permit, /* add_permit */
6470 sipe_add_deny, /* add_deny */
6471 sipe_add_deny, /* rem_permit */
6472 sipe_add_permit, /* rem_deny */
6473 dummy_permit_deny, /* set_permit_deny */
6474 NULL, /* join_chat */
6475 NULL, /* reject_chat */
6476 NULL, /* get_chat_name */
6477 sipe_chat_invite, /* chat_invite */
6478 sipe_chat_leave, /* chat_leave */
6479 NULL, /* chat_whisper */
6480 sipe_chat_send, /* chat_send */
6481 sipe_keep_alive, /* keepalive */
6482 NULL, /* register_user */
6483 NULL, /* get_cb_info */ // deprecated
6484 NULL, /* get_cb_away */ // deprecated
6485 sipe_alias_buddy, /* alias_buddy */
6486 sipe_group_buddy, /* group_buddy */
6487 sipe_rename_group, /* rename_group */
6488 NULL, /* buddy_free */
6489 sipe_convo_closed, /* convo_closed */
6490 purple_normalize_nocase, /* normalize */
6491 NULL, /* set_buddy_icon */
6492 sipe_remove_group, /* remove_group */
6493 NULL, /* get_cb_real_name */ // TODO?
6494 NULL, /* set_chat_topic */
6495 NULL, /* find_blist_chat */
6496 NULL, /* roomlist_get_list */
6497 NULL, /* roomlist_cancel */
6498 NULL, /* roomlist_expand_category */
6499 NULL, /* can_receive_file */
6500 NULL, /* send_file */
6501 NULL, /* new_xfer */
6502 NULL, /* offline_message */
6503 NULL, /* whiteboard_prpl_ops */
6504 sipe_send_raw, /* send_raw */
6505 NULL, /* roomlist_room_serialize */
6506 NULL, /* unregister_user */
6507 NULL, /* send_attention */
6508 NULL, /* get_attention_types */
6510 sizeof(PurplePluginProtocolInfo), /* struct_size */
6511 sipe_get_account_text_table, /* get_account_text_table */
6515 static PurplePluginInfo info = {
6516 PURPLE_PLUGIN_MAGIC,
6517 PURPLE_MAJOR_VERSION,
6518 PURPLE_MINOR_VERSION,
6519 PURPLE_PLUGIN_PROTOCOL, /**< type */
6520 NULL, /**< ui_requirement */
6521 0, /**< flags */
6522 NULL, /**< dependencies */
6523 PURPLE_PRIORITY_DEFAULT, /**< priority */
6524 "prpl-sipe", /**< id */
6525 "Microsoft LCS/OCS", /**< name */
6526 VERSION, /**< version */
6527 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6528 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6529 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6530 "Gabriel Burt <gburt@novell.com>", /**< author */
6531 PURPLE_WEBSITE, /**< homepage */
6532 sipe_plugin_load, /**< load */
6533 sipe_plugin_unload, /**< unload */
6534 sipe_plugin_destroy, /**< destroy */
6535 NULL, /**< ui_info */
6536 &prpl_info, /**< extra_info */
6537 NULL,
6538 sipe_actions,
6539 NULL,
6540 NULL,
6541 NULL,
6542 NULL
6545 static void sipe_plugin_destroy(PurplePlugin *plugin)
6547 GList *entry;
6549 entry = prpl_info.protocol_options;
6550 while (entry) {
6551 purple_account_option_destroy(entry->data);
6552 entry = g_list_delete_link(entry, entry);
6554 prpl_info.protocol_options = NULL;
6556 entry = prpl_info.user_splits;
6557 while (entry) {
6558 purple_account_user_split_destroy(entry->data);
6559 entry = g_list_delete_link(entry, entry);
6561 prpl_info.user_splits = NULL;
6564 static void init_plugin(PurplePlugin *plugin)
6566 PurpleAccountUserSplit *split;
6567 PurpleAccountOption *option;
6569 srand(time(NULL));
6571 #ifdef ENABLE_NLS
6572 purple_debug_info(PACKAGE, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE, LOCALEDIR));
6573 purple_debug_info(PACKAGE, "bind_textdomain_codeset = %s\n",
6574 bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8"));
6575 textdomain(GETTEXT_PACKAGE);
6576 #endif
6578 purple_plugin_register(plugin);
6580 split = purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL, ',');
6581 purple_account_user_split_set_reverse(split, FALSE);
6582 prpl_info.user_splits = g_list_append(prpl_info.user_splits, split);
6584 option = purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE);
6585 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6586 option = purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6587 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6589 option = purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE);
6590 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6591 // Translators: noun (networking port)
6592 option = purple_account_option_int_new(_("Port"), "port", 5061);
6593 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6595 option = purple_account_option_list_new(_("Connection Type"), "transport", NULL);
6596 purple_account_option_add_list_item(option, _("Auto"), "auto");
6597 purple_account_option_add_list_item(option, _("SSL/TLS"), "tls");
6598 purple_account_option_add_list_item(option, _("TCP"), "tcp");
6599 purple_account_option_add_list_item(option, _("UDP"), "udp");
6600 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6602 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6603 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6605 option = purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION);
6606 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6608 #ifdef USE_KERBEROS
6609 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
6610 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6612 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6613 * No login/password is taken into account if this option present,
6614 * instead used default credentials stored in OS.
6616 option = purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE);
6617 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
6618 #endif
6619 my_protocol = plugin;
6622 /* I had to redefined the function for it load, but works */
6623 gboolean purple_init_plugin(PurplePlugin *plugin){
6624 plugin->info = &(info);
6625 init_plugin((plugin));
6626 sipe_plugin_load((plugin));
6627 return purple_plugin_register(plugin);
6631 Local Variables:
6632 mode: c
6633 c-file-style: "bsd"
6634 indent-tabs-mode: t
6635 tab-width: 8
6636 End: