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 /* Action name templates */
110 #define ACTION_NAME_PRESENCE "<presence><%s>"
112 static char *gentag()
114 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
117 static gchar
*get_epid(struct sipe_account_data
*sip
)
120 sip
->epid
= sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
122 return g_strdup(sip
->epid
);
125 static char *genbranch()
127 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
128 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
129 rand() & 0xFFFF, rand() & 0xFFFF);
132 static char *gencallid()
134 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
135 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
136 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
137 rand() & 0xFFFF, rand() & 0xFFFF);
140 static gchar
*find_tag(const gchar
*hdr
)
142 gchar
* tag
= sipmsg_find_part_of_header (hdr
, "tag=", ";", NULL
);
144 // In case it's at the end and there's no trailing ;
145 tag
= sipmsg_find_part_of_header (hdr
, "tag=", NULL
, NULL
);
151 static const char *sipe_list_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
156 static void sipe_plugin_destroy(PurplePlugin
*plugin
);
158 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
);
160 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
161 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
164 static void sipe_close(PurpleConnection
*gc
);
166 static void send_presence_status(struct sipe_account_data
*sip
);
168 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
170 static void sipe_keep_alive(PurpleConnection
*gc
)
172 struct sipe_account_data
*sip
= gc
->proto_data
;
173 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
174 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
175 gchar buf
[2] = {0, 0};
176 purple_debug_info("sipe", "sending keep alive\n");
177 sendto(sip
->fd
, buf
, 1, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
));
179 time_t now
= time(NULL
);
180 if ((sip
->keepalive_timeout
> 0) &&
181 ((now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
182 #if PURPLE_VERSION_CHECK(2,4,0)
183 && ((now
- gc
->last_received
) >= sip
->keepalive_timeout
)
186 purple_debug_info("sipe", "sending keep alive %d\n",sip
->keepalive_timeout
);
187 sendout_pkt(gc
, "\r\n\r\n");
188 sip
->last_keepalive
= now
;
193 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
195 struct sip_connection
*ret
= NULL
;
196 GSList
*entry
= sip
->openconns
;
199 if (ret
->fd
== fd
) return ret
;
205 static void sipe_auth_free(struct sip_auth
*auth
)
209 g_free(auth
->opaque
);
213 g_free(auth
->target
);
215 g_free(auth
->digest_session_key
);
216 auth
->digest_session_key
= NULL
;
217 g_free(auth
->ntlm_key
);
218 auth
->ntlm_key
= NULL
;
219 auth
->type
= AUTH_TYPE_UNSET
;
224 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
226 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
228 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
232 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
234 struct sip_connection
*conn
= connection_find(sip
, fd
);
236 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
237 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
243 static void connection_free_all(struct sipe_account_data
*sip
)
245 struct sip_connection
*ret
= NULL
;
246 GSList
*entry
= sip
->openconns
;
249 connection_remove(sip
, ret
->fd
);
250 entry
= sip
->openconns
;
254 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
256 const gchar
*method
= msg
->method
;
257 const gchar
*target
= msg
->target
;
262 const char *authdomain
= sip
->authdomain
;
263 const char *authuser
= sip
->authuser
;
264 //const char *krb5_realm;
266 //gchar *krb5_token = NULL;
268 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
269 // and do error checking
271 // KRB realm should always be uppercase
272 //krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
274 if (sip
->realhostname
) {
275 host
= sip
->realhostname
;
276 } else if (purple_account_get_bool(sip
->account
, "useproxy", TRUE
)) {
277 host
= purple_account_get_string(sip
->account
, "proxy", "");
279 host
= sip
->sipdomain
;
282 /*gboolean new_auth = krb5_auth.gss_context == NULL;
284 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
287 if (new_auth || force_reauth) {
288 krb5_token = krb5_auth.base64_token;
291 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
292 krb5_token = krb5_auth.base64_token;*/
298 if (!authuser
|| strlen(authuser
) < 1) {
299 authuser
= sip
->username
;
302 if (auth
->type
== AUTH_TYPE_DIGEST
) { /* Digest */
303 sprintf(noncecount
, "%08d", auth
->nc
++);
304 response
= purple_cipher_http_digest_calculate_response(
305 "md5", method
, target
, NULL
, NULL
,
306 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
307 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
309 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
);
312 } else if (auth
->type
== AUTH_TYPE_NTLM
) { /* NTLM */
313 // If we have a signature for the message, include that
314 if (msg
->signature
) {
315 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
);
319 if (auth
->nc
== 3 && auth
->nonce
&& auth
->ntlm_key
== NULL
) {
320 const gchar
*ntlm_key
;
322 #if GLIB_CHECK_VERSION(2,8,0)
323 const gchar
* hostname
= g_get_host_name();
325 static char hostname
[256];
326 int ret
= gethostname(hostname
, sizeof(hostname
));
327 hostname
[sizeof(hostname
) - 1] = '\0';
328 if (ret
== -1 || hostname
[0] == '\0') {
329 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Error when getting host name. Using \"localhost.\"\n");
331 strcpy(hostname
, "localhost");
334 /*const gchar * hostname = purple_get_host_name();*/
336 gssapi_data
= purple_ntlm_gen_authenticate(&ntlm_key
, authuser
, sip
->password
, hostname
, authdomain
, (const guint8
*)auth
->nonce
, &auth
->flags
);
337 auth
->ntlm_key
= (gchar
*)ntlm_key
;
338 tmp
= g_strdup_printf("NTLM qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth
->opaque
, auth
->realm
, auth
->target
, gssapi_data
);
343 tmp
= g_strdup_printf("NTLM qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth
->realm
, auth
->target
);
345 } else if (auth
->type
== AUTH_TYPE_KERBEROS
) {
348 /*if (new_auth || force_reauth) {
349 printf ("krb5 token not NULL, so adding gssapi-data attribute; op = %s\n", auth->opaque);
351 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);
353 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", "SIP Communications Service", auth->target, krb5_token);
356 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
357 gchar * mic = "MICTODO";
358 printf ("krb5 token is NULL, so adding response attribute with mic = %s, op=%s\n", mic, auth->opaque);
359 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\", response=\"%s\"", "SIP Communications Service", auth->opaque, auth->target, mic);
360 //tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", opaque=\"%s\", targetname=\"%s\"", "SIP Communications Service",
361 //auth->opaque ? auth->opaque : "", auth->target);
362 tmp = g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\"", "SIP Communications Service", auth->target);
367 tmp
= g_strdup_printf("Kerberos qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", "SIP Communication Service", auth
->target
);
370 sprintf(noncecount
, "%08d", auth
->nc
++);
371 response
= purple_cipher_http_digest_calculate_response(
372 "md5", method
, target
, NULL
, NULL
,
373 auth
->nonce
, noncecount
, NULL
, auth
->digest_session_key
);
374 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "response %s\n", response
);
376 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
);
381 static char *parse_attribute(const char *attrname
, const char *source
)
383 const char *tmp
, *tmp2
;
385 int len
= strlen(attrname
);
387 if (!strncmp(source
, attrname
, len
)) {
389 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
391 retval
= g_strndup(tmp
, tmp2
- tmp
);
393 retval
= g_strdup(tmp
);
399 static void fill_auth(struct sipe_account_data
*sip
, gchar
*hdr
, struct sip_auth
*auth
)
402 const char *authuser
;
405 //const char *krb5_realm;
408 // XXX FIXME: Get this info from the account dialogs and/or /etc/krb5.conf
409 // and do error checking
411 // KRB realm should always be uppercase
412 /*krb5_realm = g_strup(purple_account_get_string(sip->account, "krb5_realm", ""));
414 if (sip->realhostname) {
415 host = sip->realhostname;
416 } else if (purple_account_get_bool(sip->account, "useproxy", TRUE)) {
417 host = purple_account_get_string(sip->account, "proxy", "");
419 host = sip->sipdomain;
422 authuser
= sip
->authuser
;
424 if (!authuser
|| strlen(authuser
) < 1) {
425 authuser
= sip
->username
;
429 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
433 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
434 auth
->type
= AUTH_TYPE_NTLM
;
435 parts
= g_strsplit(hdr
+5, "\", ", 0);
438 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
439 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
441 auth
->nonce
= g_memdup(purple_ntlm_parse_challenge(tmp
, &auth
->flags
), 8);
444 if ((tmp
= parse_attribute("targetname=\"",
446 g_free(auth
->target
);
449 else if ((tmp
= parse_attribute("realm=\"",
454 else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
455 g_free(auth
->opaque
);
462 if (!strstr(hdr
, "gssapi-data")) {
470 if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
471 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "setting auth type to Kerberos (3)\r\n");
472 auth
->type
= AUTH_TYPE_KERBEROS
;
473 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth - header: %s\r\n", hdr
);
474 parts
= g_strsplit(hdr
+9, "\", ", 0);
477 purple_debug_info("sipe", "krb - parts[i] %s\n", parts
[i
]);
478 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
479 /*if (krb5_auth.gss_context == NULL) {
480 purple_krb5_init_auth(&krb5_auth, authuser, krb5_realm, sip->password, host, "sip");
482 auth->nonce = g_memdup(krb5_auth.base64_token, 8);*/
485 if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
486 g_free(auth
->target
);
488 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
491 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
492 g_free(auth
->opaque
);
502 auth
->type
= AUTH_TYPE_DIGEST
;
503 parts
= g_strsplit(hdr
, " ", 0);
505 if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
509 else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
517 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "nonce: %s realm: %s\n", auth
->nonce
? auth
->nonce
: "(null)", auth
->realm
? auth
->realm
: "(null)");
519 g_free(auth
->digest_session_key
);
520 auth
->digest_session_key
= purple_cipher_http_digest_calculate_session_key(
521 "md5", authuser
, auth
->realm
, sip
->password
, auth
->nonce
, NULL
);
527 static void sipe_canwrite_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
529 PurpleConnection
*gc
= data
;
530 struct sipe_account_data
*sip
= gc
->proto_data
;
534 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
536 if (max_write
== 0) {
537 if (sip
->tx_handler
!= 0){
538 purple_input_remove(sip
->tx_handler
);
544 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
546 if (written
< 0 && errno
== EAGAIN
)
548 else if (written
<= 0) {
549 /*TODO: do we really want to disconnect on a failure to write?*/
550 purple_connection_error(gc
, _("Could not write"));
554 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
557 static void sipe_canwrite_cb_ssl(gpointer data
, gint src
, PurpleInputCondition cond
)
559 PurpleConnection
*gc
= data
;
560 struct sipe_account_data
*sip
= gc
->proto_data
;
564 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
566 if (max_write
== 0) {
567 if (sip
->tx_handler
!= 0) {
568 purple_input_remove(sip
->tx_handler
);
574 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
576 if (written
< 0 && errno
== EAGAIN
)
578 else if (written
<= 0) {
579 /*TODO: do we really want to disconnect on a failure to write?*/
580 purple_connection_error(gc
, _("Could not write"));
584 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
587 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
589 static void send_later_cb(gpointer data
, gint source
, const gchar
*error
)
591 PurpleConnection
*gc
= data
;
592 struct sipe_account_data
*sip
;
593 struct sip_connection
*conn
;
595 if (!PURPLE_CONNECTION_IS_VALID(gc
))
603 purple_connection_error(gc
, _("Could not connect"));
607 sip
= gc
->proto_data
;
609 sip
->connecting
= FALSE
;
610 sip
->last_keepalive
= time(NULL
);
612 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
614 /* If there is more to write now, we need to register a handler */
615 if (sip
->txbuf
->bufused
> 0)
616 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
618 conn
= connection_create(sip
, source
);
619 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
622 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
624 struct sipe_account_data
*sip
;
625 struct sip_connection
*conn
;
627 if (!PURPLE_CONNECTION_IS_VALID(gc
))
629 if (gsc
) purple_ssl_close(gsc
);
633 sip
= gc
->proto_data
;
636 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
637 sip
->connecting
= FALSE
;
638 sip
->last_keepalive
= time(NULL
);
640 conn
= connection_create(sip
, gsc
->fd
);
642 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
647 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
649 PurpleConnection
*gc
= data
;
650 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
651 if (sip
== NULL
) return;
653 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
655 /* If there is more to write now */
656 if (sip
->txbuf
->bufused
> 0) {
657 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
662 static void sendlater(PurpleConnection
*gc
, const char *buf
)
664 struct sipe_account_data
*sip
= gc
->proto_data
;
666 if (!sip
->connecting
) {
667 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
668 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
669 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
671 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
672 purple_connection_error(gc
, _("Couldn't create socket"));
675 sip
->connecting
= TRUE
;
678 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
679 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
681 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
684 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
686 struct sipe_account_data
*sip
= gc
->proto_data
;
687 time_t currtime
= time(NULL
);
688 int writelen
= strlen(buf
);
690 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
691 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
692 if (sendto(sip
->fd
, buf
, writelen
, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
693 purple_debug_info("sipe", "could not send packet\n");
702 if (sip
->tx_handler
) {
707 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
709 ret
= write(sip
->fd
, buf
, writelen
);
713 if (ret
< 0 && errno
== EAGAIN
)
715 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
720 if (ret
< writelen
) {
721 if (!sip
->tx_handler
){
723 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
726 sip
->tx_handler
= purple_input_add(sip
->fd
,
727 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
732 /* XXX: is it OK to do this? You might get part of a request sent
733 with part of another. */
734 if (sip
->txbuf
->bufused
> 0)
735 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
737 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
743 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
745 sendout_pkt(gc
, buf
);
749 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
751 GSList
*tmp
= msg
->headers
;
754 GString
*outstr
= g_string_new("");
755 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
757 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
758 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
759 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
760 tmp
= g_slist_next(tmp
);
762 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
763 sendout_pkt(sip
->gc
, outstr
->str
);
764 g_string_free(outstr
, TRUE
);
767 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
770 if (sip
->registrar
.ntlm_key
) {
771 struct sipmsg_breakdown msgbd
;
772 gchar
*signature_input_str
;
774 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
775 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
776 sip
->registrar
.ntlm_num
++;
777 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
778 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
779 if (signature_input_str
!= NULL
) {
780 msg
->signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
781 msg
->rand
= g_strdup(msgbd
.rand
);
782 msg
->num
= g_strdup(msgbd
.num
);
783 g_free(signature_input_str
);
785 sipmsg_breakdown_free(&msgbd
);
788 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
789 buf
= auth_header(sip
, &sip
->registrar
, msg
);
790 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
791 sipmsg_add_header(msg
, "Authorization", buf
);
793 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
796 } 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")) {
797 sip
->registrar
.nc
= 3;
798 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
800 buf
= auth_header(sip
, &sip
->registrar
, msg
);
801 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
804 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
808 static char *get_contact(struct sipe_account_data
*sip
)
810 return g_strdup(sip
->contact
);
815 static char *get_contact_service(struct sipe_account_data *sip)
817 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()));
818 //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);
822 static void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
823 const char *text
, const char *body
)
827 GString
*outstr
= g_string_new("");
828 struct sipe_account_data
*sip
= gc
->proto_data
;
832 sipmsg_remove_header(msg
, "ms-user-data");
834 contact
= get_contact(sip
);
835 sipmsg_remove_header(msg
, "Contact");
836 sipmsg_add_header(msg
, "Contact", contact
);
839 /* When sending the acknowlegements and errors, the content length from the original
840 message is still here, but there is no body; we need to make sure we're sending the
841 correct content length */
842 sipmsg_remove_header(msg
, "Content-Length");
845 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
846 sipmsg_add_header(msg
, "Content-Length", len
);
848 sipmsg_remove_header(msg
, "Content-Type");
849 sipmsg_add_header(msg
, "Content-Length", "0");
852 //gchar * mic = purple_krb5_get_mic_for_sipmsg(&krb5_auth, msg);
853 //gchar * mic = "MICTODO";
854 msg
->response
= code
;
856 sipmsg_remove_header(msg
, "Authentication-Info");
857 sign_outgoing_message(msg
, sip
, msg
->method
);
859 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
862 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
863 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
865 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
866 tmp
= g_slist_next(tmp
);
868 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
869 sendout_pkt(gc
, outstr
->str
);
870 g_string_free(outstr
, TRUE
);
873 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
875 if (trans
->msg
) sipmsg_free(trans
->msg
);
876 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
880 static struct transaction
*
881 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
883 struct transaction
*trans
= g_new0(struct transaction
, 1);
884 trans
->time
= time(NULL
);
885 trans
->msg
= (struct sipmsg
*)msg
;
886 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
887 trans
->callback
= callback
;
888 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
892 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
894 struct transaction
*trans
;
895 GSList
*transactions
= sip
->transactions
;
896 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
898 while (transactions
) {
899 trans
= transactions
->data
;
900 if (!strcmp(trans
->cseq
, cseq
)) {
903 transactions
= transactions
->next
;
909 static struct transaction
*
910 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
911 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
912 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
914 struct sipe_account_data
*sip
= gc
->proto_data
;
915 const char *addh
= "";
918 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
919 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
920 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
921 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
922 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
923 gchar
*useragent
= (gchar
*)purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
);
924 gchar
*route
= strdup("");
925 gchar
*epid
= get_epid(sip
); // TODO generate one per account/login
926 struct transaction
*trans
;
928 if (dialog
&& dialog
->routes
)
930 GSList
*iter
= dialog
->routes
;
935 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
937 iter
= g_slist_next(iter
);
941 if (!ourtag
&& !dialog
) {
945 if (!strcmp(method
, "REGISTER")) {
946 if (sip
->regcallid
) {
948 callid
= g_strdup(sip
->regcallid
);
950 sip
->regcallid
= g_strdup(callid
);
954 if (addheaders
) addh
= addheaders
;
956 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
957 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
958 "From: <sip:%s>%s%s;epid=%s\r\n"
959 "To: <%s>%s%s%s%s\r\n"
960 "Max-Forwards: 70\r\n"
965 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
967 dialog
&& dialog
->request
? dialog
->request
: url
,
968 TRANSPORT_DESCRIPTOR
,
969 purple_network_get_my_ip(-1),
971 branch
? ";branch=" : "",
972 branch
? branch
: "",
974 ourtag
? ";tag=" : "",
975 ourtag
? ourtag
: "",
978 theirtag
? ";tag=" : "",
979 theirtag
? theirtag
: "",
980 theirepid
? ";epid=" : "",
981 theirepid
? theirepid
: "",
982 dialog
? ++dialog
->cseq
: ++sip
->cseq
,
988 body
? strlen(body
) : 0,
992 //printf ("parsing msg buf:\n%s\n\n", buf);
993 msg
= sipmsg_parse_msg(buf
);
1004 sign_outgoing_message (msg
, sip
, method
);
1006 buf
= sipmsg_to_string (msg
);
1008 /* add to ongoing transactions */
1009 trans
= transactions_add_buf(sip
, msg
, tc
);
1010 sendout_pkt(gc
, buf
);
1016 static void send_soap_request_with_cb(struct sipe_account_data
*sip
, gchar
*body
, TransCallback callback
, void * payload
)
1018 gchar
*from
= g_strdup_printf("sip:%s", sip
->username
);
1019 gchar
*contact
= get_contact(sip
);
1020 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1021 "Content-Type: application/SOAP+xml\r\n",contact
);
1023 struct transaction
* tr
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1024 tr
->payload
= payload
;
1031 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1033 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
1036 static char *get_contact_register(struct sipe_account_data
*sip
)
1038 char *epid
= get_epid(sip
);
1039 char *uuid
= generateUUIDfromEPID(epid
);
1040 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
);
1046 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1048 char *expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
1049 char *uri
= g_strdup_printf("sip:%s", sip
->sipdomain
);
1050 char *to
= g_strdup_printf("sip:%s", sip
->username
);
1051 char *contact
= get_contact_register(sip
);
1052 char *hdr
= g_strdup_printf("Contact: %s\r\n"
1053 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1054 "Event: registration\r\n"
1055 "Allow-Events: presence\r\n"
1056 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1057 "%s", contact
, expires
);
1061 sip
->registerstatus
= 1;
1063 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1064 process_register_response
);
1071 static void do_register_cb(struct sipe_account_data
*sip
, void *unused
)
1073 do_register_exp(sip
, -1);
1074 sip
->reregister_set
= FALSE
;
1077 static void do_register(struct sipe_account_data
*sip
)
1079 do_register_exp(sip
, -1);
1083 * Returns URI from provided To or From header.
1085 * Needs to g_free() after use.
1087 * @return URI with sip: prefix
1089 static gchar
*parse_from(const gchar
*hdr
)
1092 const gchar
*tmp
, *tmp2
= hdr
;
1094 if (!hdr
) return NULL
;
1095 purple_debug_info("sipe", "parsing address out of %s\n", hdr
);
1096 tmp
= strchr(hdr
, '<');
1098 /* i hate the different SIP UA behaviours... */
1099 if (tmp
) { /* sip address in <...> */
1101 tmp
= strchr(tmp2
, '>');
1103 from
= g_strndup(tmp2
, tmp
- tmp2
);
1105 purple_debug_info("sipe", "found < without > in From\n");
1109 tmp
= strchr(tmp2
, ';');
1111 from
= g_strndup(tmp2
, tmp
- tmp2
);
1113 from
= g_strdup(tmp2
);
1116 purple_debug_info("sipe", "got %s\n", from
);
1120 static xmlnode
* xmlnode_get_descendant(xmlnode
* parent
, ...)
1123 xmlnode
* node
= NULL
;
1126 va_start(args
, parent
);
1127 while ((name
= va_arg(args
, const char *)) != NULL
) {
1128 node
= xmlnode_get_child(parent
, name
);
1129 if (node
== NULL
) return NULL
;
1139 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1141 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1142 send_soap_request(sip
, body
);
1147 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1150 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1152 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1155 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1159 void sipe_auth_user_cb(void * data
)
1161 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1164 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1169 void sipe_deny_user_cb(void * data
)
1171 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1174 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1179 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1181 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1182 sipe_contact_allow_deny(sip
, name
, TRUE
);
1186 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1188 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1189 sipe_contact_allow_deny(sip
, name
, FALSE
);
1193 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1195 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1196 sipe_contact_set_acl(sip, name, "");
1200 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1204 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1205 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1207 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1209 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1210 if (!watchers
) return;
1212 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1213 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1214 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1215 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1217 // TODO pull out optional displayName to pass as alias
1219 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1220 job
->who
= remote_user
;
1222 purple_account_request_authorization(
1236 xmlnode_free(watchers
);
1241 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1243 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1244 if (!purple_group
) {
1245 purple_group
= purple_group_new(group
->name
);
1246 purple_blist_add_group(purple_group
, NULL
);
1250 group
->purple_group
= purple_group
;
1251 sip
->groups
= g_slist_append(sip
->groups
, group
);
1252 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1254 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1258 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1260 struct sipe_group
*group
;
1266 entry
= sip
->groups
;
1268 group
= entry
->data
;
1269 if (group
->id
== id
) {
1272 entry
= entry
->next
;
1277 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, gchar
* name
)
1279 struct sipe_group
*group
;
1285 entry
= sip
->groups
;
1287 group
= entry
->data
;
1288 if (!strcmp(group
->name
, name
)) {
1291 entry
= entry
->next
;
1297 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1300 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1301 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1302 send_soap_request(sip
, body
);
1304 g_free(group
->name
);
1305 group
->name
= g_strdup(name
);
1309 * Only appends if no such value already stored.
1312 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1313 GSList
* res
= list
;
1314 if (!g_slist_find_custom(list
, data
, func
)) {
1315 res
= g_slist_insert_sorted(list
, data
, func
);
1321 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1322 return group1
->id
- group2
->id
;
1326 * Returns string like "2 4 7 8" - group ids buddy belong to.
1329 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1332 //creating array from GList, converting int to gchar*
1333 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1334 GSList
*entry
= buddy
->groups
;
1336 struct sipe_group
* group
= entry
->data
;
1337 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1338 entry
= entry
->next
;
1342 res
= g_strjoinv(" ", ids_arr
);
1343 g_strfreev(ids_arr
);
1348 * Sends buddy update to server
1351 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1353 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1354 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1356 if (buddy
&& purple_buddy
) {
1357 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1359 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1360 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1362 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1363 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1365 send_soap_request(sip
, body
);
1371 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1373 if (msg
->response
== 200) {
1374 struct sipe_group
*group
;
1375 struct group_user_context
*ctx
= (struct group_user_context
*)tc
->payload
;
1379 struct sipe_buddy
*buddy
;
1381 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1387 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1394 group_id
= xmlnode_get_data(node
);
1401 group
= g_new0(struct sipe_group
, 1);
1402 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1404 group
->name
= ctx
->group_name
;
1406 sipe_group_add(sip
, group
);
1408 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1410 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1413 sipe_group_set_user(sip
, ctx
->user_name
);
1422 static void sipe_group_create (struct sipe_account_data
*sip
, gchar
*name
, gchar
* who
)
1424 struct group_user_context
* ctx
= g_new0(struct group_user_context
, 1);
1426 ctx
->group_name
= g_strdup(name
);
1427 ctx
->user_name
= g_strdup(who
);
1429 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1430 send_soap_request_with_cb(sip
, body
, process_add_group_response
, ctx
);
1435 * Data structure for scheduled actions
1437 typedef void (*Action
) (struct sipe_account_data
*, void *);
1439 struct scheduled_action
{
1442 * Format is <Event>[<Data>...]
1443 * Example: <presence><sip:user@domain.com> or <registration>
1446 guint timeout_handler
;
1447 gboolean repetitive
;
1449 struct sipe_account_data
*sip
;
1455 * Should return FALSE if repetitive action is not needed
1457 static gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1460 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1461 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1462 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1463 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1464 ret
= sched_action
->repetitive
;
1465 g_free(sched_action
->payload
);
1466 g_free(sched_action
->name
);
1467 g_free(sched_action
);
1472 * Kills action timer effectively cancelling
1475 * @param name of action
1477 static void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, const gchar
*name
)
1481 if (!sip
->timeouts
|| !name
) return;
1483 entry
= sip
->timeouts
;
1485 struct scheduled_action
*sched_action
= entry
->data
;
1486 if(!strcmp(sched_action
->name
, name
)) {
1487 GSList
*to_delete
= entry
;
1488 entry
= entry
->next
;
1489 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1490 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1491 purple_timeout_remove(sched_action
->timeout_handler
);
1492 g_free(sched_action
->payload
);
1493 g_free(sched_action
->name
);
1494 g_free(sched_action
);
1496 entry
= entry
->next
;
1502 * Do schedule action for execution in the future.
1503 * Non repetitive execution.
1505 * @param name of action (will be copied)
1506 * @param timeout in seconds
1507 * @action callback function
1508 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1510 static void sipe_schedule_action(const gchar
*name
, int timeout
, Action action
, struct sipe_account_data
*sip
, void *payload
)
1512 struct scheduled_action
*sched_action
;
1514 /* Make sure each action only exists once */
1515 sipe_cancel_scheduled_action(sip
, name
);
1517 purple_debug_info("sipe","scheduling action %s timeout:%d\n", name
, timeout
);
1518 sched_action
= g_new0(struct scheduled_action
, 1);
1519 sched_action
->repetitive
= FALSE
;
1520 sched_action
->name
= g_strdup(name
);
1521 sched_action
->action
= action
;
1522 sched_action
->sip
= sip
;
1523 sched_action
->payload
= payload
;
1524 sched_action
->timeout_handler
= purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1525 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1526 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1529 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1531 static gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1533 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1535 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1540 static void sipe_subscribe_resource_uri(const char *name
, gpointer value
, gchar
**resources_uri
)
1542 gchar
*tmp
= *resources_uri
;
1543 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1547 static void sipe_subscribe_resource_uri_with_context(const char *name
, gpointer value
, gchar
**resources_uri
)
1549 gchar
*tmp
= *resources_uri
;
1550 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
1552 if(!sbuddy
->resubscribed
){ //Only not resubscribed contacts; the first time everybody are included
1553 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp
, name
);
1560 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1561 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1562 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1563 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1564 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1567 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
, void *unused
)
1569 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
1570 gchar
*contact
= get_contact(sip
);
1573 gchar
*resources_uri
= g_strdup("");
1574 gchar
*require
= "";
1576 gchar
*autoextend
= "";
1577 gchar
*content_type
;
1579 if (sip
->msrtc_event_categories
) {
1581 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
1583 require
= ", categoryList";
1584 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1585 content_type
= "application/msrtc-adrl-categorylist+xml";
1586 content
= g_strdup_printf(
1587 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1588 "<action name=\"subscribe\" id=\"63792024\">\n"
1589 "<adhocList>\n%s</adhocList>\n"
1590 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1591 "<category name=\"note\"/>\n"
1592 "<category name=\"state\"/>\n"
1595 "</batchSub>", sip
->username
, resources_uri
);
1597 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1598 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1599 content_type
= "application/adrl+xml";
1600 content
= g_strdup_printf(
1601 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1602 "<create xmlns=\"\">\n%s</create>\n"
1603 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1605 g_free(resources_uri
);
1607 request
= g_strdup_printf(
1608 "Require: adhoclist%s\r\n"
1609 "Supported: eventlist\r\n"
1610 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1611 "Supported: ms-piggyback-first-notify\r\n"
1612 "%sSupported: ms-benotify\r\n"
1613 "Proxy-Require: ms-benotify\r\n"
1614 "Event: presence\r\n"
1615 "Content-Type: %s\r\n"
1616 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
1619 /* subscribe to buddy presence */
1620 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1621 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1629 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1630 * The user sends a single SUBSCRIBE request to the subscribed contact.
1631 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1635 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, void *buddy_name
)
1637 gchar
*to
= strstr((char *)buddy_name
, "sip:") ? g_strdup((char *)buddy_name
) : g_strdup_printf("sip:%s", (char *)buddy_name
);
1638 gchar
*tmp
= get_contact(sip
);
1641 gchar
*autoextend
= "";
1643 if (!sip
->msrtc_event_categories
)
1644 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1646 request
= g_strdup_printf(
1647 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1648 "Supported: ms-piggyback-first-notify\r\n"
1649 "%sSupported: ms-benotify\r\n"
1650 "Proxy-Require: ms-benotify\r\n"
1651 "Event: presence\r\n"
1652 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1653 "Contact: %s\r\n", autoextend
,tmp
);
1655 content
= g_strdup_printf(
1656 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1657 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1658 "<resource uri=\"%s\"/>\n"
1660 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1661 "<category name=\"note\"/>\n"
1662 "<category name=\"state\"/>\n"
1665 "</batchSub>", sip
->username
, to
1670 /* subscribe to buddy presence */
1671 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1678 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
1680 if (!purple_status_is_active(status
))
1684 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
1687 g_free(sip
->status
);
1688 sip
->status
= g_strdup(purple_status_get_id(status
));
1689 send_presence_status(sip
);
1695 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
, const char *alias
)
1697 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1698 sipe_group_set_user(sip
, name
);
1702 sipe_group_buddy(PurpleConnection
*gc
,
1704 const char *old_group_name
,
1705 const char *new_group_name
)
1707 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1708 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1709 struct sipe_group
* old_group
= NULL
;
1710 struct sipe_group
* new_group
;
1712 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1713 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
1715 if(!buddy
) { // buddy not in roaming list
1719 if (old_group_name
) {
1720 old_group
= sipe_group_find_by_name(sip
, g_strdup(old_group_name
));
1722 new_group
= sipe_group_find_by_name(sip
, g_strdup(new_group_name
));
1725 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
1726 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
1730 sipe_group_create(sip
, g_strdup(new_group_name
), g_strdup(who
));
1732 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
1733 sipe_group_set_user(sip
, who
);
1737 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1739 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1740 struct sipe_buddy
*b
;
1742 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1744 // Prepend sip: if needed
1745 if (strncmp("sip:", buddy
->name
, 4)) {
1746 gchar
*buf
= g_strdup_printf("sip:%s", buddy
->name
);
1747 purple_blist_rename_buddy(buddy
, buf
);
1751 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
1752 b
= g_new0(struct sipe_buddy
, 1);
1753 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy
->name
);
1754 b
->name
= g_strdup(buddy
->name
);
1755 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
1756 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
1757 sipe_subscribe_presence_single(sip
, b
->name
); //@TODO should go to callback
1759 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy
->name
);
1763 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
1765 g_free(buddy
->name
);
1766 g_free(buddy
->annotation
);
1767 g_free(buddy
->device_name
);
1768 g_slist_free(buddy
->groups
);
1773 * Unassociates buddy from group first.
1774 * Then see if no groups left, removes buddy completely.
1775 * Otherwise updates buddy groups on server.
1777 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1779 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1780 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
1781 struct sipe_group
*g
= NULL
;
1783 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1788 g
= sipe_group_find_by_name(sip
, group
->name
);
1792 b
->groups
= g_slist_remove(b
->groups
, g
);
1793 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
1796 if (g_slist_length(b
->groups
) < 1) {
1797 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
1798 sipe_cancel_scheduled_action(sip
, action_name
);
1799 g_free(action_name
);
1801 g_hash_table_remove(sip
->buddies
, buddy
->name
);
1804 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
1805 send_soap_request(sip
, body
);
1811 //updates groups on server
1812 sipe_group_set_user(sip
, b
->name
);
1818 sipe_rename_group(PurpleConnection
*gc
,
1819 const char *old_name
,
1821 GList
*moved_buddies
)
1823 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1824 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, g_strdup(old_name
));
1826 sipe_group_rename(sip
, s_group
, group
->name
);
1828 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
1833 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
1835 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1836 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
1839 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
1840 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
1841 send_soap_request(sip
, body
);
1844 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
1845 g_free(s_group
->name
);
1848 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
1852 static GList
*sipe_status_types(PurpleAccount
*acc
)
1854 PurpleStatusType
*type
;
1855 GList
*types
= NULL
;
1858 type
= purple_status_type_new_with_attrs(
1859 PURPLE_STATUS_AVAILABLE
, NULL
, _("Online"), TRUE
, TRUE
, FALSE
,
1860 // Translators: noun
1861 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1863 types
= g_list_append(types
, type
);
1866 type
= purple_status_type_new_with_attrs(
1867 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_BUSY
, _("Busy"), TRUE
, TRUE
, FALSE
,
1868 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1870 types
= g_list_append(types
, type
);
1872 // Do Not Disturb (not user settable)
1873 type
= purple_status_type_new_with_attrs(
1874 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_DND
, _("Do Not Disturb"), TRUE
, FALSE
, FALSE
,
1875 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1877 types
= g_list_append(types
, type
);
1880 type
= purple_status_type_new_with_attrs(
1881 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_BRB
, _("Be Right Back"), TRUE
, TRUE
, FALSE
,
1882 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1884 types
= g_list_append(types
, type
);
1887 type
= purple_status_type_new_with_attrs(
1888 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
1889 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1891 types
= g_list_append(types
, type
);
1894 type
= purple_status_type_new_with_attrs(
1895 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_ONPHONE
, _("On The Phone"), TRUE
, TRUE
, FALSE
,
1896 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1898 types
= g_list_append(types
, type
);
1901 type
= purple_status_type_new_with_attrs(
1902 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_LUNCH
, _("Out To Lunch"), TRUE
, TRUE
, FALSE
,
1903 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1905 types
= g_list_append(types
, type
);
1908 type
= purple_status_type_new_full(
1909 PURPLE_STATUS_INVISIBLE
, NULL
, _("Appear Offline"), TRUE
, TRUE
, FALSE
);
1910 types
= g_list_append(types
, type
);
1913 type
= purple_status_type_new_full(
1914 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
1915 types
= g_list_append(types
, type
);
1921 * A callback for g_hash_table_foreach
1923 static void sipe_buddy_subscribe_cb(char *name
, struct sipe_buddy
*buddy
, struct sipe_account_data
*sip
)
1925 sipe_subscribe_presence_single(sip
, buddy
->name
);
1929 * Removes entries from purple buddy list
1930 * that does not correspond ones in the roaming contact list.
1932 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
1933 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
1934 GSList
*entry
= buddies
;
1935 struct sipe_buddy
*buddy
;
1939 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
1940 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
1943 g
= purple_buddy_get_group(b
);
1944 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
1946 gboolean in_sipe_groups
= FALSE
;
1947 GSList
*entry2
= buddy
->groups
;
1949 struct sipe_group
*group
= entry2
->data
;
1950 if (!strcmp(group
->name
, g
->name
)) {
1951 in_sipe_groups
= TRUE
;
1954 entry2
= entry2
->next
;
1956 if(!in_sipe_groups
) {
1957 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
1958 purple_blist_remove_buddy(b
);
1961 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
1962 purple_blist_remove_buddy(b
);
1964 entry
= entry
->next
;
1968 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1970 int len
= msg
->bodylen
;
1972 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
1975 const gchar
*contacts_delta
;
1976 xmlnode
*group_node
;
1977 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
1981 /* Convert the contact from XML to Purple Buddies */
1982 isc
= xmlnode_from_str(msg
->body
, len
);
1987 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
1988 if (contacts_delta
) {
1989 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
1993 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
1994 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1995 const char *name
= xmlnode_get_attrib(group_node
, "name");
1997 if (!strncmp(name
, "~", 1)) {
1999 name
= "Other Contacts";
2001 group
->name
= g_strdup(name
);
2002 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
2004 sipe_group_add(sip
, group
);
2007 // Make sure we have at least one group
2008 if (g_slist_length(sip
->groups
) == 0) {
2009 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2010 PurpleGroup
*purple_group
;
2012 group
->name
= g_strdup("Other Contacts");
2014 purple_group
= purple_group_new(group
->name
);
2015 purple_blist_add_group(purple_group
, NULL
);
2016 sip
->groups
= g_slist_append(sip
->groups
, group
);
2019 /* Parse contacts */
2020 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
2021 gchar
* uri
= g_strdup(xmlnode_get_attrib(item
, "uri"));
2022 gchar
* name
= g_strdup(xmlnode_get_attrib(item
, "name"));
2023 gchar
* groups
= g_strdup(xmlnode_get_attrib(item
, "groups"));
2024 gchar
* buddy_name
= g_strdup_printf("sip:%s", uri
);
2025 gchar
**item_groups
;
2026 struct sipe_group
*group
= NULL
;
2027 struct sipe_buddy
*buddy
= NULL
;
2030 // assign to group Other Contacts if nothing else received
2031 if(!groups
|| !strcmp("", groups
) ) {
2032 group
= sipe_group_find_by_name(sip
, "Other Contacts");
2033 groups
= group
? g_strdup_printf("%d", group
->id
) : "1";
2036 item_groups
= g_strsplit(groups
, " ", 0);
2038 while (item_groups
[i
]) {
2039 group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
2041 // If couldn't find the right group for this contact, just put them in the first group we have
2042 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2043 group
= sip
->groups
->data
;
2046 if (group
!= NULL
) {
2047 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2049 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2050 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2053 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2054 if (name
!= NULL
&& strlen(name
) != 0) {
2055 purple_blist_alias_buddy(b
, name
);
2060 buddy
= g_new0(struct sipe_buddy
, 1);
2061 buddy
->name
= g_strdup(b
->name
);
2062 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2065 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2067 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2069 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2074 } // while, contact groups
2075 g_strfreev(item_groups
);
2085 sipe_cleanup_local_blist(sip
);
2087 //subscribe to buddies
2088 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2089 if(sip
->batched_support
){
2090 sipe_subscribe_presence_batched(sip
, NULL
);
2093 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
2095 sip
->subscribed_buddies
= TRUE
;
2102 * Subscribe roaming contacts
2104 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2106 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2107 gchar
*tmp
= get_contact(sip
);
2108 gchar
*hdr
= g_strdup_printf(
2109 "Event: vnd-microsoft-roaming-contacts\r\n"
2110 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2111 "Supported: com.microsoft.autoextend\r\n"
2112 "Supported: ms-benotify\r\n"
2113 "Proxy-Require: ms-benotify\r\n"
2114 "Supported: ms-piggyback-first-notify\r\n"
2115 "Contact: %s\r\n", tmp
);
2118 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2123 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
, void *unused
)
2125 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2126 gchar
*tmp
= get_contact(sip
);
2127 gchar
*hdr
= g_strdup_printf(
2128 "Event: presence.wpending\r\n"
2129 "Accept: text/xml+msrtc.wpending\r\n"
2130 "Supported: com.microsoft.autoextend\r\n"
2131 "Supported: ms-benotify\r\n"
2132 "Proxy-Require: ms-benotify\r\n"
2133 "Supported: ms-piggyback-first-notify\r\n"
2134 "Contact: %s\r\n", tmp
);
2137 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2143 * Fires on deregistration event initiated by server.
2144 * [MS-SIPREGE] SIP extension.
2149 // Content-Type: text/registration-event
2150 // subscription-state: terminated;expires=0
2151 // ms-diagnostics-public: 4141;reason="User disabled"
2153 // deregistered;event=rejected
2155 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2157 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2158 gchar
*event
= NULL
;
2159 gchar
*reason
= NULL
;
2160 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
2162 warning
= warning
? warning
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2163 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2165 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2166 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2167 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2168 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2170 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2174 if (warning
!= NULL
) {
2175 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
2176 } else { // for LCS2005
2178 if (event
&& !g_ascii_strcasecmp(event
, "unregistered")) {
2179 error_id
= 4140; // [MS-SIPREGE]
2180 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2181 reason
= g_strdup(_("You have been signed off because you've signed in at another location"));
2182 } else if (event
&& !g_ascii_strcasecmp(event
, "rejected")) {
2184 reason
= g_strdup(_("User disabled")); // [MS-OCER]
2185 } else if (event
&& !g_ascii_strcasecmp(event
, "deactivated")) {
2187 reason
= g_strdup(_("User moved")); // [MS-OCER]
2191 warning
= g_strdup_printf(_("Unregistered by Server: %s."), reason
? reason
: _("no reason given"));
2194 sip
->gc
->wants_to_die
= TRUE
;
2195 purple_connection_error(sip
->gc
, warning
);
2200 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2202 const gchar
*contacts_delta
;
2205 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2211 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2214 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2223 * When we receive some self (BE) NOTIFY with a new subscriber
2224 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2228 static void sipe_process_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2234 char *display_name
= NULL
;
2235 PurpleBuddy
*pbuddy
;
2240 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2242 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2245 contact
= get_contact(sip
);
2246 to
= g_strdup_printf("sip:%s", sip
->username
);
2248 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
2250 const char *acknowledged
;
2254 user
= xmlnode_get_attrib(node
, "user");
2255 if (!user
) continue;
2256 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
2257 uri_user
= g_strdup_printf("sip:%s", user
);
2258 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri_user
);
2260 alias
= purple_buddy_get_local_alias(pbuddy
);
2261 uri_alias
= g_strdup_printf("sip:%s", alias
);
2262 display_name
= g_strdup(xmlnode_get_attrib(node
, "displayName"));
2263 if (display_name
&& !g_ascii_strcasecmp(uri_user
, uri_alias
)) { // 'bad' alias
2264 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user
, display_name
);
2265 purple_blist_alias_buddy(pbuddy
, display_name
);
2271 acknowledged
= xmlnode_get_attrib(node
, "acknowledged");
2272 if(!g_ascii_strcasecmp(acknowledged
,"false")){
2273 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user
);
2274 hdr
= g_strdup_printf(
2276 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
2278 body
= g_strdup_printf(
2279 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2280 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2281 "</setSubscribers>", user
);
2283 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
2294 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2296 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2297 gchar
*tmp
= get_contact(sip
);
2298 gchar
*hdr
= g_strdup_printf(
2299 "Event: vnd-microsoft-roaming-ACL\r\n"
2300 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2301 "Supported: com.microsoft.autoextend\r\n"
2302 "Supported: ms-benotify\r\n"
2303 "Proxy-Require: ms-benotify\r\n"
2304 "Supported: ms-piggyback-first-notify\r\n"
2305 "Contact: %s\r\n", tmp
);
2308 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2314 * To request for presence information about the user, access level settings that have already been configured by the user
2315 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2316 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2319 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2321 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2322 gchar
*tmp
= get_contact(sip
);
2323 gchar
*hdr
= g_strdup_printf(
2324 "Event: vnd-microsoft-roaming-self\r\n"
2325 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2326 "Supported: ms-benotify\r\n"
2327 "Proxy-Require: ms-benotify\r\n"
2328 "Supported: ms-piggyback-first-notify\r\n"
2330 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
2332 gchar
*body
=g_strdup(
2333 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2334 "<roaming type=\"categories\"/>"
2335 "<roaming type=\"containers\"/>"
2336 "<roaming type=\"subscribers\"/></roamingList>");
2339 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2348 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2350 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2351 gchar
*tmp
= get_contact(sip
);
2352 gchar
*hdr
= g_strdup_printf(
2353 "Event: vnd-microsoft-provisioning\r\n"
2354 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2355 "Supported: com.microsoft.autoextend\r\n"
2356 "Supported: ms-benotify\r\n"
2357 "Proxy-Require: ms-benotify\r\n"
2358 "Supported: ms-piggyback-first-notify\r\n"
2360 "Contact: %s\r\n", tmp
);
2363 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
2368 /** Subscription for provisioning information to help with initial
2369 * configuration. This subscription is a one-time query (denoted by the Expires header,
2370 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2371 * configuration, meeting policies, and policy settings that Communicator must enforce.
2372 * TODO: for what we need this information.
2375 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2377 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2378 gchar
*tmp
= get_contact(sip
);
2379 gchar
*hdr
= g_strdup_printf(
2380 "Event: vnd-microsoft-provisioning-v2\r\n"
2381 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2382 "Supported: com.microsoft.autoextend\r\n"
2383 "Supported: ms-benotify\r\n"
2384 "Proxy-Require: ms-benotify\r\n"
2385 "Supported: ms-piggyback-first-notify\r\n"
2388 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
2389 gchar
*body
= g_strdup(
2390 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2391 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2392 "<provisioningGroup name=\"ucPolicy\"/>"
2393 "</provisioningGroupList>");
2396 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2402 /* IM Session (INVITE and MESSAGE methods) */
2404 static struct sip_im_session
* find_im_session (struct sipe_account_data
*sip
, const char *who
)
2406 struct sip_im_session
*session
;
2408 if (sip
== NULL
|| who
== NULL
) {
2412 entry
= sip
->im_sessions
;
2414 session
= entry
->data
;
2415 if ((who
!= NULL
&& !strcmp(who
, session
->with
))) {
2418 entry
= entry
->next
;
2423 static struct sip_im_session
* find_or_create_im_session (struct sipe_account_data
*sip
, const char *who
)
2425 struct sip_im_session
*session
= find_im_session(sip
, who
);
2427 session
= g_new0(struct sip_im_session
, 1);
2428 session
->with
= g_strdup(who
);
2429 session
->unconfirmed_messages
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
2430 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2435 static void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2437 struct sip_dialog
*dialog
= session
->dialog
;
2440 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2443 entry
= dialog
->routes
;
2445 g_free(entry
->data
);
2446 entry
= g_slist_remove(entry
, entry
->data
);
2448 entry
= dialog
->supported
;
2450 g_free(entry
->data
);
2451 entry
= g_slist_remove(entry
, entry
->data
);
2453 g_free(dialog
->callid
);
2454 g_free(dialog
->ourtag
);
2455 g_free(dialog
->theirtag
);
2456 g_free(dialog
->theirepid
);
2457 g_free(dialog
->request
);
2459 g_free(session
->dialog
);
2461 entry
= session
->outgoing_message_queue
;
2463 g_free(entry
->data
);
2464 entry
= g_slist_remove(entry
, entry
->data
);
2467 g_hash_table_destroy(session
->unconfirmed_messages
);
2469 g_free(session
->with
);
2474 process_options_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2476 gboolean ret
= TRUE
;
2478 if (msg
->response
!= 200) {
2479 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
2483 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
2489 * Asks UA/proxy about its capabilities.
2491 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
2493 gchar
*to
= strstr(who
, "sip:") ? g_strdup(who
) : g_strdup_printf("sip:%s", who
);
2494 gchar
*contact
= get_contact(sip
);
2496 request
= g_strdup_printf(
2497 "Accept: application/sdp\r\n"
2498 "Contact: %s\r\n", contact
);
2502 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
2508 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2510 char *msg
, *msg_tmp
;
2511 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2512 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2514 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2515 "possibly because one or more persons are offline:\n%s") ,
2517 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2522 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2525 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2527 gboolean ret
= TRUE
;
2528 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2529 struct sip_im_session
* session
= find_im_session(sip
, with
);
2530 struct sip_dialog
*dialog
;
2536 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2541 dialog
= session
->dialog
;
2543 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2548 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2549 key
= g_strdup_printf("<%s><%d><MESSAGE>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
));
2551 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2553 if (msg
->response
!= 200) {
2554 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2556 sipe_present_message_undelivered_err(with
, sip
, message
);
2557 im_session_destroy(sip
, session
);
2560 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2561 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2562 key
, g_hash_table_size(session
->unconfirmed_messages
));
2568 sipe_im_process_queue(sip
, session
);
2572 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_im_session
* session
, const char *msg
)
2582 if (strncmp("sip:", session
->with
, 4)) {
2583 fullto
= g_strdup_printf("sip:%s", session
->with
);
2585 fullto
= g_strdup(session
->with
);
2588 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2589 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2591 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2594 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2597 msgr
= g_strdup("");
2600 tmp
= get_contact(sip
);
2601 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2602 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2603 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC0ASQBNAC0ARgBvAHIAbQBhAHQAOgAgAEYATgA9AE0AUwAlADIAMABTAGgAZQBsAGwAJQAyADAARABsAGcAJQAyADAAMgA7ACAARQBGAD0AOwAgAEMATwA9ADAAOwAgAEMAUwA9ADAAOwAgAFAARgA9ADAACgANAAoADQA\r\nSupported: timer\r\n");
2604 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n",
2609 send_sip_request(sip
->gc
, "MESSAGE", fullto
, fullto
, hdr
, msgtext
, session
->dialog
, process_message_response
);
2617 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2619 GSList
*entry
= session
->outgoing_message_queue
;
2621 if (session
->outgoing_invite
) return; //do not send messages until INVITE responded.
2624 char *key
= g_strdup_printf("<%s><%d><MESSAGE>", session
->dialog
->callid
, (session
->dialog
->cseq
) + 1);
2625 char *queued_msg
= entry
->data
;
2626 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
2627 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2628 key
, g_hash_table_size(session
->unconfirmed_messages
));
2630 sipe_send_message(sip
, session
, queued_msg
);
2631 entry
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2637 sipe_get_route_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2639 GSList
*hdr
= msg
->headers
;
2640 struct siphdrelement
*elem
;
2646 if(!g_ascii_strcasecmp(elem
->name
, "Record-Route"))
2648 gchar
*route
= sipmsg_find_part_of_header(elem
->value
, "<", ">", NULL
);
2649 dialog
->routes
= g_slist_append(dialog
->routes
, route
);
2651 hdr
= g_slist_next(hdr
);
2656 dialog
->routes
= g_slist_reverse(dialog
->routes
);
2661 dialog
->request
= dialog
->routes
->data
;
2662 dialog
->routes
= g_slist_remove(dialog
->routes
, dialog
->routes
->data
);
2665 contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Contact"), "<", ">", NULL
);
2666 dialog
->routes
= g_slist_append(dialog
->routes
, contact
);
2670 sipe_get_supported_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2672 GSList
*hdr
= msg
->headers
;
2673 struct siphdrelement
*elem
;
2677 if(!g_ascii_strcasecmp(elem
->name
, "Supported")
2678 && !g_slist_find_custom(dialog
->supported
, elem
->value
, (GCompareFunc
)g_ascii_strcasecmp
))
2680 dialog
->supported
= g_slist_append(dialog
->supported
, g_strdup(elem
->value
));
2683 hdr
= g_slist_next(hdr
);
2688 sipe_parse_dialog(struct sipmsg
* msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2690 gchar
*us
= outgoing
? "From" : "To";
2691 gchar
*them
= outgoing
? "To" : "From";
2693 g_free(dialog
->callid
);
2694 g_free(dialog
->ourtag
);
2695 g_free(dialog
->theirtag
);
2697 dialog
->callid
= g_strdup(sipmsg_find_header(msg
, "Call-ID"));
2698 dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, us
));
2699 dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, them
));
2700 if (!dialog
->theirepid
) {
2701 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", ";", NULL
);
2702 if (!dialog
->theirepid
) {
2703 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", NULL
, NULL
);
2707 // Catch a tag on the end of the To Header and get rid of it.
2708 if (strstr(dialog
->theirepid
, "tag=")) {
2709 dialog
->theirepid
= strtok(dialog
->theirepid
, ";");
2712 sipe_get_route_header(msg
, dialog
, outgoing
);
2713 sipe_get_supported_header(msg
, dialog
, outgoing
);
2718 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2720 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
2721 struct sip_im_session
* session
= find_im_session(sip
, with
);
2722 struct sip_dialog
*dialog
;
2728 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2733 dialog
= session
->dialog
;
2735 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2740 sipe_parse_dialog(msg
, dialog
, TRUE
);
2742 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2743 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
2745 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2747 if (msg
->response
!= 200) {
2748 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2750 sipe_present_message_undelivered_err(with
, sip
, message
);
2751 im_session_destroy(sip
, session
);
2757 send_sip_request(sip
->gc
, "ACK", session
->with
, session
->with
, NULL
, NULL
, dialog
, NULL
);
2758 session
->outgoing_invite
= NULL
;
2759 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
2760 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
2761 if (session
->outgoing_message_queue
) {
2762 char *queued_msg
= session
->outgoing_message_queue
->data
;
2763 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2768 sipe_im_process_queue(sip
, session
);
2770 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2771 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
2772 key
, g_hash_table_size(session
->unconfirmed_messages
));
2780 static void sipe_invite(struct sipe_account_data
*sip
, struct sip_im_session
*session
, const gchar
*msg_body
)
2790 char *ms_text_format
;
2795 if (session
->dialog
) {
2796 purple_debug_info("sipe", "session with %s already has a dialog open\n", session
->with
);
2800 session
->dialog
= g_new0(struct sip_dialog
, 1);
2801 session
->dialog
->callid
= gencallid();
2803 if (!(session
->dialog
->ourtag
)) {
2804 session
->dialog
->ourtag
= gentag();
2808 if (strstr(session
->with
, "sip:")) {
2809 to
= g_strdup(session
->with
);
2811 to
= g_strdup_printf("sip:%s", session
->with
);
2814 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
2815 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
2817 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2821 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2825 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
2826 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
2831 key
= g_strdup_printf("<%s><%d><INVITE>", session
->dialog
->callid
, (session
->dialog
->cseq
) + 1);
2832 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
2833 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
2834 key
, g_hash_table_size(session
->unconfirmed_messages
));
2837 contact
= get_contact(sip
);
2838 /* from = g_strdup_printf("sip:%s", sip->username);*/
2839 hdr
= g_strdup_printf(
2840 /*"Supported: ms-delayed-accept\r\n"*/
2841 /*"Roster-Manager: <%s>\r\n"*/
2842 /*"EndPoints: <%s>, <%s>\r\n"*/
2843 /*"Supported: com.microsoft.rtc-multiparty\r\n"*/
2845 "Content-Type: application/sdp\r\n",
2846 contact
, ms_text_format
);
2847 g_free(ms_text_format
);
2849 body
= g_strdup_printf(
2851 "o=- 0 0 IN IP4 %s\r\n"
2855 "m=message %d sip null\r\n"
2856 "a=accept-types:text/plain text/html image/gif "
2857 "multipart/alternative application/im-iscomposing+xml\r\n",
2858 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
2860 session
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
2861 to
, to
, hdr
, body
, session
->dialog
, process_invite_response
);
2871 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2874 send_sip_request(sip
->gc
, "BYE", session
->with
, session
->with
, NULL
, NULL
, session
->dialog
, NULL
);
2875 im_session_destroy(sip
, session
);
2880 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
2882 struct sipe_account_data
*sip
= gc
->proto_data
;
2884 purple_debug_info("sipe", "conversation with %s closed\n", who
);
2885 im_session_close(sip
, find_im_session(sip
, who
));
2889 im_session_close_all (struct sipe_account_data
*sip
)
2891 GSList
*entry
= sip
->im_sessions
;
2893 im_session_close (sip
, entry
->data
);
2894 entry
= sip
->im_sessions
;
2898 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
2900 struct sipe_account_data
*sip
;
2902 struct sip_im_session
*session
;
2904 sip
= gc
->proto_data
;
2907 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
2909 session
= find_or_create_im_session(sip
, who
);
2911 // Queue the message
2912 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
2914 if (session
->dialog
&& session
->dialog
->callid
) {
2915 sipe_im_process_queue(sip
, session
);
2916 } else if (!session
->outgoing_invite
) {
2917 // Need to send the INVITE to get the outgoing dialog setup
2918 sipe_invite(sip
, session
, what
);
2925 /* End IM Session (INVITE and MESSAGE methods) */
2928 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
2930 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2931 struct sip_im_session
*session
;
2933 if (state
== PURPLE_NOT_TYPING
)
2936 session
= find_im_session(sip
, who
);
2938 if (session
&& session
->dialog
) {
2939 send_sip_request(gc
, "INFO", who
, who
,
2940 "Content-Type: application/xml\r\n",
2941 SIPE_SEND_TYPING
, session
->dialog
, NULL
);
2943 return SIPE_TYPING_SEND_TIMEOUT
;
2946 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
2948 GSList
*tmp
= sip
->transactions
;
2949 time_t currtime
= time(NULL
);
2951 struct transaction
*trans
= tmp
->data
;
2953 purple_debug_info("sipe", "have open transaction age: %ld\n", currtime
-trans
->time
);
2954 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
2957 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
2959 sendout_sipmsg(sip
, trans
->msg
);
2966 static void do_reauthenticate_cb(struct sipe_account_data
*sip
, void *unused
)
2968 /* register again when security token expires */
2969 /* we have to start a new authentication as the security token
2970 * is almost expired by sending a not signed REGISTER message */
2971 purple_debug_info("sipe", "do a full reauthentication\n");
2972 sipe_auth_free(&sip
->registrar
);
2973 sip
->registerstatus
= 0;
2975 sip
->reauthenticate_set
= FALSE
;
2978 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2982 gboolean found
= FALSE
;
2984 from
= parse_from(sipmsg_find_header(msg
, "From"));
2988 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
2990 contenttype
= sipmsg_find_header(msg
, "Content-Type");
2991 if (!strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
2993 gchar
*html
= get_html_message(contenttype
, msg
->body
);
2994 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
2996 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
2999 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
3000 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3005 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3009 state
= xmlnode_get_child(isc
, "state");
3012 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3017 statedata
= xmlnode_get_data(state
);
3019 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
3020 else serv_got_typing_stopped(sip
->gc
, from
);
3025 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3029 purple_debug_info("sipe", "got unknown mime-type");
3030 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
3035 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3037 gchar
*ms_text_format
;
3040 gchar
*newTag
= gentag();
3041 struct sip_im_session
*session
;
3043 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? msg
->body
: "");
3045 // Only accept text invitations
3046 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
3047 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3051 // TODO There *must* be a better way to clean up the To header to add a tag...
3052 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3055 oldHeader
= sipmsg_find_header(msg
, "To");
3056 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
3057 sipmsg_remove_header(msg
, "To");
3058 sipmsg_add_header(msg
, "To", newHeader
);
3061 from
= parse_from(sipmsg_find_header(msg
, "From"));
3062 session
= find_or_create_im_session (sip
, from
);
3064 if (session
->dialog
) {
3065 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3067 session
->dialog
= g_new0(struct sip_dialog
, 1);
3069 sipe_parse_dialog(msg
, session
->dialog
, FALSE
);
3071 // This stuff is the same stuff that happens in the sipe_parse_dialog...
3072 //session->dialog->callid = g_strdup(sipmsg_find_header(msg, "Call-ID"));
3073 //session->dialog->ourtag = find_tag(sipmsg_find_header(msg, "To"));
3074 //session->dialog->theirtag = find_tag(sipmsg_find_header(msg, "From"));
3075 //session->dialog->theirepid = sipmsg_find_part_of_header(sipmsg_find_header(msg, "From"), "epid=", NULL, NULL);
3077 if (!session
->dialog
->ourtag
) {
3078 session
->dialog
->ourtag
= newTag
;
3083 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3087 //ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk=
3088 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
3089 if (ms_text_format
) {
3090 if (!strncmp(ms_text_format
, "text/plain", 10) || !strncmp(ms_text_format
, "text/html", 9)) {
3092 gchar
*html
= get_html_message(ms_text_format
, NULL
);
3094 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3096 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message received
3102 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3103 sipmsg_remove_header(msg
, "Ms-Text-Format");
3104 sipmsg_remove_header(msg
, "EndPoints");
3105 sipmsg_remove_header(msg
, "User-Agent");
3106 sipmsg_remove_header(msg
, "Roster-Manager");
3107 sipmsg_remove_header(msg
, "P-Asserted-Identity");
3109 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3110 //sipmsg_add_header(msg, "Supported", "ms-renders-gif");
3112 body
= g_strdup_printf(
3114 "o=- 0 0 IN IP4 %s\r\n"
3118 "m=message %d sip sip:%s\r\n"
3119 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3120 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3121 sip
->realport
, sip
->username
);
3122 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3126 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3130 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3131 sipmsg_remove_header(msg
, "EndPoints");
3132 sipmsg_remove_header(msg
, "User-Agent");
3134 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3135 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3137 body
= g_strdup_printf(
3139 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3141 "c=IN IP4 0.0.0.0\r\n"
3143 "m=message %d sip sip:%s\r\n"
3144 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3145 sip
->realport
, sip
->username
);
3146 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3150 static void sipe_connection_cleanup(struct sipe_account_data
*);
3151 static void create_connection(struct sipe_account_data
*, gchar
*, int);
3153 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3156 const gchar
*expires_header
;
3158 GSList
*hdr
= msg
->headers
;
3160 struct siphdrelement
*elem
;
3162 expires_header
= sipmsg_find_header(msg
, "Expires");
3163 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
3164 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
3166 switch (msg
->response
) {
3169 sip
->registerstatus
= 0;
3171 gchar
*contact_hdr
= NULL
;
3177 if (!sip
->reregister_set
) {
3178 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
3179 sipe_schedule_action(action_name
, expires
, do_register_cb
, sip
, NULL
);
3180 g_free(action_name
);
3181 sip
->reregister_set
= TRUE
;
3184 sip
->registerstatus
= 3;
3186 if (!sip
->reauthenticate_set
) {
3187 /* we have to reauthenticate as our security token expires
3188 after eight hours (be five minutes early) */
3189 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
3190 guint reauth_timeout
= (8 * 3600) - 360;
3191 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, sip
, NULL
);
3192 g_free(action_name
);
3193 sip
->reauthenticate_set
= TRUE
;
3196 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
3198 epid
= get_epid(sip
);
3199 uuid
= generateUUIDfromEPID(epid
);
3202 // There can be multiple Contact headers (one per location where the user is logged in) so
3203 // make sure to only get the one for this uuid
3204 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
3205 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
3206 if (valid_contact
) {
3207 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
3208 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3209 g_free(valid_contact
);
3212 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3217 g_free(sip
->contact
);
3219 sip
->contact
= g_strdup_printf("<%s>", gruu
);
3222 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3223 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
);
3225 sip
->msrtc_event_categories
= FALSE
;
3226 sip
->batched_support
= FALSE
;
3231 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
3232 if (!g_ascii_strcasecmp(elem
->value
, "msrtc-event-categories")) {
3233 sip
->msrtc_event_categories
= TRUE
;
3234 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->msrtc_event_categories
);
3236 if (!g_ascii_strcasecmp(elem
->value
, "adhoclist")) {
3237 sip
->batched_support
= TRUE
;
3238 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->batched_support
);
3241 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
3242 gchar
**caps
= g_strsplit(elem
->value
,",",0);
3245 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
3246 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
3251 hdr
= g_slist_next(hdr
);
3254 if (!sip
->subscribed
) { //do it just once, not every re-register
3255 if(!sip
->msrtc_event_categories
){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3256 sipe_options_request(sip
, sip
->sipdomain
);
3258 entry
= sip
->allow_events
;
3261 if (tmp
&& !g_ascii_strcasecmp(tmp
, "vnd-microsoft-roaming-contacts")) {
3262 sipe_subscribe_roaming_contacts(sip
, msg
);
3264 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-ACL")) {
3265 sipe_subscribe_roaming_acl(sip
, msg
);
3267 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-self")) {
3268 sipe_subscribe_roaming_self(sip
, msg
);
3270 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning-v2")) {
3271 sipe_subscribe_roaming_provisioning_v2(sip
, msg
);
3272 } else if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning")) { // LSC2005
3273 sipe_subscribe_roaming_provisioning(sip
, msg
);
3275 if (tmp
&& !g_ascii_strcasecmp(tmp
,"presence.wpending")) {
3276 sipe_subscribe_presence_wpending(sip
, msg
);
3278 entry
= entry
->next
;
3280 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
3281 sip
->subscribed
= TRUE
;
3284 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
3285 "timeout=", ";", NULL
);
3286 if (timeout
!= NULL
) {
3287 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
3288 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3289 sip
->keepalive_timeout
);
3292 sip
->keepalive_timeout
= 300;
3295 // Should we remove the transaction here?
3296 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
3297 transactions_remove(sip
, tc
);
3302 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
3304 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
3305 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
3309 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
3312 tmp
= g_strsplit(parts
[0], ":", 0);
3313 hostname
= g_strdup(tmp
[0]);
3314 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
3318 tmp
= g_strsplit(parts
[i
], "=", 0);
3320 if (g_strcasecmp("transport", tmp
[0]) == 0) {
3321 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
3322 transport
= SIPE_TRANSPORT_TCP
;
3323 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
3324 transport
= SIPE_TRANSPORT_UDP
;
3333 /* Close old connection */
3334 sipe_connection_cleanup(sip
);
3336 /* Create new connection */
3337 sip
->transport
= transport
;
3338 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
3339 hostname
, port
, TRANSPORT_DESCRIPTOR
);
3340 create_connection(sip
, hostname
, port
);
3346 if (sip
->registerstatus
!= 2) {
3347 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
3348 if (sip
->registrar
.retries
> 3) {
3349 sip
->gc
->wants_to_die
= TRUE
;
3350 purple_connection_error(sip
->gc
, _("Wrong Password"));
3353 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3354 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3356 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3358 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3359 fill_auth(sip
, tmp
, &sip
->registrar
);
3360 sip
->registerstatus
= 2;
3361 if (sip
->account
->disconnecting
) {
3362 do_register_exp(sip
, 0);
3370 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
3371 if (warning
!= NULL
) {
3373 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
3375 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
3376 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
3379 warning
= g_strdup(_("You have been rejected by the server"));
3382 sip
->gc
->wants_to_die
= TRUE
;
3383 purple_connection_error(sip
->gc
, warning
);
3390 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3391 if (warning
!= NULL
) {
3392 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3393 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
3396 warning
= g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
3399 sip
->gc
->wants_to_die
= TRUE
;
3400 purple_connection_error(sip
->gc
, warning
);
3407 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
3408 if (warning
!= NULL
) {
3409 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
3410 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
3413 warning
= g_strdup(_("Service unavailable: no reason given"));
3416 sip
->gc
->wants_to_die
= TRUE
;
3417 purple_connection_error(sip
->gc
, warning
);
3426 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3429 xmlnode
*xn_categories
;
3430 xmlnode
*xn_category
;
3432 const char *activity
= NULL
;
3434 xn_categories
= xmlnode_from_str(data
, len
);
3435 uri
= xmlnode_get_attrib(xn_categories
, "uri");
3437 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
3439 xn_category
= xmlnode_get_next_twin(xn_category
) )
3441 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
3443 if (!strcmp(attrVar
, "note"))
3446 struct sipe_buddy
*sbuddy
;
3447 xn_node
= xmlnode_get_child(xn_category
, "note");
3448 if (!xn_node
) continue;
3449 xn_node
= xmlnode_get_child(xn_node
, "body");
3450 if (!xn_node
) continue;
3452 note
= xmlnode_get_data(xn_node
);
3455 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3459 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri
,note
);
3460 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3461 sbuddy
->annotation
= g_strdup(note
);
3466 else if(!strcmp(attrVar
, "state"))
3470 xn_node
= xmlnode_get_child(xn_category
, "state");
3471 if (!xn_node
) continue;
3472 xn_node
= xmlnode_get_child(xn_node
, "availability");
3473 if (!xn_node
) continue;
3475 data
= xmlnode_get_data(xn_node
);
3480 activity
= SIPE_STATUS_ID_UNKNOWN
;
3481 else if (avail
< 4500)
3482 activity
= SIPE_STATUS_ID_AVAILABLE
;
3483 else if (avail
< 6000)
3484 activity
= SIPE_STATUS_ID_BRB
;
3485 else if (avail
< 7500)
3486 activity
= SIPE_STATUS_ID_ONPHONE
;
3487 else if (avail
< 9000)
3488 activity
= SIPE_STATUS_ID_BUSY
;
3489 else if (avail
< 12000)
3490 activity
= SIPE_STATUS_ID_DND
;
3491 else if (avail
< 18000)
3492 activity
= SIPE_STATUS_ID_AWAY
;
3494 activity
= SIPE_STATUS_ID_OFFLINE
;
3498 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity
);
3499 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
3502 xmlnode_free(xn_categories
);
3505 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3508 xmlnode
*xn_resource
;
3510 xn_list
= xmlnode_from_str(data
, len
);
3512 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
3514 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
3516 const char *uri
, *state
, *poolFqdn
, *cid
;
3517 xmlnode
*xn_instance
;
3519 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
3520 if (!xn_instance
) continue;
3522 uri
= xmlnode_get_attrib(xn_resource
, "uri");
3523 state
= xmlnode_get_attrib(xn_instance
, "state");
3524 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri
, state
);
3526 cid
= xmlnode_get_attrib(xn_instance
, "cid");
3527 if(cid
){ //Only OCS2007
3528 uri
= g_strdup(cid
);
3529 purple_debug_info("sipe", "The user is from a different deployment; cid=%s\n", uri
);
3532 poolFqdn
= xmlnode_get_attrib(xn_instance
, "poolFqdn");
3533 if(poolFqdn
){ //[MS-PRES] Section 3.4.5.1.3 Processing Details
3534 uri
= g_strdup(poolFqdn
);
3535 purple_debug_info("sipe", "The user is from the same domain but is hosted on a different server or server pool; poolFqdn=%s\n", uri
);
3539 if (strstr(state
, "resubscribe")) {
3540 struct sipe_buddy
*sbuddy
;
3541 sipe_subscribe_presence_single(sip
, (void *) uri
);
3543 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3545 sbuddy
->resubscribed
= TRUE
;
3551 xmlnode_free(xn_list
);
3554 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3558 gchar
*activity
= NULL
;
3560 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
3561 gboolean isonline
= FALSE
;
3562 xmlnode
*display_name_node
;
3564 pidf
= xmlnode_from_str(data
, len
);
3566 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
3570 uri
= xmlnode_get_attrib(pidf
, "entity");
3572 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
3574 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3575 basicstatus
= xmlnode_get_child(status
, "basic");
3580 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3585 getbasic
= xmlnode_get_data(basicstatus
);
3587 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3592 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
3593 if (strstr(getbasic
, "open")) {
3598 display_name_node
= xmlnode_get_child(pidf
, "display-name");
3599 // updating display name if alias was just URI
3600 if (display_name_node
) {
3601 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3602 GSList
*entry
= buddies
;
3603 PurpleBuddy
*p_buddy
;
3604 char * display_name
= xmlnode_get_data(display_name_node
);
3607 const char *server_alias
;
3610 p_buddy
= entry
->data
;
3612 alias
= (char *)purple_buddy_get_alias(p_buddy
);
3613 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
3614 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
3615 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3616 purple_blist_alias_buddy(p_buddy
, display_name
);
3620 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3622 ( (server_alias
&& strcmp(display_name
, server_alias
))
3623 || !server_alias
|| strlen(server_alias
) == 0 )
3625 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3628 entry
= entry
->next
;
3630 g_free(display_name
);
3633 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
3634 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3635 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
3636 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
3637 activity
= xmlnode_get_data(basicstatus
);
3638 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
3645 const gchar
* status_id
= NULL
;
3647 if (strstr(activity
, "busy")) {
3648 status_id
= SIPE_STATUS_ID_BUSY
;
3649 } else if (strstr(activity
, "away")) {
3650 status_id
= SIPE_STATUS_ID_AWAY
;
3655 status_id
= SIPE_STATUS_ID_AVAILABLE
;
3658 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
3659 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
3661 purple_prpl_got_user_status(sip
->account
, uri
, SIPE_STATUS_ID_OFFLINE
, NULL
);
3668 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3670 const char *availability
;
3671 const char *activity
;
3672 const char *display_name
= NULL
;
3673 const char *activity_name
= NULL
;
3678 struct sipe_buddy
*sbuddy
;
3680 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
3682 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
3683 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
3684 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
3685 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
3686 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
3687 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
3688 xmlnode
*xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
):NULL
;
3689 const char *avail
= xn_state
? xmlnode_get_attrib(xn_state
, "avail") : NULL
;
3691 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
3692 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
3693 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
3694 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
3695 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
3696 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
3698 name
= xmlnode_get_attrib(xn_presentity
, "uri");
3699 uri
= g_strdup_printf("sip:%s", name
);
3700 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
3701 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
3703 // updating display name if alias was just URI
3704 if (xn_display_name
) {
3705 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3706 GSList
*entry
= buddies
;
3707 PurpleBuddy
*p_buddy
;
3708 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
3711 const char *email_str
, *server_alias
;
3713 p_buddy
= entry
->data
;
3715 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
3716 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3717 purple_blist_alias_buddy(p_buddy
, display_name
);
3720 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3722 ( (server_alias
&& strcmp(display_name
, server_alias
))
3723 || !server_alias
|| strlen(server_alias
) == 0 )
3725 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3729 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
3730 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
3731 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
3735 entry
= entry
->next
;
3739 avl
= atoi(availability
);
3740 act
= atoi(activity
);
3742 if(sip
->msrtc_event_categories
){
3743 if (act
== 100 && avl
== 0)
3744 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3745 else if (act
== 100 && avl
== 300)
3746 activity_name
= SIPE_STATUS_ID_AWAY
;
3747 else if (act
== 300 && avl
== 300)
3748 activity_name
= SIPE_STATUS_ID_BRB
;
3749 else if (act
== 400 && avl
== 300)
3750 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3751 else if (act
== 500 && act
== 300)
3752 activity_name
= SIPE_STATUS_ID_ONPHONE
;
3753 else if (act
== 600 && avl
== 300)
3754 activity_name
= SIPE_STATUS_ID_BUSY
;
3755 else if (act
== 0 && avl
== 0){ //MSRTC elements are zero
3756 if(avail
){ //Check for LegacyInterop elements
3759 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3760 else if (avl
== 3500)
3761 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3762 else if (avl
== 15500)
3763 activity_name
= SIPE_STATUS_ID_AWAY
;
3764 else if (avl
== 6500)
3765 activity_name
= SIPE_STATUS_ID_BUSY
;
3766 else if (avl
== 12500)
3767 activity_name
= SIPE_STATUS_ID_BRB
;
3772 if(activity_name
== NULL
){
3774 activity_name
= SIPE_STATUS_ID_AWAY
;
3775 else if (act
<= 150)
3776 activity_name
= SIPE_STATUS_ID_LUNCH
;
3777 else if (act
<= 300)
3778 activity_name
= SIPE_STATUS_ID_BRB
;
3779 else if (act
<= 400)
3780 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3781 else if (act
<= 500)
3782 activity_name
= SIPE_STATUS_ID_ONPHONE
;
3783 else if (act
<= 600)
3784 activity_name
= SIPE_STATUS_ID_BUSY
;
3786 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3789 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3792 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3795 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3796 sbuddy
->annotation
= NULL
;
3797 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
3799 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
3800 sbuddy
->device_name
= NULL
;
3801 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
3804 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
3805 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
3807 xmlnode_free(xn_presentity
);
3811 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3813 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3815 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
3817 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
3818 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
3820 const char *content
= msg
->body
;
3821 unsigned length
= msg
->bodylen
;
3822 PurpleMimeDocument
*mime
= NULL
;
3824 if (strstr(ctype
, "multipart"))
3826 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3827 const char *content_type
;
3829 mime
= purple_mime_document_parse(doc
);
3830 parts
= purple_mime_document_get_parts(mime
);
3832 content
= purple_mime_part_get_data(parts
->data
);
3833 length
= purple_mime_part_get_length(parts
->data
);
3834 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
3835 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
3837 process_incoming_notify_rlmi_resub(sip
, content
, length
);
3839 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
3841 process_incoming_notify_msrtc(sip
, content
, length
);
3845 process_incoming_notify_rlmi(sip
, content
, length
);
3847 parts
= parts
->next
;
3853 purple_mime_document_free(mime
);
3856 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
3858 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
3860 else if(strstr(ctype
, "application/rlmi+xml"))
3862 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
3865 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
3867 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
3871 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
3876 * Dispatcher for all incoming subscription information
3877 * whether it comes from NOTIFY, BENOTIFY requests or
3878 * piggy-backed to subscription's OK responce.
3880 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3881 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3883 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
3885 gchar
*event
= sipmsg_find_header(msg
, "Event");
3886 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
3889 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
3890 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
3894 const gchar
*expires_header
;
3895 expires_header
= sipmsg_find_header(msg
, "Expires");
3896 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
3897 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout
);
3898 timeout
= (timeout
- 60) > 60 ? (timeout
- 60) : timeout
; // 1 min ahead of expiration
3901 if (!subscription_state
|| strstr(subscription_state
, "active"))
3903 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
3905 sipe_process_presence(sip
, msg
);
3907 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
3909 sipe_process_roaming_contacts(sip
, msg
, NULL
);
3911 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self") )
3913 sipe_process_roaming_self(sip
, msg
);
3915 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
3917 sipe_process_roaming_acl(sip
, msg
);
3919 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
3921 sipe_process_presence_wpending(sip
, msg
);
3925 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
3929 //The server sends a (BE)NOTIFY with the status 'terminated'
3930 if (request
&& subscription_state
&& strstr(subscription_state
, "terminated") ) {
3931 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3932 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
3936 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
3937 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
3938 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
3940 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
3941 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, sip, msg);
3942 g_free(action_name);
3944 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
3945 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
3947 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
3948 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, sip, msg);
3949 g_free(action_name);
3952 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
3953 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
3955 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
3956 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, sip
, NULL
);
3957 g_free(action_name
);
3959 else if (!g_ascii_strcasecmp(event
, "presence") &&
3960 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
3962 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
3963 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
3964 if(sip
->batched_support
) {
3965 gchar
*my_self
= g_strdup_printf("sip:%s",sip
->username
);
3966 if(!g_ascii_strcasecmp(who
, my_self
)){
3967 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_batched
, sip
, NULL
);
3968 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout
);
3969 g_free(who
); /* unused */
3972 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, sip
, who
);
3973 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
,timeout
);
3978 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, sip
, who
);
3979 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
,timeout
);
3981 g_free(action_name
);
3982 /* "who" will be freed by the action we just scheduled */
3986 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
3988 sipe_process_registration_notify(sip
, msg
);
3991 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3992 if (request
&& !benotify
)
3994 sipmsg_remove_header(msg
, "Expires");
3995 sipmsg_remove_header(msg
, "subscription-state");
3996 sipmsg_remove_header(msg
, "Event");
3997 sipmsg_remove_header(msg
, "Require");
3998 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4005 static gchar* gen_xpidf(struct sipe_account_data *sip)
4007 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4009 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4010 "<display name=\"sip:%s\"/>\r\n"
4011 "<atom id=\"1234\">\r\n"
4012 "<address uri=\"sip:%s\">\r\n"
4013 "<status status=\"%s\"/>\r\n"
4026 static gchar* gen_pidf(struct sipe_account_data *sip)
4028 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4029 "<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"
4030 "<tuple id=\"0\">\r\n"
4032 "<basic>open</basic>\r\n"
4033 "<ep:activities>\r\n"
4034 " <ep:activity>%s</ep:activity>\r\n"
4038 "<ci:display-name>%s</ci:display-name>\r\n"
4047 static void send_presence_soap(struct sipe_account_data
*sip
, const char * note
)
4049 int availability
= 300; // online
4050 int activity
= 400; // Available
4053 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
)) {
4055 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4057 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4059 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4061 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4063 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4065 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_INVISIBLE
) ||
4066 !strcmp(sip
->status
, SIPE_STATUS_ID_OFFLINE
)) {
4067 availability
= 0; // offline
4070 activity
= 400; // available
4073 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
4074 //@TODO: send user data - state; add hostname in upper case
4075 body
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
4076 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
4082 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4084 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4085 if (msg
->response
== 200) {
4086 sip
->status_version
= 0;
4087 send_presence_status(sip
);
4093 process_send_presence_category_publish_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4095 if (msg
->response
== 409) {
4096 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4097 // TODO need to parse the version #'s?
4098 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
4099 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
4103 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg
->body
);
4105 tmp
= get_contact(sip
);
4106 hdr
= g_strdup_printf("Contact: %s\r\n"
4107 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4109 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
4119 static void send_presence_category_publish(struct sipe_account_data
*sip
, const char * note
)
4126 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
) ||
4127 !strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4129 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_DND
)) {
4131 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4133 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4135 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4137 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4139 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_UNKNOWN
)) {
4142 // Offline or invisible
4146 uri
= g_strdup_printf("sip:%s", sip
->username
);
4147 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
4148 sip
->status_version
, code
,
4149 sip
->status_version
, code
,
4150 sip
->status_version
, note
? note
: "",
4151 sip
->status_version
, note
? note
: "",
4152 sip
->status_version
, note
? note
: ""
4154 sip
->status_version
++;
4156 tmp
= get_contact(sip
);
4157 hdr
= g_strdup_printf("Contact: %s\r\n"
4158 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4160 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
4168 static void send_presence_status(struct sipe_account_data
*sip
)
4170 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
4172 if (!status
) return;
4174 note
= purple_status_get_attr_string(status
, "message");
4176 if(sip
->msrtc_event_categories
){
4177 send_presence_category_publish(sip
, note
);
4179 send_presence_soap(sip
, note
);
4183 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
4185 gboolean found
= FALSE
;
4186 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
4187 if (msg
->response
== 0) { /* request */
4188 if (!strcmp(msg
->method
, "MESSAGE")) {
4189 process_incoming_message(sip
, msg
);
4191 } else if (!strcmp(msg
->method
, "NOTIFY")) {
4192 purple_debug_info("sipe","send->process_incoming_notify\n");
4193 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
4195 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
4196 purple_debug_info("sipe","send->process_incoming_benotify\n");
4197 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
4199 } else if (!strcmp(msg
->method
, "INVITE")) {
4200 process_incoming_invite(sip
, msg
);
4202 } else if (!strcmp(msg
->method
, "OPTIONS")) {
4203 process_incoming_options(sip
, msg
);
4205 } else if (!strcmp(msg
->method
, "INFO")) {
4207 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4209 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
4212 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4214 } else if (!strcmp(msg
->method
, "ACK")) {
4215 // ACK's don't need any response
4217 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
4218 // LCS 2005 sends us these - just respond 200 OK
4220 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4221 } else if (!strcmp(msg
->method
, "BYE")) {
4222 struct sip_im_session
*session
;
4224 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4226 from
= parse_from(sipmsg_find_header(msg
, "From"));
4227 session
= find_im_session (sip
, from
);
4231 // TODO Let the user know the other user left the conversation?
4232 im_session_destroy(sip
, session
);
4237 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4239 } else { /* response */
4240 struct transaction
*trans
= transactions_find(sip
, msg
);
4242 if (msg
->response
== 407) {
4243 gchar
*resend
, *auth
, *ptmp
;
4245 if (sip
->proxy
.retries
> 30) return;
4246 sip
->proxy
.retries
++;
4247 /* do proxy authentication */
4249 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
4251 fill_auth(sip
, ptmp
, &sip
->proxy
);
4252 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
4253 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4254 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
4256 resend
= sipmsg_to_string(trans
->msg
);
4257 /* resend request */
4258 sendout_pkt(sip
->gc
, resend
);
4261 if (msg
->response
== 100 || msg
->response
== 180) {
4262 /* ignore provisional response */
4263 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
4265 sip
->proxy
.retries
= 0;
4266 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
4267 if (msg
->response
== 401)
4269 sip
->registrar
.retries
++;
4270 sip
->registrar
.expires
= 0;
4274 sip
->registrar
.retries
= 0;
4276 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
4278 if (msg
->response
== 401) {
4279 gchar
*resend
, *auth
, *ptmp
;
4281 if (sip
->registrar
.retries
> 4) return;
4282 sip
->registrar
.retries
++;
4284 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
4285 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
4287 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
4290 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
4292 fill_auth(sip
, ptmp
, &sip
->registrar
);
4293 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
4294 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4295 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
4297 //sipmsg_remove_header(trans->msg, "Authorization");
4298 //sipmsg_add_header(trans->msg, "Authorization", auth);
4300 resend
= sipmsg_to_string(trans
->msg
);
4301 /* resend request */
4302 sendout_pkt(sip
->gc
, resend
);
4307 if (trans
->callback
) {
4308 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
4309 /* call the callback to process response*/
4310 (trans
->callback
)(sip
, msg
, trans
);
4312 /* Not sure if this is needed or what needs to be done
4313 but transactions seem to be removed prematurely so
4314 this only removes them if the response is 200 OK */
4315 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
4316 /*Has a bug and it's unneccesary*/
4317 /*transactions_remove(sip, trans);*/
4323 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
4327 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
4331 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
4339 /* according to the RFC remove CRLF at the beginning */
4340 while (*cur
== '\r' || *cur
== '\n') {
4343 if (cur
!= conn
->inbuf
) {
4344 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
4345 conn
->inbufused
= strlen(conn
->inbuf
);
4348 /* Received a full Header? */
4349 sip
->processing_input
= TRUE
;
4350 while (sip
->processing_input
&&
4351 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
4352 time_t currtime
= time(NULL
);
4355 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
4356 msg
= sipmsg_parse_header(conn
->inbuf
);
4359 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
4360 if (restlen
>= msg
->bodylen
) {
4361 dummy
= g_malloc(msg
->bodylen
+ 1);
4362 memcpy(dummy
, cur
, msg
->bodylen
);
4363 dummy
[msg
->bodylen
] = '\0';
4365 cur
+= msg
->bodylen
;
4366 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
4367 conn
->inbufused
= strlen(conn
->inbuf
);
4369 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4370 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
4376 purple_debug_info("sipe", "body:\n%s", msg->body);
4379 // Verify the signature before processing it
4380 if (sip
->registrar
.ntlm_key
) {
4381 struct sipmsg_breakdown msgbd
;
4382 gchar
*signature_input_str
;
4383 gchar
*signature
= NULL
;
4386 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
4387 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
4388 if (signature_input_str
!= NULL
) {
4389 signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
4391 g_free(signature_input_str
);
4393 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
4395 if (signature
!= NULL
) {
4396 if (rspauth
!= NULL
) {
4397 if (purple_ntlm_verify_signature (signature
, rspauth
)) {
4398 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
4399 process_input_message(sip
, msg
);
4401 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth
, signature
);
4402 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
4403 sip
->gc
->wants_to_die
= TRUE
;
4405 } else if (msg
->response
== 401) {
4406 purple_connection_error(sip
->gc
, _("Wrong Password"));
4407 sip
->gc
->wants_to_die
= TRUE
;
4413 sipmsg_breakdown_free(&msgbd
);
4415 process_input_message(sip
, msg
);
4422 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
4424 PurpleConnection
*gc
= data
;
4425 struct sipe_account_data
*sip
= gc
->proto_data
;
4430 static char buffer
[65536];
4431 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
4433 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
4434 msg
= sipmsg_parse_msg(buffer
);
4435 if (msg
) process_input_message(sip
, msg
);
4439 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
4441 struct sipe_account_data
*sip
= gc
->proto_data
;
4442 PurpleSslConnection
*gsc
= sip
->gsc
;
4444 purple_debug_error("sipe", "%s",debug
);
4445 purple_connection_error(gc
, msg
);
4447 /* Invalidate this connection. Next send will open a new one */
4449 connection_remove(sip
, gsc
->fd
);
4450 purple_ssl_close(gsc
);
4456 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4458 PurpleConnection
*gc
= data
;
4459 struct sipe_account_data
*sip
;
4460 struct sip_connection
*conn
;
4462 gboolean firstread
= TRUE
;
4464 /* NOTE: This check *IS* necessary */
4465 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
4466 purple_ssl_close(gsc
);
4470 sip
= gc
->proto_data
;
4471 conn
= connection_find(sip
, gsc
->fd
);
4473 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4474 gc
->wants_to_die
= TRUE
;
4475 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
4479 /* Read all available data from the SSL connection */
4481 /* Increase input buffer size as needed */
4482 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4483 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4484 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4485 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
4488 /* Try to read as much as there is space left in the buffer */
4489 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
4490 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
4492 if (len
< 0 && errno
== EAGAIN
) {
4493 /* Try again later */
4495 } else if (len
< 0) {
4496 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
4498 } else if (firstread
&& (len
== 0)) {
4499 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
4503 conn
->inbufused
+= len
;
4506 /* Equivalence indicates that there is possibly more data to read */
4507 } while (len
== readlen
);
4509 conn
->inbuf
[conn
->inbufused
] = '\0';
4510 process_input(sip
, conn
);
4514 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4516 PurpleConnection
*gc
= data
;
4517 struct sipe_account_data
*sip
= gc
->proto_data
;
4519 struct sip_connection
*conn
= connection_find(sip
, source
);
4521 purple_debug_error("sipe", "Connection not found!\n");
4525 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4526 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4527 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4530 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
4532 if (len
< 0 && errno
== EAGAIN
)
4534 else if (len
<= 0) {
4535 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4536 connection_remove(sip
, source
);
4537 if (sip
->fd
== source
) sip
->fd
= -1;
4541 conn
->inbufused
+= len
;
4542 conn
->inbuf
[conn
->inbufused
] = '\0';
4544 process_input(sip
, conn
);
4547 /* Callback for new connections on incoming TCP port */
4548 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4550 PurpleConnection
*gc
= data
;
4551 struct sipe_account_data
*sip
= gc
->proto_data
;
4552 struct sip_connection
*conn
;
4554 int newfd
= accept(source
, NULL
, NULL
);
4556 conn
= connection_create(sip
, newfd
);
4558 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4561 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
4563 PurpleConnection
*gc
= data
;
4564 struct sipe_account_data
*sip
;
4565 struct sip_connection
*conn
;
4567 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4575 purple_connection_error(gc
, _("Could not connect"));
4579 sip
= gc
->proto_data
;
4581 sip
->last_keepalive
= time(NULL
);
4583 conn
= connection_create(sip
, source
);
4587 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4590 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4592 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
4593 if (sip
== NULL
) return;
4598 static guint
sipe_ht_hash_nick(const char *nick
)
4600 char *lc
= g_utf8_strdown(nick
, -1);
4601 guint bucket
= g_str_hash(lc
);
4607 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
4609 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
4612 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
4614 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4616 sip
->listen_data
= NULL
;
4618 if (listenfd
== -1) {
4619 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4625 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
4626 sip
->listenfd
= sip
->fd
;
4628 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
4630 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
4634 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
4636 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4639 sip
->query_data
= NULL
;
4641 if (!hosts
|| !hosts
->data
) {
4642 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
4646 addr_size
= GPOINTER_TO_INT(hosts
->data
);
4647 hosts
= g_slist_remove(hosts
, hosts
->data
);
4648 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
4649 g_free(hosts
->data
);
4650 hosts
= g_slist_remove(hosts
, hosts
->data
);
4652 hosts
= g_slist_remove(hosts
, hosts
->data
);
4653 g_free(hosts
->data
);
4654 hosts
= g_slist_remove(hosts
, hosts
->data
);
4657 /* create socket for incoming connections */
4658 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
4659 sipe_udp_host_resolved_listen_cb
, sip
);
4660 if (sip
->listen_data
== NULL
) {
4661 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4666 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
4669 PurpleConnection
*gc
= data
;
4670 struct sipe_account_data
*sip
;
4672 /* If the connection is already disconnected, we don't need to do anything else */
4673 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4676 sip
= gc
->proto_data
;
4681 case PURPLE_SSL_CONNECT_FAILED
:
4682 purple_connection_error(gc
, _("Connection Failed"));
4684 case PURPLE_SSL_HANDSHAKE_FAILED
:
4685 purple_connection_error(gc
, _("SSL Handshake Failed"));
4687 case PURPLE_SSL_CERTIFICATE_INVALID
:
4688 purple_connection_error(gc
, _("SSL Certificate Invalid"));
4694 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
4696 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4697 PurpleProxyConnectData
*connect_data
;
4699 sip
->listen_data
= NULL
;
4701 sip
->listenfd
= listenfd
;
4702 if (sip
->listenfd
== -1) {
4703 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4707 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
4708 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4709 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
4710 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
4711 sipe_newconn_cb
, sip
->gc
);
4712 purple_debug_info("sipe", "connecting to %s port %d\n",
4713 sip
->realhostname
, sip
->realport
);
4714 /* open tcp connection to the server */
4715 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
4716 sip
->realport
, login_cb
, sip
->gc
);
4718 if (connect_data
== NULL
) {
4719 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
4724 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
4726 PurpleAccount
*account
= sip
->account
;
4727 PurpleConnection
*gc
= sip
->gc
;
4729 if (purple_account_get_bool(account
, "useport", FALSE
)) {
4730 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
4731 port
= purple_account_get_int(account
, "port", 0);
4733 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
4736 sip
->realhostname
= hostname
;
4737 sip
->realport
= port
;
4739 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
4742 /* TODO: is there a good default grow size? */
4743 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
4744 sip
->txbuf
= purple_circ_buffer_new(0);
4746 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
4748 if (!purple_ssl_is_supported()) {
4749 gc
->wants_to_die
= TRUE
;
4750 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4754 purple_debug_info("sipe", "using SSL\n");
4756 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
4757 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
4758 if (sip
->gsc
== NULL
) {
4759 purple_connection_error(gc
, _("Could not create SSL context"));
4762 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
4764 purple_debug_info("sipe", "using UDP\n");
4766 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
4767 if (sip
->query_data
== NULL
) {
4768 purple_connection_error(gc
, _("Could not resolve hostname"));
4772 purple_debug_info("sipe", "using TCP\n");
4773 /* create socket for incoming connections */
4774 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
4775 sipe_tcp_connect_listen_cb
, sip
);
4776 if (sip
->listen_data
== NULL
) {
4777 purple_connection_error(gc
, _("Could not create listen socket"));
4783 /* Service list for autodection */
4784 static const struct sipe_service_data service_autodetect
[] = {
4785 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4786 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4787 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4788 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4792 /* Service list for SSL/TLS */
4793 static const struct sipe_service_data service_tls
[] = {
4794 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4795 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4799 /* Service list for TCP */
4800 static const struct sipe_service_data service_tcp
[] = {
4801 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4802 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4806 /* Service list for UDP */
4807 static const struct sipe_service_data service_udp
[] = {
4808 { "sip", "udp", SIPE_TRANSPORT_UDP
},
4812 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
4813 static void resolve_next_service(struct sipe_account_data
*sip
,
4814 const struct sipe_service_data
*start
)
4817 sip
->service_data
= start
;
4819 sip
->service_data
++;
4820 if (sip
->service_data
->service
== NULL
) {
4822 /* Try connecting to the SIP hostname directly */
4823 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
4824 if (sip
->auto_transport
) {
4825 // If SSL is supported, default to using it; OCS servers aren't configured
4826 // by default to accept TCP
4827 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4828 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
4829 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
4832 hostname
= g_strdup(sip
->sipdomain
);
4833 create_connection(sip
, hostname
, 0);
4838 /* Try to resolve next service */
4839 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
4840 sip
->service_data
->transport
,
4845 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
4847 struct sipe_account_data
*sip
= data
;
4849 sip
->srv_query_data
= NULL
;
4851 /* find the host to connect to */
4853 gchar
*hostname
= g_strdup(resp
->hostname
);
4854 int port
= resp
->port
;
4855 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4859 sip
->transport
= sip
->service_data
->type
;
4861 create_connection(sip
, hostname
, port
);
4863 resolve_next_service(sip
, NULL
);
4867 static void sipe_login(PurpleAccount
*account
)
4869 PurpleConnection
*gc
;
4870 struct sipe_account_data
*sip
;
4871 gchar
**signinname_login
, **userserver
, **domain_user
;
4872 const char *transport
;
4874 const char *username
= purple_account_get_username(account
);
4875 gc
= purple_account_get_connection(account
);
4877 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
4878 gc
->wants_to_die
= TRUE
;
4879 purple_connection_error(gc
, _("SIP Exchange username contains invalid characters"));
4883 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
4884 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
4885 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
4887 sip
->account
= account
;
4888 sip
->reregister_set
= FALSE
;
4889 sip
->reauthenticate_set
= FALSE
;
4890 sip
->subscribed
= FALSE
;
4891 sip
->subscribed_buddies
= FALSE
;
4893 signinname_login
= g_strsplit(username
, ",", 2);
4895 userserver
= g_strsplit(signinname_login
[0], "@", 2);
4896 purple_connection_set_display_name(gc
, userserver
[0]);
4897 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
4898 sip
->sipdomain
= g_strdup(userserver
[1]);
4900 if (strpbrk(sip
->username
, " \t\v\r\n") != NULL
) {
4901 gc
->wants_to_die
= TRUE
;
4902 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
4906 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
4907 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
4908 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
4910 sip
->password
= g_strdup(purple_connection_get_password(gc
));
4912 g_strfreev(userserver
);
4913 g_strfreev(domain_user
);
4914 g_strfreev(signinname_login
);
4916 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
4918 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
4920 /* TODO: Set the status correctly. */
4921 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
4923 transport
= purple_account_get_string(account
, "transport", "auto");
4924 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
4925 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
4928 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
4929 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
4930 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
4931 } else if (strcmp(transport
, "auto") == 0) {
4932 sip
->auto_transport
= TRUE
;
4933 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
4934 } else if (strcmp(transport
, "tls") == 0) {
4935 resolve_next_service(sip
, service_tls
);
4936 } else if (strcmp(transport
, "tcp") == 0) {
4937 resolve_next_service(sip
, service_tcp
);
4939 resolve_next_service(sip
, service_udp
);
4943 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
4945 connection_free_all(sip
);
4950 if (sip
->query_data
!= NULL
)
4951 purple_dnsquery_destroy(sip
->query_data
);
4952 sip
->query_data
= NULL
;
4954 if (sip
->srv_query_data
!= NULL
)
4955 purple_srv_cancel(sip
->srv_query_data
);
4956 sip
->srv_query_data
= NULL
;
4958 if (sip
->listen_data
!= NULL
)
4959 purple_network_listen_cancel(sip
->listen_data
);
4960 sip
->listen_data
= NULL
;
4962 if (sip
->gsc
!= NULL
)
4963 purple_ssl_close(sip
->gsc
);
4966 sipe_auth_free(&sip
->registrar
);
4967 sipe_auth_free(&sip
->proxy
);
4970 purple_circ_buffer_destroy(sip
->txbuf
);
4973 g_free(sip
->realhostname
);
4974 sip
->realhostname
= NULL
;
4977 purple_input_remove(sip
->listenpa
);
4979 if (sip
->tx_handler
)
4980 purple_input_remove(sip
->tx_handler
);
4981 sip
->tx_handler
= 0;
4982 if (sip
->resendtimeout
)
4983 purple_timeout_remove(sip
->resendtimeout
);
4984 sip
->resendtimeout
= 0;
4985 if (sip
->timeouts
) {
4986 GSList
*entry
= sip
->timeouts
;
4988 struct scheduled_action
*sched_action
= entry
->data
;
4989 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
4990 purple_timeout_remove(sched_action
->timeout_handler
);
4991 g_free(sched_action
->payload
);
4992 g_free(sched_action
->name
);
4993 g_free(sched_action
);
4994 entry
= entry
->next
;
4997 g_slist_free(sip
->timeouts
);
4999 if (sip
->allow_events
) {
5000 GSList
*entry
= sip
->allow_events
;
5002 g_free(entry
->data
);
5003 entry
= entry
->next
;
5006 g_slist_free(sip
->allow_events
);
5009 g_free(sip
->contact
);
5010 sip
->contact
= NULL
;
5012 g_free(sip
->regcallid
);
5013 sip
->regcallid
= NULL
;
5016 sip
->processing_input
= FALSE
;
5020 * A callback for g_hash_table_foreach_remove
5022 static gboolean
sipe_buddy_remove(gpointer key
, struct sipe_buddy
*buddy
, gpointer user_data
)
5024 sipe_free_buddy(buddy
);
5027 static void sipe_close(PurpleConnection
*gc
)
5029 struct sipe_account_data
*sip
= gc
->proto_data
;
5032 /* leave all conversations */
5033 im_session_close_all(sip
);
5036 do_register_exp(sip
, 0);
5038 sipe_connection_cleanup(sip
);
5039 g_free(sip
->sipdomain
);
5040 g_free(sip
->username
);
5041 g_free(sip
->password
);
5042 g_free(sip
->authdomain
);
5043 g_free(sip
->authuser
);
5044 g_free(sip
->status
);
5046 g_hash_table_foreach_remove(sip
->buddies
, (GHRFunc
) sipe_buddy_remove
, NULL
);
5047 g_hash_table_destroy(sip
->buddies
);
5049 g_free(gc
->proto_data
);
5050 gc
->proto_data
= NULL
;
5053 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5055 PurpleAccount
*acct
= purple_connection_get_account(gc
);
5056 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
5057 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
5059 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
5060 purple_conversation_present(conv
);
5064 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5067 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
5068 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
5071 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
5073 PurpleNotifySearchResults
*results
;
5074 PurpleNotifySearchColumn
*column
;
5075 xmlnode
*searchResults
;
5077 int match_count
= 0;
5078 gboolean more
= FALSE
;
5081 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
5083 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5084 if (!searchResults
) {
5085 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5089 results
= purple_notify_searchresults_new();
5091 if (results
== NULL
) {
5092 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5093 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
5095 xmlnode_free(searchResults
);
5099 column
= purple_notify_searchresults_column_new(_("User Name"));
5100 purple_notify_searchresults_column_add(results
, column
);
5102 column
= purple_notify_searchresults_column_new(_("Name"));
5103 purple_notify_searchresults_column_add(results
, column
);
5105 column
= purple_notify_searchresults_column_new(_("Company"));
5106 purple_notify_searchresults_column_add(results
, column
);
5108 column
= purple_notify_searchresults_column_new(_("Country"));
5109 purple_notify_searchresults_column_add(results
, column
);
5111 column
= purple_notify_searchresults_column_new(_("Email"));
5112 purple_notify_searchresults_column_add(results
, column
);
5114 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
5117 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
5118 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
5119 g_strfreev(uri_parts
);
5121 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
5122 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
5123 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
5124 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
5126 purple_notify_searchresults_row_add(results
, row
);
5130 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
5131 char *data
= xmlnode_get_data_unescaped(mrow
);
5132 more
= (g_strcasecmp(data
, "true") == 0);
5136 secondary
= g_strdup_printf(
5137 dngettext(GETTEXT_PACKAGE
,
5138 "Found %d contact%s:",
5139 "Found %d contacts%s:", match_count
),
5140 match_count
, more
? _(" (more matched your query)") : "");
5142 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
5143 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
5144 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
5147 xmlnode_free(searchResults
);
5151 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
5153 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
5154 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
5158 PurpleRequestField
*field
= entries
->data
;
5159 const char *id
= purple_request_field_get_id(field
);
5160 const char *value
= purple_request_field_string_get_value(field
);
5162 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
5164 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
5165 } while ((entries
= g_list_next(entries
)) != NULL
);
5169 gchar
*query
= g_strjoinv(NULL
, attrs
);
5170 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
5171 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
5172 send_soap_request_with_cb(gc
->proto_data
, body
,
5173 (TransCallback
) process_search_contact_response
, NULL
);
5181 static void sipe_show_find_contact(PurplePluginAction
*action
)
5183 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
5184 PurpleRequestFields
*fields
;
5185 PurpleRequestFieldGroup
*group
;
5186 PurpleRequestField
*field
;
5188 fields
= purple_request_fields_new();
5189 group
= purple_request_field_group_new(NULL
);
5190 purple_request_fields_add_group(fields
, group
);
5192 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
5193 purple_request_field_group_add_field(group
, field
);
5194 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
5195 purple_request_field_group_add_field(group
, field
);
5196 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
5197 purple_request_field_group_add_field(group
, field
);
5198 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
5199 purple_request_field_group_add_field(group
, field
);
5201 purple_request_fields(gc
,
5203 _("Search for a Contact"),
5204 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5206 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
5208 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
5211 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
5214 PurplePluginAction
*act
;
5216 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
5217 menu
= g_list_prepend(menu
, act
);
5219 menu
= g_list_reverse(menu
);
5224 static void dummy_permit_deny(PurpleConnection
*gc
)
5228 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
5234 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
5240 static char *sipe_status_text(PurpleBuddy
*buddy
)
5242 struct sipe_account_data
*sip
;
5243 struct sipe_buddy
*sbuddy
;
5246 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5247 if (sip
) //happens on pidgin exit
5249 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5250 if (sbuddy
&& sbuddy
->annotation
)
5252 text
= g_strdup(sbuddy
->annotation
);
5259 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
5261 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
5262 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
5263 struct sipe_account_data
*sip
;
5264 struct sipe_buddy
*sbuddy
;
5265 char *annotation
= NULL
;
5267 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5268 if (sip
) //happens on pidgin exit
5270 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5273 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
5278 if (purple_presence_is_online(presence
))
5280 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
5285 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
5292 sipe_get_account_text_table(PurpleAccount
*account
)
5295 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
5296 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
5300 static PurpleBuddy
*
5301 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
5304 const gchar
*server_alias
, *email
;
5305 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
5307 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
5309 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
5311 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
5313 purple_blist_server_alias_buddy(clone
, server_alias
);
5316 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5318 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
5321 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
5323 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
5328 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
5330 PurpleBuddy
*buddy
, *b
;
5331 PurpleConnection
*gc
;
5332 PurpleGroup
* group
= purple_find_group(group_name
);
5334 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
5336 buddy
= (PurpleBuddy
*)node
;
5338 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
5339 gc
= purple_account_get_connection(buddy
->account
);
5341 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
5343 b
= purple_blist_add_buddy_clone(group
, buddy
);
5346 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
5350 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
5353 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
5355 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5358 char *mailto
= g_strdup_printf("mailto:%s", email
);
5359 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
5363 char *const parmList
[] = {mailto
, NULL
};
5364 if ((pid
= fork()) == -1)
5366 purple_debug_info("sipe", "fork() error\n");
5370 execvp("xdg-email", parmList
);
5371 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5379 //@TODO resolve env variable %WINDIR% first
5380 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
5383 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
5392 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
5397 * A menu which appear when right-clicking on buddy in contact list.
5400 sipe_buddy_menu(PurpleBuddy
*buddy
)
5402 PurpleBlistNode
*g_node
;
5403 PurpleGroup
*group
, *gr_parent
;
5404 PurpleMenuAction
*act
;
5406 GList
*menu_groups
= NULL
;
5408 act
= purple_menu_action_new(_("Send Email..."),
5409 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
5411 menu
= g_list_prepend(menu
, act
);
5413 gr_parent
= purple_buddy_get_group(buddy
);
5414 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
5415 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
5418 group
= (PurpleGroup
*)g_node
;
5419 if (group
== gr_parent
)
5422 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
5425 act
= purple_menu_action_new(purple_group_get_name(group
),
5426 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
5428 menu_groups
= g_list_prepend(menu_groups
, act
);
5430 menu_groups
= g_list_reverse(menu_groups
);
5432 act
= purple_menu_action_new(_("Copy to"),
5435 menu
= g_list_prepend(menu
, act
);
5436 menu
= g_list_reverse(menu
);
5441 GList
*sipe_blist_node_menu(PurpleBlistNode
*node
) {
5442 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
5443 return sipe_buddy_menu((PurpleBuddy
*) node
);
5450 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
5452 gboolean ret
= TRUE
;
5453 char *username
= (char *)trans
->payload
;
5455 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
5456 PurpleBuddy
*pbuddy
;
5457 struct sipe_buddy
*sbuddy
;
5459 char *server_alias
= NULL
;
5461 const char *device_name
= NULL
;
5463 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
5465 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
5466 alias
= purple_buddy_get_local_alias(pbuddy
);
5470 //will query buddy UA's capabilities and send answer to log
5471 sipe_options_request(sip
, username
);
5473 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
5476 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
5480 if (msg
->response
!= 200) {
5481 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
5483 xmlnode
*searchResults
;
5486 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
5487 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5488 if (!searchResults
) {
5489 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5490 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
5491 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
5492 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5493 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
5494 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
5495 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
5496 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
5497 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
5498 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
5499 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
5500 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
5501 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5502 if (!email
|| strcmp("", email
)) {
5503 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
5504 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
5508 xmlnode_free(searchResults
);
5511 purple_notify_user_info_add_section_break(info
);
5513 if (!server_alias
|| !strcmp("", server_alias
)) {
5514 g_free(server_alias
);
5515 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
5517 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5521 // same as server alias, do not present
5522 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
5525 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
5528 if (!email
|| !strcmp("", email
)) {
5530 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
5532 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5538 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
5541 /* show a buddy's user info in a nice dialog box */
5542 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
5543 username
, /* buddy's username */
5545 NULL
, /* callback called when dialog closed */
5546 NULL
); /* userdata for callback */
5552 * AD search first, LDAP based
5554 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
5556 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
5557 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
5559 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
5560 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
5561 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
5566 static PurplePlugin
*my_protocol
= NULL
;
5568 static PurplePluginProtocolInfo prpl_info
=
5571 NULL
, /* user_splits */
5572 NULL
, /* protocol_options */
5573 NO_BUDDY_ICONS
, /* icon_spec */
5574 sipe_list_icon
, /* list_icon */
5575 NULL
, /* list_emblems */
5576 sipe_status_text
, /* status_text */
5577 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
5578 sipe_status_types
, /* away_states */
5579 sipe_blist_node_menu
, /* blist_node_menu */
5580 NULL
, /* chat_info */
5581 NULL
, /* chat_info_defaults */
5582 sipe_login
, /* login */
5583 sipe_close
, /* close */
5584 sipe_im_send
, /* send_im */
5585 NULL
, /* set_info */ // TODO maybe
5586 sipe_send_typing
, /* send_typing */
5587 sipe_get_info
, /* get_info */
5588 sipe_set_status
, /* set_status */
5589 NULL
, /* set_idle */
5590 NULL
, /* change_passwd */
5591 sipe_add_buddy
, /* add_buddy */
5592 NULL
, /* add_buddies */
5593 sipe_remove_buddy
, /* remove_buddy */
5594 NULL
, /* remove_buddies */
5595 sipe_add_permit
, /* add_permit */
5596 sipe_add_deny
, /* add_deny */
5597 sipe_add_deny
, /* rem_permit */
5598 sipe_add_permit
, /* rem_deny */
5599 dummy_permit_deny
, /* set_permit_deny */
5600 NULL
, /* join_chat */
5601 NULL
, /* reject_chat */
5602 NULL
, /* get_chat_name */
5603 NULL
, /* chat_invite */
5604 NULL
, /* chat_leave */
5605 NULL
, /* chat_whisper */
5606 NULL
, /* chat_send */
5607 sipe_keep_alive
, /* keepalive */
5608 NULL
, /* register_user */
5609 NULL
, /* get_cb_info */ // deprecated
5610 NULL
, /* get_cb_away */ // deprecated
5611 sipe_alias_buddy
, /* alias_buddy */
5612 sipe_group_buddy
, /* group_buddy */
5613 sipe_rename_group
, /* rename_group */
5614 NULL
, /* buddy_free */
5615 sipe_convo_closed
, /* convo_closed */
5616 purple_normalize_nocase
, /* normalize */
5617 NULL
, /* set_buddy_icon */
5618 sipe_remove_group
, /* remove_group */
5619 NULL
, /* get_cb_real_name */ // TODO?
5620 NULL
, /* set_chat_topic */
5621 NULL
, /* find_blist_chat */
5622 NULL
, /* roomlist_get_list */
5623 NULL
, /* roomlist_cancel */
5624 NULL
, /* roomlist_expand_category */
5625 NULL
, /* can_receive_file */
5626 NULL
, /* send_file */
5627 NULL
, /* new_xfer */
5628 NULL
, /* offline_message */
5629 NULL
, /* whiteboard_prpl_ops */
5630 sipe_send_raw
, /* send_raw */
5631 NULL
, /* roomlist_room_serialize */
5632 NULL
, /* unregister_user */
5633 NULL
, /* send_attention */
5634 NULL
, /* get_attention_types */
5636 sizeof(PurplePluginProtocolInfo
), /* struct_size */
5637 sipe_get_account_text_table
, /* get_account_text_table */
5641 static PurplePluginInfo info
= {
5642 PURPLE_PLUGIN_MAGIC
,
5643 PURPLE_MAJOR_VERSION
,
5644 PURPLE_MINOR_VERSION
,
5645 PURPLE_PLUGIN_PROTOCOL
, /**< type */
5646 NULL
, /**< ui_requirement */
5648 NULL
, /**< dependencies */
5649 PURPLE_PRIORITY_DEFAULT
, /**< priority */
5650 "prpl-sipe", /**< id */
5651 "Microsoft LCS/OCS", /**< name */
5652 VERSION
, /**< version */
5653 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5654 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5655 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5656 "Gabriel Burt <gburt@novell.com>", /**< author */
5657 PURPLE_WEBSITE
, /**< homepage */
5658 sipe_plugin_load
, /**< load */
5659 sipe_plugin_unload
, /**< unload */
5660 sipe_plugin_destroy
, /**< destroy */
5661 NULL
, /**< ui_info */
5662 &prpl_info
, /**< extra_info */
5671 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
5675 entry
= prpl_info
.protocol_options
;
5677 purple_account_option_destroy(entry
->data
);
5678 entry
= g_list_delete_link(entry
, entry
);
5680 prpl_info
.protocol_options
= NULL
;
5682 entry
= prpl_info
.user_splits
;
5684 purple_account_user_split_destroy(entry
->data
);
5685 entry
= g_list_delete_link(entry
, entry
);
5687 prpl_info
.user_splits
= NULL
;
5690 static void init_plugin(PurplePlugin
*plugin
)
5692 PurpleAccountUserSplit
*split
;
5693 PurpleAccountOption
*option
;
5696 purple_debug_info(PACKAGE
, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
5697 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s",
5698 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
5701 purple_plugin_register(plugin
);
5703 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
5704 purple_account_user_split_set_reverse(split
, FALSE
);
5705 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
5707 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
5708 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5709 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5710 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5712 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
5713 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5714 // Translators: noun (networking port)
5715 option
= purple_account_option_int_new(_("Port"), "port", 5061);
5716 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5718 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
5719 purple_account_option_add_list_item(option
, _("Auto"), "auto");
5720 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
5721 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
5722 purple_account_option_add_list_item(option
, _("UDP"), "udp");
5723 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5725 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5726 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5728 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
5729 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5731 // TODO commented out so won't show in the preferences until we fix krb message signing
5732 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5733 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5735 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5736 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5737 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5740 my_protocol
= plugin
;
5743 /* I had to redefined the function for it load, but works */
5744 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
5745 plugin
->info
= &(info
);
5746 init_plugin((plugin
));
5747 sipe_plugin_load((plugin
));
5748 return purple_plugin_register(plugin
);