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 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
);
134 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
135 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
138 static void sipe_close(PurpleConnection
*gc
);
140 static void sipe_subscribe_to_name_single(struct sipe_account_data
*sip
, const char * buddy_name
);
141 static void sipe_subscribe_to_name_batched(struct sipe_account_data
*sip
, const char * buddy_name
);
142 static void send_presence_info(struct sipe_account_data
*sip
);
144 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
146 static void sipe_keep_alive_timeout(struct sipe_account_data
*sip
, const gchar
*hdr
)
148 gchar
*timeout
= sipmsg_find_part_of_header(hdr
, "timeout=", ";", NULL
);
149 if (timeout
!= NULL
) {
150 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
151 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
152 sip
->keepalive_timeout
);
156 static void sipe_keep_alive(PurpleConnection
*gc
)
158 struct sipe_account_data
*sip
= gc
->proto_data
;
159 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
160 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
161 gchar buf
[2] = {0, 0};
162 purple_debug_info("sipe", "sending keep alive\n");
163 sendto(sip
->fd
, buf
, 1, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
));
165 time_t now
= time(NULL
);
166 if ((sip
->keepalive_timeout
> 0) &&
167 ((now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
168 #if PURPLE_VERSION_CHECK(2,4,0)
169 && ((now
- gc
->last_received
) >= sip
->keepalive_timeout
)
172 purple_debug_info("sipe", "sending keep alive\n");
173 sendout_pkt(gc
, "\r\n\r\n");
174 sip
->last_keepalive
= now
;
179 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
181 struct sip_connection
*ret
= NULL
;
182 GSList
*entry
= sip
->openconns
;
185 if (ret
->fd
== fd
) return ret
;
191 static void sipe_auth_free(struct sip_auth
*auth
)
195 g_free(auth
->opaque
);
199 g_free(auth
->target
);
201 g_free(auth
->digest_session_key
);
202 auth
->digest_session_key
= NULL
;
203 g_free(auth
->ntlm_key
);
204 auth
->ntlm_key
= NULL
;
205 auth
->type
= AUTH_TYPE_UNSET
;
210 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
212 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
214 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
218 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
220 struct sip_connection
*conn
= connection_find(sip
, fd
);
222 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
223 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
229 static void connection_free_all(struct sipe_account_data
*sip
)
231 struct sip_connection
*ret
= NULL
;
232 GSList
*entry
= sip
->openconns
;
235 connection_remove(sip
, ret
->fd
);
236 entry
= sip
->openconns
;
240 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
242 const gchar
*method
= msg
->method
;
243 const gchar
*target
= msg
->target
;
248 const char *authdomain
= sip
->authdomain
;
249 const char *authuser
= sip
->authuser
;
250 //const char *krb5_realm;
252 //gchar *krb5_token = NULL;
254 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
255 // and do error checking
257 // KRB realm should always be uppercase
258 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
260 if (sip
->realhostname
) {
261 host
= sip
->realhostname
;
262 } else if (purple_account_get_bool(sip
->account
, "useproxy", TRUE
)) {
263 host
= purple_account_get_string(sip
->account
, "proxy", "");
265 host
= sip
->sipdomain
;
268 /*gboolean new_auth = krb5_auth.gss_context == NULL;
270 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
273 if (new_auth || force_reauth) {
274 krb5_token = krb5_auth.base64_token;
277 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
278 krb5_token = krb5_auth.base64_token;*/
284 if (!authuser
|| strlen(authuser
) < 1) {
285 authuser
= sip
->username
;
288 if (auth
->type
== AUTH_TYPE_DIGEST
) { /* Digest */
289 sprintf(noncecount
, "%08d", auth
->nc
++);
290 response
= purple_cipher_http_digest_calculate_response(
291 "md5", method
, target
, NULL
, NULL
,
292 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
293 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
295 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
);
298 } else if (auth
->type
== AUTH_TYPE_NTLM
) { /* NTLM */
299 // If we have a signature for the message, include that
300 if (msg
->signature
) {
301 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
);
305 if (auth
->nc
== 3 && auth
->nonce
&& auth
->ntlm_key
== NULL
) {
306 const gchar
*ntlm_key
;
308 #if GLIB_CHECK_VERSION(2,8,0)
309 const gchar
* hostname
= g_get_host_name();
311 static char hostname
[256];
312 int ret
= gethostname(hostname
, sizeof(hostname
));
313 hostname
[sizeof(hostname
) - 1] = '\0';
314 if (ret
== -1 || hostname
[0] == '\0') {
315 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Error when getting host name. Using \"localhost.\"\n");
317 strcpy(hostname
, "localhost");
320 /*const gchar * hostname = purple_get_host_name();*/
322 gssapi_data
= purple_ntlm_gen_authenticate(&ntlm_key
, authuser
, sip
->password
, hostname
, authdomain
, (const guint8
*)auth
->nonce
, &auth
->flags
);
323 auth
->ntlm_key
= (gchar
*)ntlm_key
;
324 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, gssapi_data
);
329 tmp
= g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth
->realm
, auth
->target
);
331 } else if (auth
->type
== AUTH_TYPE_KERBEROS
) {
334 /*if (new_auth || force_reauth) {
335 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
337 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);
339 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
342 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
343 gchar * mic = "MICTODO";
344 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
345 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
346 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
347 //auth->opaque ? auth->opaque : "", auth->target);
348 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
353 tmp
= g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth
->target
);
356 sprintf(noncecount
, "%08d", auth
->nc
++);
357 response
= purple_cipher_http_digest_calculate_response(
358 "md5", method
, target
, NULL
, NULL
,
359 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
360 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
362 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
);
367 static char *parse_attribute(const char *attrname
, const char *source
)
369 const char *tmp
, *tmp2
;
371 int len
= strlen(attrname
);
373 if (!strncmp(source
, attrname
, len
)) {
375 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
377 retval
= g_strndup(tmp
, tmp2
- tmp
);
379 retval
= g_strdup(tmp
);
385 static void fill_auth(struct sipe_account_data
*sip
, gchar
*hdr
, struct sip_auth
*auth
)
388 const char *authuser
;
391 //const char *krb5_realm;
394 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
395 // and do error checking
397 // KRB realm should always be uppercase
398 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
400 if (sip->realhostname) {
401 host = sip->realhostname;
402 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
403 host = purple_account_get_string(sip->account, "proxy", "");
405 host = sip->sipdomain;
408 authuser
= sip
->authuser
;
410 if (!authuser
|| strlen(authuser
) < 1) {
411 authuser
= sip
->username
;
415 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
419 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
420 auth
->type
= AUTH_TYPE_NTLM
;
421 parts
= g_strsplit(hdr
+5, "\", ", 0);
424 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
425 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
426 auth
->nonce
= g_memdup(purple_ntlm_parse_challenge(tmp
, &auth
->flags
), 8);
429 if ((tmp
= parse_attribute("targetname=\"",
433 else if ((tmp
= parse_attribute("realm=\"",
437 else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
444 if (!strstr(hdr
, "gssapi-data")) {
452 if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
453 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "setting auth type to Kerberos (3)\r\n");
454 auth
->type
= AUTH_TYPE_KERBEROS
;
455 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth - header: %s\r\n", hdr
);
456 parts
= g_strsplit(hdr
+9, "\", ", 0);
459 purple_debug_info("sipe", "krb - parts[i] %s\n", parts
[i
]);
460 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
461 /*if (krb5_auth.gss_context == NULL) {
462 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
464 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
467 if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
469 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
471 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
481 auth
->type
= AUTH_TYPE_DIGEST
;
482 parts
= g_strsplit(hdr
, " ", 0);
484 if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
487 else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
494 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "nonce: %s realm: %s\n", auth
->nonce
? auth
->nonce
: "(null)", auth
->realm
? auth
->realm
: "(null)");
496 auth
->digest_session_key
= purple_cipher_http_digest_calculate_session_key(
497 "md5", authuser
, auth
->realm
, sip
->password
, auth
->nonce
, NULL
);
503 static void sipe_canwrite_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
505 PurpleConnection
*gc
= data
;
506 struct sipe_account_data
*sip
= gc
->proto_data
;
510 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
512 if (max_write
== 0) {
513 if (sip
->tx_handler
!= 0){
514 purple_input_remove(sip
->tx_handler
);
520 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
522 if (written
< 0 && errno
== EAGAIN
)
524 else if (written
<= 0) {
525 /*TODO: do we really want to disconnect on a failure to write?*/
526 purple_connection_error(gc
, _("Could not write"));
530 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
533 static void sipe_canwrite_cb_ssl(gpointer data
, gint src
, PurpleInputCondition cond
)
535 PurpleConnection
*gc
= data
;
536 struct sipe_account_data
*sip
= gc
->proto_data
;
540 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
542 if (max_write
== 0) {
543 if (sip
->tx_handler
!= 0) {
544 purple_input_remove(sip
->tx_handler
);
550 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
552 if (written
< 0 && errno
== EAGAIN
)
554 else if (written
<= 0) {
555 /*TODO: do we really want to disconnect on a failure to write?*/
556 purple_connection_error(gc
, _("Could not write"));
560 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
563 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
565 static void send_later_cb(gpointer data
, gint source
, const gchar
*error
)
567 PurpleConnection
*gc
= data
;
568 struct sipe_account_data
*sip
;
569 struct sip_connection
*conn
;
571 if (!PURPLE_CONNECTION_IS_VALID(gc
))
579 purple_connection_error(gc
, _("Could not connect"));
583 sip
= gc
->proto_data
;
585 sip
->connecting
= FALSE
;
586 sip
->last_keepalive
= time(NULL
);
588 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
590 /* If there is more to write now, we need to register a handler */
591 if (sip
->txbuf
->bufused
> 0)
592 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
594 conn
= connection_create(sip
, source
);
595 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
598 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
600 struct sipe_account_data
*sip
;
601 struct sip_connection
*conn
;
603 if (!PURPLE_CONNECTION_IS_VALID(gc
))
605 if (gsc
) purple_ssl_close(gsc
);
609 sip
= gc
->proto_data
;
612 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
613 sip
->connecting
= FALSE
;
614 sip
->last_keepalive
= time(NULL
);
616 conn
= connection_create(sip
, gsc
->fd
);
618 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
623 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
625 PurpleConnection
*gc
= data
;
626 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
627 if (sip
== NULL
) return;
629 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
631 /* If there is more to write now */
632 if (sip
->txbuf
->bufused
> 0) {
633 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
638 static void sendlater(PurpleConnection
*gc
, const char *buf
)
640 struct sipe_account_data
*sip
= gc
->proto_data
;
642 if (!sip
->connecting
) {
643 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
644 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
645 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
647 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
648 purple_connection_error(gc
, _("Couldn't create socket"));
651 sip
->connecting
= TRUE
;
654 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
655 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
657 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
660 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
662 struct sipe_account_data
*sip
= gc
->proto_data
;
663 time_t currtime
= time(NULL
);
664 int writelen
= strlen(buf
);
666 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
667 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
668 if (sendto(sip
->fd
, buf
, writelen
, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
669 purple_debug_info("sipe", "could not send packet\n");
678 if (sip
->tx_handler
) {
683 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
685 ret
= write(sip
->fd
, buf
, writelen
);
689 if (ret
< 0 && errno
== EAGAIN
)
691 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
696 if (ret
< writelen
) {
697 if (!sip
->tx_handler
){
699 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
702 sip
->tx_handler
= purple_input_add(sip
->fd
,
703 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
708 /* XXX: is it OK to do this? You might get part of a request sent
709 with part of another. */
710 if (sip
->txbuf
->bufused
> 0)
711 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
713 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
719 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
721 sendout_pkt(gc
, buf
);
725 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
727 GSList
*tmp
= msg
->headers
;
730 GString
*outstr
= g_string_new("");
731 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
733 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
734 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
735 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
736 tmp
= g_slist_next(tmp
);
738 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
739 sendout_pkt(sip
->gc
, outstr
->str
);
740 g_string_free(outstr
, TRUE
);
743 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
746 if (sip
->registrar
.ntlm_key
) {
747 struct sipmsg_breakdown msgbd
;
748 gchar
*signature_input_str
;
750 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
751 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
752 sip
->registrar
.ntlm_num
++;
753 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
754 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
755 if (signature_input_str
!= NULL
) {
756 msg
->signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
757 msg
->rand
= g_strdup(msgbd
.rand
);
758 msg
->num
= g_strdup(msgbd
.num
);
759 g_free(signature_input_str
);
761 sipmsg_breakdown_free(&msgbd
);
764 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
765 buf
= auth_header(sip
, &sip
->registrar
, msg
);
766 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
767 sipmsg_add_header(msg
, "Authorization", buf
);
769 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
772 } 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")) {
773 sip
->registrar
.nc
= 3;
774 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
776 buf
= auth_header(sip
, &sip
->registrar
, msg
);
777 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
780 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
784 static char *get_contact(struct sipe_account_data
*sip
)
786 return g_strdup(sip
->contact
);
791 static char *get_contact_service(struct sipe_account_data *sip)
793 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()));
794 //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);
798 static void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
799 const char *text
, const char *body
)
803 GString
*outstr
= g_string_new("");
804 struct sipe_account_data
*sip
= gc
->proto_data
;
808 sipmsg_remove_header(msg
, "ms-user-data");
810 contact
= get_contact(sip
);
811 sipmsg_remove_header(msg
, "Contact");
812 sipmsg_add_header(msg
, "Contact", contact
);
815 /* When sending the acknowlegements and errors, the content length from the original
816 message is still here, but there is no body; we need to make sure we're sending the
817 correct content length */
818 sipmsg_remove_header(msg
, "Content-Length");
821 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
822 sipmsg_add_header(msg
, "Content-Length", len
);
824 sipmsg_remove_header(msg
, "Content-Type");
825 sipmsg_add_header(msg
, "Content-Length", "0");
828 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
829 //gchar * mic = "MICTODO";
830 msg
->response
= code
;
832 sipmsg_remove_header(msg
, "Authentication-Info");
833 sign_outgoing_message(msg
, sip
, msg
->method
);
835 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
838 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
839 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
841 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
842 tmp
= g_slist_next(tmp
);
844 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
845 sendout_pkt(gc
, outstr
->str
);
846 g_string_free(outstr
, TRUE
);
849 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
851 if (trans
->msg
) sipmsg_free(trans
->msg
);
852 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
856 static struct transaction
*
857 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
859 struct transaction
*trans
= g_new0(struct transaction
, 1);
860 trans
->time
= time(NULL
);
861 trans
->msg
= (struct sipmsg
*)msg
;
862 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
863 trans
->callback
= callback
;
864 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
868 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
870 struct transaction
*trans
;
871 GSList
*transactions
= sip
->transactions
;
872 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
874 while (transactions
) {
875 trans
= transactions
->data
;
876 if (!strcmp(trans
->cseq
, cseq
)) {
879 transactions
= transactions
->next
;
885 static struct transaction
*
886 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
887 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
888 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
890 struct sipe_account_data
*sip
= gc
->proto_data
;
891 const char *addh
= "";
894 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
895 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
896 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
897 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
898 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
899 gchar
*useragent
= (gchar
*)purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
);
900 gchar
*route
= strdup("");
901 gchar
*epid
= get_epid(); // TODO generate one per account/login
902 struct transaction
*trans
;
904 if (dialog
&& dialog
->routes
)
906 GSList
*iter
= dialog
->routes
;
911 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
913 iter
= g_slist_next(iter
);
917 if (!ourtag
&& !dialog
) {
921 if (!strcmp(method
, "REGISTER")) {
922 if (sip
->regcallid
) {
924 callid
= g_strdup(sip
->regcallid
);
926 sip
->regcallid
= g_strdup(callid
);
930 if (addheaders
) addh
= addheaders
;
932 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
933 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
934 "From: <sip:%s>%s%s;epid=%s\r\n"
935 "To: <%s>%s%s%s%s\r\n"
936 "Max-Forwards: 70\r\n"
941 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
943 dialog
&& dialog
->request
? dialog
->request
: url
,
944 TRANSPORT_DESCRIPTOR
,
945 purple_network_get_my_ip(-1),
947 branch
? ";branch=" : "",
948 branch
? branch
: "",
950 ourtag
? ";tag=" : "",
951 ourtag
? ourtag
: "",
954 theirtag
? ";tag=" : "",
955 theirtag
? theirtag
: "",
956 theirepid
? ";epid=" : "",
957 theirepid
? theirepid
: "",
958 dialog
? ++dialog
->cseq
: ++sip
->cseq
,
964 body
? strlen(body
) : 0,
968 //printf ("parsing msg buf:\n%s\n\n", buf);
969 msg
= sipmsg_parse_msg(buf
);
980 sign_outgoing_message (msg
, sip
, method
);
982 buf
= sipmsg_to_string (msg
);
984 /* add to ongoing transactions */
985 trans
= transactions_add_buf(sip
, msg
, tc
);
986 sendout_pkt(gc
, buf
);
992 static void send_soap_request_with_cb(struct sipe_account_data
*sip
, gchar
*body
, TransCallback callback
, void * payload
)
994 gchar
*from
= g_strdup_printf("sip:%s", sip
->username
);
995 gchar
*contact
= get_contact(sip
);
996 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
997 "Content-Type: application/SOAP+xml\r\n",contact
);
999 struct transaction
* tr
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1000 tr
->payload
= payload
;
1006 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1008 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
1011 static char *get_contact_register(struct sipe_account_data
*sip
)
1013 char *epid
= get_epid();
1014 char *uuid
= generateUUIDfromEPID(epid
);
1015 char *buf
= g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip
->listenport
, TRANSPORT_DESCRIPTOR
, uuid
);
1021 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1023 char *uri
= g_strdup_printf("sip:%s", sip
->sipdomain
);
1024 char *to
= g_strdup_printf("sip:%s", sip
->username
);
1025 char *contact
= get_contact_register(sip
);
1026 char *hdr
= g_strdup_printf("Contact: %s\r\n"
1027 "Supported: gruu-10, adhoclist, msrtc-event-categories\r\n"
1028 "Event: registration\r\n"
1029 "Allow-Events: presence\r\n"
1030 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1031 "Expires: %d\r\n", contact
,expire
);
1034 sip
->registerstatus
= 1;
1036 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1037 process_register_response
);
1044 static void do_register_cb(struct sipe_account_data
*sip
)
1046 do_register_exp(sip
, sip
->registerexpire
);
1047 sip
->reregister_set
= FALSE
;
1050 static void do_register(struct sipe_account_data
*sip
)
1052 do_register_exp(sip
, sip
->registerexpire
);
1056 * Returns URI from provided To or From header.
1058 * Needs to g_free() after use.
1060 * @return URI with sip: prefix
1062 static gchar
*parse_from(const gchar
*hdr
)
1065 const gchar
*tmp
, *tmp2
= hdr
;
1067 if (!hdr
) return NULL
;
1068 purple_debug_info("sipe", "parsing address out of %s\n", hdr
);
1069 tmp
= strchr(hdr
, '<');
1071 /* i hate the different SIP UA behaviours... */
1072 if (tmp
) { /* sip address in <...> */
1074 tmp
= strchr(tmp2
, '>');
1076 from
= g_strndup(tmp2
, tmp
- tmp2
);
1078 purple_debug_info("sipe", "found < without > in From\n");
1082 tmp
= strchr(tmp2
, ';');
1084 from
= g_strndup(tmp2
, tmp
- tmp2
);
1086 from
= g_strdup(tmp2
);
1089 purple_debug_info("sipe", "got %s\n", from
);
1093 static xmlnode
* xmlnode_get_descendant(xmlnode
* parent
, ...)
1096 xmlnode
* node
= NULL
;
1099 va_start(args
, parent
);
1100 while ((name
= va_arg(args
, const char *)) != NULL
) {
1101 node
= xmlnode_get_child(parent
, name
);
1102 if (node
== NULL
) return NULL
;
1112 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1114 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1115 send_soap_request(sip
, body
);
1120 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1123 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1125 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1128 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1132 void sipe_auth_user_cb(void * data
)
1134 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1137 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1142 void sipe_deny_user_cb(void * data
)
1144 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1147 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1152 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1154 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1155 sipe_contact_allow_deny(sip
, name
, TRUE
);
1159 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1161 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1162 sipe_contact_allow_deny(sip
, name
, FALSE
);
1166 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1168 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1169 sipe_contact_set_acl(sip, name, "");
1173 sipe_process_incoming_pending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1177 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1178 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1180 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1182 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1183 if (!watchers
) return;
1185 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1186 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1187 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1188 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1190 // TODO pull out optional displayName to pass as alias
1192 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1193 job
->who
= remote_user
;
1195 purple_account_request_authorization(
1209 xmlnode_free(watchers
);
1214 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1216 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1217 if (!purple_group
) {
1218 purple_group
= purple_group_new(group
->name
);
1219 purple_blist_add_group(purple_group
, NULL
);
1223 group
->purple_group
= purple_group
;
1224 sip
->groups
= g_slist_append(sip
->groups
, group
);
1225 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1227 purple_debug_info("sipe", "did not add group %s\n", group
->name
);
1231 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1233 struct sipe_group
*group
;
1239 entry
= sip
->groups
;
1241 group
= entry
->data
;
1242 if (group
->id
== id
) {
1245 entry
= entry
->next
;
1250 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, gchar
* name
)
1252 struct sipe_group
*group
;
1258 entry
= sip
->groups
;
1260 group
= entry
->data
;
1261 if (!strcmp(group
->name
, name
)) {
1264 entry
= entry
->next
;
1270 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1273 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1274 body
= g_strdup_printf(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1275 send_soap_request(sip
, body
);
1277 g_free(group
->name
);
1278 group
->name
= g_strdup(name
);
1282 * Only appends if no such value already stored.
1285 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1286 GSList
* res
= list
;
1287 if (!g_slist_find_custom(list
, data
, func
)) {
1288 res
= g_slist_insert_sorted(list
, data
, func
);
1294 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1295 return group1
->id
- group2
->id
;
1299 * Returns string like "2 4 7 8" - group ids buddy belong to.
1302 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1305 //creating array from GList, converting int to gchar*
1306 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1307 GSList
*entry
= buddy
->groups
;
1309 struct sipe_group
* group
= entry
->data
;
1310 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1311 entry
= entry
->next
;
1315 res
= g_strjoinv(" ", ids_arr
);
1316 g_strfreev(ids_arr
);
1321 * Sends buddy update to server
1324 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1326 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1327 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1329 if (buddy
&& purple_buddy
) {
1330 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1332 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1333 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1335 body
= g_strdup_printf(SIPE_SOAP_SET_CONTACT
,
1336 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1338 send_soap_request(sip
, body
);
1344 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1346 if (msg
->response
== 200) {
1347 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1349 struct group_user_context
* ctx
= (struct group_user_context
*)tc
->payload
;
1353 struct sipe_buddy
*buddy
;
1354 group
->name
= ctx
->group_name
;
1356 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1357 if (!xml
) return FALSE
;
1359 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1360 if (!node
) return FALSE
;
1362 group_id
= xmlnode_get_data(node
);
1363 if (!group_id
) return FALSE
;
1365 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1367 sipe_group_add(sip
, group
);
1369 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1371 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1374 sipe_group_set_user(sip
, ctx
->user_name
);
1383 static void sipe_group_create (struct sipe_account_data
*sip
, gchar
*name
, gchar
* who
)
1385 struct group_user_context
* ctx
= g_new0(struct group_user_context
, 1);
1387 ctx
->group_name
= g_strdup(name
);
1388 ctx
->user_name
= g_strdup(who
);
1390 body
= g_strdup_printf(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1391 send_soap_request_with_cb(sip
, body
, process_add_group_response
, ctx
);
1397 * Should return FALSE if repetitive action is not needed
1399 gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1402 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1403 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1404 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1405 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1406 ret
= sched_action
->repetitive
;
1407 g_free(sched_action
->payload
);
1408 g_free(sched_action
->name
);
1409 g_free(sched_action
);
1414 * Do schedule action for execution in the future.
1415 * Non repetitive execution.
1417 * @param name of action (will be copied)
1418 * @param timeout in seconds
1419 * @action callback function
1420 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1422 void sipe_schedule_action(gchar
*name
, int timeout
, Action action
, struct sipe_account_data
*sip
, void * payload
)
1424 struct scheduled_action
*sched_action
;
1426 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name
, timeout
);
1427 sched_action
= g_new0(struct scheduled_action
, 1);
1428 sched_action
->repetitive
= FALSE
;
1429 sched_action
->name
= g_strdup(name
);
1430 sched_action
->action
= action
;
1431 sched_action
->sip
= sip
;
1432 sched_action
->payload
= payload
;
1433 sched_action
->timeout_handler
= purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1434 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1435 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1439 * Kills action timer effectively cancelling
1442 * @param name of action
1444 void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, gchar
*name
)
1448 if (!sip
->timeouts
|| !name
) return;
1450 entry
= sip
->timeouts
;
1452 struct scheduled_action
*sched_action
= entry
->data
;
1453 if(!strcmp(sched_action
->name
, name
)) {
1454 GSList
*to_delete
= entry
;
1455 entry
= entry
->next
;
1456 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1457 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1458 purple_timeout_remove(sched_action
->timeout_handler
);
1459 g_free(sched_action
->payload
);
1460 g_free(sched_action
->name
);
1461 g_free(sched_action
);
1463 entry
= entry
->next
;
1468 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1470 static gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1473 //purple_debug_info("sipe","process_subscribe_response: body:\n%s\n", msg->body);
1475 if (msg
->response
== 200 || msg
->response
== 202)
1477 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1479 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1484 /* we can not subscribe -> user is offline (TODO unknown status?) */
1485 to
= parse_from(sipmsg_find_header(tc
->msg
, "To")); /* can't be NULL since it is our own msg */
1486 purple_prpl_got_user_status(sip
->account
, to
, "offline", NULL
);
1493 * Batch Category SUBSCRIBE [SIP-PRES] - msrtc-event-categories+xml
1494 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list
1495 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1499 static void sipe_subscribe_to_name_batched(struct sipe_account_data
*sip
, const char * buddy_name
){
1500 gchar
*resource_uri
= strstr(buddy_name
, "sip:") ? g_strdup(buddy_name
) : g_strdup_printf("sip:%s", buddy_name
);
1501 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
1502 gchar
*tmp
= get_contact(sip
);
1505 request
= g_strdup_printf(
1506 // 2005 fix. We probably should not require a feature as we are backward compatible and support either response.
1507 // But we declare (in Supported: eventlist) that we are able to recieve such messages.
1508 //"Require: adhoclist, categoryList\r\n"
1509 "Supported: eventlist\r\n"
1510 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1511 "Supported: ms-piggyback-first-notify\r\n"
1512 "Supported: com.microsoft.autoextend\r\n"
1513 "Supported: ms-benotify\r\n"
1514 "Proxy-Require: ms-benotify\r\n"
1515 "Event: presence\r\n"
1516 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1517 "Contact: %s\r\n", tmp
);
1519 content
= g_strdup_printf(
1520 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1521 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1522 "<resource uri=\"%s\"/>\n"
1524 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1525 "<category name=\"note\"/>\n"
1526 "<category name=\"state\"/>\n"
1529 "</batchSub>", sip
->username
, resource_uri
1534 /* subscribe to buddy presence */
1535 send_sip_request(sip
->gc
, "SUBSCRIBE", resource_uri
, resource_uri
, request
, content
, NULL
, process_subscribe_response
);
1540 g_free(resource_uri
);
1544 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1545 * The user sends a single SUBSCRIBE request to the subscribed contact.
1546 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1550 static void sipe_subscribe_to_name_single(struct sipe_account_data
*sip
, const char * buddy_name
)
1552 gchar
*to
= strstr(buddy_name
, "sip:") ? g_strdup(buddy_name
) : g_strdup_printf("sip:%s", buddy_name
);
1553 gchar
*tmp
= get_contact(sip
);
1556 request
= g_strdup_printf(
1557 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1558 "Supported: ms-piggyback-first-notify\r\n"
1559 "Supported: com.microsoft.autoextend\r\n"
1560 "Supported: ms-benotify\r\n"
1561 "Proxy-Require: ms-benotify\r\n"
1562 "Event: presence\r\n"
1563 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1564 "Contact: %s\r\n", tmp
);
1566 content
= g_strdup_printf(
1567 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1568 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1569 "<resource uri=\"%s\"/>\n"
1571 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1572 "<category name=\"note\"/>\n"
1573 "<category name=\"state\"/>\n"
1576 "</batchSub>", sip
->username
, to
1581 /* subscribe to buddy presence */
1582 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1589 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
1591 const char *status_id
= purple_status_get_id(status
);
1592 struct sipe_account_data
*sip
= NULL
;
1594 if (!purple_status_is_active(status
))
1598 sip
= account
->gc
->proto_data
;
1601 g_free(sip
->status
);
1602 sip
->status
= g_strdup(status_id
);
1603 send_presence_info(sip
);
1608 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
, const char *alias
)
1610 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1611 sipe_group_set_user(sip
, name
);
1615 sipe_group_buddy(PurpleConnection
*gc
,
1617 const char *old_group_name
,
1618 const char *new_group_name
)
1620 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1621 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1622 struct sipe_group
* old_group
= NULL
;
1623 struct sipe_group
* new_group
;
1625 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1626 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
1628 if(!buddy
) { // buddy not in roaming list
1632 if (old_group_name
) {
1633 old_group
= sipe_group_find_by_name(sip
, g_strdup(old_group_name
));
1635 new_group
= sipe_group_find_by_name(sip
, g_strdup(new_group_name
));
1638 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
1639 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
1643 sipe_group_create(sip
, g_strdup(new_group_name
), g_strdup(who
));
1645 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
1646 sipe_group_set_user(sip
, who
);
1650 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1652 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1653 struct sipe_buddy
*b
;
1655 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1657 // Prepend sip: if needed
1658 if (strncmp("sip:", buddy
->name
, 4)) {
1659 gchar
*buf
= g_strdup_printf("sip:%s", buddy
->name
);
1660 purple_blist_rename_buddy(buddy
, buf
);
1664 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
1665 b
= g_new0(struct sipe_buddy
, 1);
1666 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy
->name
);
1667 b
->name
= g_strdup(buddy
->name
);
1668 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
1669 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
1670 sipe_subscribe_to_name_batched(sip
, b
->name
); //@TODO should go to callback
1672 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy
->name
);
1677 * Unassociates buddy from group first.
1678 * Then see if no groups left, removes buddy completely.
1679 * Otherwise updates buddy groups on server.
1681 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1683 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1684 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
1685 struct sipe_group
*g
= NULL
;
1687 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1692 g
= sipe_group_find_by_name(sip
, group
->name
);
1696 b
->groups
= g_slist_remove(b
->groups
, g
);
1697 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
1700 if (g_slist_length(b
->groups
) < 1) {
1701 gchar
*action_name
= g_strdup_printf("<%s><%s>", "presence", buddy
->name
);
1702 sipe_cancel_scheduled_action(sip
, action_name
);
1703 g_free(action_name
);
1705 g_hash_table_remove(sip
->buddies
, buddy
->name
);
1708 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
1709 send_soap_request(sip
, body
);
1714 g_free(b
->annotation
);
1715 g_free(b
->device_name
);
1716 g_slist_free(b
->groups
);
1719 //updates groups on server
1720 sipe_group_set_user(sip
, b
->name
);
1726 sipe_rename_group(PurpleConnection
*gc
,
1727 const char *old_name
,
1729 GList
*moved_buddies
)
1731 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1732 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, g_strdup(old_name
));
1734 sipe_group_rename(sip
, s_group
, group
->name
);
1736 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
1741 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
1743 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1744 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
1747 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
1748 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
1749 send_soap_request(sip
, body
);
1752 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
1753 g_free(s_group
->name
);
1755 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
1759 static GList
*sipe_status_types(PurpleAccount
*acc
)
1761 PurpleStatusType
*type
;
1762 GList
*types
= NULL
;
1765 type
= purple_status_type_new_with_attrs(
1766 PURPLE_STATUS_AVAILABLE
, NULL
, "Online", TRUE
, TRUE
, FALSE
,
1767 // Translators: noun
1768 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1770 types
= g_list_append(types
, type
);
1773 type
= purple_status_type_new_with_attrs(
1774 PURPLE_STATUS_UNAVAILABLE
, "busy", _("Busy"), TRUE
, TRUE
, FALSE
,
1775 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1777 types
= g_list_append(types
, type
);
1779 // Do Not Disturb (Not let user set it)
1780 type
= purple_status_type_new_with_attrs(
1781 PURPLE_STATUS_UNAVAILABLE
, "do-not-disturb", "Do Not Disturb", TRUE
, FALSE
, FALSE
,
1782 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1784 types
= g_list_append(types
, type
);
1787 type
= purple_status_type_new_with_attrs(
1788 PURPLE_STATUS_AWAY
, "be-right-back", _("Be Right Back"), TRUE
, TRUE
, FALSE
,
1789 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1791 types
= g_list_append(types
, type
);
1794 type
= purple_status_type_new_with_attrs(
1795 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
1796 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1798 types
= g_list_append(types
, type
);
1801 type
= purple_status_type_new_with_attrs(
1802 PURPLE_STATUS_UNAVAILABLE
, "on-the-phone", _("On The Phone"), TRUE
, TRUE
, FALSE
,
1803 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1805 types
= g_list_append(types
, type
);
1808 type
= purple_status_type_new_with_attrs(
1809 PURPLE_STATUS_AWAY
, "out-to-lunch", "Out To Lunch", TRUE
, TRUE
, FALSE
,
1810 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1812 types
= g_list_append(types
, type
);
1815 type
= purple_status_type_new_full(
1816 PURPLE_STATUS_INVISIBLE
, NULL
, "Appear Offline", TRUE
, TRUE
, FALSE
);
1817 types
= g_list_append(types
, type
);
1820 type
= purple_status_type_new_full(
1821 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
1822 types
= g_list_append(types
, type
);
1828 * A callback for g_hash_table_foreach
1830 static void sipe_buddy_subscribe_cb(char *name
, struct sipe_buddy
*buddy
, struct sipe_account_data
*sip
)
1832 sipe_subscribe_to_name_batched(sip
, buddy
->name
);
1836 * Removes entries from purple buddy list
1837 * that does not correspond ones in the roaming contact list.
1839 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
1840 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
1841 GSList
*entry
= buddies
;
1842 struct sipe_buddy
*buddy
;
1846 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
1847 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
1850 g
= purple_buddy_get_group(b
);
1851 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
1853 gboolean in_sipe_groups
= FALSE
;
1854 GSList
*entry2
= buddy
->groups
;
1856 struct sipe_group
*group
= entry2
->data
;
1857 if (!strcmp(group
->name
, g
->name
)) {
1858 in_sipe_groups
= TRUE
;
1861 entry2
= entry2
->next
;
1863 if(!in_sipe_groups
) {
1864 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
1865 purple_blist_remove_buddy(b
);
1868 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
1869 purple_blist_remove_buddy(b
);
1871 entry
= entry
->next
;
1875 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1877 int len
= msg
->bodylen
;
1879 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
1882 gchar
*contacts_delta
;
1883 xmlnode
*group_node
;
1884 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
1888 purple_debug_info("sipe", "msg->body:%s\n", msg
->body
);
1890 /* Convert the contact from XML to Purple Buddies */
1891 isc
= xmlnode_from_str(msg
->body
, len
);
1896 contacts_delta
= g_strdup(xmlnode_get_attrib(isc
, "deltaNum"));
1897 if (contacts_delta
) {
1898 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
1902 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
1903 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1905 group
->name
= g_strdup(xmlnode_get_attrib(group_node
, "name"));
1906 if (!strncmp(group
->name
, "~", 1)){
1908 group
->name
= "Other Contacts";
1910 group
->name
= g_strdup(group
->name
);
1911 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
1913 sipe_group_add(sip
, group
);
1916 // Make sure we have at least one group
1917 if (g_slist_length(sip
->groups
) == 0) {
1918 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1919 PurpleGroup
*purple_group
;
1921 group
->name
= g_strdup("Other Contacts");
1923 purple_group
= purple_group_new(group
->name
);
1924 purple_blist_add_group(purple_group
, NULL
);
1925 sip
->groups
= g_slist_append(sip
->groups
, group
);
1928 /* Parse contacts */
1929 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
1930 gchar
* uri
= g_strdup(xmlnode_get_attrib(item
, "uri"));
1931 gchar
* name
= g_strdup(xmlnode_get_attrib(item
, "name"));
1932 gchar
* groups
= g_strdup(xmlnode_get_attrib(item
, "groups"));
1933 gchar
* buddy_name
= g_strdup_printf("sip:%s", uri
);
1934 gchar
**item_groups
;
1935 struct sipe_group
*group
= NULL
;
1936 struct sipe_buddy
*buddy
= NULL
;
1939 // assign to group Other Contacts if nothing else received
1940 if(!groups
|| !strcmp("", groups
) ) {
1941 group
= sipe_group_find_by_name(sip
, "Other Contacts");
1942 groups
= group
? g_strdup_printf("%d", group
->id
) : "1";
1945 item_groups
= g_strsplit(groups
, " ", 0);
1947 while (item_groups
[i
]) {
1948 group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
1950 // If couldn't find the right group for this contact, just put them in the first group we have
1951 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
1952 group
= sip
->groups
->data
;
1955 if (group
!= NULL
) {
1956 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
1958 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
1959 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
1962 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
1963 if (name
!= NULL
&& strlen(name
) != 0) {
1964 purple_blist_alias_buddy(b
, name
);
1969 buddy
= g_new0(struct sipe_buddy
, 1);
1970 buddy
->name
= g_strdup(b
->name
);
1971 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
1974 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1976 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
1978 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1983 } // while, contact groups
1984 g_strfreev(item_groups
);
1994 sipe_cleanup_local_blist(sip
);
1996 //subscribe to buddies
1997 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
1998 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
1999 sip
->subscribed_buddies
= TRUE
;
2008 * Subscribe roaming contacts
2010 static void sipe_subscribe_buddylist(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2012 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2013 gchar
*tmp
= get_contact(sip
);
2014 gchar
*hdr
= g_strdup_printf(
2015 "Event: vnd-microsoft-roaming-contacts\r\n"
2016 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2017 "Supported: com.microsoft.autoextend\r\n"
2018 "Supported: ms-benotify\r\n"
2019 "Proxy-Require: ms-benotify\r\n"
2020 "Supported: ms-piggyback-first-notify\r\n"
2021 "Contact: %s\r\n", tmp
);
2024 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, sipe_process_roaming_contacts
);
2030 sipe_process_pending_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
2032 sipe_process_incoming_pending (sip
, msg
);
2036 static void sipe_subscribe_pending_buddies(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2038 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2039 gchar
*tmp
= get_contact(sip
);
2040 gchar
*hdr
= g_strdup_printf(
2041 "Event: presence.wpending\r\n"
2042 "Accept: text/xml+msrtc.wpending\r\n"
2043 "Supported: com.microsoft.autoextend\r\n"
2044 "Supported: ms-benotify\r\n"
2045 "Proxy-Require: ms-benotify\r\n"
2046 "Supported: ms-piggyback-first-notify\r\n"
2047 "Contact: %s\r\n", tmp
);
2050 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, sipe_process_pending_response
);
2055 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2057 const gchar
*contacts_delta
;
2060 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2066 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2069 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2076 sipe_process_acl_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
2078 sipe_process_roaming_acl(sip
, msg
);
2083 * When we receive some self (BE) NOTIFY with a new subscriber
2084 * we sends a setSubscribers request to him [SIP-PRES]
2088 static void sipe_process_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2098 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2100 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2103 node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
);
2109 user
= xmlnode_get_attrib(node
, "user");
2115 to
= g_strdup_printf("sip:%s", sip
->username
);
2116 tmp
= get_contact(sip
);
2117 hdr
= g_strdup_printf(
2119 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", tmp
);
2121 body
= g_strdup_printf(
2122 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2123 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2124 "</setSubscribers>",user
);
2127 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
2134 static void sipe_subscribe_acl(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2136 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2137 gchar
*tmp
= get_contact(sip
);
2138 gchar
*hdr
= g_strdup_printf(
2139 "Event: vnd-microsoft-roaming-ACL\r\n"
2140 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2141 "Supported: com.microsoft.autoextend\r\n"
2142 "Supported: ms-benotify\r\n"
2143 "Proxy-Require: ms-benotify\r\n"
2144 "Supported: ms-piggyback-first-notify\r\n"
2145 "Contact: %s\r\n", tmp
);
2148 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, sipe_process_acl_response
);
2154 * To request for presence information about the user, access level settings that have already been configured by the user
2155 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2156 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2159 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2161 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2162 gchar
*tmp
= get_contact(sip
);
2163 gchar
*hdr
= g_strdup_printf(
2164 "Event: vnd-microsoft-roaming-self\r\n"
2165 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2166 "Supported: com.microsoft.autoextend\r\n"
2167 "Supported: ms-benotify\r\n"
2168 "Proxy-Require: ms-benotify\r\n"
2169 "Supported: ms-piggyback-first-notify\r\n"
2171 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
2173 gchar
*body
=g_strdup(
2174 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2175 "<roaming type=\"categories\"/>"
2176 "<roaming type=\"containers\"/>"
2177 "<roaming type=\"subscribers\"/></roamingList>");
2180 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, NULL
);
2186 /** Subscription for provisioning information to help with initial
2187 * configuration. This subscription is a one-time query (denoted by the Expires header,
2188 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2189 * configuration, meeting policies, and policy settings that Communicator must enforce.
2190 * TODO: for what we need this information.
2193 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2195 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2196 gchar
*tmp
= get_contact(sip
);
2197 gchar
*hdr
= g_strdup_printf(
2198 "Event: vnd-microsoft-provisioning-v2\r\n"
2199 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2200 "Supported: com.microsoft.autoextend\r\n"
2201 "Supported: ms-benotify\r\n"
2202 "Proxy-Require: ms-benotify\r\n"
2203 "Supported: ms-piggyback-first-notify\r\n"
2206 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
2207 gchar
*body
= g_strdup(
2208 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2209 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2210 "<provisioningGroup name=\"ucPolicy\"/>"
2211 "</provisioningGroupList>");
2214 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, NULL
);
2220 /* IM Session (INVITE and MESSAGE methods) */
2222 static struct sip_im_session
* find_im_session (struct sipe_account_data
*sip
, const char *who
)
2224 struct sip_im_session
*session
;
2226 if (sip
== NULL
|| who
== NULL
) {
2230 entry
= sip
->im_sessions
;
2232 session
= entry
->data
;
2233 if ((who
!= NULL
&& !strcmp(who
, session
->with
))) {
2236 entry
= entry
->next
;
2241 static struct sip_im_session
* find_or_create_im_session (struct sipe_account_data
*sip
, const char *who
)
2243 struct sip_im_session
*session
= find_im_session(sip
, who
);
2245 session
= g_new0(struct sip_im_session
, 1);
2246 session
->with
= g_strdup(who
);
2247 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2252 static void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2254 struct sip_dialog
*dialog
= session
->dialog
;
2257 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2260 entry
= dialog
->routes
;
2262 g_free(entry
->data
);
2263 entry
= g_slist_remove(entry
, entry
->data
);
2265 entry
= dialog
->supported
;
2267 g_free(entry
->data
);
2268 entry
= g_slist_remove(entry
, entry
->data
);
2270 g_free(dialog
->callid
);
2271 g_free(dialog
->ourtag
);
2272 g_free(dialog
->theirtag
);
2273 g_free(dialog
->theirepid
);
2274 g_free(dialog
->request
);
2276 g_free(session
->dialog
);
2278 entry
= session
->outgoing_message_queue
;
2280 g_free(entry
->data
);
2281 entry
= g_slist_remove(entry
, entry
->data
);
2284 g_free(session
->with
);
2288 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2290 char *msg
, *msg_tmp
;
2291 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2292 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2294 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2295 "possibly because one or more persons are offline:\n%s") ,
2297 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2302 static void sipe_im_remove_first_from_queue (struct sip_im_session
* session
);
2303 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2306 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2308 gboolean ret
= TRUE
;
2309 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2310 struct sip_im_session
* session
= find_im_session(sip
, with
);
2311 struct sip_dialog
*dialog
;
2314 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2319 if (msg
->response
!= 200) {
2320 gchar
*queued_msg
= NULL
;
2321 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2323 if (session
->outgoing_message_queue
) {
2324 queued_msg
= session
->outgoing_message_queue
->data
;
2326 sipe_present_message_undelivered_err(with
, sip
, queued_msg
);
2327 im_session_destroy(sip
, session
);
2332 dialog
= session
->dialog
;
2334 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2338 sipe_im_remove_first_from_queue(session
);
2339 sipe_im_process_queue(sip
, session
);
2344 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_im_session
* session
, const char *msg
)
2354 if (strncmp("sip:", session
->with
, 4)) {
2355 fullto
= g_strdup_printf("sip:%s", session
->with
);
2357 fullto
= g_strdup(session
->with
);
2360 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2361 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2363 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2366 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2369 msgr
= g_strdup("");
2372 tmp
= get_contact(sip
);
2373 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2374 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2375 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2376 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2381 send_sip_request(sip
->gc
, "MESSAGE", fullto
, fullto
, hdr
, msgtext
, session
->dialog
, process_message_response
);
2389 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2391 GSList
*entry
= session
->outgoing_message_queue
;
2393 char *queued_msg
= entry
->data
;
2394 sipe_send_message(sip
, session
, queued_msg
);
2399 sipe_im_remove_first_from_queue (struct sip_im_session
* session
)
2401 if (session
&& session
->outgoing_message_queue
) {
2402 char *queued_msg
= session
->outgoing_message_queue
->data
;
2403 // Remove from the queue and free the string
2404 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2410 sipe_get_route_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2412 GSList
*hdr
= msg
->headers
;
2413 struct siphdrelement
*elem
;
2419 if(!strcmp(elem
->name
, "Record-Route"))
2421 gchar
*route
= sipmsg_find_part_of_header(elem
->value
, "<", ">", NULL
);
2422 dialog
->routes
= g_slist_append(dialog
->routes
, route
);
2424 hdr
= g_slist_next(hdr
);
2429 dialog
->routes
= g_slist_reverse(dialog
->routes
);
2434 dialog
->request
= dialog
->routes
->data
;
2435 dialog
->routes
= g_slist_remove(dialog
->routes
, dialog
->routes
->data
);
2438 contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Contact"), "<", ">", NULL
);
2439 dialog
->routes
= g_slist_append(dialog
->routes
, contact
);
2443 sipe_get_supported_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2445 GSList
*hdr
= msg
->headers
;
2446 struct siphdrelement
*elem
;
2450 if(!strcmp(elem
->name
, "Supported")
2451 && !g_slist_find_custom(dialog
->supported
, elem
->value
, (GCompareFunc
)strcmp
))
2453 dialog
->supported
= g_slist_append(dialog
->supported
, g_strdup(elem
->value
));
2456 hdr
= g_slist_next(hdr
);
2461 sipe_parse_dialog(struct sipmsg
* msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2463 gchar
*us
= outgoing
? "From" : "To";
2464 gchar
*them
= outgoing
? "To" : "From";
2466 dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2467 dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, us
));
2468 dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, them
));
2469 if (!dialog
->theirepid
) {
2470 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", ";", NULL
);
2472 if (!dialog
->theirepid
) {
2473 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", NULL
, NULL
);
2476 sipe_get_route_header(msg
, dialog
, outgoing
);
2477 sipe_get_supported_header(msg
, dialog
, outgoing
);
2482 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2484 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2485 struct sip_im_session
* session
= find_im_session(sip
, with
);
2486 struct sip_dialog
*dialog
;
2489 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2494 if (msg
->response
!= 200) {
2495 gchar
*queued_msg
= NULL
;
2496 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2498 if (session
->outgoing_message_queue
) {
2499 queued_msg
= session
->outgoing_message_queue
->data
;
2501 sipe_present_message_undelivered_err(with
, sip
, queued_msg
);
2503 im_session_destroy(sip
, session
);
2508 dialog
= session
->dialog
;
2510 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2515 sipe_parse_dialog(msg
, dialog
, TRUE
);
2518 send_sip_request(sip
->gc
, "ACK", session
->with
, session
->with
, NULL
, NULL
, dialog
, NULL
);
2519 session
->outgoing_invite
= NULL
;
2520 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)strcmp
)) {
2521 sipe_im_remove_first_from_queue(session
);
2523 sipe_im_process_queue(sip
, session
);
2531 static void sipe_invite(struct sipe_account_data
*sip
, struct sip_im_session
* session
, gchar
* msg_body
)
2540 char *ms_text_format
;
2544 if (session
->dialog
) {
2545 purple_debug_info("sipe", "session with %s already has a dialog open\n", session
->with
);
2549 session
->dialog
= g_new0(struct sip_dialog
, 1);
2551 if (strstr(session
->with
, "sip:")) {
2552 to
= g_strdup(session
->with
);
2554 to
= g_strdup_printf("sip:%s", session
->with
);
2557 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
2558 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
2560 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2564 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2568 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
2569 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
2574 contact
= get_contact(sip
);
2575 hdr
= g_strdup_printf(
2577 "Content-Type: application/sdp\r\n",
2578 contact
, ms_text_format
);
2579 g_free(ms_text_format
);
2581 body
= g_strdup_printf(
2583 "o=- 0 0 IN IP4 %s\r\n"
2587 "m=message %d sip null\r\n"
2588 "a=accept-types:text/plain text/html image/gif "
2589 "multipart/alternative application/im-iscomposing+xml\r\n",
2590 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
2592 session
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
2593 to
, to
, hdr
, body
, session
->dialog
, process_invite_response
);
2602 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2605 send_sip_request(sip
->gc
, "BYE", session
->with
, session
->with
, NULL
, NULL
, session
->dialog
, NULL
);
2606 im_session_destroy(sip
, session
);
2611 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
2613 struct sipe_account_data
*sip
= gc
->proto_data
;
2615 purple_debug_info("sipe", "conversation with %s closed\n", who
);
2616 im_session_close(sip
, find_im_session(sip
, who
));
2620 im_session_close_all (struct sipe_account_data
*sip
)
2622 GSList
*entry
= sip
->im_sessions
;
2624 im_session_close (sip
, entry
->data
);
2625 entry
= sip
->im_sessions
;
2629 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
2631 struct sipe_account_data
*sip
;
2634 struct sip_im_session
*session
;
2636 purple_debug_info("sipe", "sipe_im_send what=%s\n", what
);
2638 sip
= gc
->proto_data
;
2640 text
= g_strdup(what
);
2642 session
= find_or_create_im_session(sip
, who
);
2644 // Queue the message
2645 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, text
);
2647 if (session
->dialog
&& session
->dialog
->callid
) {
2648 sipe_im_process_queue(sip
, session
);
2649 } else if (!session
->outgoing_invite
) {
2650 // Need to send the INVITE to get the outgoing dialog setup
2651 sipe_invite(sip
, session
, text
);
2659 /* End IM Session (INVITE and MESSAGE methods) */
2662 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
2664 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2665 struct sip_im_session
*session
;
2667 if (state
== PURPLE_NOT_TYPING
)
2670 session
= find_im_session(sip
, who
);
2672 if (session
&& session
->dialog
) {
2673 send_sip_request(gc
, "INFO", who
, who
,
2674 "Content-Type: application/xml\r\n",
2675 SIPE_SEND_TYPING
, session
->dialog
, NULL
);
2678 return SIPE_TYPING_SEND_TIMEOUT
;
2681 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
2683 GSList
*tmp
= sip
->transactions
;
2684 time_t currtime
= time(NULL
);
2686 struct transaction
*trans
= tmp
->data
;
2688 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime
-trans
->time
);
2689 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
2692 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
2694 sendout_sipmsg(sip
, trans
->msg
);
2701 static void do_reauthenticate_cb(struct sipe_account_data
*sip
)
2703 /* register again when security token expires */
2704 /* we have to start a new authentication as the security token
2705 * is almost expired by sending a not signed REGISTER message */
2706 purple_debug_info("sipe", "do a full reauthentication\n");
2707 sipe_auth_free(&sip
->registrar
);
2708 sip
->registerstatus
= 0;
2710 sip
->reauthenticate_set
= FALSE
;
2713 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2717 gboolean found
= FALSE
;
2719 from
= parse_from(sipmsg_find_header(msg
, "From"));
2723 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
2725 contenttype
= sipmsg_find_header(msg
, "Content-Type");
2726 if (!contenttype
|| !strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
2727 gchar
*msgr
= sipmsg_find_part_of_header(contenttype
, "msgr=", NULL
, NULL
);
2728 gchar
*x_mms_im_format
= sipmsg_get_x_mms_im_format(msgr
);
2730 gchar
*body_esc
= g_markup_escape_text(msg
->body
, -1);
2731 gchar
*body_html
= sipmsg_apply_x_mms_im_format(x_mms_im_format
, body_esc
);
2734 g_free(x_mms_im_format
);
2736 serv_got_im(sip
->gc
, from
, body_html
, 0, time(NULL
));
2738 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2740 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
2741 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2746 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2750 state
= xmlnode_get_child(isc
, "state");
2753 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2758 statedata
= xmlnode_get_data(state
);
2760 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
2761 else serv_got_typing_stopped(sip
->gc
, from
);
2766 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2770 purple_debug_info("sipe", "got unknown mime-type");
2771 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
2776 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2778 gchar
*ms_text_format
;
2780 struct sip_im_session
*session
;
2781 // Only accept text invitations
2782 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
2783 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
2787 from
= parse_from(sipmsg_find_header(msg
, "From"));
2788 session
= find_or_create_im_session (sip
, from
);
2790 if (session
->dialog
) {
2791 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
2793 session
->dialog
= g_new0(struct sip_dialog
, 1);
2795 sipe_parse_dialog(msg
, session
->dialog
, FALSE
);
2797 session
->dialog
->callid
= sipmsg_find_header(msg
, "Call-ID");
2798 session
->dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, "To"));
2799 session
->dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, "From"));
2800 session
->dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "From"), "epid=", NULL
, NULL
);
2803 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
2806 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
2807 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
2808 if (ms_text_format
&& !strncmp(ms_text_format
, "text/plain", 10)) {
2809 gchar
*msgr
= sipmsg_find_part_of_header(ms_text_format
, "msgr=", ";", NULL
);
2810 gchar
*x_mms_im_format
= sipmsg_get_x_mms_im_format(msgr
);
2812 gchar
*ms_body
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
2815 gchar
*body
= purple_base64_decode(ms_body
, NULL
);
2816 gchar
*body_esc
= g_markup_escape_text(body
, -1);
2817 gchar
*body_html
= sipmsg_apply_x_mms_im_format(x_mms_im_format
, body_esc
);
2821 serv_got_im(sip
->gc
, from
, body_html
, 0, time(NULL
));
2823 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message reciept
2825 g_free(x_mms_im_format
);
2829 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
2830 sipmsg_remove_header(msg
, "Ms-Text-Format");
2831 sipmsg_remove_header(msg
, "EndPoints");
2832 sipmsg_remove_header(msg
, "User-Agent");
2833 sipmsg_remove_header(msg
, "Roster-Manager");
2835 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
2836 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
2838 send_sip_response(sip
->gc
, msg
, 200, "OK", g_strdup_printf(
2840 "o=- 0 0 IN IP4 %s\r\n"
2844 "m=message %d sip sip:%s\r\n"
2845 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
2846 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
2847 sip
->realport
, sip
->username
));
2850 static void sipe_connection_cleanup(struct sipe_account_data
*);
2851 static void create_connection(struct sipe_account_data
*, gchar
*, int);
2853 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
2857 const gchar
*expires_header
;
2860 expires_header
= sipmsg_find_header(msg
, "Expires");
2861 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
2862 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
2864 switch (msg
->response
) {
2867 sip
->registerstatus
= 0;
2870 gchar
*contact_hdr
= NULL
;
2875 sip
->registerexpire
= expires
;
2877 if (!sip
->reregister_set
) {
2878 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
2879 sipe_schedule_action(action_name
, expires
, (Action
) do_register_cb
, sip
, NULL
);
2880 g_free(action_name
);
2881 sip
->reregister_set
= TRUE
;
2884 sip
->registerstatus
= 3;
2886 if (!sip
->reauthenticate_set
) {
2887 /* we have to reauthenticate as our security token expires
2888 after eight hours (be five minutes early) */
2889 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
2890 guint reauth_timeout
= (8 * 3600) - 360;
2891 sipe_schedule_action(action_name
, reauth_timeout
, (Action
) do_reauthenticate_cb
, sip
, NULL
);
2892 g_free(action_name
);
2893 sip
->reauthenticate_set
= TRUE
;
2896 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
2899 uuid
= generateUUIDfromEPID(epid
);
2902 // There can be multiple Contact headers (one per location where the user is logged in) so
2903 // make sure to only get the one for this uuid
2904 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
2905 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
2906 if (valid_contact
) {
2907 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
2908 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
2909 g_free(valid_contact
);
2912 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
2918 sip
->contact
= g_strdup_printf("<%s>", gruu
);
2921 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
2922 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
);
2925 if (!sip
->subscribed
) { //do it just once, not every re-register
2926 tmp
= sipmsg_find_header(msg
, "Allow-Events");
2927 if (tmp
&& strstr(tmp
, "vnd-microsoft-provisioning")){
2928 sipe_subscribe_buddylist(sip
, msg
);
2930 sipe_subscribe_acl(sip
, msg
);
2931 sipe_subscribe_roaming_self(sip
, msg
);
2932 sipe_subscribe_roaming_provisioning(sip
, msg
);
2933 sipe_subscribe_pending_buddies(sip
, msg
);
2934 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
2935 sip
->subscribed
= TRUE
;
2938 if (purple_account_get_bool(sip
->account
, "clientkeepalive", FALSE
)) {
2939 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Setting user defined keepalive\n");
2940 sip
->keepalive_timeout
= purple_account_get_int(sip
->account
, "keepalive", 0);
2942 tmp
= sipmsg_find_header(msg
, "ms-keep-alive");
2944 sipe_keep_alive_timeout(sip
, tmp
);
2948 // Should we remove the transaction here?
2949 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
2950 transactions_remove(sip
, tc
);
2955 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
2957 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
2958 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
2962 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
2965 tmp
= g_strsplit(parts
[0], ":", 0);
2966 hostname
= g_strdup(tmp
[0]);
2967 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
2971 tmp
= g_strsplit(parts
[i
], "=", 0);
2973 if (g_strcasecmp("transport", tmp
[0]) == 0) {
2974 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
2975 transport
= SIPE_TRANSPORT_TCP
;
2976 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
2977 transport
= SIPE_TRANSPORT_UDP
;
2986 /* Close old connection */
2987 sipe_connection_cleanup(sip
);
2989 /* Create new connection */
2990 sip
->transport
= transport
;
2991 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
2992 hostname
, port
, TRANSPORT_DESCRIPTOR
);
2993 create_connection(sip
, hostname
, port
);
2998 if (sip
->registerstatus
!= 2) {
2999 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
3000 if (sip
->registrar
.retries
> 3) {
3001 sip
->gc
->wants_to_die
= TRUE
;
3002 purple_connection_error(sip
->gc
, _("Wrong Password"));
3005 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3006 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3008 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3010 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3011 fill_auth(sip
, tmp
, &sip
->registrar
);
3012 sip
->registerstatus
= 2;
3013 if (sip
->account
->disconnecting
) {
3014 do_register_exp(sip
, 0);
3022 const gchar
*warning
= sipmsg_find_header(msg
, "Warning");
3023 if (warning
!= NULL
) {
3025 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3027 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
3028 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
3031 warning
= _("You have been rejected by the server");
3034 sip
->gc
->wants_to_die
= TRUE
;
3035 purple_connection_error(sip
->gc
, warning
);
3041 const gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3042 if (warning
!= NULL
) {
3043 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3044 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
3047 warning
= _("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator");
3050 sip
->gc
->wants_to_die
= TRUE
;
3051 purple_connection_error(sip
->gc
, warning
);
3057 const gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3058 if (warning
!= NULL
) {
3059 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3060 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
3063 warning
= _("Service unavailable: no reason given");
3066 sip
->gc
->wants_to_die
= TRUE
;
3067 purple_connection_error(sip
->gc
, warning
);
3075 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3078 xmlnode
*xn_categories
;
3079 xmlnode
*xn_category
;
3082 const char *activity
= NULL
;
3084 xn_categories
= xmlnode_from_str(data
, len
);
3085 uri
= xmlnode_get_attrib(xn_categories
, "uri");
3087 purple_debug_info("sipe", "process_incoming_notify_rlmi\n");
3089 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
3091 xn_category
= xmlnode_get_next_twin(xn_category
) )
3093 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
3095 if (!strcmp(attrVar
, "note"))
3098 struct sipe_buddy
*sbuddy
;
3099 xn_node
= xmlnode_get_child(xn_category
, "note");
3100 if (!xn_node
) continue;
3101 xn_node
= xmlnode_get_child(xn_node
, "body");
3102 if (!xn_node
) continue;
3104 note
= xmlnode_get_data(xn_node
);
3106 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3109 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3110 sbuddy
->annotation
= g_strdup(note
);
3116 else if(!strcmp(attrVar
, "state"))
3120 xn_node
= xmlnode_get_child(xn_category
, "state");
3121 if (!xn_node
) continue;
3122 xn_node
= xmlnode_get_child(xn_node
, "availability");
3123 if (!xn_node
) continue;
3125 data
= xmlnode_get_data(xn_node
);
3130 activity
= "unknown";
3131 else if (avail
< 4500)
3132 activity
= "available";
3133 else if (avail
< 6000)
3135 else if (avail
< 7500)
3137 else if (avail
< 9000)
3139 else if (avail
< 12000)
3141 else if (avail
< 18000)
3144 activity
= "offline";
3151 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
3154 xmlnode_free(xn_categories
);
3157 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3160 gchar
*getbasic
= g_strdup("closed");
3161 gchar
*activity
= g_strdup("available");
3163 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
3164 gboolean isonline
= FALSE
;
3165 xmlnode
*display_name_node
;
3167 pidf
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3169 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",msg
->body
);
3173 uri
= xmlnode_get_attrib(pidf
, "entity");
3175 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
3177 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3178 basicstatus
= xmlnode_get_child(status
, "basic");
3183 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3188 getbasic
= xmlnode_get_data(basicstatus
);
3190 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3195 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
3196 if (strstr(getbasic
, "open")) {
3200 display_name_node
= xmlnode_get_child(pidf
, "display-name");
3201 // updating display name if alias was just URI
3202 if (display_name_node
) {
3203 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3204 GSList
*entry
= buddies
;
3205 PurpleBuddy
*p_buddy
;
3206 char * display_name
= xmlnode_get_data(display_name_node
);
3209 const char *server_alias
;
3212 p_buddy
= entry
->data
;
3214 alias
= (char *)purple_buddy_get_alias(p_buddy
);
3215 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
3216 if (!g_ascii_strcasecmp(uri
, alias
)) {
3217 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3218 purple_blist_alias_buddy(p_buddy
, display_name
);
3222 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3224 ( (server_alias
&& strcmp(display_name
, server_alias
))
3225 || !server_alias
|| strlen(server_alias
) == 0 )
3227 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3230 entry
= entry
->next
;
3234 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
3235 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3236 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
3237 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
3238 activity
= xmlnode_get_data(basicstatus
);
3244 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
3247 gchar
* status_id
= NULL
;
3249 if (strstr(activity
, "busy")) {
3251 } else if (strstr(activity
, "away")) {
3257 status_id
= "available";
3260 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
3261 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
3263 purple_prpl_got_user_status(sip
->account
, uri
, "offline", NULL
);
3271 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3273 const char *availability
;
3274 const char *activity
;
3275 const char *display_name
= NULL
;
3276 const char *activity_name
;
3281 struct sipe_buddy
*sbuddy
;
3283 xmlnode
*xn_presentity
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3285 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
3286 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
3287 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
3288 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
3289 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
3290 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
3291 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
3292 const char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
3293 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
3294 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
3295 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
3296 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
3298 name
= xmlnode_get_attrib(xn_presentity
, "uri");
3299 uri
= g_strdup_printf("sip:%s", name
);
3300 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
3301 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
3303 // updating display name if alias was just URI
3304 if (xn_display_name
) {
3305 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3306 GSList
*entry
= buddies
;
3307 PurpleBuddy
*p_buddy
;
3308 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
3311 const char *email_str
, *server_alias
;
3313 p_buddy
= entry
->data
;
3315 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
3316 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3317 purple_blist_alias_buddy(p_buddy
, display_name
);
3320 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3322 ( (server_alias
&& strcmp(display_name
, server_alias
))
3323 || !server_alias
|| strlen(server_alias
) == 0 )
3325 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3329 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
3330 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
3331 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
3335 entry
= entry
->next
;
3339 avl
= atoi(availability
);
3340 act
= atoi(activity
);
3343 activity_name
= "away";
3344 else if (act
<= 150)
3345 activity_name
= "out-to-lunch";
3346 else if (act
<= 300)
3347 activity_name
= "be-right-back";
3348 else if (act
<= 400)
3349 activity_name
= "available";
3350 else if (act
<= 500)
3351 activity_name
= "on-the-phone";
3352 else if (act
<= 600)
3353 activity_name
= "busy";
3355 activity_name
= "available";
3358 activity_name
= "offline";
3360 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3363 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3364 sbuddy
->annotation
= NULL
;
3365 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
3367 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
3368 sbuddy
->device_name
= NULL
;
3369 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
3372 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
3373 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
3374 xmlnode_free(xn_presentity
);
3378 static void process_incoming_notify_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3380 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3382 purple_debug_info("sipe", "process_incoming_notify_presence: Content-Type: %s\n", ctype
? ctype
: "");
3384 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
3385 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
3387 const char *content
= msg
->body
;
3388 unsigned length
= msg
->bodylen
;
3389 PurpleMimeDocument
*mime
= NULL
;
3390 //char *subscription_state;
3392 if (strstr(ctype
, "multipart"))
3394 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3396 mime
= purple_mime_document_parse(doc
);
3397 parts
= purple_mime_document_get_parts(mime
);
3398 content
= purple_mime_part_get_data(parts
->data
);
3399 length
= purple_mime_part_get_length(parts
->data
);
3403 process_incoming_notify_rlmi(sip
, content
, length
);
3407 purple_mime_document_free(mime
);
3410 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
3412 process_incoming_notify_msrtc(sip
, msg
);
3416 process_incoming_notify_pidf(sip
, msg
);
3421 * Dispatcher for all incoming subscription information
3422 * whether it comes from NOTIFY, BENOTIFY requests or
3423 * piggy-backed to subscription's OK responce.
3425 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3426 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3428 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
3430 gchar
*event
= sipmsg_find_header(msg
, "Event");
3431 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
3432 const char *uri
,*state
;
3434 xmlnode
*xn_resource
;
3435 xmlnode
*xn_instance
;
3439 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
3440 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
3444 const gchar
*expires_header
;
3445 expires_header
= sipmsg_find_header(msg
, "Expires");
3446 expires
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
3447 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", expires
);
3450 if (!subscription_state
|| strstr(subscription_state
, "active"))
3452 if (event
&& strstr(event
, "presence"))
3454 process_incoming_notify_presence(sip
, msg
);
3456 else if (event
&& strstr(event
, "vnd-microsoft-roaming-contacts"))
3458 sipe_process_roaming_contacts(sip
, msg
, NULL
);
3460 else if (event
&& strstr(event
, "vnd-microsoft-roaming-self"))
3462 sipe_process_roaming_self(sip
, msg
);
3464 else if (event
&& strstr(event
, "vnd-microsoft-roaming-ACL"))
3466 sipe_process_roaming_acl(sip
, msg
);
3468 else if (event
&& strstr(event
, "presence.wpending"))
3470 sipe_process_incoming_pending(sip
, msg
);
3474 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
3478 //Subscription terminated and is not a (BE)NOTIFY; we need resub
3479 if ( !request
&& !benotify
&& subscription_state
&& strstr(subscription_state
, "terminated"))
3481 if(event
&& strstr(event
, "presence")){
3483 const char *content
= msg
->body
;
3484 unsigned length
= msg
->bodylen
;
3485 PurpleMimeDocument
*mime
= NULL
;
3486 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3488 purple_debug_info("sipe", "process_incoming_notify: ctype(%s)\n",ctype
);
3490 if (ctype
&& strstr(ctype
, "multipart"))
3492 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3494 mime
= purple_mime_document_parse(doc
);
3495 parts
= purple_mime_document_get_parts(mime
);
3496 content
= purple_mime_part_get_data(parts
->data
);
3497 length
= purple_mime_part_get_length(parts
->data
);
3501 xn_list
= xmlnode_from_str(content
, length
);
3502 xn_resource
= xmlnode_get_child(xn_list
, "resource");
3503 if (!xn_resource
) return;
3504 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
3505 if (!xn_instance
) return;
3506 state
= xmlnode_get_attrib(xn_instance
, "state");
3507 uri
= xmlnode_get_attrib(xn_instance
, "cid");
3509 purple_debug_info("sipe", "process_incoming_notify: cid(%s),state(%s)\n",uri
,state
);
3511 if(strstr(state
, "resubscribe"))
3513 int timeout
= expires
? expires
: 3;
3514 gchar
*action_name
= g_strdup_printf("<%s><%s>", "presence", uri
);
3515 sipe_cancel_scheduled_action(sip
, action_name
);
3516 purple_debug_info("sipe", "process_incoming_notify: Subscription to buddy %s was terminated. Resubscribing\n", uri
);
3517 sipe_schedule_action(action_name
, timeout
, (Action
) sipe_subscribe_to_name_single
, sip
, g_strdup(uri
));
3518 g_free(action_name
);
3523 purple_debug_info("sipe", "Unable to process subscription termination. Event is not supported:%s\n",
3524 event
? event
: "");
3528 //The server sends a (BE)NOTIFY with the status 'terminated'
3529 if(request
&& subscription_state
&& strstr(subscription_state
, "terminated") )
3531 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3532 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
3536 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3537 if (request
&& !benotify
)
3539 sipmsg_remove_header(msg
, "Expires");
3540 sipmsg_remove_header(msg
, "subscription-state");
3541 sipmsg_remove_header(msg
, "Event");
3542 sipmsg_remove_header(msg
, "Require");
3543 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3550 static gchar* gen_xpidf(struct sipe_account_data *sip)
3552 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3554 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3555 "<display name=\"sip:%s\"/>\r\n"
3556 "<atom id=\"1234\">\r\n"
3557 "<address uri=\"sip:%s\">\r\n"
3558 "<status status=\"%s\"/>\r\n"
3571 static gchar* gen_pidf(struct sipe_account_data *sip)
3573 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3574 "<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"
3575 "<tuple id=\"0\">\r\n"
3577 "<basic>open</basic>\r\n"
3578 "<ep:activities>\r\n"
3579 " <ep:activity>%s</ep:activity>\r\n"
3583 "<ci:display-name>%s</ci:display-name>\r\n"
3593 process_send_presence_info_v0_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3595 if (msg
->response
== 488) {
3596 sip
->presence_method_version
= 1;
3597 send_presence_info(sip
);
3602 static void send_presence_info_v0(struct sipe_account_data
*sip
, char * note
)
3604 int availability
= 300; // online
3605 int activity
= 400; // Available
3608 if (!strcmp(sip
->status
, "away")) {
3610 } else if (!strcmp(sip
->status
, "out-to-lunch")) {
3612 } else if (!strcmp(sip
->status
, "be-right-back")) {
3614 } else if (!strcmp(sip
->status
, "on-the-phone")) {
3616 } else if (!strcmp(sip
->status
, "do-not-disturb")) {
3618 } else if (!strcmp(sip
->status
, "busy")) {
3620 } else if (!strcmp(sip
->status
, "invisible")) {
3621 availability
= 0; // offline
3625 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
3626 //@TODO: send user data - state; add hostname in upper case
3627 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
3628 send_soap_request_with_cb(sip
, body
, process_send_presence_info_v0_response
, NULL
);
3634 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3636 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3637 if (msg
->response
== 200) {
3638 sip
->status_version
= 0;
3639 send_presence_info(sip
);
3645 process_send_presence_info_v1_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3647 if (msg
->response
== 409) {
3648 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3649 // TODO need to parse the version #'s?
3650 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
3651 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
3655 purple_debug_info("sipe", "process_send_presence_info_v1_response = %s\n", msg
->body
);
3657 tmp
= get_contact(sip
);
3658 hdr
= g_strdup_printf("Contact: %s\r\n"
3659 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
3661 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
3671 static void send_presence_info_v1(struct sipe_account_data
*sip
, char * note
)
3678 if (!strcmp(sip
->status
, "away")) {
3680 } else if (!strcmp(sip
->status
, "busy")) {
3687 uri
= g_strdup_printf("sip:%s", sip
->username
);
3688 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
3689 sip
->status_version
, code
,
3690 sip
->status_version
, code
,
3691 sip
->status_version
, note
? note
: "",
3692 sip
->status_version
, note
? note
: "",
3693 sip
->status_version
, note
? note
: ""
3695 sip
->status_version
++;
3697 tmp
= get_contact(sip
);
3698 hdr
= g_strdup_printf("Contact: %s\r\n"
3699 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
3701 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_info_v1_response
);
3709 static void send_presence_info(struct sipe_account_data
*sip
)
3711 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
3713 if (!status
) return;
3715 note
= g_strdup(purple_status_get_attr_string(status
, "message"));
3717 purple_debug_info("sipe", "sending presence info, version = %d\n", sip
->presence_method_version
);
3718 if (sip
->presence_method_version
!= 1) {
3719 send_presence_info_v0(sip
, note
);
3721 send_presence_info_v1(sip
, note
);
3725 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
3727 gboolean found
= FALSE
;
3728 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
3729 if (msg
->response
== 0) { /* request */
3730 if (!strcmp(msg
->method
, "MESSAGE")) {
3731 process_incoming_message(sip
, msg
);
3733 } else if (!strcmp(msg
->method
, "NOTIFY")) {
3734 purple_debug_info("sipe","send->process_incoming_notify\n");
3735 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
3737 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
3738 purple_debug_info("sipe","send->process_incoming_benotify\n");
3739 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
3741 } else if (!strcmp(msg
->method
, "INVITE")) {
3742 process_incoming_invite(sip
, msg
);
3744 } else if (!strcmp(msg
->method
, "INFO")) {
3746 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3748 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
3751 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3753 } else if (!strcmp(msg
->method
, "ACK")) {
3754 // ACK's don't need any response
3756 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
3757 // LCS 2005 sends us these - just respond 200 OK
3759 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3760 } else if (!strcmp(msg
->method
, "BYE")) {
3761 struct sip_im_session
*session
;
3763 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3765 from
= parse_from(sipmsg_find_header(msg
, "From"));
3766 session
= find_im_session (sip
, from
);
3770 // TODO Let the user know the other user left the conversation?
3771 im_session_destroy(sip
, session
);
3776 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3778 } else { /* response */
3779 struct transaction
*trans
= transactions_find(sip
, msg
);
3781 if (msg
->response
== 407) {
3782 gchar
*resend
, *auth
, *ptmp
;
3784 if (sip
->proxy
.retries
> 30) return;
3785 sip
->proxy
.retries
++;
3786 /* do proxy authentication */
3788 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
3790 fill_auth(sip
, ptmp
, &sip
->proxy
);
3791 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
3792 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
3793 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
3795 resend
= sipmsg_to_string(trans
->msg
);
3796 /* resend request */
3797 sendout_pkt(sip
->gc
, resend
);
3800 if (msg
->response
== 100 || msg
->response
== 180) {
3801 /* ignore provisional response */
3802 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
3804 sip
->proxy
.retries
= 0;
3805 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
3806 if (msg
->response
== 401)
3808 sip
->registrar
.retries
++;
3809 sip
->registrar
.expires
= 0;
3813 sip
->registrar
.retries
= 0;
3815 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
3817 if (msg
->response
== 401) {
3818 gchar
*resend
, *auth
, *ptmp
;
3820 if (sip
->registrar
.retries
> 4) return;
3821 sip
->registrar
.retries
++;
3823 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3824 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
3826 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3829 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
3831 fill_auth(sip
, ptmp
, &sip
->registrar
);
3832 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
3833 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
3834 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
3836 //sipmsg_remove_header(trans->msg, "Authorization");
3837 //sipmsg_add_header(trans->msg, "Authorization", auth);
3839 resend
= sipmsg_to_string(trans
->msg
);
3840 /* resend request */
3841 sendout_pkt(sip
->gc
, resend
);
3846 if (trans
->callback
) {
3847 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
3848 /* call the callback to process response*/
3849 (trans
->callback
)(sip
, msg
, trans
);
3851 /* Not sure if this is needed or what needs to be done
3852 but transactions seem to be removed prematurely so
3853 this only removes them if the response is 200 OK */
3854 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
3855 /*Has a bug and it's unneccesary*/
3856 /*transactions_remove(sip, trans);*/
3862 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction");
3866 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
3870 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
3878 /* according to the RFC remove CRLF at the beginning */
3879 while (*cur
== '\r' || *cur
== '\n') {
3882 if (cur
!= conn
->inbuf
) {
3883 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
3884 conn
->inbufused
= strlen(conn
->inbuf
);
3887 /* Received a full Header? */
3888 sip
->processing_input
= TRUE
;
3889 while (sip
->processing_input
&&
3890 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
3891 time_t currtime
= time(NULL
);
3894 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
3895 msg
= sipmsg_parse_header(conn
->inbuf
);
3898 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
3899 if (restlen
>= msg
->bodylen
) {
3900 dummy
= g_malloc(msg
->bodylen
+ 1);
3901 memcpy(dummy
, cur
, msg
->bodylen
);
3902 dummy
[msg
->bodylen
] = '\0';
3904 cur
+= msg
->bodylen
;
3905 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
3906 conn
->inbufused
= strlen(conn
->inbuf
);
3908 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
3909 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
3915 purple_debug_info("sipe", "body:\n%s", msg->body);
3918 // Verify the signature before processing it
3919 if (sip
->registrar
.ntlm_key
) {
3920 struct sipmsg_breakdown msgbd
;
3921 gchar
*signature_input_str
;
3922 gchar
*signature
= NULL
;
3925 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
3926 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
3927 if (signature_input_str
!= NULL
) {
3928 signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
3930 g_free(signature_input_str
);
3932 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
3934 if (signature
!= NULL
) {
3935 if (rspauth
!= NULL
) {
3936 if (purple_ntlm_verify_signature (signature
, rspauth
)) {
3937 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
3938 process_input_message(sip
, msg
);
3940 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth
, signature
);
3941 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
3942 sip
->gc
->wants_to_die
= TRUE
;
3944 } else if (msg
->response
== 401) {
3945 purple_connection_error(sip
->gc
, _("Wrong Password"));
3946 sip
->gc
->wants_to_die
= TRUE
;
3952 sipmsg_breakdown_free(&msgbd
);
3954 process_input_message(sip
, msg
);
3961 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
3963 PurpleConnection
*gc
= data
;
3964 struct sipe_account_data
*sip
= gc
->proto_data
;
3969 static char buffer
[65536];
3970 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
3972 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
3973 msg
= sipmsg_parse_msg(buffer
);
3974 if (msg
) process_input_message(sip
, msg
);
3978 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
3980 struct sipe_account_data
*sip
= gc
->proto_data
;
3981 PurpleSslConnection
*gsc
= sip
->gsc
;
3983 purple_debug_error("sipe", "%s",debug
);
3984 purple_connection_error(gc
, msg
);
3986 /* Invalidate this connection. Next send will open a new one */
3988 connection_remove(sip
, gsc
->fd
);
3989 purple_ssl_close(gsc
);
3995 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
3997 PurpleConnection
*gc
= data
;
3998 struct sipe_account_data
*sip
;
3999 struct sip_connection
*conn
;
4001 gboolean firstread
= TRUE
;
4003 /* NOTE: This check *IS* necessary */
4004 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
4005 purple_ssl_close(gsc
);
4009 sip
= gc
->proto_data
;
4010 conn
= connection_find(sip
, gsc
->fd
);
4012 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4013 gc
->wants_to_die
= TRUE
;
4014 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
4018 /* Read all available data from the SSL connection */
4020 /* Increase input buffer size as needed */
4021 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4022 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4023 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4024 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
4027 /* Try to read as much as there is space left in the buffer */
4028 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
4029 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
4031 if (len
< 0 && errno
== EAGAIN
) {
4032 /* Try again later */
4034 } else if (len
< 0) {
4035 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
4037 } else if (firstread
&& (len
== 0)) {
4038 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
4042 conn
->inbufused
+= len
;
4045 /* Equivalence indicates that there is possibly more data to read */
4046 } while (len
== readlen
);
4048 conn
->inbuf
[conn
->inbufused
] = '\0';
4049 process_input(sip
, conn
);
4053 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4055 PurpleConnection
*gc
= data
;
4056 struct sipe_account_data
*sip
= gc
->proto_data
;
4058 struct sip_connection
*conn
= connection_find(sip
, source
);
4060 purple_debug_error("sipe", "Connection not found!\n");
4064 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4065 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4066 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4069 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
4071 if (len
< 0 && errno
== EAGAIN
)
4073 else if (len
<= 0) {
4074 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4075 connection_remove(sip
, source
);
4076 if (sip
->fd
== source
) sip
->fd
= -1;
4080 conn
->inbufused
+= len
;
4081 conn
->inbuf
[conn
->inbufused
] = '\0';
4083 process_input(sip
, conn
);
4086 /* Callback for new connections on incoming TCP port */
4087 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4089 PurpleConnection
*gc
= data
;
4090 struct sipe_account_data
*sip
= gc
->proto_data
;
4091 struct sip_connection
*conn
;
4093 int newfd
= accept(source
, NULL
, NULL
);
4095 conn
= connection_create(sip
, newfd
);
4097 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4100 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
4102 PurpleConnection
*gc
= data
;
4103 struct sipe_account_data
*sip
;
4104 struct sip_connection
*conn
;
4106 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4114 purple_connection_error(gc
, _("Could not connect"));
4118 sip
= gc
->proto_data
;
4120 sip
->last_keepalive
= time(NULL
);
4122 conn
= connection_create(sip
, source
);
4126 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4129 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4131 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
4132 if (sip
== NULL
) return;
4137 static guint
sipe_ht_hash_nick(const char *nick
)
4139 char *lc
= g_utf8_strdown(nick
, -1);
4140 guint bucket
= g_str_hash(lc
);
4146 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
4148 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
4151 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
4153 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4155 sip
->listen_data
= NULL
;
4157 if (listenfd
== -1) {
4158 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4164 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
4165 sip
->listenfd
= sip
->fd
;
4167 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
4169 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
4173 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
4175 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4178 sip
->query_data
= NULL
;
4180 if (!hosts
|| !hosts
->data
) {
4181 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
4185 addr_size
= GPOINTER_TO_INT(hosts
->data
);
4186 hosts
= g_slist_remove(hosts
, hosts
->data
);
4187 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
4188 g_free(hosts
->data
);
4189 hosts
= g_slist_remove(hosts
, hosts
->data
);
4191 hosts
= g_slist_remove(hosts
, hosts
->data
);
4192 g_free(hosts
->data
);
4193 hosts
= g_slist_remove(hosts
, hosts
->data
);
4196 /* create socket for incoming connections */
4197 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
4198 sipe_udp_host_resolved_listen_cb
, sip
);
4199 if (sip
->listen_data
== NULL
) {
4200 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4205 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
4208 PurpleConnection
*gc
= data
;
4209 struct sipe_account_data
*sip
;
4211 /* If the connection is already disconnected, we don't need to do anything else */
4212 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4215 sip
= gc
->proto_data
;
4220 case PURPLE_SSL_CONNECT_FAILED
:
4221 purple_connection_error(gc
, _("Connection Failed"));
4223 case PURPLE_SSL_HANDSHAKE_FAILED
:
4224 purple_connection_error(gc
, _("SSL Handshake Failed"));
4226 case PURPLE_SSL_CERTIFICATE_INVALID
:
4227 purple_connection_error(gc
, _("SSL Certificate Invalid"));
4233 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
4235 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4236 PurpleProxyConnectData
*connect_data
;
4238 sip
->listen_data
= NULL
;
4240 sip
->listenfd
= listenfd
;
4241 if (sip
->listenfd
== -1) {
4242 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4246 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
4247 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4248 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
4249 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
4250 sipe_newconn_cb
, sip
->gc
);
4251 purple_debug_info("sipe", "connecting to %s port %d\n",
4252 sip
->realhostname
, sip
->realport
);
4253 /* open tcp connection to the server */
4254 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
4255 sip
->realport
, login_cb
, sip
->gc
);
4257 if (connect_data
== NULL
) {
4258 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
4263 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
4265 PurpleAccount
*account
= sip
->account
;
4266 PurpleConnection
*gc
= sip
->gc
;
4268 if (purple_account_get_bool(account
, "useport", FALSE
)) {
4269 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
4270 port
= purple_account_get_int(account
, "port", 0);
4272 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
4275 sip
->realhostname
= hostname
;
4276 sip
->realport
= port
;
4278 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
4281 /* TODO: is there a good default grow size? */
4282 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
4283 sip
->txbuf
= purple_circ_buffer_new(0);
4285 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
4287 if (!purple_ssl_is_supported()) {
4288 gc
->wants_to_die
= TRUE
;
4289 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4293 purple_debug_info("sipe", "using SSL\n");
4295 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
4296 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
4297 if (sip
->gsc
== NULL
) {
4298 purple_connection_error(gc
, _("Could not create SSL context"));
4301 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
4303 purple_debug_info("sipe", "using UDP\n");
4305 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
4306 if (sip
->query_data
== NULL
) {
4307 purple_connection_error(gc
, _("Could not resolve hostname"));
4311 purple_debug_info("sipe", "using TCP\n");
4312 /* create socket for incoming connections */
4313 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
4314 sipe_tcp_connect_listen_cb
, sip
);
4315 if (sip
->listen_data
== NULL
) {
4316 purple_connection_error(gc
, _("Could not create listen socket"));
4322 /* Service list for autodection */
4323 static const struct sipe_service_data service_autodetect
[] = {
4324 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4325 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4326 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4327 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4331 /* Service list for SSL/TLS */
4332 static const struct sipe_service_data service_tls
[] = {
4333 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4334 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4338 /* Service list for TCP */
4339 static const struct sipe_service_data service_tcp
[] = {
4340 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4341 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4345 /* Service list for UDP */
4346 static const struct sipe_service_data service_udp
[] = {
4347 { "sip", "udp", SIPE_TRANSPORT_UDP
},
4351 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
4352 static void resolve_next_service(struct sipe_account_data
*sip
,
4353 const struct sipe_service_data
*start
)
4356 sip
->service_data
= start
;
4358 sip
->service_data
++;
4359 if (sip
->service_data
->service
== NULL
) {
4361 /* Try connecting to the SIP hostname directly */
4362 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
4363 if (sip
->auto_transport
) {
4364 // If SSL is supported, default to using it; OCS servers aren't configured
4365 // by default to accept TCP
4366 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4367 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
4368 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
4371 hostname
= g_strdup(sip
->sipdomain
);
4372 create_connection(sip
, hostname
, 0);
4377 /* Try to resolve next service */
4378 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
4379 sip
->service_data
->transport
,
4384 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
4386 struct sipe_account_data
*sip
= data
;
4388 sip
->srv_query_data
= NULL
;
4390 /* find the host to connect to */
4392 gchar
*hostname
= g_strdup(resp
->hostname
);
4393 int port
= resp
->port
;
4394 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4398 sip
->transport
= sip
->service_data
->type
;
4400 create_connection(sip
, hostname
, port
);
4402 resolve_next_service(sip
, NULL
);
4406 static void sipe_login(PurpleAccount
*account
)
4408 PurpleConnection
*gc
;
4409 struct sipe_account_data
*sip
;
4410 gchar
**signinname_login
, **userserver
, **domain_user
;
4411 const char *transport
;
4413 const char *username
= purple_account_get_username(account
);
4414 gc
= purple_account_get_connection(account
);
4416 if (strpbrk(username
, " \t\v\r\n") != NULL
) {
4417 gc
->wants_to_die
= TRUE
;
4418 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
4422 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
4423 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
4424 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
4426 sip
->account
= account
;
4427 sip
->registerexpire
= 900;
4428 sip
->reregister_set
= FALSE
;
4429 sip
->reauthenticate_set
= FALSE
;
4430 sip
->subscribed
= FALSE
;
4431 sip
->subscribed_buddies
= FALSE
;
4433 signinname_login
= g_strsplit(username
, ",", 2);
4435 userserver
= g_strsplit(signinname_login
[0], "@", 2);
4436 purple_connection_set_display_name(gc
, userserver
[0]);
4437 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
4438 sip
->sipdomain
= g_strdup(userserver
[1]);
4440 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
4441 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
4442 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
4444 sip
->password
= g_strdup(purple_connection_get_password(gc
));
4446 g_strfreev(userserver
);
4447 g_strfreev(domain_user
);
4448 g_strfreev(signinname_login
);
4450 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
4452 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
4454 /* TODO: Set the status correctly. */
4455 sip
->status
= g_strdup("available");
4457 transport
= purple_account_get_string(account
, "transport", "auto");
4458 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
4459 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
4462 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
4463 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
4464 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
4465 } else if (strcmp(transport
, "auto") == 0) {
4466 sip
->auto_transport
= TRUE
;
4467 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
4468 } else if (strcmp(transport
, "tls") == 0) {
4469 resolve_next_service(sip
, service_tls
);
4470 } else if (strcmp(transport
, "tcp") == 0) {
4471 resolve_next_service(sip
, service_tcp
);
4473 resolve_next_service(sip
, service_udp
);
4477 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
4479 connection_free_all(sip
);
4481 if (sip
->query_data
!= NULL
)
4482 purple_dnsquery_destroy(sip
->query_data
);
4483 sip
->query_data
== NULL
;
4485 if (sip
->srv_query_data
!= NULL
)
4486 purple_srv_cancel(sip
->srv_query_data
);
4487 sip
->srv_query_data
= NULL
;
4489 if (sip
->listen_data
!= NULL
)
4490 purple_network_listen_cancel(sip
->listen_data
);
4491 sip
->listen_data
= NULL
;
4493 if (sip
->gsc
!= NULL
)
4494 purple_ssl_close(sip
->gsc
);
4497 sipe_auth_free(&sip
->registrar
);
4498 sipe_auth_free(&sip
->proxy
);
4501 purple_circ_buffer_destroy(sip
->txbuf
);
4504 g_free(sip
->realhostname
);
4505 sip
->realhostname
= NULL
;
4508 purple_input_remove(sip
->listenpa
);
4510 if (sip
->tx_handler
)
4511 purple_input_remove(sip
->tx_handler
);
4512 sip
->tx_handler
= 0;
4513 if (sip
->resendtimeout
)
4514 purple_timeout_remove(sip
->resendtimeout
);
4515 sip
->resendtimeout
= 0;
4516 if (sip
->timeouts
) {
4517 GSList
*entry
= sip
->timeouts
;
4519 struct scheduled_action
*sched_action
= entry
->data
;
4520 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
4521 purple_timeout_remove(sched_action
->timeout_handler
);
4522 g_free(sched_action
->payload
);
4523 g_free(sched_action
->name
);
4524 g_free(sched_action
);
4525 entry
= entry
->next
;
4528 g_slist_free(sip
->timeouts
);
4531 g_free(sip
->contact
);
4532 sip
->contact
= NULL
;
4534 g_free(sip
->regcallid
);
4535 sip
->regcallid
= NULL
;
4538 sip
->processing_input
= FALSE
;
4541 static void sipe_close(PurpleConnection
*gc
)
4543 struct sipe_account_data
*sip
= gc
->proto_data
;
4546 /* leave all conversations */
4547 im_session_close_all(sip
);
4550 do_register_exp(sip
, 0);
4552 sipe_connection_cleanup(sip
);
4553 g_free(sip
->sipdomain
);
4554 g_free(sip
->username
);
4555 g_free(sip
->password
);
4556 g_free(sip
->authdomain
);
4557 g_free(sip
->authuser
);
4559 g_free(gc
->proto_data
);
4560 gc
->proto_data
= NULL
;
4563 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4565 PurpleAccount
*acct
= purple_connection_get_account(gc
);
4566 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
4567 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
4569 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
4570 purple_conversation_present(conv
);
4574 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4577 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
4578 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
4581 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
4583 PurpleNotifySearchResults
*results
;
4584 PurpleNotifySearchColumn
*column
;
4585 xmlnode
*searchResults
;
4587 int match_count
= 0;
4588 gboolean more
= FALSE
;
4591 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4592 if (!searchResults
) {
4593 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4597 results
= purple_notify_searchresults_new();
4599 if (results
== NULL
) {
4600 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4601 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
4603 xmlnode_free(searchResults
);
4607 column
= purple_notify_searchresults_column_new(_("User Name"));
4608 purple_notify_searchresults_column_add(results
, column
);
4610 column
= purple_notify_searchresults_column_new(_("Name"));
4611 purple_notify_searchresults_column_add(results
, column
);
4613 column
= purple_notify_searchresults_column_new(_("Company"));
4614 purple_notify_searchresults_column_add(results
, column
);
4616 column
= purple_notify_searchresults_column_new(_("Country"));
4617 purple_notify_searchresults_column_add(results
, column
);
4619 column
= purple_notify_searchresults_column_new(_("Email"));
4620 purple_notify_searchresults_column_add(results
, column
);
4622 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
4625 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
4626 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
4627 g_strfreev(uri_parts
);
4629 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
4630 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
4631 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
4632 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
4634 purple_notify_searchresults_row_add(results
, row
);
4638 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
4639 char *data
= xmlnode_get_data_unescaped(mrow
);
4640 more
= (g_strcasecmp(data
, "true") == 0);
4644 secondary
= g_strdup_printf(
4645 dngettext(GETTEXT_PACKAGE
,
4646 "Found %d contact%s:",
4647 "Found %d contacts%s:", match_count
),
4648 match_count
, more
? _(" (more matched your query)") : "");
4650 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
4651 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
4652 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
4655 xmlnode_free(searchResults
);
4659 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
4661 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
4662 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
4666 PurpleRequestField
*field
= entries
->data
;
4667 const char *id
= purple_request_field_get_id(field
);
4668 const char *value
= purple_request_field_string_get_value(field
);
4670 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
4672 if (value
!= NULL
) attrs
[i
++] = g_strdup_printf(SIPE_SOAP_SEARCH_ROW
, id
, value
);
4673 } while ((entries
= g_list_next(entries
)) != NULL
);
4677 gchar
*query
= g_strjoinv(NULL
, attrs
);
4678 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
4679 send_soap_request_with_cb(gc
->proto_data
, body
,
4680 (TransCallback
) process_search_contact_response
, NULL
);
4688 static void sipe_show_find_contact(PurplePluginAction
*action
)
4690 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
4691 PurpleRequestFields
*fields
;
4692 PurpleRequestFieldGroup
*group
;
4693 PurpleRequestField
*field
;
4695 fields
= purple_request_fields_new();
4696 group
= purple_request_field_group_new(NULL
);
4697 purple_request_fields_add_group(fields
, group
);
4699 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
4700 purple_request_field_group_add_field(group
, field
);
4701 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
4702 purple_request_field_group_add_field(group
, field
);
4703 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
4704 purple_request_field_group_add_field(group
, field
);
4705 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
4706 purple_request_field_group_add_field(group
, field
);
4708 purple_request_fields(gc
,
4710 _("Search for a Contact"),
4711 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
4713 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
4715 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
4718 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
4721 PurplePluginAction
*act
;
4723 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
4724 menu
= g_list_prepend(menu
, act
);
4726 menu
= g_list_reverse(menu
);
4731 static void dummy_permit_deny(PurpleConnection
*gc
)
4735 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
4741 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
4747 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
4751 static char *sipe_status_text(PurpleBuddy
*buddy
)
4753 struct sipe_account_data
*sip
;
4754 struct sipe_buddy
*sbuddy
;
4757 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
4758 if (sip
) //happens on pidgin exit
4760 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
4761 if (sbuddy
&& sbuddy
->annotation
)
4763 text
= g_strdup(sbuddy
->annotation
);
4770 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
4772 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
4773 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
4774 struct sipe_account_data
*sip
;
4775 struct sipe_buddy
*sbuddy
;
4776 char *annotation
= NULL
;
4778 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
4779 if (sip
) //happens on pidgin exit
4781 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
4784 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
4789 if (purple_presence_is_online(presence
))
4791 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
4796 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
4803 sipe_get_account_text_table(PurpleAccount
*account
)
4806 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
4807 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
4811 static PurpleBuddy
*
4812 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
4815 const gchar
*server_alias
, *email
;
4816 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
4818 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
4820 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
4822 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
4824 purple_blist_server_alias_buddy(clone
, server_alias
);
4827 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
4829 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
4832 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
4834 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
4839 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
4841 PurpleBuddy
*buddy
, *b
;
4842 PurpleConnection
*gc
;
4843 PurpleGroup
* group
= purple_find_group(group_name
);
4845 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
4847 buddy
= (PurpleBuddy
*)node
;
4849 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
4850 gc
= purple_account_get_connection(buddy
->account
);
4852 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
4854 b
= purple_blist_add_buddy_clone(group
, buddy
);
4857 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
4861 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
4864 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
4866 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
4869 char *mailto
= g_strdup_printf("mailto:%s", email
);
4870 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
4874 char *const parmList
[] = {mailto
, NULL
};
4875 if ((pid
= fork()) == -1)
4877 purple_debug_info("sipe", "fork() error\n");
4881 execvp("xdg-email", parmList
);
4882 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
4890 //@TODO resolve env variable %WINDIR% first
4891 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
4894 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
4903 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
4908 * A menu which appear when right-clicking on buddy in contact list.
4911 sipe_buddy_menu(PurpleBuddy
*buddy
)
4913 PurpleBlistNode
*g_node
;
4914 PurpleGroup
*group
, *gr_parent
;
4915 PurpleMenuAction
*act
;
4917 GList
*menu_groups
= NULL
;
4919 act
= purple_menu_action_new(_("Send Email..."),
4920 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
4922 menu
= g_list_prepend(menu
, act
);
4924 gr_parent
= purple_buddy_get_group(buddy
);
4925 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
4926 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
4929 group
= (PurpleGroup
*)g_node
;
4930 if (group
== gr_parent
)
4933 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
4936 act
= purple_menu_action_new(purple_group_get_name(group
),
4937 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
4939 menu_groups
= g_list_prepend(menu_groups
, act
);
4941 menu_groups
= g_list_reverse(menu_groups
);
4943 act
= purple_menu_action_new(_("Copy to"),
4946 menu
= g_list_prepend(menu
, act
);
4947 menu
= g_list_reverse(menu
);
4952 GList
*sipe_blist_node_menu(PurpleBlistNode
*node
) {
4953 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
4954 return sipe_buddy_menu((PurpleBuddy
*) node
);
4960 static void sipe_get_info(PurpleConnection
*gc
, const char *username
) {
4961 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
4962 PurpleBuddy
*pbuddy
;
4963 struct sipe_account_data
*sip
;
4964 struct sipe_buddy
*sbuddy
;
4966 const char *device_name
= NULL
;
4968 const char *server_alias
;
4970 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
,
4971 gc
->account
->username
);
4973 pbuddy
= purple_find_buddy(gc
->account
, username
);
4974 alias
= purple_buddy_get_local_alias(pbuddy
);
4975 server_alias
= purple_buddy_get_server_alias(pbuddy
);
4977 sip
= (struct sipe_account_data
*)gc
->proto_data
;
4980 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
4983 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
4990 purple_notify_user_info_add_pair(info
, _("Nickname"), server_alias
);
4993 // same as server alias, do not present
4994 alias
= alias
&& server_alias
&& !strcmp(alias
, server_alias
) ? NULL
: alias
;
4997 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
5000 email
= purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email");
5003 purple_notify_user_info_add_pair(info
, _("Email"), email
);
5008 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
5011 /* show a buddy's user info in a nice dialog box */
5012 purple_notify_userinfo(gc
, /* connection the buddy info came through */
5013 username
, /* buddy's username */
5015 NULL
, /* callback called when dialog closed */
5016 NULL
); /* userdata for callback */
5019 static PurplePlugin
*my_protocol
= NULL
;
5021 static PurplePluginProtocolInfo prpl_info
=
5024 NULL
, /* user_splits */
5025 NULL
, /* protocol_options */
5026 NO_BUDDY_ICONS
, /* icon_spec */
5027 sipe_list_icon
, /* list_icon */
5028 NULL
, /* list_emblems */
5029 sipe_status_text
, /* status_text */
5030 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
5031 sipe_status_types
, /* away_states */
5032 sipe_blist_node_menu
, /* blist_node_menu */
5033 NULL
, /* chat_info */
5034 NULL
, /* chat_info_defaults */
5035 sipe_login
, /* login */
5036 sipe_close
, /* close */
5037 sipe_im_send
, /* send_im */
5038 NULL
, /* set_info */ // TODO maybe
5039 sipe_send_typing
, /* send_typing */
5040 sipe_get_info
, /* get_info */
5041 sipe_set_status
, /* set_status */
5042 NULL
, /* set_idle */
5043 NULL
, /* change_passwd */
5044 sipe_add_buddy
, /* add_buddy */
5045 NULL
, /* add_buddies */
5046 sipe_remove_buddy
, /* remove_buddy */
5047 NULL
, /* remove_buddies */
5048 sipe_add_permit
, /* add_permit */
5049 sipe_add_deny
, /* add_deny */
5050 sipe_add_deny
, /* rem_permit */
5051 sipe_add_permit
, /* rem_deny */
5052 dummy_permit_deny
, /* set_permit_deny */
5053 NULL
, /* join_chat */
5054 NULL
, /* reject_chat */
5055 NULL
, /* get_chat_name */
5056 NULL
, /* chat_invite */
5057 NULL
, /* chat_leave */
5058 NULL
, /* chat_whisper */
5059 NULL
, /* chat_send */
5060 sipe_keep_alive
, /* keepalive */
5061 NULL
, /* register_user */
5062 NULL
, /* get_cb_info */ // deprecated
5063 NULL
, /* get_cb_away */ // deprecated
5064 sipe_alias_buddy
, /* alias_buddy */
5065 sipe_group_buddy
, /* group_buddy */
5066 sipe_rename_group
, /* rename_group */
5067 NULL
, /* buddy_free */
5068 sipe_convo_closed
, /* convo_closed */
5069 purple_normalize_nocase
, /* normalize */
5070 NULL
, /* set_buddy_icon */
5071 sipe_remove_group
, /* remove_group */
5072 NULL
, /* get_cb_real_name */ // TODO?
5073 NULL
, /* set_chat_topic */
5074 NULL
, /* find_blist_chat */
5075 NULL
, /* roomlist_get_list */
5076 NULL
, /* roomlist_cancel */
5077 NULL
, /* roomlist_expand_category */
5078 NULL
, /* can_receive_file */
5079 NULL
, /* send_file */
5080 NULL
, /* new_xfer */
5081 NULL
, /* offline_message */
5082 NULL
, /* whiteboard_prpl_ops */
5083 sipe_send_raw
, /* send_raw */
5084 NULL
, /* roomlist_room_serialize */
5085 NULL
, /* unregister_user */
5086 NULL
, /* send_attention */
5087 NULL
, /* get_attention_types */
5089 sizeof(PurplePluginProtocolInfo
), /* struct_size */
5090 sipe_get_account_text_table
, /* get_account_text_table */
5094 static PurplePluginInfo info
= {
5095 PURPLE_PLUGIN_MAGIC
,
5096 PURPLE_MAJOR_VERSION
,
5097 PURPLE_MINOR_VERSION
,
5098 PURPLE_PLUGIN_PROTOCOL
, /**< type */
5099 NULL
, /**< ui_requirement */
5101 NULL
, /**< dependencies */
5102 PURPLE_PRIORITY_DEFAULT
, /**< priority */
5103 "prpl-sipe", /**< id */
5104 "Microsoft LCS/OCS", /**< name */
5105 VERSION
, /**< version */
5106 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5107 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5108 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5109 "Gabriel Burt <gburt@novell.com>", /**< author */
5110 PURPLE_WEBSITE
, /**< homepage */
5111 sipe_plugin_load
, /**< load */
5112 sipe_plugin_unload
, /**< unload */
5113 sipe_plugin_destroy
, /**< destroy */
5114 NULL
, /**< ui_info */
5115 &prpl_info
, /**< extra_info */
5124 static void init_plugin(PurplePlugin
*plugin
)
5126 PurpleAccountUserSplit
*split
;
5127 PurpleAccountOption
*option
;
5130 purple_debug_info(PACKAGE
, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
5131 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s",
5132 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
5135 purple_plugin_register(plugin
);
5137 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
5138 purple_account_user_split_set_reverse(split
, FALSE
);
5139 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
5141 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
5142 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5143 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5144 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5146 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
5147 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5148 // Translators: noun (networking port)
5149 option
= purple_account_option_int_new(_("Port"), "port", 5061);
5150 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5152 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
5153 purple_account_option_add_list_item(option
, _("Auto"), "auto");
5154 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
5155 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
5156 purple_account_option_add_list_item(option
, _("UDP"), "udp");
5157 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5159 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5160 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5162 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
5163 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5165 // TODO commented out so won't show in the preferences until we fix krb message signing
5166 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5167 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5169 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5170 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5171 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5174 option
= purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE
);
5175 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5176 option
= purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5177 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5178 my_protocol
= plugin
;
5181 /* I had to redefined the function for it load, but works */
5182 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
5183 plugin
->info
= &(info
);
5184 init_plugin((plugin
));
5185 sipe_plugin_load((plugin
));
5186 return purple_plugin_register(plugin
);