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"
78 #include "sipe-conf.h"
79 #include "sipe-dialog.h"
80 #include "sipe-utils.h"
82 #include "sipe-sign.h"
86 /* Keep in sync with sipe_transport_type! */
87 static const char *transport_descriptor
[] = { "tls", "tcp", "udp" };
88 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
90 /* Status identifiers (see also: sipe_status_types()) */
91 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
92 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
93 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
94 /* PURPLE_STATUS_UNAVAILABLE: */
95 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
96 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
97 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
98 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
99 /* PURPLE_STATUS_AWAY: */
100 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
101 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
102 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
103 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
104 /* ??? PURPLE_STATUS_MOBILE */
105 /* ??? PURPLE_STATUS_TUNE */
107 /* Action name templates */
108 #define ACTION_NAME_PRESENCE "<presence><%s>"
110 static gchar
*get_epid(struct sipe_account_data
*sip
)
113 sip
->epid
= sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
115 return g_strdup(sip
->epid
);
118 static char *genbranch()
120 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
121 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
122 rand() & 0xFFFF, rand() & 0xFFFF);
125 static const char *sipe_list_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
130 static void sipe_plugin_destroy(PurplePlugin
*plugin
);
132 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
);
134 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
135 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
138 static void sipe_close(PurpleConnection
*gc
);
140 static void send_presence_status(struct sipe_account_data
*sip
);
142 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
144 static void sipe_keep_alive(PurpleConnection
*gc
)
146 struct sipe_account_data
*sip
= gc
->proto_data
;
147 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
148 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
149 gchar buf
[2] = {0, 0};
150 purple_debug_info("sipe", "sending keep alive\n");
151 sendto(sip
->fd
, buf
, 1, 0, sip
->serveraddr
, sizeof(struct sockaddr_in
));
153 time_t now
= time(NULL
);
154 if ((sip
->keepalive_timeout
> 0) &&
155 ((guint
) (now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
156 #if PURPLE_VERSION_CHECK(2,4,0)
157 && ((guint
) (now
- gc
->last_received
) >= sip
->keepalive_timeout
)
160 purple_debug_info("sipe", "sending keep alive %d\n",sip
->keepalive_timeout
);
161 sendout_pkt(gc
, "\r\n\r\n");
162 sip
->last_keepalive
= now
;
167 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
169 struct sip_connection
*ret
= NULL
;
170 GSList
*entry
= sip
->openconns
;
173 if (ret
->fd
== fd
) return ret
;
179 static void sipe_auth_free(struct sip_auth
*auth
)
181 g_free(auth
->opaque
);
185 g_free(auth
->target
);
187 auth
->type
= AUTH_TYPE_UNSET
;
190 g_free(auth
->gssapi_data
);
191 auth
->gssapi_data
= NULL
;
192 sip_sec_destroy_context(auth
->gssapi_context
);
193 auth
->gssapi_context
= NULL
;
196 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
198 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
200 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
204 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
206 struct sip_connection
*conn
= connection_find(sip
, fd
);
208 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
209 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
215 static void connection_free_all(struct sipe_account_data
*sip
)
217 struct sip_connection
*ret
= NULL
;
218 GSList
*entry
= sip
->openconns
;
221 connection_remove(sip
, ret
->fd
);
222 entry
= sip
->openconns
;
226 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
229 const char *authuser
= sip
->authuser
;
233 if (!authuser
|| strlen(authuser
) < 1) {
234 authuser
= sip
->username
;
237 if (auth
->type
== AUTH_TYPE_NTLM
|| auth
->type
== AUTH_TYPE_KERBEROS
) { /* NTLM or Kerberos */
238 gchar
*auth_protocol
= (auth
->type
== AUTH_TYPE_NTLM
? "NTLM" : "Kerberos");
240 // If we have a signature for the message, include that
241 if (msg
->signature
) {
242 return g_strdup_printf("%s qop=\"auth\", opaque=\"%s\", realm=\"%s\", targetname=\"%s\", crand=\"%s\", cnum=\"%s\", response=\"%s\"", auth_protocol
, auth
->opaque
, auth
->realm
, auth
->target
, msg
->rand
, msg
->num
, msg
->signature
);
245 if ((auth
->type
== AUTH_TYPE_NTLM
&& auth
->nc
== 3 && auth
->gssapi_data
&& auth
->gssapi_context
== NULL
)
246 || (auth
->type
== AUTH_TYPE_KERBEROS
&& auth
->nc
== 3)) {
250 gssapi_data
= sip_sec_init_context(&(auth
->gssapi_context
),
253 purple_account_get_bool(sip
->account
, "sso", TRUE
),
254 sip
->authdomain
? sip
->authdomain
: "",
259 if (!gssapi_data
|| !auth
->gssapi_context
)
262 opaque
= (auth
->type
== AUTH_TYPE_NTLM
? g_strdup_printf(", opaque=\"%s\"", auth
->opaque
) : g_strdup(""));
263 ret
= g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol
, opaque
, auth
->realm
, auth
->target
, gssapi_data
);
269 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol
, auth
->realm
, auth
->target
);
271 } else { /* Digest */
273 /* Calculate new session key */
275 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Digest nonce: %s realm: %s\n", auth
->gssapi_data
, auth
->realm
);
276 auth
->opaque
= purple_cipher_http_digest_calculate_session_key("md5",
277 authuser
, auth
->realm
, sip
->password
,
278 auth
->gssapi_data
, NULL
);
281 sprintf(noncecount
, "%08d", auth
->nc
++);
282 response
= purple_cipher_http_digest_calculate_response("md5",
283 msg
->method
, msg
->target
, NULL
, NULL
,
284 auth
->gssapi_data
, noncecount
, NULL
,
286 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Digest response %s\n", response
);
288 ret
= g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser
, auth
->realm
, auth
->gssapi_data
, msg
->target
, noncecount
, response
);
294 static char *parse_attribute(const char *attrname
, const char *source
)
296 const char *tmp
, *tmp2
;
298 int len
= strlen(attrname
);
300 if (!strncmp(source
, attrname
, len
)) {
302 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
304 retval
= g_strndup(tmp
, tmp2
- tmp
);
306 retval
= g_strdup(tmp
);
312 static void fill_auth(struct sipe_account_data
*sip
, gchar
*hdr
, struct sip_auth
*auth
)
318 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
322 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
323 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type NTLM\n");
324 auth
->type
= AUTH_TYPE_NTLM
;
327 } else if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
328 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type Kerberos\n");
329 auth
->type
= AUTH_TYPE_KERBEROS
;
333 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type Digest\n");
334 auth
->type
= AUTH_TYPE_DIGEST
;
338 parts
= g_strsplit(hdr
, "\", ", 0);
339 for (i
= 0; parts
[i
]; i
++) {
342 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
344 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
345 g_free(auth
->gssapi_data
);
346 auth
->gssapi_data
= tmp
;
348 if (auth
->type
== AUTH_TYPE_NTLM
) {
349 /* NTLM module extracts nonce from gssapi-data */
353 } else if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
354 /* Only used with AUTH_TYPE_DIGEST */
355 g_free(auth
->gssapi_data
);
356 auth
->gssapi_data
= tmp
;
357 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
358 g_free(auth
->opaque
);
360 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
364 if (auth
->type
== AUTH_TYPE_DIGEST
) {
365 /* Throw away old session key */
366 g_free(auth
->opaque
);
371 } else if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
372 g_free(auth
->target
);
381 static void sipe_canwrite_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
383 PurpleConnection
*gc
= data
;
384 struct sipe_account_data
*sip
= gc
->proto_data
;
388 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
390 if (max_write
== 0) {
391 if (sip
->tx_handler
!= 0){
392 purple_input_remove(sip
->tx_handler
);
398 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
400 if (written
< 0 && errno
== EAGAIN
)
402 else if (written
<= 0) {
403 /*TODO: do we really want to disconnect on a failure to write?*/
404 purple_connection_error(gc
, _("Could not write"));
408 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
411 static void sipe_canwrite_cb_ssl(gpointer data
, gint src
, PurpleInputCondition cond
)
413 PurpleConnection
*gc
= data
;
414 struct sipe_account_data
*sip
= gc
->proto_data
;
418 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
420 if (max_write
== 0) {
421 if (sip
->tx_handler
!= 0) {
422 purple_input_remove(sip
->tx_handler
);
428 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
430 if (written
< 0 && errno
== EAGAIN
)
432 else if (written
<= 0) {
433 /*TODO: do we really want to disconnect on a failure to write?*/
434 purple_connection_error(gc
, _("Could not write"));
438 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
441 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
443 static void send_later_cb(gpointer data
, gint source
, const gchar
*error
)
445 PurpleConnection
*gc
= data
;
446 struct sipe_account_data
*sip
;
447 struct sip_connection
*conn
;
449 if (!PURPLE_CONNECTION_IS_VALID(gc
))
457 purple_connection_error(gc
, _("Could not connect"));
461 sip
= gc
->proto_data
;
463 sip
->connecting
= FALSE
;
464 sip
->last_keepalive
= time(NULL
);
466 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
468 /* If there is more to write now, we need to register a handler */
469 if (sip
->txbuf
->bufused
> 0)
470 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
472 conn
= connection_create(sip
, source
);
473 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
476 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
478 struct sipe_account_data
*sip
;
479 struct sip_connection
*conn
;
481 if (!PURPLE_CONNECTION_IS_VALID(gc
))
483 if (gsc
) purple_ssl_close(gsc
);
487 sip
= gc
->proto_data
;
490 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
491 sip
->connecting
= FALSE
;
492 sip
->last_keepalive
= time(NULL
);
494 conn
= connection_create(sip
, gsc
->fd
);
496 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
501 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
503 PurpleConnection
*gc
= data
;
504 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
505 if (sip
== NULL
) return;
507 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
509 /* If there is more to write now */
510 if (sip
->txbuf
->bufused
> 0) {
511 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
516 static void sendlater(PurpleConnection
*gc
, const char *buf
)
518 struct sipe_account_data
*sip
= gc
->proto_data
;
520 if (!sip
->connecting
) {
521 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
522 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
523 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
525 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
526 purple_connection_error(gc
, _("Couldn't create socket"));
529 sip
->connecting
= TRUE
;
532 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
533 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
535 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
538 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
540 struct sipe_account_data
*sip
= gc
->proto_data
;
541 time_t currtime
= time(NULL
);
542 int writelen
= strlen(buf
);
544 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
545 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
546 if (sendto(sip
->fd
, buf
, writelen
, 0, sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
547 purple_debug_info("sipe", "could not send packet\n");
556 if (sip
->tx_handler
) {
561 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
563 ret
= write(sip
->fd
, buf
, writelen
);
567 if (ret
< 0 && errno
== EAGAIN
)
569 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
574 if (ret
< writelen
) {
575 if (!sip
->tx_handler
){
577 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
580 sip
->tx_handler
= purple_input_add(sip
->fd
,
581 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
586 /* XXX: is it OK to do this? You might get part of a request sent
587 with part of another. */
588 if (sip
->txbuf
->bufused
> 0)
589 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
591 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
597 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
599 sendout_pkt(gc
, buf
);
603 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
605 GSList
*tmp
= msg
->headers
;
608 GString
*outstr
= g_string_new("");
609 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
611 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
612 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
613 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
614 tmp
= g_slist_next(tmp
);
616 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
617 sendout_pkt(sip
->gc
, outstr
->str
);
618 g_string_free(outstr
, TRUE
);
621 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
624 if (sip
->registrar
.gssapi_context
) {
625 struct sipmsg_breakdown msgbd
;
626 gchar
*signature_input_str
;
628 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
629 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
630 sip
->registrar
.ntlm_num
++;
631 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
632 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
633 if (signature_input_str
!= NULL
) {
634 char *signature_hex
= sip_sec_make_signature(sip
->registrar
.gssapi_context
, signature_input_str
);
635 msg
->signature
= signature_hex
;
636 msg
->rand
= g_strdup(msgbd
.rand
);
637 msg
->num
= g_strdup(msgbd
.num
);
638 g_free(signature_input_str
);
640 sipmsg_breakdown_free(&msgbd
);
643 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
644 buf
= auth_header(sip
, &sip
->registrar
, msg
);
645 sipmsg_add_header_now_pos(msg
, "Authorization", buf
, 5);
647 } 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") || !strcmp(method
, "REFER")) {
648 sip
->registrar
.nc
= 3;
650 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
652 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
655 sip
->registrar
.type
= AUTH_TYPE_KERBEROS
;
660 buf
= auth_header(sip
, &sip
->registrar
, msg
);
661 sipmsg_add_header_now_pos(msg
, "Proxy-Authorization", buf
, 5);
664 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
670 static char *get_contact_service(struct sipe_account_data *sip)
672 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()));
673 //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);
677 void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
678 const char *text
, const char *body
)
682 GString
*outstr
= g_string_new("");
683 struct sipe_account_data
*sip
= gc
->proto_data
;
686 const gchar
*keepers
[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL
};
688 contact
= get_contact(sip
);
689 sipmsg_add_header(msg
, "Contact", contact
);
694 sprintf(len
, "%" G_GSIZE_FORMAT
, (gsize
) strlen(body
));
695 sipmsg_add_header(msg
, "Content-Length", len
);
697 sipmsg_add_header(msg
, "Content-Length", "0");
700 msg
->response
= code
;
702 sipmsg_strip_headers(msg
, keepers
);
703 sipmsg_merge_new_headers(msg
);
704 sign_outgoing_message(msg
, sip
, msg
->method
);
706 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
709 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
710 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
712 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
713 tmp
= g_slist_next(tmp
);
715 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
716 sendout_pkt(gc
, outstr
->str
);
717 g_string_free(outstr
, TRUE
);
720 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
722 if (trans
->msg
) sipmsg_free(trans
->msg
);
723 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
727 static struct transaction
*
728 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
730 struct transaction
*trans
= g_new0(struct transaction
, 1);
731 trans
->time
= time(NULL
);
732 trans
->msg
= (struct sipmsg
*)msg
;
733 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
734 trans
->callback
= callback
;
735 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
739 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
741 struct transaction
*trans
;
742 GSList
*transactions
= sip
->transactions
;
743 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
745 while (transactions
) {
746 trans
= transactions
->data
;
747 if (!strcmp(trans
->cseq
, cseq
)) {
750 transactions
= transactions
->next
;
757 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
758 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
759 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
761 struct sipe_account_data
*sip
= gc
->proto_data
;
762 const char *addh
= "";
765 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
766 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
767 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
768 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
769 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
770 gchar
*useragent
= (gchar
*)purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
);
771 gchar
*route
= strdup("");
772 gchar
*epid
= get_epid(sip
); // TODO generate one per account/login
773 int cseq
= dialog
? ++dialog
->cseq
:
774 /* This breaks OCS2007: own presence, contact search, ?
775 1 .* as Call-Id is new in this case */
777 struct transaction
*trans
;
779 if (dialog
&& dialog
->routes
)
781 GSList
*iter
= dialog
->routes
;
786 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
788 iter
= g_slist_next(iter
);
792 if (!ourtag
&& !dialog
) {
796 if (!strcmp(method
, "REGISTER")) {
797 if (sip
->regcallid
) {
799 callid
= g_strdup(sip
->regcallid
);
801 sip
->regcallid
= g_strdup(callid
);
805 if (addheaders
) addh
= addheaders
;
807 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
808 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
809 "From: <sip:%s>%s%s;epid=%s\r\n"
810 "To: <%s>%s%s%s%s\r\n"
811 "Max-Forwards: 70\r\n"
816 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
818 dialog
&& dialog
->request
? dialog
->request
: url
,
819 TRANSPORT_DESCRIPTOR
,
820 purple_network_get_my_ip(-1),
822 branch
? ";branch=" : "",
823 branch
? branch
: "",
825 ourtag
? ";tag=" : "",
826 ourtag
? ourtag
: "",
829 theirtag
? ";tag=" : "",
830 theirtag
? theirtag
: "",
831 theirepid
? ";epid=" : "",
832 theirepid
? theirepid
: "",
839 body
? (gsize
) strlen(body
) : 0,
843 //printf ("parsing msg buf:\n%s\n\n", buf);
844 msg
= sipmsg_parse_msg(buf
);
855 sign_outgoing_message (msg
, sip
, method
);
857 buf
= sipmsg_to_string (msg
);
859 /* add to ongoing transactions */
860 trans
= transactions_add_buf(sip
, msg
, tc
);
861 sendout_pkt(gc
, buf
);
867 static void send_soap_request_with_cb(struct sipe_account_data
*sip
, gchar
*body
, TransCallback callback
, void * payload
)
869 gchar
*from
= g_strdup_printf("sip:%s", sip
->username
);
870 gchar
*contact
= get_contact(sip
);
871 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
872 "Content-Type: application/SOAP+xml\r\n",contact
);
874 struct transaction
* tr
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
875 tr
->payload
= payload
;
882 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
884 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
887 static char *get_contact_register(struct sipe_account_data
*sip
)
889 char *epid
= get_epid(sip
);
890 char *uuid
= generateUUIDfromEPID(epid
);
891 char *buf
= g_strdup_printf("<sip:%s:%d;transport=%s;ms-opaque=d3470f2e1d>;methods=\"INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY\";proxy=replace;+sip.instance=\"<urn:uuid:%s>\"", purple_network_get_my_ip(-1), sip
->listenport
, TRANSPORT_DESCRIPTOR
, uuid
);
897 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
899 char *expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
900 char *uri
= g_strdup_printf("sip:%s", sip
->sipdomain
);
901 char *to
= g_strdup_printf("sip:%s", sip
->username
);
902 char *contact
= get_contact_register(sip
);
903 char *hdr
= g_strdup_printf("Contact: %s\r\n"
904 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
905 "Event: registration\r\n"
906 "Allow-Events: presence\r\n"
907 "ms-keep-alive: UAC;hop-hop=yes\r\n"
908 "%s", contact
, expires
);
912 sip
->registerstatus
= 1;
914 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
915 process_register_response
);
922 static void do_register_cb(struct sipe_account_data
*sip
, void *unused
)
924 do_register_exp(sip
, -1);
925 sip
->reregister_set
= FALSE
;
928 static void do_register(struct sipe_account_data
*sip
)
930 do_register_exp(sip
, -1);
933 static xmlnode
* xmlnode_get_descendant(xmlnode
* parent
, ...)
936 xmlnode
* node
= NULL
;
939 va_start(args
, parent
);
940 while ((name
= va_arg(args
, const char *)) != NULL
) {
941 node
= xmlnode_get_child(parent
, name
);
942 if (node
== NULL
) return NULL
;
952 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
954 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
955 send_soap_request(sip
, body
);
960 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
963 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
965 purple_debug_info("sipe", "Blocking contact %s\n", who
);
968 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
972 void sipe_auth_user_cb(void * data
)
974 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
977 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
982 void sipe_deny_user_cb(void * data
)
984 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
987 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
992 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
994 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
995 sipe_contact_allow_deny(sip
, name
, TRUE
);
999 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1001 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1002 sipe_contact_allow_deny(sip
, name
, FALSE
);
1006 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1008 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1009 sipe_contact_set_acl(sip, name, "");
1013 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1017 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1018 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1020 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1022 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1023 if (!watchers
) return;
1025 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1026 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1027 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1028 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1030 // TODO pull out optional displayName to pass as alias
1032 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1033 job
->who
= remote_user
;
1035 purple_account_request_authorization(
1049 xmlnode_free(watchers
);
1054 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1056 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1057 if (!purple_group
) {
1058 purple_group
= purple_group_new(group
->name
);
1059 purple_blist_add_group(purple_group
, NULL
);
1063 group
->purple_group
= purple_group
;
1064 sip
->groups
= g_slist_append(sip
->groups
, group
);
1065 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1067 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1071 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1073 struct sipe_group
*group
;
1079 entry
= sip
->groups
;
1081 group
= entry
->data
;
1082 if (group
->id
== id
) {
1085 entry
= entry
->next
;
1090 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, const gchar
* name
)
1092 struct sipe_group
*group
;
1098 entry
= sip
->groups
;
1100 group
= entry
->data
;
1101 if (!strcmp(group
->name
, name
)) {
1104 entry
= entry
->next
;
1110 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1113 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1114 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1115 send_soap_request(sip
, body
);
1117 g_free(group
->name
);
1118 group
->name
= g_strdup(name
);
1122 * Only appends if no such value already stored.
1125 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1126 GSList
* res
= list
;
1127 if (!g_slist_find_custom(list
, data
, func
)) {
1128 res
= g_slist_insert_sorted(list
, data
, func
);
1134 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1135 return group1
->id
- group2
->id
;
1139 * Returns string like "2 4 7 8" - group ids buddy belong to.
1142 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1145 //creating array from GList, converting int to gchar*
1146 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1147 GSList
*entry
= buddy
->groups
;
1149 struct sipe_group
* group
= entry
->data
;
1150 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1151 entry
= entry
->next
;
1155 res
= g_strjoinv(" ", ids_arr
);
1156 g_strfreev(ids_arr
);
1161 * Sends buddy update to server
1164 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1166 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1167 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1169 if (buddy
&& purple_buddy
) {
1170 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1172 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1173 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1175 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1176 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1178 send_soap_request(sip
, body
);
1184 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1186 if (msg
->response
== 200) {
1187 struct sipe_group
*group
;
1188 struct group_user_context
*ctx
= (struct group_user_context
*)tc
->payload
;
1192 struct sipe_buddy
*buddy
;
1194 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1200 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1207 group_id
= xmlnode_get_data(node
);
1214 group
= g_new0(struct sipe_group
, 1);
1215 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1217 group
->name
= ctx
->group_name
;
1219 sipe_group_add(sip
, group
);
1221 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1223 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1226 sipe_group_set_user(sip
, ctx
->user_name
);
1235 static void sipe_group_create (struct sipe_account_data
*sip
, gchar
*name
, gchar
* who
)
1237 struct group_user_context
* ctx
= g_new0(struct group_user_context
, 1);
1239 ctx
->group_name
= g_strdup(name
);
1240 ctx
->user_name
= g_strdup(who
);
1242 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1243 send_soap_request_with_cb(sip
, body
, process_add_group_response
, ctx
);
1248 * Data structure for scheduled actions
1250 typedef void (*Action
) (struct sipe_account_data
*, void *);
1252 struct scheduled_action
{
1255 * Format is <Event>[<Data>...]
1256 * Example: <presence><sip:user@domain.com> or <registration>
1259 guint timeout_handler
;
1260 gboolean repetitive
;
1262 GDestroyNotify destroy
;
1263 struct sipe_account_data
*sip
;
1269 * Should return FALSE if repetitive action is not needed
1271 static gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1274 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1275 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1276 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1277 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1278 ret
= sched_action
->repetitive
;
1279 (*sched_action
->destroy
)(sched_action
->payload
);
1280 g_free(sched_action
->name
);
1281 g_free(sched_action
);
1286 * Kills action timer effectively cancelling
1289 * @param name of action
1291 static void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, const gchar
*name
)
1295 if (!sip
->timeouts
|| !name
) return;
1297 entry
= sip
->timeouts
;
1299 struct scheduled_action
*sched_action
= entry
->data
;
1300 if(!strcmp(sched_action
->name
, name
)) {
1301 GSList
*to_delete
= entry
;
1302 entry
= entry
->next
;
1303 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1304 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1305 purple_timeout_remove(sched_action
->timeout_handler
);
1306 (*sched_action
->destroy
)(sched_action
->payload
);
1307 g_free(sched_action
->name
);
1308 g_free(sched_action
);
1310 entry
= entry
->next
;
1316 sipe_schedule_action0(const gchar
*name
,
1320 GDestroyNotify destroy
,
1321 struct sipe_account_data
*sip
,
1324 struct scheduled_action
*sched_action
;
1326 /* Make sure each action only exists once */
1327 sipe_cancel_scheduled_action(sip
, name
);
1329 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name
, timeout
, isSeconds
? "sec" : "msec");
1330 sched_action
= g_new0(struct scheduled_action
, 1);
1331 sched_action
->repetitive
= FALSE
;
1332 sched_action
->name
= g_strdup(name
);
1333 sched_action
->action
= action
;
1334 sched_action
->destroy
= destroy
? destroy
: g_free
;
1335 sched_action
->sip
= sip
;
1336 sched_action
->payload
= payload
;
1337 sched_action
->timeout_handler
= isSeconds
? purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
) :
1338 purple_timeout_add(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1339 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1340 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1344 * Do schedule action for execution in the future.
1345 * Non repetitive execution.
1347 * @param name of action (will be copied)
1348 * @param timeout in seconds
1349 * @action callback function
1350 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1353 sipe_schedule_action(const gchar
*name
,
1356 GDestroyNotify destroy
,
1357 struct sipe_account_data
*sip
,
1360 sipe_schedule_action0(name
, timeout
, TRUE
, action
, destroy
, sip
, payload
);
1364 * Same as sipe_schedule_action() but timeout is in milliseconds.
1367 sipe_schedule_action_msec(const gchar
*name
,
1370 GDestroyNotify destroy
,
1371 struct sipe_account_data
*sip
,
1374 sipe_schedule_action0(name
, timeout
, FALSE
, action
, destroy
, sip
, payload
);
1378 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1380 gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1382 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1384 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1389 static void sipe_subscribe_resource_uri(const char *name
, gpointer value
, gchar
**resources_uri
)
1391 gchar
*tmp
= *resources_uri
;
1392 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1396 static void sipe_subscribe_resource_uri_with_context(const char *name
, gpointer value
, gchar
**resources_uri
)
1398 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
1399 if (sbuddy
&& !sbuddy
->resubscribed
) { // Only not resubscribed contacts; the first time everybody are included
1400 gchar
*tmp
= *resources_uri
;
1401 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp
, name
);
1407 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1408 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1409 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1410 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1411 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1414 static void sipe_subscribe_presence_batched_to(struct sipe_account_data
*sip
, gchar
*resources_uri
, gchar
*to
)
1416 gchar
*contact
= get_contact(sip
);
1419 gchar
*require
= "";
1421 gchar
*autoextend
= "";
1422 gchar
*content_type
;
1424 if (sip
->msrtc_event_categories
) {
1425 require
= ", categoryList";
1426 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1427 content_type
= "application/msrtc-adrl-categorylist+xml";
1428 content
= g_strdup_printf(
1429 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1430 "<action name=\"subscribe\" id=\"63792024\">\n"
1431 "<adhocList>\n%s</adhocList>\n"
1432 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1433 "<category name=\"note\"/>\n"
1434 "<category name=\"state\"/>\n"
1437 "</batchSub>", sip
->username
, resources_uri
);
1439 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1440 content_type
= "application/adrl+xml";
1441 content
= g_strdup_printf(
1442 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1443 "<create xmlns=\"\">\n%s</create>\n"
1444 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1446 g_free(resources_uri
);
1448 request
= g_strdup_printf(
1449 "Require: adhoclist%s\r\n"
1450 "Supported: eventlist\r\n"
1451 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1452 "Supported: ms-piggyback-first-notify\r\n"
1453 "%sSupported: ms-benotify\r\n"
1454 "Proxy-Require: ms-benotify\r\n"
1455 "Event: presence\r\n"
1456 "Content-Type: %s\r\n"
1457 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
1460 /* subscribe to buddy presence */
1461 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1462 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1469 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
, void *unused
)
1471 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
1472 gchar
*resources_uri
= g_strdup("");
1473 if (sip
->msrtc_event_categories
) {
1474 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
1476 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1478 sipe_subscribe_presence_batched_to(sip
, resources_uri
, to
);
1481 struct presence_batched_routed
{
1486 static void sipe_subscribe_presence_batched_routed_free(void *payload
)
1488 struct presence_batched_routed
*data
= payload
;
1489 GSList
*buddies
= data
->buddies
;
1491 g_free(buddies
->data
);
1492 buddies
= buddies
->next
;
1494 g_slist_free(data
->buddies
);
1499 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data
*sip
, void *payload
)
1501 struct presence_batched_routed
*data
= payload
;
1502 GSList
*buddies
= data
->buddies
;
1503 gchar
*resources_uri
= g_strdup("");
1505 gchar
*tmp
= resources_uri
;
1506 resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, (char *) buddies
->data
);
1508 buddies
= buddies
->next
;
1510 sipe_subscribe_presence_batched_to(sip
, resources_uri
,
1511 g_strdup(data
->host
));
1515 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1516 * The user sends a single SUBSCRIBE request to the subscribed contact.
1517 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1521 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, void *buddy_name
)
1523 gchar
*to
= strstr((char *)buddy_name
, "sip:") ? g_strdup((char *)buddy_name
) : g_strdup_printf("sip:%s", (char *)buddy_name
);
1524 gchar
*tmp
= get_contact(sip
);
1527 gchar
*autoextend
= "";
1529 if (!sip
->msrtc_event_categories
)
1530 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1532 request
= g_strdup_printf(
1533 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1534 "Supported: ms-piggyback-first-notify\r\n"
1535 "%sSupported: ms-benotify\r\n"
1536 "Proxy-Require: ms-benotify\r\n"
1537 "Event: presence\r\n"
1538 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1539 "Contact: %s\r\n", autoextend
,tmp
);
1541 content
= g_strdup_printf(
1542 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1543 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1544 "<resource uri=\"%s\"/>\n"
1546 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1547 "<category name=\"note\"/>\n"
1548 "<category name=\"state\"/>\n"
1551 "</batchSub>", sip
->username
, to
1556 /* subscribe to buddy presence */
1557 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1564 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
1566 if (!purple_status_is_active(status
))
1570 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
1573 g_free(sip
->status
);
1574 sip
->status
= g_strdup(purple_status_get_id(status
));
1575 send_presence_status(sip
);
1581 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
, const char *alias
)
1583 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1584 sipe_group_set_user(sip
, name
);
1588 sipe_group_buddy(PurpleConnection
*gc
,
1590 const char *old_group_name
,
1591 const char *new_group_name
)
1593 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1594 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1595 struct sipe_group
* old_group
= NULL
;
1596 struct sipe_group
* new_group
;
1598 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1599 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
1601 if(!buddy
) { // buddy not in roaming list
1605 if (old_group_name
) {
1606 old_group
= sipe_group_find_by_name(sip
, g_strdup(old_group_name
));
1608 new_group
= sipe_group_find_by_name(sip
, g_strdup(new_group_name
));
1611 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
1612 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
1616 sipe_group_create(sip
, g_strdup(new_group_name
), g_strdup(who
));
1618 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
1619 sipe_group_set_user(sip
, who
);
1623 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1625 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1626 struct sipe_buddy
*b
;
1628 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1630 // Prepend sip: if needed
1631 if (strncmp("sip:", buddy
->name
, 4)) {
1632 gchar
*buf
= g_strdup_printf("sip:%s", buddy
->name
);
1633 purple_blist_rename_buddy(buddy
, buf
);
1637 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
1638 b
= g_new0(struct sipe_buddy
, 1);
1639 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy
->name
);
1640 b
->name
= g_strdup(buddy
->name
);
1641 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
1642 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
1643 sipe_subscribe_presence_single(sip
, b
->name
); //@TODO should go to callback
1645 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy
->name
);
1649 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
1653 * We are calling g_hash_table_foreach_steal(). That means that no
1654 * key/value deallocation functions are called. Therefore the glib
1655 * hash code does not touch the key (buddy->name) or value (buddy)
1656 * of the to-be-deleted hash node at all. It follows that we
1658 * - MUST free the memory for the key ourselves and
1659 * - ARE allowed to do it in this function
1661 * Conclusion: glib must be broken on the Windows platform if sipe
1662 * crashes with SIGTRAP when closing. You'll have to live
1663 * with the memory leak until this is fixed.
1665 g_free(buddy
->name
);
1667 g_free(buddy
->annotation
);
1668 g_free(buddy
->device_name
);
1669 g_slist_free(buddy
->groups
);
1674 * Unassociates buddy from group first.
1675 * Then see if no groups left, removes buddy completely.
1676 * Otherwise updates buddy groups on server.
1678 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1680 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1681 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
1682 struct sipe_group
*g
= NULL
;
1684 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1689 g
= sipe_group_find_by_name(sip
, group
->name
);
1693 b
->groups
= g_slist_remove(b
->groups
, g
);
1694 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
1697 if (g_slist_length(b
->groups
) < 1) {
1698 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
1699 sipe_cancel_scheduled_action(sip
, action_name
);
1700 g_free(action_name
);
1702 g_hash_table_remove(sip
->buddies
, buddy
->name
);
1705 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
1706 send_soap_request(sip
, body
);
1712 //updates groups on server
1713 sipe_group_set_user(sip
, b
->name
);
1719 sipe_rename_group(PurpleConnection
*gc
,
1720 const char *old_name
,
1722 GList
*moved_buddies
)
1724 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1725 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, g_strdup(old_name
));
1727 sipe_group_rename(sip
, s_group
, group
->name
);
1729 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
1734 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
1736 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1737 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
1740 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
1741 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
1742 send_soap_request(sip
, body
);
1745 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
1746 g_free(s_group
->name
);
1749 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
1753 static GList
*sipe_status_types(PurpleAccount
*acc
)
1755 PurpleStatusType
*type
;
1756 GList
*types
= NULL
;
1759 type
= purple_status_type_new_with_attrs(
1760 PURPLE_STATUS_AVAILABLE
, NULL
, _("Online"), TRUE
, TRUE
, FALSE
,
1761 // Translators: noun
1762 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1764 types
= g_list_append(types
, type
);
1767 type
= purple_status_type_new_with_attrs(
1768 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_BUSY
, _("Busy"), TRUE
, TRUE
, FALSE
,
1769 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1771 types
= g_list_append(types
, type
);
1773 // Do Not Disturb (not user settable)
1774 type
= purple_status_type_new_with_attrs(
1775 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_DND
, _("Do Not Disturb"), TRUE
, FALSE
, FALSE
,
1776 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1778 types
= g_list_append(types
, type
);
1781 type
= purple_status_type_new_with_attrs(
1782 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_BRB
, _("Be Right Back"), TRUE
, TRUE
, FALSE
,
1783 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1785 types
= g_list_append(types
, type
);
1788 type
= purple_status_type_new_with_attrs(
1789 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
1790 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1792 types
= g_list_append(types
, type
);
1795 type
= purple_status_type_new_with_attrs(
1796 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_ONPHONE
, _("On The Phone"), TRUE
, TRUE
, FALSE
,
1797 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1799 types
= g_list_append(types
, type
);
1802 type
= purple_status_type_new_with_attrs(
1803 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_LUNCH
, _("Out To Lunch"), TRUE
, TRUE
, FALSE
,
1804 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1806 types
= g_list_append(types
, type
);
1809 type
= purple_status_type_new_full(
1810 PURPLE_STATUS_INVISIBLE
, NULL
, _("Appear Offline"), TRUE
, TRUE
, FALSE
);
1811 types
= g_list_append(types
, type
);
1814 type
= purple_status_type_new_full(
1815 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
1816 types
= g_list_append(types
, type
);
1822 * A callback for g_hash_table_foreach
1824 static void sipe_buddy_subscribe_cb(char *name
, struct sipe_buddy
*buddy
, struct sipe_account_data
*sip
)
1826 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
1827 int time_range
= (g_hash_table_size(sip
->buddies
) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1828 int timeout
= (time_range
* rand()) / RAND_MAX
; /* random period within the range */
1829 sipe_schedule_action_msec(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, buddy
->name
);
1833 * Removes entries from purple buddy list
1834 * that does not correspond ones in the roaming contact list.
1836 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
1837 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
1838 GSList
*entry
= buddies
;
1839 struct sipe_buddy
*buddy
;
1843 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
1844 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
1847 g
= purple_buddy_get_group(b
);
1848 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
1850 gboolean in_sipe_groups
= FALSE
;
1851 GSList
*entry2
= buddy
->groups
;
1853 struct sipe_group
*group
= entry2
->data
;
1854 if (!strcmp(group
->name
, g
->name
)) {
1855 in_sipe_groups
= TRUE
;
1858 entry2
= entry2
->next
;
1860 if(!in_sipe_groups
) {
1861 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
1862 purple_blist_remove_buddy(b
);
1865 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
1866 purple_blist_remove_buddy(b
);
1868 entry
= entry
->next
;
1872 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1874 int len
= msg
->bodylen
;
1876 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
1879 const gchar
*contacts_delta
;
1880 xmlnode
*group_node
;
1881 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
1885 /* Convert the contact from XML to Purple Buddies */
1886 isc
= xmlnode_from_str(msg
->body
, len
);
1891 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
1892 if (contacts_delta
) {
1893 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
1896 if (!strcmp(isc
->name
, "contactList")) {
1899 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
1900 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1901 const char *name
= xmlnode_get_attrib(group_node
, "name");
1903 if (!strncmp(name
, "~", 1)) {
1904 name
= _("Other Contacts");
1906 group
->name
= g_strdup(name
);
1907 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
1909 sipe_group_add(sip
, group
);
1912 // Make sure we have at least one group
1913 if (g_slist_length(sip
->groups
) == 0) {
1914 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1915 PurpleGroup
*purple_group
;
1916 group
->name
= g_strdup(_("Other Contacts"));
1918 purple_group
= purple_group_new(group
->name
);
1919 purple_blist_add_group(purple_group
, NULL
);
1920 sip
->groups
= g_slist_append(sip
->groups
, group
);
1923 /* Parse contacts */
1924 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
1925 gchar
* uri
= g_strdup(xmlnode_get_attrib(item
, "uri"));
1926 gchar
* name
= g_strdup(xmlnode_get_attrib(item
, "name"));
1927 gchar
* groups
= g_strdup(xmlnode_get_attrib(item
, "groups"));
1928 gchar
* buddy_name
= g_strdup_printf("sip:%s", uri
);
1929 gchar
**item_groups
;
1930 struct sipe_group
*group
= NULL
;
1931 struct sipe_buddy
*buddy
= NULL
;
1934 // assign to group Other Contacts if nothing else received
1935 if(!groups
|| !strcmp("", groups
) ) {
1936 group
= sipe_group_find_by_name(sip
, _("Other Contacts"));
1937 groups
= group
? g_strdup_printf("%d", group
->id
) : "1";
1940 item_groups
= g_strsplit(groups
, " ", 0);
1942 while (item_groups
[i
]) {
1943 group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
1945 // If couldn't find the right group for this contact, just put them in the first group we have
1946 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
1947 group
= sip
->groups
->data
;
1950 if (group
!= NULL
) {
1951 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
1953 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
1954 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
1957 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
1958 if (name
!= NULL
&& strlen(name
) != 0) {
1959 purple_blist_alias_buddy(b
, name
);
1964 buddy
= g_new0(struct sipe_buddy
, 1);
1965 buddy
->name
= g_strdup(b
->name
);
1966 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
1969 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1971 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
1973 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
1978 } // while, contact groups
1979 g_strfreev(item_groups
);
1987 sipe_cleanup_local_blist(sip
);
1991 //subscribe to buddies
1992 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
1993 if(sip
->batched_support
){
1994 sipe_subscribe_presence_batched(sip
, NULL
);
1997 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
1999 sip
->subscribed_buddies
= TRUE
;
2006 * Subscribe roaming contacts
2008 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2010 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2011 gchar
*tmp
= get_contact(sip
);
2012 gchar
*hdr
= g_strdup_printf(
2013 "Event: vnd-microsoft-roaming-contacts\r\n"
2014 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2015 "Supported: com.microsoft.autoextend\r\n"
2016 "Supported: ms-benotify\r\n"
2017 "Proxy-Require: ms-benotify\r\n"
2018 "Supported: ms-piggyback-first-notify\r\n"
2019 "Contact: %s\r\n", tmp
);
2022 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2027 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
, void *unused
)
2029 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2030 gchar
*tmp
= get_contact(sip
);
2031 gchar
*hdr
= g_strdup_printf(
2032 "Event: presence.wpending\r\n"
2033 "Accept: text/xml+msrtc.wpending\r\n"
2034 "Supported: com.microsoft.autoextend\r\n"
2035 "Supported: ms-benotify\r\n"
2036 "Proxy-Require: ms-benotify\r\n"
2037 "Supported: ms-piggyback-first-notify\r\n"
2038 "Contact: %s\r\n", tmp
);
2041 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2047 * Fires on deregistration event initiated by server.
2048 * [MS-SIPREGE] SIP extension.
2053 // Content-Type: text/registration-event
2054 // subscription-state: terminated;expires=0
2055 // ms-diagnostics-public: 4141;reason="User disabled"
2057 // deregistered;event=rejected
2059 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2061 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2062 gchar
*event
= NULL
;
2063 gchar
*reason
= NULL
;
2064 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
2066 warning
= warning
? warning
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2067 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2069 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2070 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2071 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2072 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2074 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2078 if (warning
!= NULL
) {
2079 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
2080 } else { // for LCS2005
2082 if (event
&& !g_ascii_strcasecmp(event
, "unregistered")) {
2083 error_id
= 4140; // [MS-SIPREGE]
2084 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2085 reason
= g_strdup(_("You have been signed off because you've signed in at another location"));
2086 } else if (event
&& !g_ascii_strcasecmp(event
, "rejected")) {
2088 reason
= g_strdup(_("User disabled")); // [MS-OCER]
2089 } else if (event
&& !g_ascii_strcasecmp(event
, "deactivated")) {
2091 reason
= g_strdup(_("User moved")); // [MS-OCER]
2095 warning
= g_strdup_printf(_("Unregistered by Server: %s."), reason
? reason
: _("no reason given"));
2098 sip
->gc
->wants_to_die
= TRUE
;
2099 purple_connection_error(sip
->gc
, warning
);
2104 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2106 const gchar
*contacts_delta
;
2109 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2115 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2118 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2125 free_container(struct sipe_container
*container
)
2129 if (!container
) return;
2131 entry
= container
->members
;
2133 g_free(entry
->data
);
2134 entry
= g_slist_remove(entry
, entry
->data
);
2140 * Finds locally stored MS-PRES container member
2142 static struct sipe_container_member
*
2143 sipe_find_container_member(struct sipe_container
*container
,
2147 struct sipe_container_member
*member
;
2150 if (container
== NULL
|| type
== NULL
) {
2154 entry
= container
->members
;
2156 member
= entry
->data
;
2157 if (!g_strcasecmp(member
->type
, type
)
2158 && ((!member
->value
&& !value
)
2159 || (value
&& member
->value
&& !g_strcasecmp(member
->value
, value
)))
2163 entry
= entry
->next
;
2169 * Finds locally stored MS-PRES container by id
2171 static struct sipe_container
*
2172 sipe_find_container(struct sipe_account_data
*sip
,
2175 struct sipe_container
*container
;
2182 entry
= sip
->containers
;
2184 container
= entry
->data
;
2185 if (id
== container
->id
) {
2188 entry
= entry
->next
;
2202 sipe_find_access_level(struct sipe_account_data
*sip
,
2206 guint containers
[] = {32000, 400, 300, 200, 100};
2209 for (i
= 0; i
< 5; i
++) {
2210 struct sipe_container_member
*member
;
2211 struct sipe_container
*container
= sipe_find_container(sip
, containers
[i
]);
2212 if (!container
) continue;
2214 member
= sipe_find_container_member(container
, type
, value
);
2216 return containers
[i
];
2224 sipe_send_set_container_members(struct sipe_account_data
*sip
,
2226 guint container_version
,
2227 const gchar
* action
,
2231 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
2232 gchar
*value_str
= value
? g_strdup_printf(" value=\"%s\"", value
) : g_strdup("");
2235 gchar
*body
= g_strdup_printf(
2236 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2237 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2238 "</setContainerMembers>",
2246 contact
= get_contact(sip
);
2247 hdr
= g_strdup_printf("Contact: %s\r\n"
2248 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact
);
2251 send_sip_request(sip
->gc
, "SERVICE", self
, self
, hdr
, body
, NULL
, NULL
);
2260 * When we receive some self (BE) NOTIFY with a new subscriber
2261 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2264 static void sipe_process_roaming_self(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2271 char *display_name
= NULL
;
2272 PurpleBuddy
*pbuddy
;
2277 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2279 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2282 contact
= get_contact(sip
);
2283 to
= g_strdup_printf("sip:%s", sip
->username
);
2286 for (node
= xmlnode_get_descendant(xml
, "containers", "container", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
2287 guint id
= atoi(xmlnode_get_attrib(node
, "id"));
2288 struct sipe_container
*container
= sipe_find_container(sip
, id
);
2291 sip
->containers
= g_slist_remove(sip
->containers
, container
);
2292 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container
->id
, container
->version
);
2293 free_container(container
);
2295 container
= g_new0(struct sipe_container
, 1);
2297 container
->version
= atoi(xmlnode_get_attrib(node
, "version"));
2298 sip
->containers
= g_slist_append(sip
->containers
, container
);
2299 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container
->id
, container
->version
);
2301 for (node2
= xmlnode_get_child(node
, "member"); node2
; node2
= xmlnode_get_next_twin(node2
)) {
2302 struct sipe_container_member
*member
= g_new0(struct sipe_container_member
, 1);
2303 member
->type
= xmlnode_get_attrib(node2
, "type");
2304 member
->value
= xmlnode_get_attrib(node2
, "value");
2305 container
->members
= g_slist_append(container
->members
, member
);
2306 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
2307 member
->type
, member
->value
? member
->value
: "");
2311 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip
->access_level_set
? "TRUE" : "FALSE");
2312 if (!sip
->access_level_set
&& xmlnode_get_child(xml
, "containers")) {
2313 int sameEnterpriseAL
= sipe_find_access_level(sip
, "sameEnterprise", NULL
);
2314 int federatedAL
= sipe_find_access_level(sip
, "federated", NULL
);
2315 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL
);
2316 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL
);
2317 /* initial set-up to let counterparties see your status */
2318 if (sameEnterpriseAL
< 0) {
2319 struct sipe_container
*container
= sipe_find_container(sip
, 200);
2320 guint version
= container
? container
->version
: 0;
2321 sipe_send_set_container_members(sip
, 200, version
, "add", "sameEnterprise", NULL
);
2323 if (federatedAL
< 0) {
2324 struct sipe_container
*container
= sipe_find_container(sip
, 100);
2325 guint version
= container
? container
->version
: 0;
2326 sipe_send_set_container_members(sip
, 100, version
, "add", "federated", NULL
);
2328 sip
->access_level_set
= TRUE
;
2332 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
2334 const char *acknowledged
;
2338 user
= xmlnode_get_attrib(node
, "user");
2339 if (!user
) continue;
2340 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
2341 display_name
= g_strdup(xmlnode_get_attrib(node
, "displayName"));
2342 uri_user
= g_strdup_printf("sip:%s", user
);
2343 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri_user
);
2345 alias
= purple_buddy_get_local_alias(pbuddy
);
2346 uri_alias
= g_strdup_printf("sip:%s", alias
);
2347 if (display_name
&& !g_ascii_strcasecmp(uri_user
, uri_alias
)) { // 'bad' alias
2348 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user
, display_name
);
2349 purple_blist_alias_buddy(pbuddy
, display_name
);
2354 acknowledged
= xmlnode_get_attrib(node
, "acknowledged");
2355 if(!g_ascii_strcasecmp(acknowledged
,"false")){
2356 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user
);
2357 if (!purple_find_buddy(sip
->account
, uri_user
)) {
2358 purple_account_request_add(sip
->account
, uri_user
, _("you"), display_name
, NULL
);
2361 hdr
= g_strdup_printf(
2363 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
2365 body
= g_strdup_printf(
2366 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2367 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2368 "</setSubscribers>", user
);
2370 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
2374 g_free(display_name
);
2383 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2385 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2386 gchar
*tmp
= get_contact(sip
);
2387 gchar
*hdr
= g_strdup_printf(
2388 "Event: vnd-microsoft-roaming-ACL\r\n"
2389 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2390 "Supported: com.microsoft.autoextend\r\n"
2391 "Supported: ms-benotify\r\n"
2392 "Proxy-Require: ms-benotify\r\n"
2393 "Supported: ms-piggyback-first-notify\r\n"
2394 "Contact: %s\r\n", tmp
);
2397 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2403 * To request for presence information about the user, access level settings that have already been configured by the user
2404 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2405 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2408 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2410 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2411 gchar
*tmp
= get_contact(sip
);
2412 gchar
*hdr
= g_strdup_printf(
2413 "Event: vnd-microsoft-roaming-self\r\n"
2414 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2415 "Supported: ms-benotify\r\n"
2416 "Proxy-Require: ms-benotify\r\n"
2417 "Supported: ms-piggyback-first-notify\r\n"
2419 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
2421 gchar
*body
=g_strdup(
2422 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2423 "<roaming type=\"categories\"/>"
2424 "<roaming type=\"containers\"/>"
2425 "<roaming type=\"subscribers\"/></roamingList>");
2428 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2437 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2439 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2440 gchar
*tmp
= get_contact(sip
);
2441 gchar
*hdr
= g_strdup_printf(
2442 "Event: vnd-microsoft-provisioning\r\n"
2443 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2444 "Supported: com.microsoft.autoextend\r\n"
2445 "Supported: ms-benotify\r\n"
2446 "Proxy-Require: ms-benotify\r\n"
2447 "Supported: ms-piggyback-first-notify\r\n"
2449 "Contact: %s\r\n", tmp
);
2452 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
2457 /** Subscription for provisioning information to help with initial
2458 * configuration. This subscription is a one-time query (denoted by the Expires header,
2459 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2460 * configuration, meeting policies, and policy settings that Communicator must enforce.
2461 * TODO: for what we need this information.
2464 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2466 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2467 gchar
*tmp
= get_contact(sip
);
2468 gchar
*hdr
= g_strdup_printf(
2469 "Event: vnd-microsoft-provisioning-v2\r\n"
2470 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2471 "Supported: com.microsoft.autoextend\r\n"
2472 "Supported: ms-benotify\r\n"
2473 "Proxy-Require: ms-benotify\r\n"
2474 "Supported: ms-piggyback-first-notify\r\n"
2477 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
2478 gchar
*body
= g_strdup(
2479 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2480 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2481 "<provisioningGroup name=\"ucPolicy\"/>"
2482 "</provisioningGroupList>");
2485 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2491 /* IM Session (INVITE and MESSAGE methods) */
2493 static struct sip_dialog
*
2494 get_dialog (struct sip_im_session
*session
,
2497 struct sip_dialog
*dialog
;
2499 if (session
== NULL
|| who
== NULL
) {
2503 entry
= session
->dialogs
;
2505 dialog
= entry
->data
;
2506 if (dialog
->with
&& !strcmp(who
, dialog
->with
)) {
2509 entry
= entry
->next
;
2514 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2516 get_end_points (struct sipe_account_data
*sip
,
2517 struct sip_im_session
*session
)
2520 gchar
*res
= g_strdup_printf("<sip:%s>", sip
->username
);
2521 struct sip_dialog
*dialog
;
2523 if (session
== NULL
) {
2527 entry
= session
->dialogs
;
2529 dialog
= entry
->data
;
2532 res
= g_strdup_printf("%s, <%s>", res
, dialog
->with
);
2535 if (dialog
->theirepid
) {
2537 res
= g_strdup_printf("%s;epid=%s", res
, dialog
->theirepid
);
2541 entry
= entry
->next
;
2546 static struct sip_im_session
*
2547 find_chat_session_by_id (struct sipe_account_data
*sip
,
2550 struct sip_im_session
*session
;
2556 entry
= sip
->im_sessions
;
2558 session
= entry
->data
;
2559 if (id
== session
->chat_id
) {
2562 entry
= entry
->next
;
2567 static struct sip_im_session
*
2568 find_chat_session_by_name (struct sipe_account_data
*sip
,
2569 const char *chat_name
)
2571 struct sip_im_session
*session
;
2573 if (sip
== NULL
|| chat_name
== NULL
) {
2577 entry
= sip
->im_sessions
;
2579 session
= entry
->data
;
2580 if (session
->chat_name
&& !g_strcasecmp(chat_name
, session
->chat_name
)) {
2583 entry
= entry
->next
;
2588 static struct sip_im_session
*
2589 find_chat_session (struct sipe_account_data
*sip
,
2592 struct sip_im_session
*session
;
2594 if (sip
== NULL
|| callid
== NULL
) {
2598 entry
= sip
->im_sessions
;
2600 session
= entry
->data
;
2601 if (session
->callid
&& !g_strcasecmp(callid
, session
->callid
)) {
2604 entry
= entry
->next
;
2609 static struct sip_im_session
* find_im_session (struct sipe_account_data
*sip
, const char *who
)
2611 struct sip_im_session
*session
;
2613 if (sip
== NULL
|| who
== NULL
) {
2617 entry
= sip
->im_sessions
;
2619 session
= entry
->data
;
2620 if (session
->with
&& !strcmp(who
, session
->with
)) {
2623 entry
= entry
->next
;
2628 struct sip_im_session
*
2629 create_chat_session (struct sipe_account_data
*sip
)
2631 struct sip_im_session
*session
= g_new0(struct sip_im_session
, 1);
2632 session
->callid
= gencallid();
2633 session
->is_multiparty
= TRUE
;
2634 session
->chat_id
= rand();
2635 session
->unconfirmed_messages
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
2636 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2640 static struct sip_im_session
* find_or_create_chat_session (struct sipe_account_data
*sip
, const char *callid
)
2642 struct sip_im_session
*session
= find_chat_session(sip
, callid
);
2644 session
= create_chat_session(sip
);
2645 session
->callid
= g_strdup(callid
);
2650 static struct sip_im_session
* find_or_create_im_session (struct sipe_account_data
*sip
, const char *who
)
2652 struct sip_im_session
*session
= find_im_session(sip
, who
);
2654 session
= g_new0(struct sip_im_session
, 1);
2655 session
->is_multiparty
= FALSE
;
2656 session
->with
= g_strdup(who
);
2657 session
->unconfirmed_messages
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
2658 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2663 void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2667 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2669 entry
= session
->dialogs
;
2671 free_dialog(entry
->data
);
2672 entry
= g_slist_remove(entry
, entry
->data
);
2675 entry
= session
->outgoing_message_queue
;
2677 g_free(entry
->data
);
2678 entry
= g_slist_remove(entry
, entry
->data
);
2681 entry
= session
->pending_invite_queue
;
2683 g_free(entry
->data
);
2684 entry
= g_slist_remove(entry
, entry
->data
);
2687 g_hash_table_destroy(session
->unconfirmed_messages
);
2689 g_free(session
->with
);
2690 g_free(session
->chat_name
);
2691 g_free(session
->callid
);
2692 g_free(session
->roster_manager
);
2693 g_free(session
->focus_uri
);
2698 process_options_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2700 gboolean ret
= TRUE
;
2702 if (msg
->response
!= 200) {
2703 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
2707 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
2713 * Asks UA/proxy about its capabilities.
2715 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
2717 gchar
*to
= strstr(who
, "sip:") ? g_strdup(who
) : g_strdup_printf("sip:%s", who
);
2718 gchar
*contact
= get_contact(sip
);
2720 request
= g_strdup_printf(
2721 "Accept: application/sdp\r\n"
2722 "Contact: %s\r\n", contact
);
2726 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
2732 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2734 char *msg
, *msg_tmp
;
2735 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2736 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2738 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2739 "possibly because one or more persons are offline:\n%s") ,
2741 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2746 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2749 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2751 gboolean ret
= TRUE
;
2752 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2753 struct sip_im_session
* session
= find_im_session(sip
, with
);
2754 struct sip_dialog
*dialog
;
2760 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2765 dialog
= get_dialog(session
, with
);
2768 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2773 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2774 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
), with
);
2776 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2778 if (msg
->response
!= 200) {
2779 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2781 sipe_present_message_undelivered_err(with
, sip
, message
);
2782 im_session_destroy(sip
, session
);
2785 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2786 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2787 key
, g_hash_table_size(session
->unconfirmed_messages
));
2793 if (ret
) sipe_im_process_queue(sip
, session
);
2798 sipe_is_election_finished(struct sipe_account_data
*sip
,
2799 struct sip_im_session
*session
);
2802 sipe_election_result(struct sipe_account_data
*sip
,
2806 process_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2808 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2809 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
2810 struct sip_dialog
*dialog
;
2811 struct sip_im_session
*session
;
2813 session
= find_chat_session(sip
, callid
);
2815 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid
);
2819 if (msg
->response
== 200 && !strncmp(contenttype
, "application/x-ms-mim", 20)) {
2820 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2821 xmlnode
*xn_request_rm_response
= xmlnode_get_child(xn_action
, "RequestRMResponse");
2822 xmlnode
*xn_set_rm_response
= xmlnode_get_child(xn_action
, "SetRMResponse");
2824 if (xn_request_rm_response
) {
2825 const char *with
= xmlnode_get_attrib(xn_request_rm_response
, "uri");
2826 const char *allow
= xmlnode_get_attrib(xn_request_rm_response
, "allow");
2828 dialog
= get_dialog(session
, with
);
2830 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with
);
2834 if (allow
&& !g_strcasecmp(allow
, "true")) {
2835 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with
);
2836 dialog
->election_vote
= 1;
2837 } else if (allow
&& !g_strcasecmp(allow
, "false")) {
2838 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with
);
2839 dialog
->election_vote
= -1;
2842 if (sipe_is_election_finished(sip
, session
)) {
2843 sipe_election_result(sip
, session
);
2846 } else if (xn_set_rm_response
) {
2849 xmlnode_free(xn_action
);
2856 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_dialog
*dialog
, const char *msg
)
2865 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2866 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2868 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2871 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2874 msgr
= g_strdup("");
2877 tmp
= get_contact(sip
);
2878 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2879 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2880 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2881 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp
, msgr
);
2885 send_sip_request(sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
, hdr
, msgtext
, dialog
, process_message_response
);
2892 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2894 GSList
*entry2
= session
->outgoing_message_queue
;
2896 char *queued_msg
= entry2
->data
;
2897 struct sip_dialog
*dialog
;
2898 GSList
*entry
= session
->dialogs
;
2900 if (session
->is_multiparty
) {
2901 gchar
*who
= g_strdup_printf("sip:%s", sip
->username
);
2902 serv_got_chat_in(sip
->gc
, session
->chat_id
, who
,
2903 PURPLE_MESSAGE_SEND
, queued_msg
, time(NULL
));
2910 dialog
= entry
->data
;
2911 entry
= entry
->next
;
2912 if (dialog
->outgoing_invite
) continue; //do not send messages as INVITE is not responded.
2914 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog
->callid
, (dialog
->cseq
) + 1, dialog
->with
);
2915 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
2916 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2917 key
, g_hash_table_size(session
->unconfirmed_messages
));
2919 sipe_send_message(sip
, dialog
, queued_msg
);
2922 entry2
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2928 sipe_refer_notify(struct sipe_account_data
*sip
,
2929 struct sip_im_session
*session
,
2936 struct sip_dialog
*dialog
= get_dialog(session
, who
);
2938 hdr
= g_strdup_printf(
2940 "Subscription-State: %s\r\n"
2941 "Content-Type: message/sipfrag\r\n",
2942 status
>= 200 ? "terminated" : "active");
2944 body
= g_strdup_printf(
2945 "SIP/2.0 %d %s\r\n",
2948 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "NOTIFY",
2949 who
, who
, hdr
, body
, dialog
, NULL
);
2956 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2958 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
2959 struct sip_im_session
*session
;
2960 struct sip_dialog
*dialog
;
2964 struct sipmsg
*request_msg
= trans
->msg
;
2966 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
2969 session
= find_chat_session(sip
, callid
);
2971 session
= find_im_session(sip
, with
);
2975 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2980 dialog
= get_dialog(session
, with
);
2982 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2987 sipe_parse_dialog(msg
, dialog
, TRUE
);
2989 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2990 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
2992 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2994 if (msg
->response
!= 200) {
2995 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2997 sipe_present_message_undelivered_err(with
, sip
, message
);
2998 im_session_destroy(sip
, session
);
3005 send_sip_request(sip
->gc
, "ACK", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
3006 dialog
->outgoing_invite
= NULL
;
3007 dialog
->is_established
= TRUE
;
3009 referred_by
= parse_from(sipmsg_find_header(request_msg
, "Referred-By"));
3011 sipe_refer_notify(sip
, session
, referred_by
, 200, "OK");
3012 g_free(referred_by
);
3015 /* add user to chat if it is a multiparty session */
3016 if (session
->is_multiparty
) {
3017 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3019 PURPLE_CBFLAGS_NONE
, TRUE
);
3022 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
3023 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3024 if (session
->outgoing_message_queue
) {
3025 char *queued_msg
= session
->outgoing_message_queue
->data
;
3026 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
3031 sipe_im_process_queue(sip
, session
);
3033 g_hash_table_remove(session
->unconfirmed_messages
, key
);
3034 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3035 key
, g_hash_table_size(session
->unconfirmed_messages
));
3044 sipe_invite(struct sipe_account_data
*sip
,
3045 struct sip_im_session
*session
,
3047 const gchar
*msg_body
,
3048 const gchar
*referred_by
,
3049 const gboolean is_triggered
)
3056 char *ms_text_format
= g_strdup("");
3057 gchar
*roster_manager
;
3059 gchar
*referred_by_str
;
3060 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3062 if (dialog
&& dialog
->is_established
) {
3063 purple_debug_info("sipe", "session with %s already has a dialog open\n", who
);
3068 dialog
= g_new0(struct sip_dialog
, 1);
3069 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3071 dialog
->callid
= session
->callid
? g_strdup(session
->callid
) : gencallid();
3072 dialog
->with
= g_strdup(who
);
3075 if (!(dialog
->ourtag
)) {
3076 dialog
->ourtag
= gentag();
3080 if (strstr(who
, "sip:")) {
3083 to
= g_strdup_printf("sip:%s", who
);
3094 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
3095 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
3097 msgr_value
= sipmsg_get_msgr_string(msgformat
);
3101 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
3105 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
3106 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
3111 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, (dialog
->cseq
) + 1);
3112 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
3113 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3114 key
, g_hash_table_size(session
->unconfirmed_messages
));
3118 contact
= get_contact(sip
);
3119 end_points
= get_end_points(sip
, session
);
3120 self
= g_strdup_printf("sip:%s", sip
->username
);
3121 roster_manager
= g_strdup_printf(
3122 "Roster-Manager: %s\r\n"
3123 "EndPoints: %s\r\n",
3126 referred_by_str
= referred_by
?
3128 "Referred-By: %s\r\n",
3131 hdr
= g_strdup_printf(
3137 "Content-Type: application/sdp\r\n",
3138 (session
->roster_manager
&& !strcmp(session
->roster_manager
, self
)) ? roster_manager
: "",
3140 is_triggered
? "TriggeredInvite: TRUE\r\n" : "",
3141 is_triggered
|| session
->is_multiparty
? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3144 g_free(ms_text_format
);
3147 body
= g_strdup_printf(
3149 "o=- 0 0 IN IP4 %s\r\n"
3153 "m=message %d sip null\r\n"
3154 "a=accept-types:text/plain text/html image/gif "
3155 "multipart/alternative application/im-iscomposing+xml\r\n",
3156 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
3158 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
3159 to
, to
, hdr
, body
, dialog
, process_invite_response
);
3162 g_free(roster_manager
);
3164 g_free(referred_by_str
);
3171 sipe_refer(struct sipe_account_data
*sip
,
3172 struct sip_im_session
*session
,
3177 struct sip_dialog
*dialog
= get_dialog(session
, session
->roster_manager
);
3179 contact
= get_contact(sip
);
3180 hdr
= g_strdup_printf(
3182 "Refer-to: <%s>\r\n"
3183 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3184 "Require: com.microsoft.rtc-multiparty\r\n",
3188 dialog
->ourtag
? ";tag=" : "",
3189 dialog
->ourtag
? dialog
->ourtag
: "",
3192 send_sip_request(sip
->gc
, "REFER",
3193 session
->roster_manager
, session
->roster_manager
, hdr
, NULL
, dialog
, NULL
);
3200 sipe_send_election_request_rm(struct sipe_account_data
*sip
,
3201 struct sip_im_session
*session
,
3207 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3209 hdr
= "Content-Type: application/x-ms-mim\r\n";
3211 body
= g_strdup_printf(
3212 "<?xml version=\"1.0\"?>\r\n"
3213 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3214 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3215 sip
->username
, bid
);
3217 send_sip_request(sip
->gc
, "INFO",
3218 who
, who
, hdr
, body
, dialog
, process_info_response
);
3224 sipe_send_election_set_rm(struct sipe_account_data
*sip
,
3225 struct sip_im_session
*session
,
3230 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3232 hdr
= "Content-Type: application/x-ms-mim\r\n";
3234 body
= g_strdup_printf(
3235 "<?xml version=\"1.0\"?>\r\n"
3236 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3237 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3240 send_sip_request(sip
->gc
, "INFO",
3241 who
, who
, hdr
, body
, dialog
, process_info_response
);
3247 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
3250 struct sip_dialog
*dialog
;
3252 entry
= session
->dialogs
;
3254 dialog
= entry
->data
;
3255 /* @TODO slow down BYE message sending rate */
3256 /* @see single subscription code */
3257 send_sip_request(sip
->gc
, "BYE", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
3258 entry
= entry
->next
;
3261 im_session_destroy(sip
, session
);
3266 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
3268 struct sipe_account_data
*sip
= gc
->proto_data
;
3270 purple_debug_info("sipe", "conversation with %s closed\n", who
);
3271 im_session_close(sip
, find_im_session(sip
, who
));
3275 sipe_chat_leave (PurpleConnection
*gc
, int id
)
3277 struct sipe_account_data
*sip
= gc
->proto_data
;
3278 struct sip_im_session
* session
= find_chat_session_by_id(sip
, id
);
3279 im_session_close(sip
, session
);
3283 im_session_close_all (struct sipe_account_data
*sip
)
3285 GSList
*entry
= sip
->im_sessions
;
3287 im_session_close (sip
, entry
->data
);
3288 entry
= sip
->im_sessions
;
3292 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
3294 struct sipe_account_data
*sip
= gc
->proto_data
;
3295 struct sip_im_session
*session
;
3296 struct sip_dialog
*dialog
;
3298 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
3300 session
= find_or_create_im_session(sip
, who
);
3301 dialog
= get_dialog(session
, who
);
3303 // Queue the message
3304 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
3306 if (dialog
&& dialog
->callid
) {
3307 sipe_im_process_queue(sip
, session
);
3308 } else if (!dialog
|| !dialog
->outgoing_invite
) {
3309 // Need to send the INVITE to get the outgoing dialog setup
3310 sipe_invite(sip
, session
, who
, what
, NULL
, FALSE
);
3316 static int sipe_chat_send(PurpleConnection
*gc
, int id
, const char *what
, PurpleMessageFlags flags
)
3318 struct sipe_account_data
*sip
= gc
->proto_data
;
3319 struct sip_im_session
*session
;
3321 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what
);
3323 session
= find_chat_session_by_id(sip
, id
);
3325 // Queue the message
3327 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
,
3329 sipe_im_process_queue(sip
, session
);
3335 /* End IM Session (INVITE and MESSAGE methods) */
3337 static void process_incoming_info(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3339 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
3340 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3341 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3343 struct sip_im_session
*session
= find_chat_session(sip
, callid
);
3345 session
= find_im_session(sip
, from
);
3352 if (!strncmp(contenttype
, "application/x-ms-mim", 20)) {
3353 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3354 xmlnode
*xn_request_rm
= xmlnode_get_child(xn_action
, "RequestRM");
3355 xmlnode
*xn_set_rm
= xmlnode_get_child(xn_action
, "SetRM");
3357 sipmsg_add_header(msg
, "Content-Type", "application/x-ms-mim");
3359 if (xn_request_rm
) {
3360 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3361 int bid
= atoi(xmlnode_get_attrib(xn_request_rm
, "bid"));
3362 gchar
*body
= g_strdup_printf(
3363 "<?xml version=\"1.0\"?>\r\n"
3364 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3365 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3367 session
->bid
< bid
? "true" : "false");
3368 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3370 } else if (xn_set_rm
) {
3372 const char *rm
= xmlnode_get_attrib(xn_set_rm
, "uri");
3373 g_free(session
->roster_manager
);
3374 session
->roster_manager
= g_strdup(rm
);
3376 body
= g_strdup_printf(
3377 "<?xml version=\"1.0\"?>\r\n"
3378 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3379 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3381 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3384 xmlnode_free(xn_action
);
3387 /* looks like purple lacks typing notification for chat */
3388 if (!session
->is_multiparty
) {
3389 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
3392 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3397 static void process_incoming_bye(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3399 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3400 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3401 struct sip_im_session
*session
;
3402 struct sip_dialog
*dialog
;
3404 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3406 session
= find_chat_session(sip
, callid
);
3408 session
= find_im_session(sip
, from
);
3415 if (session
->roster_manager
&& !g_strcasecmp(from
, session
->roster_manager
)) {
3416 g_free(session
->roster_manager
);
3417 session
->roster_manager
= NULL
;
3420 if (!session
->is_multiparty
) {
3421 // TODO Let the user know the other user left the conversation?
3422 im_session_destroy(sip
, session
);
3424 dialog
= get_dialog(session
, from
);
3425 session
->dialogs
= g_slist_remove(session
->dialogs
, dialog
);
3426 free_dialog(dialog
);
3428 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session
->conv
), from
, NULL
);
3430 if (!session
->dialogs
) {
3431 im_session_destroy(sip
, session
);
3438 static void process_incoming_refer(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3440 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
3441 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3442 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3443 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
3444 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
3445 struct sip_im_session
*session
;
3446 struct sip_dialog
*dialog
;
3448 session
= find_chat_session(sip
, callid
);
3449 dialog
= get_dialog(session
, from
);
3451 if (!session
|| !dialog
|| !session
->roster_manager
|| strcmp(session
->roster_manager
, self
)) {
3452 send_sip_response(sip
->gc
, msg
, 500, "Server Internal Error", NULL
);
3454 send_sip_response(sip
->gc
, msg
, 202, "Accepted", NULL
);
3456 sipe_invite(sip
, session
, refer_to
, NULL
, referred_by
, FALSE
);
3462 g_free(referred_by
);
3466 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
3468 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
3469 struct sip_im_session
*session
;
3470 struct sip_dialog
*dialog
;
3472 if (state
== PURPLE_NOT_TYPING
)
3475 session
= find_im_session(sip
, who
);
3476 dialog
= get_dialog(session
, who
);
3478 if (session
&& dialog
) {
3479 send_sip_request(gc
, "INFO", who
, who
,
3480 "Content-Type: application/xml\r\n",
3481 SIPE_SEND_TYPING
, dialog
, NULL
);
3483 return SIPE_TYPING_SEND_TIMEOUT
;
3486 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
3488 GSList
*tmp
= sip
->transactions
;
3489 time_t currtime
= time(NULL
);
3491 struct transaction
*trans
= tmp
->data
;
3493 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime
-trans
->time
);
3494 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
3497 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
3499 sendout_sipmsg(sip
, trans
->msg
);
3506 static void do_reauthenticate_cb(struct sipe_account_data
*sip
, void *unused
)
3508 /* register again when security token expires */
3509 /* we have to start a new authentication as the security token
3510 * is almost expired by sending a not signed REGISTER message */
3511 purple_debug_info("sipe", "do a full reauthentication\n");
3512 sipe_auth_free(&sip
->registrar
);
3513 sipe_auth_free(&sip
->proxy
);
3514 sip
->registerstatus
= 0;
3516 sip
->reauthenticate_set
= FALSE
;
3519 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3523 gboolean found
= FALSE
;
3525 from
= parse_from(sipmsg_find_header(msg
, "From"));
3529 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
3531 contenttype
= sipmsg_find_header(msg
, "Content-Type");
3532 if (!strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
3534 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3535 gchar
*html
= get_html_message(contenttype
, msg
->body
);
3537 struct sip_im_session
*session
= find_chat_session(sip
, callid
);
3539 session
= find_im_session(sip
, from
);
3542 if (session
&& session
->is_multiparty
) {
3543 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
3544 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
3546 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3549 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3552 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
3553 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3558 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3562 state
= xmlnode_get_child(isc
, "state");
3565 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3570 statedata
= xmlnode_get_data(state
);
3572 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
3573 else serv_got_typing_stopped(sip
->gc
, from
);
3578 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3582 purple_debug_info("sipe", "got unknown mime-type");
3583 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
3588 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3590 gchar
*ms_text_format
;
3592 gchar
*newTag
= gentag();
3595 gboolean is_multiparty
= FALSE
;
3596 gboolean is_triggered
= FALSE
;
3597 gboolean was_multiparty
= TRUE
;
3598 gboolean just_joined
= FALSE
;
3599 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3600 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
3601 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3602 gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
3603 gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
3604 gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
3605 gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
3606 GSList
*end_points
= NULL
;
3607 struct sip_im_session
*session
;
3608 struct sip_dialog
*dialog
;
3610 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? msg
->body
: "");
3612 /* Invitation to join conference */
3613 if (!strncmp(content_type
, "application/ms-conf-invite+xml", 30)) {
3614 process_incoming_invite_conf(sip
, msg
);
3618 /* Only accept text invitations */
3619 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
3620 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3624 // TODO There *must* be a better way to clean up the To header to add a tag...
3625 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3626 oldHeader
= sipmsg_find_header(msg
, "To");
3627 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
3628 sipmsg_remove_header_now(msg
, "To");
3629 sipmsg_add_header_now(msg
, "To", newHeader
);
3632 if (end_points_hdr
) {
3633 end_points
= sipmsg_parse_endpoints_header(end_points_hdr
);
3635 if (g_slist_length(end_points
) > 2) {
3636 is_multiparty
= TRUE
;
3639 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
3640 is_triggered
= TRUE
;
3641 is_multiparty
= TRUE
;
3644 session
= find_chat_session(sip
, callid
);
3645 /* Convert to multiparty */
3646 if (session
&& is_multiparty
&& !session
->is_multiparty
) {
3647 g_free(session
->with
);
3648 session
->with
= NULL
;
3649 was_multiparty
= FALSE
;
3650 session
->is_multiparty
= TRUE
;
3651 session
->chat_id
= rand();
3654 if (!session
&& is_multiparty
) {
3655 session
= find_or_create_chat_session(sip
, callid
);
3659 session
= find_or_create_im_session(sip
, from
);
3662 if (!session
->callid
) {
3663 session
->callid
= g_strdup(callid
);
3666 session
->is_multiparty
= is_multiparty
;
3667 if (roster_manager
) {
3668 session
->roster_manager
= g_strdup(roster_manager
);
3671 if (is_multiparty
&& end_points
) {
3672 GSList
*entry
= end_points
;
3674 struct sipendpoint
*end_point
= entry
->data
;
3675 entry
= entry
->next
;
3677 if (!g_strcasecmp(from
, end_point
->contact
) ||
3678 !g_strcasecmp(to
, end_point
->contact
))
3681 dialog
= get_dialog(session
, end_point
->contact
);
3683 g_free(dialog
->theirepid
);
3684 dialog
->theirepid
= end_point
->epid
;
3685 end_point
->epid
= NULL
;
3687 dialog
= g_new0(struct sip_dialog
, 1);
3688 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3690 dialog
->callid
= g_strdup(session
->callid
);
3691 dialog
->with
= end_point
->contact
;
3692 end_point
->contact
= NULL
;
3693 dialog
->theirepid
= end_point
->epid
;
3694 end_point
->epid
= NULL
;
3698 /* send triggered INVITE */
3699 sipe_invite(sip
, session
, dialog
->with
, NULL
, NULL
, TRUE
);
3705 GSList
*entry
= end_points
;
3707 struct sipendpoint
*end_point
= entry
->data
;
3708 entry
= entry
->next
;
3709 g_free(end_point
->contact
);
3710 g_free(end_point
->epid
);
3713 g_slist_free(end_points
);
3717 dialog
= get_dialog(session
, from
);
3719 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3721 dialog
= g_new0(struct sip_dialog
, 1);
3722 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3724 dialog
->callid
= g_strdup(session
->callid
);
3725 dialog
->with
= g_strdup(from
);
3726 sipe_parse_dialog(msg
, dialog
, FALSE
);
3728 if (!dialog
->ourtag
) {
3729 dialog
->ourtag
= newTag
;
3736 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3740 if (is_multiparty
&& !session
->conv
) {
3741 gchar
*chat_name
= g_strdup_printf(_("Chat #%d"), ++sip
->chat_seq
);
3742 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
3743 /* create prpl chat */
3744 session
->conv
= serv_got_joined_chat(sip
->gc
, session
->chat_id
, chat_name
);
3745 session
->chat_name
= g_strdup(chat_name
);
3747 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3749 PURPLE_CBFLAGS_NONE
, FALSE
);
3754 if (is_multiparty
&& !was_multiparty
) {
3755 /* add current IM counterparty to chat */
3756 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3757 ((struct sip_dialog
*)session
->dialogs
->data
)->with
, NULL
,
3758 PURPLE_CBFLAGS_NONE
, FALSE
);
3762 /* add inviting party */
3764 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3766 PURPLE_CBFLAGS_NONE
, TRUE
);
3769 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3770 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
3771 if (ms_text_format
) {
3772 if (!strncmp(ms_text_format
, "text/plain", 10) || !strncmp(ms_text_format
, "text/html", 9)) {
3774 gchar
*html
= get_html_message(ms_text_format
, NULL
);
3776 if (is_multiparty
) {
3777 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
3778 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
3780 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3783 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message received
3789 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
3790 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3791 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
3793 body
= g_strdup_printf(
3795 "o=- 0 0 IN IP4 %s\r\n"
3799 "m=message %d sip sip:%s\r\n"
3800 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3801 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3802 sip
->realport
, sip
->username
);
3803 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3807 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3811 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
3812 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3813 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
3815 body
= g_strdup_printf(
3817 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3819 "c=IN IP4 0.0.0.0\r\n"
3821 "m=message %d sip sip:%s\r\n"
3822 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3823 sip
->realport
, sip
->username
);
3824 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3828 static void sipe_connection_cleanup(struct sipe_account_data
*);
3829 static void create_connection(struct sipe_account_data
*, gchar
*, int);
3831 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3834 const gchar
*expires_header
;
3836 GSList
*hdr
= msg
->headers
;
3838 struct siphdrelement
*elem
;
3840 expires_header
= sipmsg_find_header(msg
, "Expires");
3841 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
3842 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
3844 switch (msg
->response
) {
3847 sip
->registerstatus
= 0;
3849 gchar
*contact_hdr
= NULL
;
3855 if (!sip
->reregister_set
) {
3856 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
3857 sipe_schedule_action(action_name
, expires
, do_register_cb
, NULL
, sip
, NULL
);
3858 g_free(action_name
);
3859 sip
->reregister_set
= TRUE
;
3862 sip
->registerstatus
= 3;
3865 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3867 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3870 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3873 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3874 fill_auth(sip
, tmp
, &sip
->registrar
);
3876 if (!sip
->reauthenticate_set
) {
3877 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
3878 guint reauth_timeout
;
3879 if (sip
->registrar
.type
== AUTH_TYPE_KERBEROS
&& sip
->registrar
.expires
> 0) {
3880 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3881 reauth_timeout
= sip
->registrar
.expires
- 300;
3883 /* NTLM: we have to reauthenticate as our security token expires
3884 after eight hours (be five minutes early) */
3885 reauth_timeout
= (8 * 3600) - 300;
3887 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, NULL
, sip
, NULL
);
3888 g_free(action_name
);
3889 sip
->reauthenticate_set
= TRUE
;
3892 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
3894 epid
= get_epid(sip
);
3895 uuid
= generateUUIDfromEPID(epid
);
3898 // There can be multiple Contact headers (one per location where the user is logged in) so
3899 // make sure to only get the one for this uuid
3900 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
3901 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
3902 if (valid_contact
) {
3903 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
3904 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3905 g_free(valid_contact
);
3908 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3913 g_free(sip
->contact
);
3915 sip
->contact
= g_strdup_printf("<%s>", gruu
);
3918 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3919 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
);
3921 sip
->msrtc_event_categories
= FALSE
;
3922 sip
->batched_support
= FALSE
;
3927 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
3928 if (!g_ascii_strcasecmp(elem
->value
, "msrtc-event-categories")) {
3929 sip
->msrtc_event_categories
= TRUE
;
3930 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->msrtc_event_categories
);
3932 if (!g_ascii_strcasecmp(elem
->value
, "adhoclist")) {
3933 sip
->batched_support
= TRUE
;
3934 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->batched_support
);
3937 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
3938 gchar
**caps
= g_strsplit(elem
->value
,",",0);
3941 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
3942 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
3947 hdr
= g_slist_next(hdr
);
3950 if (!sip
->subscribed
) { //do it just once, not every re-register
3951 if(!sip
->msrtc_event_categories
){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3952 //sipe_options_request(sip, sip->sipdomain);
3954 entry
= sip
->allow_events
;
3957 if (tmp
&& !g_ascii_strcasecmp(tmp
, "vnd-microsoft-roaming-contacts")) {
3958 sipe_subscribe_roaming_contacts(sip
, msg
);
3960 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-ACL")) {
3961 sipe_subscribe_roaming_acl(sip
, msg
);
3963 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-self")) {
3964 sipe_subscribe_roaming_self(sip
, msg
);
3966 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning-v2")) {
3967 sipe_subscribe_roaming_provisioning_v2(sip
, msg
);
3968 } else if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning")) { // LSC2005
3969 sipe_subscribe_roaming_provisioning(sip
, msg
);
3971 if (tmp
&& !g_ascii_strcasecmp(tmp
,"presence.wpending")) {
3972 sipe_subscribe_presence_wpending(sip
, msg
);
3974 entry
= entry
->next
;
3976 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
3977 sip
->subscribed
= TRUE
;
3980 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
3981 "timeout=", ";", NULL
);
3982 if (timeout
!= NULL
) {
3983 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
3984 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3985 sip
->keepalive_timeout
);
3989 // Should we remove the transaction here?
3990 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
3991 transactions_remove(sip
, tc
);
3996 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
3998 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
3999 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
4003 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
4006 tmp
= g_strsplit(parts
[0], ":", 0);
4007 hostname
= g_strdup(tmp
[0]);
4008 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
4012 tmp
= g_strsplit(parts
[i
], "=", 0);
4014 if (g_strcasecmp("transport", tmp
[0]) == 0) {
4015 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
4016 transport
= SIPE_TRANSPORT_TCP
;
4017 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
4018 transport
= SIPE_TRANSPORT_UDP
;
4027 /* Close old connection */
4028 sipe_connection_cleanup(sip
);
4030 /* Create new connection */
4031 sip
->transport
= transport
;
4032 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4033 hostname
, port
, TRANSPORT_DESCRIPTOR
);
4034 create_connection(sip
, hostname
, port
);
4040 if (sip
->registerstatus
!= 2) {
4041 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
4042 if (sip
->registrar
.retries
> 3) {
4043 sip
->gc
->wants_to_die
= TRUE
;
4044 purple_connection_error(sip
->gc
, _("Wrong Password"));
4048 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
4050 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
4053 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
4056 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
4057 fill_auth(sip
, tmp
, &sip
->registrar
);
4058 sip
->registerstatus
= 2;
4059 if (sip
->account
->disconnecting
) {
4060 do_register_exp(sip
, 0);
4068 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
4069 if (warning
!= NULL
) {
4071 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4073 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
4074 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
4077 warning
= g_strdup(_("You have been rejected by the server"));
4080 sip
->gc
->wants_to_die
= TRUE
;
4081 purple_connection_error(sip
->gc
, warning
);
4088 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
4089 if (warning
!= NULL
) {
4090 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
4091 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
4094 warning
= g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4097 sip
->gc
->wants_to_die
= TRUE
;
4098 purple_connection_error(sip
->gc
, warning
);
4105 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
4106 if (warning
!= NULL
) {
4107 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
4108 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
4111 warning
= g_strdup(_("Service unavailable: no reason given"));
4114 sip
->gc
->wants_to_die
= TRUE
;
4115 purple_connection_error(sip
->gc
, warning
);
4124 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4127 xmlnode
*xn_categories
;
4128 xmlnode
*xn_category
;
4130 const char *activity
= NULL
;
4132 xn_categories
= xmlnode_from_str(data
, len
);
4133 uri
= xmlnode_get_attrib(xn_categories
, "uri");
4135 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
4137 xn_category
= xmlnode_get_next_twin(xn_category
) )
4139 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
4141 if (!strcmp(attrVar
, "note"))
4144 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4149 xn_node
= xmlnode_get_child(xn_category
, "note");
4150 if (!xn_node
) continue;
4151 xn_node
= xmlnode_get_child(xn_node
, "body");
4152 if (!xn_node
) continue;
4153 note
= xmlnode_get_data(xn_node
);
4154 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri
,note
? note
: "");
4155 g_free(sbuddy
->annotation
);
4156 sbuddy
->annotation
= NULL
;
4157 if (note
) sbuddy
->annotation
= g_strdup(note
);
4163 else if(!strcmp(attrVar
, "state"))
4167 xn_node
= xmlnode_get_child(xn_category
, "state");
4168 if (!xn_node
) continue;
4169 xn_node
= xmlnode_get_child(xn_node
, "availability");
4170 if (!xn_node
) continue;
4172 data
= xmlnode_get_data(xn_node
);
4177 activity
= SIPE_STATUS_ID_UNKNOWN
;
4178 else if (avail
< 4500)
4179 activity
= SIPE_STATUS_ID_AVAILABLE
;
4180 else if (avail
< 6000)
4181 activity
= SIPE_STATUS_ID_BRB
;
4182 else if (avail
< 7500)
4183 activity
= SIPE_STATUS_ID_ONPHONE
;
4184 else if (avail
< 9000)
4185 activity
= SIPE_STATUS_ID_BUSY
;
4186 else if (avail
< 12000)
4187 activity
= SIPE_STATUS_ID_DND
;
4188 else if (avail
< 18000)
4189 activity
= SIPE_STATUS_ID_AWAY
;
4191 activity
= SIPE_STATUS_ID_OFFLINE
;
4195 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity
);
4196 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
4199 xmlnode_free(xn_categories
);
4202 static void sipe_subscribe_poolfqdn_resource_uri(const char *host
, GSList
*server
, struct sipe_account_data
*sip
)
4204 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
4205 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host
);
4206 payload
->host
= g_strdup(host
);
4207 payload
->buddies
= server
;
4208 sipe_subscribe_presence_batched_routed(sip
, payload
);
4209 sipe_subscribe_presence_batched_routed_free(payload
);
4212 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4215 xmlnode
*xn_resource
;
4216 GHashTable
*servers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
4221 xn_list
= xmlnode_from_str(data
, len
);
4223 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
4225 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
4227 const char *uri
, *state
;
4228 xmlnode
*xn_instance
;
4230 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
4231 if (!xn_instance
) continue;
4233 uri
= xmlnode_get_attrib(xn_resource
, "uri");
4234 state
= xmlnode_get_attrib(xn_instance
, "state");
4235 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri
, state
);
4237 if (strstr(state
, "resubscribe")) {
4238 const char *poolFqdn
= xmlnode_get_attrib(xn_instance
, "poolFqdn");
4239 struct sipe_buddy
*sbuddy
;
4240 if (poolFqdn
) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4241 gchar
*user
= g_strdup(uri
);
4242 host
= g_strdup(poolFqdn
);
4243 server
= g_hash_table_lookup(servers
, host
);
4244 server
= g_slist_append(server
, user
);
4245 g_hash_table_insert(servers
, host
, server
);
4247 sipe_subscribe_presence_single(sip
, (void *) uri
);
4249 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4251 sbuddy
->resubscribed
= TRUE
;
4256 /* Send out any deferred poolFqdn subscriptions */
4257 g_hash_table_foreach(servers
, (GHFunc
) sipe_subscribe_poolfqdn_resource_uri
, sip
);
4258 g_hash_table_destroy(servers
);
4260 xmlnode_free(xn_list
);
4263 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4267 gchar
*activity
= NULL
;
4269 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
4270 gboolean isonline
= FALSE
;
4271 xmlnode
*display_name_node
;
4273 pidf
= xmlnode_from_str(data
, len
);
4275 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
4279 uri
= xmlnode_get_attrib(pidf
, "entity");
4281 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
4283 if ((status
= xmlnode_get_child(tuple
, "status"))) {
4284 basicstatus
= xmlnode_get_child(status
, "basic");
4289 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4294 getbasic
= xmlnode_get_data(basicstatus
);
4296 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4301 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
4302 if (strstr(getbasic
, "open")) {
4307 display_name_node
= xmlnode_get_child(pidf
, "display-name");
4308 // updating display name if alias was just URI
4309 if (display_name_node
) {
4310 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
4311 GSList
*entry
= buddies
;
4312 PurpleBuddy
*p_buddy
;
4313 char * display_name
= xmlnode_get_data(display_name_node
);
4316 const char *server_alias
;
4319 p_buddy
= entry
->data
;
4321 alias
= (char *)purple_buddy_get_alias(p_buddy
);
4322 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
4323 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
4324 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
4325 purple_blist_alias_buddy(p_buddy
, display_name
);
4329 server_alias
= purple_buddy_get_server_alias(p_buddy
);
4331 ( (server_alias
&& strcmp(display_name
, server_alias
))
4332 || !server_alias
|| strlen(server_alias
) == 0 )
4334 purple_blist_server_alias_buddy(p_buddy
, display_name
);
4337 entry
= entry
->next
;
4339 g_free(display_name
);
4342 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
4343 if ((status
= xmlnode_get_child(tuple
, "status"))) {
4344 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
4345 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
4346 activity
= xmlnode_get_data(basicstatus
);
4347 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
4354 const gchar
* status_id
= NULL
;
4356 if (strstr(activity
, "busy")) {
4357 status_id
= SIPE_STATUS_ID_BUSY
;
4358 } else if (strstr(activity
, "away")) {
4359 status_id
= SIPE_STATUS_ID_AWAY
;
4364 status_id
= SIPE_STATUS_ID_AVAILABLE
;
4367 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
4368 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
4370 purple_prpl_got_user_status(sip
->account
, uri
, SIPE_STATUS_ID_OFFLINE
, NULL
);
4377 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4379 const char *availability
;
4380 const char *activity
;
4381 const char *display_name
= NULL
;
4382 const char *activity_name
= NULL
;
4387 struct sipe_buddy
*sbuddy
;
4389 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
4391 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
4392 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
4393 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
4394 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
4395 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
4396 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
4397 xmlnode
*xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
):NULL
;
4398 const char *avail
= xn_state
? xmlnode_get_attrib(xn_state
, "avail") : NULL
;
4400 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
4401 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
4402 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
4403 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
4404 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
4405 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
4407 name
= xmlnode_get_attrib(xn_presentity
, "uri");
4408 uri
= g_strdup_printf("sip:%s", name
);
4409 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
4410 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
4412 // updating display name if alias was just URI
4413 if (xn_display_name
) {
4414 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
4415 GSList
*entry
= buddies
;
4416 PurpleBuddy
*p_buddy
;
4417 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
4420 const char *email_str
, *server_alias
;
4422 p_buddy
= entry
->data
;
4424 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
4425 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
4426 purple_blist_alias_buddy(p_buddy
, display_name
);
4429 server_alias
= purple_buddy_get_server_alias(p_buddy
);
4431 ( (server_alias
&& strcmp(display_name
, server_alias
))
4432 || !server_alias
|| strlen(server_alias
) == 0 )
4434 purple_blist_server_alias_buddy(p_buddy
, display_name
);
4438 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
4439 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
4440 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
4444 entry
= entry
->next
;
4448 avl
= atoi(availability
);
4449 act
= atoi(activity
);
4451 if(sip
->msrtc_event_categories
){
4452 if (act
== 100 && avl
== 0)
4453 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4454 else if (act
== 100 && avl
== 300)
4455 activity_name
= SIPE_STATUS_ID_AWAY
;
4456 else if (act
== 300 && avl
== 300)
4457 activity_name
= SIPE_STATUS_ID_BRB
;
4458 else if (act
== 400 && avl
== 300)
4459 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4460 else if (act
== 500 && act
== 300)
4461 activity_name
= SIPE_STATUS_ID_ONPHONE
;
4462 else if (act
== 600 && avl
== 300)
4463 activity_name
= SIPE_STATUS_ID_BUSY
;
4464 else if (act
== 0 && avl
== 0){ //MSRTC elements are zero
4465 if(avail
){ //Check for LegacyInterop elements
4468 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4469 else if (avl
== 3500)
4470 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4471 else if (avl
== 15500)
4472 activity_name
= SIPE_STATUS_ID_AWAY
;
4473 else if (avl
== 6500)
4474 activity_name
= SIPE_STATUS_ID_BUSY
;
4475 else if (avl
== 12500)
4476 activity_name
= SIPE_STATUS_ID_BRB
;
4481 if(activity_name
== NULL
){
4483 activity_name
= SIPE_STATUS_ID_AWAY
;
4484 else if (act
<= 150)
4485 activity_name
= SIPE_STATUS_ID_LUNCH
;
4486 else if (act
<= 300)
4487 activity_name
= SIPE_STATUS_ID_BRB
;
4488 else if (act
<= 400)
4489 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4490 else if (act
<= 500)
4491 activity_name
= SIPE_STATUS_ID_ONPHONE
;
4492 else if (act
<= 600)
4493 activity_name
= SIPE_STATUS_ID_BUSY
;
4495 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4498 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4501 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4504 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
4505 sbuddy
->annotation
= NULL
;
4506 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
4508 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
4509 sbuddy
->device_name
= NULL
;
4510 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
4513 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
4514 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
4516 xmlnode_free(xn_presentity
);
4520 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4522 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
4524 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
4526 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
4527 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
4529 const char *content
= msg
->body
;
4530 unsigned length
= msg
->bodylen
;
4531 PurpleMimeDocument
*mime
= NULL
;
4533 if (strstr(ctype
, "multipart"))
4535 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
4536 const char *content_type
;
4538 mime
= purple_mime_document_parse(doc
);
4539 parts
= purple_mime_document_get_parts(mime
);
4541 content
= purple_mime_part_get_data(parts
->data
);
4542 length
= purple_mime_part_get_length(parts
->data
);
4543 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
4544 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
4546 process_incoming_notify_rlmi_resub(sip
, content
, length
);
4548 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
4550 process_incoming_notify_msrtc(sip
, content
, length
);
4554 process_incoming_notify_rlmi(sip
, content
, length
);
4556 parts
= parts
->next
;
4562 purple_mime_document_free(mime
);
4565 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
4567 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
4569 else if(strstr(ctype
, "application/rlmi+xml"))
4571 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
4574 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
4576 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
4580 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
4584 static void sipe_process_presence_timeout(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gchar
*who
, int timeout
)
4586 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
4587 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
4589 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype
? ctype
: "");
4592 strstr(ctype
, "multipart") &&
4593 (strstr(ctype
, "application/rlmi+xml") ||
4594 strstr(ctype
, "application/msrtc-event-categories+xml"))) {
4595 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
4596 PurpleMimeDocument
*mime
= purple_mime_document_parse(doc
);
4597 GList
*parts
= purple_mime_document_get_parts(mime
);
4598 GSList
*buddies
= NULL
;
4599 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
4602 xmlnode
*xml
= xmlnode_from_str(purple_mime_part_get_data(parts
->data
),
4603 purple_mime_part_get_length(parts
->data
));
4604 gchar
*uri
= g_strdup(xmlnode_get_attrib(xml
, "uri"));
4606 if (strstr(uri
, "sip:") == NULL
) {
4608 uri
= g_strdup_printf("sip:%s", tmp
);
4611 buddies
= g_slist_append(buddies
, uri
);
4614 parts
= parts
->next
;
4617 if (mime
) purple_mime_document_free(mime
);
4619 payload
->host
= who
;
4620 payload
->buddies
= buddies
;
4621 sipe_schedule_action(action_name
, timeout
,
4622 sipe_subscribe_presence_batched_routed
,
4623 sipe_subscribe_presence_batched_routed_free
,
4625 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who
, timeout
);
4628 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, who
);
4629 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
, timeout
);
4631 g_free(action_name
);
4635 * Dispatcher for all incoming subscription information
4636 * whether it comes from NOTIFY, BENOTIFY requests or
4637 * piggy-backed to subscription's OK responce.
4639 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4640 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4642 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
4644 gchar
*event
= sipmsg_find_header(msg
, "Event");
4645 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
4648 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
4649 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
4653 const gchar
*expires_header
;
4654 expires_header
= sipmsg_find_header(msg
, "Expires");
4655 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
4656 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout
);
4657 timeout
= (timeout
- 60) > 60 ? (timeout
- 60) : timeout
; // 1 min ahead of expiration
4660 if (!subscription_state
|| strstr(subscription_state
, "active"))
4662 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
4664 sipe_process_presence(sip
, msg
);
4666 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
4668 sipe_process_roaming_contacts(sip
, msg
, NULL
);
4670 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self") )
4672 sipe_process_roaming_self(sip
, msg
);
4674 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
4676 sipe_process_roaming_acl(sip
, msg
);
4678 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
4680 sipe_process_presence_wpending(sip
, msg
);
4684 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
4688 //The server sends a (BE)NOTIFY with the status 'terminated'
4689 if (request
&& subscription_state
&& strstr(subscription_state
, "terminated") ) {
4690 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4691 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
4695 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
4696 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4697 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4699 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4700 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4701 g_free(action_name);
4703 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4704 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4706 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4707 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4708 g_free(action_name);
4711 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
4712 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
4714 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
4715 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, NULL
, sip
, NULL
);
4716 g_free(action_name
);
4718 else if (!g_ascii_strcasecmp(event
, "presence") &&
4719 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
4721 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
4722 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
4723 if(sip
->batched_support
) {
4724 gchar
*my_self
= g_strdup_printf("sip:%s",sip
->username
);
4725 if(!g_ascii_strcasecmp(who
, my_self
)){
4726 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_batched
, NULL
, sip
, NULL
);
4727 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout
);
4728 g_free(who
); /* unused */
4731 sipe_process_presence_timeout(sip
, msg
, who
, timeout
);
4736 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, who
);
4737 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
,timeout
);
4739 g_free(action_name
);
4740 /* "who" will be freed by the action we just scheduled */
4744 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
4746 sipe_process_registration_notify(sip
, msg
);
4749 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4750 if (request
&& !benotify
)
4752 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4759 static gchar* gen_xpidf(struct sipe_account_data *sip)
4761 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4763 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4764 "<display name=\"sip:%s\"/>\r\n"
4765 "<atom id=\"1234\">\r\n"
4766 "<address uri=\"sip:%s\">\r\n"
4767 "<status status=\"%s\"/>\r\n"
4780 static gchar* gen_pidf(struct sipe_account_data *sip)
4782 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4783 "<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"
4784 "<tuple id=\"0\">\r\n"
4786 "<basic>open</basic>\r\n"
4787 "<ep:activities>\r\n"
4788 " <ep:activity>%s</ep:activity>\r\n"
4792 "<ci:display-name>%s</ci:display-name>\r\n"
4801 static void send_presence_soap(struct sipe_account_data
*sip
, const char * note
)
4803 int availability
= 300; // online
4804 int activity
= 400; // Available
4807 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
)) {
4809 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4811 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4813 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4815 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4817 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4819 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_INVISIBLE
) ||
4820 !strcmp(sip
->status
, SIPE_STATUS_ID_OFFLINE
)) {
4821 availability
= 0; // offline
4824 activity
= 400; // available
4827 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
4828 //@TODO: send user data - state; add hostname in upper case
4829 body
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
4830 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
4836 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4838 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4839 if (msg
->response
== 200) {
4840 sip
->status_version
= 0;
4841 send_presence_status(sip
);
4847 process_send_presence_category_publish_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4849 if (msg
->response
== 409) {
4850 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4851 // TODO need to parse the version #'s?
4852 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
4853 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
4857 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg
->body
);
4859 tmp
= get_contact(sip
);
4860 hdr
= g_strdup_printf("Contact: %s\r\n"
4861 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4863 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
4873 static void send_presence_category_publish(struct sipe_account_data
*sip
, const char * note
)
4880 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
) ||
4881 !strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4883 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_DND
)) {
4885 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4887 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4889 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4891 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4893 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_UNKNOWN
)) {
4896 // Offline or invisible
4900 uri
= g_strdup_printf("sip:%s", sip
->username
);
4901 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
4902 sip
->status_version
, code
,
4903 sip
->status_version
, code
,
4904 sip
->status_version
, note
? note
: "",
4905 sip
->status_version
, note
? note
: "",
4906 sip
->status_version
, note
? note
: ""
4908 sip
->status_version
++;
4910 tmp
= get_contact(sip
);
4911 hdr
= g_strdup_printf("Contact: %s\r\n"
4912 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4914 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
4922 static void send_presence_status(struct sipe_account_data
*sip
)
4924 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
4926 if (!status
) return;
4928 note
= purple_status_get_attr_string(status
, "message");
4930 if(sip
->msrtc_event_categories
){
4931 send_presence_category_publish(sip
, note
);
4933 send_presence_soap(sip
, note
);
4937 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
4939 gboolean found
= FALSE
;
4940 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
4941 if (msg
->response
== 0) { /* request */
4942 if (!strcmp(msg
->method
, "MESSAGE")) {
4943 process_incoming_message(sip
, msg
);
4945 } else if (!strcmp(msg
->method
, "NOTIFY")) {
4946 purple_debug_info("sipe","send->process_incoming_notify\n");
4947 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
4949 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
4950 purple_debug_info("sipe","send->process_incoming_benotify\n");
4951 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
4953 } else if (!strcmp(msg
->method
, "INVITE")) {
4954 process_incoming_invite(sip
, msg
);
4956 } else if (!strcmp(msg
->method
, "REFER")) {
4957 process_incoming_refer(sip
, msg
);
4959 } else if (!strcmp(msg
->method
, "OPTIONS")) {
4960 process_incoming_options(sip
, msg
);
4962 } else if (!strcmp(msg
->method
, "INFO")) {
4963 process_incoming_info(sip
, msg
);
4965 } else if (!strcmp(msg
->method
, "ACK")) {
4966 // ACK's don't need any response
4968 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
4969 // LCS 2005 sends us these - just respond 200 OK
4971 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4972 } else if (!strcmp(msg
->method
, "BYE")) {
4973 process_incoming_bye(sip
, msg
);
4976 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4978 } else { /* response */
4979 struct transaction
*trans
= transactions_find(sip
, msg
);
4981 if (msg
->response
== 407) {
4982 gchar
*resend
, *auth
, *ptmp
;
4984 if (sip
->proxy
.retries
> 30) return;
4985 sip
->proxy
.retries
++;
4986 /* do proxy authentication */
4988 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
4990 fill_auth(sip
, ptmp
, &sip
->proxy
);
4991 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
4992 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
4993 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
4995 resend
= sipmsg_to_string(trans
->msg
);
4996 /* resend request */
4997 sendout_pkt(sip
->gc
, resend
);
5000 if (msg
->response
== 100 || msg
->response
== 180) {
5001 /* ignore provisional response */
5002 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
5004 sip
->proxy
.retries
= 0;
5005 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
5006 if (msg
->response
== 401)
5008 sip
->registrar
.retries
++;
5012 sip
->registrar
.retries
= 0;
5014 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
5016 if (msg
->response
== 401) {
5017 gchar
*resend
, *auth
, *ptmp
;
5019 if (sip
->registrar
.retries
> 4) return;
5020 sip
->registrar
.retries
++;
5023 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5025 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
5028 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
5032 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
5034 fill_auth(sip
, ptmp
, &sip
->registrar
);
5035 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
5036 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
5037 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
5039 //sipmsg_remove_header_now(trans->msg, "Authorization");
5040 //sipmsg_add_header(trans->msg, "Authorization", auth);
5042 resend
= sipmsg_to_string(trans
->msg
);
5043 /* resend request */
5044 sendout_pkt(sip
->gc
, resend
);
5049 if (trans
->callback
) {
5050 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
5051 /* call the callback to process response*/
5052 (trans
->callback
)(sip
, msg
, trans
);
5054 /* Not sure if this is needed or what needs to be done
5055 but transactions seem to be removed prematurely so
5056 this only removes them if the response is 200 OK */
5057 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
5058 /*Has a bug and it's unneccesary*/
5059 /*transactions_remove(sip, trans);*/
5065 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
5069 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
5073 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
5081 /* according to the RFC remove CRLF at the beginning */
5082 while (*cur
== '\r' || *cur
== '\n') {
5085 if (cur
!= conn
->inbuf
) {
5086 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
5087 conn
->inbufused
= strlen(conn
->inbuf
);
5090 /* Received a full Header? */
5091 sip
->processing_input
= TRUE
;
5092 while (sip
->processing_input
&&
5093 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
5094 time_t currtime
= time(NULL
);
5097 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
5098 msg
= sipmsg_parse_header(conn
->inbuf
);
5101 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
5102 if (restlen
>= msg
->bodylen
) {
5103 dummy
= g_malloc(msg
->bodylen
+ 1);
5104 memcpy(dummy
, cur
, msg
->bodylen
);
5105 dummy
[msg
->bodylen
] = '\0';
5107 cur
+= msg
->bodylen
;
5108 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
5109 conn
->inbufused
= strlen(conn
->inbuf
);
5111 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5112 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
5118 purple_debug_info("sipe", "body:\n%s", msg->body);
5121 // Verify the signature before processing it
5122 if (sip
->registrar
.gssapi_context
) {
5123 struct sipmsg_breakdown msgbd
;
5124 gchar
*signature_input_str
;
5127 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
5128 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
5130 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
5132 if (rspauth
!= NULL
) {
5133 if (!sip_sec_verify_signature(sip
->registrar
.gssapi_context
, signature_input_str
, rspauth
)) {
5134 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
5135 process_input_message(sip
, msg
);
5137 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid.\n");
5138 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
5139 sip
->gc
->wants_to_die
= TRUE
;
5141 } else if (msg
->response
== 401) {
5142 purple_connection_error(sip
->gc
, _("Wrong Password"));
5143 sip
->gc
->wants_to_die
= TRUE
;
5145 g_free(signature_input_str
);
5148 sipmsg_breakdown_free(&msgbd
);
5150 process_input_message(sip
, msg
);
5157 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
5159 PurpleConnection
*gc
= data
;
5160 struct sipe_account_data
*sip
= gc
->proto_data
;
5165 static char buffer
[65536];
5166 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
5168 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
5169 msg
= sipmsg_parse_msg(buffer
);
5170 if (msg
) process_input_message(sip
, msg
);
5174 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
5176 struct sipe_account_data
*sip
= gc
->proto_data
;
5177 PurpleSslConnection
*gsc
= sip
->gsc
;
5179 purple_debug_error("sipe", "%s",debug
);
5180 purple_connection_error(gc
, msg
);
5182 /* Invalidate this connection. Next send will open a new one */
5184 connection_remove(sip
, gsc
->fd
);
5185 purple_ssl_close(gsc
);
5191 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
5193 PurpleConnection
*gc
= data
;
5194 struct sipe_account_data
*sip
;
5195 struct sip_connection
*conn
;
5197 gboolean firstread
= TRUE
;
5199 /* NOTE: This check *IS* necessary */
5200 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
5201 purple_ssl_close(gsc
);
5205 sip
= gc
->proto_data
;
5206 conn
= connection_find(sip
, gsc
->fd
);
5208 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5209 gc
->wants_to_die
= TRUE
;
5210 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
5214 /* Read all available data from the SSL connection */
5216 /* Increase input buffer size as needed */
5217 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
5218 conn
->inbuflen
+= SIMPLE_BUF_INC
;
5219 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
5220 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
5223 /* Try to read as much as there is space left in the buffer */
5224 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
5225 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
5227 if (len
< 0 && errno
== EAGAIN
) {
5228 /* Try again later */
5230 } else if (len
< 0) {
5231 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
5233 } else if (firstread
&& (len
== 0)) {
5234 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
5238 conn
->inbufused
+= len
;
5241 /* Equivalence indicates that there is possibly more data to read */
5242 } while (len
== readlen
);
5244 conn
->inbuf
[conn
->inbufused
] = '\0';
5245 process_input(sip
, conn
);
5249 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
5251 PurpleConnection
*gc
= data
;
5252 struct sipe_account_data
*sip
= gc
->proto_data
;
5254 struct sip_connection
*conn
= connection_find(sip
, source
);
5256 purple_debug_error("sipe", "Connection not found!\n");
5260 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
5261 conn
->inbuflen
+= SIMPLE_BUF_INC
;
5262 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
5265 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
5267 if (len
< 0 && errno
== EAGAIN
)
5269 else if (len
<= 0) {
5270 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5271 connection_remove(sip
, source
);
5272 if (sip
->fd
== source
) sip
->fd
= -1;
5276 conn
->inbufused
+= len
;
5277 conn
->inbuf
[conn
->inbufused
] = '\0';
5279 process_input(sip
, conn
);
5282 /* Callback for new connections on incoming TCP port */
5283 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
5285 PurpleConnection
*gc
= data
;
5286 struct sipe_account_data
*sip
= gc
->proto_data
;
5287 struct sip_connection
*conn
;
5289 int newfd
= accept(source
, NULL
, NULL
);
5291 conn
= connection_create(sip
, newfd
);
5293 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
5296 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
5298 PurpleConnection
*gc
= data
;
5299 struct sipe_account_data
*sip
;
5300 struct sip_connection
*conn
;
5302 if (!PURPLE_CONNECTION_IS_VALID(gc
))
5310 purple_connection_error(gc
, _("Could not connect"));
5314 sip
= gc
->proto_data
;
5316 sip
->last_keepalive
= time(NULL
);
5318 conn
= connection_create(sip
, source
);
5322 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
5325 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
5327 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
5328 if (sip
== NULL
) return;
5333 static guint
sipe_ht_hash_nick(const char *nick
)
5335 char *lc
= g_utf8_strdown(nick
, -1);
5336 guint bucket
= g_str_hash(lc
);
5342 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
5344 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
5347 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
5349 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5351 sip
->listen_data
= NULL
;
5353 if (listenfd
== -1) {
5354 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5360 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
5361 sip
->listenfd
= sip
->fd
;
5363 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
5365 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
5369 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
5371 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5373 sip
->query_data
= NULL
;
5375 if (!hosts
|| !hosts
->data
) {
5376 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
5380 hosts
= g_slist_remove(hosts
, hosts
->data
);
5381 g_free(sip
->serveraddr
);
5382 sip
->serveraddr
= hosts
->data
;
5383 hosts
= g_slist_remove(hosts
, hosts
->data
);
5385 hosts
= g_slist_remove(hosts
, hosts
->data
);
5386 g_free(hosts
->data
);
5387 hosts
= g_slist_remove(hosts
, hosts
->data
);
5390 /* create socket for incoming connections */
5391 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
5392 sipe_udp_host_resolved_listen_cb
, sip
);
5393 if (sip
->listen_data
== NULL
) {
5394 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5399 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
5402 PurpleConnection
*gc
= data
;
5403 struct sipe_account_data
*sip
;
5405 /* If the connection is already disconnected, we don't need to do anything else */
5406 if (!PURPLE_CONNECTION_IS_VALID(gc
))
5409 sip
= gc
->proto_data
;
5414 case PURPLE_SSL_CONNECT_FAILED
:
5415 purple_connection_error(gc
, _("Connection Failed"));
5417 case PURPLE_SSL_HANDSHAKE_FAILED
:
5418 purple_connection_error(gc
, _("SSL Handshake Failed"));
5420 case PURPLE_SSL_CERTIFICATE_INVALID
:
5421 purple_connection_error(gc
, _("SSL Certificate Invalid"));
5427 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
5429 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5430 PurpleProxyConnectData
*connect_data
;
5432 sip
->listen_data
= NULL
;
5434 sip
->listenfd
= listenfd
;
5435 if (sip
->listenfd
== -1) {
5436 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5440 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
5441 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5442 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
5443 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
5444 sipe_newconn_cb
, sip
->gc
);
5445 purple_debug_info("sipe", "connecting to %s port %d\n",
5446 sip
->realhostname
, sip
->realport
);
5447 /* open tcp connection to the server */
5448 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
5449 sip
->realport
, login_cb
, sip
->gc
);
5451 if (connect_data
== NULL
) {
5452 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
5457 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
5459 PurpleAccount
*account
= sip
->account
;
5460 PurpleConnection
*gc
= sip
->gc
;
5462 if (purple_account_get_bool(account
, "useport", FALSE
)) {
5463 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
5464 port
= purple_account_get_int(account
, "port", 0);
5466 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
5469 sip
->realhostname
= hostname
;
5470 sip
->realport
= port
;
5472 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
5475 /* TODO: is there a good default grow size? */
5476 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
5477 sip
->txbuf
= purple_circ_buffer_new(0);
5479 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
5481 if (!purple_ssl_is_supported()) {
5482 gc
->wants_to_die
= TRUE
;
5483 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5487 purple_debug_info("sipe", "using SSL\n");
5489 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
5490 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
5491 if (sip
->gsc
== NULL
) {
5492 purple_connection_error(gc
, _("Could not create SSL context"));
5495 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
5497 purple_debug_info("sipe", "using UDP\n");
5499 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
5500 if (sip
->query_data
== NULL
) {
5501 purple_connection_error(gc
, _("Could not resolve hostname"));
5505 purple_debug_info("sipe", "using TCP\n");
5506 /* create socket for incoming connections */
5507 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
5508 sipe_tcp_connect_listen_cb
, sip
);
5509 if (sip
->listen_data
== NULL
) {
5510 purple_connection_error(gc
, _("Could not create listen socket"));
5516 /* Service list for autodection */
5517 static const struct sipe_service_data service_autodetect
[] = {
5518 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
5519 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
5520 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
5521 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
5525 /* Service list for SSL/TLS */
5526 static const struct sipe_service_data service_tls
[] = {
5527 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
5528 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
5532 /* Service list for TCP */
5533 static const struct sipe_service_data service_tcp
[] = {
5534 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
5535 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
5539 /* Service list for UDP */
5540 static const struct sipe_service_data service_udp
[] = {
5541 { "sip", "udp", SIPE_TRANSPORT_UDP
},
5545 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
5546 static void resolve_next_service(struct sipe_account_data
*sip
,
5547 const struct sipe_service_data
*start
)
5550 sip
->service_data
= start
;
5552 sip
->service_data
++;
5553 if (sip
->service_data
->service
== NULL
) {
5555 /* Try connecting to the SIP hostname directly */
5556 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
5557 if (sip
->auto_transport
) {
5558 // If SSL is supported, default to using it; OCS servers aren't configured
5559 // by default to accept TCP
5560 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5561 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
5562 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
5565 hostname
= g_strdup(sip
->sipdomain
);
5566 create_connection(sip
, hostname
, 0);
5571 /* Try to resolve next service */
5572 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
5573 sip
->service_data
->transport
,
5578 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
5580 struct sipe_account_data
*sip
= data
;
5582 sip
->srv_query_data
= NULL
;
5584 /* find the host to connect to */
5586 gchar
*hostname
= g_strdup(resp
->hostname
);
5587 int port
= resp
->port
;
5588 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5592 sip
->transport
= sip
->service_data
->type
;
5594 create_connection(sip
, hostname
, port
);
5596 resolve_next_service(sip
, NULL
);
5600 static void sipe_login(PurpleAccount
*account
)
5602 PurpleConnection
*gc
;
5603 struct sipe_account_data
*sip
;
5604 gchar
**signinname_login
, **userserver
, **domain_user
;
5605 const char *transport
;
5607 const char *username
= purple_account_get_username(account
);
5608 gc
= purple_account_get_connection(account
);
5610 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
5611 gc
->wants_to_die
= TRUE
;
5612 purple_connection_error(gc
, _("SIP Exchange username contains invalid characters"));
5616 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
5617 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
5618 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
5620 sip
->account
= account
;
5621 sip
->reregister_set
= FALSE
;
5622 sip
->reauthenticate_set
= FALSE
;
5623 sip
->subscribed
= FALSE
;
5624 sip
->subscribed_buddies
= FALSE
;
5626 signinname_login
= g_strsplit(username
, ",", 2);
5628 userserver
= g_strsplit(signinname_login
[0], "@", 2);
5629 purple_connection_set_display_name(gc
, userserver
[0]);
5630 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
5631 sip
->sipdomain
= g_strdup(userserver
[1]);
5633 if (strpbrk(sip
->username
, " \t\v\r\n") != NULL
) {
5634 gc
->wants_to_die
= TRUE
;
5635 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
5639 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
5640 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
5641 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
5643 sip
->password
= g_strdup(purple_connection_get_password(gc
));
5645 g_strfreev(userserver
);
5646 g_strfreev(domain_user
);
5647 g_strfreev(signinname_login
);
5649 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
5651 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
5653 /* TODO: Set the status correctly. */
5654 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
5656 transport
= purple_account_get_string(account
, "transport", "auto");
5657 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
5658 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
5661 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
5662 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
5663 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
5664 } else if (strcmp(transport
, "auto") == 0) {
5665 sip
->auto_transport
= TRUE
;
5666 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
5667 } else if (strcmp(transport
, "tls") == 0) {
5668 resolve_next_service(sip
, service_tls
);
5669 } else if (strcmp(transport
, "tcp") == 0) {
5670 resolve_next_service(sip
, service_tcp
);
5672 resolve_next_service(sip
, service_udp
);
5676 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
5678 connection_free_all(sip
);
5683 if (sip
->query_data
!= NULL
)
5684 purple_dnsquery_destroy(sip
->query_data
);
5685 sip
->query_data
= NULL
;
5687 if (sip
->srv_query_data
!= NULL
)
5688 purple_srv_cancel(sip
->srv_query_data
);
5689 sip
->srv_query_data
= NULL
;
5691 if (sip
->listen_data
!= NULL
)
5692 purple_network_listen_cancel(sip
->listen_data
);
5693 sip
->listen_data
= NULL
;
5695 if (sip
->gsc
!= NULL
)
5696 purple_ssl_close(sip
->gsc
);
5699 sipe_auth_free(&sip
->registrar
);
5700 sipe_auth_free(&sip
->proxy
);
5703 purple_circ_buffer_destroy(sip
->txbuf
);
5706 g_free(sip
->realhostname
);
5707 sip
->realhostname
= NULL
;
5710 purple_input_remove(sip
->listenpa
);
5712 if (sip
->tx_handler
)
5713 purple_input_remove(sip
->tx_handler
);
5714 sip
->tx_handler
= 0;
5715 if (sip
->resendtimeout
)
5716 purple_timeout_remove(sip
->resendtimeout
);
5717 sip
->resendtimeout
= 0;
5718 if (sip
->timeouts
) {
5719 GSList
*entry
= sip
->timeouts
;
5721 struct scheduled_action
*sched_action
= entry
->data
;
5722 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
5723 purple_timeout_remove(sched_action
->timeout_handler
);
5724 (*sched_action
->destroy
)(sched_action
->payload
);
5725 g_free(sched_action
->name
);
5726 g_free(sched_action
);
5727 entry
= entry
->next
;
5730 g_slist_free(sip
->timeouts
);
5732 if (sip
->allow_events
) {
5733 GSList
*entry
= sip
->allow_events
;
5735 g_free(entry
->data
);
5736 entry
= entry
->next
;
5739 g_slist_free(sip
->allow_events
);
5741 if (sip
->containers
) {
5742 GSList
*entry
= sip
->containers
;
5744 free_container((struct sipe_container
*)entry
->data
);
5745 entry
= entry
->next
;
5748 g_slist_free(sip
->containers
);
5751 g_free(sip
->contact
);
5752 sip
->contact
= NULL
;
5754 g_free(sip
->regcallid
);
5755 sip
->regcallid
= NULL
;
5757 if (sip
->serveraddr
)
5758 g_free(sip
->serveraddr
);
5759 sip
->serveraddr
= NULL
;
5762 sip
->processing_input
= FALSE
;
5766 * A callback for g_hash_table_foreach_remove
5768 static gboolean
sipe_buddy_remove(gpointer key
, gpointer buddy
, gpointer user_data
)
5770 sipe_free_buddy((struct sipe_buddy
*) buddy
);
5772 /* We must return TRUE as the key/value have already been deleted */
5776 static void sipe_close(PurpleConnection
*gc
)
5778 struct sipe_account_data
*sip
= gc
->proto_data
;
5781 /* leave all conversations */
5782 im_session_close_all(sip
);
5785 do_register_exp(sip
, 0);
5787 sipe_connection_cleanup(sip
);
5788 g_free(sip
->sipdomain
);
5789 g_free(sip
->username
);
5790 g_free(sip
->password
);
5791 g_free(sip
->authdomain
);
5792 g_free(sip
->authuser
);
5793 g_free(sip
->status
);
5795 g_hash_table_foreach_steal(sip
->buddies
, sipe_buddy_remove
, NULL
);
5796 g_hash_table_destroy(sip
->buddies
);
5798 g_free(gc
->proto_data
);
5799 gc
->proto_data
= NULL
;
5802 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5804 PurpleAccount
*acct
= purple_connection_get_account(gc
);
5805 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
5806 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
5808 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
5809 purple_conversation_present(conv
);
5813 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5816 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
5817 g_list_nth_data(row
, 0), _("Other Contacts"), g_list_nth_data(row
, 1));
5820 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
5822 PurpleNotifySearchResults
*results
;
5823 PurpleNotifySearchColumn
*column
;
5824 xmlnode
*searchResults
;
5826 int match_count
= 0;
5827 gboolean more
= FALSE
;
5830 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
5832 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5833 if (!searchResults
) {
5834 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5838 results
= purple_notify_searchresults_new();
5840 if (results
== NULL
) {
5841 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5842 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
5844 xmlnode_free(searchResults
);
5848 column
= purple_notify_searchresults_column_new(_("User Name"));
5849 purple_notify_searchresults_column_add(results
, column
);
5851 column
= purple_notify_searchresults_column_new(_("Name"));
5852 purple_notify_searchresults_column_add(results
, column
);
5854 column
= purple_notify_searchresults_column_new(_("Company"));
5855 purple_notify_searchresults_column_add(results
, column
);
5857 column
= purple_notify_searchresults_column_new(_("Country"));
5858 purple_notify_searchresults_column_add(results
, column
);
5860 column
= purple_notify_searchresults_column_new(_("Email"));
5861 purple_notify_searchresults_column_add(results
, column
);
5863 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
5866 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
5867 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
5868 g_strfreev(uri_parts
);
5870 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
5871 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
5872 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
5873 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
5875 purple_notify_searchresults_row_add(results
, row
);
5879 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
5880 char *data
= xmlnode_get_data_unescaped(mrow
);
5881 more
= (g_strcasecmp(data
, "true") == 0);
5885 secondary
= g_strdup_printf(
5886 dngettext(GETTEXT_PACKAGE
,
5887 "Found %d contact%s:",
5888 "Found %d contacts%s:", match_count
),
5889 match_count
, more
? _(" (more matched your query)") : "");
5891 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
5892 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
5893 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
5896 xmlnode_free(searchResults
);
5900 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
5902 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
5903 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
5907 PurpleRequestField
*field
= entries
->data
;
5908 const char *id
= purple_request_field_get_id(field
);
5909 const char *value
= purple_request_field_string_get_value(field
);
5911 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
5913 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
5914 } while ((entries
= g_list_next(entries
)) != NULL
);
5918 gchar
*query
= g_strjoinv(NULL
, attrs
);
5919 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
5920 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
5921 send_soap_request_with_cb(gc
->proto_data
, body
,
5922 (TransCallback
) process_search_contact_response
, NULL
);
5930 static void sipe_show_find_contact(PurplePluginAction
*action
)
5932 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
5933 PurpleRequestFields
*fields
;
5934 PurpleRequestFieldGroup
*group
;
5935 PurpleRequestField
*field
;
5937 fields
= purple_request_fields_new();
5938 group
= purple_request_field_group_new(NULL
);
5939 purple_request_fields_add_group(fields
, group
);
5941 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
5942 purple_request_field_group_add_field(group
, field
);
5943 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
5944 purple_request_field_group_add_field(group
, field
);
5945 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
5946 purple_request_field_group_add_field(group
, field
);
5947 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
5948 purple_request_field_group_add_field(group
, field
);
5950 purple_request_fields(gc
,
5952 _("Search for a Contact"),
5953 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5955 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
5957 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
5960 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
5963 PurplePluginAction
*act
;
5965 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
5966 menu
= g_list_prepend(menu
, act
);
5968 menu
= g_list_reverse(menu
);
5973 static void dummy_permit_deny(PurpleConnection
*gc
)
5977 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
5983 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
5989 static char *sipe_status_text(PurpleBuddy
*buddy
)
5991 struct sipe_account_data
*sip
;
5992 struct sipe_buddy
*sbuddy
;
5995 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5996 if (sip
) //happens on pidgin exit
5998 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5999 if (sbuddy
&& sbuddy
->annotation
)
6001 text
= g_strdup(sbuddy
->annotation
);
6008 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
6010 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
6011 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
6012 struct sipe_account_data
*sip
;
6013 struct sipe_buddy
*sbuddy
;
6014 char *annotation
= NULL
;
6016 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
6017 if (sip
) //happens on pidgin exit
6019 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
6022 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
6027 if (purple_presence_is_online(presence
))
6029 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
6034 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
6041 sipe_get_account_text_table(PurpleAccount
*account
)
6044 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
6045 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
6049 static PurpleBuddy
*
6050 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
6053 const gchar
*server_alias
, *email
;
6054 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
6056 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
6058 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
6060 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
6062 purple_blist_server_alias_buddy(clone
, server_alias
);
6065 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
6067 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
6070 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
6072 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
6077 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
6079 PurpleBuddy
*buddy
, *b
;
6080 PurpleConnection
*gc
;
6081 PurpleGroup
* group
= purple_find_group(group_name
);
6083 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
6085 buddy
= (PurpleBuddy
*)node
;
6087 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
6088 gc
= purple_account_get_connection(buddy
->account
);
6090 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
6092 b
= purple_blist_add_buddy_clone(group
, buddy
);
6095 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
6099 sipe_buddy_menu_chat_new_cb(PurpleBuddy
*buddy
)
6101 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6102 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6103 gchar
*chat_name
= g_strdup_printf(_("Chat #%d"), ++sip
->chat_seq
);
6104 struct sip_im_session
*session
;
6106 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy
->name
);
6108 session
= create_chat_session(sip
);
6109 session
->roster_manager
= g_strdup(self
);
6111 session
->conv
= serv_got_joined_chat(buddy
->account
->gc
, session
->chat_id
, g_strdup(chat_name
));
6112 session
->chat_name
= g_strdup(chat_name
);
6113 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
), self
, NULL
, PURPLE_CBFLAGS_NONE
, FALSE
);
6114 sipe_invite(sip
, session
, buddy
->name
, NULL
, NULL
, FALSE
);
6121 sipe_is_election_finished(struct sipe_account_data
*sip
,
6122 struct sip_im_session
*session
)
6124 struct sip_dialog
*dialog
;
6126 gboolean res
= TRUE
;
6128 entry
= session
->dialogs
;
6130 dialog
= entry
->data
;
6131 if (dialog
->election_vote
== 0) {
6135 entry
= entry
->next
;
6139 session
->is_voting_in_progress
= FALSE
;
6145 sipe_election_start(struct sipe_account_data
*sip
,
6146 struct sip_im_session
*session
)
6148 struct sip_dialog
*dialog
;
6150 int election_timeout
;
6152 if (session
->is_voting_in_progress
) {
6153 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6156 session
->is_voting_in_progress
= TRUE
;
6158 session
->bid
= rand();
6160 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session
->bid
);
6162 /* reset election_vote for each chat participant */
6163 entry
= session
->dialogs
;
6165 dialog
= entry
->data
;
6166 dialog
->election_vote
= 0;
6167 entry
= entry
->next
;
6170 entry
= session
->dialogs
;
6172 dialog
= entry
->data
;
6173 /* send RequestRM to each chat participant*/
6174 sipe_send_election_request_rm(sip
, session
, dialog
->with
, session
->bid
);
6175 entry
= entry
->next
;
6178 election_timeout
= 15; /* sec */
6179 sipe_schedule_action("<+election-result>", election_timeout
, sipe_election_result
, NULL
, sip
, session
);
6183 * @param who a URI to whom to invite to chat
6186 sipe_invite_to_chat(struct sipe_account_data
*sip
,
6187 struct sip_im_session
*session
,
6190 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6192 if (session
->roster_manager
) {
6193 if (!strcmp(session
->roster_manager
, self
)) {
6194 sipe_invite(sip
, session
, who
, NULL
, NULL
, FALSE
);
6196 sipe_refer(sip
, session
, who
);
6199 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6201 session
->pending_invite_queue
= slist_insert_unique_sorted(
6202 session
->pending_invite_queue
, g_strdup(who
), (GCompareFunc
)strcmp
);
6204 sipe_election_start(sip
, session
);
6211 sipe_process_pending_invite_queue(struct sipe_account_data
*sip
,
6212 struct sip_im_session
*session
)
6215 GSList
*entry
= session
->pending_invite_queue
;
6218 invitee
= entry
->data
;
6219 sipe_invite_to_chat(sip
, session
, invitee
);
6220 entry
= session
->pending_invite_queue
= g_slist_remove(session
->pending_invite_queue
, invitee
);
6226 sipe_election_result(struct sipe_account_data
*sip
,
6229 struct sip_im_session
*session
= (struct sip_im_session
*)sess
;
6230 struct sip_dialog
*dialog
;
6233 gboolean has_won
= TRUE
;
6235 if (session
->roster_manager
) {
6236 purple_debug_info("sipe",
6237 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session
->roster_manager
);
6241 session
->is_voting_in_progress
= FALSE
;
6243 entry
= session
->dialogs
;
6245 dialog
= entry
->data
;
6246 if (dialog
->election_vote
< 0) {
6248 rival
= dialog
->with
;
6251 entry
= entry
->next
;
6255 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6257 session
->roster_manager
= g_strdup_printf("sip:%s", sip
->username
);
6259 entry
= session
->dialogs
;
6261 dialog
= entry
->data
;
6262 /* send SetRM to each chat participant*/
6263 sipe_send_election_set_rm(sip
, session
, dialog
->with
);
6264 entry
= entry
->next
;
6267 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival
);
6271 sipe_process_pending_invite_queue(sip
, session
);
6275 sipe_buddy_menu_chat_invite_cb(PurpleBuddy
*buddy
, const char *chat_name
)
6277 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6278 struct sip_im_session
*session
;
6280 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy
->name
);
6281 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name
);
6283 session
= find_chat_session_by_name(sip
, chat_name
);
6285 sipe_invite_to_chat(sip
, session
, buddy
->name
);
6289 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
6292 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
6294 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
6297 char *mailto
= g_strdup_printf("mailto:%s", email
);
6298 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
6302 char *const parmList
[] = {mailto
, NULL
};
6303 if ((pid
= fork()) == -1)
6305 purple_debug_info("sipe", "fork() error\n");
6309 execvp("xdg-email", parmList
);
6310 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6318 //@TODO resolve env variable %WINDIR% first
6319 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
6322 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
6331 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
6336 * A menu which appear when right-clicking on buddy in contact list.
6339 sipe_buddy_menu(PurpleBuddy
*buddy
)
6341 PurpleBlistNode
*g_node
;
6342 PurpleGroup
*group
, *gr_parent
;
6343 PurpleMenuAction
*act
;
6345 GList
*menu_groups
= NULL
;
6346 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6347 struct sip_im_session
*session
;
6349 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6351 act
= purple_menu_action_new(_("New Chat"),
6352 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb
),
6354 menu
= g_list_prepend(menu
, act
);
6356 entry
= sip
->im_sessions
;
6358 session
= entry
->data
;
6359 if (strcmp(self
, buddy
->name
) && session
->chat_name
&& !get_dialog(session
, buddy
->name
)) {
6360 gchar
*label
= g_strdup_printf(_("Invite to '%s'"), session
->chat_name
);
6361 act
= purple_menu_action_new(label
,
6362 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb
),
6363 g_strdup(session
->chat_name
), NULL
);
6365 menu
= g_list_prepend(menu
, act
);
6367 entry
= entry
->next
;
6370 act
= purple_menu_action_new(_("Send Email..."),
6371 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
6373 menu
= g_list_prepend(menu
, act
);
6375 gr_parent
= purple_buddy_get_group(buddy
);
6376 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
6377 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
6380 group
= (PurpleGroup
*)g_node
;
6381 if (group
== gr_parent
)
6384 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
6387 act
= purple_menu_action_new(purple_group_get_name(group
),
6388 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
6390 menu_groups
= g_list_prepend(menu_groups
, act
);
6392 menu_groups
= g_list_reverse(menu_groups
);
6394 act
= purple_menu_action_new(_("Copy to"),
6397 menu
= g_list_prepend(menu
, act
);
6398 menu
= g_list_reverse(menu
);
6405 sipe_blist_node_menu(PurpleBlistNode
*node
)
6407 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
6408 return sipe_buddy_menu((PurpleBuddy
*) node
);
6415 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
6417 gboolean ret
= TRUE
;
6418 char *username
= (char *)trans
->payload
;
6420 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
6421 PurpleBuddy
*pbuddy
;
6422 struct sipe_buddy
*sbuddy
;
6424 char *server_alias
= NULL
;
6426 const char *device_name
= NULL
;
6428 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
6430 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
6431 alias
= purple_buddy_get_local_alias(pbuddy
);
6435 //will query buddy UA's capabilities and send answer to log
6436 sipe_options_request(sip
, username
);
6438 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
6441 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
6445 if (msg
->response
!= 200) {
6446 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
6448 xmlnode
*searchResults
;
6451 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
6452 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
6453 if (!searchResults
) {
6454 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6455 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
6456 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
6457 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
6458 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
6459 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
6460 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
6461 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
6462 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
6463 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
6464 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
6465 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
6466 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
6467 if (!email
|| strcmp("", email
)) {
6468 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
6469 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
6473 xmlnode_free(searchResults
);
6476 purple_notify_user_info_add_section_break(info
);
6478 if (!server_alias
|| !strcmp("", server_alias
)) {
6479 g_free(server_alias
);
6480 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
6482 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
6486 // same as server alias, do not present
6487 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
6490 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
6493 if (!email
|| !strcmp("", email
)) {
6495 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
6497 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
6503 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
6506 /* show a buddy's user info in a nice dialog box */
6507 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
6508 username
, /* buddy's username */
6510 NULL
, /* callback called when dialog closed */
6511 NULL
); /* userdata for callback */
6517 * AD search first, LDAP based
6519 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
6521 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
6522 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
6524 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
6525 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
6526 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
6531 static PurplePlugin
*my_protocol
= NULL
;
6533 static PurplePluginProtocolInfo prpl_info
=
6536 NULL
, /* user_splits */
6537 NULL
, /* protocol_options */
6538 NO_BUDDY_ICONS
, /* icon_spec */
6539 sipe_list_icon
, /* list_icon */
6540 NULL
, /* list_emblems */
6541 sipe_status_text
, /* status_text */
6542 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
6543 sipe_status_types
, /* away_states */
6544 sipe_blist_node_menu
, /* blist_node_menu */
6545 NULL
, /* chat_info */
6546 NULL
, /* chat_info_defaults */
6547 sipe_login
, /* login */
6548 sipe_close
, /* close */
6549 sipe_im_send
, /* send_im */
6550 NULL
, /* set_info */ // TODO maybe
6551 sipe_send_typing
, /* send_typing */
6552 sipe_get_info
, /* get_info */
6553 sipe_set_status
, /* set_status */
6554 NULL
, /* set_idle */
6555 NULL
, /* change_passwd */
6556 sipe_add_buddy
, /* add_buddy */
6557 NULL
, /* add_buddies */
6558 sipe_remove_buddy
, /* remove_buddy */
6559 NULL
, /* remove_buddies */
6560 sipe_add_permit
, /* add_permit */
6561 sipe_add_deny
, /* add_deny */
6562 sipe_add_deny
, /* rem_permit */
6563 sipe_add_permit
, /* rem_deny */
6564 dummy_permit_deny
, /* set_permit_deny */
6565 NULL
, /* join_chat */
6566 NULL
, /* reject_chat */
6567 NULL
, /* get_chat_name */
6568 NULL
, /* chat_invite */
6569 sipe_chat_leave
, /* chat_leave */
6570 NULL
, /* chat_whisper */
6571 sipe_chat_send
, /* chat_send */
6572 sipe_keep_alive
, /* keepalive */
6573 NULL
, /* register_user */
6574 NULL
, /* get_cb_info */ // deprecated
6575 NULL
, /* get_cb_away */ // deprecated
6576 sipe_alias_buddy
, /* alias_buddy */
6577 sipe_group_buddy
, /* group_buddy */
6578 sipe_rename_group
, /* rename_group */
6579 NULL
, /* buddy_free */
6580 sipe_convo_closed
, /* convo_closed */
6581 purple_normalize_nocase
, /* normalize */
6582 NULL
, /* set_buddy_icon */
6583 sipe_remove_group
, /* remove_group */
6584 NULL
, /* get_cb_real_name */ // TODO?
6585 NULL
, /* set_chat_topic */
6586 NULL
, /* find_blist_chat */
6587 NULL
, /* roomlist_get_list */
6588 NULL
, /* roomlist_cancel */
6589 NULL
, /* roomlist_expand_category */
6590 NULL
, /* can_receive_file */
6591 NULL
, /* send_file */
6592 NULL
, /* new_xfer */
6593 NULL
, /* offline_message */
6594 NULL
, /* whiteboard_prpl_ops */
6595 sipe_send_raw
, /* send_raw */
6596 NULL
, /* roomlist_room_serialize */
6597 NULL
, /* unregister_user */
6598 NULL
, /* send_attention */
6599 NULL
, /* get_attention_types */
6601 sizeof(PurplePluginProtocolInfo
), /* struct_size */
6602 sipe_get_account_text_table
, /* get_account_text_table */
6606 static PurplePluginInfo info
= {
6607 PURPLE_PLUGIN_MAGIC
,
6608 PURPLE_MAJOR_VERSION
,
6609 PURPLE_MINOR_VERSION
,
6610 PURPLE_PLUGIN_PROTOCOL
, /**< type */
6611 NULL
, /**< ui_requirement */
6613 NULL
, /**< dependencies */
6614 PURPLE_PRIORITY_DEFAULT
, /**< priority */
6615 "prpl-sipe", /**< id */
6616 "Microsoft LCS/OCS", /**< name */
6617 VERSION
, /**< version */
6618 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6619 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6620 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6621 "Gabriel Burt <gburt@novell.com>", /**< author */
6622 PURPLE_WEBSITE
, /**< homepage */
6623 sipe_plugin_load
, /**< load */
6624 sipe_plugin_unload
, /**< unload */
6625 sipe_plugin_destroy
, /**< destroy */
6626 NULL
, /**< ui_info */
6627 &prpl_info
, /**< extra_info */
6636 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
6640 entry
= prpl_info
.protocol_options
;
6642 purple_account_option_destroy(entry
->data
);
6643 entry
= g_list_delete_link(entry
, entry
);
6645 prpl_info
.protocol_options
= NULL
;
6647 entry
= prpl_info
.user_splits
;
6649 purple_account_user_split_destroy(entry
->data
);
6650 entry
= g_list_delete_link(entry
, entry
);
6652 prpl_info
.user_splits
= NULL
;
6655 static void init_plugin(PurplePlugin
*plugin
)
6657 PurpleAccountUserSplit
*split
;
6658 PurpleAccountOption
*option
;
6663 purple_debug_info(PACKAGE
, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
6664 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s\n",
6665 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
6666 textdomain(GETTEXT_PACKAGE
);
6669 purple_plugin_register(plugin
);
6671 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
6672 purple_account_user_split_set_reverse(split
, FALSE
);
6673 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
6675 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
6676 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6677 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6678 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6680 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
6681 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6682 // Translators: noun (networking port)
6683 option
= purple_account_option_int_new(_("Port"), "port", 5061);
6684 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6686 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
6687 purple_account_option_add_list_item(option
, _("Auto"), "auto");
6688 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
6689 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
6690 purple_account_option_add_list_item(option
, _("UDP"), "udp");
6691 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6693 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6694 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6696 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
6697 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6700 option
= purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE
);
6701 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6703 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6704 * No login/password is taken into account if this option present,
6705 * instead used default credentials stored in OS.
6707 option
= purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE
);
6708 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6710 my_protocol
= plugin
;
6713 /* I had to redefined the function for it load, but works */
6714 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
6715 plugin
->info
= &(info
);
6716 init_plugin((plugin
));
6717 sipe_plugin_load((plugin
));
6718 return purple_plugin_register(plugin
);