6 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
7 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
12 * Thanks to Google's Summer of Code Program and the helpful mentors
15 * Session-based SIP MESSAGE documentation:
16 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
18 * This program is free software; you can redistribute it and/or modify
19 * it under the terms of the GNU General Public License as published by
20 * the Free Software Foundation; either version 2 of the License, or
21 * (at your option) any later version.
23 * This program is distributed in the hope that it will be useful,
24 * but WITHOUT ANY WARRANTY; without even the implied warranty of
25 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
26 * GNU General Public License for more details.
28 * You should have received a copy of the GNU General Public License
29 * along with this program; if not, write to the Free Software
30 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
34 #include <sys/socket.h>
35 #include <sys/ioctl.h>
36 #include <sys/types.h>
37 #include <netinet/in.h>
41 # define _(String) ((const char *) gettext (String))
43 # define _(String) ((const char *) (String))
44 #endif /* ENABLE_NLS */
49 #define _LIBC_INTERNAL_
62 #include "accountopt.h"
64 #include "conversation.h"
82 #endif /*USE_KERBEROS*/
85 #include "sipe-sign.h"
89 /* Keep in sync with sipe_transport_type! */
90 static const char *transport_descriptor
[] = { "tls", "tcp", "udp" };
91 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
93 /* Status identifiers (see also: sipe_status_types()) */
94 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
95 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
96 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
97 /* PURPLE_STATUS_UNAVAILABLE: */
98 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
99 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
100 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
101 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
102 /* PURPLE_STATUS_AWAY: */
103 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
104 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
105 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
106 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
107 /* ??? PURPLE_STATUS_MOBILE */
108 /* ??? PURPLE_STATUS_TUNE */
110 /* Action name templates */
111 #define ACTION_NAME_PRESENCE "<presence><%s>"
113 static char *gentag()
115 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
118 static gchar
*get_epid(struct sipe_account_data
*sip
)
121 sip
->epid
= sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
123 return g_strdup(sip
->epid
);
126 static char *genbranch()
128 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
129 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
130 rand() & 0xFFFF, rand() & 0xFFFF);
133 static char *gencallid()
135 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
136 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
137 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
138 rand() & 0xFFFF, rand() & 0xFFFF);
141 static gchar
*find_tag(const gchar
*hdr
)
143 gchar
* tag
= sipmsg_find_part_of_header (hdr
, "tag=", ";", NULL
);
145 // In case it's at the end and there's no trailing ;
146 tag
= sipmsg_find_part_of_header (hdr
, "tag=", NULL
, NULL
);
152 static const char *sipe_list_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
157 static void sipe_plugin_destroy(PurplePlugin
*plugin
);
159 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
);
161 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
162 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
165 static void sipe_close(PurpleConnection
*gc
);
167 static void send_presence_status(struct sipe_account_data
*sip
);
169 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
171 static void sipe_keep_alive(PurpleConnection
*gc
)
173 struct sipe_account_data
*sip
= gc
->proto_data
;
174 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
175 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
176 gchar buf
[2] = {0, 0};
177 purple_debug_info("sipe", "sending keep alive\n");
178 sendto(sip
->fd
, buf
, 1, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
));
180 time_t now
= time(NULL
);
181 if ((sip
->keepalive_timeout
> 0) &&
182 ((now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
183 #if PURPLE_VERSION_CHECK(2,4,0)
184 && ((now
- gc
->last_received
) >= sip
->keepalive_timeout
)
187 purple_debug_info("sipe", "sending keep alive %d\n",sip
->keepalive_timeout
);
188 sendout_pkt(gc
, "\r\n\r\n");
189 sip
->last_keepalive
= now
;
194 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
196 struct sip_connection
*ret
= NULL
;
197 GSList
*entry
= sip
->openconns
;
200 if (ret
->fd
== fd
) return ret
;
206 static void sipe_auth_free(struct sip_auth
*auth
)
210 g_free(auth
->opaque
);
214 g_free(auth
->target
);
216 g_free(auth
->digest_session_key
);
217 auth
->digest_session_key
= NULL
;
218 auth
->type
= AUTH_TYPE_UNSET
;
221 g_free(auth
->gssapi_data
);
222 auth
->gssapi_data
= NULL
;
223 sip_sec_destroy_context(auth
->gssapi_context
);
224 auth
->gssapi_context
= NULL
;
227 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
229 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
231 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
235 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
237 struct sip_connection
*conn
= connection_find(sip
, fd
);
239 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
240 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
246 static void connection_free_all(struct sipe_account_data
*sip
)
248 struct sip_connection
*ret
= NULL
;
249 GSList
*entry
= sip
->openconns
;
252 connection_remove(sip
, ret
->fd
);
253 entry
= sip
->openconns
;
257 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
259 const gchar
*method
= msg
->method
;
260 const gchar
*target
= msg
->target
;
265 const char *authdomain
= sip
->authdomain
;
266 const char *authuser
= sip
->authuser
;
267 //const char *krb5_realm;
269 //gchar *krb5_token = NULL;
271 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
272 // and do error checking
274 // KRB realm should always be uppercase
275 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
277 if (sip
->realhostname
) {
278 host
= sip
->realhostname
;
279 } else if (purple_account_get_bool(sip
->account
, "useproxy", TRUE
)) {
280 host
= purple_account_get_string(sip
->account
, "proxy", "");
282 host
= sip
->sipdomain
;
285 /*gboolean new_auth = krb5_auth.gss_context == NULL;
287 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
290 if (new_auth || force_reauth) {
291 krb5_token = krb5_auth.base64_token;
294 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
295 krb5_token = krb5_auth.base64_token;*/
301 if (!authuser
|| strlen(authuser
) < 1) {
302 authuser
= sip
->username
;
305 if (auth
->type
== AUTH_TYPE_DIGEST
) { /* Digest */
306 sprintf(noncecount
, "%08d", auth
->nc
++);
307 response
= purple_cipher_http_digest_calculate_response(
308 "md5", method
, target
, NULL
, NULL
,
309 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
310 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
312 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
);
315 } else if (auth
->type
== AUTH_TYPE_NTLM
|| auth
->type
== AUTH_TYPE_KERBEROS
) { /* NTLM */ /* Kerberos */
316 gchar
*auth_protocol
= (auth
->type
== AUTH_TYPE_NTLM
? "NTLM" : "Kerberos");
317 // If we have a signature for the message, include that
318 if (msg
->signature
) {
319 tmp
= g_strdup_printf("%s qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth_protocol
, auth
->opaque
, auth
->realm
, auth
->target
, msg
->rand
, msg
->num
, msg
->signature
);
323 if ((auth
->type
== AUTH_TYPE_NTLM
&& auth
->nc
== 3 && auth
->gssapi_data
&& auth
->gssapi_context
== NULL
)
324 || (auth
->type
== AUTH_TYPE_KERBEROS
&& auth
->nc
== 3)) {
328 gssapi_data
= sip_sec_init_context(&(auth
->gssapi_context
), auth_protocol
,
329 authdomain
, authuser
, sip
->password
,
333 opaque
= (auth
->type
== AUTH_TYPE_NTLM
? g_strdup_printf(", opaque=\"%s\"", auth
->opaque
) : g_strdup(""));
334 tmp
= g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol
, opaque
, auth
->realm
, auth
->target
, gssapi_data
);
340 tmp
= g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol
, auth
->realm
, auth
->target
);
344 sprintf(noncecount
, "%08d", auth
->nc
++);
345 response
= purple_cipher_http_digest_calculate_response(
346 "md5", method
, target
, NULL
, NULL
,
347 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
348 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
350 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
);
355 static char *parse_attribute(const char *attrname
, const char *source
)
357 const char *tmp
, *tmp2
;
359 int len
= strlen(attrname
);
361 if (!strncmp(source
, attrname
, len
)) {
363 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
365 retval
= g_strndup(tmp
, tmp2
- tmp
);
367 retval
= g_strdup(tmp
);
373 static void fill_auth(struct sipe_account_data
*sip
, gchar
*hdr
, struct sip_auth
*auth
)
376 const char *authuser
;
379 //const char *krb5_realm;
382 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
383 // and do error checking
385 // KRB realm should always be uppercase
386 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
388 if (sip->realhostname) {
389 host = sip->realhostname;
390 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
391 host = purple_account_get_string(sip->account, "proxy", "");
393 host = sip->sipdomain;
396 authuser
= sip
->authuser
;
398 if (!authuser
|| strlen(authuser
) < 1) {
399 authuser
= sip
->username
;
403 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
407 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
408 auth
->type
= AUTH_TYPE_NTLM
;
409 parts
= g_strsplit(hdr
+5, "\", ", 0);
412 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
414 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
416 auth
->nonce
= g_memdup(purple_ntlm_parse_challenge(tmp
, &auth
->flags
), 8);
418 g_free(auth
->gssapi_data
);
419 auth
->gssapi_data
= tmp
;
421 if ((tmp
= parse_attribute("targetname=\"",
423 g_free(auth
->target
);
426 else if ((tmp
= parse_attribute("realm=\"",
431 else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
432 g_free(auth
->opaque
);
439 if (!strstr(hdr
, "gssapi-data")) {
447 if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
448 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "setting auth type to Kerberos (3)\r\n");
449 auth
->type
= AUTH_TYPE_KERBEROS
;
450 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth - header: %s\r\n", hdr
);
451 parts
= g_strsplit(hdr
+9, "\", ", 0);
454 purple_debug_info("sipe", "krb - parts[i] %s\n", parts
[i
]);
455 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
456 /*if (krb5_auth.gss_context == NULL) {
457 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
459 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
461 g_free(auth
->gssapi_data
);
462 auth
->gssapi_data
= tmp
;
464 if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
465 g_free(auth
->target
);
467 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
470 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
471 g_free(auth
->opaque
);
481 auth
->type
= AUTH_TYPE_DIGEST
;
482 parts
= g_strsplit(hdr
, " ", 0);
484 if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
488 else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
496 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "nonce: %s realm: %s\n", auth
->nonce
? auth
->nonce
: "(null)", auth
->realm
? auth
->realm
: "(null)");
498 g_free(auth
->digest_session_key
);
499 auth
->digest_session_key
= purple_cipher_http_digest_calculate_session_key(
500 "md5", authuser
, auth
->realm
, sip
->password
, auth
->nonce
, NULL
);
506 static void sipe_canwrite_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
508 PurpleConnection
*gc
= data
;
509 struct sipe_account_data
*sip
= gc
->proto_data
;
513 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
515 if (max_write
== 0) {
516 if (sip
->tx_handler
!= 0){
517 purple_input_remove(sip
->tx_handler
);
523 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
525 if (written
< 0 && errno
== EAGAIN
)
527 else if (written
<= 0) {
528 /*TODO: do we really want to disconnect on a failure to write?*/
529 purple_connection_error(gc
, _("Could not write"));
533 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
536 static void sipe_canwrite_cb_ssl(gpointer data
, gint src
, PurpleInputCondition cond
)
538 PurpleConnection
*gc
= data
;
539 struct sipe_account_data
*sip
= gc
->proto_data
;
543 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
545 if (max_write
== 0) {
546 if (sip
->tx_handler
!= 0) {
547 purple_input_remove(sip
->tx_handler
);
553 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
555 if (written
< 0 && errno
== EAGAIN
)
557 else if (written
<= 0) {
558 /*TODO: do we really want to disconnect on a failure to write?*/
559 purple_connection_error(gc
, _("Could not write"));
563 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
566 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
568 static void send_later_cb(gpointer data
, gint source
, const gchar
*error
)
570 PurpleConnection
*gc
= data
;
571 struct sipe_account_data
*sip
;
572 struct sip_connection
*conn
;
574 if (!PURPLE_CONNECTION_IS_VALID(gc
))
582 purple_connection_error(gc
, _("Could not connect"));
586 sip
= gc
->proto_data
;
588 sip
->connecting
= FALSE
;
589 sip
->last_keepalive
= time(NULL
);
591 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
593 /* If there is more to write now, we need to register a handler */
594 if (sip
->txbuf
->bufused
> 0)
595 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
597 conn
= connection_create(sip
, source
);
598 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
601 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
603 struct sipe_account_data
*sip
;
604 struct sip_connection
*conn
;
606 if (!PURPLE_CONNECTION_IS_VALID(gc
))
608 if (gsc
) purple_ssl_close(gsc
);
612 sip
= gc
->proto_data
;
615 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
616 sip
->connecting
= FALSE
;
617 sip
->last_keepalive
= time(NULL
);
619 conn
= connection_create(sip
, gsc
->fd
);
621 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
626 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
628 PurpleConnection
*gc
= data
;
629 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
630 if (sip
== NULL
) return;
632 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
634 /* If there is more to write now */
635 if (sip
->txbuf
->bufused
> 0) {
636 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
641 static void sendlater(PurpleConnection
*gc
, const char *buf
)
643 struct sipe_account_data
*sip
= gc
->proto_data
;
645 if (!sip
->connecting
) {
646 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
647 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
648 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
650 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
651 purple_connection_error(gc
, _("Couldn't create socket"));
654 sip
->connecting
= TRUE
;
657 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
658 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
660 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
663 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
665 struct sipe_account_data
*sip
= gc
->proto_data
;
666 time_t currtime
= time(NULL
);
667 int writelen
= strlen(buf
);
669 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
670 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
671 if (sendto(sip
->fd
, buf
, writelen
, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
672 purple_debug_info("sipe", "could not send packet\n");
681 if (sip
->tx_handler
) {
686 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
688 ret
= write(sip
->fd
, buf
, writelen
);
692 if (ret
< 0 && errno
== EAGAIN
)
694 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
699 if (ret
< writelen
) {
700 if (!sip
->tx_handler
){
702 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
705 sip
->tx_handler
= purple_input_add(sip
->fd
,
706 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
711 /* XXX: is it OK to do this? You might get part of a request sent
712 with part of another. */
713 if (sip
->txbuf
->bufused
> 0)
714 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
716 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
722 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
724 sendout_pkt(gc
, buf
);
728 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
730 GSList
*tmp
= msg
->headers
;
733 GString
*outstr
= g_string_new("");
734 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
736 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
737 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
738 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
739 tmp
= g_slist_next(tmp
);
741 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
742 sendout_pkt(sip
->gc
, outstr
->str
);
743 g_string_free(outstr
, TRUE
);
746 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
749 if (sip
->registrar
.gssapi_context
) {
750 struct sipmsg_breakdown msgbd
;
751 gchar
*signature_input_str
;
753 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
754 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
755 sip
->registrar
.ntlm_num
++;
756 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
757 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
758 if (signature_input_str
!= NULL
) {
759 char *signature_hex
= sip_sec_make_signature(sip
->registrar
.gssapi_context
, signature_input_str
);
760 msg
->signature
= signature_hex
;
761 msg
->rand
= g_strdup(msgbd
.rand
);
762 msg
->num
= g_strdup(msgbd
.num
);
763 g_free(signature_input_str
);
765 sipmsg_breakdown_free(&msgbd
);
768 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
769 buf
= auth_header(sip
, &sip
->registrar
, msg
);
770 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
771 sipmsg_add_header(msg
, "Authorization", buf
);
773 sipmsg_add_header_pos(msg
, "Authorization", buf
, 5); // What's the point in 5?
776 } else if (!strcmp(method
,"SUBSCRIBE") || !strcmp(method
,"SERVICE") || !strcmp(method
,"MESSAGE") || !strcmp(method
,"INVITE") || !strcmp(method
, "ACK") || !strcmp(method
, "NOTIFY") || !strcmp(method
, "BYE") || !strcmp(method
, "INFO") || !strcmp(method
, "OPTIONS")) {
777 sip
->registrar
.nc
= 3;
778 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
779 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
781 sip
->registrar
.type
= AUTH_TYPE_KERBEROS
;
785 buf
= auth_header(sip
, &sip
->registrar
, msg
);
786 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
789 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
793 static char *get_contact(struct sipe_account_data
*sip
)
795 return g_strdup(sip
->contact
);
800 static char *get_contact_service(struct sipe_account_data *sip)
802 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()));
803 //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);
807 static void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
808 const char *text
, const char *body
)
812 GString
*outstr
= g_string_new("");
813 struct sipe_account_data
*sip
= gc
->proto_data
;
817 sipmsg_remove_header(msg
, "ms-user-data");
819 contact
= get_contact(sip
);
820 sipmsg_remove_header(msg
, "Contact");
821 sipmsg_add_header(msg
, "Contact", contact
);
824 /* When sending the acknowlegements and errors, the content length from the original
825 message is still here, but there is no body; we need to make sure we're sending the
826 correct content length */
827 sipmsg_remove_header(msg
, "Content-Length");
830 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
831 sipmsg_add_header(msg
, "Content-Length", len
);
833 sipmsg_remove_header(msg
, "Content-Type");
834 sipmsg_add_header(msg
, "Content-Length", "0");
837 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
838 //gchar * mic = "MICTODO";
839 msg
->response
= code
;
841 sipmsg_remove_header(msg
, "Authentication-Info");
842 sign_outgoing_message(msg
, sip
, msg
->method
);
844 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
847 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
848 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
850 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
851 tmp
= g_slist_next(tmp
);
853 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
854 sendout_pkt(gc
, outstr
->str
);
855 g_string_free(outstr
, TRUE
);
858 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
860 if (trans
->msg
) sipmsg_free(trans
->msg
);
861 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
865 static struct transaction
*
866 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
868 struct transaction
*trans
= g_new0(struct transaction
, 1);
869 trans
->time
= time(NULL
);
870 trans
->msg
= (struct sipmsg
*)msg
;
871 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
872 trans
->callback
= callback
;
873 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
877 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
879 struct transaction
*trans
;
880 GSList
*transactions
= sip
->transactions
;
881 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
883 while (transactions
) {
884 trans
= transactions
->data
;
885 if (!strcmp(trans
->cseq
, cseq
)) {
888 transactions
= transactions
->next
;
894 static struct transaction
*
895 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
896 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
897 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
899 struct sipe_account_data
*sip
= gc
->proto_data
;
900 const char *addh
= "";
903 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
904 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
905 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
906 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
907 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
908 gchar
*useragent
= (gchar
*)purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
);
909 gchar
*route
= strdup("");
910 gchar
*epid
= get_epid(sip
); // TODO generate one per account/login
911 struct transaction
*trans
;
913 if (dialog
&& dialog
->routes
)
915 GSList
*iter
= dialog
->routes
;
920 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
922 iter
= g_slist_next(iter
);
926 if (!ourtag
&& !dialog
) {
930 if (!strcmp(method
, "REGISTER")) {
931 if (sip
->regcallid
) {
933 callid
= g_strdup(sip
->regcallid
);
935 sip
->regcallid
= g_strdup(callid
);
939 if (addheaders
) addh
= addheaders
;
941 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
942 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
943 "From: <sip:%s>%s%s;epid=%s\r\n"
944 "To: <%s>%s%s%s%s\r\n"
945 "Max-Forwards: 70\r\n"
950 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
952 dialog
&& dialog
->request
? dialog
->request
: url
,
953 TRANSPORT_DESCRIPTOR
,
954 purple_network_get_my_ip(-1),
956 branch
? ";branch=" : "",
957 branch
? branch
: "",
959 ourtag
? ";tag=" : "",
960 ourtag
? ourtag
: "",
963 theirtag
? ";tag=" : "",
964 theirtag
? theirtag
: "",
965 theirepid
? ";epid=" : "",
966 theirepid
? theirepid
: "",
967 dialog
? ++dialog
->cseq
: ++sip
->cseq
,
973 body
? strlen(body
) : 0,
977 //printf ("parsing msg buf:\n%s\n\n", buf);
978 msg
= sipmsg_parse_msg(buf
);
989 sign_outgoing_message (msg
, sip
, method
);
991 buf
= sipmsg_to_string (msg
);
993 /* add to ongoing transactions */
994 trans
= transactions_add_buf(sip
, msg
, tc
);
995 sendout_pkt(gc
, buf
);
1001 static void send_soap_request_with_cb(struct sipe_account_data
*sip
, gchar
*body
, TransCallback callback
, void * payload
)
1003 gchar
*from
= g_strdup_printf("sip:%s", sip
->username
);
1004 gchar
*contact
= get_contact(sip
);
1005 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1006 "Content-Type: application/SOAP+xml\r\n",contact
);
1008 struct transaction
* tr
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1009 tr
->payload
= payload
;
1016 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1018 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
1021 static char *get_contact_register(struct sipe_account_data
*sip
)
1023 char *epid
= get_epid(sip
);
1024 char *uuid
= generateUUIDfromEPID(epid
);
1025 char *buf
= g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip
->listenport
, TRANSPORT_DESCRIPTOR
, uuid
);
1031 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1033 char *expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
1034 char *uri
= g_strdup_printf("sip:%s", sip
->sipdomain
);
1035 char *to
= g_strdup_printf("sip:%s", sip
->username
);
1036 char *contact
= get_contact_register(sip
);
1037 char *hdr
= g_strdup_printf("Contact: %s\r\n"
1038 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1039 "Event: registration\r\n"
1040 "Allow-Events: presence\r\n"
1041 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1042 "%s", contact
, expires
);
1046 sip
->registerstatus
= 1;
1048 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1049 process_register_response
);
1056 static void do_register_cb(struct sipe_account_data
*sip
, void *unused
)
1058 do_register_exp(sip
, -1);
1059 sip
->reregister_set
= FALSE
;
1062 static void do_register(struct sipe_account_data
*sip
)
1064 do_register_exp(sip
, -1);
1068 * Returns URI from provided To or From header.
1070 * Needs to g_free() after use.
1072 * @return URI with sip: prefix
1074 static gchar
*parse_from(const gchar
*hdr
)
1077 const gchar
*tmp
, *tmp2
= hdr
;
1079 if (!hdr
) return NULL
;
1080 purple_debug_info("sipe", "parsing address out of %s\n", hdr
);
1081 tmp
= strchr(hdr
, '<');
1083 /* i hate the different SIP UA behaviours... */
1084 if (tmp
) { /* sip address in <...> */
1086 tmp
= strchr(tmp2
, '>');
1088 from
= g_strndup(tmp2
, tmp
- tmp2
);
1090 purple_debug_info("sipe", "found < without > in From\n");
1094 tmp
= strchr(tmp2
, ';');
1096 from
= g_strndup(tmp2
, tmp
- tmp2
);
1098 from
= g_strdup(tmp2
);
1101 purple_debug_info("sipe", "got %s\n", from
);
1105 static xmlnode
* xmlnode_get_descendant(xmlnode
* parent
, ...)
1108 xmlnode
* node
= NULL
;
1111 va_start(args
, parent
);
1112 while ((name
= va_arg(args
, const char *)) != NULL
) {
1113 node
= xmlnode_get_child(parent
, name
);
1114 if (node
== NULL
) return NULL
;
1124 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1126 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1127 send_soap_request(sip
, body
);
1132 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1135 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1137 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1140 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1144 void sipe_auth_user_cb(void * data
)
1146 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1149 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1154 void sipe_deny_user_cb(void * data
)
1156 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1159 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1164 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1166 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1167 sipe_contact_allow_deny(sip
, name
, TRUE
);
1171 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1173 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1174 sipe_contact_allow_deny(sip
, name
, FALSE
);
1178 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1180 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1181 sipe_contact_set_acl(sip, name, "");
1185 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1189 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1190 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1192 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1194 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1195 if (!watchers
) return;
1197 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1198 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1199 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1200 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1202 // TODO pull out optional displayName to pass as alias
1204 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1205 job
->who
= remote_user
;
1207 purple_account_request_authorization(
1221 xmlnode_free(watchers
);
1226 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1228 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1229 if (!purple_group
) {
1230 purple_group
= purple_group_new(group
->name
);
1231 purple_blist_add_group(purple_group
, NULL
);
1235 group
->purple_group
= purple_group
;
1236 sip
->groups
= g_slist_append(sip
->groups
, group
);
1237 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1239 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1243 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1245 struct sipe_group
*group
;
1251 entry
= sip
->groups
;
1253 group
= entry
->data
;
1254 if (group
->id
== id
) {
1257 entry
= entry
->next
;
1262 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, gchar
* name
)
1264 struct sipe_group
*group
;
1270 entry
= sip
->groups
;
1272 group
= entry
->data
;
1273 if (!strcmp(group
->name
, name
)) {
1276 entry
= entry
->next
;
1282 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1285 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1286 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1287 send_soap_request(sip
, body
);
1289 g_free(group
->name
);
1290 group
->name
= g_strdup(name
);
1294 * Only appends if no such value already stored.
1297 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1298 GSList
* res
= list
;
1299 if (!g_slist_find_custom(list
, data
, func
)) {
1300 res
= g_slist_insert_sorted(list
, data
, func
);
1306 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1307 return group1
->id
- group2
->id
;
1311 * Returns string like "2 4 7 8" - group ids buddy belong to.
1314 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1317 //creating array from GList, converting int to gchar*
1318 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1319 GSList
*entry
= buddy
->groups
;
1321 struct sipe_group
* group
= entry
->data
;
1322 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1323 entry
= entry
->next
;
1327 res
= g_strjoinv(" ", ids_arr
);
1328 g_strfreev(ids_arr
);
1333 * Sends buddy update to server
1336 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1338 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1339 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1341 if (buddy
&& purple_buddy
) {
1342 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1344 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1345 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1347 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1348 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1350 send_soap_request(sip
, body
);
1356 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1358 if (msg
->response
== 200) {
1359 struct sipe_group
*group
;
1360 struct group_user_context
*ctx
= (struct group_user_context
*)tc
->payload
;
1364 struct sipe_buddy
*buddy
;
1366 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1372 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1379 group_id
= xmlnode_get_data(node
);
1386 group
= g_new0(struct sipe_group
, 1);
1387 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1389 group
->name
= ctx
->group_name
;
1391 sipe_group_add(sip
, group
);
1393 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1395 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1398 sipe_group_set_user(sip
, ctx
->user_name
);
1407 static void sipe_group_create (struct sipe_account_data
*sip
, gchar
*name
, gchar
* who
)
1409 struct group_user_context
* ctx
= g_new0(struct group_user_context
, 1);
1411 ctx
->group_name
= g_strdup(name
);
1412 ctx
->user_name
= g_strdup(who
);
1414 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1415 send_soap_request_with_cb(sip
, body
, process_add_group_response
, ctx
);
1420 * Data structure for scheduled actions
1422 typedef void (*Action
) (struct sipe_account_data
*, void *);
1424 struct scheduled_action
{
1427 * Format is <Event>[<Data>...]
1428 * Example: <presence><sip:user@domain.com> or <registration>
1431 guint timeout_handler
;
1432 gboolean repetitive
;
1434 GDestroyNotify destroy
;
1435 struct sipe_account_data
*sip
;
1441 * Should return FALSE if repetitive action is not needed
1443 static gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1446 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1447 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1448 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1449 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1450 ret
= sched_action
->repetitive
;
1451 (*sched_action
->destroy
)(sched_action
->payload
);
1452 g_free(sched_action
->name
);
1453 g_free(sched_action
);
1458 * Kills action timer effectively cancelling
1461 * @param name of action
1463 static void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, const gchar
*name
)
1467 if (!sip
->timeouts
|| !name
) return;
1469 entry
= sip
->timeouts
;
1471 struct scheduled_action
*sched_action
= entry
->data
;
1472 if(!strcmp(sched_action
->name
, name
)) {
1473 GSList
*to_delete
= entry
;
1474 entry
= entry
->next
;
1475 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1476 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1477 purple_timeout_remove(sched_action
->timeout_handler
);
1478 (*sched_action
->destroy
)(sched_action
->payload
);
1479 g_free(sched_action
->name
);
1480 g_free(sched_action
);
1482 entry
= entry
->next
;
1488 * Do schedule action for execution in the future.
1489 * Non repetitive execution.
1491 * @param name of action (will be copied)
1492 * @param timeout in seconds
1493 * @action callback function
1494 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1496 static void sipe_schedule_action(const gchar
*name
, int timeout
, Action action
, GDestroyNotify destroy
, struct sipe_account_data
*sip
, void *payload
)
1498 struct scheduled_action
*sched_action
;
1500 /* Make sure each action only exists once */
1501 sipe_cancel_scheduled_action(sip
, name
);
1503 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name
, timeout
);
1504 sched_action
= g_new0(struct scheduled_action
, 1);
1505 sched_action
->repetitive
= FALSE
;
1506 sched_action
->name
= g_strdup(name
);
1507 sched_action
->action
= action
;
1508 sched_action
->destroy
= destroy
? destroy
: g_free
;
1509 sched_action
->sip
= sip
;
1510 sched_action
->payload
= payload
;
1511 sched_action
->timeout_handler
= purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1512 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1513 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1516 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1518 static gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1520 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1522 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1527 static void sipe_subscribe_resource_uri(const char *name
, gpointer value
, gchar
**resources_uri
)
1529 gchar
*tmp
= *resources_uri
;
1530 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1534 static void sipe_subscribe_resource_uri_with_context(const char *name
, gpointer value
, gchar
**resources_uri
)
1536 gchar
*tmp
= *resources_uri
;
1537 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
1539 if(!sbuddy
->resubscribed
){ //Only not resubscribed contacts; the first time everybody are included
1540 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp
, name
);
1547 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1548 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1549 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1550 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1551 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1554 static void sipe_subscribe_presence_batched_to(struct sipe_account_data
*sip
, gchar
*resources_uri
, gchar
*to
)
1556 gchar
*contact
= get_contact(sip
);
1559 gchar
*require
= "";
1561 gchar
*autoextend
= "";
1562 gchar
*content_type
;
1564 if (sip
->msrtc_event_categories
) {
1565 require
= ", categoryList";
1566 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1567 content_type
= "application/msrtc-adrl-categorylist+xml";
1568 content
= g_strdup_printf(
1569 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1570 "<action name=\"subscribe\" id=\"63792024\">\n"
1571 "<adhocList>\n%s</adhocList>\n"
1572 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1573 "<category name=\"note\"/>\n"
1574 "<category name=\"state\"/>\n"
1577 "</batchSub>", sip
->username
, resources_uri
);
1579 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1580 content_type
= "application/adrl+xml";
1581 content
= g_strdup_printf(
1582 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1583 "<create xmlns=\"\">\n%s</create>\n"
1584 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1586 g_free(resources_uri
);
1588 request
= g_strdup_printf(
1589 "Require: adhoclist%s\r\n"
1590 "Supported: eventlist\r\n"
1591 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1592 "Supported: ms-piggyback-first-notify\r\n"
1593 "%sSupported: ms-benotify\r\n"
1594 "Proxy-Require: ms-benotify\r\n"
1595 "Event: presence\r\n"
1596 "Content-Type: %s\r\n"
1597 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
1600 /* subscribe to buddy presence */
1601 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1602 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1609 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
, void *unused
)
1611 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
1612 gchar
*resources_uri
= g_strdup("");
1613 if (sip
->msrtc_event_categories
) {
1614 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
1616 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1618 sipe_subscribe_presence_batched_to(sip
, resources_uri
, to
);
1621 struct presence_batched_routed
{
1626 static void sipe_subscribe_presence_batched_routed_free(void *payload
)
1628 struct presence_batched_routed
*data
= payload
;
1629 GSList
*buddies
= data
->buddies
;
1631 g_free(buddies
->data
);
1632 buddies
= buddies
->next
;
1634 g_slist_free(data
->buddies
);
1639 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data
*sip
, void *payload
)
1641 struct presence_batched_routed
*data
= payload
;
1642 GSList
*buddies
= data
->buddies
;
1643 gchar
*resources_uri
= g_strdup("");
1645 gchar
*tmp
= resources_uri
;
1646 resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, buddies
->data
);
1648 buddies
= buddies
->next
;
1650 sipe_subscribe_presence_batched_to(sip
, resources_uri
,
1651 g_strdup(data
->host
));
1655 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1656 * The user sends a single SUBSCRIBE request to the subscribed contact.
1657 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1661 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, void *buddy_name
)
1663 gchar
*to
= strstr((char *)buddy_name
, "sip:") ? g_strdup((char *)buddy_name
) : g_strdup_printf("sip:%s", (char *)buddy_name
);
1664 gchar
*tmp
= get_contact(sip
);
1667 gchar
*autoextend
= "";
1669 if (!sip
->msrtc_event_categories
)
1670 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1672 request
= g_strdup_printf(
1673 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1674 "Supported: ms-piggyback-first-notify\r\n"
1675 "%sSupported: ms-benotify\r\n"
1676 "Proxy-Require: ms-benotify\r\n"
1677 "Event: presence\r\n"
1678 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1679 "Contact: %s\r\n", autoextend
,tmp
);
1681 content
= g_strdup_printf(
1682 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1683 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1684 "<resource uri=\"%s\"/>\n"
1686 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1687 "<category name=\"note\"/>\n"
1688 "<category name=\"state\"/>\n"
1691 "</batchSub>", sip
->username
, to
1696 /* subscribe to buddy presence */
1697 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1704 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
1706 if (!purple_status_is_active(status
))
1710 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
1713 g_free(sip
->status
);
1714 sip
->status
= g_strdup(purple_status_get_id(status
));
1715 send_presence_status(sip
);
1721 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
, const char *alias
)
1723 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1724 sipe_group_set_user(sip
, name
);
1728 sipe_group_buddy(PurpleConnection
*gc
,
1730 const char *old_group_name
,
1731 const char *new_group_name
)
1733 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1734 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1735 struct sipe_group
* old_group
= NULL
;
1736 struct sipe_group
* new_group
;
1738 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1739 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
1741 if(!buddy
) { // buddy not in roaming list
1745 if (old_group_name
) {
1746 old_group
= sipe_group_find_by_name(sip
, g_strdup(old_group_name
));
1748 new_group
= sipe_group_find_by_name(sip
, g_strdup(new_group_name
));
1751 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
1752 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
1756 sipe_group_create(sip
, g_strdup(new_group_name
), g_strdup(who
));
1758 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
1759 sipe_group_set_user(sip
, who
);
1763 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1765 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1766 struct sipe_buddy
*b
;
1768 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1770 // Prepend sip: if needed
1771 if (strncmp("sip:", buddy
->name
, 4)) {
1772 gchar
*buf
= g_strdup_printf("sip:%s", buddy
->name
);
1773 purple_blist_rename_buddy(buddy
, buf
);
1777 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
1778 b
= g_new0(struct sipe_buddy
, 1);
1779 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy
->name
);
1780 b
->name
= g_strdup(buddy
->name
);
1781 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
1782 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
1783 sipe_subscribe_presence_single(sip
, b
->name
); //@TODO should go to callback
1785 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy
->name
);
1789 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
1791 g_free(buddy
->name
);
1792 g_free(buddy
->annotation
);
1793 g_free(buddy
->device_name
);
1794 g_slist_free(buddy
->groups
);
1799 * Unassociates buddy from group first.
1800 * Then see if no groups left, removes buddy completely.
1801 * Otherwise updates buddy groups on server.
1803 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1805 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1806 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
1807 struct sipe_group
*g
= NULL
;
1809 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1814 g
= sipe_group_find_by_name(sip
, group
->name
);
1818 b
->groups
= g_slist_remove(b
->groups
, g
);
1819 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
1822 if (g_slist_length(b
->groups
) < 1) {
1823 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
1824 sipe_cancel_scheduled_action(sip
, action_name
);
1825 g_free(action_name
);
1827 g_hash_table_remove(sip
->buddies
, buddy
->name
);
1830 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
1831 send_soap_request(sip
, body
);
1837 //updates groups on server
1838 sipe_group_set_user(sip
, b
->name
);
1844 sipe_rename_group(PurpleConnection
*gc
,
1845 const char *old_name
,
1847 GList
*moved_buddies
)
1849 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1850 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, g_strdup(old_name
));
1852 sipe_group_rename(sip
, s_group
, group
->name
);
1854 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
1859 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
1861 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1862 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
1865 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
1866 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
1867 send_soap_request(sip
, body
);
1870 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
1871 g_free(s_group
->name
);
1874 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
1878 static GList
*sipe_status_types(PurpleAccount
*acc
)
1880 PurpleStatusType
*type
;
1881 GList
*types
= NULL
;
1884 type
= purple_status_type_new_with_attrs(
1885 PURPLE_STATUS_AVAILABLE
, NULL
, _("Online"), TRUE
, TRUE
, FALSE
,
1886 // Translators: noun
1887 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1889 types
= g_list_append(types
, type
);
1892 type
= purple_status_type_new_with_attrs(
1893 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_BUSY
, _("Busy"), TRUE
, TRUE
, FALSE
,
1894 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1896 types
= g_list_append(types
, type
);
1898 // Do Not Disturb (not user settable)
1899 type
= purple_status_type_new_with_attrs(
1900 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_DND
, _("Do Not Disturb"), TRUE
, FALSE
, FALSE
,
1901 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1903 types
= g_list_append(types
, type
);
1906 type
= purple_status_type_new_with_attrs(
1907 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_BRB
, _("Be Right Back"), TRUE
, TRUE
, FALSE
,
1908 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1910 types
= g_list_append(types
, type
);
1913 type
= purple_status_type_new_with_attrs(
1914 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
1915 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1917 types
= g_list_append(types
, type
);
1920 type
= purple_status_type_new_with_attrs(
1921 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_ONPHONE
, _("On The Phone"), TRUE
, TRUE
, FALSE
,
1922 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1924 types
= g_list_append(types
, type
);
1927 type
= purple_status_type_new_with_attrs(
1928 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_LUNCH
, _("Out To Lunch"), TRUE
, TRUE
, FALSE
,
1929 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1931 types
= g_list_append(types
, type
);
1934 type
= purple_status_type_new_full(
1935 PURPLE_STATUS_INVISIBLE
, NULL
, _("Appear Offline"), TRUE
, TRUE
, FALSE
);
1936 types
= g_list_append(types
, type
);
1939 type
= purple_status_type_new_full(
1940 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
1941 types
= g_list_append(types
, type
);
1947 * A callback for g_hash_table_foreach
1949 static void sipe_buddy_subscribe_cb(char *name
, struct sipe_buddy
*buddy
, struct sipe_account_data
*sip
)
1951 sipe_subscribe_presence_single(sip
, buddy
->name
);
1955 * Removes entries from purple buddy list
1956 * that does not correspond ones in the roaming contact list.
1958 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
1959 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
1960 GSList
*entry
= buddies
;
1961 struct sipe_buddy
*buddy
;
1965 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
1966 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
1969 g
= purple_buddy_get_group(b
);
1970 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
1972 gboolean in_sipe_groups
= FALSE
;
1973 GSList
*entry2
= buddy
->groups
;
1975 struct sipe_group
*group
= entry2
->data
;
1976 if (!strcmp(group
->name
, g
->name
)) {
1977 in_sipe_groups
= TRUE
;
1980 entry2
= entry2
->next
;
1982 if(!in_sipe_groups
) {
1983 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
1984 purple_blist_remove_buddy(b
);
1987 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
1988 purple_blist_remove_buddy(b
);
1990 entry
= entry
->next
;
1994 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1996 int len
= msg
->bodylen
;
1998 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
2001 const gchar
*contacts_delta
;
2002 xmlnode
*group_node
;
2003 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
2007 /* Convert the contact from XML to Purple Buddies */
2008 isc
= xmlnode_from_str(msg
->body
, len
);
2013 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
2014 if (contacts_delta
) {
2015 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2019 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
2020 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2021 const char *name
= xmlnode_get_attrib(group_node
, "name");
2023 if (!strncmp(name
, "~", 1)) {
2025 name
= "Other Contacts";
2027 group
->name
= g_strdup(name
);
2028 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
2030 sipe_group_add(sip
, group
);
2033 // Make sure we have at least one group
2034 if (g_slist_length(sip
->groups
) == 0) {
2035 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2036 PurpleGroup
*purple_group
;
2038 group
->name
= g_strdup("Other Contacts");
2040 purple_group
= purple_group_new(group
->name
);
2041 purple_blist_add_group(purple_group
, NULL
);
2042 sip
->groups
= g_slist_append(sip
->groups
, group
);
2045 /* Parse contacts */
2046 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
2047 gchar
* uri
= g_strdup(xmlnode_get_attrib(item
, "uri"));
2048 gchar
* name
= g_strdup(xmlnode_get_attrib(item
, "name"));
2049 gchar
* groups
= g_strdup(xmlnode_get_attrib(item
, "groups"));
2050 gchar
* buddy_name
= g_strdup_printf("sip:%s", uri
);
2051 gchar
**item_groups
;
2052 struct sipe_group
*group
= NULL
;
2053 struct sipe_buddy
*buddy
= NULL
;
2056 // assign to group Other Contacts if nothing else received
2057 if(!groups
|| !strcmp("", groups
) ) {
2058 group
= sipe_group_find_by_name(sip
, "Other Contacts");
2059 groups
= group
? g_strdup_printf("%d", group
->id
) : "1";
2062 item_groups
= g_strsplit(groups
, " ", 0);
2064 while (item_groups
[i
]) {
2065 group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
2067 // If couldn't find the right group for this contact, just put them in the first group we have
2068 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2069 group
= sip
->groups
->data
;
2072 if (group
!= NULL
) {
2073 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2075 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2076 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2079 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2080 if (name
!= NULL
&& strlen(name
) != 0) {
2081 purple_blist_alias_buddy(b
, name
);
2086 buddy
= g_new0(struct sipe_buddy
, 1);
2087 buddy
->name
= g_strdup(b
->name
);
2088 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2091 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2093 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2095 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2100 } // while, contact groups
2101 g_strfreev(item_groups
);
2111 sipe_cleanup_local_blist(sip
);
2113 //subscribe to buddies
2114 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2115 if(sip
->batched_support
){
2116 sipe_subscribe_presence_batched(sip
, NULL
);
2119 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
2121 sip
->subscribed_buddies
= TRUE
;
2128 * Subscribe roaming contacts
2130 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2132 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2133 gchar
*tmp
= get_contact(sip
);
2134 gchar
*hdr
= g_strdup_printf(
2135 "Event: vnd-microsoft-roaming-contacts\r\n"
2136 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2137 "Supported: com.microsoft.autoextend\r\n"
2138 "Supported: ms-benotify\r\n"
2139 "Proxy-Require: ms-benotify\r\n"
2140 "Supported: ms-piggyback-first-notify\r\n"
2141 "Contact: %s\r\n", tmp
);
2144 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2149 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
, void *unused
)
2151 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2152 gchar
*tmp
= get_contact(sip
);
2153 gchar
*hdr
= g_strdup_printf(
2154 "Event: presence.wpending\r\n"
2155 "Accept: text/xml+msrtc.wpending\r\n"
2156 "Supported: com.microsoft.autoextend\r\n"
2157 "Supported: ms-benotify\r\n"
2158 "Proxy-Require: ms-benotify\r\n"
2159 "Supported: ms-piggyback-first-notify\r\n"
2160 "Contact: %s\r\n", tmp
);
2163 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2169 * Fires on deregistration event initiated by server.
2170 * [MS-SIPREGE] SIP extension.
2175 // Content-Type: text/registration-event
2176 // subscription-state: terminated;expires=0
2177 // ms-diagnostics-public: 4141;reason="User disabled"
2179 // deregistered;event=rejected
2181 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2183 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2184 gchar
*event
= NULL
;
2185 gchar
*reason
= NULL
;
2186 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
2188 warning
= warning
? warning
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2189 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2191 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2192 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2193 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2194 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2196 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2200 if (warning
!= NULL
) {
2201 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
2202 } else { // for LCS2005
2204 if (event
&& !g_ascii_strcasecmp(event
, "unregistered")) {
2205 error_id
= 4140; // [MS-SIPREGE]
2206 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2207 reason
= g_strdup(_("You have been signed off because you've signed in at another location"));
2208 } else if (event
&& !g_ascii_strcasecmp(event
, "rejected")) {
2210 reason
= g_strdup(_("User disabled")); // [MS-OCER]
2211 } else if (event
&& !g_ascii_strcasecmp(event
, "deactivated")) {
2213 reason
= g_strdup(_("User moved")); // [MS-OCER]
2217 warning
= g_strdup_printf(_("Unregistered by Server: %s."), reason
? reason
: _("no reason given"));
2220 sip
->gc
->wants_to_die
= TRUE
;
2221 purple_connection_error(sip
->gc
, warning
);
2226 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2228 const gchar
*contacts_delta
;
2231 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2237 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2240 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2249 * When we receive some self (BE) NOTIFY with a new subscriber
2250 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2254 static void sipe_process_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2260 char *display_name
= NULL
;
2261 PurpleBuddy
*pbuddy
;
2266 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2268 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2271 contact
= get_contact(sip
);
2272 to
= g_strdup_printf("sip:%s", sip
->username
);
2274 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
2276 const char *acknowledged
;
2280 user
= xmlnode_get_attrib(node
, "user");
2281 if (!user
) continue;
2282 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
2283 uri_user
= g_strdup_printf("sip:%s", user
);
2284 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri_user
);
2286 alias
= purple_buddy_get_local_alias(pbuddy
);
2287 uri_alias
= g_strdup_printf("sip:%s", alias
);
2288 display_name
= g_strdup(xmlnode_get_attrib(node
, "displayName"));
2289 if (display_name
&& !g_ascii_strcasecmp(uri_user
, uri_alias
)) { // 'bad' alias
2290 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user
, display_name
);
2291 purple_blist_alias_buddy(pbuddy
, display_name
);
2297 acknowledged
= xmlnode_get_attrib(node
, "acknowledged");
2298 if(!g_ascii_strcasecmp(acknowledged
,"false")){
2299 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user
);
2300 hdr
= g_strdup_printf(
2302 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
2304 body
= g_strdup_printf(
2305 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2306 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2307 "</setSubscribers>", user
);
2309 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
2320 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2322 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2323 gchar
*tmp
= get_contact(sip
);
2324 gchar
*hdr
= g_strdup_printf(
2325 "Event: vnd-microsoft-roaming-ACL\r\n"
2326 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2327 "Supported: com.microsoft.autoextend\r\n"
2328 "Supported: ms-benotify\r\n"
2329 "Proxy-Require: ms-benotify\r\n"
2330 "Supported: ms-piggyback-first-notify\r\n"
2331 "Contact: %s\r\n", tmp
);
2334 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2340 * To request for presence information about the user, access level settings that have already been configured by the user
2341 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2342 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2345 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2347 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2348 gchar
*tmp
= get_contact(sip
);
2349 gchar
*hdr
= g_strdup_printf(
2350 "Event: vnd-microsoft-roaming-self\r\n"
2351 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2352 "Supported: ms-benotify\r\n"
2353 "Proxy-Require: ms-benotify\r\n"
2354 "Supported: ms-piggyback-first-notify\r\n"
2356 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
2358 gchar
*body
=g_strdup(
2359 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2360 "<roaming type=\"categories\"/>"
2361 "<roaming type=\"containers\"/>"
2362 "<roaming type=\"subscribers\"/></roamingList>");
2365 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2374 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2376 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2377 gchar
*tmp
= get_contact(sip
);
2378 gchar
*hdr
= g_strdup_printf(
2379 "Event: vnd-microsoft-provisioning\r\n"
2380 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2381 "Supported: com.microsoft.autoextend\r\n"
2382 "Supported: ms-benotify\r\n"
2383 "Proxy-Require: ms-benotify\r\n"
2384 "Supported: ms-piggyback-first-notify\r\n"
2386 "Contact: %s\r\n", tmp
);
2389 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
2394 /** Subscription for provisioning information to help with initial
2395 * configuration. This subscription is a one-time query (denoted by the Expires header,
2396 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2397 * configuration, meeting policies, and policy settings that Communicator must enforce.
2398 * TODO: for what we need this information.
2401 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2403 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2404 gchar
*tmp
= get_contact(sip
);
2405 gchar
*hdr
= g_strdup_printf(
2406 "Event: vnd-microsoft-provisioning-v2\r\n"
2407 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2408 "Supported: com.microsoft.autoextend\r\n"
2409 "Supported: ms-benotify\r\n"
2410 "Proxy-Require: ms-benotify\r\n"
2411 "Supported: ms-piggyback-first-notify\r\n"
2414 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
2415 gchar
*body
= g_strdup(
2416 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2417 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2418 "<provisioningGroup name=\"ucPolicy\"/>"
2419 "</provisioningGroupList>");
2422 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2428 /* IM Session (INVITE and MESSAGE methods) */
2430 static struct sip_im_session
* find_im_session (struct sipe_account_data
*sip
, const char *who
)
2432 struct sip_im_session
*session
;
2434 if (sip
== NULL
|| who
== NULL
) {
2438 entry
= sip
->im_sessions
;
2440 session
= entry
->data
;
2441 if ((who
!= NULL
&& !strcmp(who
, session
->with
))) {
2444 entry
= entry
->next
;
2449 static struct sip_im_session
* find_or_create_im_session (struct sipe_account_data
*sip
, const char *who
)
2451 struct sip_im_session
*session
= find_im_session(sip
, who
);
2453 session
= g_new0(struct sip_im_session
, 1);
2454 session
->with
= g_strdup(who
);
2455 session
->unconfirmed_messages
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
2456 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2461 static void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2463 struct sip_dialog
*dialog
= session
->dialog
;
2466 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2469 entry
= dialog
->routes
;
2471 g_free(entry
->data
);
2472 entry
= g_slist_remove(entry
, entry
->data
);
2474 entry
= dialog
->supported
;
2476 g_free(entry
->data
);
2477 entry
= g_slist_remove(entry
, entry
->data
);
2479 g_free(dialog
->callid
);
2480 g_free(dialog
->ourtag
);
2481 g_free(dialog
->theirtag
);
2482 g_free(dialog
->theirepid
);
2483 g_free(dialog
->request
);
2485 g_free(session
->dialog
);
2487 entry
= session
->outgoing_message_queue
;
2489 g_free(entry
->data
);
2490 entry
= g_slist_remove(entry
, entry
->data
);
2493 g_hash_table_destroy(session
->unconfirmed_messages
);
2495 g_free(session
->with
);
2500 process_options_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2502 gboolean ret
= TRUE
;
2504 if (msg
->response
!= 200) {
2505 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
2509 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
2515 * Asks UA/proxy about its capabilities.
2517 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
2519 gchar
*to
= strstr(who
, "sip:") ? g_strdup(who
) : g_strdup_printf("sip:%s", who
);
2520 gchar
*contact
= get_contact(sip
);
2522 request
= g_strdup_printf(
2523 "Accept: application/sdp\r\n"
2524 "Contact: %s\r\n", contact
);
2528 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
2534 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2536 char *msg
, *msg_tmp
;
2537 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2538 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2540 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2541 "possibly because one or more persons are offline:\n%s") ,
2543 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2548 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2551 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2553 gboolean ret
= TRUE
;
2554 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2555 struct sip_im_session
* session
= find_im_session(sip
, with
);
2556 struct sip_dialog
*dialog
;
2562 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2567 dialog
= session
->dialog
;
2569 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2574 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2575 key
= g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
));
2577 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2579 if (msg
->response
!= 200) {
2580 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2582 sipe_present_message_undelivered_err(with
, sip
, message
);
2583 im_session_destroy(sip
, session
);
2586 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2587 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2588 key
, g_hash_table_size(session
->unconfirmed_messages
));
2594 sipe_im_process_queue(sip
, session
);
2598 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_im_session
* session
, const char *msg
)
2608 if (strncmp("sip:", session
->with
, 4)) {
2609 fullto
= g_strdup_printf("sip:%s", session
->with
);
2611 fullto
= g_strdup(session
->with
);
2614 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2615 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2617 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2620 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2623 msgr
= g_strdup("");
2626 tmp
= get_contact(sip
);
2627 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2628 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2629 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2630 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2635 send_sip_request(sip
->gc
, "MESSAGE", fullto
, fullto
, hdr
, msgtext
, session
->dialog
, process_message_response
);
2643 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2645 GSList
*entry
= session
->outgoing_message_queue
;
2647 if (session
->outgoing_invite
) return; //do not send messages until INVITE responded.
2650 char *key
= g_strdup_printf("<%s><%d><MESSAGE>", session
->dialog
->callid
, (session
->dialog
->cseq
) + 1);
2651 char *queued_msg
= entry
->data
;
2652 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
2653 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2654 key
, g_hash_table_size(session
->unconfirmed_messages
));
2656 sipe_send_message(sip
, session
, queued_msg
);
2657 entry
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2663 sipe_get_route_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2665 GSList
*hdr
= msg
->headers
;
2666 struct siphdrelement
*elem
;
2672 if(!g_ascii_strcasecmp(elem
->name
, "Record-Route"))
2674 gchar
*route
= sipmsg_find_part_of_header(elem
->value
, "<", ">", NULL
);
2675 dialog
->routes
= g_slist_append(dialog
->routes
, route
);
2677 hdr
= g_slist_next(hdr
);
2682 dialog
->routes
= g_slist_reverse(dialog
->routes
);
2687 dialog
->request
= dialog
->routes
->data
;
2688 dialog
->routes
= g_slist_remove(dialog
->routes
, dialog
->routes
->data
);
2691 contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Contact"), "<", ">", NULL
);
2692 dialog
->routes
= g_slist_append(dialog
->routes
, contact
);
2696 sipe_get_supported_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2698 GSList
*hdr
= msg
->headers
;
2699 struct siphdrelement
*elem
;
2703 if(!g_ascii_strcasecmp(elem
->name
, "Supported")
2704 && !g_slist_find_custom(dialog
->supported
, elem
->value
, (GCompareFunc
)g_ascii_strcasecmp
))
2706 dialog
->supported
= g_slist_append(dialog
->supported
, g_strdup(elem
->value
));
2709 hdr
= g_slist_next(hdr
);
2714 sipe_parse_dialog(struct sipmsg
* msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2716 gchar
*us
= outgoing
? "From" : "To";
2717 gchar
*them
= outgoing
? "To" : "From";
2719 g_free(dialog
->callid
);
2720 g_free(dialog
->ourtag
);
2721 g_free(dialog
->theirtag
);
2723 dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2724 dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, us
));
2725 dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, them
));
2726 if (!dialog
->theirepid
) {
2727 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", ";", NULL
);
2728 if (!dialog
->theirepid
) {
2729 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", NULL
, NULL
);
2733 // Catch a tag on the end of the To Header and get rid of it.
2734 if (strstr(dialog
->theirepid
, "tag=")) {
2735 dialog
->theirepid
= strtok(dialog
->theirepid
, ";");
2738 sipe_get_route_header(msg
, dialog
, outgoing
);
2739 sipe_get_supported_header(msg
, dialog
, outgoing
);
2744 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2746 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
2747 struct sip_im_session
* session
= find_im_session(sip
, with
);
2748 struct sip_dialog
*dialog
;
2754 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2759 dialog
= session
->dialog
;
2761 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2766 sipe_parse_dialog(msg
, dialog
, TRUE
);
2768 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2769 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
2771 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2773 if (msg
->response
!= 200) {
2774 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2776 sipe_present_message_undelivered_err(with
, sip
, message
);
2777 im_session_destroy(sip
, session
);
2783 send_sip_request(sip
->gc
, "ACK", session
->with
, session
->with
, NULL
, NULL
, dialog
, NULL
);
2784 session
->outgoing_invite
= NULL
;
2785 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
2786 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2787 if (session
->outgoing_message_queue
) {
2788 char *queued_msg
= session
->outgoing_message_queue
->data
;
2789 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2794 sipe_im_process_queue(sip
, session
);
2796 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2797 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2798 key
, g_hash_table_size(session
->unconfirmed_messages
));
2806 static void sipe_invite(struct sipe_account_data
*sip
, struct sip_im_session
*session
, const gchar
*msg_body
)
2816 char *ms_text_format
;
2821 if (session
->dialog
) {
2822 purple_debug_info("sipe", "session with %s already has a dialog open\n", session
->with
);
2826 session
->dialog
= g_new0(struct sip_dialog
, 1);
2827 session
->dialog
->callid
= gencallid();
2829 if (!(session
->dialog
->ourtag
)) {
2830 session
->dialog
->ourtag
= gentag();
2834 if (strstr(session
->with
, "sip:")) {
2835 to
= g_strdup(session
->with
);
2837 to
= g_strdup_printf("sip:%s", session
->with
);
2840 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
2841 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
2843 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2847 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2851 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
2852 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
2857 key
= g_strdup_printf("<%s><%d><INVITE>", session
->dialog
->callid
, (session
->dialog
->cseq
) + 1);
2858 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
2859 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2860 key
, g_hash_table_size(session
->unconfirmed_messages
));
2863 contact
= get_contact(sip
);
2864 /* from = g_strdup_printf("sip:%s", sip->username);*/
2865 hdr
= g_strdup_printf(
2866 /*"Supported: ms-delayed-accept\r\n"*/
2867 /*"Roster-Manager: <%s>\r\n"*/
2868 /*"EndPoints: <%s>, <%s>\r\n"*/
2869 /*"Supported: com.microsoft.rtc-multiparty\r\n"*/
2871 "Content-Type: application/sdp\r\n",
2872 contact
, ms_text_format
);
2873 g_free(ms_text_format
);
2875 body
= g_strdup_printf(
2877 "o=- 0 0 IN IP4 %s\r\n"
2881 "m=message %d sip null\r\n"
2882 "a=accept-types:text/plain text/html image/gif "
2883 "multipart/alternative application/im-iscomposing+xml\r\n",
2884 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
2886 session
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
2887 to
, to
, hdr
, body
, session
->dialog
, process_invite_response
);
2897 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2900 send_sip_request(sip
->gc
, "BYE", session
->with
, session
->with
, NULL
, NULL
, session
->dialog
, NULL
);
2901 im_session_destroy(sip
, session
);
2906 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
2908 struct sipe_account_data
*sip
= gc
->proto_data
;
2910 purple_debug_info("sipe", "conversation with %s closed\n", who
);
2911 im_session_close(sip
, find_im_session(sip
, who
));
2915 im_session_close_all (struct sipe_account_data
*sip
)
2917 GSList
*entry
= sip
->im_sessions
;
2919 im_session_close (sip
, entry
->data
);
2920 entry
= sip
->im_sessions
;
2924 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
2926 struct sipe_account_data
*sip
;
2928 struct sip_im_session
*session
;
2930 sip
= gc
->proto_data
;
2933 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
2935 session
= find_or_create_im_session(sip
, who
);
2937 // Queue the message
2938 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
2940 if (session
->dialog
&& session
->dialog
->callid
) {
2941 sipe_im_process_queue(sip
, session
);
2942 } else if (!session
->outgoing_invite
) {
2943 // Need to send the INVITE to get the outgoing dialog setup
2944 sipe_invite(sip
, session
, what
);
2951 /* End IM Session (INVITE and MESSAGE methods) */
2954 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
2956 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2957 struct sip_im_session
*session
;
2959 if (state
== PURPLE_NOT_TYPING
)
2962 session
= find_im_session(sip
, who
);
2964 if (session
&& session
->dialog
) {
2965 send_sip_request(gc
, "INFO", who
, who
,
2966 "Content-Type: application/xml\r\n",
2967 SIPE_SEND_TYPING
, session
->dialog
, NULL
);
2969 return SIPE_TYPING_SEND_TIMEOUT
;
2972 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
2974 GSList
*tmp
= sip
->transactions
;
2975 time_t currtime
= time(NULL
);
2977 struct transaction
*trans
= tmp
->data
;
2979 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime
-trans
->time
);
2980 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
2983 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
2985 sendout_sipmsg(sip
, trans
->msg
);
2992 static void do_reauthenticate_cb(struct sipe_account_data
*sip
, void *unused
)
2994 /* register again when security token expires */
2995 /* we have to start a new authentication as the security token
2996 * is almost expired by sending a not signed REGISTER message */
2997 purple_debug_info("sipe", "do a full reauthentication\n");
2998 sipe_auth_free(&sip
->registrar
);
2999 sip
->registerstatus
= 0;
3001 sip
->reauthenticate_set
= FALSE
;
3004 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3008 gboolean found
= FALSE
;
3010 from
= parse_from(sipmsg_find_header(msg
, "From"));
3014 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
3016 contenttype
= sipmsg_find_header(msg
, "Content-Type");
3017 if (!strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
3019 gchar
*html
= get_html_message(contenttype
, msg
->body
);
3020 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3022 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3025 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
3026 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3031 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3035 state
= xmlnode_get_child(isc
, "state");
3038 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3043 statedata
= xmlnode_get_data(state
);
3045 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
3046 else serv_got_typing_stopped(sip
->gc
, from
);
3051 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3055 purple_debug_info("sipe", "got unknown mime-type");
3056 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
3061 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3063 gchar
*ms_text_format
;
3066 gchar
*newTag
= gentag();
3067 struct sip_im_session
*session
;
3069 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? msg
->body
: "");
3071 // Only accept text invitations
3072 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
3073 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3077 // TODO There *must* be a better way to clean up the To header to add a tag...
3078 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3081 oldHeader
= sipmsg_find_header(msg
, "To");
3082 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
3083 sipmsg_remove_header(msg
, "To");
3084 sipmsg_add_header(msg
, "To", newHeader
);
3087 from
= parse_from(sipmsg_find_header(msg
, "From"));
3088 session
= find_or_create_im_session (sip
, from
);
3090 if (session
->dialog
) {
3091 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3093 session
->dialog
= g_new0(struct sip_dialog
, 1);
3095 sipe_parse_dialog(msg
, session
->dialog
, FALSE
);
3097 // This stuff is the same stuff that happens in the sipe_parse_dialog...
3098 //session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3099 //session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3100 //session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3101 //session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3103 if (!session
->dialog
->ourtag
) {
3104 session
->dialog
->ourtag
= newTag
;
3109 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3113 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3114 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
3115 if (ms_text_format
) {
3116 if (!strncmp(ms_text_format
, "text/plain", 10) || !strncmp(ms_text_format
, "text/html", 9)) {
3118 gchar
*html
= get_html_message(ms_text_format
, NULL
);
3120 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3122 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message received
3128 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3129 sipmsg_remove_header(msg
, "Ms-Text-Format");
3130 sipmsg_remove_header(msg
, "EndPoints");
3131 sipmsg_remove_header(msg
, "User-Agent");
3132 sipmsg_remove_header(msg
, "Roster-Manager");
3133 sipmsg_remove_header(msg
, "P-Asserted-Identity");
3135 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3136 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3138 body
= g_strdup_printf(
3140 "o=- 0 0 IN IP4 %s\r\n"
3144 "m=message %d sip sip:%s\r\n"
3145 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3146 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3147 sip
->realport
, sip
->username
);
3148 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3152 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3156 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3157 sipmsg_remove_header(msg
, "EndPoints");
3158 sipmsg_remove_header(msg
, "User-Agent");
3160 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3161 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3163 body
= g_strdup_printf(
3165 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3167 "c=IN IP4 0.0.0.0\r\n"
3169 "m=message %d sip sip:%s\r\n"
3170 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3171 sip
->realport
, sip
->username
);
3172 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3176 static void sipe_connection_cleanup(struct sipe_account_data
*);
3177 static void create_connection(struct sipe_account_data
*, gchar
*, int);
3179 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3182 const gchar
*expires_header
;
3184 GSList
*hdr
= msg
->headers
;
3186 struct siphdrelement
*elem
;
3188 expires_header
= sipmsg_find_header(msg
, "Expires");
3189 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
3190 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
3192 switch (msg
->response
) {
3195 sip
->registerstatus
= 0;
3197 gchar
*contact_hdr
= NULL
;
3203 if (!sip
->reregister_set
) {
3204 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
3205 sipe_schedule_action(action_name
, expires
, do_register_cb
, NULL
, sip
, NULL
);
3206 g_free(action_name
);
3207 sip
->reregister_set
= TRUE
;
3210 sip
->registerstatus
= 3;
3212 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3213 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3215 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3217 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3218 fill_auth(sip
, tmp
, &sip
->registrar
);
3220 if (!sip
->reauthenticate_set
) {
3221 /* we have to reauthenticate as our security token expires
3222 after eight hours (be five minutes early) */
3223 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
3224 guint reauth_timeout
= (8 * 3600) - 360;
3225 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, NULL
, sip
, NULL
);
3226 g_free(action_name
);
3227 sip
->reauthenticate_set
= TRUE
;
3230 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
3232 epid
= get_epid(sip
);
3233 uuid
= generateUUIDfromEPID(epid
);
3236 // There can be multiple Contact headers (one per location where the user is logged in) so
3237 // make sure to only get the one for this uuid
3238 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
3239 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
3240 if (valid_contact
) {
3241 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
3242 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3243 g_free(valid_contact
);
3246 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3251 g_free(sip
->contact
);
3253 sip
->contact
= g_strdup_printf("<%s>", gruu
);
3256 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3257 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
);
3259 sip
->msrtc_event_categories
= FALSE
;
3260 sip
->batched_support
= FALSE
;
3265 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
3266 if (!g_ascii_strcasecmp(elem
->value
, "msrtc-event-categories")) {
3267 sip
->msrtc_event_categories
= TRUE
;
3268 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->msrtc_event_categories
);
3270 if (!g_ascii_strcasecmp(elem
->value
, "adhoclist")) {
3271 sip
->batched_support
= TRUE
;
3272 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->batched_support
);
3275 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
3276 gchar
**caps
= g_strsplit(elem
->value
,",",0);
3279 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
3280 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
3285 hdr
= g_slist_next(hdr
);
3288 if (!sip
->subscribed
) { //do it just once, not every re-register
3289 if(!sip
->msrtc_event_categories
){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3290 //sipe_options_request(sip, sip->sipdomain);
3292 entry
= sip
->allow_events
;
3295 if (tmp
&& !g_ascii_strcasecmp(tmp
, "vnd-microsoft-roaming-contacts")) {
3296 sipe_subscribe_roaming_contacts(sip
, msg
);
3298 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-ACL")) {
3299 sipe_subscribe_roaming_acl(sip
, msg
);
3301 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-self")) {
3302 sipe_subscribe_roaming_self(sip
, msg
);
3304 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning-v2")) {
3305 sipe_subscribe_roaming_provisioning_v2(sip
, msg
);
3306 } else if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning")) { // LSC2005
3307 sipe_subscribe_roaming_provisioning(sip
, msg
);
3309 if (tmp
&& !g_ascii_strcasecmp(tmp
,"presence.wpending")) {
3310 sipe_subscribe_presence_wpending(sip
, msg
);
3312 entry
= entry
->next
;
3314 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
3315 sip
->subscribed
= TRUE
;
3318 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
3319 "timeout=", ";", NULL
);
3320 if (timeout
!= NULL
) {
3321 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
3322 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3323 sip
->keepalive_timeout
);
3326 sip
->keepalive_timeout
= 300;
3329 // Should we remove the transaction here?
3330 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
3331 transactions_remove(sip
, tc
);
3336 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
3338 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
3339 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
3343 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
3346 tmp
= g_strsplit(parts
[0], ":", 0);
3347 hostname
= g_strdup(tmp
[0]);
3348 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
3352 tmp
= g_strsplit(parts
[i
], "=", 0);
3354 if (g_strcasecmp("transport", tmp
[0]) == 0) {
3355 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
3356 transport
= SIPE_TRANSPORT_TCP
;
3357 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
3358 transport
= SIPE_TRANSPORT_UDP
;
3367 /* Close old connection */
3368 sipe_connection_cleanup(sip
);
3370 /* Create new connection */
3371 sip
->transport
= transport
;
3372 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3373 hostname
, port
, TRANSPORT_DESCRIPTOR
);
3374 create_connection(sip
, hostname
, port
);
3380 if (sip
->registerstatus
!= 2) {
3381 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
3382 if (sip
->registrar
.retries
> 3) {
3383 sip
->gc
->wants_to_die
= TRUE
;
3384 purple_connection_error(sip
->gc
, _("Wrong Password"));
3387 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3388 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3390 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3392 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3393 fill_auth(sip
, tmp
, &sip
->registrar
);
3394 sip
->registerstatus
= 2;
3395 if (sip
->account
->disconnecting
) {
3396 do_register_exp(sip
, 0);
3404 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
3405 if (warning
!= NULL
) {
3407 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3409 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
3410 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
3413 warning
= g_strdup(_("You have been rejected by the server"));
3416 sip
->gc
->wants_to_die
= TRUE
;
3417 purple_connection_error(sip
->gc
, warning
);
3424 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3425 if (warning
!= NULL
) {
3426 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3427 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
3430 warning
= g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3433 sip
->gc
->wants_to_die
= TRUE
;
3434 purple_connection_error(sip
->gc
, warning
);
3441 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3442 if (warning
!= NULL
) {
3443 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3444 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
3447 warning
= g_strdup(_("Service unavailable: no reason given"));
3450 sip
->gc
->wants_to_die
= TRUE
;
3451 purple_connection_error(sip
->gc
, warning
);
3460 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3463 xmlnode
*xn_categories
;
3464 xmlnode
*xn_category
;
3466 const char *activity
= NULL
;
3468 xn_categories
= xmlnode_from_str(data
, len
);
3469 uri
= xmlnode_get_attrib(xn_categories
, "uri");
3471 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
3473 xn_category
= xmlnode_get_next_twin(xn_category
) )
3475 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
3477 if (!strcmp(attrVar
, "note"))
3480 struct sipe_buddy
*sbuddy
;
3481 xn_node
= xmlnode_get_child(xn_category
, "note");
3482 if (!xn_node
) continue;
3483 xn_node
= xmlnode_get_child(xn_node
, "body");
3484 if (!xn_node
) continue;
3486 note
= xmlnode_get_data(xn_node
);
3489 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3493 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri
,note
);
3494 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3495 sbuddy
->annotation
= g_strdup(note
);
3500 else if(!strcmp(attrVar
, "state"))
3504 xn_node
= xmlnode_get_child(xn_category
, "state");
3505 if (!xn_node
) continue;
3506 xn_node
= xmlnode_get_child(xn_node
, "availability");
3507 if (!xn_node
) continue;
3509 data
= xmlnode_get_data(xn_node
);
3514 activity
= SIPE_STATUS_ID_UNKNOWN
;
3515 else if (avail
< 4500)
3516 activity
= SIPE_STATUS_ID_AVAILABLE
;
3517 else if (avail
< 6000)
3518 activity
= SIPE_STATUS_ID_BRB
;
3519 else if (avail
< 7500)
3520 activity
= SIPE_STATUS_ID_ONPHONE
;
3521 else if (avail
< 9000)
3522 activity
= SIPE_STATUS_ID_BUSY
;
3523 else if (avail
< 12000)
3524 activity
= SIPE_STATUS_ID_DND
;
3525 else if (avail
< 18000)
3526 activity
= SIPE_STATUS_ID_AWAY
;
3528 activity
= SIPE_STATUS_ID_OFFLINE
;
3532 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity
);
3533 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
3536 xmlnode_free(xn_categories
);
3539 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3542 xmlnode
*xn_resource
;
3543 GHashTable
*servers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
3547 GHashTableIter iter
;
3549 xn_list
= xmlnode_from_str(data
, len
);
3551 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
3553 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
3555 const char *uri
, *state
;
3556 xmlnode
*xn_instance
;
3558 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
3559 if (!xn_instance
) continue;
3561 uri
= xmlnode_get_attrib(xn_resource
, "uri");
3562 state
= xmlnode_get_attrib(xn_instance
, "state");
3563 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri
, state
);
3565 if (strstr(state
, "resubscribe")) {
3566 const char *poolFqdn
= xmlnode_get_attrib(xn_instance
, "poolFqdn");
3567 struct sipe_buddy
*sbuddy
;
3568 if (poolFqdn
) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
3569 gchar
*user
= g_strdup(uri
);
3570 host
= g_strdup(poolFqdn
);
3571 server
= g_hash_table_lookup(servers
, host
);
3572 server
= g_slist_append(server
, user
);
3573 g_hash_table_insert(servers
, host
, server
);
3575 sipe_subscribe_presence_single(sip
, (void *) uri
);
3577 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3579 sbuddy
->resubscribed
= TRUE
;
3584 g_hash_table_iter_init(&iter
, servers
);
3585 while (g_hash_table_iter_next(&iter
, (gpointer
) &host
, (gpointer
) &server
)) {
3586 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
3587 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host
);
3588 payload
->host
= g_strdup(host
);
3589 payload
->buddies
= server
;
3590 sipe_subscribe_presence_batched_routed(sip
, payload
);
3591 sipe_subscribe_presence_batched_routed_free(payload
);
3593 g_hash_table_destroy(servers
);
3595 xmlnode_free(xn_list
);
3598 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3602 gchar
*activity
= NULL
;
3604 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
3605 gboolean isonline
= FALSE
;
3606 xmlnode
*display_name_node
;
3608 pidf
= xmlnode_from_str(data
, len
);
3610 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
3614 uri
= xmlnode_get_attrib(pidf
, "entity");
3616 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
3618 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3619 basicstatus
= xmlnode_get_child(status
, "basic");
3624 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3629 getbasic
= xmlnode_get_data(basicstatus
);
3631 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3636 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
3637 if (strstr(getbasic
, "open")) {
3642 display_name_node
= xmlnode_get_child(pidf
, "display-name");
3643 // updating display name if alias was just URI
3644 if (display_name_node
) {
3645 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3646 GSList
*entry
= buddies
;
3647 PurpleBuddy
*p_buddy
;
3648 char * display_name
= xmlnode_get_data(display_name_node
);
3651 const char *server_alias
;
3654 p_buddy
= entry
->data
;
3656 alias
= (char *)purple_buddy_get_alias(p_buddy
);
3657 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
3658 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
3659 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3660 purple_blist_alias_buddy(p_buddy
, display_name
);
3664 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3666 ( (server_alias
&& strcmp(display_name
, server_alias
))
3667 || !server_alias
|| strlen(server_alias
) == 0 )
3669 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3672 entry
= entry
->next
;
3674 g_free(display_name
);
3677 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
3678 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3679 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
3680 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
3681 activity
= xmlnode_get_data(basicstatus
);
3682 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
3689 const gchar
* status_id
= NULL
;
3691 if (strstr(activity
, "busy")) {
3692 status_id
= SIPE_STATUS_ID_BUSY
;
3693 } else if (strstr(activity
, "away")) {
3694 status_id
= SIPE_STATUS_ID_AWAY
;
3699 status_id
= SIPE_STATUS_ID_AVAILABLE
;
3702 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
3703 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
3705 purple_prpl_got_user_status(sip
->account
, uri
, SIPE_STATUS_ID_OFFLINE
, NULL
);
3712 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3714 const char *availability
;
3715 const char *activity
;
3716 const char *display_name
= NULL
;
3717 const char *activity_name
= NULL
;
3722 struct sipe_buddy
*sbuddy
;
3724 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
3726 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
3727 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
3728 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
3729 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
3730 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
3731 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
3732 xmlnode
*xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
):NULL
;
3733 const char *avail
= xn_state
? xmlnode_get_attrib(xn_state
, "avail") : NULL
;
3735 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
3736 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
3737 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
3738 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
3739 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
3740 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
3742 name
= xmlnode_get_attrib(xn_presentity
, "uri");
3743 uri
= g_strdup_printf("sip:%s", name
);
3744 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
3745 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
3747 // updating display name if alias was just URI
3748 if (xn_display_name
) {
3749 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3750 GSList
*entry
= buddies
;
3751 PurpleBuddy
*p_buddy
;
3752 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
3755 const char *email_str
, *server_alias
;
3757 p_buddy
= entry
->data
;
3759 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
3760 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3761 purple_blist_alias_buddy(p_buddy
, display_name
);
3764 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3766 ( (server_alias
&& strcmp(display_name
, server_alias
))
3767 || !server_alias
|| strlen(server_alias
) == 0 )
3769 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3773 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
3774 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
3775 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
3779 entry
= entry
->next
;
3783 avl
= atoi(availability
);
3784 act
= atoi(activity
);
3786 if(sip
->msrtc_event_categories
){
3787 if (act
== 100 && avl
== 0)
3788 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3789 else if (act
== 100 && avl
== 300)
3790 activity_name
= SIPE_STATUS_ID_AWAY
;
3791 else if (act
== 300 && avl
== 300)
3792 activity_name
= SIPE_STATUS_ID_BRB
;
3793 else if (act
== 400 && avl
== 300)
3794 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3795 else if (act
== 500 && act
== 300)
3796 activity_name
= SIPE_STATUS_ID_ONPHONE
;
3797 else if (act
== 600 && avl
== 300)
3798 activity_name
= SIPE_STATUS_ID_BUSY
;
3799 else if (act
== 0 && avl
== 0){ //MSRTC elements are zero
3800 if(avail
){ //Check for LegacyInterop elements
3803 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3804 else if (avl
== 3500)
3805 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3806 else if (avl
== 15500)
3807 activity_name
= SIPE_STATUS_ID_AWAY
;
3808 else if (avl
== 6500)
3809 activity_name
= SIPE_STATUS_ID_BUSY
;
3810 else if (avl
== 12500)
3811 activity_name
= SIPE_STATUS_ID_BRB
;
3816 if(activity_name
== NULL
){
3818 activity_name
= SIPE_STATUS_ID_AWAY
;
3819 else if (act
<= 150)
3820 activity_name
= SIPE_STATUS_ID_LUNCH
;
3821 else if (act
<= 300)
3822 activity_name
= SIPE_STATUS_ID_BRB
;
3823 else if (act
<= 400)
3824 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3825 else if (act
<= 500)
3826 activity_name
= SIPE_STATUS_ID_ONPHONE
;
3827 else if (act
<= 600)
3828 activity_name
= SIPE_STATUS_ID_BUSY
;
3830 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3833 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3836 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3839 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3840 sbuddy
->annotation
= NULL
;
3841 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
3843 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
3844 sbuddy
->device_name
= NULL
;
3845 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
3848 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
3849 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
3851 xmlnode_free(xn_presentity
);
3855 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3857 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3859 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
3861 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
3862 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
3864 const char *content
= msg
->body
;
3865 unsigned length
= msg
->bodylen
;
3866 PurpleMimeDocument
*mime
= NULL
;
3868 if (strstr(ctype
, "multipart"))
3870 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3871 const char *content_type
;
3873 mime
= purple_mime_document_parse(doc
);
3874 parts
= purple_mime_document_get_parts(mime
);
3876 content
= purple_mime_part_get_data(parts
->data
);
3877 length
= purple_mime_part_get_length(parts
->data
);
3878 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
3879 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
3881 process_incoming_notify_rlmi_resub(sip
, content
, length
);
3883 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
3885 process_incoming_notify_msrtc(sip
, content
, length
);
3889 process_incoming_notify_rlmi(sip
, content
, length
);
3891 parts
= parts
->next
;
3897 purple_mime_document_free(mime
);
3900 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
3902 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
3904 else if(strstr(ctype
, "application/rlmi+xml"))
3906 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
3909 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
3911 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
3915 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
3919 static void sipe_process_presence_timeout(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gchar
*who
, int timeout
)
3921 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3922 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
3924 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype
? ctype
: "");
3927 strstr(ctype
, "multipart") &&
3928 (strstr(ctype
, "application/rlmi+xml") ||
3929 strstr(ctype
, "application/msrtc-event-categories+xml"))) {
3930 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3931 PurpleMimeDocument
*mime
= purple_mime_document_parse(doc
);
3932 GList
*parts
= purple_mime_document_get_parts(mime
);
3933 GSList
*buddies
= NULL
;
3934 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
3937 xmlnode
*xml
= xmlnode_from_str(purple_mime_part_get_data(parts
->data
),
3938 purple_mime_part_get_length(parts
->data
));
3939 gchar
*uri
= g_strdup(xmlnode_get_attrib(xml
, "uri"));
3941 if (strstr(uri
, "sip:") == NULL
) {
3943 uri
= g_strdup_printf("sip:%s", tmp
);
3946 buddies
= g_slist_append(buddies
, uri
);
3949 parts
= parts
->next
;
3952 if (mime
) purple_mime_document_free(mime
);
3954 payload
->host
= who
;
3955 payload
->buddies
= buddies
;
3956 sipe_schedule_action(action_name
, timeout
,
3957 sipe_subscribe_presence_batched_routed
,
3958 sipe_subscribe_presence_batched_routed_free
,
3960 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who
, timeout
);
3963 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, who
);
3964 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
, timeout
);
3966 g_free(action_name
);
3970 * Dispatcher for all incoming subscription information
3971 * whether it comes from NOTIFY, BENOTIFY requests or
3972 * piggy-backed to subscription's OK responce.
3974 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3975 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3977 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
3979 gchar
*event
= sipmsg_find_header(msg
, "Event");
3980 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
3983 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
3984 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
3988 const gchar
*expires_header
;
3989 expires_header
= sipmsg_find_header(msg
, "Expires");
3990 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
3991 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout
);
3992 timeout
= (timeout
- 60) > 60 ? (timeout
- 60) : timeout
; // 1 min ahead of expiration
3995 if (!subscription_state
|| strstr(subscription_state
, "active"))
3997 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
3999 sipe_process_presence(sip
, msg
);
4001 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
4003 sipe_process_roaming_contacts(sip
, msg
, NULL
);
4005 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self") )
4007 sipe_process_roaming_self(sip
, msg
);
4009 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
4011 sipe_process_roaming_acl(sip
, msg
);
4013 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
4015 sipe_process_presence_wpending(sip
, msg
);
4019 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
4023 //The server sends a (BE)NOTIFY with the status 'terminated'
4024 if (request
&& subscription_state
&& strstr(subscription_state
, "terminated") ) {
4025 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4026 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
4030 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
4031 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4032 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4034 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4035 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4036 g_free(action_name);
4038 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4039 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4041 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4042 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4043 g_free(action_name);
4046 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
4047 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
4049 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
4050 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, NULL
, sip
, NULL
);
4051 g_free(action_name
);
4053 else if (!g_ascii_strcasecmp(event
, "presence") &&
4054 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
4056 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
4057 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
4058 if(sip
->batched_support
) {
4059 gchar
*my_self
= g_strdup_printf("sip:%s",sip
->username
);
4060 if(!g_ascii_strcasecmp(who
, my_self
)){
4061 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_batched
, NULL
, sip
, NULL
);
4062 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout
);
4063 g_free(who
); /* unused */
4066 sipe_process_presence_timeout(sip
, msg
, who
, timeout
);
4071 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, who
);
4072 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
,timeout
);
4074 g_free(action_name
);
4075 /* "who" will be freed by the action we just scheduled */
4079 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
4081 sipe_process_registration_notify(sip
, msg
);
4084 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4085 if (request
&& !benotify
)
4087 sipmsg_remove_header(msg
, "Expires");
4088 sipmsg_remove_header(msg
, "subscription-state");
4089 sipmsg_remove_header(msg
, "Event");
4090 sipmsg_remove_header(msg
, "Require");
4091 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4098 static gchar* gen_xpidf(struct sipe_account_data *sip)
4100 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4102 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4103 "<display name=\"sip:%s\"/>\r\n"
4104 "<atom id=\"1234\">\r\n"
4105 "<address uri=\"sip:%s\">\r\n"
4106 "<status status=\"%s\"/>\r\n"
4119 static gchar* gen_pidf(struct sipe_account_data *sip)
4121 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4122 "<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"
4123 "<tuple id=\"0\">\r\n"
4125 "<basic>open</basic>\r\n"
4126 "<ep:activities>\r\n"
4127 " <ep:activity>%s</ep:activity>\r\n"
4131 "<ci:display-name>%s</ci:display-name>\r\n"
4140 static void send_presence_soap(struct sipe_account_data
*sip
, const char * note
)
4142 int availability
= 300; // online
4143 int activity
= 400; // Available
4146 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
)) {
4148 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4150 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4152 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4154 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4156 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4158 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_INVISIBLE
) ||
4159 !strcmp(sip
->status
, SIPE_STATUS_ID_OFFLINE
)) {
4160 availability
= 0; // offline
4163 activity
= 400; // available
4166 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
4167 //@TODO: send user data - state; add hostname in upper case
4168 body
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
4169 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
4175 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4177 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4178 if (msg
->response
== 200) {
4179 sip
->status_version
= 0;
4180 send_presence_status(sip
);
4186 process_send_presence_category_publish_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4188 if (msg
->response
== 409) {
4189 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4190 // TODO need to parse the version #'s?
4191 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
4192 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
4196 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg
->body
);
4198 tmp
= get_contact(sip
);
4199 hdr
= g_strdup_printf("Contact: %s\r\n"
4200 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4202 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
4212 static void send_presence_category_publish(struct sipe_account_data
*sip
, const char * note
)
4219 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
) ||
4220 !strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4222 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_DND
)) {
4224 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4226 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4228 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4230 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4232 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_UNKNOWN
)) {
4235 // Offline or invisible
4239 uri
= g_strdup_printf("sip:%s", sip
->username
);
4240 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
4241 sip
->status_version
, code
,
4242 sip
->status_version
, code
,
4243 sip
->status_version
, note
? note
: "",
4244 sip
->status_version
, note
? note
: "",
4245 sip
->status_version
, note
? note
: ""
4247 sip
->status_version
++;
4249 tmp
= get_contact(sip
);
4250 hdr
= g_strdup_printf("Contact: %s\r\n"
4251 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4253 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
4261 static void send_presence_status(struct sipe_account_data
*sip
)
4263 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
4265 if (!status
) return;
4267 note
= purple_status_get_attr_string(status
, "message");
4269 if(sip
->msrtc_event_categories
){
4270 send_presence_category_publish(sip
, note
);
4272 send_presence_soap(sip
, note
);
4276 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
4278 gboolean found
= FALSE
;
4279 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
4280 if (msg
->response
== 0) { /* request */
4281 if (!strcmp(msg
->method
, "MESSAGE")) {
4282 process_incoming_message(sip
, msg
);
4284 } else if (!strcmp(msg
->method
, "NOTIFY")) {
4285 purple_debug_info("sipe","send->process_incoming_notify\n");
4286 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
4288 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
4289 purple_debug_info("sipe","send->process_incoming_benotify\n");
4290 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
4292 } else if (!strcmp(msg
->method
, "INVITE")) {
4293 process_incoming_invite(sip
, msg
);
4295 } else if (!strcmp(msg
->method
, "OPTIONS")) {
4296 process_incoming_options(sip
, msg
);
4298 } else if (!strcmp(msg
->method
, "INFO")) {
4300 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4302 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
4305 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4307 } else if (!strcmp(msg
->method
, "ACK")) {
4308 // ACK's don't need any response
4310 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
4311 // LCS 2005 sends us these - just respond 200 OK
4313 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4314 } else if (!strcmp(msg
->method
, "BYE")) {
4315 struct sip_im_session
*session
;
4317 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4319 from
= parse_from(sipmsg_find_header(msg
, "From"));
4320 session
= find_im_session (sip
, from
);
4324 // TODO Let the user know the other user left the conversation?
4325 im_session_destroy(sip
, session
);
4330 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4332 } else { /* response */
4333 struct transaction
*trans
= transactions_find(sip
, msg
);
4335 if (msg
->response
== 407) {
4336 gchar
*resend
, *auth
, *ptmp
;
4338 if (sip
->proxy
.retries
> 30) return;
4339 sip
->proxy
.retries
++;
4340 /* do proxy authentication */
4342 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
4344 fill_auth(sip
, ptmp
, &sip
->proxy
);
4345 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
4346 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4347 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
4349 resend
= sipmsg_to_string(trans
->msg
);
4350 /* resend request */
4351 sendout_pkt(sip
->gc
, resend
);
4354 if (msg
->response
== 100 || msg
->response
== 180) {
4355 /* ignore provisional response */
4356 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
4358 sip
->proxy
.retries
= 0;
4359 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
4360 if (msg
->response
== 401)
4362 sip
->registrar
.retries
++;
4363 sip
->registrar
.expires
= 0;
4367 sip
->registrar
.retries
= 0;
4369 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
4371 if (msg
->response
== 401) {
4372 gchar
*resend
, *auth
, *ptmp
;
4374 if (sip
->registrar
.retries
> 4) return;
4375 sip
->registrar
.retries
++;
4377 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
4378 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
4380 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
4383 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
4385 fill_auth(sip
, ptmp
, &sip
->registrar
);
4386 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
4387 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4388 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
4390 //sipmsg_remove_header(trans->msg, "Authorization");
4391 //sipmsg_add_header(trans->msg, "Authorization", auth);
4393 resend
= sipmsg_to_string(trans
->msg
);
4394 /* resend request */
4395 sendout_pkt(sip
->gc
, resend
);
4400 if (trans
->callback
) {
4401 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
4402 /* call the callback to process response*/
4403 (trans
->callback
)(sip
, msg
, trans
);
4405 /* Not sure if this is needed or what needs to be done
4406 but transactions seem to be removed prematurely so
4407 this only removes them if the response is 200 OK */
4408 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
4409 /*Has a bug and it's unneccesary*/
4410 /*transactions_remove(sip, trans);*/
4416 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
4420 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
4424 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
4432 /* according to the RFC remove CRLF at the beginning */
4433 while (*cur
== '\r' || *cur
== '\n') {
4436 if (cur
!= conn
->inbuf
) {
4437 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
4438 conn
->inbufused
= strlen(conn
->inbuf
);
4441 /* Received a full Header? */
4442 sip
->processing_input
= TRUE
;
4443 while (sip
->processing_input
&&
4444 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
4445 time_t currtime
= time(NULL
);
4448 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
4449 msg
= sipmsg_parse_header(conn
->inbuf
);
4452 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
4453 if (restlen
>= msg
->bodylen
) {
4454 dummy
= g_malloc(msg
->bodylen
+ 1);
4455 memcpy(dummy
, cur
, msg
->bodylen
);
4456 dummy
[msg
->bodylen
] = '\0';
4458 cur
+= msg
->bodylen
;
4459 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
4460 conn
->inbufused
= strlen(conn
->inbuf
);
4462 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4463 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
4469 purple_debug_info("sipe", "body:\n%s", msg->body);
4472 // Verify the signature before processing it
4473 if (sip
->registrar
.gssapi_context
) {
4474 struct sipmsg_breakdown msgbd
;
4475 gchar
*signature_input_str
;
4478 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
4479 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
4481 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
4483 if (rspauth
!= NULL
) {
4484 if (!sip_sec_verify_signature(sip
->registrar
.gssapi_context
, signature_input_str
, rspauth
)) {
4485 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
4486 process_input_message(sip
, msg
);
4488 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid.\n");
4489 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
4490 sip
->gc
->wants_to_die
= TRUE
;
4492 } else if (msg
->response
== 401) {
4493 purple_connection_error(sip
->gc
, _("Wrong Password"));
4494 sip
->gc
->wants_to_die
= TRUE
;
4496 g_free(signature_input_str
);
4499 sipmsg_breakdown_free(&msgbd
);
4501 process_input_message(sip
, msg
);
4508 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
4510 PurpleConnection
*gc
= data
;
4511 struct sipe_account_data
*sip
= gc
->proto_data
;
4516 static char buffer
[65536];
4517 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
4519 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
4520 msg
= sipmsg_parse_msg(buffer
);
4521 if (msg
) process_input_message(sip
, msg
);
4525 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
4527 struct sipe_account_data
*sip
= gc
->proto_data
;
4528 PurpleSslConnection
*gsc
= sip
->gsc
;
4530 purple_debug_error("sipe", "%s",debug
);
4531 purple_connection_error(gc
, msg
);
4533 /* Invalidate this connection. Next send will open a new one */
4535 connection_remove(sip
, gsc
->fd
);
4536 purple_ssl_close(gsc
);
4542 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4544 PurpleConnection
*gc
= data
;
4545 struct sipe_account_data
*sip
;
4546 struct sip_connection
*conn
;
4548 gboolean firstread
= TRUE
;
4550 /* NOTE: This check *IS* necessary */
4551 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
4552 purple_ssl_close(gsc
);
4556 sip
= gc
->proto_data
;
4557 conn
= connection_find(sip
, gsc
->fd
);
4559 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4560 gc
->wants_to_die
= TRUE
;
4561 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
4565 /* Read all available data from the SSL connection */
4567 /* Increase input buffer size as needed */
4568 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4569 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4570 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4571 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
4574 /* Try to read as much as there is space left in the buffer */
4575 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
4576 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
4578 if (len
< 0 && errno
== EAGAIN
) {
4579 /* Try again later */
4581 } else if (len
< 0) {
4582 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
4584 } else if (firstread
&& (len
== 0)) {
4585 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
4589 conn
->inbufused
+= len
;
4592 /* Equivalence indicates that there is possibly more data to read */
4593 } while (len
== readlen
);
4595 conn
->inbuf
[conn
->inbufused
] = '\0';
4596 process_input(sip
, conn
);
4600 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4602 PurpleConnection
*gc
= data
;
4603 struct sipe_account_data
*sip
= gc
->proto_data
;
4605 struct sip_connection
*conn
= connection_find(sip
, source
);
4607 purple_debug_error("sipe", "Connection not found!\n");
4611 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4612 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4613 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4616 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
4618 if (len
< 0 && errno
== EAGAIN
)
4620 else if (len
<= 0) {
4621 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4622 connection_remove(sip
, source
);
4623 if (sip
->fd
== source
) sip
->fd
= -1;
4627 conn
->inbufused
+= len
;
4628 conn
->inbuf
[conn
->inbufused
] = '\0';
4630 process_input(sip
, conn
);
4633 /* Callback for new connections on incoming TCP port */
4634 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4636 PurpleConnection
*gc
= data
;
4637 struct sipe_account_data
*sip
= gc
->proto_data
;
4638 struct sip_connection
*conn
;
4640 int newfd
= accept(source
, NULL
, NULL
);
4642 conn
= connection_create(sip
, newfd
);
4644 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4647 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
4649 PurpleConnection
*gc
= data
;
4650 struct sipe_account_data
*sip
;
4651 struct sip_connection
*conn
;
4653 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4661 purple_connection_error(gc
, _("Could not connect"));
4665 sip
= gc
->proto_data
;
4667 sip
->last_keepalive
= time(NULL
);
4669 conn
= connection_create(sip
, source
);
4673 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4676 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4678 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
4679 if (sip
== NULL
) return;
4684 static guint
sipe_ht_hash_nick(const char *nick
)
4686 char *lc
= g_utf8_strdown(nick
, -1);
4687 guint bucket
= g_str_hash(lc
);
4693 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
4695 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
4698 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
4700 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4702 sip
->listen_data
= NULL
;
4704 if (listenfd
== -1) {
4705 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4711 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
4712 sip
->listenfd
= sip
->fd
;
4714 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
4716 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
4720 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
4722 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4725 sip
->query_data
= NULL
;
4727 if (!hosts
|| !hosts
->data
) {
4728 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
4732 addr_size
= GPOINTER_TO_INT(hosts
->data
);
4733 hosts
= g_slist_remove(hosts
, hosts
->data
);
4734 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
4735 g_free(hosts
->data
);
4736 hosts
= g_slist_remove(hosts
, hosts
->data
);
4738 hosts
= g_slist_remove(hosts
, hosts
->data
);
4739 g_free(hosts
->data
);
4740 hosts
= g_slist_remove(hosts
, hosts
->data
);
4743 /* create socket for incoming connections */
4744 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
4745 sipe_udp_host_resolved_listen_cb
, sip
);
4746 if (sip
->listen_data
== NULL
) {
4747 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4752 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
4755 PurpleConnection
*gc
= data
;
4756 struct sipe_account_data
*sip
;
4758 /* If the connection is already disconnected, we don't need to do anything else */
4759 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4762 sip
= gc
->proto_data
;
4767 case PURPLE_SSL_CONNECT_FAILED
:
4768 purple_connection_error(gc
, _("Connection Failed"));
4770 case PURPLE_SSL_HANDSHAKE_FAILED
:
4771 purple_connection_error(gc
, _("SSL Handshake Failed"));
4773 case PURPLE_SSL_CERTIFICATE_INVALID
:
4774 purple_connection_error(gc
, _("SSL Certificate Invalid"));
4780 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
4782 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4783 PurpleProxyConnectData
*connect_data
;
4785 sip
->listen_data
= NULL
;
4787 sip
->listenfd
= listenfd
;
4788 if (sip
->listenfd
== -1) {
4789 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4793 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
4794 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4795 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
4796 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
4797 sipe_newconn_cb
, sip
->gc
);
4798 purple_debug_info("sipe", "connecting to %s port %d\n",
4799 sip
->realhostname
, sip
->realport
);
4800 /* open tcp connection to the server */
4801 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
4802 sip
->realport
, login_cb
, sip
->gc
);
4804 if (connect_data
== NULL
) {
4805 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
4810 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
4812 PurpleAccount
*account
= sip
->account
;
4813 PurpleConnection
*gc
= sip
->gc
;
4815 if (purple_account_get_bool(account
, "useport", FALSE
)) {
4816 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
4817 port
= purple_account_get_int(account
, "port", 0);
4819 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
4822 sip
->realhostname
= hostname
;
4823 sip
->realport
= port
;
4825 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
4828 /* TODO: is there a good default grow size? */
4829 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
4830 sip
->txbuf
= purple_circ_buffer_new(0);
4832 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
4834 if (!purple_ssl_is_supported()) {
4835 gc
->wants_to_die
= TRUE
;
4836 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4840 purple_debug_info("sipe", "using SSL\n");
4842 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
4843 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
4844 if (sip
->gsc
== NULL
) {
4845 purple_connection_error(gc
, _("Could not create SSL context"));
4848 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
4850 purple_debug_info("sipe", "using UDP\n");
4852 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
4853 if (sip
->query_data
== NULL
) {
4854 purple_connection_error(gc
, _("Could not resolve hostname"));
4858 purple_debug_info("sipe", "using TCP\n");
4859 /* create socket for incoming connections */
4860 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
4861 sipe_tcp_connect_listen_cb
, sip
);
4862 if (sip
->listen_data
== NULL
) {
4863 purple_connection_error(gc
, _("Could not create listen socket"));
4869 /* Service list for autodection */
4870 static const struct sipe_service_data service_autodetect
[] = {
4871 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4872 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4873 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4874 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4878 /* Service list for SSL/TLS */
4879 static const struct sipe_service_data service_tls
[] = {
4880 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4881 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4885 /* Service list for TCP */
4886 static const struct sipe_service_data service_tcp
[] = {
4887 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4888 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4892 /* Service list for UDP */
4893 static const struct sipe_service_data service_udp
[] = {
4894 { "sip", "udp", SIPE_TRANSPORT_UDP
},
4898 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
4899 static void resolve_next_service(struct sipe_account_data
*sip
,
4900 const struct sipe_service_data
*start
)
4903 sip
->service_data
= start
;
4905 sip
->service_data
++;
4906 if (sip
->service_data
->service
== NULL
) {
4908 /* Try connecting to the SIP hostname directly */
4909 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
4910 if (sip
->auto_transport
) {
4911 // If SSL is supported, default to using it; OCS servers aren't configured
4912 // by default to accept TCP
4913 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4914 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
4915 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
4918 hostname
= g_strdup(sip
->sipdomain
);
4919 create_connection(sip
, hostname
, 0);
4924 /* Try to resolve next service */
4925 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
4926 sip
->service_data
->transport
,
4931 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
4933 struct sipe_account_data
*sip
= data
;
4935 sip
->srv_query_data
= NULL
;
4937 /* find the host to connect to */
4939 gchar
*hostname
= g_strdup(resp
->hostname
);
4940 int port
= resp
->port
;
4941 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4945 sip
->transport
= sip
->service_data
->type
;
4947 create_connection(sip
, hostname
, port
);
4949 resolve_next_service(sip
, NULL
);
4953 static void sipe_login(PurpleAccount
*account
)
4955 PurpleConnection
*gc
;
4956 struct sipe_account_data
*sip
;
4957 gchar
**signinname_login
, **userserver
, **domain_user
, **user_realm
;
4958 const char *transport
;
4960 const char *username
= purple_account_get_username(account
);
4961 gc
= purple_account_get_connection(account
);
4963 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
4964 gc
->wants_to_die
= TRUE
;
4965 purple_connection_error(gc
, _("SIP Exchange username contains invalid characters"));
4969 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
4970 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
4971 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
4973 sip
->account
= account
;
4974 sip
->reregister_set
= FALSE
;
4975 sip
->reauthenticate_set
= FALSE
;
4976 sip
->subscribed
= FALSE
;
4977 sip
->subscribed_buddies
= FALSE
;
4979 signinname_login
= g_strsplit(username
, ",", 2);
4981 userserver
= g_strsplit(signinname_login
[0], "@", 2);
4982 purple_connection_set_display_name(gc
, userserver
[0]);
4983 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
4984 sip
->sipdomain
= g_strdup(userserver
[1]);
4986 if (strpbrk(sip
->username
, " \t\v\r\n") != NULL
) {
4987 gc
->wants_to_die
= TRUE
;
4988 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
4992 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
4993 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
4994 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
4996 user_realm
= g_strsplit(sip
->authuser
, "@", 2);
4997 if (user_realm
&& user_realm
[1]) {
4998 sip
->authuser
= g_strdup(user_realm
[0]);
4999 sip
->authdomain
= g_strdup(user_realm
[1]);
5002 sip
->password
= g_strdup(purple_connection_get_password(gc
));
5004 g_strfreev(user_realm
);
5005 g_strfreev(userserver
);
5006 g_strfreev(domain_user
);
5007 g_strfreev(signinname_login
);
5009 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
5011 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
5013 /* TODO: Set the status correctly. */
5014 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
5016 transport
= purple_account_get_string(account
, "transport", "auto");
5017 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
5018 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
5021 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
5022 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
5023 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
5024 } else if (strcmp(transport
, "auto") == 0) {
5025 sip
->auto_transport
= TRUE
;
5026 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
5027 } else if (strcmp(transport
, "tls") == 0) {
5028 resolve_next_service(sip
, service_tls
);
5029 } else if (strcmp(transport
, "tcp") == 0) {
5030 resolve_next_service(sip
, service_tcp
);
5032 resolve_next_service(sip
, service_udp
);
5036 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
5038 connection_free_all(sip
);
5043 if (sip
->query_data
!= NULL
)
5044 purple_dnsquery_destroy(sip
->query_data
);
5045 sip
->query_data
= NULL
;
5047 if (sip
->srv_query_data
!= NULL
)
5048 purple_srv_cancel(sip
->srv_query_data
);
5049 sip
->srv_query_data
= NULL
;
5051 if (sip
->listen_data
!= NULL
)
5052 purple_network_listen_cancel(sip
->listen_data
);
5053 sip
->listen_data
= NULL
;
5055 if (sip
->gsc
!= NULL
)
5056 purple_ssl_close(sip
->gsc
);
5059 sipe_auth_free(&sip
->registrar
);
5060 sipe_auth_free(&sip
->proxy
);
5063 purple_circ_buffer_destroy(sip
->txbuf
);
5066 g_free(sip
->realhostname
);
5067 sip
->realhostname
= NULL
;
5070 purple_input_remove(sip
->listenpa
);
5072 if (sip
->tx_handler
)
5073 purple_input_remove(sip
->tx_handler
);
5074 sip
->tx_handler
= 0;
5075 if (sip
->resendtimeout
)
5076 purple_timeout_remove(sip
->resendtimeout
);
5077 sip
->resendtimeout
= 0;
5078 if (sip
->timeouts
) {
5079 GSList
*entry
= sip
->timeouts
;
5081 struct scheduled_action
*sched_action
= entry
->data
;
5082 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
5083 purple_timeout_remove(sched_action
->timeout_handler
);
5084 (*sched_action
->destroy
)(sched_action
->payload
);
5085 g_free(sched_action
->name
);
5086 g_free(sched_action
);
5087 entry
= entry
->next
;
5090 g_slist_free(sip
->timeouts
);
5092 if (sip
->allow_events
) {
5093 GSList
*entry
= sip
->allow_events
;
5095 g_free(entry
->data
);
5096 entry
= entry
->next
;
5099 g_slist_free(sip
->allow_events
);
5102 g_free(sip
->contact
);
5103 sip
->contact
= NULL
;
5105 g_free(sip
->regcallid
);
5106 sip
->regcallid
= NULL
;
5109 sip
->processing_input
= FALSE
;
5113 * A callback for g_hash_table_foreach_remove
5115 static gboolean
sipe_buddy_remove(gpointer key
, struct sipe_buddy
*buddy
, gpointer user_data
)
5117 sipe_free_buddy(buddy
);
5120 static void sipe_close(PurpleConnection
*gc
)
5122 struct sipe_account_data
*sip
= gc
->proto_data
;
5125 /* leave all conversations */
5126 im_session_close_all(sip
);
5129 do_register_exp(sip
, 0);
5131 sipe_connection_cleanup(sip
);
5132 g_free(sip
->sipdomain
);
5133 g_free(sip
->username
);
5134 g_free(sip
->password
);
5135 g_free(sip
->authdomain
);
5136 g_free(sip
->authuser
);
5137 g_free(sip
->status
);
5139 g_hash_table_foreach_remove(sip
->buddies
, (GHRFunc
) sipe_buddy_remove
, NULL
);
5140 g_hash_table_destroy(sip
->buddies
);
5142 g_free(gc
->proto_data
);
5143 gc
->proto_data
= NULL
;
5146 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5148 PurpleAccount
*acct
= purple_connection_get_account(gc
);
5149 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
5150 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
5152 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
5153 purple_conversation_present(conv
);
5157 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5160 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
5161 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
5164 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
5166 PurpleNotifySearchResults
*results
;
5167 PurpleNotifySearchColumn
*column
;
5168 xmlnode
*searchResults
;
5170 int match_count
= 0;
5171 gboolean more
= FALSE
;
5174 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
5176 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5177 if (!searchResults
) {
5178 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5182 results
= purple_notify_searchresults_new();
5184 if (results
== NULL
) {
5185 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5186 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
5188 xmlnode_free(searchResults
);
5192 column
= purple_notify_searchresults_column_new(_("User Name"));
5193 purple_notify_searchresults_column_add(results
, column
);
5195 column
= purple_notify_searchresults_column_new(_("Name"));
5196 purple_notify_searchresults_column_add(results
, column
);
5198 column
= purple_notify_searchresults_column_new(_("Company"));
5199 purple_notify_searchresults_column_add(results
, column
);
5201 column
= purple_notify_searchresults_column_new(_("Country"));
5202 purple_notify_searchresults_column_add(results
, column
);
5204 column
= purple_notify_searchresults_column_new(_("Email"));
5205 purple_notify_searchresults_column_add(results
, column
);
5207 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
5210 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
5211 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
5212 g_strfreev(uri_parts
);
5214 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
5215 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
5216 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
5217 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
5219 purple_notify_searchresults_row_add(results
, row
);
5223 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
5224 char *data
= xmlnode_get_data_unescaped(mrow
);
5225 more
= (g_strcasecmp(data
, "true") == 0);
5229 secondary
= g_strdup_printf(
5230 dngettext(GETTEXT_PACKAGE
,
5231 "Found %d contact%s:",
5232 "Found %d contacts%s:", match_count
),
5233 match_count
, more
? _(" (more matched your query)") : "");
5235 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
5236 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
5237 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
5240 xmlnode_free(searchResults
);
5244 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
5246 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
5247 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
5251 PurpleRequestField
*field
= entries
->data
;
5252 const char *id
= purple_request_field_get_id(field
);
5253 const char *value
= purple_request_field_string_get_value(field
);
5255 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
5257 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
5258 } while ((entries
= g_list_next(entries
)) != NULL
);
5262 gchar
*query
= g_strjoinv(NULL
, attrs
);
5263 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
5264 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
5265 send_soap_request_with_cb(gc
->proto_data
, body
,
5266 (TransCallback
) process_search_contact_response
, NULL
);
5274 static void sipe_show_find_contact(PurplePluginAction
*action
)
5276 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
5277 PurpleRequestFields
*fields
;
5278 PurpleRequestFieldGroup
*group
;
5279 PurpleRequestField
*field
;
5281 fields
= purple_request_fields_new();
5282 group
= purple_request_field_group_new(NULL
);
5283 purple_request_fields_add_group(fields
, group
);
5285 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
5286 purple_request_field_group_add_field(group
, field
);
5287 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
5288 purple_request_field_group_add_field(group
, field
);
5289 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
5290 purple_request_field_group_add_field(group
, field
);
5291 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
5292 purple_request_field_group_add_field(group
, field
);
5294 purple_request_fields(gc
,
5296 _("Search for a Contact"),
5297 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5299 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
5301 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
5304 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
5307 PurplePluginAction
*act
;
5309 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
5310 menu
= g_list_prepend(menu
, act
);
5312 menu
= g_list_reverse(menu
);
5317 static void dummy_permit_deny(PurpleConnection
*gc
)
5321 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
5327 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
5333 static char *sipe_status_text(PurpleBuddy
*buddy
)
5335 struct sipe_account_data
*sip
;
5336 struct sipe_buddy
*sbuddy
;
5339 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5340 if (sip
) //happens on pidgin exit
5342 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5343 if (sbuddy
&& sbuddy
->annotation
)
5345 text
= g_strdup(sbuddy
->annotation
);
5352 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
5354 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
5355 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
5356 struct sipe_account_data
*sip
;
5357 struct sipe_buddy
*sbuddy
;
5358 char *annotation
= NULL
;
5360 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5361 if (sip
) //happens on pidgin exit
5363 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5366 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
5371 if (purple_presence_is_online(presence
))
5373 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
5378 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
5385 sipe_get_account_text_table(PurpleAccount
*account
)
5388 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
5389 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
5393 static PurpleBuddy
*
5394 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
5397 const gchar
*server_alias
, *email
;
5398 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
5400 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
5402 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
5404 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
5406 purple_blist_server_alias_buddy(clone
, server_alias
);
5409 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5411 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
5414 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
5416 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
5421 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
5423 PurpleBuddy
*buddy
, *b
;
5424 PurpleConnection
*gc
;
5425 PurpleGroup
* group
= purple_find_group(group_name
);
5427 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
5429 buddy
= (PurpleBuddy
*)node
;
5431 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
5432 gc
= purple_account_get_connection(buddy
->account
);
5434 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
5436 b
= purple_blist_add_buddy_clone(group
, buddy
);
5439 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
5443 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
5446 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
5448 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5451 char *mailto
= g_strdup_printf("mailto:%s", email
);
5452 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
5456 char *const parmList
[] = {mailto
, NULL
};
5457 if ((pid
= fork()) == -1)
5459 purple_debug_info("sipe", "fork() error\n");
5463 execvp("xdg-email", parmList
);
5464 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5472 //@TODO resolve env variable %WINDIR% first
5473 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
5476 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
5485 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
5490 * A menu which appear when right-clicking on buddy in contact list.
5493 sipe_buddy_menu(PurpleBuddy
*buddy
)
5495 PurpleBlistNode
*g_node
;
5496 PurpleGroup
*group
, *gr_parent
;
5497 PurpleMenuAction
*act
;
5499 GList
*menu_groups
= NULL
;
5501 act
= purple_menu_action_new(_("Send Email..."),
5502 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
5504 menu
= g_list_prepend(menu
, act
);
5506 gr_parent
= purple_buddy_get_group(buddy
);
5507 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
5508 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
5511 group
= (PurpleGroup
*)g_node
;
5512 if (group
== gr_parent
)
5515 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
5518 act
= purple_menu_action_new(purple_group_get_name(group
),
5519 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
5521 menu_groups
= g_list_prepend(menu_groups
, act
);
5523 menu_groups
= g_list_reverse(menu_groups
);
5525 act
= purple_menu_action_new(_("Copy to"),
5528 menu
= g_list_prepend(menu
, act
);
5529 menu
= g_list_reverse(menu
);
5534 GList
*sipe_blist_node_menu(PurpleBlistNode
*node
) {
5535 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
5536 return sipe_buddy_menu((PurpleBuddy
*) node
);
5543 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
5545 gboolean ret
= TRUE
;
5546 char *username
= (char *)trans
->payload
;
5548 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
5549 PurpleBuddy
*pbuddy
;
5550 struct sipe_buddy
*sbuddy
;
5552 char *server_alias
= NULL
;
5554 const char *device_name
= NULL
;
5556 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
5558 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
5559 alias
= purple_buddy_get_local_alias(pbuddy
);
5563 //will query buddy UA's capabilities and send answer to log
5564 sipe_options_request(sip
, username
);
5566 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
5569 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
5573 if (msg
->response
!= 200) {
5574 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
5576 xmlnode
*searchResults
;
5579 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
5580 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5581 if (!searchResults
) {
5582 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5583 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
5584 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
5585 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5586 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
5587 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
5588 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
5589 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
5590 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
5591 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
5592 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
5593 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
5594 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5595 if (!email
|| strcmp("", email
)) {
5596 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
5597 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
5601 xmlnode_free(searchResults
);
5604 purple_notify_user_info_add_section_break(info
);
5606 if (!server_alias
|| !strcmp("", server_alias
)) {
5607 g_free(server_alias
);
5608 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
5610 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5614 // same as server alias, do not present
5615 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
5618 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
5621 if (!email
|| !strcmp("", email
)) {
5623 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
5625 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5631 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
5634 /* show a buddy's user info in a nice dialog box */
5635 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
5636 username
, /* buddy's username */
5638 NULL
, /* callback called when dialog closed */
5639 NULL
); /* userdata for callback */
5645 * AD search first, LDAP based
5647 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
5649 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
5650 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
5652 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
5653 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
5654 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
5659 static PurplePlugin
*my_protocol
= NULL
;
5661 static PurplePluginProtocolInfo prpl_info
=
5664 NULL
, /* user_splits */
5665 NULL
, /* protocol_options */
5666 NO_BUDDY_ICONS
, /* icon_spec */
5667 sipe_list_icon
, /* list_icon */
5668 NULL
, /* list_emblems */
5669 sipe_status_text
, /* status_text */
5670 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
5671 sipe_status_types
, /* away_states */
5672 sipe_blist_node_menu
, /* blist_node_menu */
5673 NULL
, /* chat_info */
5674 NULL
, /* chat_info_defaults */
5675 sipe_login
, /* login */
5676 sipe_close
, /* close */
5677 sipe_im_send
, /* send_im */
5678 NULL
, /* set_info */ // TODO maybe
5679 sipe_send_typing
, /* send_typing */
5680 sipe_get_info
, /* get_info */
5681 sipe_set_status
, /* set_status */
5682 NULL
, /* set_idle */
5683 NULL
, /* change_passwd */
5684 sipe_add_buddy
, /* add_buddy */
5685 NULL
, /* add_buddies */
5686 sipe_remove_buddy
, /* remove_buddy */
5687 NULL
, /* remove_buddies */
5688 sipe_add_permit
, /* add_permit */
5689 sipe_add_deny
, /* add_deny */
5690 sipe_add_deny
, /* rem_permit */
5691 sipe_add_permit
, /* rem_deny */
5692 dummy_permit_deny
, /* set_permit_deny */
5693 NULL
, /* join_chat */
5694 NULL
, /* reject_chat */
5695 NULL
, /* get_chat_name */
5696 NULL
, /* chat_invite */
5697 NULL
, /* chat_leave */
5698 NULL
, /* chat_whisper */
5699 NULL
, /* chat_send */
5700 sipe_keep_alive
, /* keepalive */
5701 NULL
, /* register_user */
5702 NULL
, /* get_cb_info */ // deprecated
5703 NULL
, /* get_cb_away */ // deprecated
5704 sipe_alias_buddy
, /* alias_buddy */
5705 sipe_group_buddy
, /* group_buddy */
5706 sipe_rename_group
, /* rename_group */
5707 NULL
, /* buddy_free */
5708 sipe_convo_closed
, /* convo_closed */
5709 purple_normalize_nocase
, /* normalize */
5710 NULL
, /* set_buddy_icon */
5711 sipe_remove_group
, /* remove_group */
5712 NULL
, /* get_cb_real_name */ // TODO?
5713 NULL
, /* set_chat_topic */
5714 NULL
, /* find_blist_chat */
5715 NULL
, /* roomlist_get_list */
5716 NULL
, /* roomlist_cancel */
5717 NULL
, /* roomlist_expand_category */
5718 NULL
, /* can_receive_file */
5719 NULL
, /* send_file */
5720 NULL
, /* new_xfer */
5721 NULL
, /* offline_message */
5722 NULL
, /* whiteboard_prpl_ops */
5723 sipe_send_raw
, /* send_raw */
5724 NULL
, /* roomlist_room_serialize */
5725 NULL
, /* unregister_user */
5726 NULL
, /* send_attention */
5727 NULL
, /* get_attention_types */
5729 sizeof(PurplePluginProtocolInfo
), /* struct_size */
5730 sipe_get_account_text_table
, /* get_account_text_table */
5734 static PurplePluginInfo info
= {
5735 PURPLE_PLUGIN_MAGIC
,
5736 PURPLE_MAJOR_VERSION
,
5737 PURPLE_MINOR_VERSION
,
5738 PURPLE_PLUGIN_PROTOCOL
, /**< type */
5739 NULL
, /**< ui_requirement */
5741 NULL
, /**< dependencies */
5742 PURPLE_PRIORITY_DEFAULT
, /**< priority */
5743 "prpl-sipe", /**< id */
5744 "Microsoft LCS/OCS", /**< name */
5745 VERSION
, /**< version */
5746 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5747 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5748 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5749 "Gabriel Burt <gburt@novell.com>", /**< author */
5750 PURPLE_WEBSITE
, /**< homepage */
5751 sipe_plugin_load
, /**< load */
5752 sipe_plugin_unload
, /**< unload */
5753 sipe_plugin_destroy
, /**< destroy */
5754 NULL
, /**< ui_info */
5755 &prpl_info
, /**< extra_info */
5764 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
5768 entry
= prpl_info
.protocol_options
;
5770 purple_account_option_destroy(entry
->data
);
5771 entry
= g_list_delete_link(entry
, entry
);
5773 prpl_info
.protocol_options
= NULL
;
5775 entry
= prpl_info
.user_splits
;
5777 purple_account_user_split_destroy(entry
->data
);
5778 entry
= g_list_delete_link(entry
, entry
);
5780 prpl_info
.user_splits
= NULL
;
5783 static void init_plugin(PurplePlugin
*plugin
)
5785 PurpleAccountUserSplit
*split
;
5786 PurpleAccountOption
*option
;
5789 purple_debug_info(PACKAGE
, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
5790 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s",
5791 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
5794 purple_plugin_register(plugin
);
5796 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
5797 purple_account_user_split_set_reverse(split
, FALSE
);
5798 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
5800 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
5801 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5802 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5803 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5805 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
5806 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5807 // Translators: noun (networking port)
5808 option
= purple_account_option_int_new(_("Port"), "port", 5061);
5809 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5811 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
5812 purple_account_option_add_list_item(option
, _("Auto"), "auto");
5813 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
5814 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
5815 purple_account_option_add_list_item(option
, _("UDP"), "udp");
5816 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5818 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5819 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5821 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
5822 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5824 // TODO commented out so won't show in the preferences until we fix krb message signing
5826 option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5827 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5829 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5830 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5831 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5834 my_protocol
= plugin
;
5837 /* I had to redefined the function for it load, but works */
5838 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
5839 plugin
->info
= &(info
);
5840 init_plugin((plugin
));
5841 sipe_plugin_load((plugin
));
5842 return purple_plugin_register(plugin
);