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"
81 #endif /*USE_KERBEROS*/
84 #include "sipe-sign.h"
88 /* Keep in sync with sipe_transport_type! */
89 static const char *transport_descriptor
[] = { "tls", "tcp", "udp" };
90 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
92 /* Status identifiers (see also: sipe_status_types()) */
93 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
94 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
95 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
96 /* PURPLE_STATUS_UNAVAILABLE: */
97 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
98 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
99 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
100 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
101 /* PURPLE_STATUS_AWAY: */
102 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
103 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
104 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
105 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
106 /* ??? PURPLE_STATUS_MOBILE */
107 /* ??? PURPLE_STATUS_TUNE */
109 static char *gentag()
111 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
114 static gchar
*get_epid(struct sipe_account_data
*sip
)
117 sip
->epid
= sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
119 return g_strdup(sip
->epid
);
122 static char *genbranch()
124 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
125 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
126 rand() & 0xFFFF, rand() & 0xFFFF);
129 static char *gencallid()
131 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
132 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
133 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
134 rand() & 0xFFFF, rand() & 0xFFFF);
137 static gchar
*find_tag(const gchar
*hdr
)
139 gchar
* tag
= sipmsg_find_part_of_header (hdr
, "tag=", ";", NULL
);
141 // In case it's at the end and there's no trailing ;
142 tag
= sipmsg_find_part_of_header (hdr
, "tag=", NULL
, NULL
);
148 static const char *sipe_list_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
153 static void sipe_plugin_destroy(PurplePlugin
*plugin
);
155 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
);
157 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
158 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
161 static void sipe_close(PurpleConnection
*gc
);
163 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, const char * buddy_name
);
164 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
);
165 static void send_presence_status(struct sipe_account_data
*sip
);
167 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
169 static void sipe_keep_alive_timeout(struct sipe_account_data
*sip
, gchar
*timeout
)
171 if (timeout
!= NULL
) {
172 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
173 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
174 sip
->keepalive_timeout
);
179 static void sipe_keep_alive(PurpleConnection
*gc
)
181 struct sipe_account_data
*sip
= gc
->proto_data
;
182 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
183 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
184 gchar buf
[2] = {0, 0};
185 purple_debug_info("sipe", "sending keep alive\n");
186 sendto(sip
->fd
, buf
, 1, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
));
188 time_t now
= time(NULL
);
189 if ((sip
->keepalive_timeout
> 0) &&
190 ((now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
191 #if PURPLE_VERSION_CHECK(2,4,0)
192 && ((now
- gc
->last_received
) >= sip
->keepalive_timeout
)
195 purple_debug_info("sipe", "sending keep alive %d\n",sip
->keepalive_timeout
);
196 sendout_pkt(gc
, "\r\n\r\n");
197 sip
->last_keepalive
= now
;
202 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
204 struct sip_connection
*ret
= NULL
;
205 GSList
*entry
= sip
->openconns
;
208 if (ret
->fd
== fd
) return ret
;
214 static void sipe_auth_free(struct sip_auth
*auth
)
218 g_free(auth
->opaque
);
222 g_free(auth
->target
);
224 g_free(auth
->digest_session_key
);
225 auth
->digest_session_key
= NULL
;
226 g_free(auth
->ntlm_key
);
227 auth
->ntlm_key
= NULL
;
228 auth
->type
= AUTH_TYPE_UNSET
;
233 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
235 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
237 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
241 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
243 struct sip_connection
*conn
= connection_find(sip
, fd
);
245 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
246 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
252 static void connection_free_all(struct sipe_account_data
*sip
)
254 struct sip_connection
*ret
= NULL
;
255 GSList
*entry
= sip
->openconns
;
258 connection_remove(sip
, ret
->fd
);
259 entry
= sip
->openconns
;
263 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
265 const gchar
*method
= msg
->method
;
266 const gchar
*target
= msg
->target
;
271 const char *authdomain
= sip
->authdomain
;
272 const char *authuser
= sip
->authuser
;
273 //const char *krb5_realm;
275 //gchar *krb5_token = NULL;
277 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
278 // and do error checking
280 // KRB realm should always be uppercase
281 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
283 if (sip
->realhostname
) {
284 host
= sip
->realhostname
;
285 } else if (purple_account_get_bool(sip
->account
, "useproxy", TRUE
)) {
286 host
= purple_account_get_string(sip
->account
, "proxy", "");
288 host
= sip
->sipdomain
;
291 /*gboolean new_auth = krb5_auth.gss_context == NULL;
293 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
296 if (new_auth || force_reauth) {
297 krb5_token = krb5_auth.base64_token;
300 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
301 krb5_token = krb5_auth.base64_token;*/
307 if (!authuser
|| strlen(authuser
) < 1) {
308 authuser
= sip
->username
;
311 if (auth
->type
== AUTH_TYPE_DIGEST
) { /* Digest */
312 sprintf(noncecount
, "%08d", auth
->nc
++);
313 response
= purple_cipher_http_digest_calculate_response(
314 "md5", method
, target
, NULL
, NULL
,
315 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
316 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
318 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
);
321 } else if (auth
->type
== AUTH_TYPE_NTLM
) { /* NTLM */
322 // If we have a signature for the message, include that
323 if (msg
->signature
) {
324 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, msg
->rand
, msg
->num
, msg
->signature
);
328 if (auth
->nc
== 3 && auth
->nonce
&& auth
->ntlm_key
== NULL
) {
329 const gchar
*ntlm_key
;
331 #if GLIB_CHECK_VERSION(2,8,0)
332 const gchar
* hostname
= g_get_host_name();
334 static char hostname
[256];
335 int ret
= gethostname(hostname
, sizeof(hostname
));
336 hostname
[sizeof(hostname
) - 1] = '\0';
337 if (ret
== -1 || hostname
[0] == '\0') {
338 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Error when getting host name. Using \"localhost.\"\n");
340 strcpy(hostname
, "localhost");
343 /*const gchar * hostname = purple_get_host_name();*/
345 gssapi_data
= purple_ntlm_gen_authenticate(&ntlm_key
, authuser
, sip
->password
, hostname
, authdomain
, (const guint8
*)auth
->nonce
, &auth
->flags
);
346 auth
->ntlm_key
= (gchar
*)ntlm_key
;
347 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, gssapi_data
);
352 tmp
= g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth
->realm
, auth
->target
);
354 } else if (auth
->type
== AUTH_TYPE_KERBEROS
) {
357 /*if (new_auth || force_reauth) {
358 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
360 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, krb5_token);
362 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
365 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
366 gchar * mic = "MICTODO";
367 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
368 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
369 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
370 //auth->opaque ? auth->opaque : "", auth->target);
371 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
376 tmp
= g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth
->target
);
379 sprintf(noncecount
, "%08d", auth
->nc
++);
380 response
= purple_cipher_http_digest_calculate_response(
381 "md5", method
, target
, NULL
, NULL
,
382 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
383 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
385 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
);
390 static char *parse_attribute(const char *attrname
, const char *source
)
392 const char *tmp
, *tmp2
;
394 int len
= strlen(attrname
);
396 if (!strncmp(source
, attrname
, len
)) {
398 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
400 retval
= g_strndup(tmp
, tmp2
- tmp
);
402 retval
= g_strdup(tmp
);
408 static void fill_auth(struct sipe_account_data
*sip
, gchar
*hdr
, struct sip_auth
*auth
)
411 const char *authuser
;
414 //const char *krb5_realm;
417 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
418 // and do error checking
420 // KRB realm should always be uppercase
421 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
423 if (sip->realhostname) {
424 host = sip->realhostname;
425 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
426 host = purple_account_get_string(sip->account, "proxy", "");
428 host = sip->sipdomain;
431 authuser
= sip
->authuser
;
433 if (!authuser
|| strlen(authuser
) < 1) {
434 authuser
= sip
->username
;
438 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
442 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
443 auth
->type
= AUTH_TYPE_NTLM
;
444 parts
= g_strsplit(hdr
+5, "\", ", 0);
447 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
448 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
450 auth
->nonce
= g_memdup(purple_ntlm_parse_challenge(tmp
, &auth
->flags
), 8);
453 if ((tmp
= parse_attribute("targetname=\"",
455 g_free(auth
->target
);
458 else if ((tmp
= parse_attribute("realm=\"",
463 else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
464 g_free(auth
->opaque
);
471 if (!strstr(hdr
, "gssapi-data")) {
479 if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
480 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "setting auth type to Kerberos (3)\r\n");
481 auth
->type
= AUTH_TYPE_KERBEROS
;
482 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth - header: %s\r\n", hdr
);
483 parts
= g_strsplit(hdr
+9, "\", ", 0);
486 purple_debug_info("sipe", "krb - parts[i] %s\n", parts
[i
]);
487 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
488 /*if (krb5_auth.gss_context == NULL) {
489 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
491 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
494 if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
495 g_free(auth
->target
);
497 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
500 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
501 g_free(auth
->opaque
);
511 auth
->type
= AUTH_TYPE_DIGEST
;
512 parts
= g_strsplit(hdr
, " ", 0);
514 if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
518 else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
526 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "nonce: %s realm: %s\n", auth
->nonce
? auth
->nonce
: "(null)", auth
->realm
? auth
->realm
: "(null)");
528 g_free(auth
->digest_session_key
);
529 auth
->digest_session_key
= purple_cipher_http_digest_calculate_session_key(
530 "md5", authuser
, auth
->realm
, sip
->password
, auth
->nonce
, NULL
);
536 static void sipe_canwrite_cb(gpointer data
, gint source
, 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
= write(sip
->fd
, 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_canwrite_cb_ssl(gpointer data
, gint src
, PurpleInputCondition cond
)
568 PurpleConnection
*gc
= data
;
569 struct sipe_account_data
*sip
= gc
->proto_data
;
573 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
575 if (max_write
== 0) {
576 if (sip
->tx_handler
!= 0) {
577 purple_input_remove(sip
->tx_handler
);
583 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
585 if (written
< 0 && errno
== EAGAIN
)
587 else if (written
<= 0) {
588 /*TODO: do we really want to disconnect on a failure to write?*/
589 purple_connection_error(gc
, _("Could not write"));
593 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
596 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
598 static void send_later_cb(gpointer data
, gint source
, const gchar
*error
)
600 PurpleConnection
*gc
= data
;
601 struct sipe_account_data
*sip
;
602 struct sip_connection
*conn
;
604 if (!PURPLE_CONNECTION_IS_VALID(gc
))
612 purple_connection_error(gc
, _("Could not connect"));
616 sip
= gc
->proto_data
;
618 sip
->connecting
= FALSE
;
619 sip
->last_keepalive
= time(NULL
);
621 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
623 /* If there is more to write now, we need to register a handler */
624 if (sip
->txbuf
->bufused
> 0)
625 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
627 conn
= connection_create(sip
, source
);
628 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
631 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
633 struct sipe_account_data
*sip
;
634 struct sip_connection
*conn
;
636 if (!PURPLE_CONNECTION_IS_VALID(gc
))
638 if (gsc
) purple_ssl_close(gsc
);
642 sip
= gc
->proto_data
;
645 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
646 sip
->connecting
= FALSE
;
647 sip
->last_keepalive
= time(NULL
);
649 conn
= connection_create(sip
, gsc
->fd
);
651 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
656 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
658 PurpleConnection
*gc
= data
;
659 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
660 if (sip
== NULL
) return;
662 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
664 /* If there is more to write now */
665 if (sip
->txbuf
->bufused
> 0) {
666 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
671 static void sendlater(PurpleConnection
*gc
, const char *buf
)
673 struct sipe_account_data
*sip
= gc
->proto_data
;
675 if (!sip
->connecting
) {
676 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
677 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
678 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
680 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
681 purple_connection_error(gc
, _("Couldn't create socket"));
684 sip
->connecting
= TRUE
;
687 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
688 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
690 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
693 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
695 struct sipe_account_data
*sip
= gc
->proto_data
;
696 time_t currtime
= time(NULL
);
697 int writelen
= strlen(buf
);
699 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
700 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
701 if (sendto(sip
->fd
, buf
, writelen
, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
702 purple_debug_info("sipe", "could not send packet\n");
711 if (sip
->tx_handler
) {
716 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
718 ret
= write(sip
->fd
, buf
, writelen
);
722 if (ret
< 0 && errno
== EAGAIN
)
724 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
729 if (ret
< writelen
) {
730 if (!sip
->tx_handler
){
732 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
735 sip
->tx_handler
= purple_input_add(sip
->fd
,
736 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
741 /* XXX: is it OK to do this? You might get part of a request sent
742 with part of another. */
743 if (sip
->txbuf
->bufused
> 0)
744 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
746 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
752 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
754 sendout_pkt(gc
, buf
);
758 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
760 GSList
*tmp
= msg
->headers
;
763 GString
*outstr
= g_string_new("");
764 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
766 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
767 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
768 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
769 tmp
= g_slist_next(tmp
);
771 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
772 sendout_pkt(sip
->gc
, outstr
->str
);
773 g_string_free(outstr
, TRUE
);
776 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
779 if (sip
->registrar
.ntlm_key
) {
780 struct sipmsg_breakdown msgbd
;
781 gchar
*signature_input_str
;
783 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
784 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
785 sip
->registrar
.ntlm_num
++;
786 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
787 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
788 if (signature_input_str
!= NULL
) {
789 msg
->signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
790 msg
->rand
= g_strdup(msgbd
.rand
);
791 msg
->num
= g_strdup(msgbd
.num
);
792 g_free(signature_input_str
);
794 sipmsg_breakdown_free(&msgbd
);
797 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
798 buf
= auth_header(sip
, &sip
->registrar
, msg
);
799 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
800 sipmsg_add_header(msg
, "Authorization", buf
);
802 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
805 } 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")) {
806 sip
->registrar
.nc
= 3;
807 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
809 buf
= auth_header(sip
, &sip
->registrar
, msg
);
810 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
813 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
817 static char *get_contact(struct sipe_account_data
*sip
)
819 return g_strdup(sip
->contact
);
824 static char *get_contact_service(struct sipe_account_data *sip)
826 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()));
827 //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);
831 static void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
832 const char *text
, const char *body
)
836 GString
*outstr
= g_string_new("");
837 struct sipe_account_data
*sip
= gc
->proto_data
;
841 sipmsg_remove_header(msg
, "ms-user-data");
843 contact
= get_contact(sip
);
844 sipmsg_remove_header(msg
, "Contact");
845 sipmsg_add_header(msg
, "Contact", contact
);
848 /* When sending the acknowlegements and errors, the content length from the original
849 message is still here, but there is no body; we need to make sure we're sending the
850 correct content length */
851 sipmsg_remove_header(msg
, "Content-Length");
854 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
855 sipmsg_add_header(msg
, "Content-Length", len
);
857 sipmsg_remove_header(msg
, "Content-Type");
858 sipmsg_add_header(msg
, "Content-Length", "0");
861 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
862 //gchar * mic = "MICTODO";
863 msg
->response
= code
;
865 sipmsg_remove_header(msg
, "Authentication-Info");
866 sign_outgoing_message(msg
, sip
, msg
->method
);
868 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
871 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
872 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
874 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
875 tmp
= g_slist_next(tmp
);
877 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
878 sendout_pkt(gc
, outstr
->str
);
879 g_string_free(outstr
, TRUE
);
882 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
884 if (trans
->msg
) sipmsg_free(trans
->msg
);
885 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
889 static struct transaction
*
890 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
892 struct transaction
*trans
= g_new0(struct transaction
, 1);
893 trans
->time
= time(NULL
);
894 trans
->msg
= (struct sipmsg
*)msg
;
895 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
896 trans
->callback
= callback
;
897 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
901 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
903 struct transaction
*trans
;
904 GSList
*transactions
= sip
->transactions
;
905 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
907 while (transactions
) {
908 trans
= transactions
->data
;
909 if (!strcmp(trans
->cseq
, cseq
)) {
912 transactions
= transactions
->next
;
918 static struct transaction
*
919 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
920 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
921 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
923 struct sipe_account_data
*sip
= gc
->proto_data
;
924 const char *addh
= "";
927 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
928 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
929 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
930 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
931 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
932 gchar
*useragent
= (gchar
*)purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
);
933 gchar
*route
= strdup("");
934 gchar
*epid
= get_epid(sip
); // TODO generate one per account/login
935 struct transaction
*trans
;
937 if (dialog
&& dialog
->routes
)
939 GSList
*iter
= dialog
->routes
;
944 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
946 iter
= g_slist_next(iter
);
950 if (!ourtag
&& !dialog
) {
954 if (!strcmp(method
, "REGISTER")) {
955 if (sip
->regcallid
) {
957 callid
= g_strdup(sip
->regcallid
);
959 sip
->regcallid
= g_strdup(callid
);
963 if (addheaders
) addh
= addheaders
;
965 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
966 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
967 "From: <sip:%s>%s%s;epid=%s\r\n"
968 "To: <%s>%s%s%s%s\r\n"
969 "Max-Forwards: 70\r\n"
974 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
976 dialog
&& dialog
->request
? dialog
->request
: url
,
977 TRANSPORT_DESCRIPTOR
,
978 purple_network_get_my_ip(-1),
980 branch
? ";branch=" : "",
981 branch
? branch
: "",
983 ourtag
? ";tag=" : "",
984 ourtag
? ourtag
: "",
987 theirtag
? ";tag=" : "",
988 theirtag
? theirtag
: "",
989 theirepid
? ";epid=" : "",
990 theirepid
? theirepid
: "",
991 dialog
? ++dialog
->cseq
: ++sip
->cseq
,
997 body
? strlen(body
) : 0,
1001 //printf ("parsing msg buf:\n%s\n\n", buf);
1002 msg
= sipmsg_parse_msg(buf
);
1013 sign_outgoing_message (msg
, sip
, method
);
1015 buf
= sipmsg_to_string (msg
);
1017 /* add to ongoing transactions */
1018 trans
= transactions_add_buf(sip
, msg
, tc
);
1019 sendout_pkt(gc
, buf
);
1025 static void send_soap_request_with_cb(struct sipe_account_data
*sip
, gchar
*body
, TransCallback callback
, void * payload
)
1027 gchar
*from
= g_strdup_printf("sip:%s", sip
->username
);
1028 gchar
*contact
= get_contact(sip
);
1029 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1030 "Content-Type: application/SOAP+xml\r\n",contact
);
1032 struct transaction
* tr
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1033 tr
->payload
= payload
;
1040 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1042 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
1045 static char *get_contact_register(struct sipe_account_data
*sip
)
1047 char *epid
= get_epid(sip
);
1048 char *uuid
= generateUUIDfromEPID(epid
);
1049 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
);
1055 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1057 char *expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
1058 char *uri
= g_strdup_printf("sip:%s", sip
->sipdomain
);
1059 char *to
= g_strdup_printf("sip:%s", sip
->username
);
1060 char *contact
= get_contact_register(sip
);
1061 char *hdr
= g_strdup_printf("Contact: %s\r\n"
1062 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1063 "Event: registration\r\n"
1064 "Allow-Events: presence\r\n"
1065 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1066 "%s", contact
, expires
);
1070 sip
->registerstatus
= 1;
1072 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1073 process_register_response
);
1080 static void do_register_cb(struct sipe_account_data
*sip
)
1082 do_register_exp(sip
, -1);
1083 sip
->reregister_set
= FALSE
;
1086 static void do_register(struct sipe_account_data
*sip
)
1088 do_register_exp(sip
, -1);
1092 * Returns URI from provided To or From header.
1094 * Needs to g_free() after use.
1096 * @return URI with sip: prefix
1098 static gchar
*parse_from(const gchar
*hdr
)
1101 const gchar
*tmp
, *tmp2
= hdr
;
1103 if (!hdr
) return NULL
;
1104 purple_debug_info("sipe", "parsing address out of %s\n", hdr
);
1105 tmp
= strchr(hdr
, '<');
1107 /* i hate the different SIP UA behaviours... */
1108 if (tmp
) { /* sip address in <...> */
1110 tmp
= strchr(tmp2
, '>');
1112 from
= g_strndup(tmp2
, tmp
- tmp2
);
1114 purple_debug_info("sipe", "found < without > in From\n");
1118 tmp
= strchr(tmp2
, ';');
1120 from
= g_strndup(tmp2
, tmp
- tmp2
);
1122 from
= g_strdup(tmp2
);
1125 purple_debug_info("sipe", "got %s\n", from
);
1129 static xmlnode
* xmlnode_get_descendant(xmlnode
* parent
, ...)
1132 xmlnode
* node
= NULL
;
1135 va_start(args
, parent
);
1136 while ((name
= va_arg(args
, const char *)) != NULL
) {
1137 node
= xmlnode_get_child(parent
, name
);
1138 if (node
== NULL
) return NULL
;
1148 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1150 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1151 send_soap_request(sip
, body
);
1156 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1159 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1161 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1164 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1168 void sipe_auth_user_cb(void * data
)
1170 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1173 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1178 void sipe_deny_user_cb(void * data
)
1180 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1183 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1188 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1190 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1191 sipe_contact_allow_deny(sip
, name
, TRUE
);
1195 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1197 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1198 sipe_contact_allow_deny(sip
, name
, FALSE
);
1202 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1204 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1205 sipe_contact_set_acl(sip, name, "");
1209 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1213 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1214 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1216 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1218 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1219 if (!watchers
) return;
1221 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1222 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1223 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1224 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1226 // TODO pull out optional displayName to pass as alias
1228 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1229 job
->who
= remote_user
;
1231 purple_account_request_authorization(
1245 xmlnode_free(watchers
);
1250 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1252 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1253 if (!purple_group
) {
1254 purple_group
= purple_group_new(group
->name
);
1255 purple_blist_add_group(purple_group
, NULL
);
1259 group
->purple_group
= purple_group
;
1260 sip
->groups
= g_slist_append(sip
->groups
, group
);
1261 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1263 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1267 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1269 struct sipe_group
*group
;
1275 entry
= sip
->groups
;
1277 group
= entry
->data
;
1278 if (group
->id
== id
) {
1281 entry
= entry
->next
;
1286 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, gchar
* name
)
1288 struct sipe_group
*group
;
1294 entry
= sip
->groups
;
1296 group
= entry
->data
;
1297 if (!strcmp(group
->name
, name
)) {
1300 entry
= entry
->next
;
1306 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1309 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1310 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1311 send_soap_request(sip
, body
);
1313 g_free(group
->name
);
1314 group
->name
= g_strdup(name
);
1318 * Only appends if no such value already stored.
1321 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1322 GSList
* res
= list
;
1323 if (!g_slist_find_custom(list
, data
, func
)) {
1324 res
= g_slist_insert_sorted(list
, data
, func
);
1330 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1331 return group1
->id
- group2
->id
;
1335 * Returns string like "2 4 7 8" - group ids buddy belong to.
1338 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1341 //creating array from GList, converting int to gchar*
1342 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1343 GSList
*entry
= buddy
->groups
;
1345 struct sipe_group
* group
= entry
->data
;
1346 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1347 entry
= entry
->next
;
1351 res
= g_strjoinv(" ", ids_arr
);
1352 g_strfreev(ids_arr
);
1357 * Sends buddy update to server
1360 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1362 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1363 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1365 if (buddy
&& purple_buddy
) {
1366 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1368 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1369 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1371 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1372 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1374 send_soap_request(sip
, body
);
1380 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1382 if (msg
->response
== 200) {
1383 struct sipe_group
*group
;
1384 struct group_user_context
*ctx
= (struct group_user_context
*)tc
->payload
;
1388 struct sipe_buddy
*buddy
;
1390 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1396 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1403 group_id
= xmlnode_get_data(node
);
1410 group
= g_new0(struct sipe_group
, 1);
1411 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1413 group
->name
= ctx
->group_name
;
1415 sipe_group_add(sip
, group
);
1417 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1419 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1422 sipe_group_set_user(sip
, ctx
->user_name
);
1431 static void sipe_group_create (struct sipe_account_data
*sip
, gchar
*name
, gchar
* who
)
1433 struct group_user_context
* ctx
= g_new0(struct group_user_context
, 1);
1435 ctx
->group_name
= g_strdup(name
);
1436 ctx
->user_name
= g_strdup(who
);
1438 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1439 send_soap_request_with_cb(sip
, body
, process_add_group_response
, ctx
);
1445 * Should return FALSE if repetitive action is not needed
1447 gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1450 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1451 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1452 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1453 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1454 ret
= sched_action
->repetitive
;
1455 g_free(sched_action
->payload
);
1456 g_free(sched_action
->name
);
1457 g_free(sched_action
);
1462 * Do schedule action for execution in the future.
1463 * Non repetitive execution.
1465 * @param name of action (will be copied)
1466 * @param timeout in seconds
1467 * @action callback function
1468 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1470 void sipe_schedule_action(gchar
*name
, int timeout
, Action action
, struct sipe_account_data
*sip
, void * payload
)
1472 struct scheduled_action
*sched_action
;
1474 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name
, timeout
);
1475 sched_action
= g_new0(struct scheduled_action
, 1);
1476 sched_action
->repetitive
= FALSE
;
1477 sched_action
->name
= g_strdup(name
);
1478 sched_action
->action
= action
;
1479 sched_action
->sip
= sip
;
1480 sched_action
->payload
= payload
;
1481 sched_action
->timeout_handler
= purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1482 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1483 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1487 * Kills action timer effectively cancelling
1490 * @param name of action
1492 void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, gchar
*name
)
1496 if (!sip
->timeouts
|| !name
) return;
1498 entry
= sip
->timeouts
;
1500 struct scheduled_action
*sched_action
= entry
->data
;
1501 if(!strcmp(sched_action
->name
, name
)) {
1502 GSList
*to_delete
= entry
;
1503 entry
= entry
->next
;
1504 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1505 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1506 purple_timeout_remove(sched_action
->timeout_handler
);
1507 g_free(sched_action
->payload
);
1508 g_free(sched_action
->name
);
1509 g_free(sched_action
);
1511 entry
= entry
->next
;
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 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp
, name
);
1542 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1543 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1544 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1545 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1546 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1549 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
){
1550 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
1551 gchar
*contact
= get_contact(sip
);
1554 gchar
*resources_uri
= g_strdup("");
1555 gchar
*require
= "";
1557 gchar
*autoextend
= "";
1558 gchar
*content_type
;
1561 if (sip
->msrtc_event_categories
) {
1562 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
1563 require
= ", categoryList";
1564 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1565 content_type
= "application/msrtc-adrl-categorylist+xml";
1566 content
= g_strdup_printf(
1567 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1568 "<action name=\"subscribe\" id=\"63792024\">\n"
1569 "<adhocList>\n%s</adhocList>\n"
1570 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1571 "<category name=\"note\"/>\n"
1572 "<category name=\"state\"/>\n"
1575 "</batchSub>", sip
->username
, resources_uri
);
1577 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1578 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1579 content_type
= "application/adrl+xml";
1580 content
= g_strdup_printf(
1581 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1582 "<create xmlns=\"\">\n%s</create>\n"
1583 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1585 g_free(resources_uri
);
1587 request
= g_strdup_printf(
1588 "Require: adhoclist%s\r\n"
1589 "Supported: eventlist\r\n"
1590 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1591 "Supported: ms-piggyback-first-notify\r\n"
1592 "%sSupported: ms-benotify\r\n"
1593 "Proxy-Require: ms-benotify\r\n"
1594 "Event: presence\r\n"
1595 "Content-Type: %s\r\n"
1596 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
1599 /* subscribe to buddy presence */
1600 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1601 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1609 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1610 * The user sends a single SUBSCRIBE request to the subscribed contact.
1611 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1615 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, const char * buddy_name
)
1617 gchar
*to
= strstr(buddy_name
, "sip:") ? g_strdup(buddy_name
) : g_strdup_printf("sip:%s", buddy_name
);
1618 gchar
*tmp
= get_contact(sip
);
1621 gchar
*autoextend
= "";
1623 if (!sip
->msrtc_event_categories
)
1624 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1626 request
= g_strdup_printf(
1627 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1628 "Supported: ms-piggyback-first-notify\r\n"
1629 "%sSupported: ms-benotify\r\n"
1630 "Proxy-Require: ms-benotify\r\n"
1631 "Event: presence\r\n"
1632 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1633 "Contact: %s\r\n", autoextend
,tmp
);
1635 content
= g_strdup_printf(
1636 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1637 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1638 "<resource uri=\"%s\"/>\n"
1640 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1641 "<category name=\"note\"/>\n"
1642 "<category name=\"state\"/>\n"
1645 "</batchSub>", sip
->username
, to
1650 /* subscribe to buddy presence */
1651 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1658 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
1660 if (!purple_status_is_active(status
))
1664 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
1667 g_free(sip
->status
);
1668 sip
->status
= g_strdup(purple_status_get_id(status
));
1669 send_presence_status(sip
);
1675 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
, const char *alias
)
1677 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1678 sipe_group_set_user(sip
, name
);
1682 sipe_group_buddy(PurpleConnection
*gc
,
1684 const char *old_group_name
,
1685 const char *new_group_name
)
1687 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1688 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1689 struct sipe_group
* old_group
= NULL
;
1690 struct sipe_group
* new_group
;
1692 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1693 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
1695 if(!buddy
) { // buddy not in roaming list
1699 if (old_group_name
) {
1700 old_group
= sipe_group_find_by_name(sip
, g_strdup(old_group_name
));
1702 new_group
= sipe_group_find_by_name(sip
, g_strdup(new_group_name
));
1705 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
1706 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
1710 sipe_group_create(sip
, g_strdup(new_group_name
), g_strdup(who
));
1712 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
1713 sipe_group_set_user(sip
, who
);
1717 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1719 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1720 struct sipe_buddy
*b
;
1722 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1724 // Prepend sip: if needed
1725 if (strncmp("sip:", buddy
->name
, 4)) {
1726 gchar
*buf
= g_strdup_printf("sip:%s", buddy
->name
);
1727 purple_blist_rename_buddy(buddy
, buf
);
1731 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
1732 b
= g_new0(struct sipe_buddy
, 1);
1733 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy
->name
);
1734 b
->name
= g_strdup(buddy
->name
);
1735 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
1736 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
1737 sipe_subscribe_presence_single(sip
, b
->name
); //@TODO should go to callback
1739 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy
->name
);
1743 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
1745 g_free(buddy
->name
);
1746 g_free(buddy
->annotation
);
1747 g_free(buddy
->device_name
);
1748 g_slist_free(buddy
->groups
);
1753 * Unassociates buddy from group first.
1754 * Then see if no groups left, removes buddy completely.
1755 * Otherwise updates buddy groups on server.
1757 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1759 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1760 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
1761 struct sipe_group
*g
= NULL
;
1763 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1768 g
= sipe_group_find_by_name(sip
, group
->name
);
1772 b
->groups
= g_slist_remove(b
->groups
, g
);
1773 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
1776 if (g_slist_length(b
->groups
) < 1) {
1777 gchar
*action_name
= g_strdup_printf("<%s><%s>", "presence", buddy
->name
);
1778 sipe_cancel_scheduled_action(sip
, action_name
);
1779 g_free(action_name
);
1781 g_hash_table_remove(sip
->buddies
, buddy
->name
);
1784 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
1785 send_soap_request(sip
, body
);
1791 //updates groups on server
1792 sipe_group_set_user(sip
, b
->name
);
1798 sipe_rename_group(PurpleConnection
*gc
,
1799 const char *old_name
,
1801 GList
*moved_buddies
)
1803 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1804 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, g_strdup(old_name
));
1806 sipe_group_rename(sip
, s_group
, group
->name
);
1808 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
1813 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
1815 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1816 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
1819 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
1820 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
1821 send_soap_request(sip
, body
);
1824 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
1825 g_free(s_group
->name
);
1828 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
1832 static GList
*sipe_status_types(PurpleAccount
*acc
)
1834 PurpleStatusType
*type
;
1835 GList
*types
= NULL
;
1838 type
= purple_status_type_new_with_attrs(
1839 PURPLE_STATUS_AVAILABLE
, NULL
, _("Online"), TRUE
, TRUE
, FALSE
,
1840 // Translators: noun
1841 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1843 types
= g_list_append(types
, type
);
1846 type
= purple_status_type_new_with_attrs(
1847 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_BUSY
, _("Busy"), TRUE
, TRUE
, FALSE
,
1848 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1850 types
= g_list_append(types
, type
);
1852 // Do Not Disturb (not user settable)
1853 type
= purple_status_type_new_with_attrs(
1854 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_DND
, _("Do Not Disturb"), TRUE
, FALSE
, FALSE
,
1855 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1857 types
= g_list_append(types
, type
);
1860 type
= purple_status_type_new_with_attrs(
1861 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_BRB
, _("Be Right Back"), TRUE
, TRUE
, FALSE
,
1862 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1864 types
= g_list_append(types
, type
);
1867 type
= purple_status_type_new_with_attrs(
1868 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
1869 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1871 types
= g_list_append(types
, type
);
1874 type
= purple_status_type_new_with_attrs(
1875 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_ONPHONE
, _("On The Phone"), TRUE
, TRUE
, FALSE
,
1876 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1878 types
= g_list_append(types
, type
);
1881 type
= purple_status_type_new_with_attrs(
1882 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_LUNCH
, _("Out To Lunch"), TRUE
, TRUE
, FALSE
,
1883 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1885 types
= g_list_append(types
, type
);
1888 type
= purple_status_type_new_full(
1889 PURPLE_STATUS_INVISIBLE
, NULL
, _("Appear Offline"), TRUE
, TRUE
, FALSE
);
1890 types
= g_list_append(types
, type
);
1893 type
= purple_status_type_new_full(
1894 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
1895 types
= g_list_append(types
, type
);
1901 * A callback for g_hash_table_foreach
1903 static void sipe_buddy_subscribe_cb(char *name
, struct sipe_buddy
*buddy
, struct sipe_account_data
*sip
)
1905 sipe_subscribe_presence_single(sip
, buddy
->name
);
1909 * Removes entries from purple buddy list
1910 * that does not correspond ones in the roaming contact list.
1912 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
1913 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
1914 GSList
*entry
= buddies
;
1915 struct sipe_buddy
*buddy
;
1919 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
1920 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
1923 g
= purple_buddy_get_group(b
);
1924 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
1926 gboolean in_sipe_groups
= FALSE
;
1927 GSList
*entry2
= buddy
->groups
;
1929 struct sipe_group
*group
= entry2
->data
;
1930 if (!strcmp(group
->name
, g
->name
)) {
1931 in_sipe_groups
= TRUE
;
1934 entry2
= entry2
->next
;
1936 if(!in_sipe_groups
) {
1937 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
1938 purple_blist_remove_buddy(b
);
1941 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
1942 purple_blist_remove_buddy(b
);
1944 entry
= entry
->next
;
1948 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1950 int len
= msg
->bodylen
;
1952 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
1955 const gchar
*contacts_delta
;
1956 xmlnode
*group_node
;
1957 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
1961 /* Convert the contact from XML to Purple Buddies */
1962 isc
= xmlnode_from_str(msg
->body
, len
);
1967 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
1968 if (contacts_delta
) {
1969 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
1973 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
1974 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1975 const char *name
= xmlnode_get_attrib(group_node
, "name");
1977 if (!strncmp(name
, "~", 1)) {
1979 name
= "Other Contacts";
1981 group
->name
= g_strdup(name
);
1982 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
1984 sipe_group_add(sip
, group
);
1987 // Make sure we have at least one group
1988 if (g_slist_length(sip
->groups
) == 0) {
1989 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1990 PurpleGroup
*purple_group
;
1992 group
->name
= g_strdup("Other Contacts");
1994 purple_group
= purple_group_new(group
->name
);
1995 purple_blist_add_group(purple_group
, NULL
);
1996 sip
->groups
= g_slist_append(sip
->groups
, group
);
1999 /* Parse contacts */
2000 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
2001 gchar
* uri
= g_strdup(xmlnode_get_attrib(item
, "uri"));
2002 gchar
* name
= g_strdup(xmlnode_get_attrib(item
, "name"));
2003 gchar
* groups
= g_strdup(xmlnode_get_attrib(item
, "groups"));
2004 gchar
* buddy_name
= g_strdup_printf("sip:%s", uri
);
2005 gchar
**item_groups
;
2006 struct sipe_group
*group
= NULL
;
2007 struct sipe_buddy
*buddy
= NULL
;
2010 // assign to group Other Contacts if nothing else received
2011 if(!groups
|| !strcmp("", groups
) ) {
2012 group
= sipe_group_find_by_name(sip
, "Other Contacts");
2013 groups
= group
? g_strdup_printf("%d", group
->id
) : "1";
2016 item_groups
= g_strsplit(groups
, " ", 0);
2018 while (item_groups
[i
]) {
2019 group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
2021 // If couldn't find the right group for this contact, just put them in the first group we have
2022 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2023 group
= sip
->groups
->data
;
2026 if (group
!= NULL
) {
2027 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2029 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2030 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2033 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2034 if (name
!= NULL
&& strlen(name
) != 0) {
2035 purple_blist_alias_buddy(b
, name
);
2040 buddy
= g_new0(struct sipe_buddy
, 1);
2041 buddy
->name
= g_strdup(b
->name
);
2042 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2045 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2047 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2049 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2054 } // while, contact groups
2055 g_strfreev(item_groups
);
2065 sipe_cleanup_local_blist(sip
);
2067 //subscribe to buddies
2068 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2069 //if(sip->msrtc_event_categories){
2070 sipe_subscribe_presence_batched(sip
);
2072 //g_hash_table_foreach(sip->buddies, (GHFunc)sipe_buddy_subscribe_cb, (gpointer)sip);
2074 sip
->subscribed_buddies
= TRUE
;
2081 * Subscribe roaming contacts
2083 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2085 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2086 gchar
*tmp
= get_contact(sip
);
2087 gchar
*hdr
= g_strdup_printf(
2088 "Event: vnd-microsoft-roaming-contacts\r\n"
2089 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2090 "Supported: com.microsoft.autoextend\r\n"
2091 "Supported: ms-benotify\r\n"
2092 "Proxy-Require: ms-benotify\r\n"
2093 "Supported: ms-piggyback-first-notify\r\n"
2094 "Contact: %s\r\n", tmp
);
2097 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2102 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2104 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2105 gchar
*tmp
= get_contact(sip
);
2106 gchar
*hdr
= g_strdup_printf(
2107 "Event: presence.wpending\r\n"
2108 "Accept: text/xml+msrtc.wpending\r\n"
2109 "Supported: com.microsoft.autoextend\r\n"
2110 "Supported: ms-benotify\r\n"
2111 "Proxy-Require: ms-benotify\r\n"
2112 "Supported: ms-piggyback-first-notify\r\n"
2113 "Contact: %s\r\n", tmp
);
2116 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2122 * Fires on deregistration event initiated by server.
2123 * [MS-SIPREGE] SIP extension.
2128 // Content-Type: text/registration-event
2129 // subscription-state: terminated;expires=0
2130 // ms-diagnostics-public: 4141;reason="User disabled"
2132 // deregistered;event=rejected
2134 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2136 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2137 gchar
*event
= NULL
;
2138 gchar
*reason
= NULL
;
2139 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
2141 warning
= warning
? warning
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2142 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2144 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2145 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2146 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2147 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2149 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2153 if (warning
!= NULL
) {
2154 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
2155 } else { // for LCS2005
2157 if (event
&& !g_ascii_strcasecmp(event
, "unregistered")) {
2158 error_id
= 4140; // [MS-SIPREGE]
2159 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2160 reason
= g_strdup(_("You have been signed off because you've signed in at another location"));
2161 } else if (event
&& !g_ascii_strcasecmp(event
, "rejected")) {
2163 reason
= g_strdup(_("User disabled")); // [MS-OCER]
2164 } else if (event
&& !g_ascii_strcasecmp(event
, "deactivated")) {
2166 reason
= g_strdup(_("User moved")); // [MS-OCER]
2170 warning
= g_strdup_printf(_("Unregistered by Server: %s."), reason
? reason
: _("no reason given"));
2173 sip
->gc
->wants_to_die
= TRUE
;
2174 purple_connection_error(sip
->gc
, warning
);
2179 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2181 const gchar
*contacts_delta
;
2184 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2190 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2193 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2202 * When we receive some self (BE) NOTIFY with a new subscriber
2203 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2207 static void sipe_process_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2213 char *display_name
= NULL
;
2214 PurpleBuddy
*pbuddy
;
2219 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2221 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2224 contact
= get_contact(sip
);
2225 to
= g_strdup_printf("sip:%s", sip
->username
);
2227 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
2229 const char *acknowledged
;
2233 user
= xmlnode_get_attrib(node
, "user");
2234 if (!user
) continue;
2235 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
2236 uri_user
= g_strdup_printf("sip:%s", user
);
2237 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri_user
);
2239 alias
= purple_buddy_get_local_alias(pbuddy
);
2240 uri_alias
= g_strdup_printf("sip:%s", alias
);
2241 display_name
= g_strdup(xmlnode_get_attrib(node
, "displayName"));
2242 if (display_name
&& !g_ascii_strcasecmp(uri_user
, uri_alias
)) { // 'bad' alias
2243 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user
, display_name
);
2244 purple_blist_alias_buddy(pbuddy
, display_name
);
2250 acknowledged
= xmlnode_get_attrib(node
, "acknowledged");
2251 if(!g_ascii_strcasecmp(acknowledged
,"false")){
2252 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user
);
2253 hdr
= g_strdup_printf(
2255 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
2257 body
= g_strdup_printf(
2258 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2259 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2260 "</setSubscribers>", user
);
2262 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
2273 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2275 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2276 gchar
*tmp
= get_contact(sip
);
2277 gchar
*hdr
= g_strdup_printf(
2278 "Event: vnd-microsoft-roaming-ACL\r\n"
2279 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2280 "Supported: com.microsoft.autoextend\r\n"
2281 "Supported: ms-benotify\r\n"
2282 "Proxy-Require: ms-benotify\r\n"
2283 "Supported: ms-piggyback-first-notify\r\n"
2284 "Contact: %s\r\n", tmp
);
2287 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2293 * To request for presence information about the user, access level settings that have already been configured by the user
2294 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2295 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2298 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2300 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2301 gchar
*tmp
= get_contact(sip
);
2302 gchar
*hdr
= g_strdup_printf(
2303 "Event: vnd-microsoft-roaming-self\r\n"
2304 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2305 "Supported: ms-benotify\r\n"
2306 "Proxy-Require: ms-benotify\r\n"
2307 "Supported: ms-piggyback-first-notify\r\n"
2309 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
2311 gchar
*body
=g_strdup(
2312 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2313 "<roaming type=\"categories\"/>"
2314 "<roaming type=\"containers\"/>"
2315 "<roaming type=\"subscribers\"/></roamingList>");
2318 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2327 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2329 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2330 gchar
*tmp
= get_contact(sip
);
2331 gchar
*hdr
= g_strdup_printf(
2332 "Event: vnd-microsoft-provisioning\r\n"
2333 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2334 "Supported: com.microsoft.autoextend\r\n"
2335 "Supported: ms-benotify\r\n"
2336 "Proxy-Require: ms-benotify\r\n"
2337 "Supported: ms-piggyback-first-notify\r\n"
2339 "Contact: %s\r\n", tmp
);
2342 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
2347 /** Subscription for provisioning information to help with initial
2348 * configuration. This subscription is a one-time query (denoted by the Expires header,
2349 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2350 * configuration, meeting policies, and policy settings that Communicator must enforce.
2351 * TODO: for what we need this information.
2354 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2356 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2357 gchar
*tmp
= get_contact(sip
);
2358 gchar
*hdr
= g_strdup_printf(
2359 "Event: vnd-microsoft-provisioning-v2\r\n"
2360 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2361 "Supported: com.microsoft.autoextend\r\n"
2362 "Supported: ms-benotify\r\n"
2363 "Proxy-Require: ms-benotify\r\n"
2364 "Supported: ms-piggyback-first-notify\r\n"
2367 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
2368 gchar
*body
= g_strdup(
2369 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2370 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2371 "<provisioningGroup name=\"ucPolicy\"/>"
2372 "</provisioningGroupList>");
2375 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2381 /* IM Session (INVITE and MESSAGE methods) */
2383 static struct sip_im_session
* find_im_session (struct sipe_account_data
*sip
, const char *who
)
2385 struct sip_im_session
*session
;
2387 if (sip
== NULL
|| who
== NULL
) {
2391 entry
= sip
->im_sessions
;
2393 session
= entry
->data
;
2394 if ((who
!= NULL
&& !strcmp(who
, session
->with
))) {
2397 entry
= entry
->next
;
2402 static struct sip_im_session
* find_or_create_im_session (struct sipe_account_data
*sip
, const char *who
)
2404 struct sip_im_session
*session
= find_im_session(sip
, who
);
2406 session
= g_new0(struct sip_im_session
, 1);
2407 session
->with
= g_strdup(who
);
2408 session
->unconfirmed_messages
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
2409 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2414 static void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2416 struct sip_dialog
*dialog
= session
->dialog
;
2419 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2422 entry
= dialog
->routes
;
2424 g_free(entry
->data
);
2425 entry
= g_slist_remove(entry
, entry
->data
);
2427 entry
= dialog
->supported
;
2429 g_free(entry
->data
);
2430 entry
= g_slist_remove(entry
, entry
->data
);
2432 g_free(dialog
->callid
);
2433 g_free(dialog
->ourtag
);
2434 g_free(dialog
->theirtag
);
2435 g_free(dialog
->theirepid
);
2436 g_free(dialog
->request
);
2438 g_free(session
->dialog
);
2440 entry
= session
->outgoing_message_queue
;
2442 g_free(entry
->data
);
2443 entry
= g_slist_remove(entry
, entry
->data
);
2446 g_hash_table_destroy(session
->unconfirmed_messages
);
2448 g_free(session
->with
);
2453 process_options_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2455 gboolean ret
= TRUE
;
2457 if (msg
->response
!= 200) {
2458 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
2462 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
2468 * Asks UA/proxy about its capabilities.
2470 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
2472 gchar
*to
= strstr(who
, "sip:") ? g_strdup(who
) : g_strdup_printf("sip:%s", who
);
2473 gchar
*contact
= get_contact(sip
);
2475 request
= g_strdup_printf(
2476 "Accept: application/sdp\r\n"
2477 "Contact: %s\r\n", contact
);
2481 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
2487 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2489 char *msg
, *msg_tmp
;
2490 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2491 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2493 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2494 "possibly because one or more persons are offline:\n%s") ,
2496 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2501 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2504 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2506 gboolean ret
= TRUE
;
2507 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2508 struct sip_im_session
* session
= find_im_session(sip
, with
);
2509 struct sip_dialog
*dialog
;
2515 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2520 dialog
= session
->dialog
;
2522 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2527 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2528 key
= g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
));
2530 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2532 if (msg
->response
!= 200) {
2533 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2535 sipe_present_message_undelivered_err(with
, sip
, message
);
2536 im_session_destroy(sip
, session
);
2539 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2540 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2541 key
, g_hash_table_size(session
->unconfirmed_messages
));
2547 sipe_im_process_queue(sip
, session
);
2551 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_im_session
* session
, const char *msg
)
2561 if (strncmp("sip:", session
->with
, 4)) {
2562 fullto
= g_strdup_printf("sip:%s", session
->with
);
2564 fullto
= g_strdup(session
->with
);
2567 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2568 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2570 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2573 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2576 msgr
= g_strdup("");
2579 tmp
= get_contact(sip
);
2580 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2581 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2582 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2583 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2588 send_sip_request(sip
->gc
, "MESSAGE", fullto
, fullto
, hdr
, msgtext
, session
->dialog
, process_message_response
);
2596 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2598 GSList
*entry
= session
->outgoing_message_queue
;
2600 if (session
->outgoing_invite
) return; //do not send messages until INVITE responded.
2603 char *key
= g_strdup_printf("<%s><%d><MESSAGE>", session
->dialog
->callid
, (session
->dialog
->cseq
) + 1);
2604 char *queued_msg
= entry
->data
;
2605 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
2606 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2607 key
, g_hash_table_size(session
->unconfirmed_messages
));
2609 sipe_send_message(sip
, session
, queued_msg
);
2610 entry
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2616 sipe_get_route_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2618 GSList
*hdr
= msg
->headers
;
2619 struct siphdrelement
*elem
;
2625 if(!g_ascii_strcasecmp(elem
->name
, "Record-Route"))
2627 gchar
*route
= sipmsg_find_part_of_header(elem
->value
, "<", ">", NULL
);
2628 dialog
->routes
= g_slist_append(dialog
->routes
, route
);
2630 hdr
= g_slist_next(hdr
);
2635 dialog
->routes
= g_slist_reverse(dialog
->routes
);
2640 dialog
->request
= dialog
->routes
->data
;
2641 dialog
->routes
= g_slist_remove(dialog
->routes
, dialog
->routes
->data
);
2644 contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Contact"), "<", ">", NULL
);
2645 dialog
->routes
= g_slist_append(dialog
->routes
, contact
);
2649 sipe_get_supported_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2651 GSList
*hdr
= msg
->headers
;
2652 struct siphdrelement
*elem
;
2656 if(!g_ascii_strcasecmp(elem
->name
, "Supported")
2657 && !g_slist_find_custom(dialog
->supported
, elem
->value
, (GCompareFunc
)g_ascii_strcasecmp
))
2659 dialog
->supported
= g_slist_append(dialog
->supported
, g_strdup(elem
->value
));
2662 hdr
= g_slist_next(hdr
);
2667 sipe_parse_dialog(struct sipmsg
* msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2669 gchar
*us
= outgoing
? "From" : "To";
2670 gchar
*them
= outgoing
? "To" : "From";
2672 g_free(dialog
->callid
);
2673 g_free(dialog
->ourtag
);
2674 g_free(dialog
->theirtag
);
2676 dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2677 dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, us
));
2678 dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, them
));
2679 if (!dialog
->theirepid
) {
2680 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", ";", NULL
);
2681 if (!dialog
->theirepid
) {
2682 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", NULL
, NULL
);
2686 sipe_get_route_header(msg
, dialog
, outgoing
);
2687 sipe_get_supported_header(msg
, dialog
, outgoing
);
2692 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2694 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
2695 struct sip_im_session
* session
= find_im_session(sip
, with
);
2696 struct sip_dialog
*dialog
;
2702 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2707 dialog
= session
->dialog
;
2709 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2714 sipe_parse_dialog(msg
, dialog
, TRUE
);
2716 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2717 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
2719 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2721 if (msg
->response
!= 200) {
2722 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2724 sipe_present_message_undelivered_err(with
, sip
, message
);
2725 im_session_destroy(sip
, session
);
2731 send_sip_request(sip
->gc
, "ACK", session
->with
, session
->with
, NULL
, NULL
, dialog
, NULL
);
2732 session
->outgoing_invite
= NULL
;
2733 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
2734 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2735 if (session
->outgoing_message_queue
) {
2736 char *queued_msg
= session
->outgoing_message_queue
->data
;
2737 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2742 sipe_im_process_queue(sip
, session
);
2744 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2745 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2746 key
, g_hash_table_size(session
->unconfirmed_messages
));
2754 static void sipe_invite(struct sipe_account_data
*sip
, struct sip_im_session
*session
, const gchar
*msg_body
)
2764 char *ms_text_format
;
2769 if (session
->dialog
) {
2770 purple_debug_info("sipe", "session with %s already has a dialog open\n", session
->with
);
2774 session
->dialog
= g_new0(struct sip_dialog
, 1);
2775 session
->dialog
->callid
= gencallid();
2777 if (strstr(session
->with
, "sip:")) {
2778 to
= g_strdup(session
->with
);
2780 to
= g_strdup_printf("sip:%s", session
->with
);
2783 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
2784 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
2786 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2790 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2794 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
2795 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
2800 key
= g_strdup_printf("<%s><%d><INVITE>", session
->dialog
->callid
, (session
->dialog
->cseq
) + 1);
2801 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
2802 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2803 key
, g_hash_table_size(session
->unconfirmed_messages
));
2806 contact
= get_contact(sip
);
2807 from
= g_strdup_printf("<sip:%s>", sip
->username
);
2808 hdr
= g_strdup_printf(
2809 "Supported: ms-delayed-accept\r\n"
2810 "Roster-Manager: <%s>\r\n"
2811 "EndPoints: <%s>, <%s>\r\n"
2812 "Supported: com.microsoft.rtc-multiparty\r\n"
2814 "Content-Type: application/sdp\r\n",
2815 to
, to
, from
, contact
, ms_text_format
);
2816 g_free(ms_text_format
);
2818 body
= g_strdup_printf(
2820 "o=- 0 0 IN IP4 %s\r\n"
2824 "m=message %d sip null\r\n"
2825 "a=accept-types:text/plain text/html image/gif "
2826 "multipart/alternative application/im-iscomposing+xml\r\n",
2827 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
2829 session
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
2830 to
, to
, hdr
, body
, session
->dialog
, process_invite_response
);
2840 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2843 send_sip_request(sip
->gc
, "BYE", session
->with
, session
->with
, NULL
, NULL
, session
->dialog
, NULL
);
2844 im_session_destroy(sip
, session
);
2849 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
2851 struct sipe_account_data
*sip
= gc
->proto_data
;
2853 purple_debug_info("sipe", "conversation with %s closed\n", who
);
2854 im_session_close(sip
, find_im_session(sip
, who
));
2858 im_session_close_all (struct sipe_account_data
*sip
)
2860 GSList
*entry
= sip
->im_sessions
;
2862 im_session_close (sip
, entry
->data
);
2863 entry
= sip
->im_sessions
;
2867 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
2869 struct sipe_account_data
*sip
;
2871 struct sip_im_session
*session
;
2873 sip
= gc
->proto_data
;
2876 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
2878 session
= find_or_create_im_session(sip
, who
);
2880 // Queue the message
2881 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
2883 if (session
->dialog
&& session
->dialog
->callid
) {
2884 sipe_im_process_queue(sip
, session
);
2885 } else if (!session
->outgoing_invite
) {
2886 // Need to send the INVITE to get the outgoing dialog setup
2887 sipe_invite(sip
, session
, what
);
2894 /* End IM Session (INVITE and MESSAGE methods) */
2897 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
2899 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2900 struct sip_im_session
*session
;
2902 if (state
== PURPLE_NOT_TYPING
)
2905 session
= find_im_session(sip
, who
);
2907 if (session
&& session
->dialog
) {
2908 send_sip_request(gc
, "INFO", who
, who
,
2909 "Content-Type: application/xml\r\n",
2910 SIPE_SEND_TYPING
, session
->dialog
, NULL
);
2912 return SIPE_TYPING_SEND_TIMEOUT
;
2915 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
2917 GSList
*tmp
= sip
->transactions
;
2918 time_t currtime
= time(NULL
);
2920 struct transaction
*trans
= tmp
->data
;
2922 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime
-trans
->time
);
2923 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
2926 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
2928 sendout_sipmsg(sip
, trans
->msg
);
2935 static void do_reauthenticate_cb(struct sipe_account_data
*sip
)
2937 /* register again when security token expires */
2938 /* we have to start a new authentication as the security token
2939 * is almost expired by sending a not signed REGISTER message */
2940 purple_debug_info("sipe", "do a full reauthentication\n");
2941 sipe_auth_free(&sip
->registrar
);
2942 sip
->registerstatus
= 0;
2944 sip
->reauthenticate_set
= FALSE
;
2947 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2951 gboolean found
= FALSE
;
2953 from
= parse_from(sipmsg_find_header(msg
, "From"));
2957 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
2959 contenttype
= sipmsg_find_header(msg
, "Content-Type");
2960 if (!strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
2962 gchar
*html
= get_html_message(contenttype
, msg
->body
);
2963 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
2965 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2968 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
2969 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2974 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
2978 state
= xmlnode_get_child(isc
, "state");
2981 purple_debug_info("sipe", "process_incoming_message: no state found\n");
2986 statedata
= xmlnode_get_data(state
);
2988 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
2989 else serv_got_typing_stopped(sip
->gc
, from
);
2994 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2998 purple_debug_info("sipe", "got unknown mime-type");
2999 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
3004 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3006 gchar
*ms_text_format
;
3009 struct sip_im_session
*session
;
3011 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? msg
->body
: "");
3013 // Only accept text invitations
3014 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
3015 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3019 from
= parse_from(sipmsg_find_header(msg
, "From"));
3020 session
= find_or_create_im_session (sip
, from
);
3022 if (session
->dialog
) {
3023 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3025 session
->dialog
= g_new0(struct sip_dialog
, 1);
3027 sipe_parse_dialog(msg
, session
->dialog
, FALSE
);
3029 session
->dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
3030 session
->dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, "To"));
3031 session
->dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, "From"));
3032 session
->dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "From"), "epid=", NULL
, NULL
);
3035 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3038 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3039 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
3040 if (ms_text_format
) {
3041 if (!strncmp(ms_text_format
, "text/plain", 10) || !strncmp(ms_text_format
, "text/html", 9)) {
3043 gchar
*html
= get_html_message(ms_text_format
, NULL
);
3045 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3047 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message received
3053 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3054 sipmsg_remove_header(msg
, "Ms-Text-Format");
3055 sipmsg_remove_header(msg
, "EndPoints");
3056 sipmsg_remove_header(msg
, "User-Agent");
3057 sipmsg_remove_header(msg
, "Roster-Manager");
3059 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3060 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3062 body
= g_strdup_printf(
3064 "o=- 0 0 IN IP4 %s\r\n"
3068 "m=message %d sip sip:%s\r\n"
3069 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3070 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3071 sip
->realport
, sip
->username
);
3072 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3076 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3080 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3081 sipmsg_remove_header(msg
, "EndPoints");
3082 sipmsg_remove_header(msg
, "User-Agent");
3084 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3085 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3087 body
= g_strdup_printf(
3089 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3091 "c=IN IP4 0.0.0.0\r\n"
3093 "m=message %d sip sip:%s\r\n"
3094 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3095 sip
->realport
, sip
->username
);
3096 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3100 static void sipe_connection_cleanup(struct sipe_account_data
*);
3101 static void create_connection(struct sipe_account_data
*, gchar
*, int);
3103 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3107 const gchar
*expires_header
;
3109 GSList
*hdr
= msg
->headers
;
3111 struct siphdrelement
*elem
;
3113 expires_header
= sipmsg_find_header(msg
, "Expires");
3114 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
3115 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
3117 switch (msg
->response
) {
3120 sip
->registerstatus
= 0;
3122 gchar
*contact_hdr
= NULL
;
3127 if (!sip
->reregister_set
) {
3128 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
3129 sipe_schedule_action(action_name
, expires
, (Action
) do_register_cb
, sip
, NULL
);
3130 g_free(action_name
);
3131 sip
->reregister_set
= TRUE
;
3134 sip
->registerstatus
= 3;
3136 if (!sip
->reauthenticate_set
) {
3137 /* we have to reauthenticate as our security token expires
3138 after eight hours (be five minutes early) */
3139 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
3140 guint reauth_timeout
= (8 * 3600) - 360;
3141 sipe_schedule_action(action_name
, reauth_timeout
, (Action
) do_reauthenticate_cb
, sip
, NULL
);
3142 g_free(action_name
);
3143 sip
->reauthenticate_set
= TRUE
;
3146 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
3148 epid
= get_epid(sip
);
3149 uuid
= generateUUIDfromEPID(epid
);
3152 // There can be multiple Contact headers (one per location where the user is logged in) so
3153 // make sure to only get the one for this uuid
3154 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
3155 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
3156 if (valid_contact
) {
3157 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
3158 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3159 g_free(valid_contact
);
3162 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3167 g_free(sip
->contact
);
3169 sip
->contact
= g_strdup_printf("<%s>", gruu
);
3172 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3173 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
);
3175 sip
->msrtc_event_categories
= FALSE
;
3180 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
3181 if (strstr(elem
->value
, "msrtc-event-categories")) {
3182 sip
->msrtc_event_categories
= TRUE
;
3184 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s, %d\n", elem
->value
, sip
->msrtc_event_categories
);
3186 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
3187 gchar
**caps
= g_strsplit(elem
->value
,",",0);
3190 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
3191 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
3196 hdr
= g_slist_next(hdr
);
3199 if (!sip
->subscribed
) { //do it just once, not every re-register
3200 if(!sip
->msrtc_event_categories
){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3201 sipe_options_request(sip
, sip
->sipdomain
);
3203 entry
= sip
->allow_events
;
3206 if (tmp
&& !g_ascii_strcasecmp(tmp
, "vnd-microsoft-roaming-contacts")) {
3207 sipe_subscribe_roaming_contacts(sip
, msg
);
3209 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-ACL")) {
3210 sipe_subscribe_roaming_acl(sip
, msg
);
3212 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-self")) {
3213 sipe_subscribe_roaming_self(sip
, msg
);
3215 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning-v2")) {
3216 sipe_subscribe_roaming_provisioning_v2(sip
, msg
);
3217 } else if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning")) { // LSC2005
3218 sipe_subscribe_roaming_provisioning(sip
, msg
);
3220 if (tmp
&& !g_ascii_strcasecmp(tmp
,"presence.wpending")) {
3221 sipe_subscribe_presence_wpending(sip
, msg
);
3223 entry
= entry
->next
;
3225 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
3226 sip
->subscribed
= TRUE
;
3229 /*if (purple_account_get_bool(sip->account, "clientkeepalive", FALSE)) {
3230 purple_debug(PURPLE_DEBUG_MISC, "sipe", "Setting user defined keepalive\n");
3231 sip->keepalive_timeout = purple_account_get_int(sip->account, "keepalive", 0);
3233 tmp
= sipmsg_find_header(msg
, "ms-keep-alive");
3234 timeout
= sipmsg_find_part_of_header(tmp
, "timeout=", ";", NULL
);
3236 sipe_keep_alive_timeout(sip
, timeout
);
3239 sipe_keep_alive_timeout(sip
, g_strdup("300"));
3243 // Should we remove the transaction here?
3244 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
3245 transactions_remove(sip
, tc
);
3250 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
3252 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
3253 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
3257 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
3260 tmp
= g_strsplit(parts
[0], ":", 0);
3261 hostname
= g_strdup(tmp
[0]);
3262 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
3266 tmp
= g_strsplit(parts
[i
], "=", 0);
3268 if (g_strcasecmp("transport", tmp
[0]) == 0) {
3269 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
3270 transport
= SIPE_TRANSPORT_TCP
;
3271 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
3272 transport
= SIPE_TRANSPORT_UDP
;
3281 /* Close old connection */
3282 sipe_connection_cleanup(sip
);
3284 /* Create new connection */
3285 sip
->transport
= transport
;
3286 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3287 hostname
, port
, TRANSPORT_DESCRIPTOR
);
3288 create_connection(sip
, hostname
, port
);
3294 if (sip
->registerstatus
!= 2) {
3295 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
3296 if (sip
->registrar
.retries
> 3) {
3297 sip
->gc
->wants_to_die
= TRUE
;
3298 purple_connection_error(sip
->gc
, _("Wrong Password"));
3301 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3302 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3304 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3306 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3307 fill_auth(sip
, tmp
, &sip
->registrar
);
3308 sip
->registerstatus
= 2;
3309 if (sip
->account
->disconnecting
) {
3310 do_register_exp(sip
, 0);
3318 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
3319 if (warning
!= NULL
) {
3321 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3323 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
3324 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
3327 warning
= g_strdup(_("You have been rejected by the server"));
3330 sip
->gc
->wants_to_die
= TRUE
;
3331 purple_connection_error(sip
->gc
, warning
);
3338 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3339 if (warning
!= NULL
) {
3340 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3341 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
3344 warning
= g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3347 sip
->gc
->wants_to_die
= TRUE
;
3348 purple_connection_error(sip
->gc
, warning
);
3355 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3356 if (warning
!= NULL
) {
3357 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3358 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
3361 warning
= g_strdup(_("Service unavailable: no reason given"));
3364 sip
->gc
->wants_to_die
= TRUE
;
3365 purple_connection_error(sip
->gc
, warning
);
3374 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3377 xmlnode
*xn_categories
;
3378 xmlnode
*xn_category
;
3380 const char *activity
= NULL
;
3382 xn_categories
= xmlnode_from_str(data
, len
);
3383 uri
= xmlnode_get_attrib(xn_categories
, "uri");
3385 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
3387 xn_category
= xmlnode_get_next_twin(xn_category
) )
3389 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
3391 if (!strcmp(attrVar
, "note"))
3394 struct sipe_buddy
*sbuddy
;
3395 xn_node
= xmlnode_get_child(xn_category
, "note");
3396 if (!xn_node
) continue;
3397 xn_node
= xmlnode_get_child(xn_node
, "body");
3398 if (!xn_node
) continue;
3400 note
= xmlnode_get_data(xn_node
);
3403 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3407 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri
,note
);
3408 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3409 sbuddy
->annotation
= g_strdup(note
);
3414 else if(!strcmp(attrVar
, "state"))
3418 xn_node
= xmlnode_get_child(xn_category
, "state");
3419 if (!xn_node
) continue;
3420 xn_node
= xmlnode_get_child(xn_node
, "availability");
3421 if (!xn_node
) continue;
3423 data
= xmlnode_get_data(xn_node
);
3428 activity
= SIPE_STATUS_ID_UNKNOWN
;
3429 else if (avail
< 4500)
3430 activity
= SIPE_STATUS_ID_AVAILABLE
;
3431 else if (avail
< 6000)
3432 activity
= SIPE_STATUS_ID_BRB
;
3433 else if (avail
< 7500)
3434 activity
= SIPE_STATUS_ID_ONPHONE
;
3435 else if (avail
< 9000)
3436 activity
= SIPE_STATUS_ID_BUSY
;
3437 else if (avail
< 12000)
3438 activity
= SIPE_STATUS_ID_DND
;
3439 else if (avail
< 18000)
3440 activity
= SIPE_STATUS_ID_AWAY
;
3442 activity
= SIPE_STATUS_ID_OFFLINE
;
3446 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity
);
3447 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
3450 xmlnode_free(xn_categories
);
3453 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3455 const char *uri
,*state
;
3457 xmlnode
*xn_resource
;
3458 xmlnode
*xn_instance
;
3460 xn_list
= xmlnode_from_str(data
, len
);
3462 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
3464 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
3466 uri
= xmlnode_get_attrib(xn_resource
, "uri");
3467 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
3468 if (!xn_instance
) return;
3470 state
= xmlnode_get_attrib(xn_instance
, "state");
3471 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n",uri
,state
);
3472 if(strstr(state
,"resubscribe")){
3473 sipe_subscribe_presence_single(sip
, uri
);
3478 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3482 gchar
*activity
= NULL
;
3484 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
3485 gboolean isonline
= FALSE
;
3486 xmlnode
*display_name_node
;
3488 pidf
= xmlnode_from_str(data
, len
);
3490 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
3494 uri
= xmlnode_get_attrib(pidf
, "entity");
3496 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
3498 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3499 basicstatus
= xmlnode_get_child(status
, "basic");
3504 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3509 getbasic
= xmlnode_get_data(basicstatus
);
3511 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3516 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
3517 if (strstr(getbasic
, "open")) {
3522 display_name_node
= xmlnode_get_child(pidf
, "display-name");
3523 // updating display name if alias was just URI
3524 if (display_name_node
) {
3525 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3526 GSList
*entry
= buddies
;
3527 PurpleBuddy
*p_buddy
;
3528 char * display_name
= xmlnode_get_data(display_name_node
);
3531 const char *server_alias
;
3534 p_buddy
= entry
->data
;
3536 alias
= (char *)purple_buddy_get_alias(p_buddy
);
3537 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
3538 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
3539 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3540 purple_blist_alias_buddy(p_buddy
, display_name
);
3544 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3546 ( (server_alias
&& strcmp(display_name
, server_alias
))
3547 || !server_alias
|| strlen(server_alias
) == 0 )
3549 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3552 entry
= entry
->next
;
3554 g_free(display_name
);
3557 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
3558 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3559 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
3560 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
3561 activity
= xmlnode_get_data(basicstatus
);
3562 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
3569 const gchar
* status_id
= NULL
;
3571 if (strstr(activity
, "busy")) {
3572 status_id
= SIPE_STATUS_ID_BUSY
;
3573 } else if (strstr(activity
, "away")) {
3574 status_id
= SIPE_STATUS_ID_AWAY
;
3579 status_id
= SIPE_STATUS_ID_AVAILABLE
;
3582 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
3583 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
3585 purple_prpl_got_user_status(sip
->account
, uri
, SIPE_STATUS_ID_OFFLINE
, NULL
);
3592 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3594 const char *availability
;
3595 const char *activity
;
3596 const char *display_name
= NULL
;
3597 const char *activity_name
= NULL
;
3602 struct sipe_buddy
*sbuddy
;
3604 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
3606 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
3607 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
3608 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
3609 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
3610 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
3611 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
3612 xmlnode
*xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
):NULL
;
3613 const char *avail
= xn_state
? xmlnode_get_attrib(xn_state
, "avail") : NULL
;
3615 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
3616 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
3617 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
3618 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
3619 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
3620 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
3622 name
= xmlnode_get_attrib(xn_presentity
, "uri");
3623 uri
= g_strdup_printf("sip:%s", name
);
3624 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
3625 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
3627 // updating display name if alias was just URI
3628 if (xn_display_name
) {
3629 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3630 GSList
*entry
= buddies
;
3631 PurpleBuddy
*p_buddy
;
3632 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
3635 const char *email_str
, *server_alias
;
3637 p_buddy
= entry
->data
;
3639 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
3640 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3641 purple_blist_alias_buddy(p_buddy
, display_name
);
3644 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3646 ( (server_alias
&& strcmp(display_name
, server_alias
))
3647 || !server_alias
|| strlen(server_alias
) == 0 )
3649 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3653 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
3654 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
3655 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
3659 entry
= entry
->next
;
3663 avl
= atoi(availability
);
3664 act
= atoi(activity
);
3666 if(sip
->msrtc_event_categories
){
3667 if (act
== 100 && avl
== 0)
3668 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3669 else if (act
== 100 && avl
== 300)
3670 activity_name
= SIPE_STATUS_ID_AWAY
;
3671 else if (act
== 300 && avl
== 300)
3672 activity_name
= SIPE_STATUS_ID_BRB
;
3673 else if (act
== 400 && avl
== 300)
3674 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3675 else if (act
== 500 && act
== 300)
3676 activity_name
= SIPE_STATUS_ID_ONPHONE
;
3677 else if (act
== 600 && avl
== 300)
3678 activity_name
= SIPE_STATUS_ID_BUSY
;
3679 else if (act
== 0 && avl
== 0){ //MSRTC elements are zero
3680 if(avail
){ //Check for LegacyInterop elements
3683 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3684 else if (avl
== 3500)
3685 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3686 else if (avl
== 15500)
3687 activity_name
= SIPE_STATUS_ID_AWAY
;
3688 else if (avl
== 6500)
3689 activity_name
= SIPE_STATUS_ID_BUSY
;
3690 else if (avl
== 12500)
3691 activity_name
= SIPE_STATUS_ID_BRB
;
3696 if(activity_name
== NULL
){
3698 activity_name
= SIPE_STATUS_ID_AWAY
;
3699 else if (act
<= 150)
3700 activity_name
= SIPE_STATUS_ID_LUNCH
;
3701 else if (act
<= 300)
3702 activity_name
= SIPE_STATUS_ID_BRB
;
3703 else if (act
<= 400)
3704 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3705 else if (act
<= 500)
3706 activity_name
= SIPE_STATUS_ID_ONPHONE
;
3707 else if (act
<= 600)
3708 activity_name
= SIPE_STATUS_ID_BUSY
;
3710 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3713 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3716 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3719 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3720 sbuddy
->annotation
= NULL
;
3721 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
3723 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
3724 sbuddy
->device_name
= NULL
;
3725 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
3728 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
3729 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
3731 xmlnode_free(xn_presentity
);
3735 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3737 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3739 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
3741 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
3742 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
3744 const char *content
= msg
->body
;
3745 unsigned length
= msg
->bodylen
;
3746 PurpleMimeDocument
*mime
= NULL
;
3748 if (strstr(ctype
, "multipart"))
3750 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3751 const char *content_type
;
3753 mime
= purple_mime_document_parse(doc
);
3754 parts
= purple_mime_document_get_parts(mime
);
3756 content
= purple_mime_part_get_data(parts
->data
);
3757 length
= purple_mime_part_get_length(parts
->data
);
3758 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
3759 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
3761 process_incoming_notify_rlmi_resub(sip
, content
, length
);
3763 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
3765 process_incoming_notify_msrtc(sip
, content
, length
);
3769 process_incoming_notify_rlmi(sip
, content
, length
);
3771 parts
= parts
->next
;
3777 purple_mime_document_free(mime
);
3780 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
3782 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
3784 else if(strstr(ctype
, "application/rlmi+xml"))
3786 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
3789 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
3791 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
3795 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
3800 * Dispatcher for all incoming subscription information
3801 * whether it comes from NOTIFY, BENOTIFY requests or
3802 * piggy-backed to subscription's OK responce.
3804 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3805 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3807 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
3809 gchar
*event
= sipmsg_find_header(msg
, "Event");
3810 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
3813 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
3814 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
3816 if (!subscription_state
|| strstr(subscription_state
, "active"))
3818 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
3820 sipe_process_presence(sip
, msg
);
3822 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
3824 sipe_process_roaming_contacts(sip
, msg
, NULL
);
3826 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self") )
3828 sipe_process_roaming_self(sip
, msg
);
3830 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
3832 sipe_process_roaming_acl(sip
, msg
);
3834 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
3836 sipe_process_presence_wpending(sip
, msg
);
3840 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
3844 //The server sends a (BE)NOTIFY with the status 'terminated'
3845 if(request
&& subscription_state
&& strstr(subscription_state
, "terminated") ) {
3846 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3847 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
3851 //For OCS2007/LCS2005 we need to resubscribe the contacts before the expires time in seconds comes to 0
3852 //Can be benotify, notify or 200 0k
3853 if (event
&& subscription_state
&& strstr(subscription_state
, "active") && !g_ascii_strcasecmp(event
, "presence"))//Notify or Benotify
3855 const gchar
*expires_contact
;
3856 expires_contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "subscription-state"), "expires=", ";", NULL
);
3857 timeout
= expires_contact
? strtol(expires_contact
, NULL
, 10) : 0;
3859 purple_debug_info("sipe", "process_incoming_notify: expires_contact:%d\n\n", timeout
);
3860 timeout
= timeout
- 300;
3861 if (timeout
<= 0){ // Only when expires - 300 <= 0 secs
3862 purple_debug_info("sipe", "process_incoming_notify: expires_contact needs re-subscribe:%d\n\n", timeout
);
3863 if (g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
)){
3864 gchar
*who
= parse_from(sipmsg_find_header(msg
, "From"));
3865 gchar
*action_name
= g_strdup_printf("<%s><%s>", "presence", who
);
3866 sipe_schedule_action(action_name
, timeout
, (Action
) sipe_subscribe_presence_batched
, sip
, who
);
3867 g_free(action_name
);
3873 else if (!request
&& !benotify
&& event
&& subscription_state
&& strstr(subscription_state
, "active") && !g_ascii_strcasecmp(event
, "presence.wpending")) //Ok 200
3875 const gchar
*expires_header
;
3876 expires_header
= sipmsg_find_header(msg
, "Expires");
3877 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
3879 purple_debug_info("sipe", "process_incoming_notify: expires:%d\n\n", timeout
);
3880 timeout
= (timeout
- 60) > 60 ? (timeout
- 60) : timeout
; // 1 min ahead of expiration
3881 if (g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
3883 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
3884 sipe_schedule_action(action_name
, timeout
, (Action
) sipe_subscribe_presence_wpending
, sip
, NULL
);
3885 g_free(action_name
);
3890 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
3892 sipe_process_registration_notify(sip
, msg
);
3895 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3896 if (request
&& !benotify
)
3898 sipmsg_remove_header(msg
, "Expires");
3899 sipmsg_remove_header(msg
, "subscription-state");
3900 sipmsg_remove_header(msg
, "Event");
3901 sipmsg_remove_header(msg
, "Require");
3902 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3909 static gchar* gen_xpidf(struct sipe_account_data *sip)
3911 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3913 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3914 "<display name=\"sip:%s\"/>\r\n"
3915 "<atom id=\"1234\">\r\n"
3916 "<address uri=\"sip:%s\">\r\n"
3917 "<status status=\"%s\"/>\r\n"
3930 static gchar* gen_pidf(struct sipe_account_data *sip)
3932 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3933 "<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"
3934 "<tuple id=\"0\">\r\n"
3936 "<basic>open</basic>\r\n"
3937 "<ep:activities>\r\n"
3938 " <ep:activity>%s</ep:activity>\r\n"
3942 "<ci:display-name>%s</ci:display-name>\r\n"
3951 static void send_presence_soap(struct sipe_account_data
*sip
, const char * note
)
3953 int availability
= 300; // online
3954 int activity
= 400; // Available
3957 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
)) {
3959 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
3961 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
3963 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
3965 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
3967 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
3969 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_INVISIBLE
) ||
3970 !strcmp(sip
->status
, SIPE_STATUS_ID_OFFLINE
)) {
3971 availability
= 0; // offline
3974 activity
= 400; // available
3977 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
3978 //@TODO: send user data - state; add hostname in upper case
3979 body
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
3980 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
3986 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3988 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
3989 if (msg
->response
== 200) {
3990 sip
->status_version
= 0;
3991 send_presence_status(sip
);
3997 process_send_presence_category_publish_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3999 if (msg
->response
== 409) {
4000 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4001 // TODO need to parse the version #'s?
4002 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
4003 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
4007 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg
->body
);
4009 tmp
= get_contact(sip
);
4010 hdr
= g_strdup_printf("Contact: %s\r\n"
4011 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4013 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
4023 static void send_presence_category_publish(struct sipe_account_data
*sip
, const char * note
)
4030 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
) ||
4031 !strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4033 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_DND
)) {
4035 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4037 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4039 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4041 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4043 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_UNKNOWN
)) {
4046 // Offline or invisible
4050 uri
= g_strdup_printf("sip:%s", sip
->username
);
4051 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
4052 sip
->status_version
, code
,
4053 sip
->status_version
, code
,
4054 sip
->status_version
, note
? note
: "",
4055 sip
->status_version
, note
? note
: "",
4056 sip
->status_version
, note
? note
: ""
4058 sip
->status_version
++;
4060 tmp
= get_contact(sip
);
4061 hdr
= g_strdup_printf("Contact: %s\r\n"
4062 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4064 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
4072 static void send_presence_status(struct sipe_account_data
*sip
)
4074 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
4076 if (!status
) return;
4078 note
= purple_status_get_attr_string(status
, "message");
4080 if(sip
->msrtc_event_categories
){
4081 send_presence_category_publish(sip
, note
);
4083 send_presence_soap(sip
, note
);
4087 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
4089 gboolean found
= FALSE
;
4090 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
4091 if (msg
->response
== 0) { /* request */
4092 if (!strcmp(msg
->method
, "MESSAGE")) {
4093 process_incoming_message(sip
, msg
);
4095 } else if (!strcmp(msg
->method
, "NOTIFY")) {
4096 purple_debug_info("sipe","send->process_incoming_notify\n");
4097 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
4099 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
4100 purple_debug_info("sipe","send->process_incoming_benotify\n");
4101 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
4103 } else if (!strcmp(msg
->method
, "INVITE")) {
4104 process_incoming_invite(sip
, msg
);
4106 } else if (!strcmp(msg
->method
, "OPTIONS")) {
4107 process_incoming_options(sip
, msg
);
4109 } else if (!strcmp(msg
->method
, "INFO")) {
4111 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4113 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
4116 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4118 } else if (!strcmp(msg
->method
, "ACK")) {
4119 // ACK's don't need any response
4121 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
4122 // LCS 2005 sends us these - just respond 200 OK
4124 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4125 } else if (!strcmp(msg
->method
, "BYE")) {
4126 struct sip_im_session
*session
;
4128 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4130 from
= parse_from(sipmsg_find_header(msg
, "From"));
4131 session
= find_im_session (sip
, from
);
4135 // TODO Let the user know the other user left the conversation?
4136 im_session_destroy(sip
, session
);
4141 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4143 } else { /* response */
4144 struct transaction
*trans
= transactions_find(sip
, msg
);
4146 if (msg
->response
== 407) {
4147 gchar
*resend
, *auth
, *ptmp
;
4149 if (sip
->proxy
.retries
> 30) return;
4150 sip
->proxy
.retries
++;
4151 /* do proxy authentication */
4153 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
4155 fill_auth(sip
, ptmp
, &sip
->proxy
);
4156 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
4157 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4158 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
4160 resend
= sipmsg_to_string(trans
->msg
);
4161 /* resend request */
4162 sendout_pkt(sip
->gc
, resend
);
4165 if (msg
->response
== 100 || msg
->response
== 180) {
4166 /* ignore provisional response */
4167 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
4169 sip
->proxy
.retries
= 0;
4170 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
4171 if (msg
->response
== 401)
4173 sip
->registrar
.retries
++;
4174 sip
->registrar
.expires
= 0;
4178 sip
->registrar
.retries
= 0;
4180 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
4182 if (msg
->response
== 401) {
4183 gchar
*resend
, *auth
, *ptmp
;
4185 if (sip
->registrar
.retries
> 4) return;
4186 sip
->registrar
.retries
++;
4188 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
4189 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
4191 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
4194 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
4196 fill_auth(sip
, ptmp
, &sip
->registrar
);
4197 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
4198 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4199 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
4201 //sipmsg_remove_header(trans->msg, "Authorization");
4202 //sipmsg_add_header(trans->msg, "Authorization", auth);
4204 resend
= sipmsg_to_string(trans
->msg
);
4205 /* resend request */
4206 sendout_pkt(sip
->gc
, resend
);
4211 if (trans
->callback
) {
4212 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
4213 /* call the callback to process response*/
4214 (trans
->callback
)(sip
, msg
, trans
);
4216 /* Not sure if this is needed or what needs to be done
4217 but transactions seem to be removed prematurely so
4218 this only removes them if the response is 200 OK */
4219 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
4220 /*Has a bug and it's unneccesary*/
4221 /*transactions_remove(sip, trans);*/
4227 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
4231 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
4235 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
4243 /* according to the RFC remove CRLF at the beginning */
4244 while (*cur
== '\r' || *cur
== '\n') {
4247 if (cur
!= conn
->inbuf
) {
4248 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
4249 conn
->inbufused
= strlen(conn
->inbuf
);
4252 /* Received a full Header? */
4253 sip
->processing_input
= TRUE
;
4254 while (sip
->processing_input
&&
4255 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
4256 time_t currtime
= time(NULL
);
4259 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
4260 msg
= sipmsg_parse_header(conn
->inbuf
);
4263 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
4264 if (restlen
>= msg
->bodylen
) {
4265 dummy
= g_malloc(msg
->bodylen
+ 1);
4266 memcpy(dummy
, cur
, msg
->bodylen
);
4267 dummy
[msg
->bodylen
] = '\0';
4269 cur
+= msg
->bodylen
;
4270 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
4271 conn
->inbufused
= strlen(conn
->inbuf
);
4273 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4274 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
4280 purple_debug_info("sipe", "body:\n%s", msg->body);
4283 // Verify the signature before processing it
4284 if (sip
->registrar
.ntlm_key
) {
4285 struct sipmsg_breakdown msgbd
;
4286 gchar
*signature_input_str
;
4287 gchar
*signature
= NULL
;
4290 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
4291 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
4292 if (signature_input_str
!= NULL
) {
4293 signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
4295 g_free(signature_input_str
);
4297 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
4299 if (signature
!= NULL
) {
4300 if (rspauth
!= NULL
) {
4301 if (purple_ntlm_verify_signature (signature
, rspauth
)) {
4302 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
4303 process_input_message(sip
, msg
);
4305 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth
, signature
);
4306 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
4307 sip
->gc
->wants_to_die
= TRUE
;
4309 } else if (msg
->response
== 401) {
4310 purple_connection_error(sip
->gc
, _("Wrong Password"));
4311 sip
->gc
->wants_to_die
= TRUE
;
4317 sipmsg_breakdown_free(&msgbd
);
4319 process_input_message(sip
, msg
);
4326 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
4328 PurpleConnection
*gc
= data
;
4329 struct sipe_account_data
*sip
= gc
->proto_data
;
4334 static char buffer
[65536];
4335 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
4337 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
4338 msg
= sipmsg_parse_msg(buffer
);
4339 if (msg
) process_input_message(sip
, msg
);
4343 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
4345 struct sipe_account_data
*sip
= gc
->proto_data
;
4346 PurpleSslConnection
*gsc
= sip
->gsc
;
4348 purple_debug_error("sipe", "%s",debug
);
4349 purple_connection_error(gc
, msg
);
4351 /* Invalidate this connection. Next send will open a new one */
4353 connection_remove(sip
, gsc
->fd
);
4354 purple_ssl_close(gsc
);
4360 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4362 PurpleConnection
*gc
= data
;
4363 struct sipe_account_data
*sip
;
4364 struct sip_connection
*conn
;
4366 gboolean firstread
= TRUE
;
4368 /* NOTE: This check *IS* necessary */
4369 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
4370 purple_ssl_close(gsc
);
4374 sip
= gc
->proto_data
;
4375 conn
= connection_find(sip
, gsc
->fd
);
4377 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4378 gc
->wants_to_die
= TRUE
;
4379 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
4383 /* Read all available data from the SSL connection */
4385 /* Increase input buffer size as needed */
4386 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4387 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4388 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4389 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
4392 /* Try to read as much as there is space left in the buffer */
4393 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
4394 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
4396 if (len
< 0 && errno
== EAGAIN
) {
4397 /* Try again later */
4399 } else if (len
< 0) {
4400 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
4402 } else if (firstread
&& (len
== 0)) {
4403 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
4407 conn
->inbufused
+= len
;
4410 /* Equivalence indicates that there is possibly more data to read */
4411 } while (len
== readlen
);
4413 conn
->inbuf
[conn
->inbufused
] = '\0';
4414 process_input(sip
, conn
);
4418 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4420 PurpleConnection
*gc
= data
;
4421 struct sipe_account_data
*sip
= gc
->proto_data
;
4423 struct sip_connection
*conn
= connection_find(sip
, source
);
4425 purple_debug_error("sipe", "Connection not found!\n");
4429 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4430 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4431 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4434 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
4436 if (len
< 0 && errno
== EAGAIN
)
4438 else if (len
<= 0) {
4439 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4440 connection_remove(sip
, source
);
4441 if (sip
->fd
== source
) sip
->fd
= -1;
4445 conn
->inbufused
+= len
;
4446 conn
->inbuf
[conn
->inbufused
] = '\0';
4448 process_input(sip
, conn
);
4451 /* Callback for new connections on incoming TCP port */
4452 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4454 PurpleConnection
*gc
= data
;
4455 struct sipe_account_data
*sip
= gc
->proto_data
;
4456 struct sip_connection
*conn
;
4458 int newfd
= accept(source
, NULL
, NULL
);
4460 conn
= connection_create(sip
, newfd
);
4462 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4465 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
4467 PurpleConnection
*gc
= data
;
4468 struct sipe_account_data
*sip
;
4469 struct sip_connection
*conn
;
4471 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4479 purple_connection_error(gc
, _("Could not connect"));
4483 sip
= gc
->proto_data
;
4485 sip
->last_keepalive
= time(NULL
);
4487 conn
= connection_create(sip
, source
);
4491 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4494 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4496 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
4497 if (sip
== NULL
) return;
4502 static guint
sipe_ht_hash_nick(const char *nick
)
4504 char *lc
= g_utf8_strdown(nick
, -1);
4505 guint bucket
= g_str_hash(lc
);
4511 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
4513 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
4516 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
4518 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4520 sip
->listen_data
= NULL
;
4522 if (listenfd
== -1) {
4523 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4529 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
4530 sip
->listenfd
= sip
->fd
;
4532 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
4534 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
4538 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
4540 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4543 sip
->query_data
= NULL
;
4545 if (!hosts
|| !hosts
->data
) {
4546 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
4550 addr_size
= GPOINTER_TO_INT(hosts
->data
);
4551 hosts
= g_slist_remove(hosts
, hosts
->data
);
4552 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
4553 g_free(hosts
->data
);
4554 hosts
= g_slist_remove(hosts
, hosts
->data
);
4556 hosts
= g_slist_remove(hosts
, hosts
->data
);
4557 g_free(hosts
->data
);
4558 hosts
= g_slist_remove(hosts
, hosts
->data
);
4561 /* create socket for incoming connections */
4562 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
4563 sipe_udp_host_resolved_listen_cb
, sip
);
4564 if (sip
->listen_data
== NULL
) {
4565 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4570 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
4573 PurpleConnection
*gc
= data
;
4574 struct sipe_account_data
*sip
;
4576 /* If the connection is already disconnected, we don't need to do anything else */
4577 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4580 sip
= gc
->proto_data
;
4585 case PURPLE_SSL_CONNECT_FAILED
:
4586 purple_connection_error(gc
, _("Connection Failed"));
4588 case PURPLE_SSL_HANDSHAKE_FAILED
:
4589 purple_connection_error(gc
, _("SSL Handshake Failed"));
4591 case PURPLE_SSL_CERTIFICATE_INVALID
:
4592 purple_connection_error(gc
, _("SSL Certificate Invalid"));
4598 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
4600 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4601 PurpleProxyConnectData
*connect_data
;
4603 sip
->listen_data
= NULL
;
4605 sip
->listenfd
= listenfd
;
4606 if (sip
->listenfd
== -1) {
4607 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4611 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
4612 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4613 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
4614 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
4615 sipe_newconn_cb
, sip
->gc
);
4616 purple_debug_info("sipe", "connecting to %s port %d\n",
4617 sip
->realhostname
, sip
->realport
);
4618 /* open tcp connection to the server */
4619 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
4620 sip
->realport
, login_cb
, sip
->gc
);
4622 if (connect_data
== NULL
) {
4623 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
4628 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
4630 PurpleAccount
*account
= sip
->account
;
4631 PurpleConnection
*gc
= sip
->gc
;
4633 if (purple_account_get_bool(account
, "useport", FALSE
)) {
4634 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
4635 port
= purple_account_get_int(account
, "port", 0);
4637 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
4640 sip
->realhostname
= hostname
;
4641 sip
->realport
= port
;
4643 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
4646 /* TODO: is there a good default grow size? */
4647 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
4648 sip
->txbuf
= purple_circ_buffer_new(0);
4650 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
4652 if (!purple_ssl_is_supported()) {
4653 gc
->wants_to_die
= TRUE
;
4654 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4658 purple_debug_info("sipe", "using SSL\n");
4660 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
4661 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
4662 if (sip
->gsc
== NULL
) {
4663 purple_connection_error(gc
, _("Could not create SSL context"));
4666 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
4668 purple_debug_info("sipe", "using UDP\n");
4670 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
4671 if (sip
->query_data
== NULL
) {
4672 purple_connection_error(gc
, _("Could not resolve hostname"));
4676 purple_debug_info("sipe", "using TCP\n");
4677 /* create socket for incoming connections */
4678 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
4679 sipe_tcp_connect_listen_cb
, sip
);
4680 if (sip
->listen_data
== NULL
) {
4681 purple_connection_error(gc
, _("Could not create listen socket"));
4687 /* Service list for autodection */
4688 static const struct sipe_service_data service_autodetect
[] = {
4689 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4690 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4691 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4692 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4696 /* Service list for SSL/TLS */
4697 static const struct sipe_service_data service_tls
[] = {
4698 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4699 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4703 /* Service list for TCP */
4704 static const struct sipe_service_data service_tcp
[] = {
4705 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4706 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4710 /* Service list for UDP */
4711 static const struct sipe_service_data service_udp
[] = {
4712 { "sip", "udp", SIPE_TRANSPORT_UDP
},
4716 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
4717 static void resolve_next_service(struct sipe_account_data
*sip
,
4718 const struct sipe_service_data
*start
)
4721 sip
->service_data
= start
;
4723 sip
->service_data
++;
4724 if (sip
->service_data
->service
== NULL
) {
4726 /* Try connecting to the SIP hostname directly */
4727 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
4728 if (sip
->auto_transport
) {
4729 // If SSL is supported, default to using it; OCS servers aren't configured
4730 // by default to accept TCP
4731 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4732 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
4733 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
4736 hostname
= g_strdup(sip
->sipdomain
);
4737 create_connection(sip
, hostname
, 0);
4742 /* Try to resolve next service */
4743 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
4744 sip
->service_data
->transport
,
4749 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
4751 struct sipe_account_data
*sip
= data
;
4753 sip
->srv_query_data
= NULL
;
4755 /* find the host to connect to */
4757 gchar
*hostname
= g_strdup(resp
->hostname
);
4758 int port
= resp
->port
;
4759 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4763 sip
->transport
= sip
->service_data
->type
;
4765 create_connection(sip
, hostname
, port
);
4767 resolve_next_service(sip
, NULL
);
4771 static void sipe_login(PurpleAccount
*account
)
4773 PurpleConnection
*gc
;
4774 struct sipe_account_data
*sip
;
4775 gchar
**signinname_login
, **userserver
, **domain_user
;
4776 const char *transport
;
4778 const char *username
= purple_account_get_username(account
);
4779 gc
= purple_account_get_connection(account
);
4781 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
4782 gc
->wants_to_die
= TRUE
;
4783 purple_connection_error(gc
, _("SIP Exchange username contains invalid characters"));
4787 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
4788 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
4789 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
4791 sip
->account
= account
;
4792 sip
->reregister_set
= FALSE
;
4793 sip
->reauthenticate_set
= FALSE
;
4794 sip
->subscribed
= FALSE
;
4795 sip
->subscribed_buddies
= FALSE
;
4797 signinname_login
= g_strsplit(username
, ",", 2);
4799 userserver
= g_strsplit(signinname_login
[0], "@", 2);
4800 purple_connection_set_display_name(gc
, userserver
[0]);
4801 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
4802 sip
->sipdomain
= g_strdup(userserver
[1]);
4804 if (strpbrk(sip
->username
, " \t\v\r\n") != NULL
) {
4805 gc
->wants_to_die
= TRUE
;
4806 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
4810 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
4811 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
4812 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
4814 sip
->password
= g_strdup(purple_connection_get_password(gc
));
4816 g_strfreev(userserver
);
4817 g_strfreev(domain_user
);
4818 g_strfreev(signinname_login
);
4820 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
4822 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
4824 /* TODO: Set the status correctly. */
4825 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
4827 transport
= purple_account_get_string(account
, "transport", "auto");
4828 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
4829 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
4832 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
4833 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
4834 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
4835 } else if (strcmp(transport
, "auto") == 0) {
4836 sip
->auto_transport
= TRUE
;
4837 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
4838 } else if (strcmp(transport
, "tls") == 0) {
4839 resolve_next_service(sip
, service_tls
);
4840 } else if (strcmp(transport
, "tcp") == 0) {
4841 resolve_next_service(sip
, service_tcp
);
4843 resolve_next_service(sip
, service_udp
);
4847 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
4849 connection_free_all(sip
);
4854 if (sip
->query_data
!= NULL
)
4855 purple_dnsquery_destroy(sip
->query_data
);
4856 sip
->query_data
= NULL
;
4858 if (sip
->srv_query_data
!= NULL
)
4859 purple_srv_cancel(sip
->srv_query_data
);
4860 sip
->srv_query_data
= NULL
;
4862 if (sip
->listen_data
!= NULL
)
4863 purple_network_listen_cancel(sip
->listen_data
);
4864 sip
->listen_data
= NULL
;
4866 if (sip
->gsc
!= NULL
)
4867 purple_ssl_close(sip
->gsc
);
4870 sipe_auth_free(&sip
->registrar
);
4871 sipe_auth_free(&sip
->proxy
);
4874 purple_circ_buffer_destroy(sip
->txbuf
);
4877 g_free(sip
->realhostname
);
4878 sip
->realhostname
= NULL
;
4881 purple_input_remove(sip
->listenpa
);
4883 if (sip
->tx_handler
)
4884 purple_input_remove(sip
->tx_handler
);
4885 sip
->tx_handler
= 0;
4886 if (sip
->resendtimeout
)
4887 purple_timeout_remove(sip
->resendtimeout
);
4888 sip
->resendtimeout
= 0;
4889 if (sip
->timeouts
) {
4890 GSList
*entry
= sip
->timeouts
;
4892 struct scheduled_action
*sched_action
= entry
->data
;
4893 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
4894 purple_timeout_remove(sched_action
->timeout_handler
);
4895 g_free(sched_action
->payload
);
4896 g_free(sched_action
->name
);
4897 g_free(sched_action
);
4898 entry
= entry
->next
;
4901 g_slist_free(sip
->timeouts
);
4903 if (sip
->allow_events
) {
4904 GSList
*entry
= sip
->allow_events
;
4906 g_free(entry
->data
);
4907 entry
= entry
->next
;
4910 g_slist_free(sip
->allow_events
);
4913 g_free(sip
->contact
);
4914 sip
->contact
= NULL
;
4916 g_free(sip
->regcallid
);
4917 sip
->regcallid
= NULL
;
4920 sip
->processing_input
= FALSE
;
4924 * A callback for g_hash_table_foreach_remove
4926 static gboolean
sipe_buddy_remove(gpointer key
, struct sipe_buddy
*buddy
, gpointer user_data
)
4928 sipe_free_buddy(buddy
);
4931 static void sipe_close(PurpleConnection
*gc
)
4933 struct sipe_account_data
*sip
= gc
->proto_data
;
4936 /* leave all conversations */
4937 im_session_close_all(sip
);
4940 do_register_exp(sip
, 0);
4942 sipe_connection_cleanup(sip
);
4943 g_free(sip
->sipdomain
);
4944 g_free(sip
->username
);
4945 g_free(sip
->password
);
4946 g_free(sip
->authdomain
);
4947 g_free(sip
->authuser
);
4948 g_free(sip
->status
);
4950 g_hash_table_foreach_remove(sip
->buddies
, (GHRFunc
) sipe_buddy_remove
, NULL
);
4951 g_hash_table_destroy(sip
->buddies
);
4953 g_free(gc
->proto_data
);
4954 gc
->proto_data
= NULL
;
4957 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4959 PurpleAccount
*acct
= purple_connection_get_account(gc
);
4960 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
4961 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
4963 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
4964 purple_conversation_present(conv
);
4968 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
4971 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
4972 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
4975 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
4977 PurpleNotifySearchResults
*results
;
4978 PurpleNotifySearchColumn
*column
;
4979 xmlnode
*searchResults
;
4981 int match_count
= 0;
4982 gboolean more
= FALSE
;
4985 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
4987 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4988 if (!searchResults
) {
4989 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
4993 results
= purple_notify_searchresults_new();
4995 if (results
== NULL
) {
4996 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
4997 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
4999 xmlnode_free(searchResults
);
5003 column
= purple_notify_searchresults_column_new(_("User Name"));
5004 purple_notify_searchresults_column_add(results
, column
);
5006 column
= purple_notify_searchresults_column_new(_("Name"));
5007 purple_notify_searchresults_column_add(results
, column
);
5009 column
= purple_notify_searchresults_column_new(_("Company"));
5010 purple_notify_searchresults_column_add(results
, column
);
5012 column
= purple_notify_searchresults_column_new(_("Country"));
5013 purple_notify_searchresults_column_add(results
, column
);
5015 column
= purple_notify_searchresults_column_new(_("Email"));
5016 purple_notify_searchresults_column_add(results
, column
);
5018 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
5021 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
5022 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
5023 g_strfreev(uri_parts
);
5025 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
5026 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
5027 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
5028 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
5030 purple_notify_searchresults_row_add(results
, row
);
5034 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
5035 char *data
= xmlnode_get_data_unescaped(mrow
);
5036 more
= (g_strcasecmp(data
, "true") == 0);
5040 secondary
= g_strdup_printf(
5041 dngettext(GETTEXT_PACKAGE
,
5042 "Found %d contact%s:",
5043 "Found %d contacts%s:", match_count
),
5044 match_count
, more
? _(" (more matched your query)") : "");
5046 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
5047 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
5048 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
5051 xmlnode_free(searchResults
);
5055 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
5057 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
5058 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
5062 PurpleRequestField
*field
= entries
->data
;
5063 const char *id
= purple_request_field_get_id(field
);
5064 const char *value
= purple_request_field_string_get_value(field
);
5066 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
5068 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
5069 } while ((entries
= g_list_next(entries
)) != NULL
);
5073 gchar
*query
= g_strjoinv(NULL
, attrs
);
5074 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
5075 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
5076 send_soap_request_with_cb(gc
->proto_data
, body
,
5077 (TransCallback
) process_search_contact_response
, NULL
);
5085 static void sipe_show_find_contact(PurplePluginAction
*action
)
5087 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
5088 PurpleRequestFields
*fields
;
5089 PurpleRequestFieldGroup
*group
;
5090 PurpleRequestField
*field
;
5092 fields
= purple_request_fields_new();
5093 group
= purple_request_field_group_new(NULL
);
5094 purple_request_fields_add_group(fields
, group
);
5096 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
5097 purple_request_field_group_add_field(group
, field
);
5098 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
5099 purple_request_field_group_add_field(group
, field
);
5100 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
5101 purple_request_field_group_add_field(group
, field
);
5102 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
5103 purple_request_field_group_add_field(group
, field
);
5105 purple_request_fields(gc
,
5107 _("Search for a Contact"),
5108 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5110 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
5112 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
5115 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
5118 PurplePluginAction
*act
;
5120 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
5121 menu
= g_list_prepend(menu
, act
);
5123 menu
= g_list_reverse(menu
);
5128 static void dummy_permit_deny(PurpleConnection
*gc
)
5132 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
5138 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
5144 static char *sipe_status_text(PurpleBuddy
*buddy
)
5146 struct sipe_account_data
*sip
;
5147 struct sipe_buddy
*sbuddy
;
5150 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5151 if (sip
) //happens on pidgin exit
5153 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5154 if (sbuddy
&& sbuddy
->annotation
)
5156 text
= g_strdup(sbuddy
->annotation
);
5163 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
5165 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
5166 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
5167 struct sipe_account_data
*sip
;
5168 struct sipe_buddy
*sbuddy
;
5169 char *annotation
= NULL
;
5171 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5172 if (sip
) //happens on pidgin exit
5174 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5177 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
5182 if (purple_presence_is_online(presence
))
5184 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
5189 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
5196 sipe_get_account_text_table(PurpleAccount
*account
)
5199 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
5200 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
5204 static PurpleBuddy
*
5205 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
5208 const gchar
*server_alias
, *email
;
5209 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
5211 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
5213 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
5215 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
5217 purple_blist_server_alias_buddy(clone
, server_alias
);
5220 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5222 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
5225 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
5227 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
5232 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
5234 PurpleBuddy
*buddy
, *b
;
5235 PurpleConnection
*gc
;
5236 PurpleGroup
* group
= purple_find_group(group_name
);
5238 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
5240 buddy
= (PurpleBuddy
*)node
;
5242 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
5243 gc
= purple_account_get_connection(buddy
->account
);
5245 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
5247 b
= purple_blist_add_buddy_clone(group
, buddy
);
5250 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
5254 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
5257 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
5259 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5262 char *mailto
= g_strdup_printf("mailto:%s", email
);
5263 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
5267 char *const parmList
[] = {mailto
, NULL
};
5268 if ((pid
= fork()) == -1)
5270 purple_debug_info("sipe", "fork() error\n");
5274 execvp("xdg-email", parmList
);
5275 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5283 //@TODO resolve env variable %WINDIR% first
5284 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
5287 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
5296 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
5301 * A menu which appear when right-clicking on buddy in contact list.
5304 sipe_buddy_menu(PurpleBuddy
*buddy
)
5306 PurpleBlistNode
*g_node
;
5307 PurpleGroup
*group
, *gr_parent
;
5308 PurpleMenuAction
*act
;
5310 GList
*menu_groups
= NULL
;
5312 act
= purple_menu_action_new(_("Send Email..."),
5313 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
5315 menu
= g_list_prepend(menu
, act
);
5317 gr_parent
= purple_buddy_get_group(buddy
);
5318 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
5319 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
5322 group
= (PurpleGroup
*)g_node
;
5323 if (group
== gr_parent
)
5326 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
5329 act
= purple_menu_action_new(purple_group_get_name(group
),
5330 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
5332 menu_groups
= g_list_prepend(menu_groups
, act
);
5334 menu_groups
= g_list_reverse(menu_groups
);
5336 act
= purple_menu_action_new(_("Copy to"),
5339 menu
= g_list_prepend(menu
, act
);
5340 menu
= g_list_reverse(menu
);
5345 GList
*sipe_blist_node_menu(PurpleBlistNode
*node
) {
5346 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
5347 return sipe_buddy_menu((PurpleBuddy
*) node
);
5354 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
5356 gboolean ret
= TRUE
;
5357 char *username
= (char *)trans
->payload
;
5359 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
5360 PurpleBuddy
*pbuddy
;
5361 struct sipe_buddy
*sbuddy
;
5363 char *server_alias
= NULL
;
5365 const char *device_name
= NULL
;
5367 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
5369 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
5370 alias
= purple_buddy_get_local_alias(pbuddy
);
5374 //will query buddy UA's capabilities and send answer to log
5375 sipe_options_request(sip
, username
);
5377 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
5380 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
5384 if (msg
->response
!= 200) {
5385 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
5387 xmlnode
*searchResults
;
5390 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
5391 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5392 if (!searchResults
) {
5393 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5394 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
5395 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
5396 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5397 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
5398 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
5399 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
5400 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
5401 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
5402 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
5403 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
5404 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
5405 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5406 if (!email
|| strcmp("", email
)) {
5407 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
5408 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
5412 xmlnode_free(searchResults
);
5415 purple_notify_user_info_add_section_break(info
);
5417 if (!server_alias
|| !strcmp("", server_alias
)) {
5418 g_free(server_alias
);
5419 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
5421 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5425 // same as server alias, do not present
5426 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
5429 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
5432 if (!email
|| !strcmp("", email
)) {
5434 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
5436 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5442 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
5445 /* show a buddy's user info in a nice dialog box */
5446 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
5447 username
, /* buddy's username */
5449 NULL
, /* callback called when dialog closed */
5450 NULL
); /* userdata for callback */
5456 * AD search first, LDAP based
5458 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
5460 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
5461 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
5463 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
5464 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
5465 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
5470 static PurplePlugin
*my_protocol
= NULL
;
5472 static PurplePluginProtocolInfo prpl_info
=
5475 NULL
, /* user_splits */
5476 NULL
, /* protocol_options */
5477 NO_BUDDY_ICONS
, /* icon_spec */
5478 sipe_list_icon
, /* list_icon */
5479 NULL
, /* list_emblems */
5480 sipe_status_text
, /* status_text */
5481 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
5482 sipe_status_types
, /* away_states */
5483 sipe_blist_node_menu
, /* blist_node_menu */
5484 NULL
, /* chat_info */
5485 NULL
, /* chat_info_defaults */
5486 sipe_login
, /* login */
5487 sipe_close
, /* close */
5488 sipe_im_send
, /* send_im */
5489 NULL
, /* set_info */ // TODO maybe
5490 sipe_send_typing
, /* send_typing */
5491 sipe_get_info
, /* get_info */
5492 sipe_set_status
, /* set_status */
5493 NULL
, /* set_idle */
5494 NULL
, /* change_passwd */
5495 sipe_add_buddy
, /* add_buddy */
5496 NULL
, /* add_buddies */
5497 sipe_remove_buddy
, /* remove_buddy */
5498 NULL
, /* remove_buddies */
5499 sipe_add_permit
, /* add_permit */
5500 sipe_add_deny
, /* add_deny */
5501 sipe_add_deny
, /* rem_permit */
5502 sipe_add_permit
, /* rem_deny */
5503 dummy_permit_deny
, /* set_permit_deny */
5504 NULL
, /* join_chat */
5505 NULL
, /* reject_chat */
5506 NULL
, /* get_chat_name */
5507 NULL
, /* chat_invite */
5508 NULL
, /* chat_leave */
5509 NULL
, /* chat_whisper */
5510 NULL
, /* chat_send */
5511 sipe_keep_alive
, /* keepalive */
5512 NULL
, /* register_user */
5513 NULL
, /* get_cb_info */ // deprecated
5514 NULL
, /* get_cb_away */ // deprecated
5515 sipe_alias_buddy
, /* alias_buddy */
5516 sipe_group_buddy
, /* group_buddy */
5517 sipe_rename_group
, /* rename_group */
5518 NULL
, /* buddy_free */
5519 sipe_convo_closed
, /* convo_closed */
5520 purple_normalize_nocase
, /* normalize */
5521 NULL
, /* set_buddy_icon */
5522 sipe_remove_group
, /* remove_group */
5523 NULL
, /* get_cb_real_name */ // TODO?
5524 NULL
, /* set_chat_topic */
5525 NULL
, /* find_blist_chat */
5526 NULL
, /* roomlist_get_list */
5527 NULL
, /* roomlist_cancel */
5528 NULL
, /* roomlist_expand_category */
5529 NULL
, /* can_receive_file */
5530 NULL
, /* send_file */
5531 NULL
, /* new_xfer */
5532 NULL
, /* offline_message */
5533 NULL
, /* whiteboard_prpl_ops */
5534 sipe_send_raw
, /* send_raw */
5535 NULL
, /* roomlist_room_serialize */
5536 NULL
, /* unregister_user */
5537 NULL
, /* send_attention */
5538 NULL
, /* get_attention_types */
5540 sizeof(PurplePluginProtocolInfo
), /* struct_size */
5541 sipe_get_account_text_table
, /* get_account_text_table */
5545 static PurplePluginInfo info
= {
5546 PURPLE_PLUGIN_MAGIC
,
5547 PURPLE_MAJOR_VERSION
,
5548 PURPLE_MINOR_VERSION
,
5549 PURPLE_PLUGIN_PROTOCOL
, /**< type */
5550 NULL
, /**< ui_requirement */
5552 NULL
, /**< dependencies */
5553 PURPLE_PRIORITY_DEFAULT
, /**< priority */
5554 "prpl-sipe", /**< id */
5555 "Microsoft LCS/OCS", /**< name */
5556 VERSION
, /**< version */
5557 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5558 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5559 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5560 "Gabriel Burt <gburt@novell.com>", /**< author */
5561 PURPLE_WEBSITE
, /**< homepage */
5562 sipe_plugin_load
, /**< load */
5563 sipe_plugin_unload
, /**< unload */
5564 sipe_plugin_destroy
, /**< destroy */
5565 NULL
, /**< ui_info */
5566 &prpl_info
, /**< extra_info */
5575 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
5579 entry
= prpl_info
.protocol_options
;
5581 purple_account_option_destroy(entry
->data
);
5582 entry
= g_list_delete_link(entry
, entry
);
5584 prpl_info
.protocol_options
= NULL
;
5586 entry
= prpl_info
.user_splits
;
5588 purple_account_user_split_destroy(entry
->data
);
5589 entry
= g_list_delete_link(entry
, entry
);
5591 prpl_info
.user_splits
= NULL
;
5594 static void init_plugin(PurplePlugin
*plugin
)
5596 PurpleAccountUserSplit
*split
;
5597 PurpleAccountOption
*option
;
5600 purple_debug_info(PACKAGE
, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
5601 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s",
5602 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
5605 purple_plugin_register(plugin
);
5607 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
5608 purple_account_user_split_set_reverse(split
, FALSE
);
5609 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
5611 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
5612 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5613 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5614 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5616 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
5617 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5618 // Translators: noun (networking port)
5619 option
= purple_account_option_int_new(_("Port"), "port", 5061);
5620 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5622 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
5623 purple_account_option_add_list_item(option
, _("Auto"), "auto");
5624 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
5625 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
5626 purple_account_option_add_list_item(option
, _("UDP"), "udp");
5627 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5629 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5630 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5632 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
5633 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5635 // TODO commented out so won't show in the preferences until we fix krb message signing
5636 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5637 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5639 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5640 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5641 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5644 /*option = purple_account_option_bool_new(_("Use Client-specified Keepalive"), "clientkeepalive", FALSE);
5645 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5646 option = purple_account_option_int_new(_("Keepalive Timeout"), "keepalive", 300);
5647 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5648 my_protocol
= plugin
;
5651 /* I had to redefined the function for it load, but works */
5652 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
5653 plugin
->info
= &(info
);
5654 init_plugin((plugin
));
5655 sipe_plugin_load((plugin
));
5656 return purple_plugin_register(plugin
);