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(buddy_name
, "sip:") ? g_strdup(buddy_name
) : g_strdup_printf("sip:%s", 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
;
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 if (strstr(state
, "resubscribe")) {
3527 struct sipe_buddy
*sbuddy
;
3528 sipe_subscribe_presence_single(sip
, (void *) uri
);
3529 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3531 sbuddy
->resubscribed
= TRUE
;
3536 xmlnode_free(xn_list
);
3539 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3543 gchar
*activity
= NULL
;
3545 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
3546 gboolean isonline
= FALSE
;
3547 xmlnode
*display_name_node
;
3549 pidf
= xmlnode_from_str(data
, len
);
3551 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
3555 uri
= xmlnode_get_attrib(pidf
, "entity");
3557 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
3559 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3560 basicstatus
= xmlnode_get_child(status
, "basic");
3565 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
3570 getbasic
= xmlnode_get_data(basicstatus
);
3572 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
3577 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
3578 if (strstr(getbasic
, "open")) {
3583 display_name_node
= xmlnode_get_child(pidf
, "display-name");
3584 // updating display name if alias was just URI
3585 if (display_name_node
) {
3586 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3587 GSList
*entry
= buddies
;
3588 PurpleBuddy
*p_buddy
;
3589 char * display_name
= xmlnode_get_data(display_name_node
);
3592 const char *server_alias
;
3595 p_buddy
= entry
->data
;
3597 alias
= (char *)purple_buddy_get_alias(p_buddy
);
3598 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
3599 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
3600 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3601 purple_blist_alias_buddy(p_buddy
, display_name
);
3605 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3607 ( (server_alias
&& strcmp(display_name
, server_alias
))
3608 || !server_alias
|| strlen(server_alias
) == 0 )
3610 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3613 entry
= entry
->next
;
3615 g_free(display_name
);
3618 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
3619 if ((status
= xmlnode_get_child(tuple
, "status"))) {
3620 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
3621 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
3622 activity
= xmlnode_get_data(basicstatus
);
3623 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
3630 const gchar
* status_id
= NULL
;
3632 if (strstr(activity
, "busy")) {
3633 status_id
= SIPE_STATUS_ID_BUSY
;
3634 } else if (strstr(activity
, "away")) {
3635 status_id
= SIPE_STATUS_ID_AWAY
;
3640 status_id
= SIPE_STATUS_ID_AVAILABLE
;
3643 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
3644 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
3646 purple_prpl_got_user_status(sip
->account
, uri
, SIPE_STATUS_ID_OFFLINE
, NULL
);
3653 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
3655 const char *availability
;
3656 const char *activity
;
3657 const char *display_name
= NULL
;
3658 const char *activity_name
= NULL
;
3663 struct sipe_buddy
*sbuddy
;
3665 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
3667 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
3668 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
3669 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
3670 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
3671 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
3672 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
3673 xmlnode
*xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
):NULL
;
3674 const char *avail
= xn_state
? xmlnode_get_attrib(xn_state
, "avail") : NULL
;
3676 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
3677 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
3678 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
3679 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
3680 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
3681 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
3683 name
= xmlnode_get_attrib(xn_presentity
, "uri");
3684 uri
= g_strdup_printf("sip:%s", name
);
3685 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
3686 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
3688 // updating display name if alias was just URI
3689 if (xn_display_name
) {
3690 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
3691 GSList
*entry
= buddies
;
3692 PurpleBuddy
*p_buddy
;
3693 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
3696 const char *email_str
, *server_alias
;
3698 p_buddy
= entry
->data
;
3700 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
3701 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
3702 purple_blist_alias_buddy(p_buddy
, display_name
);
3705 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3707 ( (server_alias
&& strcmp(display_name
, server_alias
))
3708 || !server_alias
|| strlen(server_alias
) == 0 )
3710 purple_blist_server_alias_buddy(p_buddy
, display_name
);
3714 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
3715 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
3716 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
3720 entry
= entry
->next
;
3724 avl
= atoi(availability
);
3725 act
= atoi(activity
);
3727 if(sip
->msrtc_event_categories
){
3728 if (act
== 100 && avl
== 0)
3729 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3730 else if (act
== 100 && avl
== 300)
3731 activity_name
= SIPE_STATUS_ID_AWAY
;
3732 else if (act
== 300 && avl
== 300)
3733 activity_name
= SIPE_STATUS_ID_BRB
;
3734 else if (act
== 400 && avl
== 300)
3735 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3736 else if (act
== 500 && act
== 300)
3737 activity_name
= SIPE_STATUS_ID_ONPHONE
;
3738 else if (act
== 600 && avl
== 300)
3739 activity_name
= SIPE_STATUS_ID_BUSY
;
3740 else if (act
== 0 && avl
== 0){ //MSRTC elements are zero
3741 if(avail
){ //Check for LegacyInterop elements
3744 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3745 else if (avl
== 3500)
3746 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3747 else if (avl
== 15500)
3748 activity_name
= SIPE_STATUS_ID_AWAY
;
3749 else if (avl
== 6500)
3750 activity_name
= SIPE_STATUS_ID_BUSY
;
3751 else if (avl
== 12500)
3752 activity_name
= SIPE_STATUS_ID_BRB
;
3757 if(activity_name
== NULL
){
3759 activity_name
= SIPE_STATUS_ID_AWAY
;
3760 else if (act
<= 150)
3761 activity_name
= SIPE_STATUS_ID_LUNCH
;
3762 else if (act
<= 300)
3763 activity_name
= SIPE_STATUS_ID_BRB
;
3764 else if (act
<= 400)
3765 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3766 else if (act
<= 500)
3767 activity_name
= SIPE_STATUS_ID_ONPHONE
;
3768 else if (act
<= 600)
3769 activity_name
= SIPE_STATUS_ID_BUSY
;
3771 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
3774 activity_name
= SIPE_STATUS_ID_OFFLINE
;
3777 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
3780 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
3781 sbuddy
->annotation
= NULL
;
3782 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
3784 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
3785 sbuddy
->device_name
= NULL
;
3786 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
3789 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
3790 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
3792 xmlnode_free(xn_presentity
);
3796 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3798 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
3800 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
3802 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
3803 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
3805 const char *content
= msg
->body
;
3806 unsigned length
= msg
->bodylen
;
3807 PurpleMimeDocument
*mime
= NULL
;
3809 if (strstr(ctype
, "multipart"))
3811 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
3812 const char *content_type
;
3814 mime
= purple_mime_document_parse(doc
);
3815 parts
= purple_mime_document_get_parts(mime
);
3817 content
= purple_mime_part_get_data(parts
->data
);
3818 length
= purple_mime_part_get_length(parts
->data
);
3819 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
3820 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
3822 process_incoming_notify_rlmi_resub(sip
, content
, length
);
3824 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
3826 process_incoming_notify_msrtc(sip
, content
, length
);
3830 process_incoming_notify_rlmi(sip
, content
, length
);
3832 parts
= parts
->next
;
3838 purple_mime_document_free(mime
);
3841 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
3843 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
3845 else if(strstr(ctype
, "application/rlmi+xml"))
3847 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
3850 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
3852 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
3856 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
3861 * Dispatcher for all incoming subscription information
3862 * whether it comes from NOTIFY, BENOTIFY requests or
3863 * piggy-backed to subscription's OK responce.
3865 * @param request whether initiated from BE/NOTIFY request or OK-response message.
3866 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
3868 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
3870 gchar
*event
= sipmsg_find_header(msg
, "Event");
3871 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
3874 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
3875 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
3879 const gchar
*expires_header
;
3880 expires_header
= sipmsg_find_header(msg
, "Expires");
3881 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
3882 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout
);
3883 timeout
= (timeout
- 60) > 60 ? (timeout
- 60) : timeout
; // 1 min ahead of expiration
3886 if (!subscription_state
|| strstr(subscription_state
, "active"))
3888 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
3890 sipe_process_presence(sip
, msg
);
3892 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
3894 sipe_process_roaming_contacts(sip
, msg
, NULL
);
3896 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self") )
3898 sipe_process_roaming_self(sip
, msg
);
3900 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
3902 sipe_process_roaming_acl(sip
, msg
);
3904 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
3906 sipe_process_presence_wpending(sip
, msg
);
3910 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
3914 //The server sends a (BE)NOTIFY with the status 'terminated'
3915 if (request
&& subscription_state
&& strstr(subscription_state
, "terminated") ) {
3916 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3917 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
3921 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
3922 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
3923 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
3925 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
3926 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, sip, msg);
3927 g_free(action_name);
3929 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
3930 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
3932 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
3933 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, sip, msg);
3934 g_free(action_name);
3937 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
3938 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
3940 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
3941 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, sip
, NULL
);
3942 g_free(action_name
);
3944 else if (!g_ascii_strcasecmp(event
, "presence") &&
3945 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
3947 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
3948 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
3949 if(sip
->batched_support
) {
3950 gchar
*my_self
= g_strdup_printf("sip:%s",sip
->username
);
3951 if(!g_ascii_strcasecmp(who
, my_self
)){
3952 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_batched
, sip
, NULL
);
3953 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout
);
3954 g_free(who
); /* unused */
3957 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, sip
, who
);
3958 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
,timeout
);
3963 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, sip
, who
);
3964 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
,timeout
);
3966 g_free(action_name
);
3967 /* "who" will be freed by the action we just scheduled */
3971 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
3973 sipe_process_registration_notify(sip
, msg
);
3976 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
3977 if (request
&& !benotify
)
3979 sipmsg_remove_header(msg
, "Expires");
3980 sipmsg_remove_header(msg
, "subscription-state");
3981 sipmsg_remove_header(msg
, "Event");
3982 sipmsg_remove_header(msg
, "Require");
3983 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3990 static gchar* gen_xpidf(struct sipe_account_data *sip)
3992 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
3994 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
3995 "<display name=\"sip:%s\"/>\r\n"
3996 "<atom id=\"1234\">\r\n"
3997 "<address uri=\"sip:%s\">\r\n"
3998 "<status status=\"%s\"/>\r\n"
4011 static gchar* gen_pidf(struct sipe_account_data *sip)
4013 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4014 "<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"
4015 "<tuple id=\"0\">\r\n"
4017 "<basic>open</basic>\r\n"
4018 "<ep:activities>\r\n"
4019 " <ep:activity>%s</ep:activity>\r\n"
4023 "<ci:display-name>%s</ci:display-name>\r\n"
4032 static void send_presence_soap(struct sipe_account_data
*sip
, const char * note
)
4034 int availability
= 300; // online
4035 int activity
= 400; // Available
4038 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
)) {
4040 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4042 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4044 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4046 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4048 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4050 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_INVISIBLE
) ||
4051 !strcmp(sip
->status
, SIPE_STATUS_ID_OFFLINE
)) {
4052 availability
= 0; // offline
4055 activity
= 400; // available
4058 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
4059 //@TODO: send user data - state; add hostname in upper case
4060 body
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
4061 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
4067 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4069 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4070 if (msg
->response
== 200) {
4071 sip
->status_version
= 0;
4072 send_presence_status(sip
);
4078 process_send_presence_category_publish_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4080 if (msg
->response
== 409) {
4081 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4082 // TODO need to parse the version #'s?
4083 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
4084 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
4088 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg
->body
);
4090 tmp
= get_contact(sip
);
4091 hdr
= g_strdup_printf("Contact: %s\r\n"
4092 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4094 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
4104 static void send_presence_category_publish(struct sipe_account_data
*sip
, const char * note
)
4111 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
) ||
4112 !strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4114 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_DND
)) {
4116 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4118 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4120 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4122 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4124 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_UNKNOWN
)) {
4127 // Offline or invisible
4131 uri
= g_strdup_printf("sip:%s", sip
->username
);
4132 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
4133 sip
->status_version
, code
,
4134 sip
->status_version
, code
,
4135 sip
->status_version
, note
? note
: "",
4136 sip
->status_version
, note
? note
: "",
4137 sip
->status_version
, note
? note
: ""
4139 sip
->status_version
++;
4141 tmp
= get_contact(sip
);
4142 hdr
= g_strdup_printf("Contact: %s\r\n"
4143 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4145 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
4153 static void send_presence_status(struct sipe_account_data
*sip
)
4155 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
4157 if (!status
) return;
4159 note
= purple_status_get_attr_string(status
, "message");
4161 if(sip
->msrtc_event_categories
){
4162 send_presence_category_publish(sip
, note
);
4164 send_presence_soap(sip
, note
);
4168 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
4170 gboolean found
= FALSE
;
4171 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
4172 if (msg
->response
== 0) { /* request */
4173 if (!strcmp(msg
->method
, "MESSAGE")) {
4174 process_incoming_message(sip
, msg
);
4176 } else if (!strcmp(msg
->method
, "NOTIFY")) {
4177 purple_debug_info("sipe","send->process_incoming_notify\n");
4178 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
4180 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
4181 purple_debug_info("sipe","send->process_incoming_benotify\n");
4182 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
4184 } else if (!strcmp(msg
->method
, "INVITE")) {
4185 process_incoming_invite(sip
, msg
);
4187 } else if (!strcmp(msg
->method
, "OPTIONS")) {
4188 process_incoming_options(sip
, msg
);
4190 } else if (!strcmp(msg
->method
, "INFO")) {
4192 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4194 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
4197 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4199 } else if (!strcmp(msg
->method
, "ACK")) {
4200 // ACK's don't need any response
4202 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
4203 // LCS 2005 sends us these - just respond 200 OK
4205 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4206 } else if (!strcmp(msg
->method
, "BYE")) {
4207 struct sip_im_session
*session
;
4209 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4211 from
= parse_from(sipmsg_find_header(msg
, "From"));
4212 session
= find_im_session (sip
, from
);
4216 // TODO Let the user know the other user left the conversation?
4217 im_session_destroy(sip
, session
);
4222 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4224 } else { /* response */
4225 struct transaction
*trans
= transactions_find(sip
, msg
);
4227 if (msg
->response
== 407) {
4228 gchar
*resend
, *auth
, *ptmp
;
4230 if (sip
->proxy
.retries
> 30) return;
4231 sip
->proxy
.retries
++;
4232 /* do proxy authentication */
4234 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
4236 fill_auth(sip
, ptmp
, &sip
->proxy
);
4237 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
4238 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4239 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
4241 resend
= sipmsg_to_string(trans
->msg
);
4242 /* resend request */
4243 sendout_pkt(sip
->gc
, resend
);
4246 if (msg
->response
== 100 || msg
->response
== 180) {
4247 /* ignore provisional response */
4248 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
4250 sip
->proxy
.retries
= 0;
4251 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
4252 if (msg
->response
== 401)
4254 sip
->registrar
.retries
++;
4255 sip
->registrar
.expires
= 0;
4259 sip
->registrar
.retries
= 0;
4261 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
4263 if (msg
->response
== 401) {
4264 gchar
*resend
, *auth
, *ptmp
;
4266 if (sip
->registrar
.retries
> 4) return;
4267 sip
->registrar
.retries
++;
4269 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
4270 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
4272 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
4275 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
4277 fill_auth(sip
, ptmp
, &sip
->registrar
);
4278 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
4279 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4280 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
4282 //sipmsg_remove_header(trans->msg, "Authorization");
4283 //sipmsg_add_header(trans->msg, "Authorization", auth);
4285 resend
= sipmsg_to_string(trans
->msg
);
4286 /* resend request */
4287 sendout_pkt(sip
->gc
, resend
);
4292 if (trans
->callback
) {
4293 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
4294 /* call the callback to process response*/
4295 (trans
->callback
)(sip
, msg
, trans
);
4297 /* Not sure if this is needed or what needs to be done
4298 but transactions seem to be removed prematurely so
4299 this only removes them if the response is 200 OK */
4300 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
4301 /*Has a bug and it's unneccesary*/
4302 /*transactions_remove(sip, trans);*/
4308 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
4312 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
4316 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
4324 /* according to the RFC remove CRLF at the beginning */
4325 while (*cur
== '\r' || *cur
== '\n') {
4328 if (cur
!= conn
->inbuf
) {
4329 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
4330 conn
->inbufused
= strlen(conn
->inbuf
);
4333 /* Received a full Header? */
4334 sip
->processing_input
= TRUE
;
4335 while (sip
->processing_input
&&
4336 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
4337 time_t currtime
= time(NULL
);
4340 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
4341 msg
= sipmsg_parse_header(conn
->inbuf
);
4344 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
4345 if (restlen
>= msg
->bodylen
) {
4346 dummy
= g_malloc(msg
->bodylen
+ 1);
4347 memcpy(dummy
, cur
, msg
->bodylen
);
4348 dummy
[msg
->bodylen
] = '\0';
4350 cur
+= msg
->bodylen
;
4351 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
4352 conn
->inbufused
= strlen(conn
->inbuf
);
4354 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
4355 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
4361 purple_debug_info("sipe", "body:\n%s", msg->body);
4364 // Verify the signature before processing it
4365 if (sip
->registrar
.ntlm_key
) {
4366 struct sipmsg_breakdown msgbd
;
4367 gchar
*signature_input_str
;
4368 gchar
*signature
= NULL
;
4371 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
4372 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
4373 if (signature_input_str
!= NULL
) {
4374 signature
= purple_ntlm_sipe_signature_make (signature_input_str
, sip
->registrar
.ntlm_key
);
4376 g_free(signature_input_str
);
4378 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
4380 if (signature
!= NULL
) {
4381 if (rspauth
!= NULL
) {
4382 if (purple_ntlm_verify_signature (signature
, rspauth
)) {
4383 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
4384 process_input_message(sip
, msg
);
4386 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid. Received %s but generated %s; Ignoring message\n", rspauth
, signature
);
4387 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
4388 sip
->gc
->wants_to_die
= TRUE
;
4390 } else if (msg
->response
== 401) {
4391 purple_connection_error(sip
->gc
, _("Wrong Password"));
4392 sip
->gc
->wants_to_die
= TRUE
;
4398 sipmsg_breakdown_free(&msgbd
);
4400 process_input_message(sip
, msg
);
4407 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
4409 PurpleConnection
*gc
= data
;
4410 struct sipe_account_data
*sip
= gc
->proto_data
;
4415 static char buffer
[65536];
4416 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
4418 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
4419 msg
= sipmsg_parse_msg(buffer
);
4420 if (msg
) process_input_message(sip
, msg
);
4424 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
4426 struct sipe_account_data
*sip
= gc
->proto_data
;
4427 PurpleSslConnection
*gsc
= sip
->gsc
;
4429 purple_debug_error("sipe", "%s",debug
);
4430 purple_connection_error(gc
, msg
);
4432 /* Invalidate this connection. Next send will open a new one */
4434 connection_remove(sip
, gsc
->fd
);
4435 purple_ssl_close(gsc
);
4441 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4443 PurpleConnection
*gc
= data
;
4444 struct sipe_account_data
*sip
;
4445 struct sip_connection
*conn
;
4447 gboolean firstread
= TRUE
;
4449 /* NOTE: This check *IS* necessary */
4450 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
4451 purple_ssl_close(gsc
);
4455 sip
= gc
->proto_data
;
4456 conn
= connection_find(sip
, gsc
->fd
);
4458 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
4459 gc
->wants_to_die
= TRUE
;
4460 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
4464 /* Read all available data from the SSL connection */
4466 /* Increase input buffer size as needed */
4467 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4468 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4469 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4470 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
4473 /* Try to read as much as there is space left in the buffer */
4474 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
4475 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
4477 if (len
< 0 && errno
== EAGAIN
) {
4478 /* Try again later */
4480 } else if (len
< 0) {
4481 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
4483 } else if (firstread
&& (len
== 0)) {
4484 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
4488 conn
->inbufused
+= len
;
4491 /* Equivalence indicates that there is possibly more data to read */
4492 } while (len
== readlen
);
4494 conn
->inbuf
[conn
->inbufused
] = '\0';
4495 process_input(sip
, conn
);
4499 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4501 PurpleConnection
*gc
= data
;
4502 struct sipe_account_data
*sip
= gc
->proto_data
;
4504 struct sip_connection
*conn
= connection_find(sip
, source
);
4506 purple_debug_error("sipe", "Connection not found!\n");
4510 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
4511 conn
->inbuflen
+= SIMPLE_BUF_INC
;
4512 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
4515 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
4517 if (len
< 0 && errno
== EAGAIN
)
4519 else if (len
<= 0) {
4520 purple_debug_info("sipe", "sipe_input_cb: read error\n");
4521 connection_remove(sip
, source
);
4522 if (sip
->fd
== source
) sip
->fd
= -1;
4526 conn
->inbufused
+= len
;
4527 conn
->inbuf
[conn
->inbufused
] = '\0';
4529 process_input(sip
, conn
);
4532 /* Callback for new connections on incoming TCP port */
4533 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
4535 PurpleConnection
*gc
= data
;
4536 struct sipe_account_data
*sip
= gc
->proto_data
;
4537 struct sip_connection
*conn
;
4539 int newfd
= accept(source
, NULL
, NULL
);
4541 conn
= connection_create(sip
, newfd
);
4543 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4546 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
4548 PurpleConnection
*gc
= data
;
4549 struct sipe_account_data
*sip
;
4550 struct sip_connection
*conn
;
4552 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4560 purple_connection_error(gc
, _("Could not connect"));
4564 sip
= gc
->proto_data
;
4566 sip
->last_keepalive
= time(NULL
);
4568 conn
= connection_create(sip
, source
);
4572 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
4575 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
4577 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
4578 if (sip
== NULL
) return;
4583 static guint
sipe_ht_hash_nick(const char *nick
)
4585 char *lc
= g_utf8_strdown(nick
, -1);
4586 guint bucket
= g_str_hash(lc
);
4592 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
4594 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
4597 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
4599 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4601 sip
->listen_data
= NULL
;
4603 if (listenfd
== -1) {
4604 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4610 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
4611 sip
->listenfd
= sip
->fd
;
4613 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
4615 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
4619 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
4621 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4624 sip
->query_data
= NULL
;
4626 if (!hosts
|| !hosts
->data
) {
4627 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
4631 addr_size
= GPOINTER_TO_INT(hosts
->data
);
4632 hosts
= g_slist_remove(hosts
, hosts
->data
);
4633 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
4634 g_free(hosts
->data
);
4635 hosts
= g_slist_remove(hosts
, hosts
->data
);
4637 hosts
= g_slist_remove(hosts
, hosts
->data
);
4638 g_free(hosts
->data
);
4639 hosts
= g_slist_remove(hosts
, hosts
->data
);
4642 /* create socket for incoming connections */
4643 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
4644 sipe_udp_host_resolved_listen_cb
, sip
);
4645 if (sip
->listen_data
== NULL
) {
4646 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4651 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
4654 PurpleConnection
*gc
= data
;
4655 struct sipe_account_data
*sip
;
4657 /* If the connection is already disconnected, we don't need to do anything else */
4658 if (!PURPLE_CONNECTION_IS_VALID(gc
))
4661 sip
= gc
->proto_data
;
4666 case PURPLE_SSL_CONNECT_FAILED
:
4667 purple_connection_error(gc
, _("Connection Failed"));
4669 case PURPLE_SSL_HANDSHAKE_FAILED
:
4670 purple_connection_error(gc
, _("SSL Handshake Failed"));
4672 case PURPLE_SSL_CERTIFICATE_INVALID
:
4673 purple_connection_error(gc
, _("SSL Certificate Invalid"));
4679 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
4681 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
4682 PurpleProxyConnectData
*connect_data
;
4684 sip
->listen_data
= NULL
;
4686 sip
->listenfd
= listenfd
;
4687 if (sip
->listenfd
== -1) {
4688 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
4692 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
4693 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
4694 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
4695 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
4696 sipe_newconn_cb
, sip
->gc
);
4697 purple_debug_info("sipe", "connecting to %s port %d\n",
4698 sip
->realhostname
, sip
->realport
);
4699 /* open tcp connection to the server */
4700 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
4701 sip
->realport
, login_cb
, sip
->gc
);
4703 if (connect_data
== NULL
) {
4704 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
4709 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
4711 PurpleAccount
*account
= sip
->account
;
4712 PurpleConnection
*gc
= sip
->gc
;
4714 if (purple_account_get_bool(account
, "useport", FALSE
)) {
4715 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
4716 port
= purple_account_get_int(account
, "port", 0);
4718 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
4721 sip
->realhostname
= hostname
;
4722 sip
->realport
= port
;
4724 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
4727 /* TODO: is there a good default grow size? */
4728 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
4729 sip
->txbuf
= purple_circ_buffer_new(0);
4731 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
4733 if (!purple_ssl_is_supported()) {
4734 gc
->wants_to_die
= TRUE
;
4735 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
4739 purple_debug_info("sipe", "using SSL\n");
4741 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
4742 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
4743 if (sip
->gsc
== NULL
) {
4744 purple_connection_error(gc
, _("Could not create SSL context"));
4747 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
4749 purple_debug_info("sipe", "using UDP\n");
4751 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
4752 if (sip
->query_data
== NULL
) {
4753 purple_connection_error(gc
, _("Could not resolve hostname"));
4757 purple_debug_info("sipe", "using TCP\n");
4758 /* create socket for incoming connections */
4759 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
4760 sipe_tcp_connect_listen_cb
, sip
);
4761 if (sip
->listen_data
== NULL
) {
4762 purple_connection_error(gc
, _("Could not create listen socket"));
4768 /* Service list for autodection */
4769 static const struct sipe_service_data service_autodetect
[] = {
4770 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4771 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4772 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4773 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4777 /* Service list for SSL/TLS */
4778 static const struct sipe_service_data service_tls
[] = {
4779 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
4780 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
4784 /* Service list for TCP */
4785 static const struct sipe_service_data service_tcp
[] = {
4786 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
4787 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
4791 /* Service list for UDP */
4792 static const struct sipe_service_data service_udp
[] = {
4793 { "sip", "udp", SIPE_TRANSPORT_UDP
},
4797 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
4798 static void resolve_next_service(struct sipe_account_data
*sip
,
4799 const struct sipe_service_data
*start
)
4802 sip
->service_data
= start
;
4804 sip
->service_data
++;
4805 if (sip
->service_data
->service
== NULL
) {
4807 /* Try connecting to the SIP hostname directly */
4808 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
4809 if (sip
->auto_transport
) {
4810 // If SSL is supported, default to using it; OCS servers aren't configured
4811 // by default to accept TCP
4812 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
4813 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
4814 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
4817 hostname
= g_strdup(sip
->sipdomain
);
4818 create_connection(sip
, hostname
, 0);
4823 /* Try to resolve next service */
4824 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
4825 sip
->service_data
->transport
,
4830 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
4832 struct sipe_account_data
*sip
= data
;
4834 sip
->srv_query_data
= NULL
;
4836 /* find the host to connect to */
4838 gchar
*hostname
= g_strdup(resp
->hostname
);
4839 int port
= resp
->port
;
4840 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
4844 sip
->transport
= sip
->service_data
->type
;
4846 create_connection(sip
, hostname
, port
);
4848 resolve_next_service(sip
, NULL
);
4852 static void sipe_login(PurpleAccount
*account
)
4854 PurpleConnection
*gc
;
4855 struct sipe_account_data
*sip
;
4856 gchar
**signinname_login
, **userserver
, **domain_user
;
4857 const char *transport
;
4859 const char *username
= purple_account_get_username(account
);
4860 gc
= purple_account_get_connection(account
);
4862 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
4863 gc
->wants_to_die
= TRUE
;
4864 purple_connection_error(gc
, _("SIP Exchange username contains invalid characters"));
4868 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
4869 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
4870 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
4872 sip
->account
= account
;
4873 sip
->reregister_set
= FALSE
;
4874 sip
->reauthenticate_set
= FALSE
;
4875 sip
->subscribed
= FALSE
;
4876 sip
->subscribed_buddies
= FALSE
;
4878 signinname_login
= g_strsplit(username
, ",", 2);
4880 userserver
= g_strsplit(signinname_login
[0], "@", 2);
4881 purple_connection_set_display_name(gc
, userserver
[0]);
4882 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
4883 sip
->sipdomain
= g_strdup(userserver
[1]);
4885 if (strpbrk(sip
->username
, " \t\v\r\n") != NULL
) {
4886 gc
->wants_to_die
= TRUE
;
4887 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
4891 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
4892 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
4893 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
4895 sip
->password
= g_strdup(purple_connection_get_password(gc
));
4897 g_strfreev(userserver
);
4898 g_strfreev(domain_user
);
4899 g_strfreev(signinname_login
);
4901 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
4903 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
4905 /* TODO: Set the status correctly. */
4906 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
4908 transport
= purple_account_get_string(account
, "transport", "auto");
4909 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
4910 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
4913 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
4914 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
4915 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
4916 } else if (strcmp(transport
, "auto") == 0) {
4917 sip
->auto_transport
= TRUE
;
4918 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
4919 } else if (strcmp(transport
, "tls") == 0) {
4920 resolve_next_service(sip
, service_tls
);
4921 } else if (strcmp(transport
, "tcp") == 0) {
4922 resolve_next_service(sip
, service_tcp
);
4924 resolve_next_service(sip
, service_udp
);
4928 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
4930 connection_free_all(sip
);
4935 if (sip
->query_data
!= NULL
)
4936 purple_dnsquery_destroy(sip
->query_data
);
4937 sip
->query_data
= NULL
;
4939 if (sip
->srv_query_data
!= NULL
)
4940 purple_srv_cancel(sip
->srv_query_data
);
4941 sip
->srv_query_data
= NULL
;
4943 if (sip
->listen_data
!= NULL
)
4944 purple_network_listen_cancel(sip
->listen_data
);
4945 sip
->listen_data
= NULL
;
4947 if (sip
->gsc
!= NULL
)
4948 purple_ssl_close(sip
->gsc
);
4951 sipe_auth_free(&sip
->registrar
);
4952 sipe_auth_free(&sip
->proxy
);
4955 purple_circ_buffer_destroy(sip
->txbuf
);
4958 g_free(sip
->realhostname
);
4959 sip
->realhostname
= NULL
;
4962 purple_input_remove(sip
->listenpa
);
4964 if (sip
->tx_handler
)
4965 purple_input_remove(sip
->tx_handler
);
4966 sip
->tx_handler
= 0;
4967 if (sip
->resendtimeout
)
4968 purple_timeout_remove(sip
->resendtimeout
);
4969 sip
->resendtimeout
= 0;
4970 if (sip
->timeouts
) {
4971 GSList
*entry
= sip
->timeouts
;
4973 struct scheduled_action
*sched_action
= entry
->data
;
4974 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
4975 purple_timeout_remove(sched_action
->timeout_handler
);
4976 g_free(sched_action
->payload
);
4977 g_free(sched_action
->name
);
4978 g_free(sched_action
);
4979 entry
= entry
->next
;
4982 g_slist_free(sip
->timeouts
);
4984 if (sip
->allow_events
) {
4985 GSList
*entry
= sip
->allow_events
;
4987 g_free(entry
->data
);
4988 entry
= entry
->next
;
4991 g_slist_free(sip
->allow_events
);
4994 g_free(sip
->contact
);
4995 sip
->contact
= NULL
;
4997 g_free(sip
->regcallid
);
4998 sip
->regcallid
= NULL
;
5001 sip
->processing_input
= FALSE
;
5005 * A callback for g_hash_table_foreach_remove
5007 static gboolean
sipe_buddy_remove(gpointer key
, struct sipe_buddy
*buddy
, gpointer user_data
)
5009 sipe_free_buddy(buddy
);
5012 static void sipe_close(PurpleConnection
*gc
)
5014 struct sipe_account_data
*sip
= gc
->proto_data
;
5017 /* leave all conversations */
5018 im_session_close_all(sip
);
5021 do_register_exp(sip
, 0);
5023 sipe_connection_cleanup(sip
);
5024 g_free(sip
->sipdomain
);
5025 g_free(sip
->username
);
5026 g_free(sip
->password
);
5027 g_free(sip
->authdomain
);
5028 g_free(sip
->authuser
);
5029 g_free(sip
->status
);
5031 g_hash_table_foreach_remove(sip
->buddies
, (GHRFunc
) sipe_buddy_remove
, NULL
);
5032 g_hash_table_destroy(sip
->buddies
);
5034 g_free(gc
->proto_data
);
5035 gc
->proto_data
= NULL
;
5038 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5040 PurpleAccount
*acct
= purple_connection_get_account(gc
);
5041 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
5042 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
5044 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
5045 purple_conversation_present(conv
);
5049 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5052 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
5053 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
5056 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
5058 PurpleNotifySearchResults
*results
;
5059 PurpleNotifySearchColumn
*column
;
5060 xmlnode
*searchResults
;
5062 int match_count
= 0;
5063 gboolean more
= FALSE
;
5066 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
5068 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5069 if (!searchResults
) {
5070 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5074 results
= purple_notify_searchresults_new();
5076 if (results
== NULL
) {
5077 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5078 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
5080 xmlnode_free(searchResults
);
5084 column
= purple_notify_searchresults_column_new(_("User Name"));
5085 purple_notify_searchresults_column_add(results
, column
);
5087 column
= purple_notify_searchresults_column_new(_("Name"));
5088 purple_notify_searchresults_column_add(results
, column
);
5090 column
= purple_notify_searchresults_column_new(_("Company"));
5091 purple_notify_searchresults_column_add(results
, column
);
5093 column
= purple_notify_searchresults_column_new(_("Country"));
5094 purple_notify_searchresults_column_add(results
, column
);
5096 column
= purple_notify_searchresults_column_new(_("Email"));
5097 purple_notify_searchresults_column_add(results
, column
);
5099 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
5102 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
5103 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
5104 g_strfreev(uri_parts
);
5106 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
5107 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
5108 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
5109 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
5111 purple_notify_searchresults_row_add(results
, row
);
5115 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
5116 char *data
= xmlnode_get_data_unescaped(mrow
);
5117 more
= (g_strcasecmp(data
, "true") == 0);
5121 secondary
= g_strdup_printf(
5122 dngettext(GETTEXT_PACKAGE
,
5123 "Found %d contact%s:",
5124 "Found %d contacts%s:", match_count
),
5125 match_count
, more
? _(" (more matched your query)") : "");
5127 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
5128 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
5129 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
5132 xmlnode_free(searchResults
);
5136 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
5138 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
5139 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
5143 PurpleRequestField
*field
= entries
->data
;
5144 const char *id
= purple_request_field_get_id(field
);
5145 const char *value
= purple_request_field_string_get_value(field
);
5147 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
5149 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
5150 } while ((entries
= g_list_next(entries
)) != NULL
);
5154 gchar
*query
= g_strjoinv(NULL
, attrs
);
5155 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
5156 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
5157 send_soap_request_with_cb(gc
->proto_data
, body
,
5158 (TransCallback
) process_search_contact_response
, NULL
);
5166 static void sipe_show_find_contact(PurplePluginAction
*action
)
5168 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
5169 PurpleRequestFields
*fields
;
5170 PurpleRequestFieldGroup
*group
;
5171 PurpleRequestField
*field
;
5173 fields
= purple_request_fields_new();
5174 group
= purple_request_field_group_new(NULL
);
5175 purple_request_fields_add_group(fields
, group
);
5177 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
5178 purple_request_field_group_add_field(group
, field
);
5179 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
5180 purple_request_field_group_add_field(group
, field
);
5181 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
5182 purple_request_field_group_add_field(group
, field
);
5183 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
5184 purple_request_field_group_add_field(group
, field
);
5186 purple_request_fields(gc
,
5188 _("Search for a Contact"),
5189 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5191 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
5193 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
5196 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
5199 PurplePluginAction
*act
;
5201 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
5202 menu
= g_list_prepend(menu
, act
);
5204 menu
= g_list_reverse(menu
);
5209 static void dummy_permit_deny(PurpleConnection
*gc
)
5213 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
5219 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
5225 static char *sipe_status_text(PurpleBuddy
*buddy
)
5227 struct sipe_account_data
*sip
;
5228 struct sipe_buddy
*sbuddy
;
5231 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5232 if (sip
) //happens on pidgin exit
5234 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5235 if (sbuddy
&& sbuddy
->annotation
)
5237 text
= g_strdup(sbuddy
->annotation
);
5244 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
5246 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
5247 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
5248 struct sipe_account_data
*sip
;
5249 struct sipe_buddy
*sbuddy
;
5250 char *annotation
= NULL
;
5252 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5253 if (sip
) //happens on pidgin exit
5255 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5258 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
5263 if (purple_presence_is_online(presence
))
5265 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
5270 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
5277 sipe_get_account_text_table(PurpleAccount
*account
)
5280 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
5281 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
5285 static PurpleBuddy
*
5286 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
5289 const gchar
*server_alias
, *email
;
5290 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
5292 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
5294 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
5296 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
5298 purple_blist_server_alias_buddy(clone
, server_alias
);
5301 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5303 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
5306 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
5308 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
5313 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
5315 PurpleBuddy
*buddy
, *b
;
5316 PurpleConnection
*gc
;
5317 PurpleGroup
* group
= purple_find_group(group_name
);
5319 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
5321 buddy
= (PurpleBuddy
*)node
;
5323 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
5324 gc
= purple_account_get_connection(buddy
->account
);
5326 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
5328 b
= purple_blist_add_buddy_clone(group
, buddy
);
5331 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
5335 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
5338 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
5340 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
5343 char *mailto
= g_strdup_printf("mailto:%s", email
);
5344 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
5348 char *const parmList
[] = {mailto
, NULL
};
5349 if ((pid
= fork()) == -1)
5351 purple_debug_info("sipe", "fork() error\n");
5355 execvp("xdg-email", parmList
);
5356 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
5364 //@TODO resolve env variable %WINDIR% first
5365 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
5368 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
5377 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
5382 * A menu which appear when right-clicking on buddy in contact list.
5385 sipe_buddy_menu(PurpleBuddy
*buddy
)
5387 PurpleBlistNode
*g_node
;
5388 PurpleGroup
*group
, *gr_parent
;
5389 PurpleMenuAction
*act
;
5391 GList
*menu_groups
= NULL
;
5393 act
= purple_menu_action_new(_("Send Email..."),
5394 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
5396 menu
= g_list_prepend(menu
, act
);
5398 gr_parent
= purple_buddy_get_group(buddy
);
5399 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
5400 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
5403 group
= (PurpleGroup
*)g_node
;
5404 if (group
== gr_parent
)
5407 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
5410 act
= purple_menu_action_new(purple_group_get_name(group
),
5411 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
5413 menu_groups
= g_list_prepend(menu_groups
, act
);
5415 menu_groups
= g_list_reverse(menu_groups
);
5417 act
= purple_menu_action_new(_("Copy to"),
5420 menu
= g_list_prepend(menu
, act
);
5421 menu
= g_list_reverse(menu
);
5426 GList
*sipe_blist_node_menu(PurpleBlistNode
*node
) {
5427 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
5428 return sipe_buddy_menu((PurpleBuddy
*) node
);
5435 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
5437 gboolean ret
= TRUE
;
5438 char *username
= (char *)trans
->payload
;
5440 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
5441 PurpleBuddy
*pbuddy
;
5442 struct sipe_buddy
*sbuddy
;
5444 char *server_alias
= NULL
;
5446 const char *device_name
= NULL
;
5448 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
5450 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
5451 alias
= purple_buddy_get_local_alias(pbuddy
);
5455 //will query buddy UA's capabilities and send answer to log
5456 sipe_options_request(sip
, username
);
5458 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
5461 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
5465 if (msg
->response
!= 200) {
5466 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
5468 xmlnode
*searchResults
;
5471 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
5472 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5473 if (!searchResults
) {
5474 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
5475 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
5476 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
5477 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5478 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
5479 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
5480 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
5481 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
5482 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
5483 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
5484 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
5485 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
5486 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5487 if (!email
|| strcmp("", email
)) {
5488 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
5489 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
5493 xmlnode_free(searchResults
);
5496 purple_notify_user_info_add_section_break(info
);
5498 if (!server_alias
|| !strcmp("", server_alias
)) {
5499 g_free(server_alias
);
5500 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
5502 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
5506 // same as server alias, do not present
5507 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
5510 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
5513 if (!email
|| !strcmp("", email
)) {
5515 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
5517 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
5523 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
5526 /* show a buddy's user info in a nice dialog box */
5527 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
5528 username
, /* buddy's username */
5530 NULL
, /* callback called when dialog closed */
5531 NULL
); /* userdata for callback */
5537 * AD search first, LDAP based
5539 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
5541 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
5542 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
5544 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
5545 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
5546 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
5551 static PurplePlugin
*my_protocol
= NULL
;
5553 static PurplePluginProtocolInfo prpl_info
=
5556 NULL
, /* user_splits */
5557 NULL
, /* protocol_options */
5558 NO_BUDDY_ICONS
, /* icon_spec */
5559 sipe_list_icon
, /* list_icon */
5560 NULL
, /* list_emblems */
5561 sipe_status_text
, /* status_text */
5562 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
5563 sipe_status_types
, /* away_states */
5564 sipe_blist_node_menu
, /* blist_node_menu */
5565 NULL
, /* chat_info */
5566 NULL
, /* chat_info_defaults */
5567 sipe_login
, /* login */
5568 sipe_close
, /* close */
5569 sipe_im_send
, /* send_im */
5570 NULL
, /* set_info */ // TODO maybe
5571 sipe_send_typing
, /* send_typing */
5572 sipe_get_info
, /* get_info */
5573 sipe_set_status
, /* set_status */
5574 NULL
, /* set_idle */
5575 NULL
, /* change_passwd */
5576 sipe_add_buddy
, /* add_buddy */
5577 NULL
, /* add_buddies */
5578 sipe_remove_buddy
, /* remove_buddy */
5579 NULL
, /* remove_buddies */
5580 sipe_add_permit
, /* add_permit */
5581 sipe_add_deny
, /* add_deny */
5582 sipe_add_deny
, /* rem_permit */
5583 sipe_add_permit
, /* rem_deny */
5584 dummy_permit_deny
, /* set_permit_deny */
5585 NULL
, /* join_chat */
5586 NULL
, /* reject_chat */
5587 NULL
, /* get_chat_name */
5588 NULL
, /* chat_invite */
5589 NULL
, /* chat_leave */
5590 NULL
, /* chat_whisper */
5591 NULL
, /* chat_send */
5592 sipe_keep_alive
, /* keepalive */
5593 NULL
, /* register_user */
5594 NULL
, /* get_cb_info */ // deprecated
5595 NULL
, /* get_cb_away */ // deprecated
5596 sipe_alias_buddy
, /* alias_buddy */
5597 sipe_group_buddy
, /* group_buddy */
5598 sipe_rename_group
, /* rename_group */
5599 NULL
, /* buddy_free */
5600 sipe_convo_closed
, /* convo_closed */
5601 purple_normalize_nocase
, /* normalize */
5602 NULL
, /* set_buddy_icon */
5603 sipe_remove_group
, /* remove_group */
5604 NULL
, /* get_cb_real_name */ // TODO?
5605 NULL
, /* set_chat_topic */
5606 NULL
, /* find_blist_chat */
5607 NULL
, /* roomlist_get_list */
5608 NULL
, /* roomlist_cancel */
5609 NULL
, /* roomlist_expand_category */
5610 NULL
, /* can_receive_file */
5611 NULL
, /* send_file */
5612 NULL
, /* new_xfer */
5613 NULL
, /* offline_message */
5614 NULL
, /* whiteboard_prpl_ops */
5615 sipe_send_raw
, /* send_raw */
5616 NULL
, /* roomlist_room_serialize */
5617 NULL
, /* unregister_user */
5618 NULL
, /* send_attention */
5619 NULL
, /* get_attention_types */
5621 sizeof(PurplePluginProtocolInfo
), /* struct_size */
5622 sipe_get_account_text_table
, /* get_account_text_table */
5626 static PurplePluginInfo info
= {
5627 PURPLE_PLUGIN_MAGIC
,
5628 PURPLE_MAJOR_VERSION
,
5629 PURPLE_MINOR_VERSION
,
5630 PURPLE_PLUGIN_PROTOCOL
, /**< type */
5631 NULL
, /**< ui_requirement */
5633 NULL
, /**< dependencies */
5634 PURPLE_PRIORITY_DEFAULT
, /**< priority */
5635 "prpl-sipe", /**< id */
5636 "Microsoft LCS/OCS", /**< name */
5637 VERSION
, /**< version */
5638 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
5639 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
5640 "Anibal Avelar <avelar@gmail.com>, " /**< author */
5641 "Gabriel Burt <gburt@novell.com>", /**< author */
5642 PURPLE_WEBSITE
, /**< homepage */
5643 sipe_plugin_load
, /**< load */
5644 sipe_plugin_unload
, /**< unload */
5645 sipe_plugin_destroy
, /**< destroy */
5646 NULL
, /**< ui_info */
5647 &prpl_info
, /**< extra_info */
5656 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
5660 entry
= prpl_info
.protocol_options
;
5662 purple_account_option_destroy(entry
->data
);
5663 entry
= g_list_delete_link(entry
, entry
);
5665 prpl_info
.protocol_options
= NULL
;
5667 entry
= prpl_info
.user_splits
;
5669 purple_account_user_split_destroy(entry
->data
);
5670 entry
= g_list_delete_link(entry
, entry
);
5672 prpl_info
.user_splits
= NULL
;
5675 static void init_plugin(PurplePlugin
*plugin
)
5677 PurpleAccountUserSplit
*split
;
5678 PurpleAccountOption
*option
;
5681 purple_debug_info(PACKAGE
, "bindtextdomain = %s", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
5682 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s",
5683 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
5686 purple_plugin_register(plugin
);
5688 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
5689 purple_account_user_split_set_reverse(split
, FALSE
);
5690 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
5692 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
5693 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5694 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
5695 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5697 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
5698 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5699 // Translators: noun (networking port)
5700 option
= purple_account_option_int_new(_("Port"), "port", 5061);
5701 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5703 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
5704 purple_account_option_add_list_item(option
, _("Auto"), "auto");
5705 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
5706 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
5707 purple_account_option_add_list_item(option
, _("UDP"), "udp");
5708 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5710 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
5711 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
5713 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
5714 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
5716 // TODO commented out so won't show in the preferences until we fix krb message signing
5717 /*option = purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE);
5718 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5720 // XXX FIXME: Add code to programmatically determine if a KRB REALM is specified in /etc/krb5.conf
5721 option = purple_account_option_string_new(_("Kerberos Realm"), "krb5_realm", "");
5722 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);
5725 my_protocol
= plugin
;
5728 /* I had to redefined the function for it load, but works */
5729 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
5730 plugin
->info
= &(info
);
5731 init_plugin((plugin
));
5732 sipe_plugin_load((plugin
));
5733 return purple_plugin_register(plugin
);