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"
79 #include "sipe-sign.h"
83 /* Keep in sync with sipe_transport_type! */
84 static const char *transport_descriptor
[] = { "tls", "tcp", "udp" };
85 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
87 /* Status identifiers (see also: sipe_status_types()) */
88 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
89 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
90 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
91 /* PURPLE_STATUS_UNAVAILABLE: */
92 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
93 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
94 #define SIPE_STATUS_ID_ONPHONE "on-the-phone" /* On The Phone */
95 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
96 /* PURPLE_STATUS_AWAY: */
97 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
98 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
99 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
100 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
101 /* ??? PURPLE_STATUS_MOBILE */
102 /* ??? PURPLE_STATUS_TUNE */
104 /* Action name templates */
105 #define ACTION_NAME_PRESENCE "<presence><%s>"
107 static char *gentag()
109 return g_strdup_printf("%04d%04d", rand() & 0xFFFF, rand() & 0xFFFF);
112 static gchar
*get_epid(struct sipe_account_data
*sip
)
115 sip
->epid
= sipe_uuid_get_macaddr(purple_network_get_my_ip(-1));
117 return g_strdup(sip
->epid
);
120 static char *genbranch()
122 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
123 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
124 rand() & 0xFFFF, rand() & 0xFFFF);
127 static char *gencallid()
129 return g_strdup_printf("%04Xg%04Xa%04Xi%04Xm%04Xt%04Xb%04Xx%04Xx",
130 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
131 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
132 rand() & 0xFFFF, rand() & 0xFFFF);
135 static gchar
*find_tag(const gchar
*hdr
)
137 gchar
* tag
= sipmsg_find_part_of_header (hdr
, "tag=", ";", NULL
);
139 // In case it's at the end and there's no trailing ;
140 tag
= sipmsg_find_part_of_header (hdr
, "tag=", NULL
, NULL
);
146 static const char *sipe_list_icon(PurpleAccount
*a
, PurpleBuddy
*b
)
151 static void sipe_plugin_destroy(PurplePlugin
*plugin
);
153 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
);
155 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
156 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
159 static void sipe_close(PurpleConnection
*gc
);
161 static void send_presence_status(struct sipe_account_data
*sip
);
163 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
165 static void sipe_keep_alive(PurpleConnection
*gc
)
167 struct sipe_account_data
*sip
= gc
->proto_data
;
168 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
169 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
170 gchar buf
[2] = {0, 0};
171 purple_debug_info("sipe", "sending keep alive\n");
172 sendto(sip
->fd
, buf
, 1, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
));
174 time_t now
= time(NULL
);
175 if ((sip
->keepalive_timeout
> 0) &&
176 ((guint
) (now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
177 #if PURPLE_VERSION_CHECK(2,4,0)
178 && ((guint
) (now
- gc
->last_received
) >= sip
->keepalive_timeout
)
181 purple_debug_info("sipe", "sending keep alive %d\n",sip
->keepalive_timeout
);
182 sendout_pkt(gc
, "\r\n\r\n");
183 sip
->last_keepalive
= now
;
188 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
190 struct sip_connection
*ret
= NULL
;
191 GSList
*entry
= sip
->openconns
;
194 if (ret
->fd
== fd
) return ret
;
200 static void sipe_auth_free(struct sip_auth
*auth
)
202 g_free(auth
->opaque
);
206 g_free(auth
->target
);
208 auth
->type
= AUTH_TYPE_UNSET
;
211 g_free(auth
->gssapi_data
);
212 auth
->gssapi_data
= NULL
;
213 sip_sec_destroy_context(auth
->gssapi_context
);
214 auth
->gssapi_context
= NULL
;
217 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
219 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
221 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
225 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
227 struct sip_connection
*conn
= connection_find(sip
, fd
);
229 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
230 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
236 static void connection_free_all(struct sipe_account_data
*sip
)
238 struct sip_connection
*ret
= NULL
;
239 GSList
*entry
= sip
->openconns
;
242 connection_remove(sip
, ret
->fd
);
243 entry
= sip
->openconns
;
247 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
250 const char *authuser
= sip
->authuser
;
254 if (!authuser
|| strlen(authuser
) < 1) {
255 authuser
= sip
->username
;
258 if (auth
->type
== AUTH_TYPE_NTLM
|| auth
->type
== AUTH_TYPE_KERBEROS
) { /* NTLM or Kerberos */
259 gchar
*auth_protocol
= (auth
->type
== AUTH_TYPE_NTLM
? "NTLM" : "Kerberos");
261 // If we have a signature for the message, include that
262 if (msg
->signature
) {
263 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
);
266 if ((auth
->type
== AUTH_TYPE_NTLM
&& auth
->nc
== 3 && auth
->gssapi_data
&& auth
->gssapi_context
== NULL
)
267 || (auth
->type
== AUTH_TYPE_KERBEROS
&& auth
->nc
== 3)) {
271 gssapi_data
= sip_sec_init_context(&(auth
->gssapi_context
),
274 purple_account_get_bool(sip
->account
, "sso", TRUE
),
275 sip
->authdomain
? sip
->authdomain
: "",
280 if (!gssapi_data
|| !auth
->gssapi_context
)
283 opaque
= (auth
->type
== AUTH_TYPE_NTLM
? g_strdup_printf(", opaque=\"%s\"", auth
->opaque
) : g_strdup(""));
284 ret
= g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol
, opaque
, auth
->realm
, auth
->target
, gssapi_data
);
290 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol
, auth
->realm
, auth
->target
);
292 } else { /* Digest */
294 /* Calculate new session key */
296 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Digest nonce: %s realm: %s\n", auth
->gssapi_data
, auth
->realm
);
297 auth
->opaque
= purple_cipher_http_digest_calculate_session_key("md5",
298 authuser
, auth
->realm
, sip
->password
,
299 auth
->gssapi_data
, NULL
);
302 sprintf(noncecount
, "%08d", auth
->nc
++);
303 response
= purple_cipher_http_digest_calculate_response("md5",
304 msg
->method
, msg
->target
, NULL
, NULL
,
305 auth
->gssapi_data
, noncecount
, NULL
,
307 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Digest response %s\n", response
);
309 ret
= g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%s\", response=\"%s\"", authuser
, auth
->realm
, auth
->gssapi_data
, msg
->target
, noncecount
, response
);
315 static char *parse_attribute(const char *attrname
, const char *source
)
317 const char *tmp
, *tmp2
;
319 int len
= strlen(attrname
);
321 if (!strncmp(source
, attrname
, len
)) {
323 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
325 retval
= g_strndup(tmp
, tmp2
- tmp
);
327 retval
= g_strdup(tmp
);
333 static void fill_auth(struct sipe_account_data
*sip
, gchar
*hdr
, struct sip_auth
*auth
)
339 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
343 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
344 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type NTLM\n");
345 auth
->type
= AUTH_TYPE_NTLM
;
348 } else if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
349 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type Kerberos\n");
350 auth
->type
= AUTH_TYPE_KERBEROS
;
354 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type Digest\n");
355 auth
->type
= AUTH_TYPE_DIGEST
;
359 parts
= g_strsplit(hdr
, "\", ", 0);
360 for (i
= 0; parts
[i
]; i
++) {
363 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
365 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
366 g_free(auth
->gssapi_data
);
367 auth
->gssapi_data
= tmp
;
369 if (auth
->type
== AUTH_TYPE_NTLM
) {
370 /* NTLM module extracts nonce from gssapi-data */
374 } else if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
375 /* Only used with AUTH_TYPE_DIGEST */
376 g_free(auth
->gssapi_data
);
377 auth
->gssapi_data
= tmp
;
378 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
379 g_free(auth
->opaque
);
381 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
385 if (auth
->type
== AUTH_TYPE_DIGEST
) {
386 /* Throw away old session key */
387 g_free(auth
->opaque
);
392 } else if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
393 g_free(auth
->target
);
402 static void sipe_canwrite_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
404 PurpleConnection
*gc
= data
;
405 struct sipe_account_data
*sip
= gc
->proto_data
;
409 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
411 if (max_write
== 0) {
412 if (sip
->tx_handler
!= 0){
413 purple_input_remove(sip
->tx_handler
);
419 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
421 if (written
< 0 && errno
== EAGAIN
)
423 else if (written
<= 0) {
424 /*TODO: do we really want to disconnect on a failure to write?*/
425 purple_connection_error(gc
, _("Could not write"));
429 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
432 static void sipe_canwrite_cb_ssl(gpointer data
, gint src
, PurpleInputCondition cond
)
434 PurpleConnection
*gc
= data
;
435 struct sipe_account_data
*sip
= gc
->proto_data
;
439 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
441 if (max_write
== 0) {
442 if (sip
->tx_handler
!= 0) {
443 purple_input_remove(sip
->tx_handler
);
449 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
451 if (written
< 0 && errno
== EAGAIN
)
453 else if (written
<= 0) {
454 /*TODO: do we really want to disconnect on a failure to write?*/
455 purple_connection_error(gc
, _("Could not write"));
459 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
462 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
464 static void send_later_cb(gpointer data
, gint source
, const gchar
*error
)
466 PurpleConnection
*gc
= data
;
467 struct sipe_account_data
*sip
;
468 struct sip_connection
*conn
;
470 if (!PURPLE_CONNECTION_IS_VALID(gc
))
478 purple_connection_error(gc
, _("Could not connect"));
482 sip
= gc
->proto_data
;
484 sip
->connecting
= FALSE
;
485 sip
->last_keepalive
= time(NULL
);
487 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
489 /* If there is more to write now, we need to register a handler */
490 if (sip
->txbuf
->bufused
> 0)
491 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
493 conn
= connection_create(sip
, source
);
494 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
497 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
499 struct sipe_account_data
*sip
;
500 struct sip_connection
*conn
;
502 if (!PURPLE_CONNECTION_IS_VALID(gc
))
504 if (gsc
) purple_ssl_close(gsc
);
508 sip
= gc
->proto_data
;
511 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
512 sip
->connecting
= FALSE
;
513 sip
->last_keepalive
= time(NULL
);
515 conn
= connection_create(sip
, gsc
->fd
);
517 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
522 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
524 PurpleConnection
*gc
= data
;
525 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
526 if (sip
== NULL
) return;
528 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
530 /* If there is more to write now */
531 if (sip
->txbuf
->bufused
> 0) {
532 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
537 static void sendlater(PurpleConnection
*gc
, const char *buf
)
539 struct sipe_account_data
*sip
= gc
->proto_data
;
541 if (!sip
->connecting
) {
542 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
543 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
544 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
546 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
547 purple_connection_error(gc
, _("Couldn't create socket"));
550 sip
->connecting
= TRUE
;
553 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
554 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
556 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
559 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
561 struct sipe_account_data
*sip
= gc
->proto_data
;
562 time_t currtime
= time(NULL
);
563 int writelen
= strlen(buf
);
565 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "\n\nsending - %s\n######\n%s\n######\n\n", ctime(&currtime
), buf
);
566 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
567 if (sendto(sip
->fd
, buf
, writelen
, 0, (struct sockaddr
*)&sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
568 purple_debug_info("sipe", "could not send packet\n");
577 if (sip
->tx_handler
) {
582 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
584 ret
= write(sip
->fd
, buf
, writelen
);
588 if (ret
< 0 && errno
== EAGAIN
)
590 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
595 if (ret
< writelen
) {
596 if (!sip
->tx_handler
){
598 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
601 sip
->tx_handler
= purple_input_add(sip
->fd
,
602 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
607 /* XXX: is it OK to do this? You might get part of a request sent
608 with part of another. */
609 if (sip
->txbuf
->bufused
> 0)
610 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
612 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
618 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
620 sendout_pkt(gc
, buf
);
624 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
626 GSList
*tmp
= msg
->headers
;
629 GString
*outstr
= g_string_new("");
630 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
632 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
633 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
634 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
635 tmp
= g_slist_next(tmp
);
637 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
638 sendout_pkt(sip
->gc
, outstr
->str
);
639 g_string_free(outstr
, TRUE
);
642 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
645 if (sip
->registrar
.gssapi_context
) {
646 struct sipmsg_breakdown msgbd
;
647 gchar
*signature_input_str
;
649 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
650 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
651 sip
->registrar
.ntlm_num
++;
652 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
653 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
654 if (signature_input_str
!= NULL
) {
655 char *signature_hex
= sip_sec_make_signature(sip
->registrar
.gssapi_context
, signature_input_str
);
656 msg
->signature
= signature_hex
;
657 msg
->rand
= g_strdup(msgbd
.rand
);
658 msg
->num
= g_strdup(msgbd
.num
);
659 g_free(signature_input_str
);
661 sipmsg_breakdown_free(&msgbd
);
664 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
665 buf
= auth_header(sip
, &sip
->registrar
, msg
);
667 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
669 sipmsg_add_header(msg
, "Authorization", buf
);
672 sipmsg_add_header_pos(msg
, "Authorization", buf
, 5); // What's the point in 5?
676 } 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")) {
677 sip
->registrar
.nc
= 3;
679 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
681 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
684 sip
->registrar
.type
= AUTH_TYPE_KERBEROS
;
689 buf
= auth_header(sip
, &sip
->registrar
, msg
);
690 sipmsg_add_header_pos(msg
, "Proxy-Authorization", buf
, 5);
693 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
697 static char *get_contact(struct sipe_account_data
*sip
)
699 return g_strdup(sip
->contact
);
704 static char *get_contact_service(struct sipe_account_data *sip)
706 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()));
707 //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);
711 static void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
712 const char *text
, const char *body
)
716 GString
*outstr
= g_string_new("");
717 struct sipe_account_data
*sip
= gc
->proto_data
;
721 sipmsg_remove_header(msg
, "ms-user-data");
723 contact
= get_contact(sip
);
724 sipmsg_remove_header(msg
, "Contact");
725 sipmsg_add_header(msg
, "Contact", contact
);
728 /* When sending the acknowlegements and errors, the content length from the original
729 message is still here, but there is no body; we need to make sure we're sending the
730 correct content length */
731 sipmsg_remove_header(msg
, "Content-Length");
734 sprintf(len
, "%" G_GSIZE_FORMAT
, strlen(body
));
735 sipmsg_add_header(msg
, "Content-Length", len
);
737 sipmsg_remove_header(msg
, "Content-Type");
738 sipmsg_add_header(msg
, "Content-Length", "0");
741 msg
->response
= code
;
743 sipmsg_remove_header(msg
, "Authentication-Info");
744 sign_outgoing_message(msg
, sip
, msg
->method
);
746 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
749 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
750 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
752 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
753 tmp
= g_slist_next(tmp
);
755 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
756 sendout_pkt(gc
, outstr
->str
);
757 g_string_free(outstr
, TRUE
);
760 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
762 if (trans
->msg
) sipmsg_free(trans
->msg
);
763 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
767 static struct transaction
*
768 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
770 struct transaction
*trans
= g_new0(struct transaction
, 1);
771 trans
->time
= time(NULL
);
772 trans
->msg
= (struct sipmsg
*)msg
;
773 trans
->cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
774 trans
->callback
= callback
;
775 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
779 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
781 struct transaction
*trans
;
782 GSList
*transactions
= sip
->transactions
;
783 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
785 while (transactions
) {
786 trans
= transactions
->data
;
787 if (!strcmp(trans
->cseq
, cseq
)) {
790 transactions
= transactions
->next
;
796 static struct transaction
*
797 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
798 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
799 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
801 struct sipe_account_data
*sip
= gc
->proto_data
;
802 const char *addh
= "";
805 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
806 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
807 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
808 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
809 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
810 gchar
*useragent
= (gchar
*)purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
);
811 gchar
*route
= strdup("");
812 gchar
*epid
= get_epid(sip
); // TODO generate one per account/login
813 int cseq
= dialog
? ++dialog
->cseq
:
814 /* This breaks OCS2007: own presence, contact search, ?
815 1 .* as Call-Id is new in this case */
817 struct transaction
*trans
;
819 if (dialog
&& dialog
->routes
)
821 GSList
*iter
= dialog
->routes
;
826 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
828 iter
= g_slist_next(iter
);
832 if (!ourtag
&& !dialog
) {
836 if (!strcmp(method
, "REGISTER")) {
837 if (sip
->regcallid
) {
839 callid
= g_strdup(sip
->regcallid
);
841 sip
->regcallid
= g_strdup(callid
);
845 if (addheaders
) addh
= addheaders
;
847 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
848 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
849 "From: <sip:%s>%s%s;epid=%s\r\n"
850 "To: <%s>%s%s%s%s\r\n"
851 "Max-Forwards: 70\r\n"
856 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
858 dialog
&& dialog
->request
? dialog
->request
: url
,
859 TRANSPORT_DESCRIPTOR
,
860 purple_network_get_my_ip(-1),
862 branch
? ";branch=" : "",
863 branch
? branch
: "",
865 ourtag
? ";tag=" : "",
866 ourtag
? ourtag
: "",
869 theirtag
? ";tag=" : "",
870 theirtag
? theirtag
: "",
871 theirepid
? ";epid=" : "",
872 theirepid
? theirepid
: "",
879 body
? strlen(body
) : 0,
883 //printf ("parsing msg buf:\n%s\n\n", buf);
884 msg
= sipmsg_parse_msg(buf
);
895 sign_outgoing_message (msg
, sip
, method
);
897 buf
= sipmsg_to_string (msg
);
899 /* add to ongoing transactions */
900 trans
= transactions_add_buf(sip
, msg
, tc
);
901 sendout_pkt(gc
, buf
);
907 static void send_soap_request_with_cb(struct sipe_account_data
*sip
, gchar
*body
, TransCallback callback
, void * payload
)
909 gchar
*from
= g_strdup_printf("sip:%s", sip
->username
);
910 gchar
*contact
= get_contact(sip
);
911 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
912 "Content-Type: application/SOAP+xml\r\n",contact
);
914 struct transaction
* tr
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
915 tr
->payload
= payload
;
922 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
924 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
927 static char *get_contact_register(struct sipe_account_data
*sip
)
929 char *epid
= get_epid(sip
);
930 char *uuid
= generateUUIDfromEPID(epid
);
931 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
);
937 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
939 char *expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
940 char *uri
= g_strdup_printf("sip:%s", sip
->sipdomain
);
941 char *to
= g_strdup_printf("sip:%s", sip
->username
);
942 char *contact
= get_contact_register(sip
);
943 char *hdr
= g_strdup_printf("Contact: %s\r\n"
944 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
945 "Event: registration\r\n"
946 "Allow-Events: presence\r\n"
947 "ms-keep-alive: UAC;hop-hop=yes\r\n"
948 "%s", contact
, expires
);
952 sip
->registerstatus
= 1;
954 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
955 process_register_response
);
962 static void do_register_cb(struct sipe_account_data
*sip
, void *unused
)
964 do_register_exp(sip
, -1);
965 sip
->reregister_set
= FALSE
;
968 static void do_register(struct sipe_account_data
*sip
)
970 do_register_exp(sip
, -1);
974 * Returns URI from provided To or From header.
976 * Needs to g_free() after use.
978 * @return URI with sip: prefix
980 static gchar
*parse_from(const gchar
*hdr
)
983 const gchar
*tmp
, *tmp2
= hdr
;
985 if (!hdr
) return NULL
;
986 purple_debug_info("sipe", "parsing address out of %s\n", hdr
);
987 tmp
= strchr(hdr
, '<');
989 /* i hate the different SIP UA behaviours... */
990 if (tmp
) { /* sip address in <...> */
992 tmp
= strchr(tmp2
, '>');
994 from
= g_strndup(tmp2
, tmp
- tmp2
);
996 purple_debug_info("sipe", "found < without > in From\n");
1000 tmp
= strchr(tmp2
, ';');
1002 from
= g_strndup(tmp2
, tmp
- tmp2
);
1004 from
= g_strdup(tmp2
);
1007 purple_debug_info("sipe", "got %s\n", from
);
1011 static xmlnode
* xmlnode_get_descendant(xmlnode
* parent
, ...)
1014 xmlnode
* node
= NULL
;
1017 va_start(args
, parent
);
1018 while ((name
= va_arg(args
, const char *)) != NULL
) {
1019 node
= xmlnode_get_child(parent
, name
);
1020 if (node
== NULL
) return NULL
;
1030 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1032 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1033 send_soap_request(sip
, body
);
1038 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1041 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1043 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1046 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1050 void sipe_auth_user_cb(void * data
)
1052 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1055 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1060 void sipe_deny_user_cb(void * data
)
1062 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1065 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1070 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1072 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1073 sipe_contact_allow_deny(sip
, name
, TRUE
);
1077 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1079 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1080 sipe_contact_allow_deny(sip
, name
, FALSE
);
1084 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1086 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1087 sipe_contact_set_acl(sip, name, "");
1091 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1095 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1096 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1098 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1100 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1101 if (!watchers
) return;
1103 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1104 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1105 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1106 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1108 // TODO pull out optional displayName to pass as alias
1110 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1111 job
->who
= remote_user
;
1113 purple_account_request_authorization(
1127 xmlnode_free(watchers
);
1132 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1134 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1135 if (!purple_group
) {
1136 purple_group
= purple_group_new(group
->name
);
1137 purple_blist_add_group(purple_group
, NULL
);
1141 group
->purple_group
= purple_group
;
1142 sip
->groups
= g_slist_append(sip
->groups
, group
);
1143 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1145 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1149 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1151 struct sipe_group
*group
;
1157 entry
= sip
->groups
;
1159 group
= entry
->data
;
1160 if (group
->id
== id
) {
1163 entry
= entry
->next
;
1168 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, gchar
* name
)
1170 struct sipe_group
*group
;
1176 entry
= sip
->groups
;
1178 group
= entry
->data
;
1179 if (!strcmp(group
->name
, name
)) {
1182 entry
= entry
->next
;
1188 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1191 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1192 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1193 send_soap_request(sip
, body
);
1195 g_free(group
->name
);
1196 group
->name
= g_strdup(name
);
1200 * Only appends if no such value already stored.
1203 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1204 GSList
* res
= list
;
1205 if (!g_slist_find_custom(list
, data
, func
)) {
1206 res
= g_slist_insert_sorted(list
, data
, func
);
1212 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1213 return group1
->id
- group2
->id
;
1217 * Returns string like "2 4 7 8" - group ids buddy belong to.
1220 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1223 //creating array from GList, converting int to gchar*
1224 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1225 GSList
*entry
= buddy
->groups
;
1227 struct sipe_group
* group
= entry
->data
;
1228 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1229 entry
= entry
->next
;
1233 res
= g_strjoinv(" ", ids_arr
);
1234 g_strfreev(ids_arr
);
1239 * Sends buddy update to server
1242 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1244 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1245 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1247 if (buddy
&& purple_buddy
) {
1248 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1250 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1251 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1253 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1254 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1256 send_soap_request(sip
, body
);
1262 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1264 if (msg
->response
== 200) {
1265 struct sipe_group
*group
;
1266 struct group_user_context
*ctx
= (struct group_user_context
*)tc
->payload
;
1270 struct sipe_buddy
*buddy
;
1272 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1278 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1285 group_id
= xmlnode_get_data(node
);
1292 group
= g_new0(struct sipe_group
, 1);
1293 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1295 group
->name
= ctx
->group_name
;
1297 sipe_group_add(sip
, group
);
1299 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1301 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1304 sipe_group_set_user(sip
, ctx
->user_name
);
1313 static void sipe_group_create (struct sipe_account_data
*sip
, gchar
*name
, gchar
* who
)
1315 struct group_user_context
* ctx
= g_new0(struct group_user_context
, 1);
1317 ctx
->group_name
= g_strdup(name
);
1318 ctx
->user_name
= g_strdup(who
);
1320 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1321 send_soap_request_with_cb(sip
, body
, process_add_group_response
, ctx
);
1326 * Data structure for scheduled actions
1328 typedef void (*Action
) (struct sipe_account_data
*, void *);
1330 struct scheduled_action
{
1333 * Format is <Event>[<Data>...]
1334 * Example: <presence><sip:user@domain.com> or <registration>
1337 guint timeout_handler
;
1338 gboolean repetitive
;
1340 GDestroyNotify destroy
;
1341 struct sipe_account_data
*sip
;
1347 * Should return FALSE if repetitive action is not needed
1349 static gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1352 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1353 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1354 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1355 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1356 ret
= sched_action
->repetitive
;
1357 (*sched_action
->destroy
)(sched_action
->payload
);
1358 g_free(sched_action
->name
);
1359 g_free(sched_action
);
1364 * Kills action timer effectively cancelling
1367 * @param name of action
1369 static void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, const gchar
*name
)
1373 if (!sip
->timeouts
|| !name
) return;
1375 entry
= sip
->timeouts
;
1377 struct scheduled_action
*sched_action
= entry
->data
;
1378 if(!strcmp(sched_action
->name
, name
)) {
1379 GSList
*to_delete
= entry
;
1380 entry
= entry
->next
;
1381 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1382 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1383 purple_timeout_remove(sched_action
->timeout_handler
);
1384 (*sched_action
->destroy
)(sched_action
->payload
);
1385 g_free(sched_action
->name
);
1386 g_free(sched_action
);
1388 entry
= entry
->next
;
1394 sipe_schedule_action0(const gchar
*name
,
1398 GDestroyNotify destroy
,
1399 struct sipe_account_data
*sip
,
1402 struct scheduled_action
*sched_action
;
1404 /* Make sure each action only exists once */
1405 sipe_cancel_scheduled_action(sip
, name
);
1407 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name
, timeout
, isSeconds
? "sec" : "msec");
1408 sched_action
= g_new0(struct scheduled_action
, 1);
1409 sched_action
->repetitive
= FALSE
;
1410 sched_action
->name
= g_strdup(name
);
1411 sched_action
->action
= action
;
1412 sched_action
->destroy
= destroy
? destroy
: g_free
;
1413 sched_action
->sip
= sip
;
1414 sched_action
->payload
= payload
;
1415 sched_action
->timeout_handler
= isSeconds
? purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
) :
1416 purple_timeout_add(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1417 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1418 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1422 * Do schedule action for execution in the future.
1423 * Non repetitive execution.
1425 * @param name of action (will be copied)
1426 * @param timeout in seconds
1427 * @action callback function
1428 * @payload callback data (can be NULL, otherwise caller must allocate memory)
1431 sipe_schedule_action(const gchar
*name
,
1434 GDestroyNotify destroy
,
1435 struct sipe_account_data
*sip
,
1438 sipe_schedule_action0(name
, timeout
, TRUE
, action
, destroy
, sip
, payload
);
1442 * Same as sipe_schedule_action() but timeout is in milliseconds.
1445 sipe_schedule_action_msec(const gchar
*name
,
1448 GDestroyNotify destroy
,
1449 struct sipe_account_data
*sip
,
1452 sipe_schedule_action0(name
, timeout
, FALSE
, action
, destroy
, sip
, payload
);
1456 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1458 static gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1460 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1462 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1467 static void sipe_subscribe_resource_uri(const char *name
, gpointer value
, gchar
**resources_uri
)
1469 gchar
*tmp
= *resources_uri
;
1470 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1474 static void sipe_subscribe_resource_uri_with_context(const char *name
, gpointer value
, gchar
**resources_uri
)
1476 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
1477 if (sbuddy
&& !sbuddy
->resubscribed
) { // Only not resubscribed contacts; the first time everybody are included
1478 gchar
*tmp
= *resources_uri
;
1479 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"><context/></resource>\n", tmp
, name
);
1485 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1486 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1487 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1488 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1489 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1492 static void sipe_subscribe_presence_batched_to(struct sipe_account_data
*sip
, gchar
*resources_uri
, gchar
*to
)
1494 gchar
*contact
= get_contact(sip
);
1497 gchar
*require
= "";
1499 gchar
*autoextend
= "";
1500 gchar
*content_type
;
1502 if (sip
->msrtc_event_categories
) {
1503 require
= ", categoryList";
1504 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1505 content_type
= "application/msrtc-adrl-categorylist+xml";
1506 content
= g_strdup_printf(
1507 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1508 "<action name=\"subscribe\" id=\"63792024\">\n"
1509 "<adhocList>\n%s</adhocList>\n"
1510 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1511 "<category name=\"note\"/>\n"
1512 "<category name=\"state\"/>\n"
1515 "</batchSub>", sip
->username
, resources_uri
);
1517 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1518 content_type
= "application/adrl+xml";
1519 content
= g_strdup_printf(
1520 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1521 "<create xmlns=\"\">\n%s</create>\n"
1522 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1524 g_free(resources_uri
);
1526 request
= g_strdup_printf(
1527 "Require: adhoclist%s\r\n"
1528 "Supported: eventlist\r\n"
1529 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1530 "Supported: ms-piggyback-first-notify\r\n"
1531 "%sSupported: ms-benotify\r\n"
1532 "Proxy-Require: ms-benotify\r\n"
1533 "Event: presence\r\n"
1534 "Content-Type: %s\r\n"
1535 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
1538 /* subscribe to buddy presence */
1539 //send_sip_request(sip->gc, "SUBSCRIBE", resource_uri, resource_uri, request, content, NULL, process_subscribe_response);
1540 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1547 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
, void *unused
)
1549 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
1550 gchar
*resources_uri
= g_strdup("");
1551 if (sip
->msrtc_event_categories
) {
1552 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
1554 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1556 sipe_subscribe_presence_batched_to(sip
, resources_uri
, to
);
1559 struct presence_batched_routed
{
1564 static void sipe_subscribe_presence_batched_routed_free(void *payload
)
1566 struct presence_batched_routed
*data
= payload
;
1567 GSList
*buddies
= data
->buddies
;
1569 g_free(buddies
->data
);
1570 buddies
= buddies
->next
;
1572 g_slist_free(data
->buddies
);
1577 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data
*sip
, void *payload
)
1579 struct presence_batched_routed
*data
= payload
;
1580 GSList
*buddies
= data
->buddies
;
1581 gchar
*resources_uri
= g_strdup("");
1583 gchar
*tmp
= resources_uri
;
1584 resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, (char *) buddies
->data
);
1586 buddies
= buddies
->next
;
1588 sipe_subscribe_presence_batched_to(sip
, resources_uri
,
1589 g_strdup(data
->host
));
1593 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1594 * The user sends a single SUBSCRIBE request to the subscribed contact.
1595 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1599 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, void *buddy_name
)
1601 gchar
*to
= strstr((char *)buddy_name
, "sip:") ? g_strdup((char *)buddy_name
) : g_strdup_printf("sip:%s", (char *)buddy_name
);
1602 gchar
*tmp
= get_contact(sip
);
1605 gchar
*autoextend
= "";
1607 if (!sip
->msrtc_event_categories
)
1608 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1610 request
= g_strdup_printf(
1611 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1612 "Supported: ms-piggyback-first-notify\r\n"
1613 "%sSupported: ms-benotify\r\n"
1614 "Proxy-Require: ms-benotify\r\n"
1615 "Event: presence\r\n"
1616 "Content-Type: application/msrtc-adrl-categorylist+xml\r\n"
1617 "Contact: %s\r\n", autoextend
,tmp
);
1619 content
= g_strdup_printf(
1620 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1621 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1622 "<resource uri=\"%s\"/>\n"
1624 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1625 "<category name=\"note\"/>\n"
1626 "<category name=\"state\"/>\n"
1629 "</batchSub>", sip
->username
, to
1634 /* subscribe to buddy presence */
1635 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, NULL
, process_subscribe_response
);
1642 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
1644 if (!purple_status_is_active(status
))
1648 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
1651 g_free(sip
->status
);
1652 sip
->status
= g_strdup(purple_status_get_id(status
));
1653 send_presence_status(sip
);
1659 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
, const char *alias
)
1661 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1662 sipe_group_set_user(sip
, name
);
1666 sipe_group_buddy(PurpleConnection
*gc
,
1668 const char *old_group_name
,
1669 const char *new_group_name
)
1671 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1672 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1673 struct sipe_group
* old_group
= NULL
;
1674 struct sipe_group
* new_group
;
1676 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
1677 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
1679 if(!buddy
) { // buddy not in roaming list
1683 if (old_group_name
) {
1684 old_group
= sipe_group_find_by_name(sip
, g_strdup(old_group_name
));
1686 new_group
= sipe_group_find_by_name(sip
, g_strdup(new_group_name
));
1689 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
1690 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
1694 sipe_group_create(sip
, g_strdup(new_group_name
), g_strdup(who
));
1696 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
1697 sipe_group_set_user(sip
, who
);
1701 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1703 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1704 struct sipe_buddy
*b
;
1706 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1708 // Prepend sip: if needed
1709 if (strncmp("sip:", buddy
->name
, 4)) {
1710 gchar
*buf
= g_strdup_printf("sip:%s", buddy
->name
);
1711 purple_blist_rename_buddy(buddy
, buf
);
1715 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
1716 b
= g_new0(struct sipe_buddy
, 1);
1717 purple_debug_info("sipe", "sipe_add_buddy %s\n", buddy
->name
);
1718 b
->name
= g_strdup(buddy
->name
);
1719 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
1720 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
1721 sipe_subscribe_presence_single(sip
, b
->name
); //@TODO should go to callback
1723 purple_debug_info("sipe", "buddy %s already in internal list\n", buddy
->name
);
1727 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
1729 g_free(buddy
->name
);
1730 g_free(buddy
->annotation
);
1731 g_free(buddy
->device_name
);
1732 g_slist_free(buddy
->groups
);
1737 * Unassociates buddy from group first.
1738 * Then see if no groups left, removes buddy completely.
1739 * Otherwise updates buddy groups on server.
1741 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
1743 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1744 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
1745 struct sipe_group
*g
= NULL
;
1747 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
1752 g
= sipe_group_find_by_name(sip
, group
->name
);
1756 b
->groups
= g_slist_remove(b
->groups
, g
);
1757 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
1760 if (g_slist_length(b
->groups
) < 1) {
1761 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
1762 sipe_cancel_scheduled_action(sip
, action_name
);
1763 g_free(action_name
);
1765 g_hash_table_remove(sip
->buddies
, buddy
->name
);
1768 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
1769 send_soap_request(sip
, body
);
1775 //updates groups on server
1776 sipe_group_set_user(sip
, b
->name
);
1782 sipe_rename_group(PurpleConnection
*gc
,
1783 const char *old_name
,
1785 GList
*moved_buddies
)
1787 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1788 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, g_strdup(old_name
));
1790 sipe_group_rename(sip
, s_group
, group
->name
);
1792 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
1797 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
1799 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1800 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
1803 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
1804 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
1805 send_soap_request(sip
, body
);
1808 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
1809 g_free(s_group
->name
);
1812 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
1816 static GList
*sipe_status_types(PurpleAccount
*acc
)
1818 PurpleStatusType
*type
;
1819 GList
*types
= NULL
;
1822 type
= purple_status_type_new_with_attrs(
1823 PURPLE_STATUS_AVAILABLE
, NULL
, _("Online"), TRUE
, TRUE
, FALSE
,
1824 // Translators: noun
1825 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1827 types
= g_list_append(types
, type
);
1830 type
= purple_status_type_new_with_attrs(
1831 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_BUSY
, _("Busy"), TRUE
, TRUE
, FALSE
,
1832 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1834 types
= g_list_append(types
, type
);
1836 // Do Not Disturb (not user settable)
1837 type
= purple_status_type_new_with_attrs(
1838 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_DND
, _("Do Not Disturb"), TRUE
, FALSE
, FALSE
,
1839 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1841 types
= g_list_append(types
, type
);
1844 type
= purple_status_type_new_with_attrs(
1845 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_BRB
, _("Be Right Back"), TRUE
, TRUE
, FALSE
,
1846 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1848 types
= g_list_append(types
, type
);
1851 type
= purple_status_type_new_with_attrs(
1852 PURPLE_STATUS_AWAY
, NULL
, NULL
, TRUE
, TRUE
, FALSE
,
1853 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1855 types
= g_list_append(types
, type
);
1858 type
= purple_status_type_new_with_attrs(
1859 PURPLE_STATUS_UNAVAILABLE
, SIPE_STATUS_ID_ONPHONE
, _("On The Phone"), TRUE
, TRUE
, FALSE
,
1860 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1862 types
= g_list_append(types
, type
);
1865 type
= purple_status_type_new_with_attrs(
1866 PURPLE_STATUS_AWAY
, SIPE_STATUS_ID_LUNCH
, _("Out To Lunch"), TRUE
, TRUE
, FALSE
,
1867 "message", _("Message"), purple_value_new(PURPLE_TYPE_STRING
),
1869 types
= g_list_append(types
, type
);
1872 type
= purple_status_type_new_full(
1873 PURPLE_STATUS_INVISIBLE
, NULL
, _("Appear Offline"), TRUE
, TRUE
, FALSE
);
1874 types
= g_list_append(types
, type
);
1877 type
= purple_status_type_new_full(
1878 PURPLE_STATUS_OFFLINE
, NULL
, NULL
, TRUE
, TRUE
, FALSE
);
1879 types
= g_list_append(types
, type
);
1885 * A callback for g_hash_table_foreach
1887 static void sipe_buddy_subscribe_cb(char *name
, struct sipe_buddy
*buddy
, struct sipe_account_data
*sip
)
1889 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
1890 int time_range
= (g_hash_table_size(sip
->buddies
) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
1891 int timeout
= (time_range
* rand()) / RAND_MAX
; /* random period within the range */
1892 sipe_schedule_action_msec(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, buddy
->name
);
1896 * Removes entries from purple buddy list
1897 * that does not correspond ones in the roaming contact list.
1899 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
1900 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
1901 GSList
*entry
= buddies
;
1902 struct sipe_buddy
*buddy
;
1906 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
1907 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
1910 g
= purple_buddy_get_group(b
);
1911 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
1913 gboolean in_sipe_groups
= FALSE
;
1914 GSList
*entry2
= buddy
->groups
;
1916 struct sipe_group
*group
= entry2
->data
;
1917 if (!strcmp(group
->name
, g
->name
)) {
1918 in_sipe_groups
= TRUE
;
1921 entry2
= entry2
->next
;
1923 if(!in_sipe_groups
) {
1924 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
1925 purple_blist_remove_buddy(b
);
1928 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
1929 purple_blist_remove_buddy(b
);
1931 entry
= entry
->next
;
1935 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
1937 int len
= msg
->bodylen
;
1939 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
1942 const gchar
*contacts_delta
;
1943 xmlnode
*group_node
;
1944 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
1948 /* Convert the contact from XML to Purple Buddies */
1949 isc
= xmlnode_from_str(msg
->body
, len
);
1954 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
1955 if (contacts_delta
) {
1956 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
1959 if (!strcmp(isc
->name
, "contactList")) {
1962 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
1963 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1964 const char *name
= xmlnode_get_attrib(group_node
, "name");
1966 if (!strncmp(name
, "~", 1)) {
1968 name
= "Other Contacts";
1970 group
->name
= g_strdup(name
);
1971 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
1973 sipe_group_add(sip
, group
);
1976 // Make sure we have at least one group
1977 if (g_slist_length(sip
->groups
) == 0) {
1978 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
1979 PurpleGroup
*purple_group
;
1981 group
->name
= g_strdup("Other Contacts");
1983 purple_group
= purple_group_new(group
->name
);
1984 purple_blist_add_group(purple_group
, NULL
);
1985 sip
->groups
= g_slist_append(sip
->groups
, group
);
1988 /* Parse contacts */
1989 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
1990 gchar
* uri
= g_strdup(xmlnode_get_attrib(item
, "uri"));
1991 gchar
* name
= g_strdup(xmlnode_get_attrib(item
, "name"));
1992 gchar
* groups
= g_strdup(xmlnode_get_attrib(item
, "groups"));
1993 gchar
* buddy_name
= g_strdup_printf("sip:%s", uri
);
1994 gchar
**item_groups
;
1995 struct sipe_group
*group
= NULL
;
1996 struct sipe_buddy
*buddy
= NULL
;
1999 // assign to group Other Contacts if nothing else received
2000 if(!groups
|| !strcmp("", groups
) ) {
2001 group
= sipe_group_find_by_name(sip
, "Other Contacts");
2002 groups
= group
? g_strdup_printf("%d", group
->id
) : "1";
2005 item_groups
= g_strsplit(groups
, " ", 0);
2007 while (item_groups
[i
]) {
2008 group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
2010 // If couldn't find the right group for this contact, just put them in the first group we have
2011 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2012 group
= sip
->groups
->data
;
2015 if (group
!= NULL
) {
2016 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2018 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2019 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2022 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2023 if (name
!= NULL
&& strlen(name
) != 0) {
2024 purple_blist_alias_buddy(b
, name
);
2029 buddy
= g_new0(struct sipe_buddy
, 1);
2030 buddy
->name
= g_strdup(b
->name
);
2031 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2034 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2036 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2038 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2043 } // while, contact groups
2044 g_strfreev(item_groups
);
2052 sipe_cleanup_local_blist(sip
);
2056 //subscribe to buddies
2057 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2058 if(sip
->batched_support
){
2059 sipe_subscribe_presence_batched(sip
, NULL
);
2062 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
2064 sip
->subscribed_buddies
= TRUE
;
2071 * Subscribe roaming contacts
2073 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2075 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2076 gchar
*tmp
= get_contact(sip
);
2077 gchar
*hdr
= g_strdup_printf(
2078 "Event: vnd-microsoft-roaming-contacts\r\n"
2079 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2080 "Supported: com.microsoft.autoextend\r\n"
2081 "Supported: ms-benotify\r\n"
2082 "Proxy-Require: ms-benotify\r\n"
2083 "Supported: ms-piggyback-first-notify\r\n"
2084 "Contact: %s\r\n", tmp
);
2087 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2092 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
, void *unused
)
2094 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2095 gchar
*tmp
= get_contact(sip
);
2096 gchar
*hdr
= g_strdup_printf(
2097 "Event: presence.wpending\r\n"
2098 "Accept: text/xml+msrtc.wpending\r\n"
2099 "Supported: com.microsoft.autoextend\r\n"
2100 "Supported: ms-benotify\r\n"
2101 "Proxy-Require: ms-benotify\r\n"
2102 "Supported: ms-piggyback-first-notify\r\n"
2103 "Contact: %s\r\n", tmp
);
2106 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2112 * Fires on deregistration event initiated by server.
2113 * [MS-SIPREGE] SIP extension.
2118 // Content-Type: text/registration-event
2119 // subscription-state: terminated;expires=0
2120 // ms-diagnostics-public: 4141;reason="User disabled"
2122 // deregistered;event=rejected
2124 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2126 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2127 gchar
*event
= NULL
;
2128 gchar
*reason
= NULL
;
2129 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
2131 warning
= warning
? warning
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2132 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2134 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2135 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2136 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2137 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2139 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2143 if (warning
!= NULL
) {
2144 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
2145 } else { // for LCS2005
2147 if (event
&& !g_ascii_strcasecmp(event
, "unregistered")) {
2148 error_id
= 4140; // [MS-SIPREGE]
2149 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2150 reason
= g_strdup(_("You have been signed off because you've signed in at another location"));
2151 } else if (event
&& !g_ascii_strcasecmp(event
, "rejected")) {
2153 reason
= g_strdup(_("User disabled")); // [MS-OCER]
2154 } else if (event
&& !g_ascii_strcasecmp(event
, "deactivated")) {
2156 reason
= g_strdup(_("User moved")); // [MS-OCER]
2160 warning
= g_strdup_printf(_("Unregistered by Server: %s."), reason
? reason
: _("no reason given"));
2163 sip
->gc
->wants_to_die
= TRUE
;
2164 purple_connection_error(sip
->gc
, warning
);
2169 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2171 const gchar
*contacts_delta
;
2174 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2180 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2183 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2192 * When we receive some self (BE) NOTIFY with a new subscriber
2193 * we sends a setSubscribers request to him [SIP-PRES] 4.8
2197 static void sipe_process_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2203 char *display_name
= NULL
;
2204 PurpleBuddy
*pbuddy
;
2209 purple_debug_info("sipe", "sipe_process_roaming_self\n");
2211 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2214 contact
= get_contact(sip
);
2215 to
= g_strdup_printf("sip:%s", sip
->username
);
2217 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
2219 const char *acknowledged
;
2223 user
= xmlnode_get_attrib(node
, "user");
2224 if (!user
) continue;
2225 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
2226 uri_user
= g_strdup_printf("sip:%s", user
);
2227 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri_user
);
2229 alias
= purple_buddy_get_local_alias(pbuddy
);
2230 uri_alias
= g_strdup_printf("sip:%s", alias
);
2231 display_name
= g_strdup(xmlnode_get_attrib(node
, "displayName"));
2232 if (display_name
&& !g_ascii_strcasecmp(uri_user
, uri_alias
)) { // 'bad' alias
2233 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri_user
, display_name
);
2234 purple_blist_alias_buddy(pbuddy
, display_name
);
2236 g_free(display_name
);
2241 acknowledged
= xmlnode_get_attrib(node
, "acknowledged");
2242 if(!g_ascii_strcasecmp(acknowledged
,"false")){
2243 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user
);
2244 hdr
= g_strdup_printf(
2246 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
2248 body
= g_strdup_printf(
2249 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
2250 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
2251 "</setSubscribers>", user
);
2253 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
2264 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2266 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2267 gchar
*tmp
= get_contact(sip
);
2268 gchar
*hdr
= g_strdup_printf(
2269 "Event: vnd-microsoft-roaming-ACL\r\n"
2270 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
2271 "Supported: com.microsoft.autoextend\r\n"
2272 "Supported: ms-benotify\r\n"
2273 "Proxy-Require: ms-benotify\r\n"
2274 "Supported: ms-piggyback-first-notify\r\n"
2275 "Contact: %s\r\n", tmp
);
2278 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2284 * To request for presence information about the user, access level settings that have already been configured by the user
2285 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
2286 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
2289 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2291 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2292 gchar
*tmp
= get_contact(sip
);
2293 gchar
*hdr
= g_strdup_printf(
2294 "Event: vnd-microsoft-roaming-self\r\n"
2295 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
2296 "Supported: ms-benotify\r\n"
2297 "Proxy-Require: ms-benotify\r\n"
2298 "Supported: ms-piggyback-first-notify\r\n"
2300 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
2302 gchar
*body
=g_strdup(
2303 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
2304 "<roaming type=\"categories\"/>"
2305 "<roaming type=\"containers\"/>"
2306 "<roaming type=\"subscribers\"/></roamingList>");
2309 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2318 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2320 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2321 gchar
*tmp
= get_contact(sip
);
2322 gchar
*hdr
= g_strdup_printf(
2323 "Event: vnd-microsoft-provisioning\r\n"
2324 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
2325 "Supported: com.microsoft.autoextend\r\n"
2326 "Supported: ms-benotify\r\n"
2327 "Proxy-Require: ms-benotify\r\n"
2328 "Supported: ms-piggyback-first-notify\r\n"
2330 "Contact: %s\r\n", tmp
);
2333 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
2338 /** Subscription for provisioning information to help with initial
2339 * configuration. This subscription is a one-time query (denoted by the Expires header,
2340 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
2341 * configuration, meeting policies, and policy settings that Communicator must enforce.
2342 * TODO: for what we need this information.
2345 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
2347 gchar
*to
= g_strdup_printf("sip:%s", sip
->username
);
2348 gchar
*tmp
= get_contact(sip
);
2349 gchar
*hdr
= g_strdup_printf(
2350 "Event: vnd-microsoft-provisioning-v2\r\n"
2351 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
2352 "Supported: com.microsoft.autoextend\r\n"
2353 "Supported: ms-benotify\r\n"
2354 "Proxy-Require: ms-benotify\r\n"
2355 "Supported: ms-piggyback-first-notify\r\n"
2358 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
2359 gchar
*body
= g_strdup(
2360 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
2361 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
2362 "<provisioningGroup name=\"ucPolicy\"/>"
2363 "</provisioningGroupList>");
2366 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
2372 /* IM Session (INVITE and MESSAGE methods) */
2374 static struct sip_dialog
*
2375 get_dialog (struct sip_im_session
*session
,
2378 struct sip_dialog
*dialog
;
2380 if (session
== NULL
|| who
== NULL
) {
2384 entry
= session
->dialogs
;
2386 dialog
= entry
->data
;
2387 if (dialog
->with
&& !strcmp(who
, dialog
->with
)) {
2390 entry
= entry
->next
;
2395 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
2397 get_end_points (struct sipe_account_data
*sip
,
2398 struct sip_im_session
*session
)
2401 gchar
*res
= g_strdup_printf("<sip:%s>", sip
->username
);
2402 struct sip_dialog
*dialog
;
2404 if (session
== NULL
) {
2408 entry
= session
->dialogs
;
2410 dialog
= entry
->data
;
2413 res
= g_strdup_printf("%s, <%s>", res
, dialog
->with
);
2416 if (dialog
->theirepid
) {
2418 res
= g_strdup_printf("%s;epid=%s", res
, dialog
->theirepid
);
2422 entry
= entry
->next
;
2427 static struct sip_im_session
*
2428 find_chat_session_by_id (struct sipe_account_data
*sip
,
2431 struct sip_im_session
*session
;
2437 entry
= sip
->im_sessions
;
2439 session
= entry
->data
;
2440 if (id
== session
->chat_id
) {
2443 entry
= entry
->next
;
2448 static struct sip_im_session
*
2449 find_chat_session_by_name (struct sipe_account_data
*sip
,
2450 const char *chat_name
)
2452 struct sip_im_session
*session
;
2454 if (sip
== NULL
|| chat_name
== NULL
) {
2458 entry
= sip
->im_sessions
;
2460 session
= entry
->data
;
2461 if (session
->chat_name
&& !g_strcasecmp(chat_name
, session
->chat_name
)) {
2464 entry
= entry
->next
;
2469 static struct sip_im_session
*
2470 find_chat_session (struct sipe_account_data
*sip
,
2473 struct sip_im_session
*session
;
2475 if (sip
== NULL
|| callid
== NULL
) {
2479 entry
= sip
->im_sessions
;
2481 session
= entry
->data
;
2482 if (session
->callid
&& !g_strcasecmp(callid
, session
->callid
)) {
2485 entry
= entry
->next
;
2490 static struct sip_im_session
* find_im_session (struct sipe_account_data
*sip
, const char *who
)
2492 struct sip_im_session
*session
;
2494 if (sip
== NULL
|| who
== NULL
) {
2498 entry
= sip
->im_sessions
;
2500 session
= entry
->data
;
2501 if (session
->with
&& !strcmp(who
, session
->with
)) {
2504 entry
= entry
->next
;
2509 static struct sip_im_session
*
2510 create_chat_session (struct sipe_account_data
*sip
)
2512 struct sip_im_session
*session
= g_new0(struct sip_im_session
, 1);
2513 session
->callid
= gencallid();
2514 session
->is_multiparty
= TRUE
;
2515 session
->chat_id
= rand();
2516 session
->unconfirmed_messages
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
2517 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2521 static struct sip_im_session
* find_or_create_chat_session (struct sipe_account_data
*sip
, const char *callid
)
2523 struct sip_im_session
*session
= find_chat_session(sip
, callid
);
2525 session
= create_chat_session(sip
);
2526 session
->callid
= g_strdup(callid
);
2531 static struct sip_im_session
* find_or_create_im_session (struct sipe_account_data
*sip
, const char *who
)
2533 struct sip_im_session
*session
= find_im_session(sip
, who
);
2535 session
= g_new0(struct sip_im_session
, 1);
2536 session
->is_multiparty
= FALSE
;
2537 session
->with
= g_strdup(who
);
2538 session
->unconfirmed_messages
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
2539 sip
->im_sessions
= g_slist_append(sip
->im_sessions
, session
);
2545 free_dialog(struct sip_dialog
*dialog
)
2549 if (!dialog
) return;
2551 g_free(dialog
->with
);
2552 entry
= dialog
->routes
;
2554 g_free(entry
->data
);
2555 entry
= g_slist_remove(entry
, entry
->data
);
2557 entry
= dialog
->supported
;
2559 g_free(entry
->data
);
2560 entry
= g_slist_remove(entry
, entry
->data
);
2563 g_free(dialog
->callid
);
2564 g_free(dialog
->ourtag
);
2565 g_free(dialog
->theirtag
);
2566 g_free(dialog
->theirepid
);
2567 g_free(dialog
->request
);
2572 static void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2576 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2578 entry
= session
->dialogs
;
2580 free_dialog(entry
->data
);
2581 entry
= g_slist_remove(entry
, entry
->data
);
2584 entry
= session
->outgoing_message_queue
;
2586 g_free(entry
->data
);
2587 entry
= g_slist_remove(entry
, entry
->data
);
2590 entry
= session
->pending_invite_queue
;
2592 g_free(entry
->data
);
2593 entry
= g_slist_remove(entry
, entry
->data
);
2596 g_hash_table_destroy(session
->unconfirmed_messages
);
2598 g_free(session
->with
);
2599 g_free(session
->chat_name
);
2600 g_free(session
->callid
);
2601 g_free(session
->roster_manager
);
2606 process_options_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2608 gboolean ret
= TRUE
;
2610 if (msg
->response
!= 200) {
2611 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
2615 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
2621 * Asks UA/proxy about its capabilities.
2623 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
2625 gchar
*to
= strstr(who
, "sip:") ? g_strdup(who
) : g_strdup_printf("sip:%s", who
);
2626 gchar
*contact
= get_contact(sip
);
2628 request
= g_strdup_printf(
2629 "Accept: application/sdp\r\n"
2630 "Contact: %s\r\n", contact
);
2634 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
2640 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2642 char *msg
, *msg_tmp
;
2643 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2644 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2646 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2647 "possibly because one or more persons are offline:\n%s") ,
2649 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2654 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2657 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2659 gboolean ret
= TRUE
;
2660 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2661 struct sip_im_session
* session
= find_im_session(sip
, with
);
2662 struct sip_dialog
*dialog
;
2668 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2673 dialog
= get_dialog(session
, with
);
2676 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2681 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2682 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
), with
);
2684 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2686 if (msg
->response
!= 200) {
2687 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2689 sipe_present_message_undelivered_err(with
, sip
, message
);
2690 im_session_destroy(sip
, session
);
2693 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2694 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2695 key
, g_hash_table_size(session
->unconfirmed_messages
));
2701 if (ret
) sipe_im_process_queue(sip
, session
);
2706 sipe_is_election_finished(struct sipe_account_data
*sip
,
2707 struct sip_im_session
*session
);
2710 sipe_election_result(struct sipe_account_data
*sip
,
2714 process_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2716 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2717 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
2718 struct sip_dialog
*dialog
;
2719 struct sip_im_session
*session
;
2721 session
= find_chat_session(sip
, callid
);
2723 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid
);
2727 if (msg
->response
== 200 && !strncmp(contenttype
, "application/x-ms-mim", 20)) {
2728 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2729 xmlnode
*xn_request_rm_response
= xmlnode_get_child(xn_action
, "RequestRMResponse");
2730 xmlnode
*xn_set_rm_response
= xmlnode_get_child(xn_action
, "SetRMResponse");
2732 if (xn_request_rm_response
) {
2733 const char *with
= xmlnode_get_attrib(xn_request_rm_response
, "uri");
2734 const char *allow
= xmlnode_get_attrib(xn_request_rm_response
, "allow");
2736 dialog
= get_dialog(session
, with
);
2738 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with
);
2742 if (allow
&& !g_strcasecmp(allow
, "true")) {
2743 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with
);
2744 dialog
->election_vote
= 1;
2745 } else if (allow
&& !g_strcasecmp(allow
, "false")) {
2746 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with
);
2747 dialog
->election_vote
= -1;
2750 if (sipe_is_election_finished(sip
, session
)) {
2751 sipe_election_result(sip
, session
);
2754 } else if (xn_set_rm_response
) {
2757 xmlnode_free(xn_action
);
2764 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_dialog
*dialog
, const char *msg
)
2773 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2774 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2776 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2779 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2782 msgr
= g_strdup("");
2785 tmp
= get_contact(sip
);
2786 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2787 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2788 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2789 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp
, msgr
);
2793 send_sip_request(sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
, hdr
, msgtext
, dialog
, process_message_response
);
2800 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2802 GSList
*entry2
= session
->outgoing_message_queue
;
2804 char *queued_msg
= entry2
->data
;
2805 struct sip_dialog
*dialog
;
2806 GSList
*entry
= session
->dialogs
;
2808 if (session
->is_multiparty
) {
2809 gchar
*who
= g_strdup_printf("sip:%s", sip
->username
);
2810 serv_got_chat_in(sip
->gc
, session
->chat_id
, who
,
2811 PURPLE_MESSAGE_SEND
, queued_msg
, time(NULL
));
2818 dialog
= entry
->data
;
2819 entry
= entry
->next
;
2820 if (dialog
->outgoing_invite
) continue; //do not send messages as INVITE is not responded.
2822 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog
->callid
, (dialog
->cseq
) + 1, dialog
->with
);
2823 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
2824 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2825 key
, g_hash_table_size(session
->unconfirmed_messages
));
2827 sipe_send_message(sip
, dialog
, queued_msg
);
2830 entry2
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2836 sipe_get_route_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2838 GSList
*hdr
= msg
->headers
;
2842 struct siphdrelement
*elem
= hdr
->data
;
2843 if(!g_ascii_strcasecmp(elem
->name
, "Record-Route")) {
2844 gchar
**parts
= g_strsplit(elem
->value
, ",", 0);
2845 gchar
**part
= parts
;
2848 gchar
*route
= sipmsg_find_part_of_header(*part
, "<", ">", NULL
);
2849 purple_debug_info("sipe", "sipe_get_route_header: route %s \n", route
);
2850 dialog
->routes
= g_slist_append(dialog
->routes
, route
);
2856 hdr
= g_slist_next(hdr
);
2861 dialog
->routes
= g_slist_reverse(dialog
->routes
);
2866 dialog
->request
= dialog
->routes
->data
;
2867 dialog
->routes
= g_slist_remove(dialog
->routes
, dialog
->routes
->data
);
2870 contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Contact"), "<", ">", NULL
);
2871 dialog
->routes
= g_slist_append(dialog
->routes
, contact
);
2875 sipe_get_supported_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2877 GSList
*hdr
= msg
->headers
;
2878 struct siphdrelement
*elem
;
2882 if(!g_ascii_strcasecmp(elem
->name
, "Supported")
2883 && !g_slist_find_custom(dialog
->supported
, elem
->value
, (GCompareFunc
)g_ascii_strcasecmp
))
2885 dialog
->supported
= g_slist_append(dialog
->supported
, g_strdup(elem
->value
));
2888 hdr
= g_slist_next(hdr
);
2893 sipe_parse_dialog(struct sipmsg
* msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2895 gchar
*us
= outgoing
? "From" : "To";
2896 gchar
*them
= outgoing
? "To" : "From";
2898 g_free(dialog
->ourtag
);
2899 g_free(dialog
->theirtag
);
2901 dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, us
));
2902 dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, them
));
2903 if (!dialog
->theirepid
) {
2904 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", ";", NULL
);
2905 if (!dialog
->theirepid
) {
2906 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", NULL
, NULL
);
2910 // Catch a tag on the end of the To Header and get rid of it.
2911 if (dialog
->theirepid
&& strstr(dialog
->theirepid
, "tag=")) {
2912 dialog
->theirepid
= strtok(dialog
->theirepid
, ";");
2915 sipe_get_route_header(msg
, dialog
, outgoing
);
2916 sipe_get_supported_header(msg
, dialog
, outgoing
);
2920 sipe_refer_notify(struct sipe_account_data
*sip
,
2921 struct sip_im_session
*session
,
2928 struct sip_dialog
*dialog
= get_dialog(session
, who
);
2930 hdr
= g_strdup_printf(
2932 "Subscription-State: %s\r\n"
2933 "Content-Type: message/sipfrag\r\n",
2934 status
>= 200 ? "terminated" : "active");
2936 body
= g_strdup_printf(
2937 "SIP/2.0 %d %s\r\n",
2940 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "NOTIFY",
2941 who
, who
, hdr
, body
, dialog
, NULL
);
2948 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2950 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
2951 struct sip_im_session
*session
;
2952 struct sip_dialog
*dialog
;
2956 struct sipmsg
*request_msg
= trans
->msg
;
2958 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
2961 session
= find_chat_session(sip
, callid
);
2963 session
= find_im_session(sip
, with
);
2967 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2972 dialog
= get_dialog(session
, with
);
2974 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2979 sipe_parse_dialog(msg
, dialog
, TRUE
);
2981 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2982 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
2984 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2986 if (msg
->response
!= 200) {
2987 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2989 sipe_present_message_undelivered_err(with
, sip
, message
);
2990 im_session_destroy(sip
, session
);
2997 send_sip_request(sip
->gc
, "ACK", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
2998 dialog
->outgoing_invite
= NULL
;
2999 dialog
->is_established
= TRUE
;
3001 referred_by
= parse_from(sipmsg_find_header(request_msg
, "Referred-By"));
3003 sipe_refer_notify(sip
, session
, referred_by
, 200, "OK");
3004 g_free(referred_by
);
3007 /* add user to chat if it is a multiparty session */
3008 if (session
->is_multiparty
) {
3009 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3011 PURPLE_CBFLAGS_NONE
, TRUE
);
3014 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
3015 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3016 if (session
->outgoing_message_queue
) {
3017 char *queued_msg
= session
->outgoing_message_queue
->data
;
3018 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
3023 sipe_im_process_queue(sip
, session
);
3025 g_hash_table_remove(session
->unconfirmed_messages
, key
);
3026 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3027 key
, g_hash_table_size(session
->unconfirmed_messages
));
3036 sipe_invite(struct sipe_account_data
*sip
,
3037 struct sip_im_session
*session
,
3039 const gchar
*msg_body
,
3040 const gchar
*referred_by
,
3041 const gboolean is_triggered
)
3048 char *ms_text_format
= g_strdup("");
3049 gchar
*roster_manager
;
3051 gchar
*referred_by_str
;
3052 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3054 if (dialog
&& dialog
->is_established
) {
3055 purple_debug_info("sipe", "session with %s already has a dialog open\n", who
);
3060 dialog
= g_new0(struct sip_dialog
, 1);
3061 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3063 dialog
->callid
= session
->callid
? g_strdup(session
->callid
) : gencallid();
3064 dialog
->with
= g_strdup(who
);
3067 if (!(dialog
->ourtag
)) {
3068 dialog
->ourtag
= gentag();
3072 if (strstr(who
, "sip:")) {
3075 to
= g_strdup_printf("sip:%s", who
);
3086 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
3087 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
3089 msgr_value
= sipmsg_get_msgr_string(msgformat
);
3093 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
3097 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
3098 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
3103 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, (dialog
->cseq
) + 1);
3104 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
3105 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3106 key
, g_hash_table_size(session
->unconfirmed_messages
));
3110 contact
= get_contact(sip
);
3111 end_points
= get_end_points(sip
, session
);
3112 self
= g_strdup_printf("sip:%s", sip
->username
);
3113 roster_manager
= g_strdup_printf(
3114 "Roster-Manager: %s\r\n"
3115 "EndPoints: %s\r\n",
3118 referred_by_str
= referred_by
?
3120 "Referred-By: %s\r\n",
3123 hdr
= g_strdup_printf(
3129 "Content-Type: application/sdp\r\n",
3130 (session
->roster_manager
&& !strcmp(session
->roster_manager
, self
)) ? roster_manager
: "",
3132 is_triggered
? "TriggeredInvite: TRUE\r\n" : "",
3133 is_triggered
|| session
->is_multiparty
? "Require: com.microsoft.rtc-multiparty\r\n" : "",
3136 g_free(ms_text_format
);
3139 body
= g_strdup_printf(
3141 "o=- 0 0 IN IP4 %s\r\n"
3145 "m=message %d sip null\r\n"
3146 "a=accept-types:text/plain text/html image/gif "
3147 "multipart/alternative application/im-iscomposing+xml\r\n",
3148 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
3150 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
3151 to
, to
, hdr
, body
, dialog
, process_invite_response
);
3154 g_free(roster_manager
);
3156 g_free(referred_by_str
);
3163 sipe_refer(struct sipe_account_data
*sip
,
3164 struct sip_im_session
*session
,
3169 struct sip_dialog
*dialog
= get_dialog(session
, session
->roster_manager
);
3171 contact
= get_contact(sip
);
3172 hdr
= g_strdup_printf(
3174 "Refer-to: <%s>\r\n"
3175 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3176 "Require: com.microsoft.rtc-multiparty\r\n",
3180 dialog
->ourtag
? ";tag=" : "",
3181 dialog
->ourtag
? dialog
->ourtag
: "",
3184 send_sip_request(sip
->gc
, "REFER",
3185 session
->roster_manager
, session
->roster_manager
, hdr
, NULL
, dialog
, NULL
);
3192 sipe_send_election_request_rm(struct sipe_account_data
*sip
,
3193 struct sip_im_session
*session
,
3199 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3201 hdr
= "Content-Type: application/x-ms-mim\r\n";
3203 body
= g_strdup_printf(
3204 "<?xml version=\"1.0\"?>\r\n"
3205 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3206 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3207 sip
->username
, bid
);
3209 send_sip_request(sip
->gc
, "INFO",
3210 who
, who
, hdr
, body
, dialog
, process_info_response
);
3216 sipe_send_election_set_rm(struct sipe_account_data
*sip
,
3217 struct sip_im_session
*session
,
3222 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3224 hdr
= "Content-Type: application/x-ms-mim\r\n";
3226 body
= g_strdup_printf(
3227 "<?xml version=\"1.0\"?>\r\n"
3228 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3229 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3232 send_sip_request(sip
->gc
, "INFO",
3233 who
, who
, hdr
, body
, dialog
, process_info_response
);
3239 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
3242 struct sip_dialog
*dialog
;
3244 entry
= session
->dialogs
;
3246 dialog
= entry
->data
;
3247 /* @TODO slow down BYE message sending rate */
3248 /* @see single subscription code */
3249 send_sip_request(sip
->gc
, "BYE", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
3250 entry
= entry
->next
;
3253 im_session_destroy(sip
, session
);
3258 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
3260 struct sipe_account_data
*sip
= gc
->proto_data
;
3262 purple_debug_info("sipe", "conversation with %s closed\n", who
);
3263 im_session_close(sip
, find_im_session(sip
, who
));
3267 sipe_chat_leave (PurpleConnection
*gc
, int id
)
3269 struct sipe_account_data
*sip
= gc
->proto_data
;
3270 struct sip_im_session
* session
= find_chat_session_by_id(sip
, id
);
3271 im_session_close(sip
, session
);
3275 im_session_close_all (struct sipe_account_data
*sip
)
3277 GSList
*entry
= sip
->im_sessions
;
3279 im_session_close (sip
, entry
->data
);
3280 entry
= sip
->im_sessions
;
3284 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
3286 struct sipe_account_data
*sip
= gc
->proto_data
;
3287 struct sip_im_session
*session
;
3288 struct sip_dialog
*dialog
;
3290 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
3292 session
= find_or_create_im_session(sip
, who
);
3293 dialog
= get_dialog(session
, who
);
3295 // Queue the message
3296 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
3298 if (dialog
&& dialog
->callid
) {
3299 sipe_im_process_queue(sip
, session
);
3300 } else if (!dialog
|| !dialog
->outgoing_invite
) {
3301 // Need to send the INVITE to get the outgoing dialog setup
3302 sipe_invite(sip
, session
, who
, what
, NULL
, FALSE
);
3308 static int sipe_chat_send(PurpleConnection
*gc
, int id
, const char *what
, PurpleMessageFlags flags
)
3310 struct sipe_account_data
*sip
= gc
->proto_data
;
3311 struct sip_im_session
*session
;
3313 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what
);
3315 session
= find_chat_session_by_id(sip
, id
);
3317 // Queue the message
3319 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
,
3321 sipe_im_process_queue(sip
, session
);
3327 /* End IM Session (INVITE and MESSAGE methods) */
3329 static void process_incoming_info(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3331 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
3332 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3333 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3335 struct sip_im_session
*session
= find_chat_session(sip
, callid
);
3337 session
= find_im_session(sip
, from
);
3344 if (!strncmp(contenttype
, "application/x-ms-mim", 20)) {
3345 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3346 xmlnode
*xn_request_rm
= xmlnode_get_child(xn_action
, "RequestRM");
3347 xmlnode
*xn_set_rm
= xmlnode_get_child(xn_action
, "SetRM");
3349 sipmsg_remove_header(msg
, "User-Agent");
3351 if (xn_request_rm
) {
3352 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3353 int bid
= atoi(xmlnode_get_attrib(xn_request_rm
, "bid"));
3354 gchar
*body
= g_strdup_printf(
3355 "<?xml version=\"1.0\"?>\r\n"
3356 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3357 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3359 session
->bid
< bid
? "true" : "false");
3360 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3362 } else if (xn_set_rm
) {
3364 const char *rm
= xmlnode_get_attrib(xn_set_rm
, "uri");
3365 g_free(session
->roster_manager
);
3366 session
->roster_manager
= g_strdup(rm
);
3368 body
= g_strdup_printf(
3369 "<?xml version=\"1.0\"?>\r\n"
3370 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3371 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3373 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3376 xmlnode_free(xn_action
);
3379 /* looks like purple lacks typing notification for chat */
3380 if (!session
->is_multiparty
) {
3381 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
3384 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3389 static void process_incoming_bye(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3391 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3392 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3393 struct sip_im_session
*session
;
3394 struct sip_dialog
*dialog
;
3396 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3398 session
= find_chat_session(sip
, callid
);
3400 session
= find_im_session(sip
, from
);
3407 if (session
->roster_manager
&& !g_strcasecmp(from
, session
->roster_manager
)) {
3408 g_free(session
->roster_manager
);
3409 session
->roster_manager
= NULL
;
3412 if (!session
->is_multiparty
) {
3413 // TODO Let the user know the other user left the conversation?
3414 im_session_destroy(sip
, session
);
3416 dialog
= get_dialog(session
, from
);
3417 session
->dialogs
= g_slist_remove(session
->dialogs
, dialog
);
3418 free_dialog(dialog
);
3420 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session
->conv
), from
, NULL
);
3422 if (!session
->dialogs
) {
3423 im_session_destroy(sip
, session
);
3430 static void process_incoming_refer(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3432 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
3433 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3434 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3435 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
3436 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
3437 struct sip_im_session
*session
;
3438 struct sip_dialog
*dialog
;
3440 session
= find_chat_session(sip
, callid
);
3441 dialog
= get_dialog(session
, from
);
3443 sipmsg_remove_header(msg
, "User-Agent");
3444 sipmsg_remove_header(msg
, "Refer-to");
3445 sipmsg_remove_header(msg
, "Referred-By");
3446 sipmsg_remove_header(msg
, "Require");
3448 if (!session
|| !dialog
|| !session
->roster_manager
|| strcmp(session
->roster_manager
, self
)) {
3449 send_sip_response(sip
->gc
, msg
, 500, "Server Internal Error", NULL
);
3451 send_sip_response(sip
->gc
, msg
, 202, "Accepted", NULL
);
3453 sipe_invite(sip
, session
, refer_to
, NULL
, referred_by
, FALSE
);
3459 g_free(referred_by
);
3463 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
3465 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
3466 struct sip_im_session
*session
;
3467 struct sip_dialog
*dialog
;
3469 if (state
== PURPLE_NOT_TYPING
)
3472 session
= find_im_session(sip
, who
);
3473 dialog
= get_dialog(session
, who
);
3475 if (session
&& dialog
) {
3476 send_sip_request(gc
, "INFO", who
, who
,
3477 "Content-Type: application/xml\r\n",
3478 SIPE_SEND_TYPING
, dialog
, NULL
);
3480 return SIPE_TYPING_SEND_TIMEOUT
;
3483 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
3485 GSList
*tmp
= sip
->transactions
;
3486 time_t currtime
= time(NULL
);
3488 struct transaction
*trans
= tmp
->data
;
3490 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime
-trans
->time
);
3491 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
3494 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
3496 sendout_sipmsg(sip
, trans
->msg
);
3503 static void do_reauthenticate_cb(struct sipe_account_data
*sip
, void *unused
)
3505 /* register again when security token expires */
3506 /* we have to start a new authentication as the security token
3507 * is almost expired by sending a not signed REGISTER message */
3508 purple_debug_info("sipe", "do a full reauthentication\n");
3509 sipe_auth_free(&sip
->registrar
);
3510 sipe_auth_free(&sip
->proxy
);
3511 sip
->registerstatus
= 0;
3513 sip
->reauthenticate_set
= FALSE
;
3516 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3520 gboolean found
= FALSE
;
3522 from
= parse_from(sipmsg_find_header(msg
, "From"));
3526 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
3528 contenttype
= sipmsg_find_header(msg
, "Content-Type");
3529 if (!strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
3531 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3532 gchar
*html
= get_html_message(contenttype
, msg
->body
);
3534 struct sip_im_session
*session
= find_chat_session(sip
, callid
);
3536 session
= find_im_session(sip
, from
);
3539 if (session
&& session
->is_multiparty
) {
3540 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
3541 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
3543 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3546 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3549 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
3550 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3555 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3559 state
= xmlnode_get_child(isc
, "state");
3562 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3567 statedata
= xmlnode_get_data(state
);
3569 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
3570 else serv_got_typing_stopped(sip
->gc
, from
);
3575 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3579 purple_debug_info("sipe", "got unknown mime-type");
3580 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
3585 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3587 gchar
*ms_text_format
;
3589 gchar
*newTag
= gentag();
3592 gboolean is_multiparty
= FALSE
;
3593 gboolean is_triggered
= FALSE
;
3594 gboolean was_multiparty
= TRUE
;
3595 gboolean just_joined
= FALSE
;
3596 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3597 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
3598 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3599 gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
3600 gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
3601 gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
3602 gchar
**end_points
= NULL
;
3603 struct sip_im_session
*session
;
3604 struct sip_dialog
*dialog
;
3606 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? msg
->body
: "");
3608 /* Only accept text invitations */
3609 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
3610 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3614 // TODO There *must* be a better way to clean up the To header to add a tag...
3615 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3616 oldHeader
= sipmsg_find_header(msg
, "To");
3617 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
3618 sipmsg_remove_header(msg
, "To");
3619 sipmsg_add_header(msg
, "To", newHeader
);
3622 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3623 if (end_points_hdr
) {
3624 end_points
= g_strsplit(end_points_hdr
, ",", 0);
3625 if (end_points
[0] && end_points
[1] && end_points
[2]) {
3626 is_multiparty
= TRUE
;
3629 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
3630 is_triggered
= TRUE
;
3631 is_multiparty
= TRUE
;
3634 session
= find_chat_session(sip
, callid
);
3635 /* Convert to multiparty */
3636 if (session
&& is_multiparty
&& !session
->is_multiparty
) {
3637 g_free(session
->with
);
3638 session
->with
= NULL
;
3639 was_multiparty
= FALSE
;
3640 session
->is_multiparty
= TRUE
;
3641 session
->chat_id
= rand();
3644 if (!session
&& is_multiparty
) {
3645 session
= find_or_create_chat_session(sip
, callid
);
3649 session
= find_or_create_im_session(sip
, from
);
3652 if (!session
->callid
) {
3653 session
->callid
= g_strdup(callid
);
3656 session
->is_multiparty
= is_multiparty
;
3657 if (roster_manager
) {
3658 session
->roster_manager
= g_strdup(roster_manager
);
3661 if (is_multiparty
&& end_points
) {
3663 while (end_points
[i
]) {
3664 gchar
*end_point
= parse_from(end_points
[i
]);
3665 gchar
*epid
= sipmsg_find_part_of_header(end_points
[i
], "epid=", ";", NULL
);
3667 if (!g_strcasecmp(from
, end_point
) || !g_strcasecmp(to
, end_point
)) {
3672 dialog
= get_dialog(session
, end_point
);
3674 dialog
= g_new0(struct sip_dialog
, 1);
3675 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3677 dialog
->callid
= g_strdup(session
->callid
);
3678 dialog
->with
= g_strdup(end_point
);
3679 dialog
->theirepid
= epid
;
3683 /* send triggered INVITE */
3684 sipe_invite(sip
, session
, end_point
, NULL
, NULL
, TRUE
);
3686 dialog
->theirepid
= epid
;
3694 dialog
= get_dialog(session
, from
);
3696 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3698 dialog
= g_new0(struct sip_dialog
, 1);
3699 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3701 dialog
->callid
= g_strdup(session
->callid
);
3702 dialog
->with
= g_strdup(from
);
3703 sipe_parse_dialog(msg
, dialog
, FALSE
);
3705 if (!dialog
->ourtag
) {
3706 dialog
->ourtag
= newTag
;
3713 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3717 if (is_multiparty
&& !session
->conv
) {
3718 gchar
*chat_name
= g_strdup_printf(_("Chat #%d"), ++sip
->chat_seq
);
3719 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
3720 /* create prpl chat */
3721 session
->conv
= serv_got_joined_chat(sip
->gc
, session
->chat_id
, chat_name
);
3722 session
->chat_name
= g_strdup(chat_name
);
3724 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3726 PURPLE_CBFLAGS_NONE
, FALSE
);
3731 if (is_multiparty
&& !was_multiparty
) {
3732 /* add current IM counterparty to chat */
3733 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3734 ((struct sip_dialog
*)session
->dialogs
->data
)->with
, NULL
,
3735 PURPLE_CBFLAGS_NONE
, FALSE
);
3739 /* add inviting party */
3741 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3743 PURPLE_CBFLAGS_NONE
, TRUE
);
3746 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3747 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
3748 if (ms_text_format
) {
3749 if (!strncmp(ms_text_format
, "text/plain", 10) || !strncmp(ms_text_format
, "text/html", 9)) {
3751 gchar
*html
= get_html_message(ms_text_format
, NULL
);
3753 if (is_multiparty
) {
3754 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
3755 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
3757 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3760 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message received
3766 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3767 sipmsg_remove_header(msg
, "Ms-Text-Format");
3768 sipmsg_remove_header(msg
, "EndPoints");
3769 sipmsg_remove_header(msg
, "User-Agent");
3770 sipmsg_remove_header(msg
, "Roster-Manager");
3771 sipmsg_remove_header(msg
, "P-Asserted-Identity");
3772 sipmsg_remove_header(msg
, "Require");
3774 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3775 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
3777 body
= g_strdup_printf(
3779 "o=- 0 0 IN IP4 %s\r\n"
3783 "m=message %d sip sip:%s\r\n"
3784 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3785 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3786 sip
->realport
, sip
->username
);
3787 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3791 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3795 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3796 sipmsg_remove_header(msg
, "EndPoints");
3797 sipmsg_remove_header(msg
, "User-Agent");
3799 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3800 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3802 body
= g_strdup_printf(
3804 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3806 "c=IN IP4 0.0.0.0\r\n"
3808 "m=message %d sip sip:%s\r\n"
3809 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3810 sip
->realport
, sip
->username
);
3811 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3815 static void sipe_connection_cleanup(struct sipe_account_data
*);
3816 static void create_connection(struct sipe_account_data
*, gchar
*, int);
3818 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3821 const gchar
*expires_header
;
3823 GSList
*hdr
= msg
->headers
;
3825 struct siphdrelement
*elem
;
3827 expires_header
= sipmsg_find_header(msg
, "Expires");
3828 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
3829 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
3831 switch (msg
->response
) {
3834 sip
->registerstatus
= 0;
3836 gchar
*contact_hdr
= NULL
;
3842 if (!sip
->reregister_set
) {
3843 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
3844 sipe_schedule_action(action_name
, expires
, do_register_cb
, NULL
, sip
, NULL
);
3845 g_free(action_name
);
3846 sip
->reregister_set
= TRUE
;
3849 sip
->registerstatus
= 3;
3852 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3854 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3857 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3860 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3861 fill_auth(sip
, tmp
, &sip
->registrar
);
3863 if (!sip
->reauthenticate_set
) {
3864 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
3865 guint reauth_timeout
;
3866 if (sip
->registrar
.type
== AUTH_TYPE_KERBEROS
&& sip
->registrar
.expires
> 0) {
3867 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3868 reauth_timeout
= sip
->registrar
.expires
- 300;
3870 /* NTLM: we have to reauthenticate as our security token expires
3871 after eight hours (be five minutes early) */
3872 reauth_timeout
= (8 * 3600) - 300;
3874 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, NULL
, sip
, NULL
);
3875 g_free(action_name
);
3876 sip
->reauthenticate_set
= TRUE
;
3879 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
3881 epid
= get_epid(sip
);
3882 uuid
= generateUUIDfromEPID(epid
);
3885 // There can be multiple Contact headers (one per location where the user is logged in) so
3886 // make sure to only get the one for this uuid
3887 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
3888 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
3889 if (valid_contact
) {
3890 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
3891 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3892 g_free(valid_contact
);
3895 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3900 g_free(sip
->contact
);
3902 sip
->contact
= g_strdup_printf("<%s>", gruu
);
3905 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3906 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
);
3908 sip
->msrtc_event_categories
= FALSE
;
3909 sip
->batched_support
= FALSE
;
3914 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
3915 if (!g_ascii_strcasecmp(elem
->value
, "msrtc-event-categories")) {
3916 sip
->msrtc_event_categories
= TRUE
;
3917 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->msrtc_event_categories
);
3919 if (!g_ascii_strcasecmp(elem
->value
, "adhoclist")) {
3920 sip
->batched_support
= TRUE
;
3921 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->batched_support
);
3924 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
3925 gchar
**caps
= g_strsplit(elem
->value
,",",0);
3928 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
3929 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
3934 hdr
= g_slist_next(hdr
);
3937 if (!sip
->subscribed
) { //do it just once, not every re-register
3938 if(!sip
->msrtc_event_categories
){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3939 //sipe_options_request(sip, sip->sipdomain);
3941 entry
= sip
->allow_events
;
3944 if (tmp
&& !g_ascii_strcasecmp(tmp
, "vnd-microsoft-roaming-contacts")) {
3945 sipe_subscribe_roaming_contacts(sip
, msg
);
3947 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-ACL")) {
3948 sipe_subscribe_roaming_acl(sip
, msg
);
3950 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-self")) {
3951 sipe_subscribe_roaming_self(sip
, msg
);
3953 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning-v2")) {
3954 sipe_subscribe_roaming_provisioning_v2(sip
, msg
);
3955 } else if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning")) { // LSC2005
3956 sipe_subscribe_roaming_provisioning(sip
, msg
);
3958 if (tmp
&& !g_ascii_strcasecmp(tmp
,"presence.wpending")) {
3959 sipe_subscribe_presence_wpending(sip
, msg
);
3961 entry
= entry
->next
;
3963 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
3964 sip
->subscribed
= TRUE
;
3967 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
3968 "timeout=", ";", NULL
);
3969 if (timeout
!= NULL
) {
3970 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
3971 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3972 sip
->keepalive_timeout
);
3976 // Should we remove the transaction here?
3977 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
3978 transactions_remove(sip
, tc
);
3983 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
3985 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
3986 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
3990 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
3993 tmp
= g_strsplit(parts
[0], ":", 0);
3994 hostname
= g_strdup(tmp
[0]);
3995 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
3999 tmp
= g_strsplit(parts
[i
], "=", 0);
4001 if (g_strcasecmp("transport", tmp
[0]) == 0) {
4002 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
4003 transport
= SIPE_TRANSPORT_TCP
;
4004 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
4005 transport
= SIPE_TRANSPORT_UDP
;
4014 /* Close old connection */
4015 sipe_connection_cleanup(sip
);
4017 /* Create new connection */
4018 sip
->transport
= transport
;
4019 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4020 hostname
, port
, TRANSPORT_DESCRIPTOR
);
4021 create_connection(sip
, hostname
, port
);
4027 if (sip
->registerstatus
!= 2) {
4028 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
4029 if (sip
->registrar
.retries
> 3) {
4030 sip
->gc
->wants_to_die
= TRUE
;
4031 purple_connection_error(sip
->gc
, _("Wrong Password"));
4035 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
4037 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
4040 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
4043 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
4044 fill_auth(sip
, tmp
, &sip
->registrar
);
4045 sip
->registerstatus
= 2;
4046 if (sip
->account
->disconnecting
) {
4047 do_register_exp(sip
, 0);
4055 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
4056 if (warning
!= NULL
) {
4058 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4060 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
4061 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
4064 warning
= g_strdup(_("You have been rejected by the server"));
4067 sip
->gc
->wants_to_die
= TRUE
;
4068 purple_connection_error(sip
->gc
, warning
);
4075 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
4076 if (warning
!= NULL
) {
4077 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
4078 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
4081 warning
= g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4084 sip
->gc
->wants_to_die
= TRUE
;
4085 purple_connection_error(sip
->gc
, warning
);
4092 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
4093 if (warning
!= NULL
) {
4094 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
4095 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
4098 warning
= g_strdup(_("Service unavailable: no reason given"));
4101 sip
->gc
->wants_to_die
= TRUE
;
4102 purple_connection_error(sip
->gc
, warning
);
4111 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4114 xmlnode
*xn_categories
;
4115 xmlnode
*xn_category
;
4117 const char *activity
= NULL
;
4119 xn_categories
= xmlnode_from_str(data
, len
);
4120 uri
= xmlnode_get_attrib(xn_categories
, "uri");
4122 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
4124 xn_category
= xmlnode_get_next_twin(xn_category
) )
4126 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
4128 if (!strcmp(attrVar
, "note"))
4131 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4136 xn_node
= xmlnode_get_child(xn_category
, "note");
4137 if (!xn_node
) continue;
4138 xn_node
= xmlnode_get_child(xn_node
, "body");
4139 if (!xn_node
) continue;
4140 note
= xmlnode_get_data(xn_node
);
4141 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri
,note
? note
: "");
4142 g_free(sbuddy
->annotation
);
4143 sbuddy
->annotation
= NULL
;
4144 if (note
) sbuddy
->annotation
= g_strdup(note
);
4150 else if(!strcmp(attrVar
, "state"))
4154 xn_node
= xmlnode_get_child(xn_category
, "state");
4155 if (!xn_node
) continue;
4156 xn_node
= xmlnode_get_child(xn_node
, "availability");
4157 if (!xn_node
) continue;
4159 data
= xmlnode_get_data(xn_node
);
4164 activity
= SIPE_STATUS_ID_UNKNOWN
;
4165 else if (avail
< 4500)
4166 activity
= SIPE_STATUS_ID_AVAILABLE
;
4167 else if (avail
< 6000)
4168 activity
= SIPE_STATUS_ID_BRB
;
4169 else if (avail
< 7500)
4170 activity
= SIPE_STATUS_ID_ONPHONE
;
4171 else if (avail
< 9000)
4172 activity
= SIPE_STATUS_ID_BUSY
;
4173 else if (avail
< 12000)
4174 activity
= SIPE_STATUS_ID_DND
;
4175 else if (avail
< 18000)
4176 activity
= SIPE_STATUS_ID_AWAY
;
4178 activity
= SIPE_STATUS_ID_OFFLINE
;
4182 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity
);
4183 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
4186 xmlnode_free(xn_categories
);
4189 static void sipe_subscribe_poolfqdn_resource_uri(const char *host
, GSList
*server
, struct sipe_account_data
*sip
)
4191 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
4192 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host
);
4193 payload
->host
= g_strdup(host
);
4194 payload
->buddies
= server
;
4195 sipe_subscribe_presence_batched_routed(sip
, payload
);
4196 sipe_subscribe_presence_batched_routed_free(payload
);
4199 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4202 xmlnode
*xn_resource
;
4203 GHashTable
*servers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
4208 xn_list
= xmlnode_from_str(data
, len
);
4210 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
4212 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
4214 const char *uri
, *state
;
4215 xmlnode
*xn_instance
;
4217 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
4218 if (!xn_instance
) continue;
4220 uri
= xmlnode_get_attrib(xn_resource
, "uri");
4221 state
= xmlnode_get_attrib(xn_instance
, "state");
4222 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri
, state
);
4224 if (strstr(state
, "resubscribe")) {
4225 const char *poolFqdn
= xmlnode_get_attrib(xn_instance
, "poolFqdn");
4226 struct sipe_buddy
*sbuddy
;
4227 if (poolFqdn
) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4228 gchar
*user
= g_strdup(uri
);
4229 host
= g_strdup(poolFqdn
);
4230 server
= g_hash_table_lookup(servers
, host
);
4231 server
= g_slist_append(server
, user
);
4232 g_hash_table_insert(servers
, host
, server
);
4234 sipe_subscribe_presence_single(sip
, (void *) uri
);
4236 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4238 sbuddy
->resubscribed
= TRUE
;
4243 /* Send out any deferred poolFqdn subscriptions */
4244 g_hash_table_foreach(servers
, (GHFunc
) sipe_subscribe_poolfqdn_resource_uri
, sip
);
4245 g_hash_table_destroy(servers
);
4247 xmlnode_free(xn_list
);
4250 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4254 gchar
*activity
= NULL
;
4256 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
4257 gboolean isonline
= FALSE
;
4258 xmlnode
*display_name_node
;
4260 pidf
= xmlnode_from_str(data
, len
);
4262 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
4266 uri
= xmlnode_get_attrib(pidf
, "entity");
4268 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
4270 if ((status
= xmlnode_get_child(tuple
, "status"))) {
4271 basicstatus
= xmlnode_get_child(status
, "basic");
4276 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4281 getbasic
= xmlnode_get_data(basicstatus
);
4283 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4288 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
4289 if (strstr(getbasic
, "open")) {
4294 display_name_node
= xmlnode_get_child(pidf
, "display-name");
4295 // updating display name if alias was just URI
4296 if (display_name_node
) {
4297 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
4298 GSList
*entry
= buddies
;
4299 PurpleBuddy
*p_buddy
;
4300 char * display_name
= xmlnode_get_data(display_name_node
);
4303 const char *server_alias
;
4306 p_buddy
= entry
->data
;
4308 alias
= (char *)purple_buddy_get_alias(p_buddy
);
4309 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
4310 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
4311 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
4312 purple_blist_alias_buddy(p_buddy
, display_name
);
4316 server_alias
= purple_buddy_get_server_alias(p_buddy
);
4318 ( (server_alias
&& strcmp(display_name
, server_alias
))
4319 || !server_alias
|| strlen(server_alias
) == 0 )
4321 purple_blist_server_alias_buddy(p_buddy
, display_name
);
4324 entry
= entry
->next
;
4326 g_free(display_name
);
4329 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
4330 if ((status
= xmlnode_get_child(tuple
, "status"))) {
4331 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
4332 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
4333 activity
= xmlnode_get_data(basicstatus
);
4334 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
4341 const gchar
* status_id
= NULL
;
4343 if (strstr(activity
, "busy")) {
4344 status_id
= SIPE_STATUS_ID_BUSY
;
4345 } else if (strstr(activity
, "away")) {
4346 status_id
= SIPE_STATUS_ID_AWAY
;
4351 status_id
= SIPE_STATUS_ID_AVAILABLE
;
4354 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
4355 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
4357 purple_prpl_got_user_status(sip
->account
, uri
, SIPE_STATUS_ID_OFFLINE
, NULL
);
4364 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4366 const char *availability
;
4367 const char *activity
;
4368 const char *display_name
= NULL
;
4369 const char *activity_name
= NULL
;
4374 struct sipe_buddy
*sbuddy
;
4376 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
4378 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
4379 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
4380 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
4381 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
4382 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
4383 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
4384 xmlnode
*xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
):NULL
;
4385 const char *avail
= xn_state
? xmlnode_get_attrib(xn_state
, "avail") : NULL
;
4387 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
4388 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
4389 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
4390 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
4391 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
4392 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
4394 name
= xmlnode_get_attrib(xn_presentity
, "uri");
4395 uri
= g_strdup_printf("sip:%s", name
);
4396 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
4397 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
4399 // updating display name if alias was just URI
4400 if (xn_display_name
) {
4401 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
4402 GSList
*entry
= buddies
;
4403 PurpleBuddy
*p_buddy
;
4404 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
4407 const char *email_str
, *server_alias
;
4409 p_buddy
= entry
->data
;
4411 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
4412 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
4413 purple_blist_alias_buddy(p_buddy
, display_name
);
4416 server_alias
= purple_buddy_get_server_alias(p_buddy
);
4418 ( (server_alias
&& strcmp(display_name
, server_alias
))
4419 || !server_alias
|| strlen(server_alias
) == 0 )
4421 purple_blist_server_alias_buddy(p_buddy
, display_name
);
4425 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
4426 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
4427 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
4431 entry
= entry
->next
;
4435 avl
= atoi(availability
);
4436 act
= atoi(activity
);
4438 if(sip
->msrtc_event_categories
){
4439 if (act
== 100 && avl
== 0)
4440 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4441 else if (act
== 100 && avl
== 300)
4442 activity_name
= SIPE_STATUS_ID_AWAY
;
4443 else if (act
== 300 && avl
== 300)
4444 activity_name
= SIPE_STATUS_ID_BRB
;
4445 else if (act
== 400 && avl
== 300)
4446 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4447 else if (act
== 500 && act
== 300)
4448 activity_name
= SIPE_STATUS_ID_ONPHONE
;
4449 else if (act
== 600 && avl
== 300)
4450 activity_name
= SIPE_STATUS_ID_BUSY
;
4451 else if (act
== 0 && avl
== 0){ //MSRTC elements are zero
4452 if(avail
){ //Check for LegacyInterop elements
4455 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4456 else if (avl
== 3500)
4457 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4458 else if (avl
== 15500)
4459 activity_name
= SIPE_STATUS_ID_AWAY
;
4460 else if (avl
== 6500)
4461 activity_name
= SIPE_STATUS_ID_BUSY
;
4462 else if (avl
== 12500)
4463 activity_name
= SIPE_STATUS_ID_BRB
;
4468 if(activity_name
== NULL
){
4470 activity_name
= SIPE_STATUS_ID_AWAY
;
4471 else if (act
<= 150)
4472 activity_name
= SIPE_STATUS_ID_LUNCH
;
4473 else if (act
<= 300)
4474 activity_name
= SIPE_STATUS_ID_BRB
;
4475 else if (act
<= 400)
4476 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4477 else if (act
<= 500)
4478 activity_name
= SIPE_STATUS_ID_ONPHONE
;
4479 else if (act
<= 600)
4480 activity_name
= SIPE_STATUS_ID_BUSY
;
4482 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4485 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4488 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4491 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
4492 sbuddy
->annotation
= NULL
;
4493 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
4495 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
4496 sbuddy
->device_name
= NULL
;
4497 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
4500 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
4501 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
4503 xmlnode_free(xn_presentity
);
4507 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4509 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
4511 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
4513 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
4514 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
4516 const char *content
= msg
->body
;
4517 unsigned length
= msg
->bodylen
;
4518 PurpleMimeDocument
*mime
= NULL
;
4520 if (strstr(ctype
, "multipart"))
4522 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
4523 const char *content_type
;
4525 mime
= purple_mime_document_parse(doc
);
4526 parts
= purple_mime_document_get_parts(mime
);
4528 content
= purple_mime_part_get_data(parts
->data
);
4529 length
= purple_mime_part_get_length(parts
->data
);
4530 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
4531 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
4533 process_incoming_notify_rlmi_resub(sip
, content
, length
);
4535 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
4537 process_incoming_notify_msrtc(sip
, content
, length
);
4541 process_incoming_notify_rlmi(sip
, content
, length
);
4543 parts
= parts
->next
;
4549 purple_mime_document_free(mime
);
4552 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
4554 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
4556 else if(strstr(ctype
, "application/rlmi+xml"))
4558 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
4561 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
4563 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
4567 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
4571 static void sipe_process_presence_timeout(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gchar
*who
, int timeout
)
4573 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
4574 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
4576 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype
? ctype
: "");
4579 strstr(ctype
, "multipart") &&
4580 (strstr(ctype
, "application/rlmi+xml") ||
4581 strstr(ctype
, "application/msrtc-event-categories+xml"))) {
4582 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
4583 PurpleMimeDocument
*mime
= purple_mime_document_parse(doc
);
4584 GList
*parts
= purple_mime_document_get_parts(mime
);
4585 GSList
*buddies
= NULL
;
4586 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
4589 xmlnode
*xml
= xmlnode_from_str(purple_mime_part_get_data(parts
->data
),
4590 purple_mime_part_get_length(parts
->data
));
4591 gchar
*uri
= g_strdup(xmlnode_get_attrib(xml
, "uri"));
4593 if (strstr(uri
, "sip:") == NULL
) {
4595 uri
= g_strdup_printf("sip:%s", tmp
);
4598 buddies
= g_slist_append(buddies
, uri
);
4601 parts
= parts
->next
;
4604 if (mime
) purple_mime_document_free(mime
);
4606 payload
->host
= who
;
4607 payload
->buddies
= buddies
;
4608 sipe_schedule_action(action_name
, timeout
,
4609 sipe_subscribe_presence_batched_routed
,
4610 sipe_subscribe_presence_batched_routed_free
,
4612 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who
, timeout
);
4615 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, who
);
4616 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
, timeout
);
4618 g_free(action_name
);
4622 * Dispatcher for all incoming subscription information
4623 * whether it comes from NOTIFY, BENOTIFY requests or
4624 * piggy-backed to subscription's OK responce.
4626 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4627 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4629 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
4631 gchar
*event
= sipmsg_find_header(msg
, "Event");
4632 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
4635 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
4636 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
4640 const gchar
*expires_header
;
4641 expires_header
= sipmsg_find_header(msg
, "Expires");
4642 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
4643 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout
);
4644 timeout
= (timeout
- 60) > 60 ? (timeout
- 60) : timeout
; // 1 min ahead of expiration
4647 if (!subscription_state
|| strstr(subscription_state
, "active"))
4649 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
4651 sipe_process_presence(sip
, msg
);
4653 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
4655 sipe_process_roaming_contacts(sip
, msg
, NULL
);
4657 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self") )
4659 sipe_process_roaming_self(sip
, msg
);
4661 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
4663 sipe_process_roaming_acl(sip
, msg
);
4665 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
4667 sipe_process_presence_wpending(sip
, msg
);
4671 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
4675 //The server sends a (BE)NOTIFY with the status 'terminated'
4676 if (request
&& subscription_state
&& strstr(subscription_state
, "terminated") ) {
4677 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4678 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
4682 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
4683 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4684 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4686 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4687 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4688 g_free(action_name);
4690 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4691 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4693 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4694 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4695 g_free(action_name);
4698 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
4699 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
4701 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
4702 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, NULL
, sip
, NULL
);
4703 g_free(action_name
);
4705 else if (!g_ascii_strcasecmp(event
, "presence") &&
4706 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
4708 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
4709 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
4710 if(sip
->batched_support
) {
4711 gchar
*my_self
= g_strdup_printf("sip:%s",sip
->username
);
4712 if(!g_ascii_strcasecmp(who
, my_self
)){
4713 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_batched
, NULL
, sip
, NULL
);
4714 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout
);
4715 g_free(who
); /* unused */
4718 sipe_process_presence_timeout(sip
, msg
, who
, timeout
);
4723 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, who
);
4724 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
,timeout
);
4726 g_free(action_name
);
4727 /* "who" will be freed by the action we just scheduled */
4731 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
4733 sipe_process_registration_notify(sip
, msg
);
4736 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4737 if (request
&& !benotify
)
4739 sipmsg_remove_header(msg
, "Expires");
4740 sipmsg_remove_header(msg
, "subscription-state");
4741 sipmsg_remove_header(msg
, "Event");
4742 sipmsg_remove_header(msg
, "Require");
4743 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4750 static gchar* gen_xpidf(struct sipe_account_data *sip)
4752 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4754 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4755 "<display name=\"sip:%s\"/>\r\n"
4756 "<atom id=\"1234\">\r\n"
4757 "<address uri=\"sip:%s\">\r\n"
4758 "<status status=\"%s\"/>\r\n"
4771 static gchar* gen_pidf(struct sipe_account_data *sip)
4773 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4774 "<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"
4775 "<tuple id=\"0\">\r\n"
4777 "<basic>open</basic>\r\n"
4778 "<ep:activities>\r\n"
4779 " <ep:activity>%s</ep:activity>\r\n"
4783 "<ci:display-name>%s</ci:display-name>\r\n"
4792 static void send_presence_soap(struct sipe_account_data
*sip
, const char * note
)
4794 int availability
= 300; // online
4795 int activity
= 400; // Available
4798 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
)) {
4800 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4802 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4804 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4806 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4808 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4810 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_INVISIBLE
) ||
4811 !strcmp(sip
->status
, SIPE_STATUS_ID_OFFLINE
)) {
4812 availability
= 0; // offline
4815 activity
= 400; // available
4818 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
4819 //@TODO: send user data - state; add hostname in upper case
4820 body
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
4821 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
4827 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4829 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4830 if (msg
->response
== 200) {
4831 sip
->status_version
= 0;
4832 send_presence_status(sip
);
4838 process_send_presence_category_publish_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4840 if (msg
->response
== 409) {
4841 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4842 // TODO need to parse the version #'s?
4843 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
4844 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
4848 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg
->body
);
4850 tmp
= get_contact(sip
);
4851 hdr
= g_strdup_printf("Contact: %s\r\n"
4852 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4854 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
4864 static void send_presence_category_publish(struct sipe_account_data
*sip
, const char * note
)
4871 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
) ||
4872 !strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4874 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_DND
)) {
4876 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4878 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4880 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4882 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4884 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_UNKNOWN
)) {
4887 // Offline or invisible
4891 uri
= g_strdup_printf("sip:%s", sip
->username
);
4892 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
4893 sip
->status_version
, code
,
4894 sip
->status_version
, code
,
4895 sip
->status_version
, note
? note
: "",
4896 sip
->status_version
, note
? note
: "",
4897 sip
->status_version
, note
? note
: ""
4899 sip
->status_version
++;
4901 tmp
= get_contact(sip
);
4902 hdr
= g_strdup_printf("Contact: %s\r\n"
4903 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4905 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
4913 static void send_presence_status(struct sipe_account_data
*sip
)
4915 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
4917 if (!status
) return;
4919 note
= purple_status_get_attr_string(status
, "message");
4921 if(sip
->msrtc_event_categories
){
4922 send_presence_category_publish(sip
, note
);
4924 send_presence_soap(sip
, note
);
4928 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
4930 gboolean found
= FALSE
;
4931 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
4932 if (msg
->response
== 0) { /* request */
4933 if (!strcmp(msg
->method
, "MESSAGE")) {
4934 process_incoming_message(sip
, msg
);
4936 } else if (!strcmp(msg
->method
, "NOTIFY")) {
4937 purple_debug_info("sipe","send->process_incoming_notify\n");
4938 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
4940 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
4941 purple_debug_info("sipe","send->process_incoming_benotify\n");
4942 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
4944 } else if (!strcmp(msg
->method
, "INVITE")) {
4945 process_incoming_invite(sip
, msg
);
4947 } else if (!strcmp(msg
->method
, "REFER")) {
4948 process_incoming_refer(sip
, msg
);
4950 } else if (!strcmp(msg
->method
, "OPTIONS")) {
4951 process_incoming_options(sip
, msg
);
4953 } else if (!strcmp(msg
->method
, "INFO")) {
4954 process_incoming_info(sip
, msg
);
4956 } else if (!strcmp(msg
->method
, "ACK")) {
4957 // ACK's don't need any response
4959 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
4960 // LCS 2005 sends us these - just respond 200 OK
4962 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4963 } else if (!strcmp(msg
->method
, "BYE")) {
4964 process_incoming_bye(sip
, msg
);
4967 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4969 } else { /* response */
4970 struct transaction
*trans
= transactions_find(sip
, msg
);
4972 if (msg
->response
== 407) {
4973 gchar
*resend
, *auth
, *ptmp
;
4975 if (sip
->proxy
.retries
> 30) return;
4976 sip
->proxy
.retries
++;
4977 /* do proxy authentication */
4979 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
4981 fill_auth(sip
, ptmp
, &sip
->proxy
);
4982 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
4983 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4984 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
4986 resend
= sipmsg_to_string(trans
->msg
);
4987 /* resend request */
4988 sendout_pkt(sip
->gc
, resend
);
4991 if (msg
->response
== 100 || msg
->response
== 180) {
4992 /* ignore provisional response */
4993 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
4995 sip
->proxy
.retries
= 0;
4996 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
4997 if (msg
->response
== 401)
4999 sip
->registrar
.retries
++;
5003 sip
->registrar
.retries
= 0;
5005 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
5007 if (msg
->response
== 401) {
5008 gchar
*resend
, *auth
, *ptmp
;
5010 if (sip
->registrar
.retries
> 4) return;
5011 sip
->registrar
.retries
++;
5014 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5016 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
5019 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
5023 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
5025 fill_auth(sip
, ptmp
, &sip
->registrar
);
5026 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
5027 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
5028 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
5030 //sipmsg_remove_header(trans->msg, "Authorization");
5031 //sipmsg_add_header(trans->msg, "Authorization", auth);
5033 resend
= sipmsg_to_string(trans
->msg
);
5034 /* resend request */
5035 sendout_pkt(sip
->gc
, resend
);
5040 if (trans
->callback
) {
5041 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
5042 /* call the callback to process response*/
5043 (trans
->callback
)(sip
, msg
, trans
);
5045 /* Not sure if this is needed or what needs to be done
5046 but transactions seem to be removed prematurely so
5047 this only removes them if the response is 200 OK */
5048 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
5049 /*Has a bug and it's unneccesary*/
5050 /*transactions_remove(sip, trans);*/
5056 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
5060 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
5064 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
5072 /* according to the RFC remove CRLF at the beginning */
5073 while (*cur
== '\r' || *cur
== '\n') {
5076 if (cur
!= conn
->inbuf
) {
5077 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
5078 conn
->inbufused
= strlen(conn
->inbuf
);
5081 /* Received a full Header? */
5082 sip
->processing_input
= TRUE
;
5083 while (sip
->processing_input
&&
5084 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
5085 time_t currtime
= time(NULL
);
5088 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
5089 msg
= sipmsg_parse_header(conn
->inbuf
);
5092 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
5093 if (restlen
>= msg
->bodylen
) {
5094 dummy
= g_malloc(msg
->bodylen
+ 1);
5095 memcpy(dummy
, cur
, msg
->bodylen
);
5096 dummy
[msg
->bodylen
] = '\0';
5098 cur
+= msg
->bodylen
;
5099 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
5100 conn
->inbufused
= strlen(conn
->inbuf
);
5102 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5103 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
5109 purple_debug_info("sipe", "body:\n%s", msg->body);
5112 // Verify the signature before processing it
5113 if (sip
->registrar
.gssapi_context
) {
5114 struct sipmsg_breakdown msgbd
;
5115 gchar
*signature_input_str
;
5118 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
5119 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
5121 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
5123 if (rspauth
!= NULL
) {
5124 if (!sip_sec_verify_signature(sip
->registrar
.gssapi_context
, signature_input_str
, rspauth
)) {
5125 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
5126 process_input_message(sip
, msg
);
5128 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid.\n");
5129 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
5130 sip
->gc
->wants_to_die
= TRUE
;
5132 } else if (msg
->response
== 401) {
5133 purple_connection_error(sip
->gc
, _("Wrong Password"));
5134 sip
->gc
->wants_to_die
= TRUE
;
5136 g_free(signature_input_str
);
5139 sipmsg_breakdown_free(&msgbd
);
5141 process_input_message(sip
, msg
);
5148 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
5150 PurpleConnection
*gc
= data
;
5151 struct sipe_account_data
*sip
= gc
->proto_data
;
5156 static char buffer
[65536];
5157 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
5159 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
5160 msg
= sipmsg_parse_msg(buffer
);
5161 if (msg
) process_input_message(sip
, msg
);
5165 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
5167 struct sipe_account_data
*sip
= gc
->proto_data
;
5168 PurpleSslConnection
*gsc
= sip
->gsc
;
5170 purple_debug_error("sipe", "%s",debug
);
5171 purple_connection_error(gc
, msg
);
5173 /* Invalidate this connection. Next send will open a new one */
5175 connection_remove(sip
, gsc
->fd
);
5176 purple_ssl_close(gsc
);
5182 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
5184 PurpleConnection
*gc
= data
;
5185 struct sipe_account_data
*sip
;
5186 struct sip_connection
*conn
;
5188 gboolean firstread
= TRUE
;
5190 /* NOTE: This check *IS* necessary */
5191 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
5192 purple_ssl_close(gsc
);
5196 sip
= gc
->proto_data
;
5197 conn
= connection_find(sip
, gsc
->fd
);
5199 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5200 gc
->wants_to_die
= TRUE
;
5201 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
5205 /* Read all available data from the SSL connection */
5207 /* Increase input buffer size as needed */
5208 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
5209 conn
->inbuflen
+= SIMPLE_BUF_INC
;
5210 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
5211 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
5214 /* Try to read as much as there is space left in the buffer */
5215 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
5216 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
5218 if (len
< 0 && errno
== EAGAIN
) {
5219 /* Try again later */
5221 } else if (len
< 0) {
5222 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
5224 } else if (firstread
&& (len
== 0)) {
5225 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
5229 conn
->inbufused
+= len
;
5232 /* Equivalence indicates that there is possibly more data to read */
5233 } while (len
== readlen
);
5235 conn
->inbuf
[conn
->inbufused
] = '\0';
5236 process_input(sip
, conn
);
5240 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
5242 PurpleConnection
*gc
= data
;
5243 struct sipe_account_data
*sip
= gc
->proto_data
;
5245 struct sip_connection
*conn
= connection_find(sip
, source
);
5247 purple_debug_error("sipe", "Connection not found!\n");
5251 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
5252 conn
->inbuflen
+= SIMPLE_BUF_INC
;
5253 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
5256 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
5258 if (len
< 0 && errno
== EAGAIN
)
5260 else if (len
<= 0) {
5261 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5262 connection_remove(sip
, source
);
5263 if (sip
->fd
== source
) sip
->fd
= -1;
5267 conn
->inbufused
+= len
;
5268 conn
->inbuf
[conn
->inbufused
] = '\0';
5270 process_input(sip
, conn
);
5273 /* Callback for new connections on incoming TCP port */
5274 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
5276 PurpleConnection
*gc
= data
;
5277 struct sipe_account_data
*sip
= gc
->proto_data
;
5278 struct sip_connection
*conn
;
5280 int newfd
= accept(source
, NULL
, NULL
);
5282 conn
= connection_create(sip
, newfd
);
5284 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
5287 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
5289 PurpleConnection
*gc
= data
;
5290 struct sipe_account_data
*sip
;
5291 struct sip_connection
*conn
;
5293 if (!PURPLE_CONNECTION_IS_VALID(gc
))
5301 purple_connection_error(gc
, _("Could not connect"));
5305 sip
= gc
->proto_data
;
5307 sip
->last_keepalive
= time(NULL
);
5309 conn
= connection_create(sip
, source
);
5313 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
5316 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
5318 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
5319 if (sip
== NULL
) return;
5324 static guint
sipe_ht_hash_nick(const char *nick
)
5326 char *lc
= g_utf8_strdown(nick
, -1);
5327 guint bucket
= g_str_hash(lc
);
5333 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
5335 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
5338 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
5340 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5342 sip
->listen_data
= NULL
;
5344 if (listenfd
== -1) {
5345 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5351 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
5352 sip
->listenfd
= sip
->fd
;
5354 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
5356 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
5360 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
5362 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5365 sip
->query_data
= NULL
;
5367 if (!hosts
|| !hosts
->data
) {
5368 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
5372 addr_size
= GPOINTER_TO_INT(hosts
->data
);
5373 hosts
= g_slist_remove(hosts
, hosts
->data
);
5374 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
5375 g_free(hosts
->data
);
5376 hosts
= g_slist_remove(hosts
, hosts
->data
);
5378 hosts
= g_slist_remove(hosts
, hosts
->data
);
5379 g_free(hosts
->data
);
5380 hosts
= g_slist_remove(hosts
, hosts
->data
);
5383 /* create socket for incoming connections */
5384 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
5385 sipe_udp_host_resolved_listen_cb
, sip
);
5386 if (sip
->listen_data
== NULL
) {
5387 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5392 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
5395 PurpleConnection
*gc
= data
;
5396 struct sipe_account_data
*sip
;
5398 /* If the connection is already disconnected, we don't need to do anything else */
5399 if (!PURPLE_CONNECTION_IS_VALID(gc
))
5402 sip
= gc
->proto_data
;
5407 case PURPLE_SSL_CONNECT_FAILED
:
5408 purple_connection_error(gc
, _("Connection Failed"));
5410 case PURPLE_SSL_HANDSHAKE_FAILED
:
5411 purple_connection_error(gc
, _("SSL Handshake Failed"));
5413 case PURPLE_SSL_CERTIFICATE_INVALID
:
5414 purple_connection_error(gc
, _("SSL Certificate Invalid"));
5420 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
5422 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5423 PurpleProxyConnectData
*connect_data
;
5425 sip
->listen_data
= NULL
;
5427 sip
->listenfd
= listenfd
;
5428 if (sip
->listenfd
== -1) {
5429 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5433 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
5434 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5435 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
5436 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
5437 sipe_newconn_cb
, sip
->gc
);
5438 purple_debug_info("sipe", "connecting to %s port %d\n",
5439 sip
->realhostname
, sip
->realport
);
5440 /* open tcp connection to the server */
5441 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
5442 sip
->realport
, login_cb
, sip
->gc
);
5444 if (connect_data
== NULL
) {
5445 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
5450 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
5452 PurpleAccount
*account
= sip
->account
;
5453 PurpleConnection
*gc
= sip
->gc
;
5455 if (purple_account_get_bool(account
, "useport", FALSE
)) {
5456 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
5457 port
= purple_account_get_int(account
, "port", 0);
5459 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
5462 sip
->realhostname
= hostname
;
5463 sip
->realport
= port
;
5465 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
5468 /* TODO: is there a good default grow size? */
5469 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
5470 sip
->txbuf
= purple_circ_buffer_new(0);
5472 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
5474 if (!purple_ssl_is_supported()) {
5475 gc
->wants_to_die
= TRUE
;
5476 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5480 purple_debug_info("sipe", "using SSL\n");
5482 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
5483 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
5484 if (sip
->gsc
== NULL
) {
5485 purple_connection_error(gc
, _("Could not create SSL context"));
5488 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
5490 purple_debug_info("sipe", "using UDP\n");
5492 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
5493 if (sip
->query_data
== NULL
) {
5494 purple_connection_error(gc
, _("Could not resolve hostname"));
5498 purple_debug_info("sipe", "using TCP\n");
5499 /* create socket for incoming connections */
5500 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
5501 sipe_tcp_connect_listen_cb
, sip
);
5502 if (sip
->listen_data
== NULL
) {
5503 purple_connection_error(gc
, _("Could not create listen socket"));
5509 /* Service list for autodection */
5510 static const struct sipe_service_data service_autodetect
[] = {
5511 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
5512 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
5513 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
5514 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
5518 /* Service list for SSL/TLS */
5519 static const struct sipe_service_data service_tls
[] = {
5520 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
5521 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
5525 /* Service list for TCP */
5526 static const struct sipe_service_data service_tcp
[] = {
5527 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
5528 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
5532 /* Service list for UDP */
5533 static const struct sipe_service_data service_udp
[] = {
5534 { "sip", "udp", SIPE_TRANSPORT_UDP
},
5538 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
5539 static void resolve_next_service(struct sipe_account_data
*sip
,
5540 const struct sipe_service_data
*start
)
5543 sip
->service_data
= start
;
5545 sip
->service_data
++;
5546 if (sip
->service_data
->service
== NULL
) {
5548 /* Try connecting to the SIP hostname directly */
5549 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
5550 if (sip
->auto_transport
) {
5551 // If SSL is supported, default to using it; OCS servers aren't configured
5552 // by default to accept TCP
5553 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5554 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
5555 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
5558 hostname
= g_strdup(sip
->sipdomain
);
5559 create_connection(sip
, hostname
, 0);
5564 /* Try to resolve next service */
5565 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
5566 sip
->service_data
->transport
,
5571 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
5573 struct sipe_account_data
*sip
= data
;
5575 sip
->srv_query_data
= NULL
;
5577 /* find the host to connect to */
5579 gchar
*hostname
= g_strdup(resp
->hostname
);
5580 int port
= resp
->port
;
5581 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5585 sip
->transport
= sip
->service_data
->type
;
5587 create_connection(sip
, hostname
, port
);
5589 resolve_next_service(sip
, NULL
);
5593 static void sipe_login(PurpleAccount
*account
)
5595 PurpleConnection
*gc
;
5596 struct sipe_account_data
*sip
;
5597 gchar
**signinname_login
, **userserver
, **domain_user
;
5598 const char *transport
;
5600 const char *username
= purple_account_get_username(account
);
5601 gc
= purple_account_get_connection(account
);
5603 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
5604 gc
->wants_to_die
= TRUE
;
5605 purple_connection_error(gc
, _("SIP Exchange username contains invalid characters"));
5609 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
5610 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
5611 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
5613 sip
->account
= account
;
5614 sip
->reregister_set
= FALSE
;
5615 sip
->reauthenticate_set
= FALSE
;
5616 sip
->subscribed
= FALSE
;
5617 sip
->subscribed_buddies
= FALSE
;
5619 signinname_login
= g_strsplit(username
, ",", 2);
5621 userserver
= g_strsplit(signinname_login
[0], "@", 2);
5622 purple_connection_set_display_name(gc
, userserver
[0]);
5623 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
5624 sip
->sipdomain
= g_strdup(userserver
[1]);
5626 if (strpbrk(sip
->username
, " \t\v\r\n") != NULL
) {
5627 gc
->wants_to_die
= TRUE
;
5628 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
5632 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
5633 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
5634 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
5636 sip
->password
= g_strdup(purple_connection_get_password(gc
));
5638 g_strfreev(userserver
);
5639 g_strfreev(domain_user
);
5640 g_strfreev(signinname_login
);
5642 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
5644 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
5646 /* TODO: Set the status correctly. */
5647 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
5649 transport
= purple_account_get_string(account
, "transport", "auto");
5650 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
5651 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
5654 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
5655 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
5656 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
5657 } else if (strcmp(transport
, "auto") == 0) {
5658 sip
->auto_transport
= TRUE
;
5659 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
5660 } else if (strcmp(transport
, "tls") == 0) {
5661 resolve_next_service(sip
, service_tls
);
5662 } else if (strcmp(transport
, "tcp") == 0) {
5663 resolve_next_service(sip
, service_tcp
);
5665 resolve_next_service(sip
, service_udp
);
5669 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
5671 connection_free_all(sip
);
5676 if (sip
->query_data
!= NULL
)
5677 purple_dnsquery_destroy(sip
->query_data
);
5678 sip
->query_data
= NULL
;
5680 if (sip
->srv_query_data
!= NULL
)
5681 purple_srv_cancel(sip
->srv_query_data
);
5682 sip
->srv_query_data
= NULL
;
5684 if (sip
->listen_data
!= NULL
)
5685 purple_network_listen_cancel(sip
->listen_data
);
5686 sip
->listen_data
= NULL
;
5688 if (sip
->gsc
!= NULL
)
5689 purple_ssl_close(sip
->gsc
);
5692 sipe_auth_free(&sip
->registrar
);
5693 sipe_auth_free(&sip
->proxy
);
5696 purple_circ_buffer_destroy(sip
->txbuf
);
5699 g_free(sip
->realhostname
);
5700 sip
->realhostname
= NULL
;
5703 purple_input_remove(sip
->listenpa
);
5705 if (sip
->tx_handler
)
5706 purple_input_remove(sip
->tx_handler
);
5707 sip
->tx_handler
= 0;
5708 if (sip
->resendtimeout
)
5709 purple_timeout_remove(sip
->resendtimeout
);
5710 sip
->resendtimeout
= 0;
5711 if (sip
->timeouts
) {
5712 GSList
*entry
= sip
->timeouts
;
5714 struct scheduled_action
*sched_action
= entry
->data
;
5715 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
5716 purple_timeout_remove(sched_action
->timeout_handler
);
5717 (*sched_action
->destroy
)(sched_action
->payload
);
5718 g_free(sched_action
->name
);
5719 g_free(sched_action
);
5720 entry
= entry
->next
;
5723 g_slist_free(sip
->timeouts
);
5725 if (sip
->allow_events
) {
5726 GSList
*entry
= sip
->allow_events
;
5728 g_free(entry
->data
);
5729 entry
= entry
->next
;
5732 g_slist_free(sip
->allow_events
);
5735 g_free(sip
->contact
);
5736 sip
->contact
= NULL
;
5738 g_free(sip
->regcallid
);
5739 sip
->regcallid
= NULL
;
5742 sip
->processing_input
= FALSE
;
5746 * A callback for g_hash_table_foreach_remove
5748 static gboolean
sipe_buddy_remove(gpointer key
, gpointer buddy
, gpointer user_data
)
5750 sipe_free_buddy((struct sipe_buddy
*) buddy
);
5754 static void sipe_close(PurpleConnection
*gc
)
5756 struct sipe_account_data
*sip
= gc
->proto_data
;
5759 /* leave all conversations */
5760 im_session_close_all(sip
);
5763 do_register_exp(sip
, 0);
5765 sipe_connection_cleanup(sip
);
5766 g_free(sip
->sipdomain
);
5767 g_free(sip
->username
);
5768 g_free(sip
->password
);
5769 g_free(sip
->authdomain
);
5770 g_free(sip
->authuser
);
5771 g_free(sip
->status
);
5773 g_hash_table_foreach_steal(sip
->buddies
, sipe_buddy_remove
, NULL
);
5774 g_hash_table_destroy(sip
->buddies
);
5776 g_free(gc
->proto_data
);
5777 gc
->proto_data
= NULL
;
5780 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5782 PurpleAccount
*acct
= purple_connection_get_account(gc
);
5783 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
5784 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
5786 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
5787 purple_conversation_present(conv
);
5791 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5794 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
5795 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
5798 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
5800 PurpleNotifySearchResults
*results
;
5801 PurpleNotifySearchColumn
*column
;
5802 xmlnode
*searchResults
;
5804 int match_count
= 0;
5805 gboolean more
= FALSE
;
5808 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
5810 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5811 if (!searchResults
) {
5812 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5816 results
= purple_notify_searchresults_new();
5818 if (results
== NULL
) {
5819 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5820 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
5822 xmlnode_free(searchResults
);
5826 column
= purple_notify_searchresults_column_new(_("User Name"));
5827 purple_notify_searchresults_column_add(results
, column
);
5829 column
= purple_notify_searchresults_column_new(_("Name"));
5830 purple_notify_searchresults_column_add(results
, column
);
5832 column
= purple_notify_searchresults_column_new(_("Company"));
5833 purple_notify_searchresults_column_add(results
, column
);
5835 column
= purple_notify_searchresults_column_new(_("Country"));
5836 purple_notify_searchresults_column_add(results
, column
);
5838 column
= purple_notify_searchresults_column_new(_("Email"));
5839 purple_notify_searchresults_column_add(results
, column
);
5841 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
5844 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
5845 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
5846 g_strfreev(uri_parts
);
5848 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
5849 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
5850 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
5851 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
5853 purple_notify_searchresults_row_add(results
, row
);
5857 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
5858 char *data
= xmlnode_get_data_unescaped(mrow
);
5859 more
= (g_strcasecmp(data
, "true") == 0);
5863 secondary
= g_strdup_printf(
5864 dngettext(GETTEXT_PACKAGE
,
5865 "Found %d contact%s:",
5866 "Found %d contacts%s:", match_count
),
5867 match_count
, more
? _(" (more matched your query)") : "");
5869 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
5870 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
5871 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
5874 xmlnode_free(searchResults
);
5878 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
5880 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
5881 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
5885 PurpleRequestField
*field
= entries
->data
;
5886 const char *id
= purple_request_field_get_id(field
);
5887 const char *value
= purple_request_field_string_get_value(field
);
5889 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
5891 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
5892 } while ((entries
= g_list_next(entries
)) != NULL
);
5896 gchar
*query
= g_strjoinv(NULL
, attrs
);
5897 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
5898 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
5899 send_soap_request_with_cb(gc
->proto_data
, body
,
5900 (TransCallback
) process_search_contact_response
, NULL
);
5908 static void sipe_show_find_contact(PurplePluginAction
*action
)
5910 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
5911 PurpleRequestFields
*fields
;
5912 PurpleRequestFieldGroup
*group
;
5913 PurpleRequestField
*field
;
5915 fields
= purple_request_fields_new();
5916 group
= purple_request_field_group_new(NULL
);
5917 purple_request_fields_add_group(fields
, group
);
5919 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
5920 purple_request_field_group_add_field(group
, field
);
5921 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
5922 purple_request_field_group_add_field(group
, field
);
5923 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
5924 purple_request_field_group_add_field(group
, field
);
5925 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
5926 purple_request_field_group_add_field(group
, field
);
5928 purple_request_fields(gc
,
5930 _("Search for a Contact"),
5931 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5933 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
5935 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
5938 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
5941 PurplePluginAction
*act
;
5943 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
5944 menu
= g_list_prepend(menu
, act
);
5946 menu
= g_list_reverse(menu
);
5951 static void dummy_permit_deny(PurpleConnection
*gc
)
5955 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
5961 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
5967 static char *sipe_status_text(PurpleBuddy
*buddy
)
5969 struct sipe_account_data
*sip
;
5970 struct sipe_buddy
*sbuddy
;
5973 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5974 if (sip
) //happens on pidgin exit
5976 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5977 if (sbuddy
&& sbuddy
->annotation
)
5979 text
= g_strdup(sbuddy
->annotation
);
5986 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
5988 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
5989 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
5990 struct sipe_account_data
*sip
;
5991 struct sipe_buddy
*sbuddy
;
5992 char *annotation
= NULL
;
5994 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5995 if (sip
) //happens on pidgin exit
5997 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
6000 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
6005 if (purple_presence_is_online(presence
))
6007 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
6012 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
6019 sipe_get_account_text_table(PurpleAccount
*account
)
6022 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
6023 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
6027 static PurpleBuddy
*
6028 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
6031 const gchar
*server_alias
, *email
;
6032 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
6034 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
6036 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
6038 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
6040 purple_blist_server_alias_buddy(clone
, server_alias
);
6043 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
6045 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
6048 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
6050 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
6055 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
6057 PurpleBuddy
*buddy
, *b
;
6058 PurpleConnection
*gc
;
6059 PurpleGroup
* group
= purple_find_group(group_name
);
6061 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
6063 buddy
= (PurpleBuddy
*)node
;
6065 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
6066 gc
= purple_account_get_connection(buddy
->account
);
6068 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
6070 b
= purple_blist_add_buddy_clone(group
, buddy
);
6073 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
6077 sipe_buddy_menu_chat_new_cb(PurpleBuddy
*buddy
)
6079 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6080 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6081 gchar
*chat_name
= g_strdup_printf(_("Chat #%d"), ++sip
->chat_seq
);
6082 struct sip_im_session
*session
;
6084 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy
->name
);
6086 session
= create_chat_session(sip
);
6087 session
->roster_manager
= g_strdup(self
);
6089 session
->conv
= serv_got_joined_chat(buddy
->account
->gc
, session
->chat_id
, g_strdup(chat_name
));
6090 session
->chat_name
= g_strdup(chat_name
);
6091 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
), self
, NULL
, PURPLE_CBFLAGS_NONE
, FALSE
);
6092 sipe_invite(sip
, session
, buddy
->name
, NULL
, NULL
, FALSE
);
6099 sipe_is_election_finished(struct sipe_account_data
*sip
,
6100 struct sip_im_session
*session
)
6102 struct sip_dialog
*dialog
;
6104 gboolean res
= TRUE
;
6106 entry
= session
->dialogs
;
6108 dialog
= entry
->data
;
6109 if (dialog
->election_vote
== 0) {
6113 entry
= entry
->next
;
6117 session
->is_voting_in_progress
= FALSE
;
6123 sipe_election_start(struct sipe_account_data
*sip
,
6124 struct sip_im_session
*session
)
6126 struct sip_dialog
*dialog
;
6128 int election_timeout
;
6130 if (session
->is_voting_in_progress
) {
6131 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6134 session
->is_voting_in_progress
= TRUE
;
6136 session
->bid
= rand();
6138 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session
->bid
);
6140 /* reset election_vote for each chat participant */
6141 entry
= session
->dialogs
;
6143 dialog
= entry
->data
;
6144 dialog
->election_vote
= 0;
6145 entry
= entry
->next
;
6148 entry
= session
->dialogs
;
6150 dialog
= entry
->data
;
6151 /* send RequestRM to each chat participant*/
6152 sipe_send_election_request_rm(sip
, session
, dialog
->with
, session
->bid
);
6153 entry
= entry
->next
;
6156 election_timeout
= 15; /* sec */
6157 sipe_schedule_action("<+election-result>", election_timeout
, sipe_election_result
, NULL
, sip
, session
);
6161 * @param who a URI to whom to invite to chat
6164 sipe_invite_to_chat(struct sipe_account_data
*sip
,
6165 struct sip_im_session
*session
,
6168 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6170 if (session
->roster_manager
) {
6171 if (!strcmp(session
->roster_manager
, self
)) {
6172 sipe_invite(sip
, session
, who
, NULL
, NULL
, FALSE
);
6174 sipe_refer(sip
, session
, who
);
6177 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6179 session
->pending_invite_queue
= slist_insert_unique_sorted(
6180 session
->pending_invite_queue
, g_strdup(who
), (GCompareFunc
)strcmp
);
6182 sipe_election_start(sip
, session
);
6189 sipe_process_pending_invite_queue(struct sipe_account_data
*sip
,
6190 struct sip_im_session
*session
)
6193 GSList
*entry
= session
->pending_invite_queue
;
6196 invitee
= entry
->data
;
6197 sipe_invite_to_chat(sip
, session
, invitee
);
6198 entry
= session
->pending_invite_queue
= g_slist_remove(session
->pending_invite_queue
, invitee
);
6204 sipe_election_result(struct sipe_account_data
*sip
,
6207 struct sip_im_session
*session
= (struct sip_im_session
*)sess
;
6208 struct sip_dialog
*dialog
;
6211 gboolean has_won
= TRUE
;
6213 if (session
->roster_manager
) {
6214 purple_debug_info("sipe",
6215 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session
->roster_manager
);
6219 session
->is_voting_in_progress
= FALSE
;
6221 entry
= session
->dialogs
;
6223 dialog
= entry
->data
;
6224 if (dialog
->election_vote
< 0) {
6226 rival
= dialog
->with
;
6229 entry
= entry
->next
;
6233 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6235 session
->roster_manager
= g_strdup_printf("sip:%s", sip
->username
);
6237 entry
= session
->dialogs
;
6239 dialog
= entry
->data
;
6240 /* send SetRM to each chat participant*/
6241 sipe_send_election_set_rm(sip
, session
, dialog
->with
);
6242 entry
= entry
->next
;
6245 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival
);
6249 sipe_process_pending_invite_queue(sip
, session
);
6253 sipe_buddy_menu_chat_invite_cb(PurpleBuddy
*buddy
, const char *chat_name
)
6255 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6256 struct sip_im_session
*session
;
6258 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy
->name
);
6259 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name
);
6261 session
= find_chat_session_by_name(sip
, chat_name
);
6263 sipe_invite_to_chat(sip
, session
, buddy
->name
);
6267 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
6270 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
6272 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
6275 char *mailto
= g_strdup_printf("mailto:%s", email
);
6276 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
6280 char *const parmList
[] = {mailto
, NULL
};
6281 if ((pid
= fork()) == -1)
6283 purple_debug_info("sipe", "fork() error\n");
6287 execvp("xdg-email", parmList
);
6288 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6296 //@TODO resolve env variable %WINDIR% first
6297 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
6300 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
6309 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
6314 * A menu which appear when right-clicking on buddy in contact list.
6317 sipe_buddy_menu(PurpleBuddy
*buddy
)
6319 PurpleBlistNode
*g_node
;
6320 PurpleGroup
*group
, *gr_parent
;
6321 PurpleMenuAction
*act
;
6323 GList
*menu_groups
= NULL
;
6324 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6325 struct sip_im_session
*session
;
6327 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6329 act
= purple_menu_action_new(_("New Chat"),
6330 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb
),
6332 menu
= g_list_prepend(menu
, act
);
6334 entry
= sip
->im_sessions
;
6336 session
= entry
->data
;
6337 if (strcmp(self
, buddy
->name
) && session
->chat_name
&& !get_dialog(session
, buddy
->name
)) {
6338 gchar
*label
= g_strdup_printf(_("Invite to '%s'"), session
->chat_name
);
6339 act
= purple_menu_action_new(label
,
6340 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb
),
6341 g_strdup(session
->chat_name
), NULL
);
6343 menu
= g_list_prepend(menu
, act
);
6345 entry
= entry
->next
;
6348 act
= purple_menu_action_new(_("Send Email..."),
6349 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
6351 menu
= g_list_prepend(menu
, act
);
6353 gr_parent
= purple_buddy_get_group(buddy
);
6354 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
6355 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
6358 group
= (PurpleGroup
*)g_node
;
6359 if (group
== gr_parent
)
6362 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
6365 act
= purple_menu_action_new(purple_group_get_name(group
),
6366 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
6368 menu_groups
= g_list_prepend(menu_groups
, act
);
6370 menu_groups
= g_list_reverse(menu_groups
);
6372 act
= purple_menu_action_new(_("Copy to"),
6375 menu
= g_list_prepend(menu
, act
);
6376 menu
= g_list_reverse(menu
);
6383 sipe_blist_node_menu(PurpleBlistNode
*node
)
6385 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
6386 return sipe_buddy_menu((PurpleBuddy
*) node
);
6393 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
6395 gboolean ret
= TRUE
;
6396 char *username
= (char *)trans
->payload
;
6398 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
6399 PurpleBuddy
*pbuddy
;
6400 struct sipe_buddy
*sbuddy
;
6402 char *server_alias
= NULL
;
6404 const char *device_name
= NULL
;
6406 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
6408 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
6409 alias
= purple_buddy_get_local_alias(pbuddy
);
6413 //will query buddy UA's capabilities and send answer to log
6414 sipe_options_request(sip
, username
);
6416 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
6419 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
6423 if (msg
->response
!= 200) {
6424 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
6426 xmlnode
*searchResults
;
6429 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
6430 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
6431 if (!searchResults
) {
6432 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6433 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
6434 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
6435 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
6436 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
6437 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
6438 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
6439 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
6440 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
6441 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
6442 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
6443 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
6444 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
6445 if (!email
|| strcmp("", email
)) {
6446 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
6447 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
6451 xmlnode_free(searchResults
);
6454 purple_notify_user_info_add_section_break(info
);
6456 if (!server_alias
|| !strcmp("", server_alias
)) {
6457 g_free(server_alias
);
6458 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
6460 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
6464 // same as server alias, do not present
6465 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
6468 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
6471 if (!email
|| !strcmp("", email
)) {
6473 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
6475 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
6481 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
6484 /* show a buddy's user info in a nice dialog box */
6485 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
6486 username
, /* buddy's username */
6488 NULL
, /* callback called when dialog closed */
6489 NULL
); /* userdata for callback */
6495 * AD search first, LDAP based
6497 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
6499 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
6500 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
6502 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
6503 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
6504 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
6509 static PurplePlugin
*my_protocol
= NULL
;
6511 static PurplePluginProtocolInfo prpl_info
=
6514 NULL
, /* user_splits */
6515 NULL
, /* protocol_options */
6516 NO_BUDDY_ICONS
, /* icon_spec */
6517 sipe_list_icon
, /* list_icon */
6518 NULL
, /* list_emblems */
6519 sipe_status_text
, /* status_text */
6520 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
6521 sipe_status_types
, /* away_states */
6522 sipe_blist_node_menu
, /* blist_node_menu */
6523 NULL
, /* chat_info */
6524 NULL
, /* chat_info_defaults */
6525 sipe_login
, /* login */
6526 sipe_close
, /* close */
6527 sipe_im_send
, /* send_im */
6528 NULL
, /* set_info */ // TODO maybe
6529 sipe_send_typing
, /* send_typing */
6530 sipe_get_info
, /* get_info */
6531 sipe_set_status
, /* set_status */
6532 NULL
, /* set_idle */
6533 NULL
, /* change_passwd */
6534 sipe_add_buddy
, /* add_buddy */
6535 NULL
, /* add_buddies */
6536 sipe_remove_buddy
, /* remove_buddy */
6537 NULL
, /* remove_buddies */
6538 sipe_add_permit
, /* add_permit */
6539 sipe_add_deny
, /* add_deny */
6540 sipe_add_deny
, /* rem_permit */
6541 sipe_add_permit
, /* rem_deny */
6542 dummy_permit_deny
, /* set_permit_deny */
6543 NULL
, /* join_chat */
6544 NULL
, /* reject_chat */
6545 NULL
, /* get_chat_name */
6546 NULL
, /* chat_invite */
6547 sipe_chat_leave
, /* chat_leave */
6548 NULL
, /* chat_whisper */
6549 sipe_chat_send
, /* chat_send */
6550 sipe_keep_alive
, /* keepalive */
6551 NULL
, /* register_user */
6552 NULL
, /* get_cb_info */ // deprecated
6553 NULL
, /* get_cb_away */ // deprecated
6554 sipe_alias_buddy
, /* alias_buddy */
6555 sipe_group_buddy
, /* group_buddy */
6556 sipe_rename_group
, /* rename_group */
6557 NULL
, /* buddy_free */
6558 sipe_convo_closed
, /* convo_closed */
6559 purple_normalize_nocase
, /* normalize */
6560 NULL
, /* set_buddy_icon */
6561 sipe_remove_group
, /* remove_group */
6562 NULL
, /* get_cb_real_name */ // TODO?
6563 NULL
, /* set_chat_topic */
6564 NULL
, /* find_blist_chat */
6565 NULL
, /* roomlist_get_list */
6566 NULL
, /* roomlist_cancel */
6567 NULL
, /* roomlist_expand_category */
6568 NULL
, /* can_receive_file */
6569 NULL
, /* send_file */
6570 NULL
, /* new_xfer */
6571 NULL
, /* offline_message */
6572 NULL
, /* whiteboard_prpl_ops */
6573 sipe_send_raw
, /* send_raw */
6574 NULL
, /* roomlist_room_serialize */
6575 NULL
, /* unregister_user */
6576 NULL
, /* send_attention */
6577 NULL
, /* get_attention_types */
6579 sizeof(PurplePluginProtocolInfo
), /* struct_size */
6580 sipe_get_account_text_table
, /* get_account_text_table */
6584 static PurplePluginInfo info
= {
6585 PURPLE_PLUGIN_MAGIC
,
6586 PURPLE_MAJOR_VERSION
,
6587 PURPLE_MINOR_VERSION
,
6588 PURPLE_PLUGIN_PROTOCOL
, /**< type */
6589 NULL
, /**< ui_requirement */
6591 NULL
, /**< dependencies */
6592 PURPLE_PRIORITY_DEFAULT
, /**< priority */
6593 "prpl-sipe", /**< id */
6594 "Microsoft LCS/OCS", /**< name */
6595 VERSION
, /**< version */
6596 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6597 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6598 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6599 "Gabriel Burt <gburt@novell.com>", /**< author */
6600 PURPLE_WEBSITE
, /**< homepage */
6601 sipe_plugin_load
, /**< load */
6602 sipe_plugin_unload
, /**< unload */
6603 sipe_plugin_destroy
, /**< destroy */
6604 NULL
, /**< ui_info */
6605 &prpl_info
, /**< extra_info */
6614 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
6618 entry
= prpl_info
.protocol_options
;
6620 purple_account_option_destroy(entry
->data
);
6621 entry
= g_list_delete_link(entry
, entry
);
6623 prpl_info
.protocol_options
= NULL
;
6625 entry
= prpl_info
.user_splits
;
6627 purple_account_user_split_destroy(entry
->data
);
6628 entry
= g_list_delete_link(entry
, entry
);
6630 prpl_info
.user_splits
= NULL
;
6633 static void init_plugin(PurplePlugin
*plugin
)
6635 PurpleAccountUserSplit
*split
;
6636 PurpleAccountOption
*option
;
6641 purple_debug_info(PACKAGE
, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
6642 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s\n",
6643 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
6644 textdomain(GETTEXT_PACKAGE
);
6647 purple_plugin_register(plugin
);
6649 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
6650 purple_account_user_split_set_reverse(split
, FALSE
);
6651 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
6653 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
6654 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6655 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6656 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6658 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
6659 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6660 // Translators: noun (networking port)
6661 option
= purple_account_option_int_new(_("Port"), "port", 5061);
6662 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6664 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
6665 purple_account_option_add_list_item(option
, _("Auto"), "auto");
6666 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
6667 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
6668 purple_account_option_add_list_item(option
, _("UDP"), "udp");
6669 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6671 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6672 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6674 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
6675 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6678 option
= purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE
);
6679 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6681 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6682 * No login/password is taken into account if this option present,
6683 * instead used default credentials stored in OS.
6685 option
= purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE
);
6686 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6688 my_protocol
= plugin
;
6691 /* I had to redefined the function for it load, but works */
6692 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
6693 plugin
->info
= &(info
);
6694 init_plugin((plugin
));
6695 sipe_plugin_load((plugin
));
6696 return purple_plugin_register(plugin
);