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_status(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_status(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 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
3219 xn_category
= xmlnode_get_next_twin(xn_category
) )
3221 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
3223 if (!strcmp(attrVar
, "note"))
3226 struct sipe_buddy
*sbuddy
;
3227 xn_node
= xmlnode_get_child(xn_category
, "note");
3228 if (!xn_node
) continue;
3229 xn_node
= xmlnode_get_child(xn_node
, "body");
3230 if (!xn_node
) continue;
3232 note
= xmlnode_get_data(xn_node
);
3235 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_debug_info("sipe", "process_incoming_notify_rlmi: %s\n",activity
);
3283 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
3287 xmlnode_free(xn_categories
);
3290 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3292 const char *uri
,*state
;
3294 xmlnode
*xn_resource
;
3295 xmlnode
*xn_instance
;
3297 xn_list
= xmlnode_from_str(data
, len
);
3299 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
3301 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
3303 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
3304 if (!xn_instance
) return;
3306 state
= xmlnode_get_attrib(xn_instance
, "state");
3307 uri
= xmlnode_get_attrib(xn_instance
, "cid");
3308 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri
,state
);
3309 if(strstr(state
,"resubscribe")){
3310 sipe_subscribe_presence_single(sip
, uri
);
3315 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3319 gchar
*activity
= NULL
;
3321 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
3322 gboolean isonline
= FALSE
;
3323 xmlnode
*display_name_node
;
3325 pidf
= xmlnode_from_str(data
, len
);
3327 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
3331 uri
= xmlnode_get_attrib(pidf
, "entity");
3333 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
3335 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3336 basicstatus
= xmlnode_get_child(status
, "basic");
3341 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3346 getbasic
= xmlnode_get_data(basicstatus
);
3348 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3353 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
3354 if (strstr(getbasic
, "open")) {
3359 display_name_node
= xmlnode_get_child(pidf
, "display-name");
3360 // updating display name if alias was just URI
3361 if (display_name_node
) {
3362 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3363 GSList
*entry
= buddies
;
3364 PurpleBuddy
*p_buddy
;
3365 char * display_name
= xmlnode_get_data(display_name_node
);
3368 const char *server_alias
;
3371 p_buddy
= entry
->data
;
3373 alias
= (char *)purple_buddy_get_alias(p_buddy
);
3374 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
3375 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
3376 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3377 purple_blist_alias_buddy(p_buddy
, display_name
);
3381 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3383 ( (server_alias
&& strcmp(display_name
, server_alias
))
3384 || !server_alias
|| strlen(server_alias
) == 0 )
3386 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3389 entry
= entry
->next
;
3391 g_free(display_name
);
3394 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
3395 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3396 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
3397 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
3398 activity
= xmlnode_get_data(basicstatus
);
3399 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
3406 gchar
* status_id
= NULL
;
3408 if (strstr(activity
, "busy")) {
3410 } else if (strstr(activity
, "away")) {
3416 status_id
= "available";
3419 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
3420 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
3422 purple_prpl_got_user_status(sip
->account
, uri
, "offline", NULL
);
3429 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3431 const char *availability
;
3432 const char *activity
;
3433 const char *display_name
= NULL
;
3434 const char *activity_name
;
3439 struct sipe_buddy
*sbuddy
;
3441 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
3443 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
3444 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
3445 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
3446 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
3447 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
3448 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
3449 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
3450 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
3451 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
3452 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
3453 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
3454 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
3456 name
= xmlnode_get_attrib(xn_presentity
, "uri");
3457 uri
= g_strdup_printf("sip:%s", name
);
3458 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
3459 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
3461 // updating display name if alias was just URI
3462 if (xn_display_name
) {
3463 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3464 GSList
*entry
= buddies
;
3465 PurpleBuddy
*p_buddy
;
3466 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
3469 const char *email_str
, *server_alias
;
3471 p_buddy
= entry
->data
;
3473 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
3474 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3475 purple_blist_alias_buddy(p_buddy
, display_name
);
3478 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3480 ( (server_alias
&& strcmp(display_name
, server_alias
))
3481 || !server_alias
|| strlen(server_alias
) == 0 )
3483 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3487 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
3488 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
3489 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
3493 entry
= entry
->next
;
3497 avl
= atoi(availability
);
3498 act
= atoi(activity
);
3501 activity_name
= "away";
3502 else if (act
<= 150)
3503 activity_name
= "out-to-lunch";
3504 else if (act
<= 300)
3505 activity_name
= "be-right-back";
3506 else if (act
<= 400)
3507 activity_name
= "available";
3508 else if (act
<= 500)
3509 activity_name
= "on-the-phone";
3510 else if (act
<= 600)
3511 activity_name
= "busy";
3513 activity_name
= "available";
3516 activity_name
= "offline";
3518 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3521 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3522 sbuddy
->annotation
= NULL
;
3523 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
3525 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
3526 sbuddy
->device_name
= NULL
;
3527 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
3530 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
3531 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
3533 xmlnode_free(xn_presentity
);
3537 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3539 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3541 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
3543 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
3544 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
3546 const char *content
= msg
->body
;
3547 unsigned length
= msg
->bodylen
;
3548 PurpleMimeDocument
*mime
= NULL
;
3550 if (strstr(ctype
, "multipart"))
3552 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3553 const char *content_type
;
3555 mime
= purple_mime_document_parse(doc
);
3556 parts
= purple_mime_document_get_parts(mime
);
3558 content
= purple_mime_part_get_data(parts
->data
);
3559 length
= purple_mime_part_get_length(parts
->data
);
3560 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
3561 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
3563 process_incoming_notify_rlmi_resub(sip
, content
, length
);
3565 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
3567 process_incoming_notify_msrtc(sip
, content
, length
);
3571 process_incoming_notify_rlmi(sip
, content
, length
);
3573 parts
= parts
->next
;
3579 purple_mime_document_free(mime
);
3582 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
3584 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
3586 else if(strstr(ctype
, "application/rlmi+xml"))
3588 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
3591 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
3593 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
3597 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
3602 * Dispatcher for all incoming subscription information
3603 * whether it comes from NOTIFY, BENOTIFY requests or
3604 * piggy-backed to subscription's OK responce.
3606 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3607 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3609 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
3611 gchar
*event
= sipmsg_find_header(msg
, "Event");
3612 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
3613 const char *uri
,*state
;
3615 xmlnode
*xn_resource
;
3616 xmlnode
*xn_instance
;
3620 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
3621 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
3625 const gchar
*expires_header
;
3626 expires_header
= sipmsg_find_header(msg
, "Expires");
3627 expires
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
3628 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires
);
3631 if (!subscription_state
|| strstr(subscription_state
, "active"))
3633 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
3635 sipe_process_presence(sip
, msg
);
3637 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
3639 sipe_process_roaming_contacts(sip
, msg
, NULL
);
3641 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self") && benotify
)
3643 sipe_process_roaming_self(sip
, msg
);
3645 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-provisioning-v2"))
3648 purple_debug_info("sipe", "vnd-microsoft-provisioning-v2 data is not supported yet.");
3650 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
3652 sipe_process_roaming_acl(sip
, msg
);
3654 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
3656 sipe_process_presence_wpending(sip
, msg
);
3660 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
3664 //The server sends a (BE)NOTIFY with the status 'terminated'
3665 if(request
&& subscription_state
&& strstr(subscription_state
, "terminated") )
3667 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3668 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
3672 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3673 if (request
&& !benotify
)
3675 sipmsg_remove_header(msg
, "Expires");
3676 sipmsg_remove_header(msg
, "subscription-state");
3677 sipmsg_remove_header(msg
, "Event");
3678 sipmsg_remove_header(msg
, "Require");
3679 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3686 static gchar* gen_xpidf(struct sipe_account_data *sip)
3688 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3690 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3691 "<display name=\"sip:%s\"/>\r\n"
3692 "<atom id=\"1234\">\r\n"
3693 "<address uri=\"sip:%s\">\r\n"
3694 "<status status=\"%s\"/>\r\n"
3707 static gchar* gen_pidf(struct sipe_account_data *sip)
3709 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3710 "<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"
3711 "<tuple id=\"0\">\r\n"
3713 "<basic>open</basic>\r\n"
3714 "<ep:activities>\r\n"
3715 " <ep:activity>%s</ep:activity>\r\n"
3719 "<ci:display-name>%s</ci:display-name>\r\n"
3728 static void send_presence_soap(struct sipe_account_data
*sip
, const char * note
)
3730 int availability
= 300; // online
3731 int activity
= 400; // Available
3734 if (!strcmp(sip
->status
, "away")) {
3736 } else if (!strcmp(sip
->status
, "out-to-lunch")) {
3738 } else if (!strcmp(sip
->status
, "be-right-back")) {
3740 } else if (!strcmp(sip
->status
, "on-the-phone")) {
3742 } else if (!strcmp(sip
->status
, "do-not-disturb")) {
3744 } else if (!strcmp(sip
->status
, "busy")) {
3746 } else if (!strcmp(sip
->status
, "invisible")) {
3747 availability
= 0; // offline
3751 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
3752 //@TODO: send user data - state; add hostname in upper case
3753 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
3754 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
3760 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3762 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3763 if (msg
->response
== 200) {
3764 sip
->status_version
= 0;
3765 send_presence_status(sip
);
3771 process_send_presence_category_publish_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3773 if (msg
->response
== 409) {
3774 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3775 // TODO need to parse the version #'s?
3776 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
3777 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
3781 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg
->body
);
3783 tmp
= get_contact(sip
);
3784 hdr
= g_strdup_printf("Contact: %s\r\n"
3785 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
3787 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
3797 static void send_presence_category_publish(struct sipe_account_data
*sip
, const char * note
)
3804 if (!strcmp(sip
->status
, "away")) {
3806 } else if (!strcmp(sip
->status
, "busy")) {
3813 uri
= g_strdup_printf("sip:%s", sip
->username
);
3814 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
3815 sip
->status_version
, code
,
3816 sip
->status_version
, code
,
3817 sip
->status_version
, note
? note
: "",
3818 sip
->status_version
, note
? note
: "",
3819 sip
->status_version
, note
? note
: ""
3821 sip
->status_version
++;
3823 tmp
= get_contact(sip
);
3824 hdr
= g_strdup_printf("Contact: %s\r\n"
3825 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
3827 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
3835 static void send_presence_status(struct sipe_account_data
*sip
)
3837 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
3839 if (!status
) return;
3841 note
= purple_status_get_attr_string(status
, "message");
3843 if(sip
->msrtc_event_categories
){
3844 send_presence_category_publish(sip
, note
);
3846 send_presence_soap(sip
, note
);
3850 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
3852 gboolean found
= FALSE
;
3853 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
3854 if (msg
->response
== 0) { /* request */
3855 if (!strcmp(msg
->method
, "MESSAGE")) {
3856 process_incoming_message(sip
, msg
);
3858 } else if (!strcmp(msg
->method
, "NOTIFY")) {
3859 purple_debug_info("sipe","send->process_incoming_notify\n");
3860 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
3862 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
3863 purple_debug_info("sipe","send->process_incoming_benotify\n");
3864 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
3866 } else if (!strcmp(msg
->method
, "INVITE")) {
3867 process_incoming_invite(sip
, msg
);
3869 } else if (!strcmp(msg
->method
, "OPTIONS")) {
3870 process_incoming_options(sip
, msg
);
3872 } else if (!strcmp(msg
->method
, "INFO")) {
3874 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3876 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
3879 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3881 } else if (!strcmp(msg
->method
, "ACK")) {
3882 // ACK's don't need any response
3884 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
3885 // LCS 2005 sends us these - just respond 200 OK
3887 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3888 } else if (!strcmp(msg
->method
, "BYE")) {
3889 struct sip_im_session
*session
;
3891 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3893 from
= parse_from(sipmsg_find_header(msg
, "From"));
3894 session
= find_im_session (sip
, from
);
3898 // TODO Let the user know the other user left the conversation?
3899 im_session_destroy(sip
, session
);
3904 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3906 } else { /* response */
3907 struct transaction
*trans
= transactions_find(sip
, msg
);
3909 if (msg
->response
== 407) {
3910 gchar
*resend
, *auth
, *ptmp
;
3912 if (sip
->proxy
.retries
> 30) return;
3913 sip
->proxy
.retries
++;
3914 /* do proxy authentication */
3916 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
3918 fill_auth(sip
, ptmp
, &sip
->proxy
);
3919 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
3920 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
3921 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
3923 resend
= sipmsg_to_string(trans
->msg
);
3924 /* resend request */
3925 sendout_pkt(sip
->gc
, resend
);
3928 if (msg
->response
== 100 || msg
->response
== 180) {
3929 /* ignore provisional response */
3930 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
3932 sip
->proxy
.retries
= 0;
3933 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
3934 if (msg
->response
== 401)
3936 sip
->registrar
.retries
++;
3937 sip
->registrar
.expires
= 0;
3941 sip
->registrar
.retries
= 0;
3943 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
3945 if (msg
->response
== 401) {
3946 gchar
*resend
, *auth
, *ptmp
;
3948 if (sip
->registrar
.retries
> 4) return;
3949 sip
->registrar
.retries
++;
3951 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3952 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
3954 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3957 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
3959 fill_auth(sip
, ptmp
, &sip
->registrar
);
3960 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
3961 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
3962 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
3964 //sipmsg_remove_header(trans->msg, "Authorization");
3965 //sipmsg_add_header(trans->msg, "Authorization", auth);
3967 resend
= sipmsg_to_string(trans
->msg
);
3968 /* resend request */
3969 sendout_pkt(sip
->gc
, resend
);
3974 if (trans
->callback
) {
3975 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
3976 /* call the callback to process response*/
3977 (trans
->callback
)(sip
, msg
, trans
);
3979 /* Not sure if this is needed or what needs to be done
3980 but transactions seem to be removed prematurely so
3981 this only removes them if the response is 200 OK */
3982 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
3983 /*Has a bug and it's unneccesary*/
3984 /*transactions_remove(sip, trans);*/
3990 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction");
3994 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
3998 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
4006 /* according to the RFC remove CRLF at the beginning */
4007 while (*cur
== '\r' || *cur
== '\n') {
4010 if (cur
!= conn
->inbuf
) {
4011 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
4012 conn
->inbufused
= strlen(conn
->inbuf
);
4015 /* Received a full Header? */
4016 sip
->processing_input
= TRUE
;
4017 while (sip
->processing_input
&&
4018 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
4019 time_t currtime
= time(NULL
);
4022 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
4023 msg
= sipmsg_parse_header(conn
->inbuf
);
4026 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
4027 if (restlen
>= msg
->bodylen
) {
4028 dummy
= g_malloc(msg
->bodylen
+ 1);
4029 memcpy(dummy
, cur
, msg
->bodylen
);
4030 dummy
[msg
->bodylen
] = '\0';
4032 cur
+= msg
->bodylen
;
4033 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
4034 conn
->inbufused
= strlen(conn
->inbuf
);
4036 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4037 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
4043 purple_debug_info("sipe", "body:\n%s", msg->body);
4046 // Verify the signature before processing it
4047 if (sip
->registrar
.ntlm_key
) {
4048 struct sipmsg_breakdown msgbd
;
4049 gchar
*signature_input_str
;
4050 gchar
*signature
= NULL
;
4053 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
4054 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
4055 if (signature_input_str
!= NULL
) {
4056 signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
4058 g_free(signature_input_str
);
4060 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
4062 if (signature
!= NULL
) {
4063 if (rspauth
!= NULL
) {
4064 if (purple_ntlm_verify_signature (signature
, rspauth
)) {
4065 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
4066 process_input_message(sip
, msg
);
4068 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth
, signature
);
4069 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
4070 sip
->gc
->wants_to_die
= TRUE
;
4072 } else if (msg
->response
== 401) {
4073 purple_connection_error(sip
->gc
, _("Wrong Password"));
4074 sip
->gc
->wants_to_die
= TRUE
;
4080 sipmsg_breakdown_free(&msgbd
);
4082 process_input_message(sip
, msg
);
4089 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
4091 PurpleConnection
*gc
= data
;
4092 struct sipe_account_data
*sip
= gc
->proto_data
;
4097 static char buffer
[65536];
4098 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
4100 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
4101 msg
= sipmsg_parse_msg(buffer
);
4102 if (msg
) process_input_message(sip
, msg
);
4106 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
4108 struct sipe_account_data
*sip
= gc
->proto_data
;
4109 PurpleSslConnection
*gsc
= sip
->gsc
;
4111 purple_debug_error("sipe", "%s",debug
);
4112 purple_connection_error(gc
, msg
);
4114 /* Invalidate this connection. Next send will open a new one */
4116 connection_remove(sip
, gsc
->fd
);
4117 purple_ssl_close(gsc
);
4123 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4125 PurpleConnection
*gc
= data
;
4126 struct sipe_account_data
*sip
;
4127 struct sip_connection
*conn
;
4129 gboolean firstread
= TRUE
;
4131 /* NOTE: This check *IS* necessary */
4132 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
4133 purple_ssl_close(gsc
);
4137 sip
= gc
->proto_data
;
4138 conn
= connection_find(sip
, gsc
->fd
);
4140 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4141 gc
->wants_to_die
= TRUE
;
4142 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
4146 /* Read all available data from the SSL connection */
4148 /* Increase input buffer size as needed */
4149 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4150 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4151 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4152 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
4155 /* Try to read as much as there is space left in the buffer */
4156 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
4157 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
4159 if (len
< 0 && errno
== EAGAIN
) {
4160 /* Try again later */
4162 } else if (len
< 0) {
4163 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
4165 } else if (firstread
&& (len
== 0)) {
4166 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
4170 conn
->inbufused
+= len
;
4173 /* Equivalence indicates that there is possibly more data to read */
4174 } while (len
== readlen
);
4176 conn
->inbuf
[conn
->inbufused
] = '\0';
4177 process_input(sip
, conn
);
4181 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4183 PurpleConnection
*gc
= data
;
4184 struct sipe_account_data
*sip
= gc
->proto_data
;
4186 struct sip_connection
*conn
= connection_find(sip
, source
);
4188 purple_debug_error("sipe", "Connection not found!\n");
4192 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4193 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4194 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4197 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
4199 if (len
< 0 && errno
== EAGAIN
)
4201 else if (len
<= 0) {
4202 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4203 connection_remove(sip
, source
);
4204 if (sip
->fd
== source
) sip
->fd
= -1;
4208 conn
->inbufused
+= len
;
4209 conn
->inbuf
[conn
->inbufused
] = '\0';
4211 process_input(sip
, conn
);
4214 /* Callback for new connections on incoming TCP port */
4215 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4217 PurpleConnection
*gc
= data
;
4218 struct sipe_account_data
*sip
= gc
->proto_data
;
4219 struct sip_connection
*conn
;
4221 int newfd
= accept(source
, NULL
, NULL
);
4223 conn
= connection_create(sip
, newfd
);
4225 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4228 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
4230 PurpleConnection
*gc
= data
;
4231 struct sipe_account_data
*sip
;
4232 struct sip_connection
*conn
;
4234 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4242 purple_connection_error(gc
, _("Could not connect"));
4246 sip
= gc
->proto_data
;
4248 sip
->last_keepalive
= time(NULL
);
4250 conn
= connection_create(sip
, source
);
4254 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4257 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4259 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
4260 if (sip
== NULL
) return;
4265 static guint
sipe_ht_hash_nick(const char *nick
)
4267 char *lc
= g_utf8_strdown(nick
, -1);
4268 guint bucket
= g_str_hash(lc
);
4274 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
4276 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
4279 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
4281 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4283 sip
->listen_data
= NULL
;
4285 if (listenfd
== -1) {
4286 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4292 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
4293 sip
->listenfd
= sip
->fd
;
4295 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
4297 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
4301 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
4303 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4306 sip
->query_data
= NULL
;
4308 if (!hosts
|| !hosts
->data
) {
4309 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
4313 addr_size
= GPOINTER_TO_INT(hosts
->data
);
4314 hosts
= g_slist_remove(hosts
, hosts
->data
);
4315 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
4316 g_free(hosts
->data
);
4317 hosts
= g_slist_remove(hosts
, hosts
->data
);
4319 hosts
= g_slist_remove(hosts
, hosts
->data
);
4320 g_free(hosts
->data
);
4321 hosts
= g_slist_remove(hosts
, hosts
->data
);
4324 /* create socket for incoming connections */
4325 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
4326 sipe_udp_host_resolved_listen_cb
, sip
);
4327 if (sip
->listen_data
== NULL
) {
4328 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4333 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
4336 PurpleConnection
*gc
= data
;
4337 struct sipe_account_data
*sip
;
4339 /* If the connection is already disconnected, we don't need to do anything else */
4340 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4343 sip
= gc
->proto_data
;
4348 case PURPLE_SSL_CONNECT_FAILED
:
4349 purple_connection_error(gc
, _("Connection Failed"));
4351 case PURPLE_SSL_HANDSHAKE_FAILED
:
4352 purple_connection_error(gc
, _("SSL Handshake Failed"));
4354 case PURPLE_SSL_CERTIFICATE_INVALID
:
4355 purple_connection_error(gc
, _("SSL Certificate Invalid"));
4361 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
4363 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4364 PurpleProxyConnectData
*connect_data
;
4366 sip
->listen_data
= NULL
;
4368 sip
->listenfd
= listenfd
;
4369 if (sip
->listenfd
== -1) {
4370 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4374 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
4375 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4376 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
4377 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
4378 sipe_newconn_cb
, sip
->gc
);
4379 purple_debug_info("sipe", "connecting to %s port %d\n",
4380 sip
->realhostname
, sip
->realport
);
4381 /* open tcp connection to the server */
4382 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
4383 sip
->realport
, login_cb
, sip
->gc
);
4385 if (connect_data
== NULL
) {
4386 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
4391 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
4393 PurpleAccount
*account
= sip
->account
;
4394 PurpleConnection
*gc
= sip
->gc
;
4396 if (purple_account_get_bool(account
, "useport", FALSE
)) {
4397 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
4398 port
= purple_account_get_int(account
, "port", 0);
4400 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
4403 sip
->realhostname
= hostname
;
4404 sip
->realport
= port
;
4406 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
4409 /* TODO: is there a good default grow size? */
4410 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
4411 sip
->txbuf
= purple_circ_buffer_new(0);
4413 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
4415 if (!purple_ssl_is_supported()) {
4416 gc
->wants_to_die
= TRUE
;
4417 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4421 purple_debug_info("sipe", "using SSL\n");
4423 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
4424 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
4425 if (sip
->gsc
== NULL
) {
4426 purple_connection_error(gc
, _("Could not create SSL context"));
4429 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
4431 purple_debug_info("sipe", "using UDP\n");
4433 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
4434 if (sip
->query_data
== NULL
) {
4435 purple_connection_error(gc
, _("Could not resolve hostname"));
4439 purple_debug_info("sipe", "using TCP\n");
4440 /* create socket for incoming connections */
4441 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
4442 sipe_tcp_connect_listen_cb
, sip
);
4443 if (sip
->listen_data
== NULL
) {
4444 purple_connection_error(gc
, _("Could not create listen socket"));
4450 /* Service list for autodection */
4451 static const struct sipe_service_data service_autodetect
[] = {
4452 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4453 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4454 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4455 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4459 /* Service list for SSL/TLS */
4460 static const struct sipe_service_data service_tls
[] = {
4461 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4462 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4466 /* Service list for TCP */
4467 static const struct sipe_service_data service_tcp
[] = {
4468 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4469 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4473 /* Service list for UDP */
4474 static const struct sipe_service_data service_udp
[] = {
4475 { "sip", "udp", SIPE_TRANSPORT_UDP
},
4479 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
4480 static void resolve_next_service(struct sipe_account_data
*sip
,
4481 const struct sipe_service_data
*start
)
4484 sip
->service_data
= start
;
4486 sip
->service_data
++;
4487 if (sip
->service_data
->service
== NULL
) {
4489 /* Try connecting to the SIP hostname directly */
4490 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
4491 if (sip
->auto_transport
) {
4492 // If SSL is supported, default to using it; OCS servers aren't configured
4493 // by default to accept TCP
4494 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4495 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
4496 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
4499 hostname
= g_strdup(sip
->sipdomain
);
4500 create_connection(sip
, hostname
, 0);
4505 /* Try to resolve next service */
4506 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
4507 sip
->service_data
->transport
,
4512 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
4514 struct sipe_account_data
*sip
= data
;
4516 sip
->srv_query_data
= NULL
;
4518 /* find the host to connect to */
4520 gchar
*hostname
= g_strdup(resp
->hostname
);
4521 int port
= resp
->port
;
4522 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4526 sip
->transport
= sip
->service_data
->type
;
4528 create_connection(sip
, hostname
, port
);
4530 resolve_next_service(sip
, NULL
);
4534 static void sipe_login(PurpleAccount
*account
)
4536 PurpleConnection
*gc
;
4537 struct sipe_account_data
*sip
;
4538 gchar
**signinname_login
, **userserver
, **domain_user
;
4539 const char *transport
;
4541 const char *username
= purple_account_get_username(account
);
4542 gc
= purple_account_get_connection(account
);
4544 if (strpbrk(username
, " \t\v\r\n") != NULL
) {
4545 gc
->wants_to_die
= TRUE
;
4546 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
4550 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
4551 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
4552 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
4554 sip
->account
= account
;
4555 sip
->reregister_set
= FALSE
;
4556 sip
->reauthenticate_set
= FALSE
;
4557 sip
->subscribed
= FALSE
;
4558 sip
->subscribed_buddies
= FALSE
;
4560 signinname_login
= g_strsplit(username
, ",", 2);
4562 userserver
= g_strsplit(signinname_login
[0], "@", 2);
4563 purple_connection_set_display_name(gc
, userserver
[0]);
4564 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
4565 sip
->sipdomain
= g_strdup(userserver
[1]);
4567 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
4568 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
4569 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
4571 sip
->password
= g_strdup(purple_connection_get_password(gc
));
4573 g_strfreev(userserver
);
4574 g_strfreev(domain_user
);
4575 g_strfreev(signinname_login
);
4577 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
4579 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
4581 /* TODO: Set the status correctly. */
4582 sip
->status
= g_strdup("available");
4584 transport
= purple_account_get_string(account
, "transport", "auto");
4585 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
4586 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
4589 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
4590 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
4591 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
4592 } else if (strcmp(transport
, "auto") == 0) {
4593 sip
->auto_transport
= TRUE
;
4594 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
4595 } else if (strcmp(transport
, "tls") == 0) {
4596 resolve_next_service(sip
, service_tls
);
4597 } else if (strcmp(transport
, "tcp") == 0) {
4598 resolve_next_service(sip
, service_tcp
);
4600 resolve_next_service(sip
, service_udp
);
4604 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
4606 connection_free_all(sip
);
4608 if (sip
->query_data
!= NULL
)
4609 purple_dnsquery_destroy(sip
->query_data
);
4610 sip
->query_data
= NULL
;
4612 if (sip
->srv_query_data
!= NULL
)
4613 purple_srv_cancel(sip
->srv_query_data
);
4614 sip
->srv_query_data
= NULL
;
4616 if (sip
->listen_data
!= NULL
)
4617 purple_network_listen_cancel(sip
->listen_data
);
4618 sip
->listen_data
= NULL
;
4620 if (sip
->gsc
!= NULL
)
4621 purple_ssl_close(sip
->gsc
);
4624 sipe_auth_free(&sip
->registrar
);
4625 sipe_auth_free(&sip
->proxy
);
4628 purple_circ_buffer_destroy(sip
->txbuf
);
4631 g_free(sip
->realhostname
);
4632 sip
->realhostname
= NULL
;
4635 purple_input_remove(sip
->listenpa
);
4637 if (sip
->tx_handler
)
4638 purple_input_remove(sip
->tx_handler
);
4639 sip
->tx_handler
= 0;
4640 if (sip
->resendtimeout
)
4641 purple_timeout_remove(sip
->resendtimeout
);
4642 sip
->resendtimeout
= 0;
4643 if (sip
->timeouts
) {
4644 GSList
*entry
= sip
->timeouts
;
4646 struct scheduled_action
*sched_action
= entry
->data
;
4647 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
4648 purple_timeout_remove(sched_action
->timeout_handler
);
4649 g_free(sched_action
->payload
);
4650 g_free(sched_action
->name
);
4651 g_free(sched_action
);
4652 entry
= entry
->next
;
4655 g_slist_free(sip
->timeouts
);
4657 g_slist_free(sip
->allow_events
);
4660 g_free(sip
->contact
);
4661 sip
->contact
= NULL
;
4663 g_free(sip
->regcallid
);
4664 sip
->regcallid
= NULL
;
4667 sip
->processing_input
= FALSE
;
4671 * A callback for g_hash_table_foreach_remove
4673 static gboolean
sipe_buddy_remove(gpointer key
, struct sipe_buddy
*buddy
, gpointer user_data
)
4675 sipe_free_buddy(buddy
);
4678 static void sipe_close(PurpleConnection
*gc
)
4680 struct sipe_account_data
*sip
= gc
->proto_data
;
4683 /* leave all conversations */
4684 im_session_close_all(sip
);
4687 do_register_exp(sip
, 0);
4689 sipe_connection_cleanup(sip
);
4690 g_free(sip
->sipdomain
);
4691 g_free(sip
->username
);
4692 g_free(sip
->password
);
4693 g_free(sip
->authdomain
);
4694 g_free(sip
->authuser
);
4695 g_free(sip
->status
);
4697 g_hash_table_foreach_remove(sip
->buddies
, (GHRFunc
) sipe_buddy_remove
, NULL
);
4698 g_hash_table_destroy(sip
->buddies
);
4700 g_free(gc
->proto_data
);
4701 gc
->proto_data
= NULL
;
4704 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4706 PurpleAccount
*acct
= purple_connection_get_account(gc
);
4707 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
4708 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
4710 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
4711 purple_conversation_present(conv
);
4715 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4718 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
4719 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
4722 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
4724 PurpleNotifySearchResults
*results
;
4725 PurpleNotifySearchColumn
*column
;
4726 xmlnode
*searchResults
;
4728 int match_count
= 0;
4729 gboolean more
= FALSE
;
4732 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
4734 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4735 if (!searchResults
) {
4736 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4740 results
= purple_notify_searchresults_new();
4742 if (results
== NULL
) {
4743 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4744 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
4746 xmlnode_free(searchResults
);
4750 column
= purple_notify_searchresults_column_new(_("User Name"));
4751 purple_notify_searchresults_column_add(results
, column
);
4753 column
= purple_notify_searchresults_column_new(_("Name"));
4754 purple_notify_searchresults_column_add(results
, column
);
4756 column
= purple_notify_searchresults_column_new(_("Company"));
4757 purple_notify_searchresults_column_add(results
, column
);
4759 column
= purple_notify_searchresults_column_new(_("Country"));
4760 purple_notify_searchresults_column_add(results
, column
);
4762 column
= purple_notify_searchresults_column_new(_("Email"));
4763 purple_notify_searchresults_column_add(results
, column
);
4765 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
4768 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
4769 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
4770 g_strfreev(uri_parts
);
4772 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
4773 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
4774 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
4775 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
4777 purple_notify_searchresults_row_add(results
, row
);
4781 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
4782 char *data
= xmlnode_get_data_unescaped(mrow
);
4783 more
= (g_strcasecmp(data
, "true") == 0);
4787 secondary
= g_strdup_printf(
4788 dngettext(GETTEXT_PACKAGE
,
4789 "Found %d contact%s:",
4790 "Found %d contacts%s:", match_count
),
4791 match_count
, more
? _(" (more matched your query)") : "");
4793 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
4794 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
4795 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
4798 xmlnode_free(searchResults
);
4802 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
4804 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
4805 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
4809 PurpleRequestField
*field
= entries
->data
;
4810 const char *id
= purple_request_field_get_id(field
);
4811 const char *value
= purple_request_field_string_get_value(field
);
4813 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
4815 if (value
!= NULL
) attrs
[i
++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW
, id
, value
);
4816 } while ((entries
= g_list_next(entries
)) != NULL
);
4820 gchar
*query
= g_strjoinv(NULL
, attrs
);
4821 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
4822 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
4823 send_soap_request_with_cb(gc
->proto_data
, body
,
4824 (TransCallback
) process_search_contact_response
, NULL
);
4832 static void sipe_show_find_contact(PurplePluginAction
*action
)
4834 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
4835 PurpleRequestFields
*fields
;
4836 PurpleRequestFieldGroup
*group
;
4837 PurpleRequestField
*field
;
4839 fields
= purple_request_fields_new();
4840 group
= purple_request_field_group_new(NULL
);
4841 purple_request_fields_add_group(fields
, group
);
4843 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
4844 purple_request_field_group_add_field(group
, field
);
4845 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
4846 purple_request_field_group_add_field(group
, field
);
4847 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
4848 purple_request_field_group_add_field(group
, field
);
4849 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
4850 purple_request_field_group_add_field(group
, field
);
4852 purple_request_fields(gc
,
4854 _("Search for a Contact"),
4855 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4857 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
4859 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
4862 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
4865 PurplePluginAction
*act
;
4867 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
4868 menu
= g_list_prepend(menu
, act
);
4870 menu
= g_list_reverse(menu
);
4875 static void dummy_permit_deny(PurpleConnection
*gc
)
4879 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
4885 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
4891 static char *sipe_status_text(PurpleBuddy
*buddy
)
4893 struct sipe_account_data
*sip
;
4894 struct sipe_buddy
*sbuddy
;
4897 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
4898 if (sip
) //happens on pidgin exit
4900 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
4901 if (sbuddy
&& sbuddy
->annotation
)
4903 text
= g_strdup(sbuddy
->annotation
);
4910 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
4912 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
4913 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
4914 struct sipe_account_data
*sip
;
4915 struct sipe_buddy
*sbuddy
;
4916 char *annotation
= NULL
;
4918 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
4919 if (sip
) //happens on pidgin exit
4921 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
4924 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
4929 if (purple_presence_is_online(presence
))
4931 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
4936 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
4943 sipe_get_account_text_table(PurpleAccount
*account
)
4946 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
4947 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
4951 static PurpleBuddy
*
4952 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
4955 const gchar
*server_alias
, *email
;
4956 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
4958 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
4960 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
4962 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
4964 purple_blist_server_alias_buddy(clone
, server_alias
);
4967 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
4969 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
4972 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
4974 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
4979 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
4981 PurpleBuddy
*buddy
, *b
;
4982 PurpleConnection
*gc
;
4983 PurpleGroup
* group
= purple_find_group(group_name
);
4985 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
4987 buddy
= (PurpleBuddy
*)node
;
4989 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
4990 gc
= purple_account_get_connection(buddy
->account
);
4992 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
4994 b
= purple_blist_add_buddy_clone(group
, buddy
);
4997 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
5001 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
5004 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
5006 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5009 char *mailto
= g_strdup_printf("mailto:%s", email
);
5010 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
5014 char *const parmList
[] = {mailto
, NULL
};
5015 if ((pid
= fork()) == -1)
5017 purple_debug_info("sipe", "fork() error\n");
5021 execvp("xdg-email", parmList
);
5022 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5030 //@TODO resolve env variable %WINDIR% first
5031 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
5034 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
5043 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
5048 * A menu which appear when right-clicking on buddy in contact list.
5051 sipe_buddy_menu(PurpleBuddy
*buddy
)
5053 PurpleBlistNode
*g_node
;
5054 PurpleGroup
*group
, *gr_parent
;
5055 PurpleMenuAction
*act
;
5057 GList
*menu_groups
= NULL
;
5059 act
= purple_menu_action_new(_("Send Email..."),
5060 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
5062 menu
= g_list_prepend(menu
, act
);
5064 gr_parent
= purple_buddy_get_group(buddy
);
5065 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
5066 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
5069 group
= (PurpleGroup
*)g_node
;
5070 if (group
== gr_parent
)
5073 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
5076 act
= purple_menu_action_new(purple_group_get_name(group
),
5077 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
5079 menu_groups
= g_list_prepend(menu_groups
, act
);
5081 menu_groups
= g_list_reverse(menu_groups
);
5083 act
= purple_menu_action_new(_("Copy to"),
5086 menu
= g_list_prepend(menu
, act
);
5087 menu
= g_list_reverse(menu
);
5092 GList
*sipe_blist_node_menu(PurpleBlistNode
*node
) {
5093 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
5094 return sipe_buddy_menu((PurpleBuddy
*) node
);
5101 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
5103 gboolean ret
= TRUE
;
5104 char *username
= (char *)trans
->payload
;
5106 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
5107 PurpleBuddy
*pbuddy
;
5108 struct sipe_buddy
*sbuddy
;
5110 char *server_alias
= NULL
;
5112 const char *device_name
= NULL
;
5114 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
5116 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
5117 alias
= purple_buddy_get_local_alias(pbuddy
);
5121 //will query buddy UA's capabilities and send answer to log
5122 sipe_options_request(sip
, username
);
5124 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
5127 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
5131 if (msg
->response
!= 200) {
5132 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
5134 xmlnode
*searchResults
;
5137 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
5138 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5139 if (!searchResults
) {
5140 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5141 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
5142 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
5143 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5144 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
5145 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
5146 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
5147 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
5148 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
5149 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
5150 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
5151 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
5152 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5153 if (!email
|| strcmp("", email
)) {
5154 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
5155 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
5159 xmlnode_free(searchResults
);
5162 purple_notify_user_info_add_section_break(info
);
5164 if (!server_alias
|| !strcmp("", server_alias
)) {
5165 g_free(server_alias
);
5166 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
5168 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5172 // same as server alias, do not present
5173 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
5176 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
5179 if (!email
|| !strcmp("", email
)) {
5181 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
5183 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5189 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
5192 /* show a buddy's user info in a nice dialog box */
5193 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
5194 username
, /* buddy's username */
5196 NULL
, /* callback called when dialog closed */
5197 NULL
); /* userdata for callback */
5203 * AD search first, LDAP based
5205 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
5207 char *row
= g_strdup_printf(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
5208 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
5210 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
5211 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
5212 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
5217 static PurplePlugin
*my_protocol
= NULL
;
5219 static PurplePluginProtocolInfo prpl_info
=
5222 NULL
, /* user_splits */
5223 NULL
, /* protocol_options */
5224 NO_BUDDY_ICONS
, /* icon_spec */
5225 sipe_list_icon
, /* list_icon */
5226 NULL
, /* list_emblems */
5227 sipe_status_text
, /* status_text */
5228 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
5229 sipe_status_types
, /* away_states */
5230 sipe_blist_node_menu
, /* blist_node_menu */
5231 NULL
, /* chat_info */
5232 NULL
, /* chat_info_defaults */
5233 sipe_login
, /* login */
5234 sipe_close
, /* close */
5235 sipe_im_send
, /* send_im */
5236 NULL
, /* set_info */ // TODO maybe
5237 sipe_send_typing
, /* send_typing */
5238 sipe_get_info
, /* get_info */
5239 sipe_set_status
, /* set_status */
5240 NULL
, /* set_idle */
5241 NULL
, /* change_passwd */
5242 sipe_add_buddy
, /* add_buddy */
5243 NULL
, /* add_buddies */
5244 sipe_remove_buddy
, /* remove_buddy */
5245 NULL
, /* remove_buddies */
5246 sipe_add_permit
, /* add_permit */
5247 sipe_add_deny
, /* add_deny */
5248 sipe_add_deny
, /* rem_permit */
5249 sipe_add_permit
, /* rem_deny */
5250 dummy_permit_deny
, /* set_permit_deny */
5251 NULL
, /* join_chat */
5252 NULL
, /* reject_chat */
5253 NULL
, /* get_chat_name */
5254 NULL
, /* chat_invite */
5255 NULL
, /* chat_leave */
5256 NULL
, /* chat_whisper */
5257 NULL
, /* chat_send */
5258 sipe_keep_alive
, /* keepalive */
5259 NULL
, /* register_user */
5260 NULL
, /* get_cb_info */ // deprecated
5261 NULL
, /* get_cb_away */ // deprecated
5262 sipe_alias_buddy
, /* alias_buddy */
5263 sipe_group_buddy
, /* group_buddy */
5264 sipe_rename_group
, /* rename_group */
5265 NULL
, /* buddy_free */
5266 sipe_convo_closed
, /* convo_closed */
5267 purple_normalize_nocase
, /* normalize */
5268 NULL
, /* set_buddy_icon */
5269 sipe_remove_group
, /* remove_group */
5270 NULL
, /* get_cb_real_name */ // TODO?
5271 NULL
, /* set_chat_topic */
5272 NULL
, /* find_blist_chat */
5273 NULL
, /* roomlist_get_list */
5274 NULL
, /* roomlist_cancel */
5275 NULL
, /* roomlist_expand_category */
5276 NULL
, /* can_receive_file */
5277 NULL
, /* send_file */
5278 NULL
, /* new_xfer */
5279 NULL
, /* offline_message */
5280 NULL
, /* whiteboard_prpl_ops */
5281 sipe_send_raw
, /* send_raw */
5282 NULL
, /* roomlist_room_serialize */
5283 NULL
, /* unregister_user */
5284 NULL
, /* send_attention */
5285 NULL
, /* get_attention_types */
5287 sizeof(PurplePluginProtocolInfo
), /* struct_size */
5288 sipe_get_account_text_table
, /* get_account_text_table */
5292 static PurplePluginInfo info
= {
5293 PURPLE_PLUGIN_MAGIC
,
5294 PURPLE_MAJOR_VERSION
,
5295 PURPLE_MINOR_VERSION
,
5296 PURPLE_PLUGIN_PROTOCOL
, /**< type */
5297 NULL
, /**< ui_requirement */
5299 NULL
, /**< dependencies */
5300 PURPLE_PRIORITY_DEFAULT
, /**< priority */
5301 "prpl-sipe", /**< id */
5302 "Microsoft LCS/OCS", /**< name */
5303 VERSION
, /**< version */
5304 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5305 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5306 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5307 "Gabriel Burt <gburt@novell.com>", /**< author */
5308 PURPLE_WEBSITE
, /**< homepage */
5309 sipe_plugin_load
, /**< load */
5310 sipe_plugin_unload
, /**< unload */
5311 sipe_plugin_destroy
, /**< destroy */
5312 NULL
, /**< ui_info */
5313 &prpl_info
, /**< extra_info */
5322 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
5326 entry
= prpl_info
.protocol_options
;
5328 purple_account_option_destroy(entry
->data
);
5329 entry
= g_list_delete_link(entry
, entry
);
5331 prpl_info
.protocol_options
= NULL
;
5333 entry
= prpl_info
.user_splits
;
5335 purple_account_user_split_destroy(entry
->data
);
5336 entry
= g_list_delete_link(entry
, entry
);
5338 prpl_info
.user_splits
= NULL
;
5341 static void init_plugin(PurplePlugin
*plugin
)
5343 PurpleAccountUserSplit
*split
;
5344 PurpleAccountOption
*option
;
5347 purple_debug_info(PACKAGE
, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
5348 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s",
5349 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
5352 purple_plugin_register(plugin
);
5354 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
5355 purple_account_user_split_set_reverse(split
, FALSE
);
5356 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
5358 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
5359 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5360 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5361 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5363 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
5364 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5365 // Translators: noun (networking port)
5366 option
= purple_account_option_int_new(_("Port"), "port", 5061);
5367 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5369 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
5370 purple_account_option_add_list_item(option
, _("Auto"), "auto");
5371 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
5372 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
5373 purple_account_option_add_list_item(option
, _("UDP"), "udp");
5374 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5376 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5377 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5379 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
5380 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5382 // TODO commented out so won't show in the preferences until we fix krb message signing
5383 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5384 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5386 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5387 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5388 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5391 option
= purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE
);
5392 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5393 option
= purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5394 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5395 my_protocol
= plugin
;
5398 /* I had to redefined the function for it load, but works */
5399 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
5400 plugin
->info
= &(info
);
5401 init_plugin((plugin
));
5402 sipe_plugin_load((plugin
));
5403 return purple_plugin_register(plugin
);