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_to_name_single(struct sipe_account_data
*sip
, const char * buddy_name
);
143 static void sipe_subscribe_to_buddies_batched(struct sipe_account_data
*sip
);
144 static void send_presence_info(struct sipe_account_data
*sip
);
146 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
148 static void sipe_keep_alive_timeout(struct sipe_account_data
*sip
, const gchar
*hdr
)
150 gchar
*timeout
= sipmsg_find_part_of_header(hdr
, "timeout=", ";", NULL
);
151 if (timeout
!= NULL
) {
152 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
153 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
154 sip
->keepalive_timeout
);
159 static void sipe_keep_alive(PurpleConnection
*gc
)
161 struct sipe_account_data
*sip
= gc
->proto_data
;
162 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
163 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
164 gchar buf
[2] = {0, 0};
165 purple_debug_info("sipe", "sending keep alive\n");
166 sendto(sip
->fd
, buf
, 1, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
));
168 time_t now
= time(NULL
);
169 if ((sip
->keepalive_timeout
> 0) &&
170 ((now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
171 #if PURPLE_VERSION_CHECK(2,4,0)
172 && ((now
- gc
->last_received
) >= sip
->keepalive_timeout
)
175 purple_debug_info("sipe", "sending keep alive\n");
176 sendout_pkt(gc
, "\r\n\r\n");
177 sip
->last_keepalive
= now
;
182 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
184 struct sip_connection
*ret
= NULL
;
185 GSList
*entry
= sip
->openconns
;
188 if (ret
->fd
== fd
) return ret
;
194 static void sipe_auth_free(struct sip_auth
*auth
)
198 g_free(auth
->opaque
);
202 g_free(auth
->target
);
204 g_free(auth
->digest_session_key
);
205 auth
->digest_session_key
= NULL
;
206 g_free(auth
->ntlm_key
);
207 auth
->ntlm_key
= NULL
;
208 auth
->type
= AUTH_TYPE_UNSET
;
213 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
215 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
217 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
221 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
223 struct sip_connection
*conn
= connection_find(sip
, fd
);
225 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
226 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
232 static void connection_free_all(struct sipe_account_data
*sip
)
234 struct sip_connection
*ret
= NULL
;
235 GSList
*entry
= sip
->openconns
;
238 connection_remove(sip
, ret
->fd
);
239 entry
= sip
->openconns
;
243 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
245 const gchar
*method
= msg
->method
;
246 const gchar
*target
= msg
->target
;
251 const char *authdomain
= sip
->authdomain
;
252 const char *authuser
= sip
->authuser
;
253 //const char *krb5_realm;
255 //gchar *krb5_token = NULL;
257 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
258 // and do error checking
260 // KRB realm should always be uppercase
261 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
263 if (sip
->realhostname
) {
264 host
= sip
->realhostname
;
265 } else if (purple_account_get_bool(sip
->account
, "useproxy", TRUE
)) {
266 host
= purple_account_get_string(sip
->account
, "proxy", "");
268 host
= sip
->sipdomain
;
271 /*gboolean new_auth = krb5_auth.gss_context == NULL;
273 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
276 if (new_auth || force_reauth) {
277 krb5_token = krb5_auth.base64_token;
280 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
281 krb5_token = krb5_auth.base64_token;*/
287 if (!authuser
|| strlen(authuser
) < 1) {
288 authuser
= sip
->username
;
291 if (auth
->type
== AUTH_TYPE_DIGEST
) { /* Digest */
292 sprintf(noncecount
, "%08d", auth
->nc
++);
293 response
= purple_cipher_http_digest_calculate_response(
294 "md5", method
, target
, NULL
, NULL
,
295 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
296 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
298 ret
= g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser
, auth
->realm
, auth
->nonce
, target
, noncecount
, response
);
301 } else if (auth
->type
== AUTH_TYPE_NTLM
) { /* NTLM */
302 // If we have a signature for the message, include that
303 if (msg
->signature
) {
304 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, msg
->rand
, msg
->num
, msg
->signature
);
308 if (auth
->nc
== 3 && auth
->nonce
&& auth
->ntlm_key
== NULL
) {
309 const gchar
*ntlm_key
;
311 #if GLIB_CHECK_VERSION(2,8,0)
312 const gchar
* hostname
= g_get_host_name();
314 static char hostname
[256];
315 int ret
= gethostname(hostname
, sizeof(hostname
));
316 hostname
[sizeof(hostname
) - 1] = '\0';
317 if (ret
== -1 || hostname
[0] == '\0') {
318 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Error when getting host name. Using \"localhost.\"\n");
320 strcpy(hostname
, "localhost");
323 /*const gchar * hostname = purple_get_host_name();*/
325 gssapi_data
= purple_ntlm_gen_authenticate(&ntlm_key
, authuser
, sip
->password
, hostname
, authdomain
, (const guint8
*)auth
->nonce
, &auth
->flags
);
326 auth
->ntlm_key
= (gchar
*)ntlm_key
;
327 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, gssapi_data
);
332 tmp
= g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth
->realm
, auth
->target
);
334 } else if (auth
->type
== AUTH_TYPE_KERBEROS
) {
337 /*if (new_auth || force_reauth) {
338 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
340 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, krb5_token);
342 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
345 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
346 gchar * mic = "MICTODO";
347 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
348 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
349 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
350 //auth->opaque ? auth->opaque : "", auth->target);
351 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
356 tmp
= g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth
->target
);
359 sprintf(noncecount
, "%08d", auth
->nc
++);
360 response
= purple_cipher_http_digest_calculate_response(
361 "md5", method
, target
, NULL
, NULL
,
362 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
363 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
365 ret
= g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser
, auth
->realm
, auth
->nonce
, target
, noncecount
, response
);
370 static char *parse_attribute(const char *attrname
, const char *source
)
372 const char *tmp
, *tmp2
;
374 int len
= strlen(attrname
);
376 if (!strncmp(source
, attrname
, len
)) {
378 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
380 retval
= g_strndup(tmp
, tmp2
- tmp
);
382 retval
= g_strdup(tmp
);
388 static void fill_auth(struct sipe_account_data
*sip
, gchar
*hdr
, struct sip_auth
*auth
)
391 const char *authuser
;
394 //const char *krb5_realm;
397 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
398 // and do error checking
400 // KRB realm should always be uppercase
401 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
403 if (sip->realhostname) {
404 host = sip->realhostname;
405 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
406 host = purple_account_get_string(sip->account, "proxy", "");
408 host = sip->sipdomain;
411 authuser
= sip
->authuser
;
413 if (!authuser
|| strlen(authuser
) < 1) {
414 authuser
= sip
->username
;
418 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
422 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
423 auth
->type
= AUTH_TYPE_NTLM
;
424 parts
= g_strsplit(hdr
+5, "\", ", 0);
427 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
428 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
430 auth
->nonce
= g_memdup(purple_ntlm_parse_challenge(tmp
, &auth
->flags
), 8);
433 if ((tmp
= parse_attribute("targetname=\"",
435 g_free(auth
->target
);
438 else if ((tmp
= parse_attribute("realm=\"",
443 else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
444 g_free(auth
->opaque
);
451 if (!strstr(hdr
, "gssapi-data")) {
459 if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
460 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "setting auth type to Kerberos (3)\r\n");
461 auth
->type
= AUTH_TYPE_KERBEROS
;
462 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth - header: %s\r\n", hdr
);
463 parts
= g_strsplit(hdr
+9, "\", ", 0);
466 purple_debug_info("sipe", "krb - parts[i] %s\n", parts
[i
]);
467 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
468 /*if (krb5_auth.gss_context == NULL) {
469 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
471 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
474 if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
475 g_free(auth
->target
);
477 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
480 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
481 g_free(auth
->opaque
);
491 auth
->type
= AUTH_TYPE_DIGEST
;
492 parts
= g_strsplit(hdr
, " ", 0);
494 if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
498 else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
506 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "nonce: %s realm: %s\n", auth
->nonce
? auth
->nonce
: "(null)", auth
->realm
? auth
->realm
: "(null)");
508 g_free(auth
->digest_session_key
);
509 auth
->digest_session_key
= purple_cipher_http_digest_calculate_session_key(
510 "md5", authuser
, auth
->realm
, sip
->password
, auth
->nonce
, NULL
);
516 static void sipe_canwrite_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
518 PurpleConnection
*gc
= data
;
519 struct sipe_account_data
*sip
= gc
->proto_data
;
523 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
525 if (max_write
== 0) {
526 if (sip
->tx_handler
!= 0){
527 purple_input_remove(sip
->tx_handler
);
533 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
535 if (written
< 0 && errno
== EAGAIN
)
537 else if (written
<= 0) {
538 /*TODO: do we really want to disconnect on a failure to write?*/
539 purple_connection_error(gc
, _("Could not write"));
543 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
546 static void sipe_canwrite_cb_ssl(gpointer data
, gint src
, PurpleInputCondition cond
)
548 PurpleConnection
*gc
= data
;
549 struct sipe_account_data
*sip
= gc
->proto_data
;
553 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
555 if (max_write
== 0) {
556 if (sip
->tx_handler
!= 0) {
557 purple_input_remove(sip
->tx_handler
);
563 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
565 if (written
< 0 && errno
== EAGAIN
)
567 else if (written
<= 0) {
568 /*TODO: do we really want to disconnect on a failure to write?*/
569 purple_connection_error(gc
, _("Could not write"));
573 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
576 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
578 static void send_later_cb(gpointer data
, gint source
, const gchar
*error
)
580 PurpleConnection
*gc
= data
;
581 struct sipe_account_data
*sip
;
582 struct sip_connection
*conn
;
584 if (!PURPLE_CONNECTION_IS_VALID(gc
))
592 purple_connection_error(gc
, _("Could not connect"));
596 sip
= gc
->proto_data
;
598 sip
->connecting
= FALSE
;
599 sip
->last_keepalive
= time(NULL
);
601 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
603 /* If there is more to write now, we need to register a handler */
604 if (sip
->txbuf
->bufused
> 0)
605 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
607 conn
= connection_create(sip
, source
);
608 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
611 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
613 struct sipe_account_data
*sip
;
614 struct sip_connection
*conn
;
616 if (!PURPLE_CONNECTION_IS_VALID(gc
))
618 if (gsc
) purple_ssl_close(gsc
);
622 sip
= gc
->proto_data
;
625 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
626 sip
->connecting
= FALSE
;
627 sip
->last_keepalive
= time(NULL
);
629 conn
= connection_create(sip
, gsc
->fd
);
631 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
636 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
638 PurpleConnection
*gc
= data
;
639 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
640 if (sip
== NULL
) return;
642 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
644 /* If there is more to write now */
645 if (sip
->txbuf
->bufused
> 0) {
646 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
651 static void sendlater(PurpleConnection
*gc
, const char *buf
)
653 struct sipe_account_data
*sip
= gc
->proto_data
;
655 if (!sip
->connecting
) {
656 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
657 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
658 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
660 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
661 purple_connection_error(gc
, _("Couldn't create socket"));
664 sip
->connecting
= TRUE
;
667 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
668 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
670 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
673 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
675 struct sipe_account_data
*sip
= gc
->proto_data
;
676 time_t currtime
= time(NULL
);
677 int writelen
= strlen(buf
);
679 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
680 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
681 if (sendto(sip
->fd
, buf
, writelen
, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
682 purple_debug_info("sipe", "could not send packet\n");
691 if (sip
->tx_handler
) {
696 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
698 ret
= write(sip
->fd
, buf
, writelen
);
702 if (ret
< 0 && errno
== EAGAIN
)
704 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
709 if (ret
< writelen
) {
710 if (!sip
->tx_handler
){
712 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
715 sip
->tx_handler
= purple_input_add(sip
->fd
,
716 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
721 /* XXX: is it OK to do this? You might get part of a request sent
722 with part of another. */
723 if (sip
->txbuf
->bufused
> 0)
724 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
726 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
732 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
734 sendout_pkt(gc
, buf
);
738 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
740 GSList
*tmp
= msg
->headers
;
743 GString
*outstr
= g_string_new("");
744 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
746 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
747 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
748 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
749 tmp
= g_slist_next(tmp
);
751 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
752 sendout_pkt(sip
->gc
, outstr
->str
);
753 g_string_free(outstr
, TRUE
);
756 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
759 if (sip
->registrar
.ntlm_key
) {
760 struct sipmsg_breakdown msgbd
;
761 gchar
*signature_input_str
;
763 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
764 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
765 sip
->registrar
.ntlm_num
++;
766 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
767 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
768 if (signature_input_str
!= NULL
) {
769 msg
->signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
770 msg
->rand
= g_strdup(msgbd
.rand
);
771 msg
->num
= g_strdup(msgbd
.num
);
772 g_free(signature_input_str
);
774 sipmsg_breakdown_free(&msgbd
);
777 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
778 buf
= auth_header(sip
, &sip
->registrar
, msg
);
779 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
780 sipmsg_add_header(msg
, "Authorization", buf
);
782 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
785 } else if (!strcmp(method
,"SUBSCRIBE") || !strcmp(method
,"SERVICE") || !strcmp(method
,"MESSAGE") || !strcmp(method
,"INVITE") || !strcmp(method
, "ACK") || !strcmp(method
, "NOTIFY") || !strcmp(method
, "BYE") || !strcmp(method
, "INFO") || !strcmp(method
, "OPTIONS")) {
786 sip
->registrar
.nc
= 3;
787 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
789 buf
= auth_header(sip
, &sip
->registrar
, msg
);
790 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
793 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
797 static char *get_contact(struct sipe_account_data
*sip
)
799 return g_strdup(sip
->contact
);
804 static char *get_contact_service(struct sipe_account_data *sip)
806 return g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip->listenport, TRANSPORT_DESCRIPTOR, generateUUIDfromEPID(get_epid()));
807 //return g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip->username, sip->listenport, purple_network_get_my_ip(-1), TRANSPORT_DESCRIPTOR);
811 static void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
812 const char *text
, const char *body
)
816 GString
*outstr
= g_string_new("");
817 struct sipe_account_data
*sip
= gc
->proto_data
;
821 sipmsg_remove_header(msg
, "ms-user-data");
823 contact
= get_contact(sip
);
824 sipmsg_remove_header(msg
, "Contact");
825 sipmsg_add_header(msg
, "Contact", contact
);
828 /* When sending the acknowlegements and errors, the content length from the original
829 message is still here, but there is no body; we need to make sure we're sending the
830 correct content length */
831 sipmsg_remove_header(msg
, "Content-Length");
834 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
835 sipmsg_add_header(msg
, "Content-Length", len
);
837 sipmsg_remove_header(msg
, "Content-Type");
838 sipmsg_add_header(msg
, "Content-Length", "0");
841 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
842 //gchar * mic = "MICTODO";
843 msg
->response
= code
;
845 sipmsg_remove_header(msg
, "Authentication-Info");
846 sign_outgoing_message(msg
, sip
, msg
->method
);
848 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
851 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
852 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
854 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
855 tmp
= g_slist_next(tmp
);
857 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
858 sendout_pkt(gc
, outstr
->str
);
859 g_string_free(outstr
, TRUE
);
862 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
864 if (trans
->msg
) sipmsg_free(trans
->msg
);
865 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
869 static struct transaction
*
870 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
872 struct transaction
*trans
= g_new0(struct transaction
, 1);
873 trans
->time
= time(NULL
);
874 trans
->msg
= (struct sipmsg
*)msg
;
875 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
876 trans
->callback
= callback
;
877 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
881 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
883 struct transaction
*trans
;
884 GSList
*transactions
= sip
->transactions
;
885 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
887 while (transactions
) {
888 trans
= transactions
->data
;
889 if (!strcmp(trans
->cseq
, cseq
)) {
892 transactions
= transactions
->next
;
898 static struct transaction
*
899 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
900 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
901 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
903 struct sipe_account_data
*sip
= gc
->proto_data
;
904 const char *addh
= "";
907 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
908 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
909 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
910 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
911 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
912 gchar
*useragent
= (gchar
*)purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
);
913 gchar
*route
= strdup("");
914 gchar
*epid
= get_epid(); // TODO generate one per account/login
915 struct transaction
*trans
;
917 if (dialog
&& dialog
->routes
)
919 GSList
*iter
= dialog
->routes
;
924 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
926 iter
= g_slist_next(iter
);
930 if (!ourtag
&& !dialog
) {
934 if (!strcmp(method
, "REGISTER")) {
935 if (sip
->regcallid
) {
937 callid
= g_strdup(sip
->regcallid
);
939 sip
->regcallid
= g_strdup(callid
);
943 if (addheaders
) addh
= addheaders
;
945 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
946 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
947 "From: <sip:%s>%s%s;epid=%s\r\n"
948 "To: <%s>%s%s%s%s\r\n"
949 "Max-Forwards: 70\r\n"
954 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
956 dialog
&& dialog
->request
? dialog
->request
: url
,
957 TRANSPORT_DESCRIPTOR
,
958 purple_network_get_my_ip(-1),
960 branch
? ";branch=" : "",
961 branch
? branch
: "",
963 ourtag
? ";tag=" : "",
964 ourtag
? ourtag
: "",
967 theirtag
? ";tag=" : "",
968 theirtag
? theirtag
: "",
969 theirepid
? ";epid=" : "",
970 theirepid
? theirepid
: "",
971 dialog
? ++dialog
->cseq
: ++sip
->cseq
,
977 body
? strlen(body
) : 0,
981 //printf ("parsing msg buf:\n%s\n\n", buf);
982 msg
= sipmsg_parse_msg(buf
);
993 sign_outgoing_message (msg
, sip
, method
);
995 buf
= sipmsg_to_string (msg
);
997 /* add to ongoing transactions */
998 trans
= transactions_add_buf(sip
, msg
, tc
);
999 sendout_pkt(gc
, buf
);
1005 static void send_soap_request_with_cb(struct sipe_account_data
*sip
, gchar
*body
, TransCallback callback
, void * payload
)
1007 gchar
*from
= g_strdup_printf("sip:%s", sip
->username
);
1008 gchar
*contact
= get_contact(sip
);
1009 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1010 "Content-Type: application/SOAP+xml\r\n",contact
);
1012 struct transaction
* tr
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1013 tr
->payload
= payload
;
1020 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1022 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
1025 static char *get_contact_register(struct sipe_account_data
*sip
)
1027 char *epid
= get_epid();
1028 char *uuid
= generateUUIDfromEPID(epid
);
1029 char *buf
= g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip
->listenport
, TRANSPORT_DESCRIPTOR
, uuid
);
1035 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1037 char *uri
= g_strdup_printf("sip:%s", sip
->sipdomain
);
1038 char *to
= g_strdup_printf("sip:%s", sip
->username
);
1039 char *contact
= get_contact_register(sip
);
1040 char *hdr
= g_strdup_printf("Contact: %s\r\n"
1041 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1042 "Event: registration\r\n"
1043 "Allow-Events: presence\r\n"
1044 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1045 "Expires: %d\r\n", contact
,expire
);
1048 sip
->registerstatus
= 1;
1050 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1051 process_register_response
);
1058 static void do_register_cb(struct sipe_account_data
*sip
)
1060 do_register_exp(sip
, sip
->registerexpire
);
1061 sip
->reregister_set
= FALSE
;
1064 static void do_register(struct sipe_account_data
*sip
)
1066 do_register_exp(sip
, sip
->registerexpire
);
1070 * Returns URI from provided To or From header.
1072 * Needs to g_free() after use.
1074 * @return URI with sip: prefix
1076 static gchar
*parse_from(const gchar
*hdr
)
1079 const gchar
*tmp
, *tmp2
= hdr
;
1081 if (!hdr
) return NULL
;
1082 purple_debug_info("sipe", "parsing address out of %s\n", hdr
);
1083 tmp
= strchr(hdr
, '<');
1085 /* i hate the different SIP UA behaviours... */
1086 if (tmp
) { /* sip address in <...> */
1088 tmp
= strchr(tmp2
, '>');
1090 from
= g_strndup(tmp2
, tmp
- tmp2
);
1092 purple_debug_info("sipe", "found < without > in From\n");
1096 tmp
= strchr(tmp2
, ';');
1098 from
= g_strndup(tmp2
, tmp
- tmp2
);
1100 from
= g_strdup(tmp2
);
1103 purple_debug_info("sipe", "got %s\n", from
);
1107 static xmlnode
* xmlnode_get_descendant(xmlnode
* parent
, ...)
1110 xmlnode
* node
= NULL
;
1113 va_start(args
, parent
);
1114 while ((name
= va_arg(args
, const char *)) != NULL
) {
1115 node
= xmlnode_get_child(parent
, name
);
1116 if (node
== NULL
) return NULL
;
1126 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1128 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1129 send_soap_request(sip
, body
);
1134 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1137 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1139 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1142 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1146 void sipe_auth_user_cb(void * data
)
1148 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1151 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1156 void sipe_deny_user_cb(void * data
)
1158 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1161 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1166 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1168 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1169 sipe_contact_allow_deny(sip
, name
, TRUE
);
1173 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1175 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1176 sipe_contact_allow_deny(sip
, name
, FALSE
);
1180 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1182 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1183 sipe_contact_set_acl(sip, name, "");
1187 sipe_process_incoming_pending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1191 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1192 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1194 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1196 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1197 if (!watchers
) return;
1199 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1200 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1201 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1202 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1204 // TODO pull out optional displayName to pass as alias
1206 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1207 job
->who
= remote_user
;
1209 purple_account_request_authorization(
1223 xmlnode_free(watchers
);
1228 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1230 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1231 if (!purple_group
) {
1232 purple_group
= purple_group_new(group
->name
);
1233 purple_blist_add_group(purple_group
, NULL
);
1237 group
->purple_group
= purple_group
;
1238 sip
->groups
= g_slist_append(sip
->groups
, group
);
1239 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1241 purple_debug_info("sipe", "did not add group %s\n", group
->name
);
1245 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1247 struct sipe_group
*group
;
1253 entry
= sip
->groups
;
1255 group
= entry
->data
;
1256 if (group
->id
== id
) {
1259 entry
= entry
->next
;
1264 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, gchar
* name
)
1266 struct sipe_group
*group
;
1272 entry
= sip
->groups
;
1274 group
= entry
->data
;
1275 if (!strcmp(group
->name
, name
)) {
1278 entry
= entry
->next
;
1284 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1287 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1288 body
= g_strdup_printf(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1289 send_soap_request(sip
, body
);
1291 g_free(group
->name
);
1292 group
->name
= g_strdup(name
);
1296 * Only appends if no such value already stored.
1299 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1300 GSList
* res
= list
;
1301 if (!g_slist_find_custom(list
, data
, func
)) {
1302 res
= g_slist_insert_sorted(list
, data
, func
);
1308 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1309 return group1
->id
- group2
->id
;
1313 * Returns string like "2 4 7 8" - group ids buddy belong to.
1316 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1319 //creating array from GList, converting int to gchar*
1320 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1321 GSList
*entry
= buddy
->groups
;
1323 struct sipe_group
* group
= entry
->data
;
1324 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1325 entry
= entry
->next
;
1329 res
= g_strjoinv(" ", ids_arr
);
1330 g_strfreev(ids_arr
);
1335 * Sends buddy update to server
1338 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1340 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1341 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1343 if (buddy
&& purple_buddy
) {
1344 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1346 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1347 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1349 body
= g_strdup_printf(SIPE_SOAP_SET_CONTACT
,
1350 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1352 send_soap_request(sip
, body
);
1358 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1360 if (msg
->response
== 200) {
1361 struct sipe_group
*group
;
1362 struct group_user_context
*ctx
= (struct group_user_context
*)tc
->payload
;
1366 struct sipe_buddy
*buddy
;
1367 group
->name
= ctx
->group_name
;
1369 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1375 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1382 group_id
= xmlnode_get_data(node
);
1389 group
= g_new0(struct sipe_group
, 1);
1390 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1393 sipe_group_add(sip
, group
);
1395 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1397 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1400 sipe_group_set_user(sip
, ctx
->user_name
);
1409 static void sipe_group_create (struct sipe_account_data
*sip
, gchar
*name
, gchar
* who
)
1411 struct group_user_context
* ctx
= g_new0(struct group_user_context
, 1);
1413 ctx
->group_name
= g_strdup(name
);
1414 ctx
->user_name
= g_strdup(who
);
1416 body
= g_strdup_printf(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1417 send_soap_request_with_cb(sip
, body
, process_add_group_response
, ctx
);
1423 * Should return FALSE if repetitive action is not needed
1425 gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1428 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1429 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1430 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1431 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1432 ret
= sched_action
->repetitive
;
1433 g_free(sched_action
->payload
);
1434 g_free(sched_action
->name
);
1435 g_free(sched_action
);
1440 * Do schedule action for execution in the future.
1441 * Non repetitive execution.
1443 * @param name of action (will be copied)
1444 * @param timeout in seconds
1445 * @action callback function
1446 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1448 void sipe_schedule_action(gchar
*name
, int timeout
, Action action
, struct sipe_account_data
*sip
, void * payload
)
1450 struct scheduled_action
*sched_action
;
1452 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name
, timeout
);
1453 sched_action
= g_new0(struct scheduled_action
, 1);
1454 sched_action
->repetitive
= FALSE
;
1455 sched_action
->name
= g_strdup(name
);
1456 sched_action
->action
= action
;
1457 sched_action
->sip
= sip
;
1458 sched_action
->payload
= payload
;
1459 sched_action
->timeout_handler
= purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1460 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1461 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1465 * Kills action timer effectively cancelling
1468 * @param name of action
1470 void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, gchar
*name
)
1474 if (!sip
->timeouts
|| !name
) return;
1476 entry
= sip
->timeouts
;
1478 struct scheduled_action
*sched_action
= entry
->data
;
1479 if(!strcmp(sched_action
->name
, name
)) {
1480 GSList
*to_delete
= entry
;
1481 entry
= entry
->next
;
1482 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1483 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1484 purple_timeout_remove(sched_action
->timeout_handler
);
1485 g_free(sched_action
->payload
);
1486 g_free(sched_action
->name
);
1487 g_free(sched_action
);
1489 entry
= entry
->next
;
1494 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1496 static gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1499 //purple_debug_info("sipe","process_subscribe_response: body:\n%s\n", msg->body);
1501 if (msg
->response
== 200 || msg
->response
== 202)
1503 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1505 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1510 /* we can not subscribe -> user is offline (TODO unknown status?) */
1511 to
= parse_from(sipmsg_find_header(tc
->msg
, "To")); /* can't be NULL since it is our own msg */
1512 purple_prpl_got_user_status(sip
->account
, to
, "offline", NULL
);
1517 static void sipe_subscribe_resource_uri(const char *name
, gpointer value
, gchar
**resources_uri
)
1519 gchar
*tmp
= *resources_uri
;
1520 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1525 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1526 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1527 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1528 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1529 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1532 static void sipe_subscribe_to_buddies_batched(struct sipe_account_data
*sip
){
1533 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
1534 gchar
*contact
= get_contact(sip
);
1537 gchar
*resources_uri
= g_strdup("");
1538 gchar
*require
= "";
1540 gchar
*content_type
;
1542 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1544 if (sip
->msrtc_event_categories
) {
1545 require
= ", categoryList";
1546 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1547 content_type
= "application/msrtc-adrl-categorylist+xml";
1548 content
= g_strdup_printf(
1549 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1550 "<action name=\"subscribe\" id=\"63792024\">\n"
1551 "<adhocList>\n%s</adhocList>\n"
1552 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1553 "<category name=\"note\"/>\n"
1554 "<category name=\"state\"/>\n"
1557 "</batchSub>", sip
->username
, resources_uri
);
1559 content_type
= "application/adrl+xml";
1560 content
= g_strdup_printf(
1561 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1562 "<create xmlns=\"\">\n%s</create>\n"
1563 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1565 g_free(resources_uri
);
1567 request
= g_strdup_printf(
1568 "Require: adhoclist%s\r\n"
1569 "Supported: eventlist\r\n"
1570 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1571 "Supported: ms-piggyback-first-notify\r\n"
1572 "Supported: com.microsoft.autoextend\r\n"
1573 "Supported: ms-benotify\r\n"
1574 "Proxy-Require: ms-benotify\r\n"
1575 "Event: presence\r\n"
1576 "Content-Type: %s\r\n"
1577 "Contact: %s\r\n", require
, accept
, content_type
, contact
);
1580 /* subscribe to buddy presence */
1581 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1582 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1590 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1591 * The user sends a single SUBSCRIBE request to the subscribed contact.
1592 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1596 static void sipe_subscribe_to_name_single(struct sipe_account_data
*sip
, const char * buddy_name
)
1598 gchar
*to
= strstr(buddy_name
, "sip:") ? g_strdup(buddy_name
) : g_strdup_printf("sip:%s", buddy_name
);
1599 gchar
*tmp
= get_contact(sip
);
1602 request
= g_strdup_printf(
1603 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1604 "Supported: ms-piggyback-first-notify\r\n"
1605 "Supported: com.microsoft.autoextend\r\n"
1606 "Supported: ms-benotify\r\n"
1607 "Proxy-Require: ms-benotify\r\n"
1608 "Event: presence\r\n"
1609 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1610 "Contact: %s\r\n", tmp
);
1612 content
= g_strdup_printf(
1613 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1614 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1615 "<resource uri=\"%s\"/>\n"
1617 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1618 "<category name=\"note\"/>\n"
1619 "<category name=\"state\"/>\n"
1622 "</batchSub>", sip
->username
, to
1627 /* subscribe to buddy presence */
1628 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1635 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
1637 if (!purple_status_is_active(status
))
1641 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
1644 g_free(sip
->status
);
1645 sip
->status
= g_strdup(purple_status_get_id(status
));
1646 send_presence_info(sip
);
1652 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
, const char *alias
)
1654 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1655 sipe_group_set_user(sip
, name
);
1659 sipe_group_buddy(PurpleConnection
*gc
,
1661 const char *old_group_name
,
1662 const char *new_group_name
)
1664 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1665 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1666 struct sipe_group
* old_group
= NULL
;
1667 struct sipe_group
* new_group
;
1669 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1670 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
1672 if(!buddy
) { // buddy not in roaming list
1676 if (old_group_name
) {
1677 old_group
= sipe_group_find_by_name(sip
, g_strdup(old_group_name
));
1679 new_group
= sipe_group_find_by_name(sip
, g_strdup(new_group_name
));
1682 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
1683 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
1687 sipe_group_create(sip
, g_strdup(new_group_name
), g_strdup(who
));
1689 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
1690 sipe_group_set_user(sip
, who
);
1694 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1696 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1697 struct sipe_buddy
*b
;
1699 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1701 // Prepend sip: if needed
1702 if (strncmp("sip:", buddy
->name
, 4)) {
1703 gchar
*buf
= g_strdup_printf("sip:%s", buddy
->name
);
1704 purple_blist_rename_buddy(buddy
, buf
);
1708 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
1709 b
= g_new0(struct sipe_buddy
, 1);
1710 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy
->name
);
1711 b
->name
= g_strdup(buddy
->name
);
1712 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
1713 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
1714 sipe_subscribe_to_name_single(sip
, b
->name
); //@TODO should go to callback
1716 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy
->name
);
1720 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
1722 g_free(buddy
->name
);
1723 g_free(buddy
->annotation
);
1724 g_free(buddy
->device_name
);
1725 g_slist_free(buddy
->groups
);
1730 * Unassociates buddy from group first.
1731 * Then see if no groups left, removes buddy completely.
1732 * Otherwise updates buddy groups on server.
1734 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1736 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1737 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
1738 struct sipe_group
*g
= NULL
;
1740 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1745 g
= sipe_group_find_by_name(sip
, group
->name
);
1749 b
->groups
= g_slist_remove(b
->groups
, g
);
1750 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
1753 if (g_slist_length(b
->groups
) < 1) {
1754 gchar
*action_name
= g_strdup_printf("<%s><%s>", "presence", buddy
->name
);
1755 sipe_cancel_scheduled_action(sip
, action_name
);
1756 g_free(action_name
);
1758 g_hash_table_remove(sip
->buddies
, buddy
->name
);
1761 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
1762 send_soap_request(sip
, body
);
1768 //updates groups on server
1769 sipe_group_set_user(sip
, b
->name
);
1775 sipe_rename_group(PurpleConnection
*gc
,
1776 const char *old_name
,
1778 GList
*moved_buddies
)
1780 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1781 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, g_strdup(old_name
));
1783 sipe_group_rename(sip
, s_group
, group
->name
);
1785 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
1790 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
1792 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1793 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
1796 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
1797 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
1798 send_soap_request(sip
, body
);
1801 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
1802 g_free(s_group
->name
);
1804 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
1808 static GList
*sipe_status_types(PurpleAccount
*acc
)
1810 PurpleStatusType
*type
;
1811 GList
*types
= NULL
;
1814 type
= purple_status_type_new_with_attrs(
1815 PURPLE_STATUS_AVAILABLE
, NULL
, "Online", TRUE
, TRUE
, FALSE
,
1816 // Translators: noun
1817 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1819 types
= g_list_append(types
, type
);
1822 type
= purple_status_type_new_with_attrs(
1823 PURPLE_STATUS_UNAVAILABLE
, "busy", _("Busy"), TRUE
, TRUE
, FALSE
,
1824 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1826 types
= g_list_append(types
, type
);
1828 // Do Not Disturb (Not let user set it)
1829 type
= purple_status_type_new_with_attrs(
1830 PURPLE_STATUS_UNAVAILABLE
, "do-not-disturb", "Do Not Disturb", TRUE
, FALSE
, FALSE
,
1831 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1833 types
= g_list_append(types
, type
);
1836 type
= purple_status_type_new_with_attrs(
1837 PURPLE_STATUS_AWAY
, "be-right-back", _("Be Right Back"), TRUE
, TRUE
, FALSE
,
1838 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1840 types
= g_list_append(types
, type
);
1843 type
= purple_status_type_new_with_attrs(
1844 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
1845 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1847 types
= g_list_append(types
, type
);
1850 type
= purple_status_type_new_with_attrs(
1851 PURPLE_STATUS_UNAVAILABLE
, "on-the-phone", _("On The Phone"), TRUE
, TRUE
, FALSE
,
1852 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1854 types
= g_list_append(types
, type
);
1857 type
= purple_status_type_new_with_attrs(
1858 PURPLE_STATUS_AWAY
, "out-to-lunch", "Out To Lunch", TRUE
, TRUE
, FALSE
,
1859 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1861 types
= g_list_append(types
, type
);
1864 type
= purple_status_type_new_full(
1865 PURPLE_STATUS_INVISIBLE
, NULL
, "Appear Offline", TRUE
, TRUE
, FALSE
);
1866 types
= g_list_append(types
, type
);
1869 type
= purple_status_type_new_full(
1870 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
1871 types
= g_list_append(types
, type
);
1877 * A callback for g_hash_table_foreach
1879 static void sipe_buddy_subscribe_cb(char *name
, struct sipe_buddy
*buddy
, struct sipe_account_data
*sip
)
1881 sipe_subscribe_to_name_single(sip
, buddy
->name
);
1885 * Removes entries from purple buddy list
1886 * that does not correspond ones in the roaming contact list.
1888 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
1889 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
1890 GSList
*entry
= buddies
;
1891 struct sipe_buddy
*buddy
;
1895 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
1896 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
1899 g
= purple_buddy_get_group(b
);
1900 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
1902 gboolean in_sipe_groups
= FALSE
;
1903 GSList
*entry2
= buddy
->groups
;
1905 struct sipe_group
*group
= entry2
->data
;
1906 if (!strcmp(group
->name
, g
->name
)) {
1907 in_sipe_groups
= TRUE
;
1910 entry2
= entry2
->next
;
1912 if(!in_sipe_groups
) {
1913 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
1914 purple_blist_remove_buddy(b
);
1917 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
1918 purple_blist_remove_buddy(b
);
1920 entry
= entry
->next
;
1924 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1926 int len
= msg
->bodylen
;
1928 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
1931 const gchar
*contacts_delta
;
1932 xmlnode
*group_node
;
1933 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
1937 purple_debug_info("sipe", "msg->body:%s\n", msg
->body
);
1939 /* Convert the contact from XML to Purple Buddies */
1940 isc
= xmlnode_from_str(msg
->body
, len
);
1945 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
1946 if (contacts_delta
) {
1947 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
1951 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
1952 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1953 const char *name
= xmlnode_get_attrib(group_node
, "name");
1955 if (!strncmp(name
, "~", 1)) {
1957 name
= "Other Contacts";
1959 group
->name
= g_strdup(name
);
1960 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
1962 sipe_group_add(sip
, group
);
1965 // Make sure we have at least one group
1966 if (g_slist_length(sip
->groups
) == 0) {
1967 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1968 PurpleGroup
*purple_group
;
1970 group
->name
= g_strdup("Other Contacts");
1972 purple_group
= purple_group_new(group
->name
);
1973 purple_blist_add_group(purple_group
, NULL
);
1974 sip
->groups
= g_slist_append(sip
->groups
, group
);
1977 /* Parse contacts */
1978 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
1979 gchar
* uri
= g_strdup(xmlnode_get_attrib(item
, "uri"));
1980 gchar
* name
= g_strdup(xmlnode_get_attrib(item
, "name"));
1981 gchar
* groups
= g_strdup(xmlnode_get_attrib(item
, "groups"));
1982 gchar
* buddy_name
= g_strdup_printf("sip:%s", uri
);
1983 gchar
**item_groups
;
1984 struct sipe_group
*group
= NULL
;
1985 struct sipe_buddy
*buddy
= NULL
;
1988 // assign to group Other Contacts if nothing else received
1989 if(!groups
|| !strcmp("", groups
) ) {
1990 group
= sipe_group_find_by_name(sip
, "Other Contacts");
1991 groups
= group
? g_strdup_printf("%d", group
->id
) : "1";
1994 item_groups
= g_strsplit(groups
, " ", 0);
1996 while (item_groups
[i
]) {
1997 group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
1999 // If couldn't find the right group for this contact, just put them in the first group we have
2000 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2001 group
= sip
->groups
->data
;
2004 if (group
!= NULL
) {
2005 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2007 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2008 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2011 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2012 if (name
!= NULL
&& strlen(name
) != 0) {
2013 purple_blist_alias_buddy(b
, name
);
2018 buddy
= g_new0(struct sipe_buddy
, 1);
2019 buddy
->name
= g_strdup(b
->name
);
2020 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2023 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2025 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2027 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2032 } // while, contact groups
2033 g_strfreev(item_groups
);
2043 sipe_cleanup_local_blist(sip
);
2045 //subscribe to buddies
2046 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2047 //if(sip->msrtc_event_categories){
2048 sipe_subscribe_to_buddies_batched(sip
);
2050 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2052 sip
->subscribed_buddies
= TRUE
;
2061 * Subscribe roaming contacts
2063 static void sipe_subscribe_buddylist(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2065 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2066 gchar
*tmp
= get_contact(sip
);
2067 gchar
*hdr
= g_strdup_printf(
2068 "Event: vnd-microsoft-roaming-contacts\r\n"
2069 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2070 "Supported: com.microsoft.autoextend\r\n"
2071 "Supported: ms-benotify\r\n"
2072 "Proxy-Require: ms-benotify\r\n"
2073 "Supported: ms-piggyback-first-notify\r\n"
2074 "Contact: %s\r\n", tmp
);
2077 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, sipe_process_roaming_contacts
);
2083 sipe_process_pending_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
2085 sipe_process_incoming_pending (sip
, msg
);
2089 static void sipe_subscribe_pending_buddies(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2091 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2092 gchar
*tmp
= get_contact(sip
);
2093 gchar
*hdr
= g_strdup_printf(
2094 "Event: presence.wpending\r\n"
2095 "Accept: text/xml+msrtc.wpending\r\n"
2096 "Supported: com.microsoft.autoextend\r\n"
2097 "Supported: ms-benotify\r\n"
2098 "Proxy-Require: ms-benotify\r\n"
2099 "Supported: ms-piggyback-first-notify\r\n"
2100 "Contact: %s\r\n", tmp
);
2103 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, sipe_process_pending_response
);
2108 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2110 const gchar
*contacts_delta
;
2113 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2119 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2122 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2129 sipe_process_acl_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
2131 sipe_process_roaming_acl(sip
, msg
);
2136 * When we receive some self (BE) NOTIFY with a new subscriber
2137 * we sends a setSubscribers request to him [SIP-PRES]
2141 static void sipe_process_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2148 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2150 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2153 contact
= get_contact(sip
);
2154 to
= g_strdup_printf("sip:%s", sip
->username
);
2156 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
2161 user
= xmlnode_get_attrib(node
, "user");
2162 if (!user
) continue;
2164 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
2166 hdr
= g_strdup_printf(
2168 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
2170 body
= g_strdup_printf(
2171 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2172 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2173 "</setSubscribers>", user
);
2175 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
2185 static void sipe_subscribe_acl(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2187 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2188 gchar
*tmp
= get_contact(sip
);
2189 gchar
*hdr
= g_strdup_printf(
2190 "Event: vnd-microsoft-roaming-ACL\r\n"
2191 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2192 "Supported: com.microsoft.autoextend\r\n"
2193 "Supported: ms-benotify\r\n"
2194 "Proxy-Require: ms-benotify\r\n"
2195 "Supported: ms-piggyback-first-notify\r\n"
2196 "Contact: %s\r\n", tmp
);
2199 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, sipe_process_acl_response
);
2205 * To request for presence information about the user, access level settings that have already been configured by the user
2206 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2207 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2210 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2212 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2213 gchar
*tmp
= get_contact(sip
);
2214 gchar
*hdr
= g_strdup_printf(
2215 "Event: vnd-microsoft-roaming-self\r\n"
2216 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2217 "Supported: com.microsoft.autoextend\r\n"
2218 "Supported: ms-benotify\r\n"
2219 "Proxy-Require: ms-benotify\r\n"
2220 "Supported: ms-piggyback-first-notify\r\n"
2222 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
2224 gchar
*body
=g_strdup(
2225 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2226 "<roaming type=\"categories\"/>"
2227 "<roaming type=\"containers\"/>"
2228 "<roaming type=\"subscribers\"/></roamingList>");
2231 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, NULL
);
2237 /** Subscription for provisioning information to help with initial
2238 * configuration. This subscription is a one-time query (denoted by the Expires header,
2239 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2240 * configuration, meeting policies, and policy settings that Communicator must enforce.
2241 * TODO: for what we need this information.
2244 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2246 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2247 gchar
*tmp
= get_contact(sip
);
2248 gchar
*hdr
= g_strdup_printf(
2249 "Event: vnd-microsoft-provisioning-v2\r\n"
2250 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2251 "Supported: com.microsoft.autoextend\r\n"
2252 "Supported: ms-benotify\r\n"
2253 "Proxy-Require: ms-benotify\r\n"
2254 "Supported: ms-piggyback-first-notify\r\n"
2257 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
2258 gchar
*body
= g_strdup(
2259 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2260 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2261 "<provisioningGroup name=\"ucPolicy\"/>"
2262 "</provisioningGroupList>");
2265 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, NULL
);
2271 /* IM Session (INVITE and MESSAGE methods) */
2273 static struct sip_im_session
* find_im_session (struct sipe_account_data
*sip
, const char *who
)
2275 struct sip_im_session
*session
;
2277 if (sip
== NULL
|| who
== NULL
) {
2281 entry
= sip
->im_sessions
;
2283 session
= entry
->data
;
2284 if ((who
!= NULL
&& !strcmp(who
, session
->with
))) {
2287 entry
= entry
->next
;
2292 static struct sip_im_session
* find_or_create_im_session (struct sipe_account_data
*sip
, const char *who
)
2294 struct sip_im_session
*session
= find_im_session(sip
, who
);
2296 session
= g_new0(struct sip_im_session
, 1);
2297 session
->with
= g_strdup(who
);
2298 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2303 static void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2305 struct sip_dialog
*dialog
= session
->dialog
;
2308 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2311 entry
= dialog
->routes
;
2313 g_free(entry
->data
);
2314 entry
= g_slist_remove(entry
, entry
->data
);
2316 entry
= dialog
->supported
;
2318 g_free(entry
->data
);
2319 entry
= g_slist_remove(entry
, entry
->data
);
2321 g_free(dialog
->callid
);
2322 g_free(dialog
->ourtag
);
2323 g_free(dialog
->theirtag
);
2324 g_free(dialog
->theirepid
);
2325 g_free(dialog
->request
);
2327 g_free(session
->dialog
);
2329 entry
= session
->outgoing_message_queue
;
2331 g_free(entry
->data
);
2332 entry
= g_slist_remove(entry
, entry
->data
);
2335 g_free(session
->with
);
2340 process_options_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2342 gboolean ret
= TRUE
;
2344 if (msg
->response
!= 200) {
2345 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
2349 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
2355 * Asks UA/proxy about its capabilities.
2357 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
2359 gchar
*to
= strstr(who
, "sip:") ? g_strdup(who
) : g_strdup_printf("sip:%s", who
);
2360 gchar
*contact
= get_contact(sip
);
2362 request
= g_strdup_printf(
2363 "Accept: application/sdp\r\n"
2364 "Contact: %s\r\n", contact
);
2368 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
2374 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2376 char *msg
, *msg_tmp
;
2377 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2378 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2380 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2381 "possibly because one or more persons are offline:\n%s") ,
2383 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2388 static void sipe_im_remove_first_from_queue (struct sip_im_session
* session
);
2389 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2392 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2394 gboolean ret
= TRUE
;
2395 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2396 struct sip_im_session
* session
= find_im_session(sip
, with
);
2397 struct sip_dialog
*dialog
;
2400 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2405 if (msg
->response
!= 200) {
2406 gchar
*queued_msg
= NULL
;
2407 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2409 if (session
->outgoing_message_queue
) {
2410 queued_msg
= session
->outgoing_message_queue
->data
;
2412 sipe_present_message_undelivered_err(with
, sip
, queued_msg
);
2413 im_session_destroy(sip
, session
);
2418 dialog
= session
->dialog
;
2420 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2424 sipe_im_remove_first_from_queue(session
);
2425 sipe_im_process_queue(sip
, session
);
2430 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_im_session
* session
, const char *msg
)
2440 if (strncmp("sip:", session
->with
, 4)) {
2441 fullto
= g_strdup_printf("sip:%s", session
->with
);
2443 fullto
= g_strdup(session
->with
);
2446 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2447 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2449 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2452 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2455 msgr
= g_strdup("");
2458 tmp
= get_contact(sip
);
2459 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2460 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2461 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2462 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2467 send_sip_request(sip
->gc
, "MESSAGE", fullto
, fullto
, hdr
, msgtext
, session
->dialog
, process_message_response
);
2475 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2477 GSList
*entry
= session
->outgoing_message_queue
;
2479 char *queued_msg
= entry
->data
;
2480 sipe_send_message(sip
, session
, queued_msg
);
2485 sipe_im_remove_first_from_queue (struct sip_im_session
* session
)
2487 if (session
&& session
->outgoing_message_queue
) {
2488 char *queued_msg
= session
->outgoing_message_queue
->data
;
2489 // Remove from the queue and free the string
2490 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2496 sipe_get_route_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2498 GSList
*hdr
= msg
->headers
;
2499 struct siphdrelement
*elem
;
2505 if(!g_ascii_strcasecmp(elem
->name
, "Record-Route"))
2507 gchar
*route
= sipmsg_find_part_of_header(elem
->value
, "<", ">", NULL
);
2508 dialog
->routes
= g_slist_append(dialog
->routes
, route
);
2510 hdr
= g_slist_next(hdr
);
2515 dialog
->routes
= g_slist_reverse(dialog
->routes
);
2520 dialog
->request
= dialog
->routes
->data
;
2521 dialog
->routes
= g_slist_remove(dialog
->routes
, dialog
->routes
->data
);
2524 contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Contact"), "<", ">", NULL
);
2525 dialog
->routes
= g_slist_append(dialog
->routes
, contact
);
2529 sipe_get_supported_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2531 GSList
*hdr
= msg
->headers
;
2532 struct siphdrelement
*elem
;
2536 if(!g_ascii_strcasecmp(elem
->name
, "Supported")
2537 && !g_slist_find_custom(dialog
->supported
, elem
->value
, (GCompareFunc
)strcmp
))
2539 dialog
->supported
= g_slist_append(dialog
->supported
, g_strdup(elem
->value
));
2542 hdr
= g_slist_next(hdr
);
2547 sipe_parse_dialog(struct sipmsg
* msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2549 gchar
*us
= outgoing
? "From" : "To";
2550 gchar
*them
= outgoing
? "To" : "From";
2552 dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2553 dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, us
));
2554 dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, them
));
2555 if (!dialog
->theirepid
) {
2556 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", ";", NULL
);
2558 if (!dialog
->theirepid
) {
2559 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", NULL
, NULL
);
2562 sipe_get_route_header(msg
, dialog
, outgoing
);
2563 sipe_get_supported_header(msg
, dialog
, outgoing
);
2568 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2570 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2571 struct sip_im_session
* session
= find_im_session(sip
, with
);
2572 struct sip_dialog
*dialog
;
2575 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2580 if (msg
->response
!= 200) {
2581 gchar
*queued_msg
= NULL
;
2582 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2584 if (session
->outgoing_message_queue
) {
2585 queued_msg
= session
->outgoing_message_queue
->data
;
2587 sipe_present_message_undelivered_err(with
, sip
, queued_msg
);
2589 im_session_destroy(sip
, session
);
2594 dialog
= session
->dialog
;
2596 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2601 sipe_parse_dialog(msg
, dialog
, TRUE
);
2604 send_sip_request(sip
->gc
, "ACK", session
->with
, session
->with
, NULL
, NULL
, dialog
, NULL
);
2605 session
->outgoing_invite
= NULL
;
2606 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)strcmp
)) {
2607 sipe_im_remove_first_from_queue(session
);
2609 sipe_im_process_queue(sip
, session
);
2617 static void sipe_invite(struct sipe_account_data
*sip
, struct sip_im_session
* session
, gchar
* msg_body
)
2626 char *ms_text_format
;
2630 if (session
->dialog
) {
2631 purple_debug_info("sipe", "session with %s already has a dialog open\n", session
->with
);
2635 session
->dialog
= g_new0(struct sip_dialog
, 1);
2637 if (strstr(session
->with
, "sip:")) {
2638 to
= g_strdup(session
->with
);
2640 to
= g_strdup_printf("sip:%s", session
->with
);
2643 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
2644 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
2646 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2650 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2654 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
2655 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
2660 contact
= get_contact(sip
);
2661 hdr
= g_strdup_printf(
2663 "Content-Type: application/sdp\r\n",
2664 contact
, ms_text_format
);
2665 g_free(ms_text_format
);
2667 body
= g_strdup_printf(
2669 "o=- 0 0 IN IP4 %s\r\n"
2673 "m=message %d sip null\r\n"
2674 "a=accept-types:text/plain text/html image/gif "
2675 "multipart/alternative application/im-iscomposing+xml\r\n",
2676 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
2678 session
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
2679 to
, to
, hdr
, body
, session
->dialog
, process_invite_response
);
2688 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2691 send_sip_request(sip
->gc
, "BYE", session
->with
, session
->with
, NULL
, NULL
, session
->dialog
, NULL
);
2692 im_session_destroy(sip
, session
);
2697 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
2699 struct sipe_account_data
*sip
= gc
->proto_data
;
2701 purple_debug_info("sipe", "conversation with %s closed\n", who
);
2702 im_session_close(sip
, find_im_session(sip
, who
));
2706 im_session_close_all (struct sipe_account_data
*sip
)
2708 GSList
*entry
= sip
->im_sessions
;
2710 im_session_close (sip
, entry
->data
);
2711 entry
= sip
->im_sessions
;
2715 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
2717 struct sipe_account_data
*sip
;
2720 struct sip_im_session
*session
;
2722 purple_debug_info("sipe", "sipe_im_send what=%s\n", what
);
2724 sip
= gc
->proto_data
;
2726 text
= g_strdup(what
);
2728 session
= find_or_create_im_session(sip
, who
);
2730 // Queue the message
2731 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, text
);
2733 if (session
->dialog
&& session
->dialog
->callid
) {
2734 sipe_im_process_queue(sip
, session
);
2735 } else if (!session
->outgoing_invite
) {
2736 // Need to send the INVITE to get the outgoing dialog setup
2737 sipe_invite(sip
, session
, text
);
2745 /* End IM Session (INVITE and MESSAGE methods) */
2748 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
2750 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2751 struct sip_im_session
*session
;
2753 if (state
== PURPLE_NOT_TYPING
)
2756 session
= find_im_session(sip
, who
);
2758 if (session
&& session
->dialog
) {
2759 send_sip_request(gc
, "INFO", who
, who
,
2760 "Content-Type: application/xml\r\n",
2761 SIPE_SEND_TYPING
, session
->dialog
, NULL
);
2764 return SIPE_TYPING_SEND_TIMEOUT
;
2767 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
2769 GSList
*tmp
= sip
->transactions
;
2770 time_t currtime
= time(NULL
);
2772 struct transaction
*trans
= tmp
->data
;
2774 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime
-trans
->time
);
2775 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
2778 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
2780 sendout_sipmsg(sip
, trans
->msg
);
2787 static void do_reauthenticate_cb(struct sipe_account_data
*sip
)
2789 /* register again when security token expires */
2790 /* we have to start a new authentication as the security token
2791 * is almost expired by sending a not signed REGISTER message */
2792 purple_debug_info("sipe", "do a full reauthentication\n");
2793 sipe_auth_free(&sip
->registrar
);
2794 sip
->registerstatus
= 0;
2796 sip
->reauthenticate_set
= FALSE
;
2799 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2803 gboolean found
= FALSE
;
2805 from
= parse_from(sipmsg_find_header(msg
, "From"));
2809 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
2811 contenttype
= sipmsg_find_header(msg
, "Content-Type");
2812 if (!contenttype
|| !strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
2813 gchar
*msgr
= sipmsg_find_part_of_header(contenttype
, "msgr=", NULL
, NULL
);
2814 gchar
*x_mms_im_format
= sipmsg_get_x_mms_im_format(msgr
);
2816 gchar
*body_esc
= g_markup_escape_text(msg
->body
, -1);
2817 gchar
*body_html
= sipmsg_apply_x_mms_im_format(x_mms_im_format
, body_esc
);
2820 g_free(x_mms_im_format
);
2822 serv_got_im(sip
->gc
, from
, body_html
, 0, time(NULL
));
2824 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2826 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
2827 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2832 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2836 state
= xmlnode_get_child(isc
, "state");
2839 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2844 statedata
= xmlnode_get_data(state
);
2846 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
2847 else serv_got_typing_stopped(sip
->gc
, from
);
2852 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2856 purple_debug_info("sipe", "got unknown mime-type");
2857 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
2862 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2864 gchar
*ms_text_format
;
2867 struct sip_im_session
*session
;
2869 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? msg
->body
: "");
2871 // Only accept text invitations
2872 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
2873 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
2877 from
= parse_from(sipmsg_find_header(msg
, "From"));
2878 session
= find_or_create_im_session (sip
, from
);
2880 if (session
->dialog
) {
2881 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2883 session
->dialog
= g_new0(struct sip_dialog
, 1);
2885 sipe_parse_dialog(msg
, session
->dialog
, FALSE
);
2887 session
->dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2888 session
->dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, "To"));
2889 session
->dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, "From"));
2890 session
->dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "From"), "epid=", NULL
, NULL
);
2893 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2896 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2897 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
2898 if (ms_text_format
&& !strncmp(ms_text_format
, "text/plain", 10)) {
2899 gchar
*msgr
= sipmsg_find_part_of_header(ms_text_format
, "msgr=", ";", NULL
);
2900 gchar
*x_mms_im_format
= sipmsg_get_x_mms_im_format(msgr
);
2902 gchar
*ms_body
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
2905 gchar
*body
= purple_base64_decode(ms_body
, NULL
);
2906 gchar
*body_esc
= g_markup_escape_text(body
, -1);
2907 gchar
*body_html
= sipmsg_apply_x_mms_im_format(x_mms_im_format
, body_esc
);
2911 serv_got_im(sip
->gc
, from
, body_html
, 0, time(NULL
));
2913 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message reciept
2915 g_free(x_mms_im_format
);
2919 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
2920 sipmsg_remove_header(msg
, "Ms-Text-Format");
2921 sipmsg_remove_header(msg
, "EndPoints");
2922 sipmsg_remove_header(msg
, "User-Agent");
2923 sipmsg_remove_header(msg
, "Roster-Manager");
2925 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
2926 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2928 body
= g_strdup_printf(
2930 "o=- 0 0 IN IP4 %s\r\n"
2934 "m=message %d sip sip:%s\r\n"
2935 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2936 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2937 sip
->realport
, sip
->username
);
2938 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
2942 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2946 struct sip_im_session
*session
;
2949 from
= parse_from(sipmsg_find_header(msg
, "From"));
2950 session
= find_or_create_im_session (sip
, from
);
2952 if (session
->dialog
) {
2953 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2955 session
->dialog
= g_new0(struct sip_dialog
, 1);
2957 sipe_parse_dialog(msg
, session
->dialog
, FALSE
);
2959 session
->dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2960 session
->dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, "To"));
2961 session
->dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, "From"));
2962 session
->dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "From"), "epid=", NULL
, NULL
);
2965 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2970 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
2971 sipmsg_remove_header(msg
, "EndPoints");
2972 sipmsg_remove_header(msg
, "User-Agent");
2974 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
2975 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
2977 body
= g_strdup_printf(
2979 "o=- 0 0 IN IP4 0.0.0.0\r\n"
2981 "c=IN IP4 0.0.0.0\r\n"
2983 "m=message %d sip sip:%s\r\n"
2984 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2985 sip
->realport
, sip
->username
);
2986 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
2990 static void sipe_connection_cleanup(struct sipe_account_data
*);
2991 static void create_connection(struct sipe_account_data
*, gchar
*, int);
2993 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
2997 const gchar
*expires_header
;
2999 GSList
*hdr
= msg
->headers
;
3000 struct siphdrelement
*elem
;
3002 expires_header
= sipmsg_find_header(msg
, "Expires");
3003 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
3004 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
3006 switch (msg
->response
) {
3009 sip
->registerstatus
= 0;
3012 gchar
*contact_hdr
= NULL
;
3017 sip
->registerexpire
= expires
;
3019 if (!sip
->reregister_set
) {
3020 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
3021 sipe_schedule_action(action_name
, expires
, (Action
) do_register_cb
, sip
, NULL
);
3022 g_free(action_name
);
3023 sip
->reregister_set
= TRUE
;
3026 sip
->registerstatus
= 3;
3028 if (!sip
->reauthenticate_set
) {
3029 /* we have to reauthenticate as our security token expires
3030 after eight hours (be five minutes early) */
3031 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
3032 guint reauth_timeout
= (8 * 3600) - 360;
3033 sipe_schedule_action(action_name
, reauth_timeout
, (Action
) do_reauthenticate_cb
, sip
, NULL
);
3034 g_free(action_name
);
3035 sip
->reauthenticate_set
= TRUE
;
3038 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
3041 uuid
= generateUUIDfromEPID(epid
);
3044 // There can be multiple Contact headers (one per location where the user is logged in) so
3045 // make sure to only get the one for this uuid
3046 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
3047 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
3048 if (valid_contact
) {
3049 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
3050 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3051 g_free(valid_contact
);
3054 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3059 g_free(sip
->contact
);
3061 sip
->contact
= g_strdup_printf("<%s>", gruu
);
3064 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3065 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
);
3067 sip
->msrtc_event_categories
= FALSE
;
3072 if(!g_ascii_strcasecmp(elem
->name
, "Supported"))
3074 if (strstr(elem
->value
, "msrtc-event-categories")){
3075 sip
->msrtc_event_categories
= TRUE
;
3077 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s, %d\n", elem
->value
, sip
->msrtc_event_categories
);
3079 hdr
= g_slist_next(hdr
);
3082 if (!sip
->subscribed
) { //do it just once, not every re-register
3083 tmp
= sipmsg_find_header(msg
, "Allow-Events");
3084 sipe_options_request(sip
, sip
->sipdomain
);
3085 if (tmp
&& strstr(tmp
, "vnd-microsoft-provisioning")){
3086 sipe_subscribe_buddylist(sip
, msg
);
3088 sipe_subscribe_acl(sip
, msg
);
3089 sipe_subscribe_roaming_self(sip
, msg
);
3090 sipe_subscribe_roaming_provisioning(sip
, msg
);
3091 sipe_subscribe_pending_buddies(sip
, msg
);
3092 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
3093 sip
->subscribed
= TRUE
;
3096 if (purple_account_get_bool(sip
->account
, "clientkeepalive", FALSE
)) {
3097 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Setting user defined keepalive\n");
3098 sip
->keepalive_timeout
= purple_account_get_int(sip
->account
, "keepalive", 0);
3100 tmp
= sipmsg_find_header(msg
, "ms-keep-alive");
3102 sipe_keep_alive_timeout(sip
, tmp
);
3106 // Should we remove the transaction here?
3107 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
3108 transactions_remove(sip
, tc
);
3113 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
3115 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
3116 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
3120 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
3123 tmp
= g_strsplit(parts
[0], ":", 0);
3124 hostname
= g_strdup(tmp
[0]);
3125 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
3129 tmp
= g_strsplit(parts
[i
], "=", 0);
3131 if (g_strcasecmp("transport", tmp
[0]) == 0) {
3132 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
3133 transport
= SIPE_TRANSPORT_TCP
;
3134 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
3135 transport
= SIPE_TRANSPORT_UDP
;
3144 /* Close old connection */
3145 sipe_connection_cleanup(sip
);
3147 /* Create new connection */
3148 sip
->transport
= transport
;
3149 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3150 hostname
, port
, TRANSPORT_DESCRIPTOR
);
3151 create_connection(sip
, hostname
, port
);
3157 if (sip
->registerstatus
!= 2) {
3158 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
3159 if (sip
->registrar
.retries
> 3) {
3160 sip
->gc
->wants_to_die
= TRUE
;
3161 purple_connection_error(sip
->gc
, _("Wrong Password"));
3164 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3165 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3167 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3169 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3170 fill_auth(sip
, tmp
, &sip
->registrar
);
3171 sip
->registerstatus
= 2;
3172 if (sip
->account
->disconnecting
) {
3173 do_register_exp(sip
, 0);
3181 const gchar
*warning
= sipmsg_find_header(msg
, "Warning");
3182 if (warning
!= NULL
) {
3184 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3186 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
3187 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
3190 warning
= _("You have been rejected by the server");
3193 sip
->gc
->wants_to_die
= TRUE
;
3194 purple_connection_error(sip
->gc
, warning
);
3200 const gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3201 if (warning
!= NULL
) {
3202 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3203 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
3206 warning
= _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3209 sip
->gc
->wants_to_die
= TRUE
;
3210 purple_connection_error(sip
->gc
, warning
);
3216 const gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3217 if (warning
!= NULL
) {
3218 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3219 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
3222 warning
= _("Service unavailable: no reason given");
3225 sip
->gc
->wants_to_die
= TRUE
;
3226 purple_connection_error(sip
->gc
, warning
);
3234 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3237 xmlnode
*xn_categories
;
3238 xmlnode
*xn_category
;
3241 const char *activity
= NULL
;
3243 xn_categories
= xmlnode_from_str(data
, len
);
3244 uri
= xmlnode_get_attrib(xn_categories
, "uri");
3246 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
3248 xn_category
= xmlnode_get_next_twin(xn_category
) )
3250 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
3252 if (!strcmp(attrVar
, "note"))
3255 struct sipe_buddy
*sbuddy
;
3256 xn_node
= xmlnode_get_child(xn_category
, "note");
3257 if (!xn_node
) continue;
3258 xn_node
= xmlnode_get_child(xn_node
, "body");
3259 if (!xn_node
) continue;
3261 note
= xmlnode_get_data(xn_node
);
3264 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3268 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3269 sbuddy
->annotation
= g_strdup(note
);
3275 else if(!strcmp(attrVar
, "state"))
3279 xn_node
= xmlnode_get_child(xn_category
, "state");
3280 if (!xn_node
) continue;
3281 xn_node
= xmlnode_get_child(xn_node
, "availability");
3282 if (!xn_node
) continue;
3284 data
= xmlnode_get_data(xn_node
);
3289 activity
= "unknown";
3290 else if (avail
< 4500)
3291 activity
= "available";
3292 else if (avail
< 6000)
3294 else if (avail
< 7500)
3296 else if (avail
< 9000)
3298 else if (avail
< 12000)
3300 else if (avail
< 18000)
3303 activity
= "offline";
3311 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n",activity
);
3312 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
3316 xmlnode_free(xn_categories
);
3319 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3321 const char *uri
,*state
;
3323 xmlnode
*xn_resource
;
3324 xmlnode
*xn_instance
;
3326 xn_list
= xmlnode_from_str(data
, len
);
3328 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
3330 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
3332 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
3333 if (!xn_instance
) return;
3335 state
= xmlnode_get_attrib(xn_instance
, "state");
3336 uri
= xmlnode_get_attrib(xn_instance
, "cid");
3337 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri
,state
);
3338 if(strstr(state
,"resubscribe")){
3339 sipe_subscribe_to_name_single(sip
, uri
);
3344 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3348 gchar
*activity
= NULL
;
3350 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
3351 gboolean isonline
= FALSE
;
3352 xmlnode
*display_name_node
;
3354 pidf
= xmlnode_from_str(data
, len
);
3356 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
3360 uri
= xmlnode_get_attrib(pidf
, "entity");
3362 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
3364 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3365 basicstatus
= xmlnode_get_child(status
, "basic");
3370 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3375 getbasic
= xmlnode_get_data(basicstatus
);
3377 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3382 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
3383 if (strstr(getbasic
, "open")) {
3388 display_name_node
= xmlnode_get_child(pidf
, "display-name");
3389 // updating display name if alias was just URI
3390 if (display_name_node
) {
3391 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3392 GSList
*entry
= buddies
;
3393 PurpleBuddy
*p_buddy
;
3394 char * display_name
= xmlnode_get_data(display_name_node
);
3397 const char *server_alias
;
3400 p_buddy
= entry
->data
;
3402 alias
= (char *)purple_buddy_get_alias(p_buddy
);
3403 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
3404 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
3405 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3406 purple_blist_alias_buddy(p_buddy
, display_name
);
3410 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3412 ( (server_alias
&& strcmp(display_name
, server_alias
))
3413 || !server_alias
|| strlen(server_alias
) == 0 )
3415 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3418 entry
= entry
->next
;
3420 g_free(display_name
);
3423 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
3424 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3425 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
3426 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
3427 activity
= xmlnode_get_data(basicstatus
);
3428 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
3435 gchar
* status_id
= NULL
;
3437 if (strstr(activity
, "busy")) {
3439 } else if (strstr(activity
, "away")) {
3445 status_id
= "available";
3448 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
3449 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
3451 purple_prpl_got_user_status(sip
->account
, uri
, "offline", NULL
);
3458 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3460 const char *availability
;
3461 const char *activity
;
3462 const char *display_name
= NULL
;
3463 const char *activity_name
;
3468 struct sipe_buddy
*sbuddy
;
3470 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
3472 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
3473 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
3474 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
3475 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
3476 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
3477 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
3478 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
3479 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
3480 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
3481 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
3482 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
3483 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
3485 name
= xmlnode_get_attrib(xn_presentity
, "uri");
3486 uri
= g_strdup_printf("sip:%s", name
);
3487 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
3488 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
3490 // updating display name if alias was just URI
3491 if (xn_display_name
) {
3492 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3493 GSList
*entry
= buddies
;
3494 PurpleBuddy
*p_buddy
;
3495 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
3498 const char *email_str
, *server_alias
;
3500 p_buddy
= entry
->data
;
3502 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
3503 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3504 purple_blist_alias_buddy(p_buddy
, display_name
);
3507 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3509 ( (server_alias
&& strcmp(display_name
, server_alias
))
3510 || !server_alias
|| strlen(server_alias
) == 0 )
3512 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3516 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
3517 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
3518 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
3522 entry
= entry
->next
;
3526 avl
= atoi(availability
);
3527 act
= atoi(activity
);
3530 activity_name
= "away";
3531 else if (act
<= 150)
3532 activity_name
= "out-to-lunch";
3533 else if (act
<= 300)
3534 activity_name
= "be-right-back";
3535 else if (act
<= 400)
3536 activity_name
= "available";
3537 else if (act
<= 500)
3538 activity_name
= "on-the-phone";
3539 else if (act
<= 600)
3540 activity_name
= "busy";
3542 activity_name
= "available";
3545 activity_name
= "offline";
3547 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3550 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3551 sbuddy
->annotation
= NULL
;
3552 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
3554 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
3555 sbuddy
->device_name
= NULL
;
3556 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
3559 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
3560 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
3562 xmlnode_free(xn_presentity
);
3566 static void process_incoming_notify_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3568 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3570 purple_debug_info("sipe", "process_incoming_notify_presence: Content-Type: %s\n", ctype
? ctype
: "");
3572 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
3573 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
3575 const char *content
= msg
->body
;
3576 unsigned length
= msg
->bodylen
;
3577 PurpleMimeDocument
*mime
= NULL
;
3579 if (strstr(ctype
, "multipart"))
3581 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3582 const char *content_type
;
3584 mime
= purple_mime_document_parse(doc
);
3585 parts
= purple_mime_document_get_parts(mime
);
3587 content
= purple_mime_part_get_data(parts
->data
);
3588 length
= purple_mime_part_get_length(parts
->data
);
3589 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
3590 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
3592 process_incoming_notify_rlmi_resub(sip
, content
, length
);
3594 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
3596 process_incoming_notify_msrtc(sip
, content
, length
);
3600 process_incoming_notify_rlmi(sip
, content
, length
);
3602 parts
= parts
->next
;
3608 purple_mime_document_free(mime
);
3611 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
3613 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
3615 else if(strstr(ctype
, "application/rlmi+xml"))
3617 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
3620 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
3622 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
3626 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
3631 * Dispatcher for all incoming subscription information
3632 * whether it comes from NOTIFY, BENOTIFY requests or
3633 * piggy-backed to subscription's OK responce.
3635 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3636 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3638 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
3640 gchar
*event
= sipmsg_find_header(msg
, "Event");
3641 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
3642 const char *uri
,*state
;
3644 xmlnode
*xn_resource
;
3645 xmlnode
*xn_instance
;
3649 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
3650 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
3654 const gchar
*expires_header
;
3655 expires_header
= sipmsg_find_header(msg
, "Expires");
3656 expires
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
3657 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires
);
3660 if (!subscription_state
|| strstr(subscription_state
, "active"))
3662 if (event
&& strstr(event
, "presence"))
3664 process_incoming_notify_presence(sip
, msg
);
3666 else if (event
&& strstr(event
, "vnd-microsoft-roaming-contacts"))
3668 sipe_process_roaming_contacts(sip
, msg
, NULL
);
3670 else if (event
&& strstr(event
, "vnd-microsoft-roaming-self"))
3672 sipe_process_roaming_self(sip
, msg
);
3674 else if (event
&& strstr(event
, "vnd-microsoft-roaming-ACL"))
3676 sipe_process_roaming_acl(sip
, msg
);
3678 else if (event
&& strstr(event
, "presence.wpending"))
3680 sipe_process_incoming_pending(sip
, msg
);
3684 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
3688 //The server sends a (BE)NOTIFY with the status 'terminated'
3689 if(request
&& subscription_state
&& strstr(subscription_state
, "terminated") )
3691 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3692 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
3696 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3697 if (request
&& !benotify
)
3699 sipmsg_remove_header(msg
, "Expires");
3700 sipmsg_remove_header(msg
, "subscription-state");
3701 sipmsg_remove_header(msg
, "Event");
3702 sipmsg_remove_header(msg
, "Require");
3703 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3710 static gchar* gen_xpidf(struct sipe_account_data *sip)
3712 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3714 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3715 "<display name=\"sip:%s\"/>\r\n"
3716 "<atom id=\"1234\">\r\n"
3717 "<address uri=\"sip:%s\">\r\n"
3718 "<status status=\"%s\"/>\r\n"
3731 static gchar* gen_pidf(struct sipe_account_data *sip)
3733 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3734 "<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"
3735 "<tuple id=\"0\">\r\n"
3737 "<basic>open</basic>\r\n"
3738 "<ep:activities>\r\n"
3739 " <ep:activity>%s</ep:activity>\r\n"
3743 "<ci:display-name>%s</ci:display-name>\r\n"
3753 process_send_presence_info_v0_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3755 if (msg
->response
== 488) {
3756 sip
->presence_method_version
= 1;
3757 send_presence_info(sip
);
3762 static void send_presence_info_v0(struct sipe_account_data
*sip
, const char * note
)
3764 int availability
= 300; // online
3765 int activity
= 400; // Available
3768 if (!strcmp(sip
->status
, "away")) {
3770 } else if (!strcmp(sip
->status
, "out-to-lunch")) {
3772 } else if (!strcmp(sip
->status
, "be-right-back")) {
3774 } else if (!strcmp(sip
->status
, "on-the-phone")) {
3776 } else if (!strcmp(sip
->status
, "do-not-disturb")) {
3778 } else if (!strcmp(sip
->status
, "busy")) {
3780 } else if (!strcmp(sip
->status
, "invisible")) {
3781 availability
= 0; // offline
3785 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
3786 //@TODO: send user data - state; add hostname in upper case
3787 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
3788 send_soap_request_with_cb(sip
, body
, process_send_presence_info_v0_response
, NULL
);
3794 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3796 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3797 if (msg
->response
== 200) {
3798 sip
->status_version
= 0;
3799 send_presence_info(sip
);
3805 process_send_presence_info_v1_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3807 if (msg
->response
== 409) {
3808 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3809 // TODO need to parse the version #'s?
3810 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
3811 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
3815 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg
->body
);
3817 tmp
= get_contact(sip
);
3818 hdr
= g_strdup_printf("Contact: %s\r\n"
3819 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
3821 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
3831 static void send_presence_info_v1(struct sipe_account_data
*sip
, const char * note
)
3838 if (!strcmp(sip
->status
, "away")) {
3840 } else if (!strcmp(sip
->status
, "busy")) {
3847 uri
= g_strdup_printf("sip:%s", sip
->username
);
3848 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
3849 sip
->status_version
, code
,
3850 sip
->status_version
, code
,
3851 sip
->status_version
, note
? note
: "",
3852 sip
->status_version
, note
? note
: "",
3853 sip
->status_version
, note
? note
: ""
3855 sip
->status_version
++;
3857 tmp
= get_contact(sip
);
3858 hdr
= g_strdup_printf("Contact: %s\r\n"
3859 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
3861 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_info_v1_response
);
3869 static void send_presence_info(struct sipe_account_data
*sip
)
3871 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
3873 if (!status
) return;
3875 note
= purple_status_get_attr_string(status
, "message");
3877 purple_debug_info("sipe", "sending presence info, version = %d\n", sip
->presence_method_version
);
3878 if (sip
->presence_method_version
!= 1) {
3879 send_presence_info_v0(sip
, note
);
3881 send_presence_info_v1(sip
, note
);
3885 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
3887 gboolean found
= FALSE
;
3888 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
3889 if (msg
->response
== 0) { /* request */
3890 if (!strcmp(msg
->method
, "MESSAGE")) {
3891 process_incoming_message(sip
, msg
);
3893 } else if (!strcmp(msg
->method
, "NOTIFY")) {
3894 purple_debug_info("sipe","send->process_incoming_notify\n");
3895 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
3897 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
3898 purple_debug_info("sipe","send->process_incoming_benotify\n");
3899 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
3901 } else if (!strcmp(msg
->method
, "INVITE")) {
3902 process_incoming_invite(sip
, msg
);
3904 } else if (!strcmp(msg
->method
, "OPTIONS")) {
3905 process_incoming_options(sip
, msg
);
3907 } else if (!strcmp(msg
->method
, "INFO")) {
3909 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3911 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
3914 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3916 } else if (!strcmp(msg
->method
, "ACK")) {
3917 // ACK's don't need any response
3919 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
3920 // LCS 2005 sends us these - just respond 200 OK
3922 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3923 } else if (!strcmp(msg
->method
, "BYE")) {
3924 struct sip_im_session
*session
;
3926 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3928 from
= parse_from(sipmsg_find_header(msg
, "From"));
3929 session
= find_im_session (sip
, from
);
3933 // TODO Let the user know the other user left the conversation?
3934 im_session_destroy(sip
, session
);
3939 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3941 } else { /* response */
3942 struct transaction
*trans
= transactions_find(sip
, msg
);
3944 if (msg
->response
== 407) {
3945 gchar
*resend
, *auth
, *ptmp
;
3947 if (sip
->proxy
.retries
> 30) return;
3948 sip
->proxy
.retries
++;
3949 /* do proxy authentication */
3951 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
3953 fill_auth(sip
, ptmp
, &sip
->proxy
);
3954 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
3955 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
3956 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
3958 resend
= sipmsg_to_string(trans
->msg
);
3959 /* resend request */
3960 sendout_pkt(sip
->gc
, resend
);
3963 if (msg
->response
== 100 || msg
->response
== 180) {
3964 /* ignore provisional response */
3965 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
3967 sip
->proxy
.retries
= 0;
3968 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
3969 if (msg
->response
== 401)
3971 sip
->registrar
.retries
++;
3972 sip
->registrar
.expires
= 0;
3976 sip
->registrar
.retries
= 0;
3978 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
3980 if (msg
->response
== 401) {
3981 gchar
*resend
, *auth
, *ptmp
;
3983 if (sip
->registrar
.retries
> 4) return;
3984 sip
->registrar
.retries
++;
3986 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3987 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
3989 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3992 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
3994 fill_auth(sip
, ptmp
, &sip
->registrar
);
3995 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
3996 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
3997 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
3999 //sipmsg_remove_header(trans->msg, "Authorization");
4000 //sipmsg_add_header(trans->msg, "Authorization", auth);
4002 resend
= sipmsg_to_string(trans
->msg
);
4003 /* resend request */
4004 sendout_pkt(sip
->gc
, resend
);
4009 if (trans
->callback
) {
4010 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
4011 /* call the callback to process response*/
4012 (trans
->callback
)(sip
, msg
, trans
);
4014 /* Not sure if this is needed or what needs to be done
4015 but transactions seem to be removed prematurely so
4016 this only removes them if the response is 200 OK */
4017 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
4018 /*Has a bug and it's unneccesary*/
4019 /*transactions_remove(sip, trans);*/
4025 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction");
4029 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
4033 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
4041 /* according to the RFC remove CRLF at the beginning */
4042 while (*cur
== '\r' || *cur
== '\n') {
4045 if (cur
!= conn
->inbuf
) {
4046 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
4047 conn
->inbufused
= strlen(conn
->inbuf
);
4050 /* Received a full Header? */
4051 sip
->processing_input
= TRUE
;
4052 while (sip
->processing_input
&&
4053 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
4054 time_t currtime
= time(NULL
);
4057 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
4058 msg
= sipmsg_parse_header(conn
->inbuf
);
4061 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
4062 if (restlen
>= msg
->bodylen
) {
4063 dummy
= g_malloc(msg
->bodylen
+ 1);
4064 memcpy(dummy
, cur
, msg
->bodylen
);
4065 dummy
[msg
->bodylen
] = '\0';
4067 cur
+= msg
->bodylen
;
4068 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
4069 conn
->inbufused
= strlen(conn
->inbuf
);
4071 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4072 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
4078 purple_debug_info("sipe", "body:\n%s", msg->body);
4081 // Verify the signature before processing it
4082 if (sip
->registrar
.ntlm_key
) {
4083 struct sipmsg_breakdown msgbd
;
4084 gchar
*signature_input_str
;
4085 gchar
*signature
= NULL
;
4088 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
4089 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
4090 if (signature_input_str
!= NULL
) {
4091 signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
4093 g_free(signature_input_str
);
4095 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
4097 if (signature
!= NULL
) {
4098 if (rspauth
!= NULL
) {
4099 if (purple_ntlm_verify_signature (signature
, rspauth
)) {
4100 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
4101 process_input_message(sip
, msg
);
4103 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth
, signature
);
4104 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
4105 sip
->gc
->wants_to_die
= TRUE
;
4107 } else if (msg
->response
== 401) {
4108 purple_connection_error(sip
->gc
, _("Wrong Password"));
4109 sip
->gc
->wants_to_die
= TRUE
;
4115 sipmsg_breakdown_free(&msgbd
);
4117 process_input_message(sip
, msg
);
4124 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
4126 PurpleConnection
*gc
= data
;
4127 struct sipe_account_data
*sip
= gc
->proto_data
;
4132 static char buffer
[65536];
4133 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
4135 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
4136 msg
= sipmsg_parse_msg(buffer
);
4137 if (msg
) process_input_message(sip
, msg
);
4141 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
4143 struct sipe_account_data
*sip
= gc
->proto_data
;
4144 PurpleSslConnection
*gsc
= sip
->gsc
;
4146 purple_debug_error("sipe", "%s",debug
);
4147 purple_connection_error(gc
, msg
);
4149 /* Invalidate this connection. Next send will open a new one */
4151 connection_remove(sip
, gsc
->fd
);
4152 purple_ssl_close(gsc
);
4158 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4160 PurpleConnection
*gc
= data
;
4161 struct sipe_account_data
*sip
;
4162 struct sip_connection
*conn
;
4164 gboolean firstread
= TRUE
;
4166 /* NOTE: This check *IS* necessary */
4167 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
4168 purple_ssl_close(gsc
);
4172 sip
= gc
->proto_data
;
4173 conn
= connection_find(sip
, gsc
->fd
);
4175 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4176 gc
->wants_to_die
= TRUE
;
4177 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
4181 /* Read all available data from the SSL connection */
4183 /* Increase input buffer size as needed */
4184 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4185 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4186 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4187 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
4190 /* Try to read as much as there is space left in the buffer */
4191 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
4192 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
4194 if (len
< 0 && errno
== EAGAIN
) {
4195 /* Try again later */
4197 } else if (len
< 0) {
4198 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
4200 } else if (firstread
&& (len
== 0)) {
4201 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
4205 conn
->inbufused
+= len
;
4208 /* Equivalence indicates that there is possibly more data to read */
4209 } while (len
== readlen
);
4211 conn
->inbuf
[conn
->inbufused
] = '\0';
4212 process_input(sip
, conn
);
4216 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4218 PurpleConnection
*gc
= data
;
4219 struct sipe_account_data
*sip
= gc
->proto_data
;
4221 struct sip_connection
*conn
= connection_find(sip
, source
);
4223 purple_debug_error("sipe", "Connection not found!\n");
4227 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4228 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4229 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4232 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
4234 if (len
< 0 && errno
== EAGAIN
)
4236 else if (len
<= 0) {
4237 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4238 connection_remove(sip
, source
);
4239 if (sip
->fd
== source
) sip
->fd
= -1;
4243 conn
->inbufused
+= len
;
4244 conn
->inbuf
[conn
->inbufused
] = '\0';
4246 process_input(sip
, conn
);
4249 /* Callback for new connections on incoming TCP port */
4250 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4252 PurpleConnection
*gc
= data
;
4253 struct sipe_account_data
*sip
= gc
->proto_data
;
4254 struct sip_connection
*conn
;
4256 int newfd
= accept(source
, NULL
, NULL
);
4258 conn
= connection_create(sip
, newfd
);
4260 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4263 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
4265 PurpleConnection
*gc
= data
;
4266 struct sipe_account_data
*sip
;
4267 struct sip_connection
*conn
;
4269 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4277 purple_connection_error(gc
, _("Could not connect"));
4281 sip
= gc
->proto_data
;
4283 sip
->last_keepalive
= time(NULL
);
4285 conn
= connection_create(sip
, source
);
4289 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4292 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4294 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
4295 if (sip
== NULL
) return;
4300 static guint
sipe_ht_hash_nick(const char *nick
)
4302 char *lc
= g_utf8_strdown(nick
, -1);
4303 guint bucket
= g_str_hash(lc
);
4309 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
4311 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
4314 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
4316 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4318 sip
->listen_data
= NULL
;
4320 if (listenfd
== -1) {
4321 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4327 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
4328 sip
->listenfd
= sip
->fd
;
4330 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
4332 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
4336 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
4338 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4341 sip
->query_data
= NULL
;
4343 if (!hosts
|| !hosts
->data
) {
4344 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
4348 addr_size
= GPOINTER_TO_INT(hosts
->data
);
4349 hosts
= g_slist_remove(hosts
, hosts
->data
);
4350 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
4351 g_free(hosts
->data
);
4352 hosts
= g_slist_remove(hosts
, hosts
->data
);
4354 hosts
= g_slist_remove(hosts
, hosts
->data
);
4355 g_free(hosts
->data
);
4356 hosts
= g_slist_remove(hosts
, hosts
->data
);
4359 /* create socket for incoming connections */
4360 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
4361 sipe_udp_host_resolved_listen_cb
, sip
);
4362 if (sip
->listen_data
== NULL
) {
4363 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4368 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
4371 PurpleConnection
*gc
= data
;
4372 struct sipe_account_data
*sip
;
4374 /* If the connection is already disconnected, we don't need to do anything else */
4375 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4378 sip
= gc
->proto_data
;
4383 case PURPLE_SSL_CONNECT_FAILED
:
4384 purple_connection_error(gc
, _("Connection Failed"));
4386 case PURPLE_SSL_HANDSHAKE_FAILED
:
4387 purple_connection_error(gc
, _("SSL Handshake Failed"));
4389 case PURPLE_SSL_CERTIFICATE_INVALID
:
4390 purple_connection_error(gc
, _("SSL Certificate Invalid"));
4396 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
4398 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4399 PurpleProxyConnectData
*connect_data
;
4401 sip
->listen_data
= NULL
;
4403 sip
->listenfd
= listenfd
;
4404 if (sip
->listenfd
== -1) {
4405 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4409 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
4410 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4411 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
4412 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
4413 sipe_newconn_cb
, sip
->gc
);
4414 purple_debug_info("sipe", "connecting to %s port %d\n",
4415 sip
->realhostname
, sip
->realport
);
4416 /* open tcp connection to the server */
4417 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
4418 sip
->realport
, login_cb
, sip
->gc
);
4420 if (connect_data
== NULL
) {
4421 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
4426 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
4428 PurpleAccount
*account
= sip
->account
;
4429 PurpleConnection
*gc
= sip
->gc
;
4431 if (purple_account_get_bool(account
, "useport", FALSE
)) {
4432 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
4433 port
= purple_account_get_int(account
, "port", 0);
4435 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
4438 sip
->realhostname
= hostname
;
4439 sip
->realport
= port
;
4441 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
4444 /* TODO: is there a good default grow size? */
4445 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
4446 sip
->txbuf
= purple_circ_buffer_new(0);
4448 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
4450 if (!purple_ssl_is_supported()) {
4451 gc
->wants_to_die
= TRUE
;
4452 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4456 purple_debug_info("sipe", "using SSL\n");
4458 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
4459 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
4460 if (sip
->gsc
== NULL
) {
4461 purple_connection_error(gc
, _("Could not create SSL context"));
4464 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
4466 purple_debug_info("sipe", "using UDP\n");
4468 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
4469 if (sip
->query_data
== NULL
) {
4470 purple_connection_error(gc
, _("Could not resolve hostname"));
4474 purple_debug_info("sipe", "using TCP\n");
4475 /* create socket for incoming connections */
4476 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
4477 sipe_tcp_connect_listen_cb
, sip
);
4478 if (sip
->listen_data
== NULL
) {
4479 purple_connection_error(gc
, _("Could not create listen socket"));
4485 /* Service list for autodection */
4486 static const struct sipe_service_data service_autodetect
[] = {
4487 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4488 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4489 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4490 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4494 /* Service list for SSL/TLS */
4495 static const struct sipe_service_data service_tls
[] = {
4496 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4497 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4501 /* Service list for TCP */
4502 static const struct sipe_service_data service_tcp
[] = {
4503 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4504 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4508 /* Service list for UDP */
4509 static const struct sipe_service_data service_udp
[] = {
4510 { "sip", "udp", SIPE_TRANSPORT_UDP
},
4514 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
4515 static void resolve_next_service(struct sipe_account_data
*sip
,
4516 const struct sipe_service_data
*start
)
4519 sip
->service_data
= start
;
4521 sip
->service_data
++;
4522 if (sip
->service_data
->service
== NULL
) {
4524 /* Try connecting to the SIP hostname directly */
4525 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
4526 if (sip
->auto_transport
) {
4527 // If SSL is supported, default to using it; OCS servers aren't configured
4528 // by default to accept TCP
4529 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4530 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
4531 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
4534 hostname
= g_strdup(sip
->sipdomain
);
4535 create_connection(sip
, hostname
, 0);
4540 /* Try to resolve next service */
4541 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
4542 sip
->service_data
->transport
,
4547 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
4549 struct sipe_account_data
*sip
= data
;
4551 sip
->srv_query_data
= NULL
;
4553 /* find the host to connect to */
4555 gchar
*hostname
= g_strdup(resp
->hostname
);
4556 int port
= resp
->port
;
4557 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4561 sip
->transport
= sip
->service_data
->type
;
4563 create_connection(sip
, hostname
, port
);
4565 resolve_next_service(sip
, NULL
);
4569 static void sipe_login(PurpleAccount
*account
)
4571 PurpleConnection
*gc
;
4572 struct sipe_account_data
*sip
;
4573 gchar
**signinname_login
, **userserver
, **domain_user
;
4574 const char *transport
;
4576 const char *username
= purple_account_get_username(account
);
4577 gc
= purple_account_get_connection(account
);
4579 if (strpbrk(username
, " \t\v\r\n") != NULL
) {
4580 gc
->wants_to_die
= TRUE
;
4581 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
4585 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
4586 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
4587 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
4589 sip
->account
= account
;
4590 sip
->registerexpire
= 900;
4591 sip
->reregister_set
= FALSE
;
4592 sip
->reauthenticate_set
= FALSE
;
4593 sip
->subscribed
= FALSE
;
4594 sip
->subscribed_buddies
= FALSE
;
4596 signinname_login
= g_strsplit(username
, ",", 2);
4598 userserver
= g_strsplit(signinname_login
[0], "@", 2);
4599 purple_connection_set_display_name(gc
, userserver
[0]);
4600 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
4601 sip
->sipdomain
= g_strdup(userserver
[1]);
4603 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
4604 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
4605 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
4607 sip
->password
= g_strdup(purple_connection_get_password(gc
));
4609 g_strfreev(userserver
);
4610 g_strfreev(domain_user
);
4611 g_strfreev(signinname_login
);
4613 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
4615 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
4617 /* TODO: Set the status correctly. */
4618 sip
->status
= g_strdup("available");
4620 transport
= purple_account_get_string(account
, "transport", "auto");
4621 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
4622 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
4625 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
4626 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
4627 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
4628 } else if (strcmp(transport
, "auto") == 0) {
4629 sip
->auto_transport
= TRUE
;
4630 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
4631 } else if (strcmp(transport
, "tls") == 0) {
4632 resolve_next_service(sip
, service_tls
);
4633 } else if (strcmp(transport
, "tcp") == 0) {
4634 resolve_next_service(sip
, service_tcp
);
4636 resolve_next_service(sip
, service_udp
);
4640 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
4642 connection_free_all(sip
);
4644 if (sip
->query_data
!= NULL
)
4645 purple_dnsquery_destroy(sip
->query_data
);
4646 sip
->query_data
== NULL
;
4648 if (sip
->srv_query_data
!= NULL
)
4649 purple_srv_cancel(sip
->srv_query_data
);
4650 sip
->srv_query_data
= NULL
;
4652 if (sip
->listen_data
!= NULL
)
4653 purple_network_listen_cancel(sip
->listen_data
);
4654 sip
->listen_data
= NULL
;
4656 if (sip
->gsc
!= NULL
)
4657 purple_ssl_close(sip
->gsc
);
4660 sipe_auth_free(&sip
->registrar
);
4661 sipe_auth_free(&sip
->proxy
);
4664 purple_circ_buffer_destroy(sip
->txbuf
);
4667 g_free(sip
->realhostname
);
4668 sip
->realhostname
= NULL
;
4671 purple_input_remove(sip
->listenpa
);
4673 if (sip
->tx_handler
)
4674 purple_input_remove(sip
->tx_handler
);
4675 sip
->tx_handler
= 0;
4676 if (sip
->resendtimeout
)
4677 purple_timeout_remove(sip
->resendtimeout
);
4678 sip
->resendtimeout
= 0;
4679 if (sip
->timeouts
) {
4680 GSList
*entry
= sip
->timeouts
;
4682 struct scheduled_action
*sched_action
= entry
->data
;
4683 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
4684 purple_timeout_remove(sched_action
->timeout_handler
);
4685 g_free(sched_action
->payload
);
4686 g_free(sched_action
->name
);
4687 g_free(sched_action
);
4688 entry
= entry
->next
;
4691 g_slist_free(sip
->timeouts
);
4693 g_slist_free(sip
->allow_events
);
4696 g_free(sip
->contact
);
4697 sip
->contact
= NULL
;
4699 g_free(sip
->regcallid
);
4700 sip
->regcallid
= NULL
;
4703 sip
->processing_input
= FALSE
;
4707 * A callback for g_hash_table_foreach_remove
4709 static gboolean
sipe_buddy_remove(gpointer key
, struct sipe_buddy
*buddy
, gpointer user_data
)
4711 sipe_free_buddy(buddy
);
4714 static void sipe_close(PurpleConnection
*gc
)
4716 struct sipe_account_data
*sip
= gc
->proto_data
;
4719 /* leave all conversations */
4720 im_session_close_all(sip
);
4723 do_register_exp(sip
, 0);
4725 sipe_connection_cleanup(sip
);
4726 g_free(sip
->sipdomain
);
4727 g_free(sip
->username
);
4728 g_free(sip
->password
);
4729 g_free(sip
->authdomain
);
4730 g_free(sip
->authuser
);
4731 g_free(sip
->status
);
4733 g_hash_table_foreach_remove(sip
->buddies
, (GHRFunc
) sipe_buddy_remove
, NULL
);
4734 g_hash_table_destroy(sip
->buddies
);
4736 g_free(gc
->proto_data
);
4737 gc
->proto_data
= NULL
;
4740 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4742 PurpleAccount
*acct
= purple_connection_get_account(gc
);
4743 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
4744 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
4746 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
4747 purple_conversation_present(conv
);
4751 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4754 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
4755 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
4758 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
4760 PurpleNotifySearchResults
*results
;
4761 PurpleNotifySearchColumn
*column
;
4762 xmlnode
*searchResults
;
4764 int match_count
= 0;
4765 gboolean more
= FALSE
;
4768 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
4770 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4771 if (!searchResults
) {
4772 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4776 results
= purple_notify_searchresults_new();
4778 if (results
== NULL
) {
4779 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4780 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
4782 xmlnode_free(searchResults
);
4786 column
= purple_notify_searchresults_column_new(_("User Name"));
4787 purple_notify_searchresults_column_add(results
, column
);
4789 column
= purple_notify_searchresults_column_new(_("Name"));
4790 purple_notify_searchresults_column_add(results
, column
);
4792 column
= purple_notify_searchresults_column_new(_("Company"));
4793 purple_notify_searchresults_column_add(results
, column
);
4795 column
= purple_notify_searchresults_column_new(_("Country"));
4796 purple_notify_searchresults_column_add(results
, column
);
4798 column
= purple_notify_searchresults_column_new(_("Email"));
4799 purple_notify_searchresults_column_add(results
, column
);
4801 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
4804 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
4805 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
4806 g_strfreev(uri_parts
);
4808 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
4809 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
4810 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
4811 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
4813 purple_notify_searchresults_row_add(results
, row
);
4817 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
4818 char *data
= xmlnode_get_data_unescaped(mrow
);
4819 more
= (g_strcasecmp(data
, "true") == 0);
4823 secondary
= g_strdup_printf(
4824 dngettext(GETTEXT_PACKAGE
,
4825 "Found %d contact%s:",
4826 "Found %d contacts%s:", match_count
),
4827 match_count
, more
? _(" (more matched your query)") : "");
4829 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
4830 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
4831 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
4834 xmlnode_free(searchResults
);
4838 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
4840 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
4841 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
4845 PurpleRequestField
*field
= entries
->data
;
4846 const char *id
= purple_request_field_get_id(field
);
4847 const char *value
= purple_request_field_string_get_value(field
);
4849 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
4851 if (value
!= NULL
) attrs
[i
++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW
, id
, value
);
4852 } while ((entries
= g_list_next(entries
)) != NULL
);
4856 gchar
*query
= g_strjoinv(NULL
, attrs
);
4857 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
4858 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
4859 send_soap_request_with_cb(gc
->proto_data
, body
,
4860 (TransCallback
) process_search_contact_response
, NULL
);
4868 static void sipe_show_find_contact(PurplePluginAction
*action
)
4870 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
4871 PurpleRequestFields
*fields
;
4872 PurpleRequestFieldGroup
*group
;
4873 PurpleRequestField
*field
;
4875 fields
= purple_request_fields_new();
4876 group
= purple_request_field_group_new(NULL
);
4877 purple_request_fields_add_group(fields
, group
);
4879 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
4880 purple_request_field_group_add_field(group
, field
);
4881 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
4882 purple_request_field_group_add_field(group
, field
);
4883 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
4884 purple_request_field_group_add_field(group
, field
);
4885 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
4886 purple_request_field_group_add_field(group
, field
);
4888 purple_request_fields(gc
,
4890 _("Search for a Contact"),
4891 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4893 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
4895 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
4898 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
4901 PurplePluginAction
*act
;
4903 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
4904 menu
= g_list_prepend(menu
, act
);
4906 menu
= g_list_reverse(menu
);
4911 static void dummy_permit_deny(PurpleConnection
*gc
)
4915 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
4921 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
4927 static char *sipe_status_text(PurpleBuddy
*buddy
)
4929 struct sipe_account_data
*sip
;
4930 struct sipe_buddy
*sbuddy
;
4933 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
4934 if (sip
) //happens on pidgin exit
4936 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
4937 if (sbuddy
&& sbuddy
->annotation
)
4939 text
= g_strdup(sbuddy
->annotation
);
4946 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
4948 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
4949 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
4950 struct sipe_account_data
*sip
;
4951 struct sipe_buddy
*sbuddy
;
4952 char *annotation
= NULL
;
4954 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
4955 if (sip
) //happens on pidgin exit
4957 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
4960 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
4965 if (purple_presence_is_online(presence
))
4967 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
4972 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
4979 sipe_get_account_text_table(PurpleAccount
*account
)
4982 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
4983 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
4987 static PurpleBuddy
*
4988 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
4991 const gchar
*server_alias
, *email
;
4992 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
4994 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
4996 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
4998 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
5000 purple_blist_server_alias_buddy(clone
, server_alias
);
5003 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5005 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
5008 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
5010 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
5015 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
5017 PurpleBuddy
*buddy
, *b
;
5018 PurpleConnection
*gc
;
5019 PurpleGroup
* group
= purple_find_group(group_name
);
5021 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
5023 buddy
= (PurpleBuddy
*)node
;
5025 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
5026 gc
= purple_account_get_connection(buddy
->account
);
5028 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
5030 b
= purple_blist_add_buddy_clone(group
, buddy
);
5033 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
5037 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
5040 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
5042 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5045 char *mailto
= g_strdup_printf("mailto:%s", email
);
5046 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
5050 char *const parmList
[] = {mailto
, NULL
};
5051 if ((pid
= fork()) == -1)
5053 purple_debug_info("sipe", "fork() error\n");
5057 execvp("xdg-email", parmList
);
5058 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5066 //@TODO resolve env variable %WINDIR% first
5067 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
5070 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
5079 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
5084 * A menu which appear when right-clicking on buddy in contact list.
5087 sipe_buddy_menu(PurpleBuddy
*buddy
)
5089 PurpleBlistNode
*g_node
;
5090 PurpleGroup
*group
, *gr_parent
;
5091 PurpleMenuAction
*act
;
5093 GList
*menu_groups
= NULL
;
5095 act
= purple_menu_action_new(_("Send Email..."),
5096 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
5098 menu
= g_list_prepend(menu
, act
);
5100 gr_parent
= purple_buddy_get_group(buddy
);
5101 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
5102 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
5105 group
= (PurpleGroup
*)g_node
;
5106 if (group
== gr_parent
)
5109 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
5112 act
= purple_menu_action_new(purple_group_get_name(group
),
5113 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
5115 menu_groups
= g_list_prepend(menu_groups
, act
);
5117 menu_groups
= g_list_reverse(menu_groups
);
5119 act
= purple_menu_action_new(_("Copy to"),
5122 menu
= g_list_prepend(menu
, act
);
5123 menu
= g_list_reverse(menu
);
5128 GList
*sipe_blist_node_menu(PurpleBlistNode
*node
) {
5129 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
5130 return sipe_buddy_menu((PurpleBuddy
*) node
);
5137 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
5139 gboolean ret
= TRUE
;
5140 char *username
= (char *)trans
->payload
;
5142 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
5143 PurpleBuddy
*pbuddy
;
5144 struct sipe_buddy
*sbuddy
;
5146 char *server_alias
= NULL
;
5148 const char *device_name
= NULL
;
5150 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
5152 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
5153 alias
= purple_buddy_get_local_alias(pbuddy
);
5157 //will query buddy UA's capabilities and send answer to log
5158 sipe_options_request(sip
, username
);
5160 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
5163 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
5167 if (msg
->response
!= 200) {
5168 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
5170 xmlnode
*searchResults
;
5173 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
5174 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5175 if (!searchResults
) {
5176 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5177 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
5178 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
5179 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5180 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
5181 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
5182 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
5183 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
5184 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
5185 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
5186 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
5187 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
5188 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5189 if (!email
|| strcmp("", email
)) {
5190 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
5191 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
5195 xmlnode_free(searchResults
);
5198 purple_notify_user_info_add_section_break(info
);
5200 if (!server_alias
|| !strcmp("", server_alias
)) {
5201 g_free(server_alias
);
5202 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
5204 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5208 // same as server alias, do not present
5209 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
5212 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
5215 if (!email
|| !strcmp("", email
)) {
5217 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
5219 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5225 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
5228 /* show a buddy's user info in a nice dialog box */
5229 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
5230 username
, /* buddy's username */
5232 NULL
, /* callback called when dialog closed */
5233 NULL
); /* userdata for callback */
5239 * AD search first, LDAP based
5241 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
5243 char *row
= g_strdup_printf(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
5244 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
5246 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
5247 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
5248 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
5253 static PurplePlugin
*my_protocol
= NULL
;
5255 static PurplePluginProtocolInfo prpl_info
=
5258 NULL
, /* user_splits */
5259 NULL
, /* protocol_options */
5260 NO_BUDDY_ICONS
, /* icon_spec */
5261 sipe_list_icon
, /* list_icon */
5262 NULL
, /* list_emblems */
5263 sipe_status_text
, /* status_text */
5264 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
5265 sipe_status_types
, /* away_states */
5266 sipe_blist_node_menu
, /* blist_node_menu */
5267 NULL
, /* chat_info */
5268 NULL
, /* chat_info_defaults */
5269 sipe_login
, /* login */
5270 sipe_close
, /* close */
5271 sipe_im_send
, /* send_im */
5272 NULL
, /* set_info */ // TODO maybe
5273 sipe_send_typing
, /* send_typing */
5274 sipe_get_info
, /* get_info */
5275 sipe_set_status
, /* set_status */
5276 NULL
, /* set_idle */
5277 NULL
, /* change_passwd */
5278 sipe_add_buddy
, /* add_buddy */
5279 NULL
, /* add_buddies */
5280 sipe_remove_buddy
, /* remove_buddy */
5281 NULL
, /* remove_buddies */
5282 sipe_add_permit
, /* add_permit */
5283 sipe_add_deny
, /* add_deny */
5284 sipe_add_deny
, /* rem_permit */
5285 sipe_add_permit
, /* rem_deny */
5286 dummy_permit_deny
, /* set_permit_deny */
5287 NULL
, /* join_chat */
5288 NULL
, /* reject_chat */
5289 NULL
, /* get_chat_name */
5290 NULL
, /* chat_invite */
5291 NULL
, /* chat_leave */
5292 NULL
, /* chat_whisper */
5293 NULL
, /* chat_send */
5294 sipe_keep_alive
, /* keepalive */
5295 NULL
, /* register_user */
5296 NULL
, /* get_cb_info */ // deprecated
5297 NULL
, /* get_cb_away */ // deprecated
5298 sipe_alias_buddy
, /* alias_buddy */
5299 sipe_group_buddy
, /* group_buddy */
5300 sipe_rename_group
, /* rename_group */
5301 NULL
, /* buddy_free */
5302 sipe_convo_closed
, /* convo_closed */
5303 purple_normalize_nocase
, /* normalize */
5304 NULL
, /* set_buddy_icon */
5305 sipe_remove_group
, /* remove_group */
5306 NULL
, /* get_cb_real_name */ // TODO?
5307 NULL
, /* set_chat_topic */
5308 NULL
, /* find_blist_chat */
5309 NULL
, /* roomlist_get_list */
5310 NULL
, /* roomlist_cancel */
5311 NULL
, /* roomlist_expand_category */
5312 NULL
, /* can_receive_file */
5313 NULL
, /* send_file */
5314 NULL
, /* new_xfer */
5315 NULL
, /* offline_message */
5316 NULL
, /* whiteboard_prpl_ops */
5317 sipe_send_raw
, /* send_raw */
5318 NULL
, /* roomlist_room_serialize */
5319 NULL
, /* unregister_user */
5320 NULL
, /* send_attention */
5321 NULL
, /* get_attention_types */
5323 sizeof(PurplePluginProtocolInfo
), /* struct_size */
5324 sipe_get_account_text_table
, /* get_account_text_table */
5328 static PurplePluginInfo info
= {
5329 PURPLE_PLUGIN_MAGIC
,
5330 PURPLE_MAJOR_VERSION
,
5331 PURPLE_MINOR_VERSION
,
5332 PURPLE_PLUGIN_PROTOCOL
, /**< type */
5333 NULL
, /**< ui_requirement */
5335 NULL
, /**< dependencies */
5336 PURPLE_PRIORITY_DEFAULT
, /**< priority */
5337 "prpl-sipe", /**< id */
5338 "Microsoft LCS/OCS", /**< name */
5339 VERSION
, /**< version */
5340 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5341 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5342 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5343 "Gabriel Burt <gburt@novell.com>", /**< author */
5344 PURPLE_WEBSITE
, /**< homepage */
5345 sipe_plugin_load
, /**< load */
5346 sipe_plugin_unload
, /**< unload */
5347 sipe_plugin_destroy
, /**< destroy */
5348 NULL
, /**< ui_info */
5349 &prpl_info
, /**< extra_info */
5358 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
5362 entry
= prpl_info
.protocol_options
;
5364 purple_account_option_destroy(entry
->data
);
5365 entry
= g_list_delete_link(entry
, entry
);
5367 prpl_info
.protocol_options
= NULL
;
5369 entry
= prpl_info
.user_splits
;
5371 purple_account_user_split_destroy(entry
->data
);
5372 entry
= g_list_delete_link(entry
, entry
);
5374 prpl_info
.user_splits
= NULL
;
5377 static void init_plugin(PurplePlugin
*plugin
)
5379 PurpleAccountUserSplit
*split
;
5380 PurpleAccountOption
*option
;
5383 purple_debug_info(PACKAGE
, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
5384 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s",
5385 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
5388 purple_plugin_register(plugin
);
5390 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
5391 purple_account_user_split_set_reverse(split
, FALSE
);
5392 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
5394 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
5395 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5396 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5397 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5399 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
5400 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5401 // Translators: noun (networking port)
5402 option
= purple_account_option_int_new(_("Port"), "port", 5061);
5403 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5405 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
5406 purple_account_option_add_list_item(option
, _("Auto"), "auto");
5407 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
5408 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
5409 purple_account_option_add_list_item(option
, _("UDP"), "udp");
5410 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5412 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5413 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5415 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
5416 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5418 // TODO commented out so won't show in the preferences until we fix krb message signing
5419 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5420 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5422 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5423 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5424 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5427 option
= purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE
);
5428 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5429 option
= purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5430 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5431 my_protocol
= plugin
;
5434 /* I had to redefined the function for it load, but works */
5435 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
5436 plugin
->info
= &(info
);
5437 init_plugin((plugin
));
5438 sipe_plugin_load((plugin
));
5439 return purple_plugin_register(plugin
);