6 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2007 Anibal Avelar <avelar@gmail.com>
8 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
11 * Thanks to Google's Summer of Code Program and the helpful mentors
14 * Session-based SIP MESSAGE documentation:
15 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
17 * This program is free software; you can redistribute it and/or modify
18 * it under the terms of the GNU General Public License as published by
19 * the Free Software Foundation; either version 2 of the License, or
20 * (at your option) any later version.
22 * This program is distributed in the hope that it will be useful,
23 * but WITHOUT ANY WARRANTY; without even the implied warranty of
24 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
25 * GNU General Public License for more details.
27 * You should have received a copy of the GNU General Public License
28 * along with this program; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
33 #include <sys/socket.h>
34 #include <sys/ioctl.h>
35 #include <sys/types.h>
36 #include <netinet/in.h>
40 # define _(String) ((const char *) gettext (String))
42 # define _(String) ((const char *) (String))
43 #endif /* ENABLE_NLS */
48 #define _LIBC_INTERNAL_
61 #include "accountopt.h"
63 #include "conversation.h"
80 #endif /*USE_KERBEROS*/
83 #include "sipe-sign.h"
87 /* Keep in sync with sipe_transport_type! */
88 static const char *transport_descriptor
[] = { "tls", "tcp", "udp" };
89 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
93 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
96 static gchar
*get_epid()
98 return sipe_uuid_get_macaddr();
101 static char *genbranch()
103 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
104 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
105 rand() & 0xFFFF, rand() & 0xFFFF);
108 static char *gencallid()
110 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
111 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
112 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
113 rand() & 0xFFFF, rand() & 0xFFFF);
116 static gchar
*find_tag(const gchar
*hdr
)
118 gchar
* tag
= sipmsg_find_part_of_header (hdr
, "tag=", ";", NULL
);
120 // In case it's at the end and there's no trailing ;
121 tag
= sipmsg_find_part_of_header (hdr
, "tag=", NULL
, NULL
);
127 static const char *sipe_list_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
132 static void sipe_plugin_destroy(PurplePlugin
*plugin
);
134 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
);
136 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
137 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
140 static void sipe_close(PurpleConnection
*gc
);
142 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, const char * buddy_name
);
143 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
);
144 static void send_presence_info(struct sipe_account_data
*sip
);
146 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
148 static void sipe_keep_alive_timeout(struct sipe_account_data
*sip
, const gchar
*hdr
)
150 gchar
*timeout
= sipmsg_find_part_of_header(hdr
, "timeout=", ";", NULL
);
151 if (timeout
!= NULL
) {
152 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
153 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
154 sip
->keepalive_timeout
);
159 static void sipe_keep_alive(PurpleConnection
*gc
)
161 struct sipe_account_data
*sip
= gc
->proto_data
;
162 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
163 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
164 gchar buf
[2] = {0, 0};
165 purple_debug_info("sipe", "sending keep alive\n");
166 sendto(sip
->fd
, buf
, 1, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
));
168 time_t now
= time(NULL
);
169 if ((sip
->keepalive_timeout
> 0) &&
170 ((now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
171 #if PURPLE_VERSION_CHECK(2,4,0)
172 && ((now
- gc
->last_received
) >= sip
->keepalive_timeout
)
175 purple_debug_info("sipe", "sending keep alive\n");
176 sendout_pkt(gc
, "\r\n\r\n");
177 sip
->last_keepalive
= now
;
182 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
184 struct sip_connection
*ret
= NULL
;
185 GSList
*entry
= sip
->openconns
;
188 if (ret
->fd
== fd
) return ret
;
194 static void sipe_auth_free(struct sip_auth
*auth
)
198 g_free(auth
->opaque
);
202 g_free(auth
->target
);
204 g_free(auth
->digest_session_key
);
205 auth
->digest_session_key
= NULL
;
206 g_free(auth
->ntlm_key
);
207 auth
->ntlm_key
= NULL
;
208 auth
->type
= AUTH_TYPE_UNSET
;
213 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
215 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
217 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
221 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
223 struct sip_connection
*conn
= connection_find(sip
, fd
);
225 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
226 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
232 static void connection_free_all(struct sipe_account_data
*sip
)
234 struct sip_connection
*ret
= NULL
;
235 GSList
*entry
= sip
->openconns
;
238 connection_remove(sip
, ret
->fd
);
239 entry
= sip
->openconns
;
243 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
245 const gchar
*method
= msg
->method
;
246 const gchar
*target
= msg
->target
;
251 const char *authdomain
= sip
->authdomain
;
252 const char *authuser
= sip
->authuser
;
253 //const char *krb5_realm;
255 //gchar *krb5_token = NULL;
257 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
258 // and do error checking
260 // KRB realm should always be uppercase
261 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
263 if (sip
->realhostname
) {
264 host
= sip
->realhostname
;
265 } else if (purple_account_get_bool(sip
->account
, "useproxy", TRUE
)) {
266 host
= purple_account_get_string(sip
->account
, "proxy", "");
268 host
= sip
->sipdomain
;
271 /*gboolean new_auth = krb5_auth.gss_context == NULL;
273 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
276 if (new_auth || force_reauth) {
277 krb5_token = krb5_auth.base64_token;
280 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
281 krb5_token = krb5_auth.base64_token;*/
287 if (!authuser
|| strlen(authuser
) < 1) {
288 authuser
= sip
->username
;
291 if (auth
->type
== AUTH_TYPE_DIGEST
) { /* Digest */
292 sprintf(noncecount
, "%08d", auth
->nc
++);
293 response
= purple_cipher_http_digest_calculate_response(
294 "md5", method
, target
, NULL
, NULL
,
295 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
296 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
298 ret
= g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser
, auth
->realm
, auth
->nonce
, target
, noncecount
, response
);
301 } else if (auth
->type
== AUTH_TYPE_NTLM
) { /* NTLM */
302 // If we have a signature for the message, include that
303 if (msg
->signature
) {
304 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, msg
->rand
, msg
->num
, msg
->signature
);
308 if (auth
->nc
== 3 && auth
->nonce
&& auth
->ntlm_key
== NULL
) {
309 const gchar
*ntlm_key
;
311 #if GLIB_CHECK_VERSION(2,8,0)
312 const gchar
* hostname
= g_get_host_name();
314 static char hostname
[256];
315 int ret
= gethostname(hostname
, sizeof(hostname
));
316 hostname
[sizeof(hostname
) - 1] = '\0';
317 if (ret
== -1 || hostname
[0] == '\0') {
318 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Error when getting host name. Using \"localhost.\"\n");
320 strcpy(hostname
, "localhost");
323 /*const gchar * hostname = purple_get_host_name();*/
325 gssapi_data
= purple_ntlm_gen_authenticate(&ntlm_key
, authuser
, sip
->password
, hostname
, authdomain
, (const guint8
*)auth
->nonce
, &auth
->flags
);
326 auth
->ntlm_key
= (gchar
*)ntlm_key
;
327 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, gssapi_data
);
332 tmp
= g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth
->realm
, auth
->target
);
334 } else if (auth
->type
== AUTH_TYPE_KERBEROS
) {
337 /*if (new_auth || force_reauth) {
338 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
340 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, krb5_token);
342 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
345 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
346 gchar * mic = "MICTODO";
347 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
348 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
349 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
350 //auth->opaque ? auth->opaque : "", auth->target);
351 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
356 tmp
= g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth
->target
);
359 sprintf(noncecount
, "%08d", auth
->nc
++);
360 response
= purple_cipher_http_digest_calculate_response(
361 "md5", method
, target
, NULL
, NULL
,
362 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
363 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
365 ret
= g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser
, auth
->realm
, auth
->nonce
, target
, noncecount
, response
);
370 static char *parse_attribute(const char *attrname
, const char *source
)
372 const char *tmp
, *tmp2
;
374 int len
= strlen(attrname
);
376 if (!strncmp(source
, attrname
, len
)) {
378 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
380 retval
= g_strndup(tmp
, tmp2
- tmp
);
382 retval
= g_strdup(tmp
);
388 static void fill_auth(struct sipe_account_data
*sip
, gchar
*hdr
, struct sip_auth
*auth
)
391 const char *authuser
;
394 //const char *krb5_realm;
397 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
398 // and do error checking
400 // KRB realm should always be uppercase
401 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
403 if (sip->realhostname) {
404 host = sip->realhostname;
405 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
406 host = purple_account_get_string(sip->account, "proxy", "");
408 host = sip->sipdomain;
411 authuser
= sip
->authuser
;
413 if (!authuser
|| strlen(authuser
) < 1) {
414 authuser
= sip
->username
;
418 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
422 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
423 auth
->type
= AUTH_TYPE_NTLM
;
424 parts
= g_strsplit(hdr
+5, "\", ", 0);
427 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
428 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
430 auth
->nonce
= g_memdup(purple_ntlm_parse_challenge(tmp
, &auth
->flags
), 8);
433 if ((tmp
= parse_attribute("targetname=\"",
435 g_free(auth
->target
);
438 else if ((tmp
= parse_attribute("realm=\"",
443 else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
444 g_free(auth
->opaque
);
451 if (!strstr(hdr
, "gssapi-data")) {
459 if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
460 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "setting auth type to Kerberos (3)\r\n");
461 auth
->type
= AUTH_TYPE_KERBEROS
;
462 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth - header: %s\r\n", hdr
);
463 parts
= g_strsplit(hdr
+9, "\", ", 0);
466 purple_debug_info("sipe", "krb - parts[i] %s\n", parts
[i
]);
467 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
468 /*if (krb5_auth.gss_context == NULL) {
469 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
471 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
474 if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
475 g_free(auth
->target
);
477 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
480 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
481 g_free(auth
->opaque
);
491 auth
->type
= AUTH_TYPE_DIGEST
;
492 parts
= g_strsplit(hdr
, " ", 0);
494 if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
498 else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
506 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "nonce: %s realm: %s\n", auth
->nonce
? auth
->nonce
: "(null)", auth
->realm
? auth
->realm
: "(null)");
508 g_free(auth
->digest_session_key
);
509 auth
->digest_session_key
= purple_cipher_http_digest_calculate_session_key(
510 "md5", authuser
, auth
->realm
, sip
->password
, auth
->nonce
, NULL
);
516 static void sipe_canwrite_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
518 PurpleConnection
*gc
= data
;
519 struct sipe_account_data
*sip
= gc
->proto_data
;
523 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
525 if (max_write
== 0) {
526 if (sip
->tx_handler
!= 0){
527 purple_input_remove(sip
->tx_handler
);
533 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
535 if (written
< 0 && errno
== EAGAIN
)
537 else if (written
<= 0) {
538 /*TODO: do we really want to disconnect on a failure to write?*/
539 purple_connection_error(gc
, _("Could not write"));
543 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
546 static void sipe_canwrite_cb_ssl(gpointer data
, gint src
, PurpleInputCondition cond
)
548 PurpleConnection
*gc
= data
;
549 struct sipe_account_data
*sip
= gc
->proto_data
;
553 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
555 if (max_write
== 0) {
556 if (sip
->tx_handler
!= 0) {
557 purple_input_remove(sip
->tx_handler
);
563 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
565 if (written
< 0 && errno
== EAGAIN
)
567 else if (written
<= 0) {
568 /*TODO: do we really want to disconnect on a failure to write?*/
569 purple_connection_error(gc
, _("Could not write"));
573 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
576 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
578 static void send_later_cb(gpointer data
, gint source
, const gchar
*error
)
580 PurpleConnection
*gc
= data
;
581 struct sipe_account_data
*sip
;
582 struct sip_connection
*conn
;
584 if (!PURPLE_CONNECTION_IS_VALID(gc
))
592 purple_connection_error(gc
, _("Could not connect"));
596 sip
= gc
->proto_data
;
598 sip
->connecting
= FALSE
;
599 sip
->last_keepalive
= time(NULL
);
601 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
603 /* If there is more to write now, we need to register a handler */
604 if (sip
->txbuf
->bufused
> 0)
605 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
607 conn
= connection_create(sip
, source
);
608 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
611 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
613 struct sipe_account_data
*sip
;
614 struct sip_connection
*conn
;
616 if (!PURPLE_CONNECTION_IS_VALID(gc
))
618 if (gsc
) purple_ssl_close(gsc
);
622 sip
= gc
->proto_data
;
625 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
626 sip
->connecting
= FALSE
;
627 sip
->last_keepalive
= time(NULL
);
629 conn
= connection_create(sip
, gsc
->fd
);
631 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
636 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
638 PurpleConnection
*gc
= data
;
639 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
640 if (sip
== NULL
) return;
642 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
644 /* If there is more to write now */
645 if (sip
->txbuf
->bufused
> 0) {
646 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
651 static void sendlater(PurpleConnection
*gc
, const char *buf
)
653 struct sipe_account_data
*sip
= gc
->proto_data
;
655 if (!sip
->connecting
) {
656 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
657 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
658 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
660 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
661 purple_connection_error(gc
, _("Couldn't create socket"));
664 sip
->connecting
= TRUE
;
667 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
668 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
670 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
673 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
675 struct sipe_account_data
*sip
= gc
->proto_data
;
676 time_t currtime
= time(NULL
);
677 int writelen
= strlen(buf
);
679 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
680 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
681 if (sendto(sip
->fd
, buf
, writelen
, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
682 purple_debug_info("sipe", "could not send packet\n");
691 if (sip
->tx_handler
) {
696 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
698 ret
= write(sip
->fd
, buf
, writelen
);
702 if (ret
< 0 && errno
== EAGAIN
)
704 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
709 if (ret
< writelen
) {
710 if (!sip
->tx_handler
){
712 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
715 sip
->tx_handler
= purple_input_add(sip
->fd
,
716 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
721 /* XXX: is it OK to do this? You might get part of a request sent
722 with part of another. */
723 if (sip
->txbuf
->bufused
> 0)
724 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
726 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
732 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
734 sendout_pkt(gc
, buf
);
738 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
740 GSList
*tmp
= msg
->headers
;
743 GString
*outstr
= g_string_new("");
744 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
746 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
747 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
748 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
749 tmp
= g_slist_next(tmp
);
751 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
752 sendout_pkt(sip
->gc
, outstr
->str
);
753 g_string_free(outstr
, TRUE
);
756 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
759 if (sip
->registrar
.ntlm_key
) {
760 struct sipmsg_breakdown msgbd
;
761 gchar
*signature_input_str
;
763 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
764 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
765 sip
->registrar
.ntlm_num
++;
766 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
767 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
768 if (signature_input_str
!= NULL
) {
769 msg
->signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
770 msg
->rand
= g_strdup(msgbd
.rand
);
771 msg
->num
= g_strdup(msgbd
.num
);
772 g_free(signature_input_str
);
774 sipmsg_breakdown_free(&msgbd
);
777 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
778 buf
= auth_header(sip
, &sip
->registrar
, msg
);
779 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
780 sipmsg_add_header(msg
, "Authorization", buf
);
782 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
785 } 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")) {
786 sip
->registrar
.nc
= 3;
787 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
789 buf
= auth_header(sip
, &sip
->registrar
, msg
);
790 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
793 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
797 static char *get_contact(struct sipe_account_data
*sip
)
799 return g_strdup(sip
->contact
);
804 static char *get_contact_service(struct sipe_account_data *sip)
806 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()));
807 //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);
811 static void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
812 const char *text
, const char *body
)
816 GString
*outstr
= g_string_new("");
817 struct sipe_account_data
*sip
= gc
->proto_data
;
821 sipmsg_remove_header(msg
, "ms-user-data");
823 contact
= get_contact(sip
);
824 sipmsg_remove_header(msg
, "Contact");
825 sipmsg_add_header(msg
, "Contact", contact
);
828 /* When sending the acknowlegements and errors, the content length from the original
829 message is still here, but there is no body; we need to make sure we're sending the
830 correct content length */
831 sipmsg_remove_header(msg
, "Content-Length");
834 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
835 sipmsg_add_header(msg
, "Content-Length", len
);
837 sipmsg_remove_header(msg
, "Content-Type");
838 sipmsg_add_header(msg
, "Content-Length", "0");
841 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
842 //gchar * mic = "MICTODO";
843 msg
->response
= code
;
845 sipmsg_remove_header(msg
, "Authentication-Info");
846 sign_outgoing_message(msg
, sip
, msg
->method
);
848 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
851 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
852 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
854 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
855 tmp
= g_slist_next(tmp
);
857 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
858 sendout_pkt(gc
, outstr
->str
);
859 g_string_free(outstr
, TRUE
);
862 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
864 if (trans
->msg
) sipmsg_free(trans
->msg
);
865 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
869 static struct transaction
*
870 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
872 struct transaction
*trans
= g_new0(struct transaction
, 1);
873 trans
->time
= time(NULL
);
874 trans
->msg
= (struct sipmsg
*)msg
;
875 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
876 trans
->callback
= callback
;
877 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
881 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
883 struct transaction
*trans
;
884 GSList
*transactions
= sip
->transactions
;
885 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
887 while (transactions
) {
888 trans
= transactions
->data
;
889 if (!strcmp(trans
->cseq
, cseq
)) {
892 transactions
= transactions
->next
;
898 static struct transaction
*
899 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
900 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
901 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
903 struct sipe_account_data
*sip
= gc
->proto_data
;
904 const char *addh
= "";
907 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
908 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
909 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
910 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
911 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
912 gchar
*useragent
= (gchar
*)purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
);
913 gchar
*route
= strdup("");
914 gchar
*epid
= get_epid(); // TODO generate one per account/login
915 struct transaction
*trans
;
917 if (dialog
&& dialog
->routes
)
919 GSList
*iter
= dialog
->routes
;
924 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
926 iter
= g_slist_next(iter
);
930 if (!ourtag
&& !dialog
) {
934 if (!strcmp(method
, "REGISTER")) {
935 if (sip
->regcallid
) {
937 callid
= g_strdup(sip
->regcallid
);
939 sip
->regcallid
= g_strdup(callid
);
943 if (addheaders
) addh
= addheaders
;
945 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
946 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
947 "From: <sip:%s>%s%s;epid=%s\r\n"
948 "To: <%s>%s%s%s%s\r\n"
949 "Max-Forwards: 70\r\n"
954 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
956 dialog
&& dialog
->request
? dialog
->request
: url
,
957 TRANSPORT_DESCRIPTOR
,
958 purple_network_get_my_ip(-1),
960 branch
? ";branch=" : "",
961 branch
? branch
: "",
963 ourtag
? ";tag=" : "",
964 ourtag
? ourtag
: "",
967 theirtag
? ";tag=" : "",
968 theirtag
? theirtag
: "",
969 theirepid
? ";epid=" : "",
970 theirepid
? theirepid
: "",
971 dialog
? ++dialog
->cseq
: ++sip
->cseq
,
977 body
? strlen(body
) : 0,
981 //printf ("parsing msg buf:\n%s\n\n", buf);
982 msg
= sipmsg_parse_msg(buf
);
993 sign_outgoing_message (msg
, sip
, method
);
995 buf
= sipmsg_to_string (msg
);
997 /* add to ongoing transactions */
998 trans
= transactions_add_buf(sip
, msg
, tc
);
999 sendout_pkt(gc
, buf
);
1005 static void send_soap_request_with_cb(struct sipe_account_data
*sip
, gchar
*body
, TransCallback callback
, void * payload
)
1007 gchar
*from
= g_strdup_printf("sip:%s", sip
->username
);
1008 gchar
*contact
= get_contact(sip
);
1009 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1010 "Content-Type: application/SOAP+xml\r\n",contact
);
1012 struct transaction
* tr
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1013 tr
->payload
= payload
;
1020 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1022 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
1025 static char *get_contact_register(struct sipe_account_data
*sip
)
1027 char *epid
= get_epid();
1028 char *uuid
= generateUUIDfromEPID(epid
);
1029 char *buf
= g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip
->listenport
, TRANSPORT_DESCRIPTOR
, uuid
);
1035 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1037 char *expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
1038 char *uri
= g_strdup_printf("sip:%s", sip
->sipdomain
);
1039 char *to
= g_strdup_printf("sip:%s", sip
->username
);
1040 char *contact
= get_contact_register(sip
);
1041 char *hdr
= g_strdup_printf("Contact: %s\r\n"
1042 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1043 "Event: registration\r\n"
1044 "Allow-Events: presence\r\n"
1045 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1046 "%s", contact
, expires
);
1050 sip
->registerstatus
= 1;
1052 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1053 process_register_response
);
1060 static void do_register_cb(struct sipe_account_data
*sip
)
1062 do_register_exp(sip
, -1);
1063 sip
->reregister_set
= FALSE
;
1066 static void do_register(struct sipe_account_data
*sip
)
1068 do_register_exp(sip
, -1);
1072 * Returns URI from provided To or From header.
1074 * Needs to g_free() after use.
1076 * @return URI with sip: prefix
1078 static gchar
*parse_from(const gchar
*hdr
)
1081 const gchar
*tmp
, *tmp2
= hdr
;
1083 if (!hdr
) return NULL
;
1084 purple_debug_info("sipe", "parsing address out of %s\n", hdr
);
1085 tmp
= strchr(hdr
, '<');
1087 /* i hate the different SIP UA behaviours... */
1088 if (tmp
) { /* sip address in <...> */
1090 tmp
= strchr(tmp2
, '>');
1092 from
= g_strndup(tmp2
, tmp
- tmp2
);
1094 purple_debug_info("sipe", "found < without > in From\n");
1098 tmp
= strchr(tmp2
, ';');
1100 from
= g_strndup(tmp2
, tmp
- tmp2
);
1102 from
= g_strdup(tmp2
);
1105 purple_debug_info("sipe", "got %s\n", from
);
1109 static xmlnode
* xmlnode_get_descendant(xmlnode
* parent
, ...)
1112 xmlnode
* node
= NULL
;
1115 va_start(args
, parent
);
1116 while ((name
= va_arg(args
, const char *)) != NULL
) {
1117 node
= xmlnode_get_child(parent
, name
);
1118 if (node
== NULL
) return NULL
;
1128 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1130 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1131 send_soap_request(sip
, body
);
1136 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1139 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1141 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1144 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1148 void sipe_auth_user_cb(void * data
)
1150 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1153 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1158 void sipe_deny_user_cb(void * data
)
1160 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1163 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1168 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1170 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1171 sipe_contact_allow_deny(sip
, name
, TRUE
);
1175 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1177 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1178 sipe_contact_allow_deny(sip
, name
, FALSE
);
1182 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1184 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1185 sipe_contact_set_acl(sip, name, "");
1189 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1193 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1194 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1196 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1198 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1199 if (!watchers
) return;
1201 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1202 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1203 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1204 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1206 // TODO pull out optional displayName to pass as alias
1208 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1209 job
->who
= remote_user
;
1211 purple_account_request_authorization(
1225 xmlnode_free(watchers
);
1230 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1232 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1233 if (!purple_group
) {
1234 purple_group
= purple_group_new(group
->name
);
1235 purple_blist_add_group(purple_group
, NULL
);
1239 group
->purple_group
= purple_group
;
1240 sip
->groups
= g_slist_append(sip
->groups
, group
);
1241 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1243 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1247 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1249 struct sipe_group
*group
;
1255 entry
= sip
->groups
;
1257 group
= entry
->data
;
1258 if (group
->id
== id
) {
1261 entry
= entry
->next
;
1266 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, gchar
* name
)
1268 struct sipe_group
*group
;
1274 entry
= sip
->groups
;
1276 group
= entry
->data
;
1277 if (!strcmp(group
->name
, name
)) {
1280 entry
= entry
->next
;
1286 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1289 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1290 body
= g_strdup_printf(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1291 send_soap_request(sip
, body
);
1293 g_free(group
->name
);
1294 group
->name
= g_strdup(name
);
1298 * Only appends if no such value already stored.
1301 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1302 GSList
* res
= list
;
1303 if (!g_slist_find_custom(list
, data
, func
)) {
1304 res
= g_slist_insert_sorted(list
, data
, func
);
1310 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1311 return group1
->id
- group2
->id
;
1315 * Returns string like "2 4 7 8" - group ids buddy belong to.
1318 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1321 //creating array from GList, converting int to gchar*
1322 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1323 GSList
*entry
= buddy
->groups
;
1325 struct sipe_group
* group
= entry
->data
;
1326 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1327 entry
= entry
->next
;
1331 res
= g_strjoinv(" ", ids_arr
);
1332 g_strfreev(ids_arr
);
1337 * Sends buddy update to server
1340 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1342 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1343 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1345 if (buddy
&& purple_buddy
) {
1346 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1348 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1349 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1351 body
= g_strdup_printf(SIPE_SOAP_SET_CONTACT
,
1352 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1354 send_soap_request(sip
, body
);
1360 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1362 if (msg
->response
== 200) {
1363 struct sipe_group
*group
;
1364 struct group_user_context
*ctx
= (struct group_user_context
*)tc
->payload
;
1368 struct sipe_buddy
*buddy
;
1369 group
->name
= ctx
->group_name
;
1371 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1377 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1384 group_id
= xmlnode_get_data(node
);
1391 group
= g_new0(struct sipe_group
, 1);
1392 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1395 sipe_group_add(sip
, group
);
1397 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1399 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1402 sipe_group_set_user(sip
, ctx
->user_name
);
1411 static void sipe_group_create (struct sipe_account_data
*sip
, gchar
*name
, gchar
* who
)
1413 struct group_user_context
* ctx
= g_new0(struct group_user_context
, 1);
1415 ctx
->group_name
= g_strdup(name
);
1416 ctx
->user_name
= g_strdup(who
);
1418 body
= g_strdup_printf(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1419 send_soap_request_with_cb(sip
, body
, process_add_group_response
, ctx
);
1425 * Should return FALSE if repetitive action is not needed
1427 gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1430 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1431 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1432 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1433 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1434 ret
= sched_action
->repetitive
;
1435 g_free(sched_action
->payload
);
1436 g_free(sched_action
->name
);
1437 g_free(sched_action
);
1442 * Do schedule action for execution in the future.
1443 * Non repetitive execution.
1445 * @param name of action (will be copied)
1446 * @param timeout in seconds
1447 * @action callback function
1448 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1450 void sipe_schedule_action(gchar
*name
, int timeout
, Action action
, struct sipe_account_data
*sip
, void * payload
)
1452 struct scheduled_action
*sched_action
;
1454 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name
, timeout
);
1455 sched_action
= g_new0(struct scheduled_action
, 1);
1456 sched_action
->repetitive
= FALSE
;
1457 sched_action
->name
= g_strdup(name
);
1458 sched_action
->action
= action
;
1459 sched_action
->sip
= sip
;
1460 sched_action
->payload
= payload
;
1461 sched_action
->timeout_handler
= purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1462 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1463 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1467 * Kills action timer effectively cancelling
1470 * @param name of action
1472 void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, gchar
*name
)
1476 if (!sip
->timeouts
|| !name
) return;
1478 entry
= sip
->timeouts
;
1480 struct scheduled_action
*sched_action
= entry
->data
;
1481 if(!strcmp(sched_action
->name
, name
)) {
1482 GSList
*to_delete
= entry
;
1483 entry
= entry
->next
;
1484 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1485 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1486 purple_timeout_remove(sched_action
->timeout_handler
);
1487 g_free(sched_action
->payload
);
1488 g_free(sched_action
->name
);
1489 g_free(sched_action
);
1491 entry
= entry
->next
;
1496 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1498 static gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1500 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1502 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1507 static void sipe_subscribe_resource_uri(const char *name
, gpointer value
, gchar
**resources_uri
)
1509 gchar
*tmp
= *resources_uri
;
1510 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1515 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1516 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1517 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1518 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1519 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1522 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
){
1523 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
1524 gchar
*contact
= get_contact(sip
);
1527 gchar
*resources_uri
= g_strdup("");
1528 gchar
*require
= "";
1530 gchar
*content_type
;
1532 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1534 if (sip
->msrtc_event_categories
) {
1535 require
= ", categoryList";
1536 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1537 content_type
= "application/msrtc-adrl-categorylist+xml";
1538 content
= g_strdup_printf(
1539 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1540 "<action name=\"subscribe\" id=\"63792024\">\n"
1541 "<adhocList>\n%s</adhocList>\n"
1542 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1543 "<category name=\"note\"/>\n"
1544 "<category name=\"state\"/>\n"
1547 "</batchSub>", sip
->username
, resources_uri
);
1549 content_type
= "application/adrl+xml";
1550 content
= g_strdup_printf(
1551 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1552 "<create xmlns=\"\">\n%s</create>\n"
1553 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1555 g_free(resources_uri
);
1557 request
= g_strdup_printf(
1558 "Require: adhoclist%s\r\n"
1559 "Supported: eventlist\r\n"
1560 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1561 "Supported: ms-piggyback-first-notify\r\n"
1562 "Supported: com.microsoft.autoextend\r\n"
1563 "Supported: ms-benotify\r\n"
1564 "Proxy-Require: ms-benotify\r\n"
1565 "Event: presence\r\n"
1566 "Content-Type: %s\r\n"
1567 "Contact: %s\r\n", require
, accept
, content_type
, contact
);
1570 /* subscribe to buddy presence */
1571 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1572 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1580 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1581 * The user sends a single SUBSCRIBE request to the subscribed contact.
1582 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1586 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, const char * buddy_name
)
1588 gchar
*to
= strstr(buddy_name
, "sip:") ? g_strdup(buddy_name
) : g_strdup_printf("sip:%s", buddy_name
);
1589 gchar
*tmp
= get_contact(sip
);
1592 request
= g_strdup_printf(
1593 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1594 "Supported: ms-piggyback-first-notify\r\n"
1595 "Supported: com.microsoft.autoextend\r\n"
1596 "Supported: ms-benotify\r\n"
1597 "Proxy-Require: ms-benotify\r\n"
1598 "Event: presence\r\n"
1599 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1600 "Contact: %s\r\n", tmp
);
1602 content
= g_strdup_printf(
1603 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1604 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1605 "<resource uri=\"%s\"/>\n"
1607 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1608 "<category name=\"note\"/>\n"
1609 "<category name=\"state\"/>\n"
1612 "</batchSub>", sip
->username
, to
1617 /* subscribe to buddy presence */
1618 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1625 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
1627 if (!purple_status_is_active(status
))
1631 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
1634 g_free(sip
->status
);
1635 sip
->status
= g_strdup(purple_status_get_id(status
));
1636 send_presence_info(sip
);
1642 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
, const char *alias
)
1644 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1645 sipe_group_set_user(sip
, name
);
1649 sipe_group_buddy(PurpleConnection
*gc
,
1651 const char *old_group_name
,
1652 const char *new_group_name
)
1654 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1655 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1656 struct sipe_group
* old_group
= NULL
;
1657 struct sipe_group
* new_group
;
1659 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1660 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
1662 if(!buddy
) { // buddy not in roaming list
1666 if (old_group_name
) {
1667 old_group
= sipe_group_find_by_name(sip
, g_strdup(old_group_name
));
1669 new_group
= sipe_group_find_by_name(sip
, g_strdup(new_group_name
));
1672 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
1673 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
1677 sipe_group_create(sip
, g_strdup(new_group_name
), g_strdup(who
));
1679 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
1680 sipe_group_set_user(sip
, who
);
1684 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1686 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1687 struct sipe_buddy
*b
;
1689 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1691 // Prepend sip: if needed
1692 if (strncmp("sip:", buddy
->name
, 4)) {
1693 gchar
*buf
= g_strdup_printf("sip:%s", buddy
->name
);
1694 purple_blist_rename_buddy(buddy
, buf
);
1698 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
1699 b
= g_new0(struct sipe_buddy
, 1);
1700 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy
->name
);
1701 b
->name
= g_strdup(buddy
->name
);
1702 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
1703 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
1704 sipe_subscribe_presence_single(sip
, b
->name
); //@TODO should go to callback
1706 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy
->name
);
1710 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
1712 g_free(buddy
->name
);
1713 g_free(buddy
->annotation
);
1714 g_free(buddy
->device_name
);
1715 g_slist_free(buddy
->groups
);
1720 * Unassociates buddy from group first.
1721 * Then see if no groups left, removes buddy completely.
1722 * Otherwise updates buddy groups on server.
1724 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1726 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1727 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
1728 struct sipe_group
*g
= NULL
;
1730 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1735 g
= sipe_group_find_by_name(sip
, group
->name
);
1739 b
->groups
= g_slist_remove(b
->groups
, g
);
1740 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
1743 if (g_slist_length(b
->groups
) < 1) {
1744 gchar
*action_name
= g_strdup_printf("<%s><%s>", "presence", buddy
->name
);
1745 sipe_cancel_scheduled_action(sip
, action_name
);
1746 g_free(action_name
);
1748 g_hash_table_remove(sip
->buddies
, buddy
->name
);
1751 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
1752 send_soap_request(sip
, body
);
1758 //updates groups on server
1759 sipe_group_set_user(sip
, b
->name
);
1765 sipe_rename_group(PurpleConnection
*gc
,
1766 const char *old_name
,
1768 GList
*moved_buddies
)
1770 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1771 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, g_strdup(old_name
));
1773 sipe_group_rename(sip
, s_group
, group
->name
);
1775 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
1780 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
1782 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1783 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
1786 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
1787 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
1788 send_soap_request(sip
, body
);
1791 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
1792 g_free(s_group
->name
);
1794 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
1798 static GList
*sipe_status_types(PurpleAccount
*acc
)
1800 PurpleStatusType
*type
;
1801 GList
*types
= NULL
;
1804 type
= purple_status_type_new_with_attrs(
1805 PURPLE_STATUS_AVAILABLE
, NULL
, "Online", TRUE
, TRUE
, FALSE
,
1806 // Translators: noun
1807 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1809 types
= g_list_append(types
, type
);
1812 type
= purple_status_type_new_with_attrs(
1813 PURPLE_STATUS_UNAVAILABLE
, "busy", _("Busy"), TRUE
, TRUE
, FALSE
,
1814 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1816 types
= g_list_append(types
, type
);
1818 // Do Not Disturb (Not let user set it)
1819 type
= purple_status_type_new_with_attrs(
1820 PURPLE_STATUS_UNAVAILABLE
, "do-not-disturb", "Do Not Disturb", TRUE
, FALSE
, FALSE
,
1821 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1823 types
= g_list_append(types
, type
);
1826 type
= purple_status_type_new_with_attrs(
1827 PURPLE_STATUS_AWAY
, "be-right-back", _("Be Right Back"), TRUE
, TRUE
, FALSE
,
1828 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1830 types
= g_list_append(types
, type
);
1833 type
= purple_status_type_new_with_attrs(
1834 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
1835 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1837 types
= g_list_append(types
, type
);
1840 type
= purple_status_type_new_with_attrs(
1841 PURPLE_STATUS_UNAVAILABLE
, "on-the-phone", _("On The Phone"), TRUE
, TRUE
, FALSE
,
1842 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1844 types
= g_list_append(types
, type
);
1847 type
= purple_status_type_new_with_attrs(
1848 PURPLE_STATUS_AWAY
, "out-to-lunch", "Out To Lunch", TRUE
, TRUE
, FALSE
,
1849 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1851 types
= g_list_append(types
, type
);
1854 type
= purple_status_type_new_full(
1855 PURPLE_STATUS_INVISIBLE
, NULL
, "Appear Offline", TRUE
, TRUE
, FALSE
);
1856 types
= g_list_append(types
, type
);
1859 type
= purple_status_type_new_full(
1860 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
1861 types
= g_list_append(types
, type
);
1867 * A callback for g_hash_table_foreach
1869 static void sipe_buddy_subscribe_cb(char *name
, struct sipe_buddy
*buddy
, struct sipe_account_data
*sip
)
1871 sipe_subscribe_presence_single(sip
, buddy
->name
);
1875 * Removes entries from purple buddy list
1876 * that does not correspond ones in the roaming contact list.
1878 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
1879 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
1880 GSList
*entry
= buddies
;
1881 struct sipe_buddy
*buddy
;
1885 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
1886 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
1889 g
= purple_buddy_get_group(b
);
1890 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
1892 gboolean in_sipe_groups
= FALSE
;
1893 GSList
*entry2
= buddy
->groups
;
1895 struct sipe_group
*group
= entry2
->data
;
1896 if (!strcmp(group
->name
, g
->name
)) {
1897 in_sipe_groups
= TRUE
;
1900 entry2
= entry2
->next
;
1902 if(!in_sipe_groups
) {
1903 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
1904 purple_blist_remove_buddy(b
);
1907 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
1908 purple_blist_remove_buddy(b
);
1910 entry
= entry
->next
;
1914 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1916 int len
= msg
->bodylen
;
1918 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
1921 const gchar
*contacts_delta
;
1922 xmlnode
*group_node
;
1923 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
1927 /* Convert the contact from XML to Purple Buddies */
1928 isc
= xmlnode_from_str(msg
->body
, len
);
1933 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
1934 if (contacts_delta
) {
1935 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
1939 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
1940 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1941 const char *name
= xmlnode_get_attrib(group_node
, "name");
1943 if (!strncmp(name
, "~", 1)) {
1945 name
= "Other Contacts";
1947 group
->name
= g_strdup(name
);
1948 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
1950 sipe_group_add(sip
, group
);
1953 // Make sure we have at least one group
1954 if (g_slist_length(sip
->groups
) == 0) {
1955 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1956 PurpleGroup
*purple_group
;
1958 group
->name
= g_strdup("Other Contacts");
1960 purple_group
= purple_group_new(group
->name
);
1961 purple_blist_add_group(purple_group
, NULL
);
1962 sip
->groups
= g_slist_append(sip
->groups
, group
);
1965 /* Parse contacts */
1966 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
1967 gchar
* uri
= g_strdup(xmlnode_get_attrib(item
, "uri"));
1968 gchar
* name
= g_strdup(xmlnode_get_attrib(item
, "name"));
1969 gchar
* groups
= g_strdup(xmlnode_get_attrib(item
, "groups"));
1970 gchar
* buddy_name
= g_strdup_printf("sip:%s", uri
);
1971 gchar
**item_groups
;
1972 struct sipe_group
*group
= NULL
;
1973 struct sipe_buddy
*buddy
= NULL
;
1976 // assign to group Other Contacts if nothing else received
1977 if(!groups
|| !strcmp("", groups
) ) {
1978 group
= sipe_group_find_by_name(sip
, "Other Contacts");
1979 groups
= group
? g_strdup_printf("%d", group
->id
) : "1";
1982 item_groups
= g_strsplit(groups
, " ", 0);
1984 while (item_groups
[i
]) {
1985 group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
1987 // If couldn't find the right group for this contact, just put them in the first group we have
1988 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
1989 group
= sip
->groups
->data
;
1992 if (group
!= NULL
) {
1993 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
1995 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
1996 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
1999 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2000 if (name
!= NULL
&& strlen(name
) != 0) {
2001 purple_blist_alias_buddy(b
, name
);
2006 buddy
= g_new0(struct sipe_buddy
, 1);
2007 buddy
->name
= g_strdup(b
->name
);
2008 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2011 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2013 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2015 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2020 } // while, contact groups
2021 g_strfreev(item_groups
);
2031 sipe_cleanup_local_blist(sip
);
2033 //subscribe to buddies
2034 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2035 //if(sip->msrtc_event_categories){
2036 sipe_subscribe_presence_batched(sip
);
2038 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2040 sip
->subscribed_buddies
= TRUE
;
2047 * Subscribe roaming contacts
2049 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2051 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2052 gchar
*tmp
= get_contact(sip
);
2053 gchar
*hdr
= g_strdup_printf(
2054 "Event: vnd-microsoft-roaming-contacts\r\n"
2055 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2056 "Supported: com.microsoft.autoextend\r\n"
2057 "Supported: ms-benotify\r\n"
2058 "Proxy-Require: ms-benotify\r\n"
2059 "Supported: ms-piggyback-first-notify\r\n"
2060 "Contact: %s\r\n", tmp
);
2063 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2068 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2070 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2071 gchar
*tmp
= get_contact(sip
);
2072 gchar
*hdr
= g_strdup_printf(
2073 "Event: presence.wpending\r\n"
2074 "Accept: text/xml+msrtc.wpending\r\n"
2075 "Supported: com.microsoft.autoextend\r\n"
2076 "Supported: ms-benotify\r\n"
2077 "Proxy-Require: ms-benotify\r\n"
2078 "Supported: ms-piggyback-first-notify\r\n"
2079 "Contact: %s\r\n", tmp
);
2082 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2087 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2089 const gchar
*contacts_delta
;
2092 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2098 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2101 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2108 * When we receive some self (BE) NOTIFY with a new subscriber
2109 * we sends a setSubscribers request to him [SIP-PRES]
2113 static void sipe_process_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2120 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2122 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2125 contact
= get_contact(sip
);
2126 to
= g_strdup_printf("sip:%s", sip
->username
);
2128 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
2133 user
= xmlnode_get_attrib(node
, "user");
2134 if (!user
) continue;
2136 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
2138 hdr
= g_strdup_printf(
2140 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
2142 body
= g_strdup_printf(
2143 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2144 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2145 "</setSubscribers>", user
);
2147 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
2157 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2159 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2160 gchar
*tmp
= get_contact(sip
);
2161 gchar
*hdr
= g_strdup_printf(
2162 "Event: vnd-microsoft-roaming-ACL\r\n"
2163 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2164 "Supported: com.microsoft.autoextend\r\n"
2165 "Supported: ms-benotify\r\n"
2166 "Proxy-Require: ms-benotify\r\n"
2167 "Supported: ms-piggyback-first-notify\r\n"
2168 "Contact: %s\r\n", tmp
);
2171 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2177 * To request for presence information about the user, access level settings that have already been configured by the user
2178 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2179 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2182 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2184 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2185 gchar
*tmp
= get_contact(sip
);
2186 gchar
*hdr
= g_strdup_printf(
2187 "Event: vnd-microsoft-roaming-self\r\n"
2188 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2189 "Supported: com.microsoft.autoextend\r\n"
2190 "Supported: ms-benotify\r\n"
2191 "Proxy-Require: ms-benotify\r\n"
2192 "Supported: ms-piggyback-first-notify\r\n"
2194 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
2196 gchar
*body
=g_strdup(
2197 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2198 "<roaming type=\"categories\"/>"
2199 "<roaming type=\"containers\"/>"
2200 "<roaming type=\"subscribers\"/></roamingList>");
2203 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2209 /** Subscription for provisioning information to help with initial
2210 * configuration. This subscription is a one-time query (denoted by the Expires header,
2211 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2212 * configuration, meeting policies, and policy settings that Communicator must enforce.
2213 * TODO: for what we need this information.
2216 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2218 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2219 gchar
*tmp
= get_contact(sip
);
2220 gchar
*hdr
= g_strdup_printf(
2221 "Event: vnd-microsoft-provisioning-v2\r\n"
2222 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2223 "Supported: com.microsoft.autoextend\r\n"
2224 "Supported: ms-benotify\r\n"
2225 "Proxy-Require: ms-benotify\r\n"
2226 "Supported: ms-piggyback-first-notify\r\n"
2229 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
2230 gchar
*body
= g_strdup(
2231 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2232 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2233 "<provisioningGroup name=\"ucPolicy\"/>"
2234 "</provisioningGroupList>");
2237 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2243 /* IM Session (INVITE and MESSAGE methods) */
2245 static struct sip_im_session
* find_im_session (struct sipe_account_data
*sip
, const char *who
)
2247 struct sip_im_session
*session
;
2249 if (sip
== NULL
|| who
== NULL
) {
2253 entry
= sip
->im_sessions
;
2255 session
= entry
->data
;
2256 if ((who
!= NULL
&& !strcmp(who
, session
->with
))) {
2259 entry
= entry
->next
;
2264 static struct sip_im_session
* find_or_create_im_session (struct sipe_account_data
*sip
, const char *who
)
2266 struct sip_im_session
*session
= find_im_session(sip
, who
);
2268 session
= g_new0(struct sip_im_session
, 1);
2269 session
->with
= g_strdup(who
);
2270 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2275 static void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2277 struct sip_dialog
*dialog
= session
->dialog
;
2280 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2283 entry
= dialog
->routes
;
2285 g_free(entry
->data
);
2286 entry
= g_slist_remove(entry
, entry
->data
);
2288 entry
= dialog
->supported
;
2290 g_free(entry
->data
);
2291 entry
= g_slist_remove(entry
, entry
->data
);
2293 g_free(dialog
->callid
);
2294 g_free(dialog
->ourtag
);
2295 g_free(dialog
->theirtag
);
2296 g_free(dialog
->theirepid
);
2297 g_free(dialog
->request
);
2299 g_free(session
->dialog
);
2301 entry
= session
->outgoing_message_queue
;
2303 g_free(entry
->data
);
2304 entry
= g_slist_remove(entry
, entry
->data
);
2307 g_free(session
->with
);
2312 process_options_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2314 gboolean ret
= TRUE
;
2316 if (msg
->response
!= 200) {
2317 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
2321 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
2327 * Asks UA/proxy about its capabilities.
2329 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
2331 gchar
*to
= strstr(who
, "sip:") ? g_strdup(who
) : g_strdup_printf("sip:%s", who
);
2332 gchar
*contact
= get_contact(sip
);
2334 request
= g_strdup_printf(
2335 "Accept: application/sdp\r\n"
2336 "Contact: %s\r\n", contact
);
2340 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
2346 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2348 char *msg
, *msg_tmp
;
2349 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2350 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2352 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2353 "possibly because one or more persons are offline:\n%s") ,
2355 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2360 static void sipe_im_remove_first_from_queue (struct sip_im_session
* session
);
2361 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2364 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2366 gboolean ret
= TRUE
;
2367 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2368 struct sip_im_session
* session
= find_im_session(sip
, with
);
2369 struct sip_dialog
*dialog
;
2372 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2377 if (msg
->response
!= 200) {
2378 gchar
*queued_msg
= NULL
;
2379 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2381 if (session
->outgoing_message_queue
) {
2382 queued_msg
= session
->outgoing_message_queue
->data
;
2384 sipe_present_message_undelivered_err(with
, sip
, queued_msg
);
2385 im_session_destroy(sip
, session
);
2390 dialog
= session
->dialog
;
2392 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2396 sipe_im_remove_first_from_queue(session
);
2397 sipe_im_process_queue(sip
, session
);
2402 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_im_session
* session
, const char *msg
)
2412 if (strncmp("sip:", session
->with
, 4)) {
2413 fullto
= g_strdup_printf("sip:%s", session
->with
);
2415 fullto
= g_strdup(session
->with
);
2418 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2419 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2421 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2424 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2427 msgr
= g_strdup("");
2430 tmp
= get_contact(sip
);
2431 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2432 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2433 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2434 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2439 send_sip_request(sip
->gc
, "MESSAGE", fullto
, fullto
, hdr
, msgtext
, session
->dialog
, process_message_response
);
2447 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2449 GSList
*entry
= session
->outgoing_message_queue
;
2451 char *queued_msg
= entry
->data
;
2452 sipe_send_message(sip
, session
, queued_msg
);
2457 sipe_im_remove_first_from_queue (struct sip_im_session
* session
)
2459 if (session
&& session
->outgoing_message_queue
) {
2460 char *queued_msg
= session
->outgoing_message_queue
->data
;
2461 // Remove from the queue and free the string
2462 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2468 sipe_get_route_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2470 GSList
*hdr
= msg
->headers
;
2471 struct siphdrelement
*elem
;
2477 if(!g_ascii_strcasecmp(elem
->name
, "Record-Route"))
2479 gchar
*route
= sipmsg_find_part_of_header(elem
->value
, "<", ">", NULL
);
2480 dialog
->routes
= g_slist_append(dialog
->routes
, route
);
2482 hdr
= g_slist_next(hdr
);
2487 dialog
->routes
= g_slist_reverse(dialog
->routes
);
2492 dialog
->request
= dialog
->routes
->data
;
2493 dialog
->routes
= g_slist_remove(dialog
->routes
, dialog
->routes
->data
);
2496 contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Contact"), "<", ">", NULL
);
2497 dialog
->routes
= g_slist_append(dialog
->routes
, contact
);
2501 sipe_get_supported_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2503 GSList
*hdr
= msg
->headers
;
2504 struct siphdrelement
*elem
;
2508 if(!g_ascii_strcasecmp(elem
->name
, "Supported")
2509 && !g_slist_find_custom(dialog
->supported
, elem
->value
, (GCompareFunc
)strcmp
))
2511 dialog
->supported
= g_slist_append(dialog
->supported
, g_strdup(elem
->value
));
2514 hdr
= g_slist_next(hdr
);
2519 sipe_parse_dialog(struct sipmsg
* msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2521 gchar
*us
= outgoing
? "From" : "To";
2522 gchar
*them
= outgoing
? "To" : "From";
2524 dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2525 dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, us
));
2526 dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, them
));
2527 if (!dialog
->theirepid
) {
2528 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", ";", NULL
);
2530 if (!dialog
->theirepid
) {
2531 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", NULL
, NULL
);
2534 sipe_get_route_header(msg
, dialog
, outgoing
);
2535 sipe_get_supported_header(msg
, dialog
, outgoing
);
2540 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2542 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2543 struct sip_im_session
* session
= find_im_session(sip
, with
);
2544 struct sip_dialog
*dialog
;
2547 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2552 if (msg
->response
!= 200) {
2553 gchar
*queued_msg
= NULL
;
2554 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2556 if (session
->outgoing_message_queue
) {
2557 queued_msg
= session
->outgoing_message_queue
->data
;
2559 sipe_present_message_undelivered_err(with
, sip
, queued_msg
);
2561 im_session_destroy(sip
, session
);
2566 dialog
= session
->dialog
;
2568 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2573 sipe_parse_dialog(msg
, dialog
, TRUE
);
2576 send_sip_request(sip
->gc
, "ACK", session
->with
, session
->with
, NULL
, NULL
, dialog
, NULL
);
2577 session
->outgoing_invite
= NULL
;
2578 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)strcmp
)) {
2579 sipe_im_remove_first_from_queue(session
);
2581 sipe_im_process_queue(sip
, session
);
2589 static void sipe_invite(struct sipe_account_data
*sip
, struct sip_im_session
* session
, gchar
* msg_body
)
2598 char *ms_text_format
;
2602 if (session
->dialog
) {
2603 purple_debug_info("sipe", "session with %s already has a dialog open\n", session
->with
);
2607 session
->dialog
= g_new0(struct sip_dialog
, 1);
2609 if (strstr(session
->with
, "sip:")) {
2610 to
= g_strdup(session
->with
);
2612 to
= g_strdup_printf("sip:%s", session
->with
);
2615 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
2616 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
2618 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2622 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2626 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
2627 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
2632 contact
= get_contact(sip
);
2633 hdr
= g_strdup_printf(
2635 "Content-Type: application/sdp\r\n",
2636 contact
, ms_text_format
);
2637 g_free(ms_text_format
);
2639 body
= g_strdup_printf(
2641 "o=- 0 0 IN IP4 %s\r\n"
2645 "m=message %d sip null\r\n"
2646 "a=accept-types:text/plain text/html image/gif "
2647 "multipart/alternative application/im-iscomposing+xml\r\n",
2648 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
2650 session
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
2651 to
, to
, hdr
, body
, session
->dialog
, process_invite_response
);
2660 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2663 send_sip_request(sip
->gc
, "BYE", session
->with
, session
->with
, NULL
, NULL
, session
->dialog
, NULL
);
2664 im_session_destroy(sip
, session
);
2669 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
2671 struct sipe_account_data
*sip
= gc
->proto_data
;
2673 purple_debug_info("sipe", "conversation with %s closed\n", who
);
2674 im_session_close(sip
, find_im_session(sip
, who
));
2678 im_session_close_all (struct sipe_account_data
*sip
)
2680 GSList
*entry
= sip
->im_sessions
;
2682 im_session_close (sip
, entry
->data
);
2683 entry
= sip
->im_sessions
;
2687 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
2689 struct sipe_account_data
*sip
;
2692 struct sip_im_session
*session
;
2694 sip
= gc
->proto_data
;
2696 text
= g_strdup(what
);
2698 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
2700 session
= find_or_create_im_session(sip
, who
);
2702 // Queue the message
2703 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, text
);
2705 if (session
->dialog
&& session
->dialog
->callid
) {
2706 sipe_im_process_queue(sip
, session
);
2707 } else if (!session
->outgoing_invite
) {
2708 // Need to send the INVITE to get the outgoing dialog setup
2709 sipe_invite(sip
, session
, text
);
2718 /* End IM Session (INVITE and MESSAGE methods) */
2721 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
2723 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2724 struct sip_im_session
*session
;
2726 if (state
== PURPLE_NOT_TYPING
)
2729 session
= find_im_session(sip
, who
);
2731 if (session
&& session
->dialog
) {
2732 send_sip_request(gc
, "INFO", who
, who
,
2733 "Content-Type: application/xml\r\n",
2734 SIPE_SEND_TYPING
, session
->dialog
, NULL
);
2737 return SIPE_TYPING_SEND_TIMEOUT
;
2740 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
2742 GSList
*tmp
= sip
->transactions
;
2743 time_t currtime
= time(NULL
);
2745 struct transaction
*trans
= tmp
->data
;
2747 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime
-trans
->time
);
2748 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
2751 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
2753 sendout_sipmsg(sip
, trans
->msg
);
2760 static void do_reauthenticate_cb(struct sipe_account_data
*sip
)
2762 /* register again when security token expires */
2763 /* we have to start a new authentication as the security token
2764 * is almost expired by sending a not signed REGISTER message */
2765 purple_debug_info("sipe", "do a full reauthentication\n");
2766 sipe_auth_free(&sip
->registrar
);
2767 sip
->registerstatus
= 0;
2769 sip
->reauthenticate_set
= FALSE
;
2772 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2776 gboolean found
= FALSE
;
2778 from
= parse_from(sipmsg_find_header(msg
, "From"));
2782 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
2784 contenttype
= sipmsg_find_header(msg
, "Content-Type");
2785 if (!contenttype
|| !strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
2786 gchar
*msgr
= sipmsg_find_part_of_header(contenttype
, "msgr=", NULL
, NULL
);
2787 gchar
*x_mms_im_format
= sipmsg_get_x_mms_im_format(msgr
);
2789 gchar
*body_esc
= g_markup_escape_text(msg
->body
, -1);
2790 gchar
*body_html
= sipmsg_apply_x_mms_im_format(x_mms_im_format
, body_esc
);
2793 g_free(x_mms_im_format
);
2795 serv_got_im(sip
->gc
, from
, body_html
, 0, time(NULL
));
2797 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2799 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
2800 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2805 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2809 state
= xmlnode_get_child(isc
, "state");
2812 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2817 statedata
= xmlnode_get_data(state
);
2819 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
2820 else serv_got_typing_stopped(sip
->gc
, from
);
2825 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2829 purple_debug_info("sipe", "got unknown mime-type");
2830 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
2835 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2837 gchar
*ms_text_format
;
2840 struct sip_im_session
*session
;
2842 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? msg
->body
: "");
2844 // Only accept text invitations
2845 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
2846 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
2850 from
= parse_from(sipmsg_find_header(msg
, "From"));
2851 session
= find_or_create_im_session (sip
, from
);
2853 if (session
->dialog
) {
2854 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2856 session
->dialog
= g_new0(struct sip_dialog
, 1);
2858 sipe_parse_dialog(msg
, session
->dialog
, FALSE
);
2860 session
->dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2861 session
->dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, "To"));
2862 session
->dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, "From"));
2863 session
->dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "From"), "epid=", NULL
, NULL
);
2866 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2869 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2870 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
2871 if (ms_text_format
&& !strncmp(ms_text_format
, "text/plain", 10)) {
2872 gchar
*msgr
= sipmsg_find_part_of_header(ms_text_format
, "msgr=", ";", NULL
);
2873 gchar
*x_mms_im_format
= sipmsg_get_x_mms_im_format(msgr
);
2875 gchar
*ms_body
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
2878 gchar
*body
= purple_base64_decode(ms_body
, NULL
);
2879 gchar
*body_esc
= g_markup_escape_text(body
, -1);
2880 gchar
*body_html
= sipmsg_apply_x_mms_im_format(x_mms_im_format
, body_esc
);
2884 serv_got_im(sip
->gc
, from
, body_html
, 0, time(NULL
));
2886 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message reciept
2888 g_free(x_mms_im_format
);
2892 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
2893 sipmsg_remove_header(msg
, "Ms-Text-Format");
2894 sipmsg_remove_header(msg
, "EndPoints");
2895 sipmsg_remove_header(msg
, "User-Agent");
2896 sipmsg_remove_header(msg
, "Roster-Manager");
2898 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
2899 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2901 body
= g_strdup_printf(
2903 "o=- 0 0 IN IP4 %s\r\n"
2907 "m=message %d sip sip:%s\r\n"
2908 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2909 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2910 sip
->realport
, sip
->username
);
2911 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
2915 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2919 struct sip_im_session
*session
;
2922 from
= parse_from(sipmsg_find_header(msg
, "From"));
2923 session
= find_or_create_im_session (sip
, from
);
2925 if (session
->dialog
) {
2926 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2928 session
->dialog
= g_new0(struct sip_dialog
, 1);
2930 sipe_parse_dialog(msg
, session
->dialog
, FALSE
);
2932 session
->dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2933 session
->dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, "To"));
2934 session
->dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, "From"));
2935 session
->dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "From"), "epid=", NULL
, NULL
);
2938 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2943 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
2944 sipmsg_remove_header(msg
, "EndPoints");
2945 sipmsg_remove_header(msg
, "User-Agent");
2947 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
2948 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
2950 body
= g_strdup_printf(
2952 "o=- 0 0 IN IP4 0.0.0.0\r\n"
2954 "c=IN IP4 0.0.0.0\r\n"
2956 "m=message %d sip sip:%s\r\n"
2957 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2958 sip
->realport
, sip
->username
);
2959 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
2963 static void sipe_connection_cleanup(struct sipe_account_data
*);
2964 static void create_connection(struct sipe_account_data
*, gchar
*, int);
2966 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
2970 const gchar
*expires_header
;
2972 GSList
*hdr
= msg
->headers
;
2973 struct siphdrelement
*elem
;
2975 expires_header
= sipmsg_find_header(msg
, "Expires");
2976 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
2977 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
2979 switch (msg
->response
) {
2982 sip
->registerstatus
= 0;
2985 gchar
*contact_hdr
= NULL
;
2990 if (!sip
->reregister_set
) {
2991 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
2992 sipe_schedule_action(action_name
, expires
, (Action
) do_register_cb
, sip
, NULL
);
2993 g_free(action_name
);
2994 sip
->reregister_set
= TRUE
;
2997 sip
->registerstatus
= 3;
2999 if (!sip
->reauthenticate_set
) {
3000 /* we have to reauthenticate as our security token expires
3001 after eight hours (be five minutes early) */
3002 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
3003 guint reauth_timeout
= (8 * 3600) - 360;
3004 sipe_schedule_action(action_name
, reauth_timeout
, (Action
) do_reauthenticate_cb
, sip
, NULL
);
3005 g_free(action_name
);
3006 sip
->reauthenticate_set
= TRUE
;
3009 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
3012 uuid
= generateUUIDfromEPID(epid
);
3015 // There can be multiple Contact headers (one per location where the user is logged in) so
3016 // make sure to only get the one for this uuid
3017 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
3018 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
3019 if (valid_contact
) {
3020 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
3021 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3022 g_free(valid_contact
);
3025 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3030 g_free(sip
->contact
);
3032 sip
->contact
= g_strdup_printf("<%s>", gruu
);
3035 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3036 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
);
3038 sip
->msrtc_event_categories
= FALSE
;
3043 if(!g_ascii_strcasecmp(elem
->name
, "Supported"))
3045 if (strstr(elem
->value
, "msrtc-event-categories")){
3046 sip
->msrtc_event_categories
= TRUE
;
3048 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s, %d\n", elem
->value
, sip
->msrtc_event_categories
);
3050 hdr
= g_slist_next(hdr
);
3053 if (!sip
->subscribed
) { //do it just once, not every re-register
3054 tmp
= sipmsg_find_header(msg
, "Allow-Events");
3055 sipe_options_request(sip
, sip
->sipdomain
);
3056 if (tmp
&& strstr(tmp
, "vnd-microsoft-provisioning")){
3057 sipe_subscribe_roaming_contacts(sip
, msg
);
3059 sipe_subscribe_roaming_acl(sip
, msg
);
3060 sipe_subscribe_roaming_self(sip
, msg
);
3061 sipe_subscribe_roaming_provisioning(sip
, msg
);
3062 sipe_subscribe_presence_wpending(sip
, msg
);
3063 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
3064 sip
->subscribed
= TRUE
;
3067 if (purple_account_get_bool(sip
->account
, "clientkeepalive", FALSE
)) {
3068 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Setting user defined keepalive\n");
3069 sip
->keepalive_timeout
= purple_account_get_int(sip
->account
, "keepalive", 0);
3071 tmp
= sipmsg_find_header(msg
, "ms-keep-alive");
3073 sipe_keep_alive_timeout(sip
, tmp
);
3077 // Should we remove the transaction here?
3078 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
3079 transactions_remove(sip
, tc
);
3084 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
3086 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
3087 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
3091 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
3094 tmp
= g_strsplit(parts
[0], ":", 0);
3095 hostname
= g_strdup(tmp
[0]);
3096 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
3100 tmp
= g_strsplit(parts
[i
], "=", 0);
3102 if (g_strcasecmp("transport", tmp
[0]) == 0) {
3103 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
3104 transport
= SIPE_TRANSPORT_TCP
;
3105 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
3106 transport
= SIPE_TRANSPORT_UDP
;
3115 /* Close old connection */
3116 sipe_connection_cleanup(sip
);
3118 /* Create new connection */
3119 sip
->transport
= transport
;
3120 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3121 hostname
, port
, TRANSPORT_DESCRIPTOR
);
3122 create_connection(sip
, hostname
, port
);
3128 if (sip
->registerstatus
!= 2) {
3129 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
3130 if (sip
->registrar
.retries
> 3) {
3131 sip
->gc
->wants_to_die
= TRUE
;
3132 purple_connection_error(sip
->gc
, _("Wrong Password"));
3135 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3136 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3138 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3140 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3141 fill_auth(sip
, tmp
, &sip
->registrar
);
3142 sip
->registerstatus
= 2;
3143 if (sip
->account
->disconnecting
) {
3144 do_register_exp(sip
, 0);
3152 const gchar
*warning
= sipmsg_find_header(msg
, "Warning");
3153 if (warning
!= NULL
) {
3155 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3157 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
3158 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
3161 warning
= _("You have been rejected by the server");
3164 sip
->gc
->wants_to_die
= TRUE
;
3165 purple_connection_error(sip
->gc
, warning
);
3171 const gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3172 if (warning
!= NULL
) {
3173 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3174 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
3177 warning
= _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3180 sip
->gc
->wants_to_die
= TRUE
;
3181 purple_connection_error(sip
->gc
, warning
);
3187 const gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3188 if (warning
!= NULL
) {
3189 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3190 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
3193 warning
= _("Service unavailable: no reason given");
3196 sip
->gc
->wants_to_die
= TRUE
;
3197 purple_connection_error(sip
->gc
, warning
);
3205 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3208 xmlnode
*xn_categories
;
3209 xmlnode
*xn_category
;
3212 const char *activity
= NULL
;
3214 xn_categories
= xmlnode_from_str(data
, len
);
3215 uri
= xmlnode_get_attrib(xn_categories
, "uri");
3217 purple_debug_info("sipe", "process_incoming_notify_rlmi\n");
3219 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
3221 xn_category
= xmlnode_get_next_twin(xn_category
) )
3223 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
3225 if (!strcmp(attrVar
, "note"))
3228 struct sipe_buddy
*sbuddy
;
3229 xn_node
= xmlnode_get_child(xn_category
, "note");
3230 if (!xn_node
) continue;
3231 xn_node
= xmlnode_get_child(xn_node
, "body");
3232 if (!xn_node
) continue;
3234 note
= xmlnode_get_data(xn_node
);
3236 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3239 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3240 sbuddy
->annotation
= g_strdup(note
);
3246 else if(!strcmp(attrVar
, "state"))
3250 xn_node
= xmlnode_get_child(xn_category
, "state");
3251 if (!xn_node
) continue;
3252 xn_node
= xmlnode_get_child(xn_node
, "availability");
3253 if (!xn_node
) continue;
3255 data
= xmlnode_get_data(xn_node
);
3260 activity
= "unknown";
3261 else if (avail
< 4500)
3262 activity
= "available";
3263 else if (avail
< 6000)
3265 else if (avail
< 7500)
3267 else if (avail
< 9000)
3269 else if (avail
< 12000)
3271 else if (avail
< 18000)
3274 activity
= "offline";
3282 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
3286 xmlnode_free(xn_categories
);
3289 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3291 const char *uri
,*state
;
3293 xmlnode
*xn_resource
;
3294 xmlnode
*xn_instance
;
3296 xn_list
= xmlnode_from_str(data
, len
);
3298 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
3300 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
3302 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
3303 if (!xn_instance
) return;
3305 state
= xmlnode_get_attrib(xn_instance
, "state");
3306 uri
= xmlnode_get_attrib(xn_instance
, "cid");
3307 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri
,state
);
3308 if(strstr(state
,"resubscribe")){
3309 sipe_subscribe_presence_single(sip
, uri
);
3314 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3318 gchar
*activity
= NULL
;
3320 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
3321 gboolean isonline
= FALSE
;
3322 xmlnode
*display_name_node
;
3324 pidf
= xmlnode_from_str(data
, len
);
3326 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
3330 uri
= xmlnode_get_attrib(pidf
, "entity");
3332 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
3334 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3335 basicstatus
= xmlnode_get_child(status
, "basic");
3340 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3345 getbasic
= xmlnode_get_data(basicstatus
);
3347 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3352 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
3353 if (strstr(getbasic
, "open")) {
3358 display_name_node
= xmlnode_get_child(pidf
, "display-name");
3359 // updating display name if alias was just URI
3360 if (display_name_node
) {
3361 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3362 GSList
*entry
= buddies
;
3363 PurpleBuddy
*p_buddy
;
3364 char * display_name
= xmlnode_get_data(display_name_node
);
3367 const char *server_alias
;
3370 p_buddy
= entry
->data
;
3372 alias
= (char *)purple_buddy_get_alias(p_buddy
);
3373 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
3374 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
3375 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3376 purple_blist_alias_buddy(p_buddy
, display_name
);
3380 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3382 ( (server_alias
&& strcmp(display_name
, server_alias
))
3383 || !server_alias
|| strlen(server_alias
) == 0 )
3385 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3388 entry
= entry
->next
;
3390 g_free(display_name
);
3393 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
3394 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3395 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
3396 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
3397 activity
= xmlnode_get_data(basicstatus
);
3398 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
3405 gchar
* status_id
= NULL
;
3407 if (strstr(activity
, "busy")) {
3409 } else if (strstr(activity
, "away")) {
3415 status_id
= "available";
3418 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
3419 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
3421 purple_prpl_got_user_status(sip
->account
, uri
, "offline", NULL
);
3428 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3430 const char *availability
;
3431 const char *activity
;
3432 const char *display_name
= NULL
;
3433 const char *activity_name
;
3438 struct sipe_buddy
*sbuddy
;
3440 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
3442 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
3443 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
3444 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
3445 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
3446 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
3447 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
3448 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
3449 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
3450 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
3451 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
3452 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
3453 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
3455 name
= xmlnode_get_attrib(xn_presentity
, "uri");
3456 uri
= g_strdup_printf("sip:%s", name
);
3457 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
3458 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
3460 // updating display name if alias was just URI
3461 if (xn_display_name
) {
3462 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3463 GSList
*entry
= buddies
;
3464 PurpleBuddy
*p_buddy
;
3465 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
3468 const char *email_str
, *server_alias
;
3470 p_buddy
= entry
->data
;
3472 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
3473 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3474 purple_blist_alias_buddy(p_buddy
, display_name
);
3477 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3479 ( (server_alias
&& strcmp(display_name
, server_alias
))
3480 || !server_alias
|| strlen(server_alias
) == 0 )
3482 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3486 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
3487 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
3488 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
3492 entry
= entry
->next
;
3496 avl
= atoi(availability
);
3497 act
= atoi(activity
);
3500 activity_name
= "away";
3501 else if (act
<= 150)
3502 activity_name
= "out-to-lunch";
3503 else if (act
<= 300)
3504 activity_name
= "be-right-back";
3505 else if (act
<= 400)
3506 activity_name
= "available";
3507 else if (act
<= 500)
3508 activity_name
= "on-the-phone";
3509 else if (act
<= 600)
3510 activity_name
= "busy";
3512 activity_name
= "available";
3515 activity_name
= "offline";
3517 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3520 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3521 sbuddy
->annotation
= NULL
;
3522 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
3524 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
3525 sbuddy
->device_name
= NULL
;
3526 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
3529 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
3530 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
3532 xmlnode_free(xn_presentity
);
3536 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3538 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3540 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
3542 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
3543 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
3545 const char *content
= msg
->body
;
3546 unsigned length
= msg
->bodylen
;
3547 PurpleMimeDocument
*mime
= NULL
;
3549 if (strstr(ctype
, "multipart"))
3551 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3552 const char *content_type
;
3554 mime
= purple_mime_document_parse(doc
);
3555 parts
= purple_mime_document_get_parts(mime
);
3557 content
= purple_mime_part_get_data(parts
->data
);
3558 length
= purple_mime_part_get_length(parts
->data
);
3559 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
3560 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
3562 process_incoming_notify_rlmi_resub(sip
, content
, length
);
3564 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
3566 process_incoming_notify_msrtc(sip
, content
, length
);
3570 process_incoming_notify_rlmi(sip
, content
, length
);
3572 parts
= parts
->next
;
3578 purple_mime_document_free(mime
);
3581 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
3583 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
3585 else if(strstr(ctype
, "application/rlmi+xml"))
3587 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
3590 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
3592 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
3596 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
3601 * Dispatcher for all incoming subscription information
3602 * whether it comes from NOTIFY, BENOTIFY requests or
3603 * piggy-backed to subscription's OK responce.
3605 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3606 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3608 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
3610 gchar
*event
= sipmsg_find_header(msg
, "Event");
3611 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
3612 const char *uri
,*state
;
3614 xmlnode
*xn_resource
;
3615 xmlnode
*xn_instance
;
3619 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
3620 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
3624 const gchar
*expires_header
;
3625 expires_header
= sipmsg_find_header(msg
, "Expires");
3626 expires
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
3627 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires
);
3630 if (!subscription_state
|| strstr(subscription_state
, "active"))
3632 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
3634 sipe_process_presence(sip
, msg
);
3636 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
3638 sipe_process_roaming_contacts(sip
, msg
, NULL
);
3640 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self"))
3642 sipe_process_roaming_self(sip
, msg
);
3644 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-provisioning-v2"))
3647 purple_debug_info("sipe", "vnd-microsoft-provisioning-v2 data is not supported yet.");
3649 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
3651 sipe_process_roaming_acl(sip
, msg
);
3653 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
3655 sipe_process_presence_wpending(sip
, msg
);
3659 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
3663 //The server sends a (BE)NOTIFY with the status 'terminated'
3664 if(request
&& subscription_state
&& strstr(subscription_state
, "terminated") )
3666 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3667 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
3671 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3672 if (request
&& !benotify
)
3674 sipmsg_remove_header(msg
, "Expires");
3675 sipmsg_remove_header(msg
, "subscription-state");
3676 sipmsg_remove_header(msg
, "Event");
3677 sipmsg_remove_header(msg
, "Require");
3678 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3685 static gchar* gen_xpidf(struct sipe_account_data *sip)
3687 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3689 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3690 "<display name=\"sip:%s\"/>\r\n"
3691 "<atom id=\"1234\">\r\n"
3692 "<address uri=\"sip:%s\">\r\n"
3693 "<status status=\"%s\"/>\r\n"
3706 static gchar* gen_pidf(struct sipe_account_data *sip)
3708 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3709 "<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"
3710 "<tuple id=\"0\">\r\n"
3712 "<basic>open</basic>\r\n"
3713 "<ep:activities>\r\n"
3714 " <ep:activity>%s</ep:activity>\r\n"
3718 "<ci:display-name>%s</ci:display-name>\r\n"
3728 process_send_presence_info_v0_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3730 if (msg
->response
== 488) {
3731 sip
->presence_method_version
= 1;
3732 send_presence_info(sip
);
3737 static void send_presence_info_v0(struct sipe_account_data
*sip
, const char * note
)
3739 int availability
= 300; // online
3740 int activity
= 400; // Available
3743 if (!strcmp(sip
->status
, "away")) {
3745 } else if (!strcmp(sip
->status
, "out-to-lunch")) {
3747 } else if (!strcmp(sip
->status
, "be-right-back")) {
3749 } else if (!strcmp(sip
->status
, "on-the-phone")) {
3751 } else if (!strcmp(sip
->status
, "do-not-disturb")) {
3753 } else if (!strcmp(sip
->status
, "busy")) {
3755 } else if (!strcmp(sip
->status
, "invisible")) {
3756 availability
= 0; // offline
3760 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
3761 //@TODO: send user data - state; add hostname in upper case
3762 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
3763 send_soap_request_with_cb(sip
, body
, process_send_presence_info_v0_response
, NULL
);
3769 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3771 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3772 if (msg
->response
== 200) {
3773 sip
->status_version
= 0;
3774 send_presence_info(sip
);
3780 process_send_presence_info_v1_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3782 if (msg
->response
== 409) {
3783 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3784 // TODO need to parse the version #'s?
3785 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
3786 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
3790 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg
->body
);
3792 tmp
= get_contact(sip
);
3793 hdr
= g_strdup_printf("Contact: %s\r\n"
3794 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
3796 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
3806 static void send_presence_info_v1(struct sipe_account_data
*sip
, const char * note
)
3813 if (!strcmp(sip
->status
, "away")) {
3815 } else if (!strcmp(sip
->status
, "busy")) {
3822 uri
= g_strdup_printf("sip:%s", sip
->username
);
3823 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
3824 sip
->status_version
, code
,
3825 sip
->status_version
, code
,
3826 sip
->status_version
, note
? note
: "",
3827 sip
->status_version
, note
? note
: "",
3828 sip
->status_version
, note
? note
: ""
3830 sip
->status_version
++;
3832 tmp
= get_contact(sip
);
3833 hdr
= g_strdup_printf("Contact: %s\r\n"
3834 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
3836 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_info_v1_response
);
3844 static void send_presence_info(struct sipe_account_data
*sip
)
3846 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
3848 if (!status
) return;
3850 note
= purple_status_get_attr_string(status
, "message");
3852 purple_debug_info("sipe", "sending presence info, version = %d\n", sip
->presence_method_version
);
3853 if (sip
->presence_method_version
!= 1) {
3854 send_presence_info_v0(sip
, note
);
3856 send_presence_info_v1(sip
, note
);
3860 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
3862 gboolean found
= FALSE
;
3863 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
3864 if (msg
->response
== 0) { /* request */
3865 if (!strcmp(msg
->method
, "MESSAGE")) {
3866 process_incoming_message(sip
, msg
);
3868 } else if (!strcmp(msg
->method
, "NOTIFY")) {
3869 purple_debug_info("sipe","send->process_incoming_notify\n");
3870 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
3872 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
3873 purple_debug_info("sipe","send->process_incoming_benotify\n");
3874 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
3876 } else if (!strcmp(msg
->method
, "INVITE")) {
3877 process_incoming_invite(sip
, msg
);
3879 } else if (!strcmp(msg
->method
, "OPTIONS")) {
3880 process_incoming_options(sip
, msg
);
3882 } else if (!strcmp(msg
->method
, "INFO")) {
3884 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3886 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
3889 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3891 } else if (!strcmp(msg
->method
, "ACK")) {
3892 // ACK's don't need any response
3894 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
3895 // LCS 2005 sends us these - just respond 200 OK
3897 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3898 } else if (!strcmp(msg
->method
, "BYE")) {
3899 struct sip_im_session
*session
;
3901 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3903 from
= parse_from(sipmsg_find_header(msg
, "From"));
3904 session
= find_im_session (sip
, from
);
3908 // TODO Let the user know the other user left the conversation?
3909 im_session_destroy(sip
, session
);
3914 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3916 } else { /* response */
3917 struct transaction
*trans
= transactions_find(sip
, msg
);
3919 if (msg
->response
== 407) {
3920 gchar
*resend
, *auth
, *ptmp
;
3922 if (sip
->proxy
.retries
> 30) return;
3923 sip
->proxy
.retries
++;
3924 /* do proxy authentication */
3926 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
3928 fill_auth(sip
, ptmp
, &sip
->proxy
);
3929 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
3930 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
3931 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
3933 resend
= sipmsg_to_string(trans
->msg
);
3934 /* resend request */
3935 sendout_pkt(sip
->gc
, resend
);
3938 if (msg
->response
== 100 || msg
->response
== 180) {
3939 /* ignore provisional response */
3940 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
3942 sip
->proxy
.retries
= 0;
3943 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
3944 if (msg
->response
== 401)
3946 sip
->registrar
.retries
++;
3947 sip
->registrar
.expires
= 0;
3951 sip
->registrar
.retries
= 0;
3953 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
3955 if (msg
->response
== 401) {
3956 gchar
*resend
, *auth
, *ptmp
;
3958 if (sip
->registrar
.retries
> 4) return;
3959 sip
->registrar
.retries
++;
3961 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3962 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
3964 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3967 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
3969 fill_auth(sip
, ptmp
, &sip
->registrar
);
3970 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
3971 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
3972 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
3974 //sipmsg_remove_header(trans->msg, "Authorization");
3975 //sipmsg_add_header(trans->msg, "Authorization", auth);
3977 resend
= sipmsg_to_string(trans
->msg
);
3978 /* resend request */
3979 sendout_pkt(sip
->gc
, resend
);
3984 if (trans
->callback
) {
3985 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
3986 /* call the callback to process response*/
3987 (trans
->callback
)(sip
, msg
, trans
);
3989 /* Not sure if this is needed or what needs to be done
3990 but transactions seem to be removed prematurely so
3991 this only removes them if the response is 200 OK */
3992 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
3993 /*Has a bug and it's unneccesary*/
3994 /*transactions_remove(sip, trans);*/
4000 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction");
4004 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
4008 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
4016 /* according to the RFC remove CRLF at the beginning */
4017 while (*cur
== '\r' || *cur
== '\n') {
4020 if (cur
!= conn
->inbuf
) {
4021 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
4022 conn
->inbufused
= strlen(conn
->inbuf
);
4025 /* Received a full Header? */
4026 sip
->processing_input
= TRUE
;
4027 while (sip
->processing_input
&&
4028 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
4029 time_t currtime
= time(NULL
);
4032 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
4033 msg
= sipmsg_parse_header(conn
->inbuf
);
4036 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
4037 if (restlen
>= msg
->bodylen
) {
4038 dummy
= g_malloc(msg
->bodylen
+ 1);
4039 memcpy(dummy
, cur
, msg
->bodylen
);
4040 dummy
[msg
->bodylen
] = '\0';
4042 cur
+= msg
->bodylen
;
4043 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
4044 conn
->inbufused
= strlen(conn
->inbuf
);
4046 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4047 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
4053 purple_debug_info("sipe", "body:\n%s", msg->body);
4056 // Verify the signature before processing it
4057 if (sip
->registrar
.ntlm_key
) {
4058 struct sipmsg_breakdown msgbd
;
4059 gchar
*signature_input_str
;
4060 gchar
*signature
= NULL
;
4063 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
4064 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
4065 if (signature_input_str
!= NULL
) {
4066 signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
4068 g_free(signature_input_str
);
4070 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
4072 if (signature
!= NULL
) {
4073 if (rspauth
!= NULL
) {
4074 if (purple_ntlm_verify_signature (signature
, rspauth
)) {
4075 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
4076 process_input_message(sip
, msg
);
4078 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth
, signature
);
4079 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
4080 sip
->gc
->wants_to_die
= TRUE
;
4082 } else if (msg
->response
== 401) {
4083 purple_connection_error(sip
->gc
, _("Wrong Password"));
4084 sip
->gc
->wants_to_die
= TRUE
;
4090 sipmsg_breakdown_free(&msgbd
);
4092 process_input_message(sip
, msg
);
4099 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
4101 PurpleConnection
*gc
= data
;
4102 struct sipe_account_data
*sip
= gc
->proto_data
;
4107 static char buffer
[65536];
4108 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
4110 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
4111 msg
= sipmsg_parse_msg(buffer
);
4112 if (msg
) process_input_message(sip
, msg
);
4116 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
4118 struct sipe_account_data
*sip
= gc
->proto_data
;
4119 PurpleSslConnection
*gsc
= sip
->gsc
;
4121 purple_debug_error("sipe", "%s",debug
);
4122 purple_connection_error(gc
, msg
);
4124 /* Invalidate this connection. Next send will open a new one */
4126 connection_remove(sip
, gsc
->fd
);
4127 purple_ssl_close(gsc
);
4133 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4135 PurpleConnection
*gc
= data
;
4136 struct sipe_account_data
*sip
;
4137 struct sip_connection
*conn
;
4139 gboolean firstread
= TRUE
;
4141 /* NOTE: This check *IS* necessary */
4142 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
4143 purple_ssl_close(gsc
);
4147 sip
= gc
->proto_data
;
4148 conn
= connection_find(sip
, gsc
->fd
);
4150 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4151 gc
->wants_to_die
= TRUE
;
4152 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
4156 /* Read all available data from the SSL connection */
4158 /* Increase input buffer size as needed */
4159 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4160 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4161 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4162 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
4165 /* Try to read as much as there is space left in the buffer */
4166 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
4167 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
4169 if (len
< 0 && errno
== EAGAIN
) {
4170 /* Try again later */
4172 } else if (len
< 0) {
4173 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
4175 } else if (firstread
&& (len
== 0)) {
4176 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
4180 conn
->inbufused
+= len
;
4183 /* Equivalence indicates that there is possibly more data to read */
4184 } while (len
== readlen
);
4186 conn
->inbuf
[conn
->inbufused
] = '\0';
4187 process_input(sip
, conn
);
4191 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4193 PurpleConnection
*gc
= data
;
4194 struct sipe_account_data
*sip
= gc
->proto_data
;
4196 struct sip_connection
*conn
= connection_find(sip
, source
);
4198 purple_debug_error("sipe", "Connection not found!\n");
4202 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4203 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4204 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4207 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
4209 if (len
< 0 && errno
== EAGAIN
)
4211 else if (len
<= 0) {
4212 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4213 connection_remove(sip
, source
);
4214 if (sip
->fd
== source
) sip
->fd
= -1;
4218 conn
->inbufused
+= len
;
4219 conn
->inbuf
[conn
->inbufused
] = '\0';
4221 process_input(sip
, conn
);
4224 /* Callback for new connections on incoming TCP port */
4225 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4227 PurpleConnection
*gc
= data
;
4228 struct sipe_account_data
*sip
= gc
->proto_data
;
4229 struct sip_connection
*conn
;
4231 int newfd
= accept(source
, NULL
, NULL
);
4233 conn
= connection_create(sip
, newfd
);
4235 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4238 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
4240 PurpleConnection
*gc
= data
;
4241 struct sipe_account_data
*sip
;
4242 struct sip_connection
*conn
;
4244 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4252 purple_connection_error(gc
, _("Could not connect"));
4256 sip
= gc
->proto_data
;
4258 sip
->last_keepalive
= time(NULL
);
4260 conn
= connection_create(sip
, source
);
4264 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4267 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4269 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
4270 if (sip
== NULL
) return;
4275 static guint
sipe_ht_hash_nick(const char *nick
)
4277 char *lc
= g_utf8_strdown(nick
, -1);
4278 guint bucket
= g_str_hash(lc
);
4284 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
4286 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
4289 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
4291 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4293 sip
->listen_data
= NULL
;
4295 if (listenfd
== -1) {
4296 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4302 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
4303 sip
->listenfd
= sip
->fd
;
4305 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
4307 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
4311 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
4313 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4316 sip
->query_data
= NULL
;
4318 if (!hosts
|| !hosts
->data
) {
4319 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
4323 addr_size
= GPOINTER_TO_INT(hosts
->data
);
4324 hosts
= g_slist_remove(hosts
, hosts
->data
);
4325 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
4326 g_free(hosts
->data
);
4327 hosts
= g_slist_remove(hosts
, hosts
->data
);
4329 hosts
= g_slist_remove(hosts
, hosts
->data
);
4330 g_free(hosts
->data
);
4331 hosts
= g_slist_remove(hosts
, hosts
->data
);
4334 /* create socket for incoming connections */
4335 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
4336 sipe_udp_host_resolved_listen_cb
, sip
);
4337 if (sip
->listen_data
== NULL
) {
4338 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4343 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
4346 PurpleConnection
*gc
= data
;
4347 struct sipe_account_data
*sip
;
4349 /* If the connection is already disconnected, we don't need to do anything else */
4350 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4353 sip
= gc
->proto_data
;
4358 case PURPLE_SSL_CONNECT_FAILED
:
4359 purple_connection_error(gc
, _("Connection Failed"));
4361 case PURPLE_SSL_HANDSHAKE_FAILED
:
4362 purple_connection_error(gc
, _("SSL Handshake Failed"));
4364 case PURPLE_SSL_CERTIFICATE_INVALID
:
4365 purple_connection_error(gc
, _("SSL Certificate Invalid"));
4371 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
4373 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4374 PurpleProxyConnectData
*connect_data
;
4376 sip
->listen_data
= NULL
;
4378 sip
->listenfd
= listenfd
;
4379 if (sip
->listenfd
== -1) {
4380 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4384 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
4385 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4386 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
4387 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
4388 sipe_newconn_cb
, sip
->gc
);
4389 purple_debug_info("sipe", "connecting to %s port %d\n",
4390 sip
->realhostname
, sip
->realport
);
4391 /* open tcp connection to the server */
4392 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
4393 sip
->realport
, login_cb
, sip
->gc
);
4395 if (connect_data
== NULL
) {
4396 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
4401 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
4403 PurpleAccount
*account
= sip
->account
;
4404 PurpleConnection
*gc
= sip
->gc
;
4406 if (purple_account_get_bool(account
, "useport", FALSE
)) {
4407 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
4408 port
= purple_account_get_int(account
, "port", 0);
4410 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
4413 sip
->realhostname
= hostname
;
4414 sip
->realport
= port
;
4416 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
4419 /* TODO: is there a good default grow size? */
4420 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
4421 sip
->txbuf
= purple_circ_buffer_new(0);
4423 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
4425 if (!purple_ssl_is_supported()) {
4426 gc
->wants_to_die
= TRUE
;
4427 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4431 purple_debug_info("sipe", "using SSL\n");
4433 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
4434 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
4435 if (sip
->gsc
== NULL
) {
4436 purple_connection_error(gc
, _("Could not create SSL context"));
4439 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
4441 purple_debug_info("sipe", "using UDP\n");
4443 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
4444 if (sip
->query_data
== NULL
) {
4445 purple_connection_error(gc
, _("Could not resolve hostname"));
4449 purple_debug_info("sipe", "using TCP\n");
4450 /* create socket for incoming connections */
4451 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
4452 sipe_tcp_connect_listen_cb
, sip
);
4453 if (sip
->listen_data
== NULL
) {
4454 purple_connection_error(gc
, _("Could not create listen socket"));
4460 /* Service list for autodection */
4461 static const struct sipe_service_data service_autodetect
[] = {
4462 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4463 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4464 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4465 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4469 /* Service list for SSL/TLS */
4470 static const struct sipe_service_data service_tls
[] = {
4471 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4472 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4476 /* Service list for TCP */
4477 static const struct sipe_service_data service_tcp
[] = {
4478 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4479 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4483 /* Service list for UDP */
4484 static const struct sipe_service_data service_udp
[] = {
4485 { "sip", "udp", SIPE_TRANSPORT_UDP
},
4489 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
4490 static void resolve_next_service(struct sipe_account_data
*sip
,
4491 const struct sipe_service_data
*start
)
4494 sip
->service_data
= start
;
4496 sip
->service_data
++;
4497 if (sip
->service_data
->service
== NULL
) {
4499 /* Try connecting to the SIP hostname directly */
4500 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
4501 if (sip
->auto_transport
) {
4502 // If SSL is supported, default to using it; OCS servers aren't configured
4503 // by default to accept TCP
4504 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4505 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
4506 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
4509 hostname
= g_strdup(sip
->sipdomain
);
4510 create_connection(sip
, hostname
, 0);
4515 /* Try to resolve next service */
4516 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
4517 sip
->service_data
->transport
,
4522 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
4524 struct sipe_account_data
*sip
= data
;
4526 sip
->srv_query_data
= NULL
;
4528 /* find the host to connect to */
4530 gchar
*hostname
= g_strdup(resp
->hostname
);
4531 int port
= resp
->port
;
4532 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4536 sip
->transport
= sip
->service_data
->type
;
4538 create_connection(sip
, hostname
, port
);
4540 resolve_next_service(sip
, NULL
);
4544 static void sipe_login(PurpleAccount
*account
)
4546 PurpleConnection
*gc
;
4547 struct sipe_account_data
*sip
;
4548 gchar
**signinname_login
, **userserver
, **domain_user
;
4549 const char *transport
;
4551 const char *username
= purple_account_get_username(account
);
4552 gc
= purple_account_get_connection(account
);
4554 if (strpbrk(username
, " \t\v\r\n") != NULL
) {
4555 gc
->wants_to_die
= TRUE
;
4556 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
4560 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
4561 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
4562 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
4564 sip
->account
= account
;
4565 sip
->reregister_set
= FALSE
;
4566 sip
->reauthenticate_set
= FALSE
;
4567 sip
->subscribed
= FALSE
;
4568 sip
->subscribed_buddies
= FALSE
;
4570 signinname_login
= g_strsplit(username
, ",", 2);
4572 userserver
= g_strsplit(signinname_login
[0], "@", 2);
4573 purple_connection_set_display_name(gc
, userserver
[0]);
4574 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
4575 sip
->sipdomain
= g_strdup(userserver
[1]);
4577 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
4578 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
4579 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
4581 sip
->password
= g_strdup(purple_connection_get_password(gc
));
4583 g_strfreev(userserver
);
4584 g_strfreev(domain_user
);
4585 g_strfreev(signinname_login
);
4587 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
4589 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
4591 /* TODO: Set the status correctly. */
4592 sip
->status
= g_strdup("available");
4594 transport
= purple_account_get_string(account
, "transport", "auto");
4595 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
4596 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
4599 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
4600 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
4601 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
4602 } else if (strcmp(transport
, "auto") == 0) {
4603 sip
->auto_transport
= TRUE
;
4604 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
4605 } else if (strcmp(transport
, "tls") == 0) {
4606 resolve_next_service(sip
, service_tls
);
4607 } else if (strcmp(transport
, "tcp") == 0) {
4608 resolve_next_service(sip
, service_tcp
);
4610 resolve_next_service(sip
, service_udp
);
4614 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
4616 connection_free_all(sip
);
4618 if (sip
->query_data
!= NULL
)
4619 purple_dnsquery_destroy(sip
->query_data
);
4620 sip
->query_data
= NULL
;
4622 if (sip
->srv_query_data
!= NULL
)
4623 purple_srv_cancel(sip
->srv_query_data
);
4624 sip
->srv_query_data
= NULL
;
4626 if (sip
->listen_data
!= NULL
)
4627 purple_network_listen_cancel(sip
->listen_data
);
4628 sip
->listen_data
= NULL
;
4630 if (sip
->gsc
!= NULL
)
4631 purple_ssl_close(sip
->gsc
);
4634 sipe_auth_free(&sip
->registrar
);
4635 sipe_auth_free(&sip
->proxy
);
4638 purple_circ_buffer_destroy(sip
->txbuf
);
4641 g_free(sip
->realhostname
);
4642 sip
->realhostname
= NULL
;
4645 purple_input_remove(sip
->listenpa
);
4647 if (sip
->tx_handler
)
4648 purple_input_remove(sip
->tx_handler
);
4649 sip
->tx_handler
= 0;
4650 if (sip
->resendtimeout
)
4651 purple_timeout_remove(sip
->resendtimeout
);
4652 sip
->resendtimeout
= 0;
4653 if (sip
->timeouts
) {
4654 GSList
*entry
= sip
->timeouts
;
4656 struct scheduled_action
*sched_action
= entry
->data
;
4657 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
4658 purple_timeout_remove(sched_action
->timeout_handler
);
4659 g_free(sched_action
->payload
);
4660 g_free(sched_action
->name
);
4661 g_free(sched_action
);
4662 entry
= entry
->next
;
4665 g_slist_free(sip
->timeouts
);
4667 g_slist_free(sip
->allow_events
);
4670 g_free(sip
->contact
);
4671 sip
->contact
= NULL
;
4673 g_free(sip
->regcallid
);
4674 sip
->regcallid
= NULL
;
4677 sip
->processing_input
= FALSE
;
4681 * A callback for g_hash_table_foreach_remove
4683 static gboolean
sipe_buddy_remove(gpointer key
, struct sipe_buddy
*buddy
, gpointer user_data
)
4685 sipe_free_buddy(buddy
);
4688 static void sipe_close(PurpleConnection
*gc
)
4690 struct sipe_account_data
*sip
= gc
->proto_data
;
4693 /* leave all conversations */
4694 im_session_close_all(sip
);
4697 do_register_exp(sip
, 0);
4699 sipe_connection_cleanup(sip
);
4700 g_free(sip
->sipdomain
);
4701 g_free(sip
->username
);
4702 g_free(sip
->password
);
4703 g_free(sip
->authdomain
);
4704 g_free(sip
->authuser
);
4705 g_free(sip
->status
);
4707 g_hash_table_foreach_remove(sip
->buddies
, (GHRFunc
) sipe_buddy_remove
, NULL
);
4708 g_hash_table_destroy(sip
->buddies
);
4710 g_free(gc
->proto_data
);
4711 gc
->proto_data
= NULL
;
4714 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4716 PurpleAccount
*acct
= purple_connection_get_account(gc
);
4717 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
4718 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
4720 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
4721 purple_conversation_present(conv
);
4725 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4728 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
4729 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
4732 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
4734 PurpleNotifySearchResults
*results
;
4735 PurpleNotifySearchColumn
*column
;
4736 xmlnode
*searchResults
;
4738 int match_count
= 0;
4739 gboolean more
= FALSE
;
4742 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
4744 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4745 if (!searchResults
) {
4746 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4750 results
= purple_notify_searchresults_new();
4752 if (results
== NULL
) {
4753 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4754 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
4756 xmlnode_free(searchResults
);
4760 column
= purple_notify_searchresults_column_new(_("User Name"));
4761 purple_notify_searchresults_column_add(results
, column
);
4763 column
= purple_notify_searchresults_column_new(_("Name"));
4764 purple_notify_searchresults_column_add(results
, column
);
4766 column
= purple_notify_searchresults_column_new(_("Company"));
4767 purple_notify_searchresults_column_add(results
, column
);
4769 column
= purple_notify_searchresults_column_new(_("Country"));
4770 purple_notify_searchresults_column_add(results
, column
);
4772 column
= purple_notify_searchresults_column_new(_("Email"));
4773 purple_notify_searchresults_column_add(results
, column
);
4775 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
4778 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
4779 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
4780 g_strfreev(uri_parts
);
4782 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
4783 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
4784 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
4785 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
4787 purple_notify_searchresults_row_add(results
, row
);
4791 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
4792 char *data
= xmlnode_get_data_unescaped(mrow
);
4793 more
= (g_strcasecmp(data
, "true") == 0);
4797 secondary
= g_strdup_printf(
4798 dngettext(GETTEXT_PACKAGE
,
4799 "Found %d contact%s:",
4800 "Found %d contacts%s:", match_count
),
4801 match_count
, more
? _(" (more matched your query)") : "");
4803 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
4804 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
4805 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
4808 xmlnode_free(searchResults
);
4812 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
4814 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
4815 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
4819 PurpleRequestField
*field
= entries
->data
;
4820 const char *id
= purple_request_field_get_id(field
);
4821 const char *value
= purple_request_field_string_get_value(field
);
4823 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
4825 if (value
!= NULL
) attrs
[i
++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW
, id
, value
);
4826 } while ((entries
= g_list_next(entries
)) != NULL
);
4830 gchar
*query
= g_strjoinv(NULL
, attrs
);
4831 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
4832 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
4833 send_soap_request_with_cb(gc
->proto_data
, body
,
4834 (TransCallback
) process_search_contact_response
, NULL
);
4842 static void sipe_show_find_contact(PurplePluginAction
*action
)
4844 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
4845 PurpleRequestFields
*fields
;
4846 PurpleRequestFieldGroup
*group
;
4847 PurpleRequestField
*field
;
4849 fields
= purple_request_fields_new();
4850 group
= purple_request_field_group_new(NULL
);
4851 purple_request_fields_add_group(fields
, group
);
4853 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
4854 purple_request_field_group_add_field(group
, field
);
4855 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
4856 purple_request_field_group_add_field(group
, field
);
4857 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
4858 purple_request_field_group_add_field(group
, field
);
4859 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
4860 purple_request_field_group_add_field(group
, field
);
4862 purple_request_fields(gc
,
4864 _("Search for a Contact"),
4865 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4867 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
4869 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
4872 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
4875 PurplePluginAction
*act
;
4877 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
4878 menu
= g_list_prepend(menu
, act
);
4880 menu
= g_list_reverse(menu
);
4885 static void dummy_permit_deny(PurpleConnection
*gc
)
4889 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
4895 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
4901 static char *sipe_status_text(PurpleBuddy
*buddy
)
4903 struct sipe_account_data
*sip
;
4904 struct sipe_buddy
*sbuddy
;
4907 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
4908 if (sip
) //happens on pidgin exit
4910 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
4911 if (sbuddy
&& sbuddy
->annotation
)
4913 text
= g_strdup(sbuddy
->annotation
);
4920 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
4922 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
4923 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
4924 struct sipe_account_data
*sip
;
4925 struct sipe_buddy
*sbuddy
;
4926 char *annotation
= NULL
;
4928 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
4929 if (sip
) //happens on pidgin exit
4931 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
4934 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
4939 if (purple_presence_is_online(presence
))
4941 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
4946 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
4953 sipe_get_account_text_table(PurpleAccount
*account
)
4956 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
4957 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
4961 static PurpleBuddy
*
4962 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
4965 const gchar
*server_alias
, *email
;
4966 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
4968 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
4970 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
4972 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
4974 purple_blist_server_alias_buddy(clone
, server_alias
);
4977 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
4979 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
4982 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
4984 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
4989 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
4991 PurpleBuddy
*buddy
, *b
;
4992 PurpleConnection
*gc
;
4993 PurpleGroup
* group
= purple_find_group(group_name
);
4995 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
4997 buddy
= (PurpleBuddy
*)node
;
4999 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
5000 gc
= purple_account_get_connection(buddy
->account
);
5002 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
5004 b
= purple_blist_add_buddy_clone(group
, buddy
);
5007 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
5011 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
5014 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
5016 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5019 char *mailto
= g_strdup_printf("mailto:%s", email
);
5020 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
5024 char *const parmList
[] = {mailto
, NULL
};
5025 if ((pid
= fork()) == -1)
5027 purple_debug_info("sipe", "fork() error\n");
5031 execvp("xdg-email", parmList
);
5032 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5040 //@TODO resolve env variable %WINDIR% first
5041 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
5044 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
5053 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
5058 * A menu which appear when right-clicking on buddy in contact list.
5061 sipe_buddy_menu(PurpleBuddy
*buddy
)
5063 PurpleBlistNode
*g_node
;
5064 PurpleGroup
*group
, *gr_parent
;
5065 PurpleMenuAction
*act
;
5067 GList
*menu_groups
= NULL
;
5069 act
= purple_menu_action_new(_("Send Email..."),
5070 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
5072 menu
= g_list_prepend(menu
, act
);
5074 gr_parent
= purple_buddy_get_group(buddy
);
5075 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
5076 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
5079 group
= (PurpleGroup
*)g_node
;
5080 if (group
== gr_parent
)
5083 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
5086 act
= purple_menu_action_new(purple_group_get_name(group
),
5087 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
5089 menu_groups
= g_list_prepend(menu_groups
, act
);
5091 menu_groups
= g_list_reverse(menu_groups
);
5093 act
= purple_menu_action_new(_("Copy to"),
5096 menu
= g_list_prepend(menu
, act
);
5097 menu
= g_list_reverse(menu
);
5102 GList
*sipe_blist_node_menu(PurpleBlistNode
*node
) {
5103 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
5104 return sipe_buddy_menu((PurpleBuddy
*) node
);
5111 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
5113 gboolean ret
= TRUE
;
5114 char *username
= (char *)trans
->payload
;
5116 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
5117 PurpleBuddy
*pbuddy
;
5118 struct sipe_buddy
*sbuddy
;
5120 char *server_alias
= NULL
;
5122 const char *device_name
= NULL
;
5124 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
5126 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
5127 alias
= purple_buddy_get_local_alias(pbuddy
);
5131 //will query buddy UA's capabilities and send answer to log
5132 sipe_options_request(sip
, username
);
5134 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
5137 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
5141 if (msg
->response
!= 200) {
5142 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
5144 xmlnode
*searchResults
;
5147 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
5148 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5149 if (!searchResults
) {
5150 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5151 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
5152 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
5153 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5154 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
5155 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
5156 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
5157 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
5158 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
5159 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
5160 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
5161 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
5162 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5163 if (!email
|| strcmp("", email
)) {
5164 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
5165 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
5169 xmlnode_free(searchResults
);
5172 purple_notify_user_info_add_section_break(info
);
5174 if (!server_alias
|| !strcmp("", server_alias
)) {
5175 g_free(server_alias
);
5176 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
5178 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5182 // same as server alias, do not present
5183 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
5186 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
5189 if (!email
|| !strcmp("", email
)) {
5191 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
5193 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5199 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
5202 /* show a buddy's user info in a nice dialog box */
5203 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
5204 username
, /* buddy's username */
5206 NULL
, /* callback called when dialog closed */
5207 NULL
); /* userdata for callback */
5213 * AD search first, LDAP based
5215 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
5217 char *row
= g_strdup_printf(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
5218 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
5220 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
5221 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
5222 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
5227 static PurplePlugin
*my_protocol
= NULL
;
5229 static PurplePluginProtocolInfo prpl_info
=
5232 NULL
, /* user_splits */
5233 NULL
, /* protocol_options */
5234 NO_BUDDY_ICONS
, /* icon_spec */
5235 sipe_list_icon
, /* list_icon */
5236 NULL
, /* list_emblems */
5237 sipe_status_text
, /* status_text */
5238 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
5239 sipe_status_types
, /* away_states */
5240 sipe_blist_node_menu
, /* blist_node_menu */
5241 NULL
, /* chat_info */
5242 NULL
, /* chat_info_defaults */
5243 sipe_login
, /* login */
5244 sipe_close
, /* close */
5245 sipe_im_send
, /* send_im */
5246 NULL
, /* set_info */ // TODO maybe
5247 sipe_send_typing
, /* send_typing */
5248 sipe_get_info
, /* get_info */
5249 sipe_set_status
, /* set_status */
5250 NULL
, /* set_idle */
5251 NULL
, /* change_passwd */
5252 sipe_add_buddy
, /* add_buddy */
5253 NULL
, /* add_buddies */
5254 sipe_remove_buddy
, /* remove_buddy */
5255 NULL
, /* remove_buddies */
5256 sipe_add_permit
, /* add_permit */
5257 sipe_add_deny
, /* add_deny */
5258 sipe_add_deny
, /* rem_permit */
5259 sipe_add_permit
, /* rem_deny */
5260 dummy_permit_deny
, /* set_permit_deny */
5261 NULL
, /* join_chat */
5262 NULL
, /* reject_chat */
5263 NULL
, /* get_chat_name */
5264 NULL
, /* chat_invite */
5265 NULL
, /* chat_leave */
5266 NULL
, /* chat_whisper */
5267 NULL
, /* chat_send */
5268 sipe_keep_alive
, /* keepalive */
5269 NULL
, /* register_user */
5270 NULL
, /* get_cb_info */ // deprecated
5271 NULL
, /* get_cb_away */ // deprecated
5272 sipe_alias_buddy
, /* alias_buddy */
5273 sipe_group_buddy
, /* group_buddy */
5274 sipe_rename_group
, /* rename_group */
5275 NULL
, /* buddy_free */
5276 sipe_convo_closed
, /* convo_closed */
5277 purple_normalize_nocase
, /* normalize */
5278 NULL
, /* set_buddy_icon */
5279 sipe_remove_group
, /* remove_group */
5280 NULL
, /* get_cb_real_name */ // TODO?
5281 NULL
, /* set_chat_topic */
5282 NULL
, /* find_blist_chat */
5283 NULL
, /* roomlist_get_list */
5284 NULL
, /* roomlist_cancel */
5285 NULL
, /* roomlist_expand_category */
5286 NULL
, /* can_receive_file */
5287 NULL
, /* send_file */
5288 NULL
, /* new_xfer */
5289 NULL
, /* offline_message */
5290 NULL
, /* whiteboard_prpl_ops */
5291 sipe_send_raw
, /* send_raw */
5292 NULL
, /* roomlist_room_serialize */
5293 NULL
, /* unregister_user */
5294 NULL
, /* send_attention */
5295 NULL
, /* get_attention_types */
5297 sizeof(PurplePluginProtocolInfo
), /* struct_size */
5298 sipe_get_account_text_table
, /* get_account_text_table */
5302 static PurplePluginInfo info
= {
5303 PURPLE_PLUGIN_MAGIC
,
5304 PURPLE_MAJOR_VERSION
,
5305 PURPLE_MINOR_VERSION
,
5306 PURPLE_PLUGIN_PROTOCOL
, /**< type */
5307 NULL
, /**< ui_requirement */
5309 NULL
, /**< dependencies */
5310 PURPLE_PRIORITY_DEFAULT
, /**< priority */
5311 "prpl-sipe", /**< id */
5312 "Microsoft LCS/OCS", /**< name */
5313 VERSION
, /**< version */
5314 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5315 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5316 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5317 "Gabriel Burt <gburt@novell.com>", /**< author */
5318 PURPLE_WEBSITE
, /**< homepage */
5319 sipe_plugin_load
, /**< load */
5320 sipe_plugin_unload
, /**< unload */
5321 sipe_plugin_destroy
, /**< destroy */
5322 NULL
, /**< ui_info */
5323 &prpl_info
, /**< extra_info */
5332 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
5336 entry
= prpl_info
.protocol_options
;
5338 purple_account_option_destroy(entry
->data
);
5339 entry
= g_list_delete_link(entry
, entry
);
5341 prpl_info
.protocol_options
= NULL
;
5343 entry
= prpl_info
.user_splits
;
5345 purple_account_user_split_destroy(entry
->data
);
5346 entry
= g_list_delete_link(entry
, entry
);
5348 prpl_info
.user_splits
= NULL
;
5351 static void init_plugin(PurplePlugin
*plugin
)
5353 PurpleAccountUserSplit
*split
;
5354 PurpleAccountOption
*option
;
5357 purple_debug_info(PACKAGE
, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
5358 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s",
5359 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
5362 purple_plugin_register(plugin
);
5364 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
5365 purple_account_user_split_set_reverse(split
, FALSE
);
5366 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
5368 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
5369 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5370 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5371 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5373 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
5374 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5375 // Translators: noun (networking port)
5376 option
= purple_account_option_int_new(_("Port"), "port", 5061);
5377 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5379 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
5380 purple_account_option_add_list_item(option
, _("Auto"), "auto");
5381 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
5382 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
5383 purple_account_option_add_list_item(option
, _("UDP"), "udp");
5384 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5386 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5387 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5389 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
5390 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5392 // TODO commented out so won't show in the preferences until we fix krb message signing
5393 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5394 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5396 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5397 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5398 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5401 option
= purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE
);
5402 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5403 option
= purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5404 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5405 my_protocol
= plugin
;
5408 /* I had to redefined the function for it load, but works */
5409 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
5410 plugin
->info
= &(info
);
5411 init_plugin((plugin
));
5412 sipe_plugin_load((plugin
));
5413 return purple_plugin_register(plugin
);