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); /* g_hash_table_foreach_remove does not like it */
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
)
2547 GSList
*entry
= dialog
->routes
;
2548 g_free(dialog
->with
);
2550 g_free(entry
->data
);
2551 entry
= g_slist_remove(entry
, entry
->data
);
2553 entry
= dialog
->supported
;
2555 g_free(entry
->data
);
2556 entry
= g_slist_remove(entry
, entry
->data
);
2559 g_free(dialog
->ourtag
);
2560 g_free(dialog
->theirtag
);
2561 g_free(dialog
->theirepid
);
2562 g_free(dialog
->request
);
2567 static void im_session_destroy(struct sipe_account_data
*sip
, struct sip_im_session
* session
)
2571 sip
->im_sessions
= g_slist_remove(sip
->im_sessions
, session
);
2573 entry
= session
->dialogs
;
2575 free_dialog(entry
->data
);
2576 entry
= g_slist_remove(entry
, entry
->data
);
2579 entry
= session
->outgoing_message_queue
;
2581 g_free(entry
->data
);
2582 entry
= g_slist_remove(entry
, entry
->data
);
2585 entry
= session
->pending_invite_queue
;
2587 g_free(entry
->data
);
2588 entry
= g_slist_remove(entry
, entry
->data
);
2591 g_hash_table_destroy(session
->unconfirmed_messages
);
2593 g_free(session
->with
);
2594 g_free(session
->chat_name
);
2595 g_free(session
->callid
);
2596 g_free(session
->roster_manager
);
2601 process_options_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2603 gboolean ret
= TRUE
;
2605 if (msg
->response
!= 200) {
2606 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
2610 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
2616 * Asks UA/proxy about its capabilities.
2618 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
2620 gchar
*to
= strstr(who
, "sip:") ? g_strdup(who
) : g_strdup_printf("sip:%s", who
);
2621 gchar
*contact
= get_contact(sip
);
2623 request
= g_strdup_printf(
2624 "Accept: application/sdp\r\n"
2625 "Contact: %s\r\n", contact
);
2629 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
2635 static void sipe_present_message_undelivered_err(gchar
*with
, struct sipe_account_data
*sip
, gchar
*message
)
2637 char *msg
, *msg_tmp
;
2638 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
2639 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
2641 msg_tmp
= g_strdup_printf( _("The following message could not be delivered to all recipients, "\
2642 "possibly because one or more persons are offline:\n%s") ,
2644 purple_conv_present_error(with
, sip
->account
, msg_tmp
);
2649 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
);
2652 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2654 gboolean ret
= TRUE
;
2655 gchar
* with
= parse_from(sipmsg_find_header(msg
, "To"));
2656 struct sip_im_session
* session
= find_im_session(sip
, with
);
2657 struct sip_dialog
*dialog
;
2663 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
2668 dialog
= get_dialog(session
, with
);
2671 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
2676 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2677 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
), with
);
2679 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2681 if (msg
->response
!= 200) {
2682 purple_debug_info("sipe", "process_message_response: MESSAGE response not 200\n");
2684 sipe_present_message_undelivered_err(with
, sip
, message
);
2685 im_session_destroy(sip
, session
);
2688 g_hash_table_remove(session
->unconfirmed_messages
, key
);
2689 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
2690 key
, g_hash_table_size(session
->unconfirmed_messages
));
2696 if (ret
) sipe_im_process_queue(sip
, session
);
2701 sipe_is_election_finished(struct sipe_account_data
*sip
,
2702 struct sip_im_session
*session
);
2705 sipe_election_result(struct sipe_account_data
*sip
,
2709 process_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2711 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2712 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
2713 struct sip_dialog
*dialog
;
2714 struct sip_im_session
*session
;
2716 session
= find_chat_session(sip
, callid
);
2718 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid
);
2722 if (msg
->response
== 200 && !strncmp(contenttype
, "application/x-ms-mim", 20)) {
2723 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2724 xmlnode
*xn_request_rm_response
= xmlnode_get_child(xn_action
, "RequestRMResponse");
2725 xmlnode
*xn_set_rm_response
= xmlnode_get_child(xn_action
, "SetRMResponse");
2727 if (xn_request_rm_response
) {
2728 const char *with
= xmlnode_get_attrib(xn_request_rm_response
, "uri");
2729 const char *allow
= xmlnode_get_attrib(xn_request_rm_response
, "allow");
2731 dialog
= get_dialog(session
, with
);
2733 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with
);
2737 if (allow
&& !g_strcasecmp(allow
, "true")) {
2738 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with
);
2739 dialog
->election_vote
= 1;
2740 } else if (allow
&& !g_strcasecmp(allow
, "false")) {
2741 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with
);
2742 dialog
->election_vote
= -1;
2745 if (sipe_is_election_finished(sip
, session
)) {
2746 sipe_election_result(sip
, session
);
2749 } else if (xn_set_rm_response
) {
2752 xmlnode_free(xn_action
);
2759 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_dialog
*dialog
, const char *msg
)
2768 sipe_parse_html(msg
, &msgformat
, &msgtext
);
2769 purple_debug_info("sipe", "sipe_send_message: msgformat=%s", msgformat
);
2771 msgr_value
= sipmsg_get_msgr_string(msgformat
);
2774 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
2777 msgr
= g_strdup("");
2780 tmp
= get_contact(sip
);
2781 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
2782 //hdr = g_strdup("Content-Type: text/rtf\r\n");
2783 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
2784 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp
, msgr
);
2788 send_sip_request(sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
, hdr
, msgtext
, dialog
, process_message_response
);
2795 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_im_session
* session
)
2797 GSList
*entry2
= session
->outgoing_message_queue
;
2799 char *queued_msg
= entry2
->data
;
2800 struct sip_dialog
*dialog
;
2801 GSList
*entry
= session
->dialogs
;
2803 if (session
->is_multiparty
) {
2804 serv_got_chat_in(sip
->gc
, session
->chat_id
, g_strdup_printf("sip:%s", sip
->username
),
2805 PURPLE_MESSAGE_SEND
, queued_msg
, time(NULL
));
2811 dialog
= entry
->data
;
2812 if (dialog
->outgoing_invite
) continue; //do not send messages as INVITE is not responded.
2814 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog
->callid
, (dialog
->cseq
) + 1, dialog
->with
);
2815 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
2816 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
2817 key
, g_hash_table_size(session
->unconfirmed_messages
));
2819 sipe_send_message(sip
, dialog
, queued_msg
);
2821 entry
= entry
->next
;
2824 entry2
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
2830 sipe_get_route_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2832 GSList
*hdr
= msg
->headers
;
2836 struct siphdrelement
*elem
= hdr
->data
;
2837 if(!g_ascii_strcasecmp(elem
->name
, "Record-Route")) {
2838 gchar
**parts
= g_strsplit(elem
->value
, ",", 0);
2839 gchar
**part
= parts
;
2842 gchar
*route
= sipmsg_find_part_of_header(*part
, "<", ">", NULL
);
2843 purple_debug_info("sipe", "sipe_get_route_header: route %s \n", route
);
2844 dialog
->routes
= g_slist_append(dialog
->routes
, route
);
2850 hdr
= g_slist_next(hdr
);
2855 dialog
->routes
= g_slist_reverse(dialog
->routes
);
2860 dialog
->request
= dialog
->routes
->data
;
2861 dialog
->routes
= g_slist_remove(dialog
->routes
, dialog
->routes
->data
);
2864 contact
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Contact"), "<", ">", NULL
);
2865 dialog
->routes
= g_slist_append(dialog
->routes
, contact
);
2869 sipe_get_supported_header(struct sipmsg
*msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2871 GSList
*hdr
= msg
->headers
;
2872 struct siphdrelement
*elem
;
2876 if(!g_ascii_strcasecmp(elem
->name
, "Supported")
2877 && !g_slist_find_custom(dialog
->supported
, elem
->value
, (GCompareFunc
)g_ascii_strcasecmp
))
2879 dialog
->supported
= g_slist_append(dialog
->supported
, g_strdup(elem
->value
));
2882 hdr
= g_slist_next(hdr
);
2887 sipe_parse_dialog(struct sipmsg
* msg
, struct sip_dialog
* dialog
, gboolean outgoing
)
2889 gchar
*us
= outgoing
? "From" : "To";
2890 gchar
*them
= outgoing
? "To" : "From";
2892 g_free(dialog
->ourtag
);
2893 g_free(dialog
->theirtag
);
2895 dialog
->ourtag
= find_tag(sipmsg_find_header(msg
, us
));
2896 dialog
->theirtag
= find_tag(sipmsg_find_header(msg
, them
));
2897 if (!dialog
->theirepid
) {
2898 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", ";", NULL
);
2899 if (!dialog
->theirepid
) {
2900 dialog
->theirepid
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, them
), "epid=", NULL
, NULL
);
2904 // Catch a tag on the end of the To Header and get rid of it.
2905 if (dialog
->theirepid
&& strstr(dialog
->theirepid
, "tag=")) {
2906 dialog
->theirepid
= strtok(dialog
->theirepid
, ";");
2909 sipe_get_route_header(msg
, dialog
, outgoing
);
2910 sipe_get_supported_header(msg
, dialog
, outgoing
);
2914 sipe_refer_notify(struct sipe_account_data
*sip
,
2915 struct sip_im_session
*session
,
2922 struct sip_dialog
*dialog
= get_dialog(session
, who
);
2924 hdr
= g_strdup_printf(
2926 "Subscription-State: %s\r\n"
2927 "Content-Type: message/sipfrag\r\n",
2928 status
>= 200 ? "terminated" : "active");
2930 body
= g_strdup_printf(
2931 "SIP/2.0 %d %s\r\n",
2934 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "NOTIFY",
2935 who
, who
, hdr
, body
, dialog
, NULL
);
2942 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
2944 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
2945 struct sip_im_session
*session
;
2946 struct sip_dialog
*dialog
;
2950 struct sipmsg
*request_msg
= trans
->msg
;
2952 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
2953 gchar
*referred_by
= parse_from(sipmsg_find_header(request_msg
, "Referred-By"));
2955 session
= find_chat_session(sip
, callid
);
2957 session
= find_im_session(sip
, with
);
2961 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
2966 dialog
= get_dialog(session
, with
);
2968 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
2973 sipe_parse_dialog(msg
, dialog
, TRUE
);
2975 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
2976 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
2978 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
2980 if (msg
->response
!= 200) {
2981 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
2983 sipe_present_message_undelivered_err(with
, sip
, message
);
2984 im_session_destroy(sip
, session
);
2990 send_sip_request(sip
->gc
, "ACK", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
2991 dialog
->outgoing_invite
= NULL
;
2992 dialog
->is_established
= TRUE
;
2995 sipe_refer_notify(sip
, session
, referred_by
, 200, "OK");
2998 /* add user to chat if it is a multiparty session */
2999 if (session
->is_multiparty
) {
3000 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3002 PURPLE_CBFLAGS_NONE
, TRUE
);
3005 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
3006 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
3007 if (session
->outgoing_message_queue
) {
3008 char *queued_msg
= session
->outgoing_message_queue
->data
;
3009 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
3014 sipe_im_process_queue(sip
, session
);
3016 g_hash_table_remove(session
->unconfirmed_messages
, key
);
3017 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
3018 key
, g_hash_table_size(session
->unconfirmed_messages
));
3022 g_free(referred_by
);
3028 sipe_invite(struct sipe_account_data
*sip
,
3029 struct sip_im_session
*session
,
3031 const gchar
*msg_body
,
3032 const gchar
*referred_by
,
3033 const gboolean is_triggered
)
3039 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
3040 char *ms_text_format
= g_strdup("");
3041 gchar
*roster_manager
;
3043 gchar
*referred_by_str
;
3045 gchar
*require_multiparty
;
3046 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3048 if (dialog
&& dialog
->is_established
) {
3049 purple_debug_info("sipe", "session with %s already has a dialog open\n", who
);
3054 dialog
= g_new0(struct sip_dialog
, 1);
3055 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3057 dialog
->callid
= session
->callid
? session
->callid
: gencallid();
3058 dialog
->with
= g_strdup(who
);
3061 if (!(dialog
->ourtag
)) {
3062 dialog
->ourtag
= gentag();
3066 if (strstr(who
, "sip:")) {
3069 to
= g_strdup_printf("sip:%s", who
);
3080 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
3081 purple_debug_info("sipe", "sipe_invite: msgformat=%s", msgformat
);
3083 msgr_value
= sipmsg_get_msgr_string(msgformat
);
3087 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
3091 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
3092 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
3097 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, (dialog
->cseq
) + 1);
3098 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
3099 purple_debug_info("sipe", "sipe_im_send: added message %s to unconfirmed_messages(count=%d)\n",
3100 key
, g_hash_table_size(session
->unconfirmed_messages
));
3104 contact
= get_contact(sip
);
3105 end_points
= get_end_points(sip
, session
);
3106 roster_manager
= g_strdup_printf(
3107 "Roster-Manager: %s\r\n"
3108 "EndPoints: %s\r\n",
3111 referred_by_str
= referred_by
?
3113 "Referred-By: %s\r\n",
3117 "TriggeredInvite: TRUE\r\n";
3118 require_multiparty
=
3119 "Require: com.microsoft.rtc-multiparty\r\n";
3120 hdr
= g_strdup_printf(
3126 "Content-Type: application/sdp\r\n",
3127 (session
->roster_manager
&& !strcmp(session
->roster_manager
, self
)) ? roster_manager
: "",
3129 is_triggered
? triggered
: "",
3130 is_triggered
|| session
->is_multiparty
? require_multiparty
: "",
3133 g_free(ms_text_format
);
3135 body
= g_strdup_printf(
3137 "o=- 0 0 IN IP4 %s\r\n"
3141 "m=message %d sip null\r\n"
3142 "a=accept-types:text/plain text/html image/gif "
3143 "multipart/alternative application/im-iscomposing+xml\r\n",
3144 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1), sip
->realport
);
3146 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
3147 to
, to
, hdr
, body
, dialog
, process_invite_response
);
3150 g_free(roster_manager
);
3152 g_free(referred_by_str
);
3159 sipe_refer(struct sipe_account_data
*sip
,
3160 struct sip_im_session
*session
,
3165 struct sip_dialog
*dialog
= get_dialog(session
, session
->roster_manager
);
3167 contact
= get_contact(sip
);
3168 hdr
= g_strdup_printf(
3170 "Refer-to: <%s>\r\n"
3171 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
3172 "Require: com.microsoft.rtc-multiparty\r\n",
3176 dialog
->ourtag
? ";tag=" : "",
3177 dialog
->ourtag
? dialog
->ourtag
: "",
3180 send_sip_request(sip
->gc
, "REFER",
3181 session
->roster_manager
, session
->roster_manager
, hdr
, NULL
, dialog
, NULL
);
3188 sipe_send_election_request_rm(struct sipe_account_data
*sip
,
3189 struct sip_im_session
*session
,
3195 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3197 hdr
= "Content-Type: application/x-ms-mim\r\n";
3199 body
= g_strdup_printf(
3200 "<?xml version=\"1.0\"?>\r\n"
3201 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3202 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
3203 sip
->username
, bid
);
3205 send_sip_request(sip
->gc
, "INFO",
3206 who
, who
, hdr
, body
, dialog
, process_info_response
);
3212 sipe_send_election_set_rm(struct sipe_account_data
*sip
,
3213 struct sip_im_session
*session
,
3218 struct sip_dialog
*dialog
= get_dialog(session
, who
);
3220 hdr
= "Content-Type: application/x-ms-mim\r\n";
3222 body
= g_strdup_printf(
3223 "<?xml version=\"1.0\"?>\r\n"
3224 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3225 "<SetRM uri=\"sip:%s\"/></action>\r\n",
3228 send_sip_request(sip
->gc
, "INFO",
3229 who
, who
, hdr
, body
, dialog
, process_info_response
);
3235 im_session_close (struct sipe_account_data
*sip
, struct sip_im_session
* session
)
3238 struct sip_dialog
*dialog
;
3240 entry
= session
->dialogs
;
3242 dialog
= entry
->data
;
3243 /* @TODO slow down BYE message sending rate */
3244 /* @see single subscription code */
3245 send_sip_request(sip
->gc
, "BYE", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
3246 entry
= entry
->next
;
3249 im_session_destroy(sip
, session
);
3254 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
3256 struct sipe_account_data
*sip
= gc
->proto_data
;
3258 purple_debug_info("sipe", "conversation with %s closed\n", who
);
3259 im_session_close(sip
, find_im_session(sip
, who
));
3263 sipe_chat_leave (PurpleConnection
*gc
, int id
)
3265 struct sipe_account_data
*sip
= gc
->proto_data
;
3266 struct sip_im_session
* session
= find_chat_session_by_id(sip
, id
);
3267 im_session_close(sip
, session
);
3271 im_session_close_all (struct sipe_account_data
*sip
)
3273 GSList
*entry
= sip
->im_sessions
;
3275 im_session_close (sip
, entry
->data
);
3276 entry
= sip
->im_sessions
;
3280 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
, PurpleMessageFlags flags
)
3282 struct sipe_account_data
*sip
= gc
->proto_data
;
3283 struct sip_im_session
*session
;
3284 struct sip_dialog
*dialog
;
3286 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
3288 session
= find_or_create_im_session(sip
, who
);
3289 dialog
= get_dialog(session
, who
);
3291 // Queue the message
3292 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
3294 if (dialog
&& dialog
->callid
) {
3295 sipe_im_process_queue(sip
, session
);
3296 } else if (!dialog
|| !dialog
->outgoing_invite
) {
3297 // Need to send the INVITE to get the outgoing dialog setup
3298 sipe_invite(sip
, session
, who
, what
, NULL
, FALSE
);
3304 static int sipe_chat_send(PurpleConnection
*gc
, int id
, const char *what
, PurpleMessageFlags flags
)
3306 struct sipe_account_data
*sip
= gc
->proto_data
;
3307 struct sip_im_session
*session
;
3309 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what
);
3311 session
= find_chat_session_by_id(sip
, id
);
3313 // Queue the message
3314 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
3316 sipe_im_process_queue(sip
, session
);
3321 /* End IM Session (INVITE and MESSAGE methods) */
3323 static void process_incoming_info(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3325 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
3326 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3327 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3329 struct sip_im_session
*session
= find_chat_session(sip
, callid
);
3331 session
= find_im_session(sip
, from
);
3338 if (!strncmp(contenttype
, "application/x-ms-mim", 20)) {
3339 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3340 xmlnode
*xn_request_rm
= xmlnode_get_child(xn_action
, "RequestRM");
3341 xmlnode
*xn_set_rm
= xmlnode_get_child(xn_action
, "SetRM");
3343 sipmsg_remove_header(msg
, "User-Agent");
3345 if (xn_request_rm
) {
3346 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
3347 int bid
= atoi(xmlnode_get_attrib(xn_request_rm
, "bid"));
3348 gchar
*body
= g_strdup_printf(
3349 "<?xml version=\"1.0\"?>\r\n"
3350 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3351 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
3353 session
->bid
< bid
? "true" : "false");
3354 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3356 } else if (xn_set_rm
) {
3358 const char *rm
= xmlnode_get_attrib(xn_set_rm
, "uri");
3359 g_free(session
->roster_manager
);
3360 session
->roster_manager
= g_strdup(rm
);
3362 body
= g_strdup_printf(
3363 "<?xml version=\"1.0\"?>\r\n"
3364 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
3365 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
3367 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3370 xmlnode_free(xn_action
);
3373 /* looks like purple lacks typing notification for chat */
3374 if (!session
->is_multiparty
) {
3375 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
3378 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3383 static void process_incoming_bye(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3385 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3386 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3387 struct sip_im_session
*session
;
3388 struct sip_dialog
*dialog
;
3390 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3392 session
= find_chat_session(sip
, callid
);
3394 session
= find_im_session(sip
, from
);
3401 if (session
->roster_manager
&& !g_strcasecmp(from
, session
->roster_manager
)) {
3402 g_free(session
->roster_manager
);
3403 session
->roster_manager
= NULL
;
3406 if (!session
->is_multiparty
) {
3407 // TODO Let the user know the other user left the conversation?
3408 im_session_destroy(sip
, session
);
3410 dialog
= get_dialog(session
, from
);
3411 session
->dialogs
= g_slist_remove(session
->dialogs
, dialog
);
3412 free_dialog(dialog
);
3414 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session
->conv
), from
, NULL
);
3416 if (!session
->dialogs
) {
3417 im_session_destroy(sip
, session
);
3424 static void process_incoming_refer(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3426 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
3427 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3428 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3429 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
3430 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
3431 struct sip_im_session
*session
;
3432 struct sip_dialog
*dialog
;
3434 session
= find_chat_session(sip
, callid
);
3435 dialog
= get_dialog(session
, from
);
3437 sipmsg_remove_header(msg
, "User-Agent");
3438 sipmsg_remove_header(msg
, "Refer-to");
3439 sipmsg_remove_header(msg
, "Referred-By");
3440 sipmsg_remove_header(msg
, "Require");
3442 if (!session
|| !dialog
|| !session
->roster_manager
|| strcmp(session
->roster_manager
, self
)) {
3443 send_sip_response(sip
->gc
, msg
, 500, "Server Internal Error", NULL
);
3445 send_sip_response(sip
->gc
, msg
, 202, "Accepted", NULL
);
3447 sipe_invite(sip
, session
, refer_to
, NULL
, referred_by
, FALSE
);
3453 g_free(referred_by
);
3457 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
3459 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
3460 struct sip_im_session
*session
;
3461 struct sip_dialog
*dialog
;
3463 if (state
== PURPLE_NOT_TYPING
)
3466 session
= find_im_session(sip
, who
);
3467 dialog
= get_dialog(session
, who
);
3469 if (session
&& dialog
) {
3470 send_sip_request(gc
, "INFO", who
, who
,
3471 "Content-Type: application/xml\r\n",
3472 SIPE_SEND_TYPING
, dialog
, NULL
);
3474 return SIPE_TYPING_SEND_TIMEOUT
;
3477 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
3479 GSList
*tmp
= sip
->transactions
;
3480 time_t currtime
= time(NULL
);
3482 struct transaction
*trans
= tmp
->data
;
3484 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime
-trans
->time
);
3485 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
3488 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
3490 sendout_sipmsg(sip
, trans
->msg
);
3497 static void do_reauthenticate_cb(struct sipe_account_data
*sip
, void *unused
)
3499 /* register again when security token expires */
3500 /* we have to start a new authentication as the security token
3501 * is almost expired by sending a not signed REGISTER message */
3502 purple_debug_info("sipe", "do a full reauthentication\n");
3503 sipe_auth_free(&sip
->registrar
);
3504 sipe_auth_free(&sip
->proxy
);
3505 sip
->registerstatus
= 0;
3507 sip
->reauthenticate_set
= FALSE
;
3510 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3514 gboolean found
= FALSE
;
3516 from
= parse_from(sipmsg_find_header(msg
, "From"));
3520 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
3522 contenttype
= sipmsg_find_header(msg
, "Content-Type");
3523 if (!strncmp(contenttype
, "text/plain", 10) || !strncmp(contenttype
, "text/html", 9)) {
3525 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3526 gchar
*html
= get_html_message(contenttype
, msg
->body
);
3528 struct sip_im_session
*session
= find_chat_session(sip
, callid
);
3530 session
= find_im_session(sip
, from
);
3533 if (session
->is_multiparty
) {
3534 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
3535 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
3537 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3540 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3543 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
3544 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3549 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
3553 state
= xmlnode_get_child(isc
, "state");
3556 purple_debug_info("sipe", "process_incoming_message: no state found\n");
3561 statedata
= xmlnode_get_data(state
);
3563 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
3564 else serv_got_typing_stopped(sip
->gc
, from
);
3569 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
3573 purple_debug_info("sipe", "got unknown mime-type");
3574 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
3579 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3581 gchar
*ms_text_format
;
3583 gchar
*newTag
= gentag();
3586 gboolean is_multiparty
;
3587 gboolean is_triggered
= FALSE
;
3588 gboolean was_multiparty
;
3589 gboolean just_joined
= FALSE
;
3590 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
3591 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
3592 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3593 gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
3594 gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
3595 gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
3596 gchar
**end_points
= NULL
;
3597 struct sip_im_session
*session
;
3598 struct sip_dialog
*dialog
;
3600 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? msg
->body
: "");
3602 /* Only accept text invitations */
3603 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
3604 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
3608 // TODO There *must* be a better way to clean up the To header to add a tag...
3609 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
3610 oldHeader
= sipmsg_find_header(msg
, "To");
3611 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
3612 sipmsg_remove_header(msg
, "To");
3613 sipmsg_add_header(msg
, "To", newHeader
);
3616 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3617 is_multiparty
= FALSE
;
3618 if (end_points_hdr
) {
3619 end_points
= g_strsplit(end_points_hdr
, ",", 0);
3620 if (end_points
[0] && end_points
[1] && end_points
[2]) {
3621 is_multiparty
= TRUE
;
3624 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
3625 is_triggered
= TRUE
;
3626 is_multiparty
= TRUE
;
3629 session
= find_chat_session(sip
, callid
);
3630 /* Convert to multiparty */
3631 if (session
&& is_multiparty
&& !session
->is_multiparty
) {
3632 g_free(session
->with
);
3633 session
->with
= NULL
;
3634 was_multiparty
= session
->is_multiparty
;
3635 session
->is_multiparty
= TRUE
;
3636 session
->chat_id
= rand();
3639 if (!session
&& is_multiparty
) {
3640 session
= find_or_create_chat_session(sip
, callid
);
3644 session
= find_or_create_im_session(sip
, from
);
3647 if (!session
->callid
) {
3648 session
->callid
= g_strdup(callid
);
3651 session
->is_multiparty
= is_multiparty
;
3652 if (roster_manager
) {
3653 session
->roster_manager
= g_strdup(roster_manager
);
3656 if (is_multiparty
&& end_points
) {
3658 while (end_points
[i
]) {
3659 gchar
*end_point
= parse_from(end_points
[i
]);
3660 gchar
*epid
= sipmsg_find_part_of_header(end_points
[i
], "epid=", ";", NULL
);
3662 if (!g_strcasecmp(from
, end_point
) || !g_strcasecmp(to
, end_point
)) {
3667 dialog
= get_dialog(session
, end_point
);
3669 dialog
= g_new0(struct sip_dialog
, 1);
3670 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3672 dialog
->callid
= session
->callid
;
3673 dialog
->with
= g_strdup(end_point
);
3674 dialog
->theirepid
= epid
;
3678 /* send triggered INVITE */
3679 sipe_invite(sip
, session
, end_point
, NULL
, NULL
, TRUE
);
3681 dialog
->theirepid
= epid
;
3689 dialog
= get_dialog(session
, from
);
3691 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
3693 dialog
= g_new0(struct sip_dialog
, 1);
3694 session
->dialogs
= g_slist_append(session
->dialogs
, dialog
);
3696 dialog
->callid
= session
->callid
;
3697 dialog
->with
= g_strdup(from
);
3698 sipe_parse_dialog(msg
, dialog
, FALSE
);
3700 if (!dialog
->ourtag
) {
3701 dialog
->ourtag
= newTag
;
3708 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
3712 if (is_multiparty
&& !session
->conv
) {
3713 gchar
*chat_name
= g_strdup_printf(_("Chat #%d"), ++sip
->chat_seq
);
3714 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
3715 /* create prpl chat */
3716 session
->conv
= serv_got_joined_chat(sip
->gc
, session
->chat_id
, chat_name
);
3717 session
->chat_name
= g_strdup(chat_name
);
3719 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3721 PURPLE_CBFLAGS_NONE
, FALSE
);
3726 if (is_multiparty
&& !was_multiparty
) {
3727 /* add current IM counterparty to chat */
3728 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3729 ((struct sip_dialog
*)session
->dialogs
->data
)->with
, NULL
,
3730 PURPLE_CBFLAGS_NONE
, FALSE
);
3734 /* add inviting party */
3736 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
3738 PURPLE_CBFLAGS_NONE
, TRUE
);
3741 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
3742 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
3743 if (ms_text_format
) {
3744 if (!strncmp(ms_text_format
, "text/plain", 10) || !strncmp(ms_text_format
, "text/html", 9)) {
3746 gchar
*html
= get_html_message(ms_text_format
, NULL
);
3748 if (is_multiparty
) {
3749 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
3750 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
3752 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
3755 sipmsg_add_header(msg
, "Supported", "ms-text-format"); // accepts message received
3761 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3762 sipmsg_remove_header(msg
, "Ms-Text-Format");
3763 sipmsg_remove_header(msg
, "EndPoints");
3764 sipmsg_remove_header(msg
, "User-Agent");
3765 sipmsg_remove_header(msg
, "Roster-Manager");
3766 sipmsg_remove_header(msg
, "P-Asserted-Identity");
3767 sipmsg_remove_header(msg
, "Require");
3769 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3770 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
3772 body
= g_strdup_printf(
3774 "o=- 0 0 IN IP4 %s\r\n"
3778 "m=message %d sip sip:%s\r\n"
3779 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3780 purple_network_get_my_ip(-1), purple_network_get_my_ip(-1),
3781 sip
->realport
, sip
->username
);
3782 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3786 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3790 sipmsg_remove_header(msg
, "Ms-Conversation-ID");
3791 sipmsg_remove_header(msg
, "EndPoints");
3792 sipmsg_remove_header(msg
, "User-Agent");
3794 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, BENOTIFY");
3795 sipmsg_add_header(msg
, "User-Agent", purple_account_get_string(sip
->account
, "useragent", "Purple/" VERSION
));
3797 body
= g_strdup_printf(
3799 "o=- 0 0 IN IP4 0.0.0.0\r\n"
3801 "c=IN IP4 0.0.0.0\r\n"
3803 "m=message %d sip sip:%s\r\n"
3804 "a=accept-types:text/plain text/html image/gif multipart/alternative application/im-iscomposing+xml\r\n",
3805 sip
->realport
, sip
->username
);
3806 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
3810 static void sipe_connection_cleanup(struct sipe_account_data
*);
3811 static void create_connection(struct sipe_account_data
*, gchar
*, int);
3813 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
3816 const gchar
*expires_header
;
3818 GSList
*hdr
= msg
->headers
;
3820 struct siphdrelement
*elem
;
3822 expires_header
= sipmsg_find_header(msg
, "Expires");
3823 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
3824 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
3826 switch (msg
->response
) {
3829 sip
->registerstatus
= 0;
3831 gchar
*contact_hdr
= NULL
;
3837 if (!sip
->reregister_set
) {
3838 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
3839 sipe_schedule_action(action_name
, expires
, do_register_cb
, NULL
, sip
, NULL
);
3840 g_free(action_name
);
3841 sip
->reregister_set
= TRUE
;
3844 sip
->registerstatus
= 3;
3847 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
3849 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
3852 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
3855 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
3856 fill_auth(sip
, tmp
, &sip
->registrar
);
3858 if (!sip
->reauthenticate_set
) {
3859 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
3860 guint reauth_timeout
;
3861 if (sip
->registrar
.type
== AUTH_TYPE_KERBEROS
&& sip
->registrar
.expires
> 0) {
3862 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
3863 reauth_timeout
= sip
->registrar
.expires
- 300;
3865 /* NTLM: we have to reauthenticate as our security token expires
3866 after eight hours (be five minutes early) */
3867 reauth_timeout
= (8 * 3600) - 300;
3869 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, NULL
, sip
, NULL
);
3870 g_free(action_name
);
3871 sip
->reauthenticate_set
= TRUE
;
3874 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
3876 epid
= get_epid(sip
);
3877 uuid
= generateUUIDfromEPID(epid
);
3880 // There can be multiple Contact headers (one per location where the user is logged in) so
3881 // make sure to only get the one for this uuid
3882 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
3883 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
3884 if (valid_contact
) {
3885 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
3886 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
3887 g_free(valid_contact
);
3890 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
3895 g_free(sip
->contact
);
3897 sip
->contact
= g_strdup_printf("<%s>", gruu
);
3900 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
3901 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
);
3903 sip
->msrtc_event_categories
= FALSE
;
3904 sip
->batched_support
= FALSE
;
3909 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
3910 if (!g_ascii_strcasecmp(elem
->value
, "msrtc-event-categories")) {
3911 sip
->msrtc_event_categories
= TRUE
;
3912 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->msrtc_event_categories
);
3914 if (!g_ascii_strcasecmp(elem
->value
, "adhoclist")) {
3915 sip
->batched_support
= TRUE
;
3916 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s: %d\n", elem
->value
,sip
->batched_support
);
3919 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
3920 gchar
**caps
= g_strsplit(elem
->value
,",",0);
3923 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
3924 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
3929 hdr
= g_slist_next(hdr
);
3932 if (!sip
->subscribed
) { //do it just once, not every re-register
3933 if(!sip
->msrtc_event_categories
){ //Only for LCS2005, on OCS2007 always backs the error 504 Server time-out
3934 //sipe_options_request(sip, sip->sipdomain);
3936 entry
= sip
->allow_events
;
3939 if (tmp
&& !g_ascii_strcasecmp(tmp
, "vnd-microsoft-roaming-contacts")) {
3940 sipe_subscribe_roaming_contacts(sip
, msg
);
3942 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-ACL")) {
3943 sipe_subscribe_roaming_acl(sip
, msg
);
3945 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-roaming-self")) {
3946 sipe_subscribe_roaming_self(sip
, msg
);
3948 if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning-v2")) {
3949 sipe_subscribe_roaming_provisioning_v2(sip
, msg
);
3950 } else if (tmp
&& !g_ascii_strcasecmp(tmp
,"vnd-microsoft-provisioning")) { // LSC2005
3951 sipe_subscribe_roaming_provisioning(sip
, msg
);
3953 if (tmp
&& !g_ascii_strcasecmp(tmp
,"presence.wpending")) {
3954 sipe_subscribe_presence_wpending(sip
, msg
);
3956 entry
= entry
->next
;
3958 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
3959 sip
->subscribed
= TRUE
;
3962 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
3963 "timeout=", ";", NULL
);
3964 if (timeout
!= NULL
) {
3965 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
3966 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
3967 sip
->keepalive_timeout
);
3971 // Should we remove the transaction here?
3972 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\r\n", sip
->cseq
);
3973 transactions_remove(sip
, tc
);
3978 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
3980 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
3981 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
3985 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
3988 tmp
= g_strsplit(parts
[0], ":", 0);
3989 hostname
= g_strdup(tmp
[0]);
3990 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
3994 tmp
= g_strsplit(parts
[i
], "=", 0);
3996 if (g_strcasecmp("transport", tmp
[0]) == 0) {
3997 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
3998 transport
= SIPE_TRANSPORT_TCP
;
3999 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
4000 transport
= SIPE_TRANSPORT_UDP
;
4009 /* Close old connection */
4010 sipe_connection_cleanup(sip
);
4012 /* Create new connection */
4013 sip
->transport
= transport
;
4014 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
4015 hostname
, port
, TRANSPORT_DESCRIPTOR
);
4016 create_connection(sip
, hostname
, port
);
4022 if (sip
->registerstatus
!= 2) {
4023 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
4024 if (sip
->registrar
.retries
> 3) {
4025 sip
->gc
->wants_to_die
= TRUE
;
4026 purple_connection_error(sip
->gc
, _("Wrong Password"));
4030 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
4032 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
4035 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
4038 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\r\n", tmp
);
4039 fill_auth(sip
, tmp
, &sip
->registrar
);
4040 sip
->registerstatus
= 2;
4041 if (sip
->account
->disconnecting
) {
4042 do_register_exp(sip
, 0);
4050 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
4051 if (warning
!= NULL
) {
4053 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
4055 gchar
**tmp
= g_strsplit(warning
, "\"", 0);
4056 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), tmp
[1] ? tmp
[1] : _("no reason given"));
4059 warning
= g_strdup(_("You have been rejected by the server"));
4062 sip
->gc
->wants_to_die
= TRUE
;
4063 purple_connection_error(sip
->gc
, warning
);
4070 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
4071 if (warning
!= NULL
) {
4072 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
4073 warning
= g_strdup_printf(_("Not Found: %s. Please, contact with your Administrator"), reason
? reason
: _("no reason given"));
4076 warning
= g_strdup(_("Not Found: Destination URI either not enabled for SIP or does not exist. Please, contact with your Administrator"));
4079 sip
->gc
->wants_to_die
= TRUE
;
4080 purple_connection_error(sip
->gc
, warning
);
4087 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
4088 if (warning
!= NULL
) {
4089 gchar
*reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
4090 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
4093 warning
= g_strdup(_("Service unavailable: no reason given"));
4096 sip
->gc
->wants_to_die
= TRUE
;
4097 purple_connection_error(sip
->gc
, warning
);
4106 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4109 xmlnode
*xn_categories
;
4110 xmlnode
*xn_category
;
4112 const char *activity
= NULL
;
4114 xn_categories
= xmlnode_from_str(data
, len
);
4115 uri
= xmlnode_get_attrib(xn_categories
, "uri");
4117 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
4119 xn_category
= xmlnode_get_next_twin(xn_category
) )
4121 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
4123 if (!strcmp(attrVar
, "note"))
4126 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4131 xn_node
= xmlnode_get_child(xn_category
, "note");
4132 if (!xn_node
) continue;
4133 xn_node
= xmlnode_get_child(xn_node
, "body");
4134 if (!xn_node
) continue;
4135 note
= xmlnode_get_data(xn_node
);
4136 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",uri
,note
? note
: "");
4137 g_free(sbuddy
->annotation
);
4138 sbuddy
->annotation
= NULL
;
4139 if (note
) sbuddy
->annotation
= g_strdup(note
);
4145 else if(!strcmp(attrVar
, "state"))
4149 xn_node
= xmlnode_get_child(xn_category
, "state");
4150 if (!xn_node
) continue;
4151 xn_node
= xmlnode_get_child(xn_node
, "availability");
4152 if (!xn_node
) continue;
4154 data
= xmlnode_get_data(xn_node
);
4159 activity
= SIPE_STATUS_ID_UNKNOWN
;
4160 else if (avail
< 4500)
4161 activity
= SIPE_STATUS_ID_AVAILABLE
;
4162 else if (avail
< 6000)
4163 activity
= SIPE_STATUS_ID_BRB
;
4164 else if (avail
< 7500)
4165 activity
= SIPE_STATUS_ID_ONPHONE
;
4166 else if (avail
< 9000)
4167 activity
= SIPE_STATUS_ID_BUSY
;
4168 else if (avail
< 12000)
4169 activity
= SIPE_STATUS_ID_DND
;
4170 else if (avail
< 18000)
4171 activity
= SIPE_STATUS_ID_AWAY
;
4173 activity
= SIPE_STATUS_ID_OFFLINE
;
4177 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", activity
);
4178 purple_prpl_got_user_status(sip
->account
, uri
, activity
, NULL
);
4181 xmlnode_free(xn_categories
);
4184 static void sipe_subscribe_poolfqdn_resource_uri(const char *host
, GSList
*server
, struct sipe_account_data
*sip
)
4186 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
4187 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host
);
4188 payload
->host
= g_strdup(host
);
4189 payload
->buddies
= server
;
4190 sipe_subscribe_presence_batched_routed(sip
, payload
);
4191 sipe_subscribe_presence_batched_routed_free(payload
);
4194 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4197 xmlnode
*xn_resource
;
4198 GHashTable
*servers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
4203 xn_list
= xmlnode_from_str(data
, len
);
4205 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
4207 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
4209 const char *uri
, *state
;
4210 xmlnode
*xn_instance
;
4212 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
4213 if (!xn_instance
) continue;
4215 uri
= xmlnode_get_attrib(xn_resource
, "uri");
4216 state
= xmlnode_get_attrib(xn_instance
, "state");
4217 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri
, state
);
4219 if (strstr(state
, "resubscribe")) {
4220 const char *poolFqdn
= xmlnode_get_attrib(xn_instance
, "poolFqdn");
4221 struct sipe_buddy
*sbuddy
;
4222 if (poolFqdn
) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
4223 gchar
*user
= g_strdup(uri
);
4224 host
= g_strdup(poolFqdn
);
4225 server
= g_hash_table_lookup(servers
, host
);
4226 server
= g_slist_append(server
, user
);
4227 g_hash_table_insert(servers
, host
, server
);
4229 sipe_subscribe_presence_single(sip
, (void *) uri
);
4231 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4233 sbuddy
->resubscribed
= TRUE
;
4238 /* Send out any deferred poolFqdn subscriptions */
4239 g_hash_table_foreach(servers
, (GHFunc
) sipe_subscribe_poolfqdn_resource_uri
, sip
);
4240 g_hash_table_destroy(servers
);
4242 xmlnode_free(xn_list
);
4245 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4249 gchar
*activity
= NULL
;
4251 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
4252 gboolean isonline
= FALSE
;
4253 xmlnode
*display_name_node
;
4255 pidf
= xmlnode_from_str(data
, len
);
4257 purple_debug_info("sipe", "process_incoming_notify: no parseable pidf:%s\n",data
);
4261 uri
= xmlnode_get_attrib(pidf
, "entity");
4263 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
4265 if ((status
= xmlnode_get_child(tuple
, "status"))) {
4266 basicstatus
= xmlnode_get_child(status
, "basic");
4271 purple_debug_info("sipe", "process_incoming_notify: no basic found\n");
4276 getbasic
= xmlnode_get_data(basicstatus
);
4278 purple_debug_info("sipe", "process_incoming_notify: no basic data found\n");
4283 purple_debug_info("sipe", "process_incoming_notify: basic-status(%s)\n", getbasic
);
4284 if (strstr(getbasic
, "open")) {
4289 display_name_node
= xmlnode_get_child(pidf
, "display-name");
4290 // updating display name if alias was just URI
4291 if (display_name_node
) {
4292 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
4293 GSList
*entry
= buddies
;
4294 PurpleBuddy
*p_buddy
;
4295 char * display_name
= xmlnode_get_data(display_name_node
);
4298 const char *server_alias
;
4301 p_buddy
= entry
->data
;
4303 alias
= (char *)purple_buddy_get_alias(p_buddy
);
4304 alias
= alias
? g_strdup_printf("sip:%s", alias
) : NULL
;
4305 if (!alias
|| !g_ascii_strcasecmp(uri
, alias
)) {
4306 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
4307 purple_blist_alias_buddy(p_buddy
, display_name
);
4311 server_alias
= purple_buddy_get_server_alias(p_buddy
);
4313 ( (server_alias
&& strcmp(display_name
, server_alias
))
4314 || !server_alias
|| strlen(server_alias
) == 0 )
4316 purple_blist_server_alias_buddy(p_buddy
, display_name
);
4319 entry
= entry
->next
;
4321 g_free(display_name
);
4324 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
4325 if ((status
= xmlnode_get_child(tuple
, "status"))) {
4326 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
4327 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
4328 activity
= xmlnode_get_data(basicstatus
);
4329 purple_debug_info("sipe", "process_incoming_notify: activity(%s)\n", activity
);
4336 const gchar
* status_id
= NULL
;
4338 if (strstr(activity
, "busy")) {
4339 status_id
= SIPE_STATUS_ID_BUSY
;
4340 } else if (strstr(activity
, "away")) {
4341 status_id
= SIPE_STATUS_ID_AWAY
;
4346 status_id
= SIPE_STATUS_ID_AVAILABLE
;
4349 purple_debug_info("sipe", "process_incoming_notify: status_id(%s)\n", status_id
);
4350 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
4352 purple_prpl_got_user_status(sip
->account
, uri
, SIPE_STATUS_ID_OFFLINE
, NULL
);
4359 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
4361 const char *availability
;
4362 const char *activity
;
4363 const char *display_name
= NULL
;
4364 const char *activity_name
= NULL
;
4369 struct sipe_buddy
*sbuddy
;
4371 xmlnode
*xn_presentity
= xmlnode_from_str(data
, len
);
4373 xmlnode
*xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
4374 xmlnode
*xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
4375 xmlnode
*xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
4376 xmlnode
*xn_email
= xmlnode_get_child(xn_presentity
, "email");
4377 const char *email
= xn_email
? xmlnode_get_attrib(xn_email
, "email") : NULL
;
4378 xmlnode
*xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
4379 xmlnode
*xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
):NULL
;
4380 const char *avail
= xn_state
? xmlnode_get_attrib(xn_state
, "avail") : NULL
;
4382 xmlnode
*xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
4383 char *note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
4384 xmlnode
*xn_devices
= xmlnode_get_child(xn_presentity
, "devices");
4385 xmlnode
*xn_device_presence
= xn_devices
? xmlnode_get_child(xn_devices
, "devicePresence") : NULL
;
4386 xmlnode
*xn_device_name
= xn_device_presence
? xmlnode_get_child(xn_device_presence
, "deviceName") : NULL
;
4387 const char *device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
4389 name
= xmlnode_get_attrib(xn_presentity
, "uri");
4390 uri
= g_strdup_printf("sip:%s", name
);
4391 availability
= xmlnode_get_attrib(xn_availability
, "aggregate");
4392 activity
= xmlnode_get_attrib(xn_activity
, "aggregate");
4394 // updating display name if alias was just URI
4395 if (xn_display_name
) {
4396 GSList
*buddies
= purple_find_buddies(sip
->account
, uri
); //all buddies in different groups
4397 GSList
*entry
= buddies
;
4398 PurpleBuddy
*p_buddy
;
4399 display_name
= xmlnode_get_attrib(xn_display_name
, "displayName");
4402 const char *email_str
, *server_alias
;
4404 p_buddy
= entry
->data
;
4406 if (!g_ascii_strcasecmp(name
, purple_buddy_get_alias(p_buddy
))) {
4407 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, display_name
);
4408 purple_blist_alias_buddy(p_buddy
, display_name
);
4411 server_alias
= purple_buddy_get_server_alias(p_buddy
);
4413 ( (server_alias
&& strcmp(display_name
, server_alias
))
4414 || !server_alias
|| strlen(server_alias
) == 0 )
4416 purple_blist_server_alias_buddy(p_buddy
, display_name
);
4420 email_str
= purple_blist_node_get_string((PurpleBlistNode
*)p_buddy
, "email");
4421 if (!email_str
|| g_ascii_strcasecmp(email_str
, email
)) {
4422 purple_blist_node_set_string((PurpleBlistNode
*)p_buddy
, "email", email
);
4426 entry
= entry
->next
;
4430 avl
= atoi(availability
);
4431 act
= atoi(activity
);
4433 if(sip
->msrtc_event_categories
){
4434 if (act
== 100 && avl
== 0)
4435 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4436 else if (act
== 100 && avl
== 300)
4437 activity_name
= SIPE_STATUS_ID_AWAY
;
4438 else if (act
== 300 && avl
== 300)
4439 activity_name
= SIPE_STATUS_ID_BRB
;
4440 else if (act
== 400 && avl
== 300)
4441 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4442 else if (act
== 500 && act
== 300)
4443 activity_name
= SIPE_STATUS_ID_ONPHONE
;
4444 else if (act
== 600 && avl
== 300)
4445 activity_name
= SIPE_STATUS_ID_BUSY
;
4446 else if (act
== 0 && avl
== 0){ //MSRTC elements are zero
4447 if(avail
){ //Check for LegacyInterop elements
4450 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4451 else if (avl
== 3500)
4452 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4453 else if (avl
== 15500)
4454 activity_name
= SIPE_STATUS_ID_AWAY
;
4455 else if (avl
== 6500)
4456 activity_name
= SIPE_STATUS_ID_BUSY
;
4457 else if (avl
== 12500)
4458 activity_name
= SIPE_STATUS_ID_BRB
;
4463 if(activity_name
== NULL
){
4465 activity_name
= SIPE_STATUS_ID_AWAY
;
4466 else if (act
<= 150)
4467 activity_name
= SIPE_STATUS_ID_LUNCH
;
4468 else if (act
<= 300)
4469 activity_name
= SIPE_STATUS_ID_BRB
;
4470 else if (act
<= 400)
4471 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4472 else if (act
<= 500)
4473 activity_name
= SIPE_STATUS_ID_ONPHONE
;
4474 else if (act
<= 600)
4475 activity_name
= SIPE_STATUS_ID_BUSY
;
4477 activity_name
= SIPE_STATUS_ID_AVAILABLE
;
4480 activity_name
= SIPE_STATUS_ID_OFFLINE
;
4483 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
4486 if (sbuddy
->annotation
) { g_free(sbuddy
->annotation
); }
4487 sbuddy
->annotation
= NULL
;
4488 if (note
) { sbuddy
->annotation
= g_strdup(note
); }
4490 if (sbuddy
->device_name
) { g_free(sbuddy
->device_name
); }
4491 sbuddy
->device_name
= NULL
;
4492 if (device_name
) { sbuddy
->device_name
= g_strdup(device_name
); }
4495 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", activity_name
);
4496 purple_prpl_got_user_status(sip
->account
, uri
, activity_name
, NULL
);
4498 xmlnode_free(xn_presentity
);
4502 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4504 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
4506 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
4508 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
4509 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
4511 const char *content
= msg
->body
;
4512 unsigned length
= msg
->bodylen
;
4513 PurpleMimeDocument
*mime
= NULL
;
4515 if (strstr(ctype
, "multipart"))
4517 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
4518 const char *content_type
;
4520 mime
= purple_mime_document_parse(doc
);
4521 parts
= purple_mime_document_get_parts(mime
);
4523 content
= purple_mime_part_get_data(parts
->data
);
4524 length
= purple_mime_part_get_length(parts
->data
);
4525 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
4526 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
4528 process_incoming_notify_rlmi_resub(sip
, content
, length
);
4530 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
4532 process_incoming_notify_msrtc(sip
, content
, length
);
4536 process_incoming_notify_rlmi(sip
, content
, length
);
4538 parts
= parts
->next
;
4544 purple_mime_document_free(mime
);
4547 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
4549 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
4551 else if(strstr(ctype
, "application/rlmi+xml"))
4553 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
4556 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
4558 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
4562 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
4566 static void sipe_process_presence_timeout(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gchar
*who
, int timeout
)
4568 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
4569 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
4571 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype
? ctype
: "");
4574 strstr(ctype
, "multipart") &&
4575 (strstr(ctype
, "application/rlmi+xml") ||
4576 strstr(ctype
, "application/msrtc-event-categories+xml"))) {
4577 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
4578 PurpleMimeDocument
*mime
= purple_mime_document_parse(doc
);
4579 GList
*parts
= purple_mime_document_get_parts(mime
);
4580 GSList
*buddies
= NULL
;
4581 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
4584 xmlnode
*xml
= xmlnode_from_str(purple_mime_part_get_data(parts
->data
),
4585 purple_mime_part_get_length(parts
->data
));
4586 gchar
*uri
= g_strdup(xmlnode_get_attrib(xml
, "uri"));
4588 if (strstr(uri
, "sip:") == NULL
) {
4590 uri
= g_strdup_printf("sip:%s", tmp
);
4593 buddies
= g_slist_append(buddies
, uri
);
4596 parts
= parts
->next
;
4599 if (mime
) purple_mime_document_free(mime
);
4601 payload
->host
= who
;
4602 payload
->buddies
= buddies
;
4603 sipe_schedule_action(action_name
, timeout
,
4604 sipe_subscribe_presence_batched_routed
,
4605 sipe_subscribe_presence_batched_routed_free
,
4607 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who
, timeout
);
4610 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, who
);
4611 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
, timeout
);
4613 g_free(action_name
);
4617 * Dispatcher for all incoming subscription information
4618 * whether it comes from NOTIFY, BENOTIFY requests or
4619 * piggy-backed to subscription's OK responce.
4621 * @param request whether initiated from BE/NOTIFY request or OK-response message.
4622 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
4624 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
4626 gchar
*event
= sipmsg_find_header(msg
, "Event");
4627 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
4630 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n", event
? event
: "", msg
->body
);
4631 purple_debug_info("sipe", "process_incoming_notify: subscription_state:%s\n\n", subscription_state
);
4635 const gchar
*expires_header
;
4636 expires_header
= sipmsg_find_header(msg
, "Expires");
4637 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
4638 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n\n", timeout
);
4639 timeout
= (timeout
- 60) > 60 ? (timeout
- 60) : timeout
; // 1 min ahead of expiration
4642 if (!subscription_state
|| strstr(subscription_state
, "active"))
4644 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
4646 sipe_process_presence(sip
, msg
);
4648 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
4650 sipe_process_roaming_contacts(sip
, msg
, NULL
);
4652 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self") )
4654 sipe_process_roaming_self(sip
, msg
);
4656 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
4658 sipe_process_roaming_acl(sip
, msg
);
4660 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
4662 sipe_process_presence_wpending(sip
, msg
);
4666 purple_debug_info("sipe", "Unable to process (BE)NOTIFY. Event is not supported:%s\n", event
? event
: "");
4670 //The server sends a (BE)NOTIFY with the status 'terminated'
4671 if (request
&& subscription_state
&& strstr(subscription_state
, "terminated") ) {
4672 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4673 purple_debug_info("sipe", "process_incoming_notify: (BE)NOTIFY says that subscription to buddy %s was terminated. \n", from
);
4677 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
4678 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
4679 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
4681 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
4682 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
4683 g_free(action_name);
4685 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
4686 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
4688 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
4689 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
4690 g_free(action_name);
4693 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
4694 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
4696 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
4697 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, NULL
, sip
, NULL
);
4698 g_free(action_name
);
4700 else if (!g_ascii_strcasecmp(event
, "presence") &&
4701 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
4703 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
4704 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
4705 if(sip
->batched_support
) {
4706 gchar
*my_self
= g_strdup_printf("sip:%s",sip
->username
);
4707 if(!g_ascii_strcasecmp(who
, my_self
)){
4708 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_batched
, NULL
, sip
, NULL
);
4709 purple_debug_info("sipe", "Resubscription full batched list in %d\n",timeout
);
4710 g_free(who
); /* unused */
4713 sipe_process_presence_timeout(sip
, msg
, who
, timeout
);
4718 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, NULL
, sip
, who
);
4719 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
,timeout
);
4721 g_free(action_name
);
4722 /* "who" will be freed by the action we just scheduled */
4726 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
4728 sipe_process_registration_notify(sip
, msg
);
4731 //The client responses 'Ok' when receive a NOTIFY message (lcs2005)
4732 if (request
&& !benotify
)
4734 sipmsg_remove_header(msg
, "Expires");
4735 sipmsg_remove_header(msg
, "subscription-state");
4736 sipmsg_remove_header(msg
, "Event");
4737 sipmsg_remove_header(msg
, "Require");
4738 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4745 static gchar* gen_xpidf(struct sipe_account_data *sip)
4747 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4749 "<presentity uri=\"sip:%s;method=SUBSCRIBE\"/>\r\n"
4750 "<display name=\"sip:%s\"/>\r\n"
4751 "<atom id=\"1234\">\r\n"
4752 "<address uri=\"sip:%s\">\r\n"
4753 "<status status=\"%s\"/>\r\n"
4766 static gchar* gen_pidf(struct sipe_account_data *sip)
4768 gchar *doc = g_strdup_printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n"
4769 "<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"
4770 "<tuple id=\"0\">\r\n"
4772 "<basic>open</basic>\r\n"
4773 "<ep:activities>\r\n"
4774 " <ep:activity>%s</ep:activity>\r\n"
4778 "<ci:display-name>%s</ci:display-name>\r\n"
4787 static void send_presence_soap(struct sipe_account_data
*sip
, const char * note
)
4789 int availability
= 300; // online
4790 int activity
= 400; // Available
4793 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
)) {
4795 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4797 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4799 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4801 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4803 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4805 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_INVISIBLE
) ||
4806 !strcmp(sip
->status
, SIPE_STATUS_ID_OFFLINE
)) {
4807 availability
= 0; // offline
4810 activity
= 400; // available
4813 name
= g_strdup_printf("sip: sip:%s", sip
->username
);
4814 //@TODO: send user data - state; add hostname in upper case
4815 body
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE
, name
, availability
, activity
, note
? note
: "");
4816 send_soap_request_with_cb(sip
, body
, NULL
, NULL
);
4822 process_clear_presence_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4824 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4825 if (msg
->response
== 200) {
4826 sip
->status_version
= 0;
4827 send_presence_status(sip
);
4833 process_send_presence_category_publish_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*tc
)
4835 if (msg
->response
== 409) {
4836 // Version(s) of presence info were out of date; tell the server to clear them, then we'll try again
4837 // TODO need to parse the version #'s?
4838 gchar
*uri
= g_strdup_printf("sip:%s", sip
->username
);
4839 gchar
*doc
= g_strdup_printf(SIPE_SEND_CLEAR_PRESENCE
, uri
);
4843 purple_debug_info("sipe", "process_send_presence_category_publish_response = %s\n", msg
->body
);
4845 tmp
= get_contact(sip
);
4846 hdr
= g_strdup_printf("Contact: %s\r\n"
4847 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4849 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_clear_presence_response
);
4859 static void send_presence_category_publish(struct sipe_account_data
*sip
, const char * note
)
4866 if (!strcmp(sip
->status
, SIPE_STATUS_ID_AWAY
) ||
4867 !strcmp(sip
->status
, SIPE_STATUS_ID_LUNCH
)) {
4869 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_DND
)) {
4871 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BUSY
)) {
4873 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_ONPHONE
)) {
4875 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_BRB
)) {
4877 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_AVAILABLE
)) {
4879 } else if (!strcmp(sip
->status
, SIPE_STATUS_ID_UNKNOWN
)) {
4882 // Offline or invisible
4886 uri
= g_strdup_printf("sip:%s", sip
->username
);
4887 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
, uri
,
4888 sip
->status_version
, code
,
4889 sip
->status_version
, code
,
4890 sip
->status_version
, note
? note
: "",
4891 sip
->status_version
, note
? note
: "",
4892 sip
->status_version
, note
? note
: ""
4894 sip
->status_version
++;
4896 tmp
= get_contact(sip
);
4897 hdr
= g_strdup_printf("Contact: %s\r\n"
4898 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
4900 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
4908 static void send_presence_status(struct sipe_account_data
*sip
)
4910 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
4912 if (!status
) return;
4914 note
= purple_status_get_attr_string(status
, "message");
4916 if(sip
->msrtc_event_categories
){
4917 send_presence_category_publish(sip
, note
);
4919 send_presence_soap(sip
, note
);
4923 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
4925 gboolean found
= FALSE
;
4926 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
4927 if (msg
->response
== 0) { /* request */
4928 if (!strcmp(msg
->method
, "MESSAGE")) {
4929 process_incoming_message(sip
, msg
);
4931 } else if (!strcmp(msg
->method
, "NOTIFY")) {
4932 purple_debug_info("sipe","send->process_incoming_notify\n");
4933 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
4935 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
4936 purple_debug_info("sipe","send->process_incoming_benotify\n");
4937 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
4939 } else if (!strcmp(msg
->method
, "INVITE")) {
4940 process_incoming_invite(sip
, msg
);
4942 } else if (!strcmp(msg
->method
, "REFER")) {
4943 process_incoming_refer(sip
, msg
);
4945 } else if (!strcmp(msg
->method
, "OPTIONS")) {
4946 process_incoming_options(sip
, msg
);
4948 } else if (!strcmp(msg
->method
, "INFO")) {
4949 process_incoming_info(sip
, msg
);
4951 } else if (!strcmp(msg
->method
, "ACK")) {
4952 // ACK's don't need any response
4954 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
4955 // LCS 2005 sends us these - just respond 200 OK
4957 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4958 } else if (!strcmp(msg
->method
, "BYE")) {
4959 process_incoming_bye(sip
, msg
);
4962 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4964 } else { /* response */
4965 struct transaction
*trans
= transactions_find(sip
, msg
);
4967 if (msg
->response
== 407) {
4968 gchar
*resend
, *auth
, *ptmp
;
4970 if (sip
->proxy
.retries
> 30) return;
4971 sip
->proxy
.retries
++;
4972 /* do proxy authentication */
4974 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
4976 fill_auth(sip
, ptmp
, &sip
->proxy
);
4977 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
4978 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
4979 sipmsg_add_header_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
4981 resend
= sipmsg_to_string(trans
->msg
);
4982 /* resend request */
4983 sendout_pkt(sip
->gc
, resend
);
4986 if (msg
->response
== 100 || msg
->response
== 180) {
4987 /* ignore provisional response */
4988 purple_debug_info("sipe", "got trying (%d) response\n", msg
->response
);
4990 sip
->proxy
.retries
= 0;
4991 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
4992 if (msg
->response
== 401)
4994 sip
->registrar
.retries
++;
4998 sip
->registrar
.retries
= 0;
5000 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\r\n", sip
->cseq
);
5002 if (msg
->response
== 401) {
5003 gchar
*resend
, *auth
, *ptmp
;
5005 if (sip
->registrar
.retries
> 4) return;
5006 sip
->registrar
.retries
++;
5009 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5011 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
5014 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
5018 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\r\n", ptmp
);
5020 fill_auth(sip
, ptmp
, &sip
->registrar
);
5021 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
5022 sipmsg_remove_header(trans
->msg
, "Proxy-Authorization");
5023 sipmsg_add_header(trans
->msg
, "Proxy-Authorization", auth
);
5025 //sipmsg_remove_header(trans->msg, "Authorization");
5026 //sipmsg_add_header(trans->msg, "Authorization", auth);
5028 resend
= sipmsg_to_string(trans
->msg
);
5029 /* resend request */
5030 sendout_pkt(sip
->gc
, resend
);
5035 if (trans
->callback
) {
5036 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\r\n");
5037 /* call the callback to process response*/
5038 (trans
->callback
)(sip
, msg
, trans
);
5040 /* Not sure if this is needed or what needs to be done
5041 but transactions seem to be removed prematurely so
5042 this only removes them if the response is 200 OK */
5043 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\r\n", sip
->cseq
);
5044 /*Has a bug and it's unneccesary*/
5045 /*transactions_remove(sip, trans);*/
5051 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
5055 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
5059 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
5067 /* according to the RFC remove CRLF at the beginning */
5068 while (*cur
== '\r' || *cur
== '\n') {
5071 if (cur
!= conn
->inbuf
) {
5072 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
5073 conn
->inbufused
= strlen(conn
->inbuf
);
5076 /* Received a full Header? */
5077 sip
->processing_input
= TRUE
;
5078 while (sip
->processing_input
&&
5079 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
5080 time_t currtime
= time(NULL
);
5083 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), conn
->inbuf
);
5084 msg
= sipmsg_parse_header(conn
->inbuf
);
5087 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
5088 if (restlen
>= msg
->bodylen
) {
5089 dummy
= g_malloc(msg
->bodylen
+ 1);
5090 memcpy(dummy
, cur
, msg
->bodylen
);
5091 dummy
[msg
->bodylen
] = '\0';
5093 cur
+= msg
->bodylen
;
5094 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
5095 conn
->inbufused
= strlen(conn
->inbuf
);
5097 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n",
5098 restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
5104 purple_debug_info("sipe", "body:\n%s", msg->body);
5107 // Verify the signature before processing it
5108 if (sip
->registrar
.gssapi_context
) {
5109 struct sipmsg_breakdown msgbd
;
5110 gchar
*signature_input_str
;
5113 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
5114 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
5116 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
5118 if (rspauth
!= NULL
) {
5119 if (!sip_sec_verify_signature(sip
->registrar
.gssapi_context
, signature_input_str
, rspauth
)) {
5120 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
5121 process_input_message(sip
, msg
);
5123 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid.\n");
5124 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
5125 sip
->gc
->wants_to_die
= TRUE
;
5127 } else if (msg
->response
== 401) {
5128 purple_connection_error(sip
->gc
, _("Wrong Password"));
5129 sip
->gc
->wants_to_die
= TRUE
;
5131 g_free(signature_input_str
);
5134 sipmsg_breakdown_free(&msgbd
);
5136 process_input_message(sip
, msg
);
5143 static void sipe_udp_process(gpointer data
, gint source
, PurpleInputCondition con
)
5145 PurpleConnection
*gc
= data
;
5146 struct sipe_account_data
*sip
= gc
->proto_data
;
5151 static char buffer
[65536];
5152 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
5154 purple_debug_info("sipe", "\n\nreceived - %s\n######\n%s\n#######\n\n", ctime(&currtime
), buffer
);
5155 msg
= sipmsg_parse_msg(buffer
);
5156 if (msg
) process_input_message(sip
, msg
);
5160 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
5162 struct sipe_account_data
*sip
= gc
->proto_data
;
5163 PurpleSslConnection
*gsc
= sip
->gsc
;
5165 purple_debug_error("sipe", "%s",debug
);
5166 purple_connection_error(gc
, msg
);
5168 /* Invalidate this connection. Next send will open a new one */
5170 connection_remove(sip
, gsc
->fd
);
5171 purple_ssl_close(gsc
);
5177 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
5179 PurpleConnection
*gc
= data
;
5180 struct sipe_account_data
*sip
;
5181 struct sip_connection
*conn
;
5183 gboolean firstread
= TRUE
;
5185 /* NOTE: This check *IS* necessary */
5186 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
5187 purple_ssl_close(gsc
);
5191 sip
= gc
->proto_data
;
5192 conn
= connection_find(sip
, gsc
->fd
);
5194 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
5195 gc
->wants_to_die
= TRUE
;
5196 purple_connection_error(gc
, _("Connection not found; Please try to connect again.\n"));
5200 /* Read all available data from the SSL connection */
5202 /* Increase input buffer size as needed */
5203 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
5204 conn
->inbuflen
+= SIMPLE_BUF_INC
;
5205 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
5206 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
5209 /* Try to read as much as there is space left in the buffer */
5210 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
5211 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
5213 if (len
< 0 && errno
== EAGAIN
) {
5214 /* Try again later */
5216 } else if (len
< 0) {
5217 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
5219 } else if (firstread
&& (len
== 0)) {
5220 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
5224 conn
->inbufused
+= len
;
5227 /* Equivalence indicates that there is possibly more data to read */
5228 } while (len
== readlen
);
5230 conn
->inbuf
[conn
->inbufused
] = '\0';
5231 process_input(sip
, conn
);
5235 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
5237 PurpleConnection
*gc
= data
;
5238 struct sipe_account_data
*sip
= gc
->proto_data
;
5240 struct sip_connection
*conn
= connection_find(sip
, source
);
5242 purple_debug_error("sipe", "Connection not found!\n");
5246 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
5247 conn
->inbuflen
+= SIMPLE_BUF_INC
;
5248 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
5251 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
5253 if (len
< 0 && errno
== EAGAIN
)
5255 else if (len
<= 0) {
5256 purple_debug_info("sipe", "sipe_input_cb: read error\n");
5257 connection_remove(sip
, source
);
5258 if (sip
->fd
== source
) sip
->fd
= -1;
5262 conn
->inbufused
+= len
;
5263 conn
->inbuf
[conn
->inbufused
] = '\0';
5265 process_input(sip
, conn
);
5268 /* Callback for new connections on incoming TCP port */
5269 static void sipe_newconn_cb(gpointer data
, gint source
, PurpleInputCondition cond
)
5271 PurpleConnection
*gc
= data
;
5272 struct sipe_account_data
*sip
= gc
->proto_data
;
5273 struct sip_connection
*conn
;
5275 int newfd
= accept(source
, NULL
, NULL
);
5277 conn
= connection_create(sip
, newfd
);
5279 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
5282 static void login_cb(gpointer data
, gint source
, const gchar
*error_message
)
5284 PurpleConnection
*gc
= data
;
5285 struct sipe_account_data
*sip
;
5286 struct sip_connection
*conn
;
5288 if (!PURPLE_CONNECTION_IS_VALID(gc
))
5296 purple_connection_error(gc
, _("Could not connect"));
5300 sip
= gc
->proto_data
;
5302 sip
->last_keepalive
= time(NULL
);
5304 conn
= connection_create(sip
, source
);
5308 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
5311 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
)
5313 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
5314 if (sip
== NULL
) return;
5319 static guint
sipe_ht_hash_nick(const char *nick
)
5321 char *lc
= g_utf8_strdown(nick
, -1);
5322 guint bucket
= g_str_hash(lc
);
5328 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
5330 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
5333 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
5335 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5337 sip
->listen_data
= NULL
;
5339 if (listenfd
== -1) {
5340 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5346 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
5347 sip
->listenfd
= sip
->fd
;
5349 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
5351 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
5355 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
, const char *error_message
)
5357 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5360 sip
->query_data
= NULL
;
5362 if (!hosts
|| !hosts
->data
) {
5363 purple_connection_error(sip
->gc
, _("Couldn't resolve host"));
5367 addr_size
= GPOINTER_TO_INT(hosts
->data
);
5368 hosts
= g_slist_remove(hosts
, hosts
->data
);
5369 memcpy(&(sip
->serveraddr
), hosts
->data
, addr_size
);
5370 g_free(hosts
->data
);
5371 hosts
= g_slist_remove(hosts
, hosts
->data
);
5373 hosts
= g_slist_remove(hosts
, hosts
->data
);
5374 g_free(hosts
->data
);
5375 hosts
= g_slist_remove(hosts
, hosts
->data
);
5378 /* create socket for incoming connections */
5379 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
5380 sipe_udp_host_resolved_listen_cb
, sip
);
5381 if (sip
->listen_data
== NULL
) {
5382 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5387 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
5390 PurpleConnection
*gc
= data
;
5391 struct sipe_account_data
*sip
;
5393 /* If the connection is already disconnected, we don't need to do anything else */
5394 if (!PURPLE_CONNECTION_IS_VALID(gc
))
5397 sip
= gc
->proto_data
;
5402 case PURPLE_SSL_CONNECT_FAILED
:
5403 purple_connection_error(gc
, _("Connection Failed"));
5405 case PURPLE_SSL_HANDSHAKE_FAILED
:
5406 purple_connection_error(gc
, _("SSL Handshake Failed"));
5408 case PURPLE_SSL_CERTIFICATE_INVALID
:
5409 purple_connection_error(gc
, _("SSL Certificate Invalid"));
5415 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
5417 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
5418 PurpleProxyConnectData
*connect_data
;
5420 sip
->listen_data
= NULL
;
5422 sip
->listenfd
= listenfd
;
5423 if (sip
->listenfd
== -1) {
5424 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
5428 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
5429 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
5430 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
5431 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
5432 sipe_newconn_cb
, sip
->gc
);
5433 purple_debug_info("sipe", "connecting to %s port %d\n",
5434 sip
->realhostname
, sip
->realport
);
5435 /* open tcp connection to the server */
5436 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
5437 sip
->realport
, login_cb
, sip
->gc
);
5439 if (connect_data
== NULL
) {
5440 purple_connection_error(sip
->gc
, _("Couldn't create socket"));
5445 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
5447 PurpleAccount
*account
= sip
->account
;
5448 PurpleConnection
*gc
= sip
->gc
;
5450 if (purple_account_get_bool(account
, "useport", FALSE
)) {
5451 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - using specified SIP port\n");
5452 port
= purple_account_get_int(account
, "port", 0);
5454 port
= port
? port
: (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
5457 sip
->realhostname
= hostname
;
5458 sip
->realport
= port
;
5460 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
5463 /* TODO: is there a good default grow size? */
5464 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
5465 sip
->txbuf
= purple_circ_buffer_new(0);
5467 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
5469 if (!purple_ssl_is_supported()) {
5470 gc
->wants_to_die
= TRUE
;
5471 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor."));
5475 purple_debug_info("sipe", "using SSL\n");
5477 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
5478 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
5479 if (sip
->gsc
== NULL
) {
5480 purple_connection_error(gc
, _("Could not create SSL context"));
5483 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
5485 purple_debug_info("sipe", "using UDP\n");
5487 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
5488 if (sip
->query_data
== NULL
) {
5489 purple_connection_error(gc
, _("Could not resolve hostname"));
5493 purple_debug_info("sipe", "using TCP\n");
5494 /* create socket for incoming connections */
5495 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
5496 sipe_tcp_connect_listen_cb
, sip
);
5497 if (sip
->listen_data
== NULL
) {
5498 purple_connection_error(gc
, _("Could not create listen socket"));
5504 /* Service list for autodection */
5505 static const struct sipe_service_data service_autodetect
[] = {
5506 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
5507 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
5508 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
5509 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
5513 /* Service list for SSL/TLS */
5514 static const struct sipe_service_data service_tls
[] = {
5515 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
5516 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
5520 /* Service list for TCP */
5521 static const struct sipe_service_data service_tcp
[] = {
5522 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
5523 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
5527 /* Service list for UDP */
5528 static const struct sipe_service_data service_udp
[] = {
5529 { "sip", "udp", SIPE_TRANSPORT_UDP
},
5533 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
5534 static void resolve_next_service(struct sipe_account_data
*sip
,
5535 const struct sipe_service_data
*start
)
5538 sip
->service_data
= start
;
5540 sip
->service_data
++;
5541 if (sip
->service_data
->service
== NULL
) {
5543 /* Try connecting to the SIP hostname directly */
5544 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
5545 if (sip
->auto_transport
) {
5546 // If SSL is supported, default to using it; OCS servers aren't configured
5547 // by default to accept TCP
5548 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
5549 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
5550 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
5553 hostname
= g_strdup(sip
->sipdomain
);
5554 create_connection(sip
, hostname
, 0);
5559 /* Try to resolve next service */
5560 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
5561 sip
->service_data
->transport
,
5566 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
5568 struct sipe_account_data
*sip
= data
;
5570 sip
->srv_query_data
= NULL
;
5572 /* find the host to connect to */
5574 gchar
*hostname
= g_strdup(resp
->hostname
);
5575 int port
= resp
->port
;
5576 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
5580 sip
->transport
= sip
->service_data
->type
;
5582 create_connection(sip
, hostname
, port
);
5584 resolve_next_service(sip
, NULL
);
5588 static void sipe_login(PurpleAccount
*account
)
5590 PurpleConnection
*gc
;
5591 struct sipe_account_data
*sip
;
5592 gchar
**signinname_login
, **userserver
, **domain_user
;
5593 const char *transport
;
5595 const char *username
= purple_account_get_username(account
);
5596 gc
= purple_account_get_connection(account
);
5598 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
5599 gc
->wants_to_die
= TRUE
;
5600 purple_connection_error(gc
, _("SIP Exchange username contains invalid characters"));
5604 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
5605 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
5606 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
5608 sip
->account
= account
;
5609 sip
->reregister_set
= FALSE
;
5610 sip
->reauthenticate_set
= FALSE
;
5611 sip
->subscribed
= FALSE
;
5612 sip
->subscribed_buddies
= FALSE
;
5614 signinname_login
= g_strsplit(username
, ",", 2);
5616 userserver
= g_strsplit(signinname_login
[0], "@", 2);
5617 purple_connection_set_display_name(gc
, userserver
[0]);
5618 sip
->username
= g_strjoin("@", userserver
[0], userserver
[1], NULL
);
5619 sip
->sipdomain
= g_strdup(userserver
[1]);
5621 if (strpbrk(sip
->username
, " \t\v\r\n") != NULL
) {
5622 gc
->wants_to_die
= TRUE
;
5623 purple_connection_error(gc
, _("SIP Exchange usernames may not contain whitespaces"));
5627 domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
5628 sip
->authdomain
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[0]) : NULL
;
5629 sip
->authuser
= (domain_user
&& domain_user
[1]) ? g_strdup(domain_user
[1]) : (signinname_login
? g_strdup(signinname_login
[1]) : NULL
);
5631 sip
->password
= g_strdup(purple_connection_get_password(gc
));
5633 g_strfreev(userserver
);
5634 g_strfreev(domain_user
);
5635 g_strfreev(signinname_login
);
5637 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
5639 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
5641 /* TODO: Set the status correctly. */
5642 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
5644 transport
= purple_account_get_string(account
, "transport", "auto");
5645 sip
->transport
= (strcmp(transport
, "tls") == 0) ? SIPE_TRANSPORT_TLS
:
5646 (strcmp(transport
, "tcp") == 0) ? SIPE_TRANSPORT_TCP
:
5649 if (purple_account_get_bool(account
, "useproxy", FALSE
)) {
5650 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login - using specified SIP proxy\n");
5651 create_connection(sip
, g_strdup(purple_account_get_string(account
, "proxy", sip
->sipdomain
)), 0);
5652 } else if (strcmp(transport
, "auto") == 0) {
5653 sip
->auto_transport
= TRUE
;
5654 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
5655 } else if (strcmp(transport
, "tls") == 0) {
5656 resolve_next_service(sip
, service_tls
);
5657 } else if (strcmp(transport
, "tcp") == 0) {
5658 resolve_next_service(sip
, service_tcp
);
5660 resolve_next_service(sip
, service_udp
);
5664 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
5666 connection_free_all(sip
);
5671 if (sip
->query_data
!= NULL
)
5672 purple_dnsquery_destroy(sip
->query_data
);
5673 sip
->query_data
= NULL
;
5675 if (sip
->srv_query_data
!= NULL
)
5676 purple_srv_cancel(sip
->srv_query_data
);
5677 sip
->srv_query_data
= NULL
;
5679 if (sip
->listen_data
!= NULL
)
5680 purple_network_listen_cancel(sip
->listen_data
);
5681 sip
->listen_data
= NULL
;
5683 if (sip
->gsc
!= NULL
)
5684 purple_ssl_close(sip
->gsc
);
5687 sipe_auth_free(&sip
->registrar
);
5688 sipe_auth_free(&sip
->proxy
);
5691 purple_circ_buffer_destroy(sip
->txbuf
);
5694 g_free(sip
->realhostname
);
5695 sip
->realhostname
= NULL
;
5698 purple_input_remove(sip
->listenpa
);
5700 if (sip
->tx_handler
)
5701 purple_input_remove(sip
->tx_handler
);
5702 sip
->tx_handler
= 0;
5703 if (sip
->resendtimeout
)
5704 purple_timeout_remove(sip
->resendtimeout
);
5705 sip
->resendtimeout
= 0;
5706 if (sip
->timeouts
) {
5707 GSList
*entry
= sip
->timeouts
;
5709 struct scheduled_action
*sched_action
= entry
->data
;
5710 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
5711 purple_timeout_remove(sched_action
->timeout_handler
);
5712 (*sched_action
->destroy
)(sched_action
->payload
);
5713 g_free(sched_action
->name
);
5714 g_free(sched_action
);
5715 entry
= entry
->next
;
5718 g_slist_free(sip
->timeouts
);
5720 if (sip
->allow_events
) {
5721 GSList
*entry
= sip
->allow_events
;
5723 g_free(entry
->data
);
5724 entry
= entry
->next
;
5727 g_slist_free(sip
->allow_events
);
5730 g_free(sip
->contact
);
5731 sip
->contact
= NULL
;
5733 g_free(sip
->regcallid
);
5734 sip
->regcallid
= NULL
;
5737 sip
->processing_input
= FALSE
;
5741 * A callback for g_hash_table_foreach_remove
5743 static void sipe_buddy_remove(gpointer key
, struct sipe_buddy
*buddy
, gpointer user_data
)
5745 sipe_free_buddy(buddy
);
5748 static void sipe_close(PurpleConnection
*gc
)
5750 struct sipe_account_data
*sip
= gc
->proto_data
;
5753 /* leave all conversations */
5754 im_session_close_all(sip
);
5757 do_register_exp(sip
, 0);
5759 sipe_connection_cleanup(sip
);
5760 g_free(sip
->sipdomain
);
5761 g_free(sip
->username
);
5762 g_free(sip
->password
);
5763 g_free(sip
->authdomain
);
5764 g_free(sip
->authuser
);
5765 g_free(sip
->status
);
5767 g_hash_table_foreach_remove(sip
->buddies
, (GHRFunc
) sipe_buddy_remove
, NULL
);
5768 g_hash_table_destroy(sip
->buddies
);
5770 g_free(gc
->proto_data
);
5771 gc
->proto_data
= NULL
;
5774 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5776 PurpleAccount
*acct
= purple_connection_get_account(gc
);
5777 char *id
= g_strdup_printf("sip:%s", (char *)g_list_nth_data(row
, 0));
5778 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
5780 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
5781 purple_conversation_present(conv
);
5785 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
, void *user_data
)
5788 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
5789 g_list_nth_data(row
, 0), NULL
, g_list_nth_data(row
, 1));
5792 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,struct transaction
*tc
)
5794 PurpleNotifySearchResults
*results
;
5795 PurpleNotifySearchColumn
*column
;
5796 xmlnode
*searchResults
;
5798 int match_count
= 0;
5799 gboolean more
= FALSE
;
5802 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
5804 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
5805 if (!searchResults
) {
5806 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
5810 results
= purple_notify_searchresults_new();
5812 if (results
== NULL
) {
5813 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
5814 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results."), NULL
);
5816 xmlnode_free(searchResults
);
5820 column
= purple_notify_searchresults_column_new(_("User Name"));
5821 purple_notify_searchresults_column_add(results
, column
);
5823 column
= purple_notify_searchresults_column_new(_("Name"));
5824 purple_notify_searchresults_column_add(results
, column
);
5826 column
= purple_notify_searchresults_column_new(_("Company"));
5827 purple_notify_searchresults_column_add(results
, column
);
5829 column
= purple_notify_searchresults_column_new(_("Country"));
5830 purple_notify_searchresults_column_add(results
, column
);
5832 column
= purple_notify_searchresults_column_new(_("Email"));
5833 purple_notify_searchresults_column_add(results
, column
);
5835 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
5838 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
5839 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
5840 g_strfreev(uri_parts
);
5842 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
5843 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
5844 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
5845 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
5847 purple_notify_searchresults_row_add(results
, row
);
5851 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
5852 char *data
= xmlnode_get_data_unescaped(mrow
);
5853 more
= (g_strcasecmp(data
, "true") == 0);
5857 secondary
= g_strdup_printf(
5858 dngettext(GETTEXT_PACKAGE
,
5859 "Found %d contact%s:",
5860 "Found %d contacts%s:", match_count
),
5861 match_count
, more
? _(" (more matched your query)") : "");
5863 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
5864 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
5865 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
5868 xmlnode_free(searchResults
);
5872 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
5874 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
5875 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
5879 PurpleRequestField
*field
= entries
->data
;
5880 const char *id
= purple_request_field_get_id(field
);
5881 const char *value
= purple_request_field_string_get_value(field
);
5883 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
5885 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
5886 } while ((entries
= g_list_next(entries
)) != NULL
);
5890 gchar
*query
= g_strjoinv(NULL
, attrs
);
5891 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
5892 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
5893 send_soap_request_with_cb(gc
->proto_data
, body
,
5894 (TransCallback
) process_search_contact_response
, NULL
);
5902 static void sipe_show_find_contact(PurplePluginAction
*action
)
5904 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
5905 PurpleRequestFields
*fields
;
5906 PurpleRequestFieldGroup
*group
;
5907 PurpleRequestField
*field
;
5909 fields
= purple_request_fields_new();
5910 group
= purple_request_field_group_new(NULL
);
5911 purple_request_fields_add_group(fields
, group
);
5913 field
= purple_request_field_string_new("givenName", _("First Name"), NULL
, FALSE
);
5914 purple_request_field_group_add_field(group
, field
);
5915 field
= purple_request_field_string_new("sn", _("Last Name"), NULL
, FALSE
);
5916 purple_request_field_group_add_field(group
, field
);
5917 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
5918 purple_request_field_group_add_field(group
, field
);
5919 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
5920 purple_request_field_group_add_field(group
, field
);
5922 purple_request_fields(gc
,
5924 _("Search for a Contact"),
5925 _("Enter the information of the person you wish to find. Empty fields will be ignored."),
5927 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
5929 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
5932 GList
*sipe_actions(PurplePlugin
*plugin
, gpointer context
)
5935 PurplePluginAction
*act
;
5937 act
= purple_plugin_action_new(_("Contact Search..."), sipe_show_find_contact
);
5938 menu
= g_list_prepend(menu
, act
);
5940 menu
= g_list_reverse(menu
);
5945 static void dummy_permit_deny(PurpleConnection
*gc
)
5949 static gboolean
sipe_plugin_load(PurplePlugin
*plugin
)
5955 static gboolean
sipe_plugin_unload(PurplePlugin
*plugin
)
5961 static char *sipe_status_text(PurpleBuddy
*buddy
)
5963 struct sipe_account_data
*sip
;
5964 struct sipe_buddy
*sbuddy
;
5967 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5968 if (sip
) //happens on pidgin exit
5970 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5971 if (sbuddy
&& sbuddy
->annotation
)
5973 text
= g_strdup(sbuddy
->annotation
);
5980 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, gboolean full
)
5982 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
5983 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
5984 struct sipe_account_data
*sip
;
5985 struct sipe_buddy
*sbuddy
;
5986 char *annotation
= NULL
;
5988 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
5989 if (sip
) //happens on pidgin exit
5991 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
5994 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
5999 if (purple_presence_is_online(presence
))
6001 purple_notify_user_info_add_pair(user_info
, _("Status"), purple_status_get_name(status
));
6006 purple_notify_user_info_add_pair( user_info
, _("Note"), annotation
);
6013 sipe_get_account_text_table(PurpleAccount
*account
)
6016 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
6017 g_hash_table_insert(table
, "login_label", (gpointer
)_("Sign-In Name..."));
6021 static PurpleBuddy
*
6022 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
6025 const gchar
*server_alias
, *email
;
6026 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
6028 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
6030 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
6032 server_alias
= g_strdup(purple_buddy_get_server_alias(buddy
));
6034 purple_blist_server_alias_buddy(clone
, server_alias
);
6037 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
6039 purple_blist_node_set_string((PurpleBlistNode
*)clone
, "email", email
);
6042 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
6044 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
6049 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
6051 PurpleBuddy
*buddy
, *b
;
6052 PurpleConnection
*gc
;
6053 PurpleGroup
* group
= purple_find_group(group_name
);
6055 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
6057 buddy
= (PurpleBuddy
*)node
;
6059 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
6060 gc
= purple_account_get_connection(buddy
->account
);
6062 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
6064 b
= purple_blist_add_buddy_clone(group
, buddy
);
6067 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
6071 sipe_buddy_menu_chat_new_cb(PurpleBuddy
*buddy
)
6073 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6074 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6075 gchar
*chat_name
= g_strdup_printf(_("Chat #%d"), ++sip
->chat_seq
);
6076 struct sip_im_session
*session
;
6078 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy
->name
);
6080 session
= create_chat_session(sip
);
6081 session
->roster_manager
= g_strdup(self
);
6083 session
->conv
= serv_got_joined_chat(buddy
->account
->gc
, session
->chat_id
, g_strdup(chat_name
));
6084 session
->chat_name
= g_strdup(chat_name
);
6085 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
), self
, NULL
, PURPLE_CBFLAGS_NONE
, FALSE
);
6086 sipe_invite(sip
, session
, buddy
->name
, NULL
, NULL
, FALSE
);
6093 sipe_is_election_finished(struct sipe_account_data
*sip
,
6094 struct sip_im_session
*session
)
6096 struct sip_dialog
*dialog
;
6098 gboolean res
= TRUE
;
6100 entry
= session
->dialogs
;
6102 dialog
= entry
->data
;
6103 if (dialog
->election_vote
== 0) {
6107 entry
= entry
->next
;
6111 session
->is_voting_in_progress
= FALSE
;
6117 sipe_election_start(struct sipe_account_data
*sip
,
6118 struct sip_im_session
*session
)
6120 struct sip_dialog
*dialog
;
6122 int election_timeout
;
6124 if (session
->is_voting_in_progress
) {
6125 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
6128 session
->is_voting_in_progress
= TRUE
;
6130 session
->bid
= rand();
6132 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session
->bid
);
6134 /* reset election_vote for each chat participant */
6135 entry
= session
->dialogs
;
6137 dialog
= entry
->data
;
6138 dialog
->election_vote
= 0;
6139 entry
= entry
->next
;
6142 entry
= session
->dialogs
;
6144 dialog
= entry
->data
;
6145 /* send RequestRM to each chat participant*/
6146 sipe_send_election_request_rm(sip
, session
, dialog
->with
, session
->bid
);
6147 entry
= entry
->next
;
6150 election_timeout
= 15; /* sec */
6151 sipe_schedule_action("<+election-result>", election_timeout
, sipe_election_result
, NULL
, sip
, session
);
6155 * @param who a URI to whom to invite to chat
6158 sipe_invite_to_chat(struct sipe_account_data
*sip
,
6159 struct sip_im_session
*session
,
6162 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6164 if (session
->roster_manager
) {
6165 if (!strcmp(session
->roster_manager
, self
)) {
6166 sipe_invite(sip
, session
, who
, NULL
, NULL
, FALSE
);
6168 sipe_refer(sip
, session
, who
);
6171 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: no RM available\n");
6173 session
->pending_invite_queue
= slist_insert_unique_sorted(
6174 session
->pending_invite_queue
, g_strdup(who
), (GCompareFunc
)strcmp
);
6176 sipe_election_start(sip
, session
);
6183 sipe_process_pending_invite_queue(struct sipe_account_data
*sip
,
6184 struct sip_im_session
*session
)
6187 GSList
*entry
= session
->pending_invite_queue
;
6190 invitee
= entry
->data
;
6191 sipe_invite_to_chat(sip
, session
, invitee
);
6192 entry
= session
->pending_invite_queue
= g_slist_remove(session
->pending_invite_queue
, invitee
);
6198 sipe_election_result(struct sipe_account_data
*sip
,
6201 struct sip_im_session
*session
= (struct sip_im_session
*)sess
;
6202 struct sip_dialog
*dialog
;
6205 gboolean has_won
= TRUE
;
6207 if (session
->roster_manager
) {
6208 purple_debug_info("sipe",
6209 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session
->roster_manager
);
6213 session
->is_voting_in_progress
= FALSE
;
6215 entry
= session
->dialogs
;
6217 dialog
= entry
->data
;
6218 if (dialog
->election_vote
< 0) {
6220 rival
= dialog
->with
;
6223 entry
= entry
->next
;
6227 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
6229 session
->roster_manager
= g_strdup_printf("sip:%s", sip
->username
);
6231 entry
= session
->dialogs
;
6233 dialog
= entry
->data
;
6234 /* send SetRM to each chat participant*/
6235 sipe_send_election_set_rm(sip
, session
, dialog
->with
);
6236 entry
= entry
->next
;
6239 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival
);
6243 sipe_process_pending_invite_queue(sip
, session
);
6247 sipe_buddy_menu_chat_invite_cb(PurpleBuddy
*buddy
, const char *chat_name
)
6249 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6250 struct sip_im_session
*session
;
6252 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: buddy->name=%s\n", buddy
->name
);
6253 purple_debug_info("sipe", "sipe_buddy_menu_chat_cb: chat_name=%s\n", chat_name
);
6255 session
= find_chat_session_by_name(sip
, chat_name
);
6257 sipe_invite_to_chat(sip
, session
, buddy
->name
);
6261 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
6264 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
6266 email
= purple_blist_node_get_string((PurpleBlistNode
*)buddy
, "email");
6269 char *mailto
= g_strdup_printf("mailto:%s", email
);
6270 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
6274 char *const parmList
[] = {mailto
, NULL
};
6275 if ((pid
= fork()) == -1)
6277 purple_debug_info("sipe", "fork() error\n");
6281 execvp("xdg-email", parmList
);
6282 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
6290 //@TODO resolve env variable %WINDIR% first
6291 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
6294 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
6303 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
6308 * A menu which appear when right-clicking on buddy in contact list.
6311 sipe_buddy_menu(PurpleBuddy
*buddy
)
6313 PurpleBlistNode
*g_node
;
6314 PurpleGroup
*group
, *gr_parent
;
6315 PurpleMenuAction
*act
;
6317 GList
*menu_groups
= NULL
;
6318 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
6319 struct sip_im_session
*session
;
6321 gchar
*self
= g_strdup_printf("sip:%s", sip
->username
);
6323 act
= purple_menu_action_new(_("New Chat"),
6324 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb
),
6326 menu
= g_list_prepend(menu
, act
);
6328 entry
= sip
->im_sessions
;
6330 session
= entry
->data
;
6331 if (strcmp(self
, buddy
->name
) && session
->chat_name
&& !get_dialog(session
, buddy
->name
)) {
6332 act
= purple_menu_action_new(g_strdup_printf(_("Invite to '%s'"), session
->chat_name
),
6333 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb
),
6334 g_strdup(session
->chat_name
), NULL
);
6335 menu
= g_list_prepend(menu
, act
);
6337 entry
= entry
->next
;
6340 act
= purple_menu_action_new(_("Send Email..."),
6341 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
6343 menu
= g_list_prepend(menu
, act
);
6345 gr_parent
= purple_buddy_get_group(buddy
);
6346 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
6347 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
6350 group
= (PurpleGroup
*)g_node
;
6351 if (group
== gr_parent
)
6354 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
6357 act
= purple_menu_action_new(purple_group_get_name(group
),
6358 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
6360 menu_groups
= g_list_prepend(menu_groups
, act
);
6362 menu_groups
= g_list_reverse(menu_groups
);
6364 act
= purple_menu_action_new(_("Copy to"),
6367 menu
= g_list_prepend(menu
, act
);
6368 menu
= g_list_reverse(menu
);
6375 sipe_blist_node_menu(PurpleBlistNode
*node
)
6377 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
6378 return sipe_buddy_menu((PurpleBuddy
*) node
);
6385 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
6387 gboolean ret
= TRUE
;
6388 char *username
= (char *)trans
->payload
;
6390 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
6391 PurpleBuddy
*pbuddy
;
6392 struct sipe_buddy
*sbuddy
;
6394 char *server_alias
= NULL
;
6396 const char *device_name
= NULL
;
6398 purple_debug_info("sipe", "Fetching %s's user info for %s\n", username
, sip
->username
);
6400 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, username
);
6401 alias
= purple_buddy_get_local_alias(pbuddy
);
6405 //will query buddy UA's capabilities and send answer to log
6406 sipe_options_request(sip
, username
);
6408 sbuddy
= g_hash_table_lookup(sip
->buddies
, username
);
6411 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
6415 if (msg
->response
!= 200) {
6416 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
6418 xmlnode
*searchResults
;
6421 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
6422 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
6423 if (!searchResults
) {
6424 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
6425 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
6426 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
6427 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
6428 purple_notify_user_info_add_pair(info
, _("Job Title"), g_strdup(xmlnode_get_attrib(mrow
, "title")));
6429 purple_notify_user_info_add_pair(info
, _("Office"), g_strdup(xmlnode_get_attrib(mrow
, "office")));
6430 purple_notify_user_info_add_pair(info
, _("Business Phone"), g_strdup(xmlnode_get_attrib(mrow
, "phone")));
6431 purple_notify_user_info_add_pair(info
, _("Company"), g_strdup(xmlnode_get_attrib(mrow
, "company")));
6432 purple_notify_user_info_add_pair(info
, _("City"), g_strdup(xmlnode_get_attrib(mrow
, "city")));
6433 purple_notify_user_info_add_pair(info
, _("State"), g_strdup(xmlnode_get_attrib(mrow
, "state")));
6434 purple_notify_user_info_add_pair(info
, _("Country"), g_strdup(xmlnode_get_attrib(mrow
, "country")));
6435 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
6436 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
6437 if (!email
|| strcmp("", email
)) {
6438 if (!purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email")) {
6439 purple_blist_node_set_string((PurpleBlistNode
*)pbuddy
, "email", email
);
6443 xmlnode_free(searchResults
);
6446 purple_notify_user_info_add_section_break(info
);
6448 if (!server_alias
|| !strcmp("", server_alias
)) {
6449 g_free(server_alias
);
6450 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
6452 purple_notify_user_info_add_pair(info
, _("Display Name"), server_alias
);
6456 // same as server alias, do not present
6457 alias
= (alias
&& server_alias
&& !strcmp(alias
, server_alias
)) ? NULL
: alias
;
6460 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
6463 if (!email
|| !strcmp("", email
)) {
6465 email
= g_strdup(purple_blist_node_get_string((PurpleBlistNode
*)pbuddy
, "email"));
6467 purple_notify_user_info_add_pair(info
, _("E-Mail Address"), email
);
6473 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
6476 /* show a buddy's user info in a nice dialog box */
6477 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
6478 username
, /* buddy's username */
6480 NULL
, /* callback called when dialog closed */
6481 NULL
); /* userdata for callback */
6487 * AD search first, LDAP based
6489 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
6491 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
6492 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
6494 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
6495 send_soap_request_with_cb((struct sipe_account_data
*)gc
->proto_data
, body
,
6496 (TransCallback
) process_get_info_response
, (gpointer
)g_strdup(username
));
6501 static PurplePlugin
*my_protocol
= NULL
;
6503 static PurplePluginProtocolInfo prpl_info
=
6506 NULL
, /* user_splits */
6507 NULL
, /* protocol_options */
6508 NO_BUDDY_ICONS
, /* icon_spec */
6509 sipe_list_icon
, /* list_icon */
6510 NULL
, /* list_emblems */
6511 sipe_status_text
, /* status_text */
6512 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
6513 sipe_status_types
, /* away_states */
6514 sipe_blist_node_menu
, /* blist_node_menu */
6515 NULL
, /* chat_info */
6516 NULL
, /* chat_info_defaults */
6517 sipe_login
, /* login */
6518 sipe_close
, /* close */
6519 sipe_im_send
, /* send_im */
6520 NULL
, /* set_info */ // TODO maybe
6521 sipe_send_typing
, /* send_typing */
6522 sipe_get_info
, /* get_info */
6523 sipe_set_status
, /* set_status */
6524 NULL
, /* set_idle */
6525 NULL
, /* change_passwd */
6526 sipe_add_buddy
, /* add_buddy */
6527 NULL
, /* add_buddies */
6528 sipe_remove_buddy
, /* remove_buddy */
6529 NULL
, /* remove_buddies */
6530 sipe_add_permit
, /* add_permit */
6531 sipe_add_deny
, /* add_deny */
6532 sipe_add_deny
, /* rem_permit */
6533 sipe_add_permit
, /* rem_deny */
6534 dummy_permit_deny
, /* set_permit_deny */
6535 NULL
, /* join_chat */
6536 NULL
, /* reject_chat */
6537 NULL
, /* get_chat_name */
6538 NULL
, /* chat_invite */
6539 sipe_chat_leave
, /* chat_leave */
6540 NULL
, /* chat_whisper */
6541 sipe_chat_send
, /* chat_send */
6542 sipe_keep_alive
, /* keepalive */
6543 NULL
, /* register_user */
6544 NULL
, /* get_cb_info */ // deprecated
6545 NULL
, /* get_cb_away */ // deprecated
6546 sipe_alias_buddy
, /* alias_buddy */
6547 sipe_group_buddy
, /* group_buddy */
6548 sipe_rename_group
, /* rename_group */
6549 NULL
, /* buddy_free */
6550 sipe_convo_closed
, /* convo_closed */
6551 purple_normalize_nocase
, /* normalize */
6552 NULL
, /* set_buddy_icon */
6553 sipe_remove_group
, /* remove_group */
6554 NULL
, /* get_cb_real_name */ // TODO?
6555 NULL
, /* set_chat_topic */
6556 NULL
, /* find_blist_chat */
6557 NULL
, /* roomlist_get_list */
6558 NULL
, /* roomlist_cancel */
6559 NULL
, /* roomlist_expand_category */
6560 NULL
, /* can_receive_file */
6561 NULL
, /* send_file */
6562 NULL
, /* new_xfer */
6563 NULL
, /* offline_message */
6564 NULL
, /* whiteboard_prpl_ops */
6565 sipe_send_raw
, /* send_raw */
6566 NULL
, /* roomlist_room_serialize */
6567 NULL
, /* unregister_user */
6568 NULL
, /* send_attention */
6569 NULL
, /* get_attention_types */
6571 sizeof(PurplePluginProtocolInfo
), /* struct_size */
6572 sipe_get_account_text_table
, /* get_account_text_table */
6576 static PurplePluginInfo info
= {
6577 PURPLE_PLUGIN_MAGIC
,
6578 PURPLE_MAJOR_VERSION
,
6579 PURPLE_MINOR_VERSION
,
6580 PURPLE_PLUGIN_PROTOCOL
, /**< type */
6581 NULL
, /**< ui_requirement */
6583 NULL
, /**< dependencies */
6584 PURPLE_PRIORITY_DEFAULT
, /**< priority */
6585 "prpl-sipe", /**< id */
6586 "Microsoft LCS/OCS", /**< name */
6587 VERSION
, /**< version */
6588 "SIP/SIMPLE OCS/LCS Protocol Plugin", /** summary */
6589 "The SIP/SIMPLE LCS/OCS Protocol Plugin", /** description */
6590 "Anibal Avelar <avelar@gmail.com>, " /**< author */
6591 "Gabriel Burt <gburt@novell.com>", /**< author */
6592 PURPLE_WEBSITE
, /**< homepage */
6593 sipe_plugin_load
, /**< load */
6594 sipe_plugin_unload
, /**< unload */
6595 sipe_plugin_destroy
, /**< destroy */
6596 NULL
, /**< ui_info */
6597 &prpl_info
, /**< extra_info */
6606 static void sipe_plugin_destroy(PurplePlugin
*plugin
)
6610 entry
= prpl_info
.protocol_options
;
6612 purple_account_option_destroy(entry
->data
);
6613 entry
= g_list_delete_link(entry
, entry
);
6615 prpl_info
.protocol_options
= NULL
;
6617 entry
= prpl_info
.user_splits
;
6619 purple_account_user_split_destroy(entry
->data
);
6620 entry
= g_list_delete_link(entry
, entry
);
6622 prpl_info
.user_splits
= NULL
;
6625 static void init_plugin(PurplePlugin
*plugin
)
6627 PurpleAccountUserSplit
*split
;
6628 PurpleAccountOption
*option
;
6633 purple_debug_info(PACKAGE
, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
6634 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s\n",
6635 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
6636 textdomain(GETTEXT_PACKAGE
);
6639 purple_plugin_register(plugin
);
6641 split
= purple_account_user_split_new(_("Login \n domain\\user or\n someone@linux.com "), NULL
, ',');
6642 purple_account_user_split_set_reverse(split
, FALSE
);
6643 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
6645 option
= purple_account_option_bool_new(_("Use proxy"), "useproxy", FALSE
);
6646 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6647 option
= purple_account_option_string_new(_("Proxy Server"), "proxy", "");
6648 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6650 option
= purple_account_option_bool_new(_("Use non-standard port"), "useport", FALSE
);
6651 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6652 // Translators: noun (networking port)
6653 option
= purple_account_option_int_new(_("Port"), "port", 5061);
6654 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6656 option
= purple_account_option_list_new(_("Connection Type"), "transport", NULL
);
6657 purple_account_option_add_list_item(option
, _("Auto"), "auto");
6658 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
6659 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
6660 purple_account_option_add_list_item(option
, _("UDP"), "udp");
6661 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6663 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
6664 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
6666 option
= purple_account_option_string_new(_("User Agent"), "useragent", "Purple/" VERSION
);
6667 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6670 option
= purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE
);
6671 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6673 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
6674 * No login/password is taken into account if this option present,
6675 * instead used default credentials stored in OS.
6677 option
= purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE
);
6678 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
6680 my_protocol
= plugin
;
6683 /* I had to redefined the function for it load, but works */
6684 gboolean
purple_init_plugin(PurplePlugin
*plugin
){
6685 plugin
->info
= &(info
);
6686 init_plugin((plugin
));
6687 sipe_plugin_load((plugin
));
6688 return purple_plugin_register(plugin
);