6 * Copyright (C) 2010 pier11 <pier11@operamail.com>
7 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
8 * Copyright (C) 2009 pier11 <pier11@operamail.com>
9 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
10 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
14 * Thanks to Google's Summer of Code Program and the helpful mentors
17 * Session-based SIP MESSAGE documentation:
18 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
20 * This program is free software; you can redistribute it and/or modify
21 * it under the terms of the GNU General Public License as published by
22 * the Free Software Foundation; either version 2 of the License, or
23 * (at your option) any later version.
25 * This program is distributed in the hope that it will be useful,
26 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28 * GNU General Public License for more details.
30 * You should have received a copy of the GNU General Public License
31 * along with this program; if not, write to the Free Software
32 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
36 #include <sys/socket.h>
37 #include <sys/ioctl.h>
38 #include <sys/types.h>
39 #include <netinet/in.h>
45 #define _LIBC_INTERNAL_
58 #include "accountopt.h"
60 #include "conversation.h"
64 #include "savedstatuses.h"
78 #include "sipe-chat.h"
79 #include "sipe-conf.h"
81 #include "sipe-dialog.h"
83 #include "sipe-session.h"
84 #include "sipe-utils.h"
86 #include "sipe-sign.h"
90 /* Backward compatibility when compiling against 2.4.x API */
91 #if !PURPLE_VERSION_CHECK(2,5,0)
92 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
95 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
96 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
98 /* Keep in sync with sipe_transport_type! */
99 static const char *transport_descriptor
[] = { "tls", "tcp", "udp" };
100 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
102 /* Status identifiers (see also: sipe_status_types()) */
103 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
104 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
105 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
106 /* PURPLE_STATUS_UNAVAILABLE: */
107 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
108 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
109 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
110 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
111 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
112 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
113 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
114 /* PURPLE_STATUS_AWAY: */
115 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
116 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
117 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
118 /** Reuters status (user settable) */
119 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
120 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
121 /* ??? PURPLE_STATUS_MOBILE */
122 /* ??? PURPLE_STATUS_TUNE */
124 /* Status attributes (see also sipe_status_types() */
125 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
126 #define SIPE_STATUS_ATTR_ID_DONT_PUB "do-not-publish"
128 /** Activity (token and description) 2007 */
131 SIPE_ACTIVITY_UNSET
= 0,
132 SIPE_ACTIVITY_ONLINE
,
133 SIPE_ACTIVITY_INACTIVE
,
135 SIPE_ACTIVITY_BUSYIDLE
,
140 SIPE_ACTIVITY_OFFLINE
,
141 SIPE_ACTIVITY_ON_PHONE
,
142 SIPE_ACTIVITY_IN_CONF
,
143 SIPE_ACTIVITY_IN_MEETING
,
145 SIPE_ACTIVITY_URGENT_ONLY
,
146 SIPE_ACTIVITY_NUM_TYPES
149 static struct sipe_activity_map_struct
154 const char *status_id
;
156 } const sipe_activity_map
[] =
158 /* This has nothing to do with Availability numbers, like 3500 (online).
159 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
161 { SIPE_ACTIVITY_UNSET
, "unset", NULL
, NULL
},
162 { SIPE_ACTIVITY_ONLINE
, "online", NULL
, NULL
},
163 { SIPE_ACTIVITY_INACTIVE
, SIPE_STATUS_ID_IDLE
, N_("Inactive") , SIPE_STATUS_ID_IDLE
},
164 { SIPE_ACTIVITY_BUSY
, SIPE_STATUS_ID_BUSY
, N_("Busy") , SIPE_STATUS_ID_BUSY
},
165 { SIPE_ACTIVITY_BUSYIDLE
, SIPE_STATUS_ID_BUSYIDLE
, N_("BusyIdle") , SIPE_STATUS_ID_BUSYIDLE
},
166 { SIPE_ACTIVITY_DND
, SIPE_STATUS_ID_DND
, NULL
, SIPE_STATUS_ID_DND
},
167 { SIPE_ACTIVITY_BRB
, SIPE_STATUS_ID_BRB
, N_("Be right back") , SIPE_STATUS_ID_BRB
},
168 { SIPE_ACTIVITY_AWAY
, "away", NULL
, NULL
},
169 { SIPE_ACTIVITY_LUNCH
, SIPE_STATUS_ID_LUNCH
, N_("Out to lunch") , SIPE_STATUS_ID_LUNCH
},
170 { SIPE_ACTIVITY_OFFLINE
, "offline", NULL
, NULL
},
171 { SIPE_ACTIVITY_ON_PHONE
, SIPE_STATUS_ID_ON_PHONE
, N_("In a call") , SIPE_STATUS_ID_ON_PHONE
},
172 { SIPE_ACTIVITY_IN_CONF
, SIPE_STATUS_ID_IN_CONF
, N_("In a conference") , SIPE_STATUS_ID_IN_CONF
},
173 { SIPE_ACTIVITY_IN_MEETING
, SIPE_STATUS_ID_IN_MEETING
, N_("In a meeting") , SIPE_STATUS_ID_IN_MEETING
},
174 { SIPE_ACTIVITY_OOF
, "out-of-office", N_("Out of office") , NULL
},
175 { SIPE_ACTIVITY_URGENT_ONLY
, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL
}
177 /** @param x is sipe_activity */
178 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
181 /* Action name templates */
182 #define ACTION_NAME_PRESENCE "<presence><%s>"
185 sipe_get_activity_by_token(const char *token
)
189 for (i
= 0; i
< SIPE_ACTIVITY_NUM_TYPES
; i
++)
191 if (!strcmp(token
, sipe_activity_map
[i
].token
))
192 return sipe_activity_map
[i
].type
;
195 return sipe_activity_map
[0].type
;
199 sipe_get_activity_desc_by_token(const char *token
)
201 if (!token
) return NULL
;
203 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token
));
206 /** Allows to send typed messages from chat window again after account reinstantiation. */
208 sipe_rejoin_chat(PurpleConversation
*conv
)
210 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
&&
211 PURPLE_CONV_CHAT(conv
)->left
)
213 PURPLE_CONV_CHAT(conv
)->left
= FALSE
;
214 purple_conversation_update(conv
, PURPLE_CONV_UPDATE_CHATLEFT
);
218 static char *genbranch()
220 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
221 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
222 rand() & 0xFFFF, rand() & 0xFFFF);
226 static char *default_ua
= NULL
;
228 sipe_get_useragent(struct sipe_account_data
*sip
)
230 const char *useragent
= purple_account_get_string(sip
->account
, "useragent", "");
231 if (is_empty(useragent
)) {
233 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
235 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
236 #define SIPE_TARGET_PLATFORM "linux"
237 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
238 #define SIPE_TARGET_PLATFORM "bsd"
239 # elif defined(__APPLE__) || defined(__MACOS__)
240 #define SIPE_TARGET_PLATFORM "macosx"
241 #elif defined(__solaris__) || defined(__sun)
242 #define SIPE_TARGET_PLATFORM "sun"
243 #elif defined(_WIN32)
244 #define SIPE_TARGET_PLATFORM "win"
246 #define SIPE_TARGET_PLATFORM "generic"
249 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
250 #define SIPE_TARGET_ARCH "x86_64"
251 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
252 #define SIPE_TARGET_ARCH "i386"
253 #elif defined(__ppc64__)
254 #define SIPE_TARGET_ARCH "ppc64"
255 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
256 #define SIPE_TARGET_ARCH "ppc"
258 #define SIPE_TARGET_ARCH "other"
261 default_ua
= g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
262 purple_core_get_version(),
264 SIPE_TARGET_PLATFORM
,
266 sip
->server_version
? sip
->server_version
: "");
268 useragent
= default_ua
;
273 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount
*a
,
274 SIPE_UNUSED_PARAMETER PurpleBuddy
*b
)
279 static void sipe_plugin_destroy(PurplePlugin
*plugin
);
281 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
);
283 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
284 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
287 static void sipe_close(PurpleConnection
*gc
);
289 static void send_presence_status(struct sipe_account_data
*sip
);
291 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
293 static void sipe_keep_alive(PurpleConnection
*gc
)
295 struct sipe_account_data
*sip
= gc
->proto_data
;
296 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
297 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
298 gchar buf
[2] = {0, 0};
299 purple_debug_info("sipe", "sending keep alive\n");
300 sendto(sip
->fd
, buf
, 1, 0, sip
->serveraddr
, sizeof(struct sockaddr_in
));
302 time_t now
= time(NULL
);
303 if ((sip
->keepalive_timeout
> 0) &&
304 ((guint
) (now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
305 #if PURPLE_VERSION_CHECK(2,4,0)
306 && ((guint
) (now
- gc
->last_received
) >= sip
->keepalive_timeout
)
309 purple_debug_info("sipe", "sending keep alive %d\n",sip
->keepalive_timeout
);
310 sendout_pkt(gc
, "\r\n\r\n");
311 sip
->last_keepalive
= now
;
316 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
318 struct sip_connection
*ret
= NULL
;
319 GSList
*entry
= sip
->openconns
;
322 if (ret
->fd
== fd
) return ret
;
328 static void sipe_auth_free(struct sip_auth
*auth
)
330 g_free(auth
->opaque
);
334 g_free(auth
->target
);
336 auth
->type
= AUTH_TYPE_UNSET
;
339 g_free(auth
->gssapi_data
);
340 auth
->gssapi_data
= NULL
;
341 sip_sec_destroy_context(auth
->gssapi_context
);
342 auth
->gssapi_context
= NULL
;
345 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
347 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
349 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
353 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
355 struct sip_connection
*conn
= connection_find(sip
, fd
);
357 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
358 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
364 static void connection_free_all(struct sipe_account_data
*sip
)
366 struct sip_connection
*ret
= NULL
;
367 GSList
*entry
= sip
->openconns
;
370 connection_remove(sip
, ret
->fd
);
371 entry
= sip
->openconns
;
375 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
378 const char *authuser
= sip
->authuser
;
382 if (!authuser
|| strlen(authuser
) < 1) {
383 authuser
= sip
->username
;
386 if (auth
->type
== AUTH_TYPE_NTLM
|| auth
->type
== AUTH_TYPE_KERBEROS
) { /* NTLM or Kerberos */
387 gchar
*auth_protocol
= (auth
->type
== AUTH_TYPE_NTLM
? "NTLM" : "Kerberos");
389 // If we have a signature for the message, include that
390 if (msg
->signature
) {
391 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
);
394 if ((auth
->type
== AUTH_TYPE_NTLM
&& auth
->nc
== 3 && auth
->gssapi_data
&& auth
->gssapi_context
== NULL
)
395 || (auth
->type
== AUTH_TYPE_KERBEROS
&& auth
->nc
== 3)) {
399 gssapi_data
= sip_sec_init_context(&(auth
->gssapi_context
),
402 purple_account_get_bool(sip
->account
, "sso", TRUE
),
403 sip
->authdomain
? sip
->authdomain
: "",
408 if (!gssapi_data
|| !auth
->gssapi_context
) {
409 sip
->gc
->wants_to_die
= TRUE
;
410 purple_connection_error(sip
->gc
, _("Failed to authenticate to server"));
414 opaque
= (auth
->type
== AUTH_TYPE_NTLM
? g_strdup_printf(", opaque=\"%s\"", auth
->opaque
) : g_strdup(""));
415 ret
= g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol
, opaque
, auth
->realm
, auth
->target
, gssapi_data
);
421 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol
, auth
->realm
, auth
->target
);
423 } else { /* Digest */
425 /* Calculate new session key */
427 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Digest nonce: %s realm: %s\n", auth
->gssapi_data
, auth
->realm
);
428 auth
->opaque
= purple_cipher_http_digest_calculate_session_key("md5",
429 authuser
, auth
->realm
, sip
->password
,
430 auth
->gssapi_data
, NULL
);
433 sprintf(noncecount
, "%08d", auth
->nc
++);
434 response
= purple_cipher_http_digest_calculate_response("md5",
435 msg
->method
, msg
->target
, NULL
, NULL
,
436 auth
->gssapi_data
, noncecount
, NULL
,
438 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Digest response %s\n", response
);
440 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
);
446 static char *parse_attribute(const char *attrname
, const char *source
)
448 const char *tmp
, *tmp2
;
450 int len
= strlen(attrname
);
452 if (!strncmp(source
, attrname
, len
)) {
454 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
456 retval
= g_strndup(tmp
, tmp2
- tmp
);
458 retval
= g_strdup(tmp
);
464 static void fill_auth(gchar
*hdr
, struct sip_auth
*auth
)
470 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
474 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
475 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type NTLM\n");
476 auth
->type
= AUTH_TYPE_NTLM
;
479 } else if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
480 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type Kerberos\n");
481 auth
->type
= AUTH_TYPE_KERBEROS
;
485 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type Digest\n");
486 auth
->type
= AUTH_TYPE_DIGEST
;
490 parts
= g_strsplit(hdr
, "\", ", 0);
491 for (i
= 0; parts
[i
]; i
++) {
494 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
496 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
497 g_free(auth
->gssapi_data
);
498 auth
->gssapi_data
= tmp
;
500 if (auth
->type
== AUTH_TYPE_NTLM
) {
501 /* NTLM module extracts nonce from gssapi-data */
505 } else if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
506 /* Only used with AUTH_TYPE_DIGEST */
507 g_free(auth
->gssapi_data
);
508 auth
->gssapi_data
= tmp
;
509 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
510 g_free(auth
->opaque
);
512 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
516 if (auth
->type
== AUTH_TYPE_DIGEST
) {
517 /* Throw away old session key */
518 g_free(auth
->opaque
);
523 } else if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
524 g_free(auth
->target
);
533 static void sipe_canwrite_cb(gpointer data
,
534 SIPE_UNUSED_PARAMETER gint source
,
535 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
537 PurpleConnection
*gc
= data
;
538 struct sipe_account_data
*sip
= gc
->proto_data
;
542 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
544 if (max_write
== 0) {
545 if (sip
->tx_handler
!= 0){
546 purple_input_remove(sip
->tx_handler
);
552 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
554 if (written
< 0 && errno
== EAGAIN
)
556 else if (written
<= 0) {
557 /*TODO: do we really want to disconnect on a failure to write?*/
558 purple_connection_error(gc
, _("Could not write"));
562 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
565 static void sipe_canwrite_cb_ssl(gpointer data
,
566 SIPE_UNUSED_PARAMETER gint src
,
567 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
569 PurpleConnection
*gc
= data
;
570 struct sipe_account_data
*sip
= gc
->proto_data
;
574 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
576 if (max_write
== 0) {
577 if (sip
->tx_handler
!= 0) {
578 purple_input_remove(sip
->tx_handler
);
584 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
586 if (written
< 0 && errno
== EAGAIN
)
588 else if (written
<= 0) {
589 /*TODO: do we really want to disconnect on a failure to write?*/
590 purple_connection_error(gc
, _("Could not write"));
594 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
597 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
599 static void send_later_cb(gpointer data
, gint source
,
600 SIPE_UNUSED_PARAMETER
const gchar
*error
)
602 PurpleConnection
*gc
= data
;
603 struct sipe_account_data
*sip
;
604 struct sip_connection
*conn
;
606 if (!PURPLE_CONNECTION_IS_VALID(gc
))
614 purple_connection_error(gc
, _("Could not connect"));
618 sip
= gc
->proto_data
;
620 sip
->connecting
= FALSE
;
621 sip
->last_keepalive
= time(NULL
);
623 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
625 /* If there is more to write now, we need to register a handler */
626 if (sip
->txbuf
->bufused
> 0)
627 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
629 conn
= connection_create(sip
, source
);
630 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
633 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
635 struct sipe_account_data
*sip
;
636 struct sip_connection
*conn
;
638 if (!PURPLE_CONNECTION_IS_VALID(gc
))
640 if (gsc
) purple_ssl_close(gsc
);
644 sip
= gc
->proto_data
;
647 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
648 sip
->connecting
= FALSE
;
649 sip
->last_keepalive
= time(NULL
);
651 conn
= connection_create(sip
, gsc
->fd
);
653 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
658 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
659 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
661 PurpleConnection
*gc
= data
;
662 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
663 if (sip
== NULL
) return;
665 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
667 /* If there is more to write now */
668 if (sip
->txbuf
->bufused
> 0) {
669 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
674 static void sendlater(PurpleConnection
*gc
, const char *buf
)
676 struct sipe_account_data
*sip
= gc
->proto_data
;
678 if (!sip
->connecting
) {
679 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
680 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
681 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
683 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
684 purple_connection_error(gc
, _("Could not create socket"));
687 sip
->connecting
= TRUE
;
690 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
691 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
693 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
696 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
698 struct sipe_account_data
*sip
= gc
->proto_data
;
699 time_t currtime
= time(NULL
);
700 int writelen
= strlen(buf
);
703 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sending - %s######\n%s######\n", ctime(&currtime
), tmp
= fix_newlines(buf
));
705 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
706 if (sendto(sip
->fd
, buf
, writelen
, 0, sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
707 purple_debug_info("sipe", "could not send packet\n");
716 if (sip
->tx_handler
) {
721 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
723 ret
= write(sip
->fd
, buf
, writelen
);
727 if (ret
< 0 && errno
== EAGAIN
)
729 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
734 if (ret
< writelen
) {
735 if (!sip
->tx_handler
){
737 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
740 sip
->tx_handler
= purple_input_add(sip
->fd
,
741 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
746 /* XXX: is it OK to do this? You might get part of a request sent
747 with part of another. */
748 if (sip
->txbuf
->bufused
> 0)
749 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
751 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
757 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
759 sendout_pkt(gc
, buf
);
763 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
765 GSList
*tmp
= msg
->headers
;
768 GString
*outstr
= g_string_new("");
769 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
771 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
772 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
773 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
774 tmp
= g_slist_next(tmp
);
776 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
777 sendout_pkt(sip
->gc
, outstr
->str
);
778 g_string_free(outstr
, TRUE
);
781 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
785 if (sip
->registrar
.type
== AUTH_TYPE_UNSET
) {
789 if (sip
->registrar
.gssapi_context
) {
790 struct sipmsg_breakdown msgbd
;
791 gchar
*signature_input_str
;
793 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
794 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
795 sip
->registrar
.ntlm_num
++;
796 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
797 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
798 if (signature_input_str
!= NULL
) {
799 char *signature_hex
= sip_sec_make_signature(sip
->registrar
.gssapi_context
, signature_input_str
);
800 msg
->signature
= signature_hex
;
801 msg
->rand
= g_strdup(msgbd
.rand
);
802 msg
->num
= g_strdup(msgbd
.num
);
803 g_free(signature_input_str
);
805 sipmsg_breakdown_free(&msgbd
);
808 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
809 buf
= auth_header(sip
, &sip
->registrar
, msg
);
811 sipmsg_add_header_now_pos(msg
, "Authorization", buf
, 5);
814 } 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")) {
815 sip
->registrar
.nc
= 3;
817 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
819 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
822 sip
->registrar
.type
= AUTH_TYPE_KERBEROS
;
827 buf
= auth_header(sip
, &sip
->registrar
, msg
);
828 sipmsg_add_header_now_pos(msg
, "Proxy-Authorization", buf
, 5);
831 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
835 void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
836 const char *text
, const char *body
)
840 GString
*outstr
= g_string_new("");
841 struct sipe_account_data
*sip
= gc
->proto_data
;
844 const gchar
*keepers
[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL
};
846 contact
= get_contact(sip
);
847 sipmsg_add_header(msg
, "Contact", contact
);
852 sprintf(len
, "%" G_GSIZE_FORMAT
, (gsize
) strlen(body
));
853 sipmsg_add_header(msg
, "Content-Length", len
);
855 sipmsg_add_header(msg
, "Content-Length", "0");
858 msg
->response
= code
;
860 sipmsg_strip_headers(msg
, keepers
);
861 sipmsg_merge_new_headers(msg
);
862 sign_outgoing_message(msg
, sip
, msg
->method
);
864 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
867 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
868 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
870 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
871 tmp
= g_slist_next(tmp
);
873 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
874 sendout_pkt(gc
, outstr
->str
);
875 g_string_free(outstr
, TRUE
);
878 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
880 if (sip
->transactions
) {
881 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
882 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip
->transactions
));
884 if (trans
->msg
) sipmsg_free(trans
->msg
);
885 if (trans
->payload
) {
886 (*trans
->payload
->destroy
)(trans
->payload
->data
);
887 g_free(trans
->payload
);
894 static struct transaction
*
895 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
897 gchar
*call_id
= NULL
;
899 struct transaction
*trans
= g_new0(struct transaction
, 1);
901 trans
->time
= time(NULL
);
902 trans
->msg
= (struct sipmsg
*)msg
;
903 call_id
= sipmsg_find_header(trans
->msg
, "Call-ID");
904 cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
905 trans
->key
= g_strdup_printf("<%s><%s>", call_id
, cseq
);
906 trans
->callback
= callback
;
907 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
908 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip
->transactions
));
912 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
914 struct transaction
*trans
;
915 GSList
*transactions
= sip
->transactions
;
916 gchar
*call_id
= sipmsg_find_header(msg
, "Call-ID");
917 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
918 gchar
*key
= g_strdup_printf("<%s><%s>", call_id
, cseq
);
920 while (transactions
) {
921 trans
= transactions
->data
;
922 if (!g_strcasecmp(trans
->key
, key
)) {
926 transactions
= transactions
->next
;
934 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
935 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
936 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
938 struct sipe_account_data
*sip
= gc
->proto_data
;
939 const char *addh
= "";
942 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
943 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
944 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
945 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
946 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
947 gchar
*route
= g_strdup("");
948 gchar
*epid
= get_epid(sip
);
949 int cseq
= dialog
? ++dialog
->cseq
: 1 /* as Call-Id is new in this case */;
950 struct transaction
*trans
= NULL
;
952 if (dialog
&& dialog
->routes
)
954 GSList
*iter
= dialog
->routes
;
959 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
961 iter
= g_slist_next(iter
);
965 if (!ourtag
&& !dialog
) {
969 if (!strcmp(method
, "REGISTER")) {
970 if (sip
->regcallid
) {
972 callid
= g_strdup(sip
->regcallid
);
974 sip
->regcallid
= g_strdup(callid
);
979 if (addheaders
) addh
= addheaders
;
981 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
982 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
983 "From: <sip:%s>%s%s;epid=%s\r\n"
984 "To: <%s>%s%s%s%s\r\n"
985 "Max-Forwards: 70\r\n"
990 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
992 dialog
&& dialog
->request
? dialog
->request
: url
,
993 TRANSPORT_DESCRIPTOR
,
994 purple_network_get_my_ip(-1),
996 branch
? ";branch=" : "",
997 branch
? branch
: "",
999 ourtag
? ";tag=" : "",
1000 ourtag
? ourtag
: "",
1003 theirtag
? ";tag=" : "",
1004 theirtag
? theirtag
: "",
1005 theirepid
? ";epid=" : "",
1006 theirepid
? theirepid
: "",
1009 sipe_get_useragent(sip
),
1013 body
? (gsize
) strlen(body
) : 0,
1017 //printf ("parsing msg buf:\n%s\n\n", buf);
1018 msg
= sipmsg_parse_msg(buf
);
1029 sign_outgoing_message (msg
, sip
, method
);
1031 buf
= sipmsg_to_string (msg
);
1033 /* add to ongoing transactions */
1034 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1035 if (strcmp(method
, "ACK")) {
1036 trans
= transactions_add_buf(sip
, msg
, tc
);
1040 sendout_pkt(gc
, buf
);
1047 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1050 send_soap_request_with_cb(struct sipe_account_data
*sip
,
1053 TransCallback callback
,
1054 struct transaction_payload
*payload
)
1056 gchar
*from
= from0
? g_strdup(from0
) : sip_uri_self(sip
);
1057 gchar
*contact
= get_contact(sip
);
1058 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1059 "Content-Type: application/SOAP+xml\r\n",contact
);
1061 struct transaction
*trans
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1062 trans
->payload
= payload
;
1069 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1071 send_soap_request_with_cb(sip
, NULL
, body
, NULL
, NULL
);
1074 static char *get_contact_register(struct sipe_account_data
*sip
)
1076 char *epid
= get_epid(sip
);
1077 char *uuid
= generateUUIDfromEPID(epid
);
1078 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
);
1084 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1092 if (!sip
->sipdomain
) return;
1094 uri
= sip_uri_from_name(sip
->sipdomain
);
1095 expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
1096 to
= sip_uri_self(sip
);
1097 contact
= get_contact_register(sip
);
1098 hdr
= g_strdup_printf("Contact: %s\r\n"
1099 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1100 "Event: registration\r\n"
1101 "Allow-Events: presence\r\n"
1102 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1103 "%s", contact
, expires
);
1107 sip
->registerstatus
= 1;
1109 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1110 process_register_response
);
1117 static void do_register_cb(struct sipe_account_data
*sip
,
1118 SIPE_UNUSED_PARAMETER
void *unused
)
1120 do_register_exp(sip
, -1);
1121 sip
->reregister_set
= FALSE
;
1124 static void do_register(struct sipe_account_data
*sip
)
1126 do_register_exp(sip
, -1);
1130 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1132 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1133 send_soap_request(sip
, body
);
1138 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1141 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1143 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1146 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1150 void sipe_auth_user_cb(void * data
)
1152 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1155 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1160 void sipe_deny_user_cb(void * data
)
1162 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1165 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1170 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1172 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1173 sipe_contact_allow_deny(sip
, name
, TRUE
);
1177 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1179 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1180 sipe_contact_allow_deny(sip
, name
, FALSE
);
1184 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1186 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1187 sipe_contact_set_acl(sip, name, "");
1191 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1195 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1196 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1198 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1200 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1201 if (!watchers
) return;
1203 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1204 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1205 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1206 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1208 // TODO pull out optional displayName to pass as alias
1210 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1211 job
->who
= remote_user
;
1213 purple_account_request_authorization(
1227 xmlnode_free(watchers
);
1232 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1234 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1235 if (!purple_group
) {
1236 purple_group
= purple_group_new(group
->name
);
1237 purple_blist_add_group(purple_group
, NULL
);
1241 group
->purple_group
= purple_group
;
1242 sip
->groups
= g_slist_append(sip
->groups
, group
);
1243 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1245 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1249 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1251 struct sipe_group
*group
;
1257 entry
= sip
->groups
;
1259 group
= entry
->data
;
1260 if (group
->id
== id
) {
1263 entry
= entry
->next
;
1268 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, const gchar
* name
)
1270 struct sipe_group
*group
;
1276 entry
= sip
->groups
;
1278 group
= entry
->data
;
1279 if (!strcmp(group
->name
, name
)) {
1282 entry
= entry
->next
;
1288 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1291 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1292 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1293 send_soap_request(sip
, body
);
1295 g_free(group
->name
);
1296 group
->name
= g_strdup(name
);
1300 * Only appends if no such value already stored.
1303 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1304 GSList
* res
= list
;
1305 if (!g_slist_find_custom(list
, data
, func
)) {
1306 res
= g_slist_insert_sorted(list
, data
, func
);
1312 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1313 return group1
->id
- group2
->id
;
1317 * Returns string like "2 4 7 8" - group ids buddy belong to.
1320 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1323 //creating array from GList, converting int to gchar*
1324 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1325 GSList
*entry
= buddy
->groups
;
1327 struct sipe_group
* group
= entry
->data
;
1328 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1329 entry
= entry
->next
;
1333 res
= g_strjoinv(" ", ids_arr
);
1334 g_strfreev(ids_arr
);
1339 * Sends buddy update to server
1342 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1344 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1345 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1347 if (buddy
&& purple_buddy
) {
1348 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1350 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1351 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1353 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1354 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1356 send_soap_request(sip
, body
);
1362 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
1364 if (msg
->response
== 200) {
1365 struct sipe_group
*group
;
1366 struct group_user_context
*ctx
= trans
->payload
->data
;
1370 struct sipe_buddy
*buddy
;
1372 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1377 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1383 group_id
= xmlnode_get_data(node
);
1389 group
= g_new0(struct sipe_group
, 1);
1390 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1392 group
->name
= g_strdup(ctx
->group_name
);
1394 sipe_group_add(sip
, group
);
1396 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1398 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1401 sipe_group_set_user(sip
, ctx
->user_name
);
1409 static void sipe_group_context_destroy(gpointer data
)
1411 struct group_user_context
*ctx
= data
;
1412 g_free(ctx
->group_name
);
1413 g_free(ctx
->user_name
);
1417 static void sipe_group_create (struct sipe_account_data
*sip
, const gchar
*name
, const gchar
* who
)
1419 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1420 struct group_user_context
*ctx
= g_new0(struct group_user_context
, 1);
1422 ctx
->group_name
= g_strdup(name
);
1423 ctx
->user_name
= g_strdup(who
);
1424 payload
->destroy
= sipe_group_context_destroy
;
1425 payload
->data
= ctx
;
1427 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1428 send_soap_request_with_cb(sip
, NULL
, body
, process_add_group_response
, payload
);
1433 * Data structure for scheduled actions
1436 struct scheduled_action
{
1439 * Format is <Event>[<Data>...]
1440 * Example: <presence><sip:user@domain.com> or <registration>
1443 guint timeout_handler
;
1444 gboolean repetitive
;
1446 GDestroyNotify destroy
;
1447 struct sipe_account_data
*sip
;
1453 * Should return FALSE if repetitive action is not needed
1455 static gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1458 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1459 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1460 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1461 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1462 ret
= sched_action
->repetitive
;
1463 if (sched_action
->destroy
) {
1464 (*sched_action
->destroy
)(sched_action
->payload
);
1466 g_free(sched_action
->name
);
1467 g_free(sched_action
);
1472 * Kills action timer effectively cancelling
1475 * @param name of action
1477 static void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, const gchar
*name
)
1481 if (!sip
->timeouts
|| !name
) return;
1483 entry
= sip
->timeouts
;
1485 struct scheduled_action
*sched_action
= entry
->data
;
1486 if(!strcmp(sched_action
->name
, name
)) {
1487 GSList
*to_delete
= entry
;
1488 entry
= entry
->next
;
1489 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1490 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1491 purple_timeout_remove(sched_action
->timeout_handler
);
1492 if (sched_action
->destroy
) {
1493 (*sched_action
->destroy
)(sched_action
->payload
);
1495 g_free(sched_action
->name
);
1496 g_free(sched_action
);
1498 entry
= entry
->next
;
1504 sipe_schedule_action0(const gchar
*name
,
1508 GDestroyNotify destroy
,
1509 struct sipe_account_data
*sip
,
1512 struct scheduled_action
*sched_action
;
1514 /* Make sure each action only exists once */
1515 sipe_cancel_scheduled_action(sip
, name
);
1517 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name
, timeout
, isSeconds
? "sec" : "msec");
1518 sched_action
= g_new0(struct scheduled_action
, 1);
1519 sched_action
->repetitive
= FALSE
;
1520 sched_action
->name
= g_strdup(name
);
1521 sched_action
->action
= action
;
1522 sched_action
->destroy
= destroy
;
1523 sched_action
->sip
= sip
;
1524 sched_action
->payload
= payload
;
1525 sched_action
->timeout_handler
= isSeconds
? purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
) :
1526 purple_timeout_add(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1527 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1528 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1532 sipe_schedule_action(const gchar
*name
,
1535 GDestroyNotify destroy
,
1536 struct sipe_account_data
*sip
,
1539 sipe_schedule_action0(name
, timeout
, TRUE
, action
, destroy
, sip
, payload
);
1543 * Same as sipe_schedule_action() but timeout is in milliseconds.
1546 sipe_schedule_action_msec(const gchar
*name
,
1549 GDestroyNotify destroy
,
1550 struct sipe_account_data
*sip
,
1553 sipe_schedule_action0(name
, timeout
, FALSE
, action
, destroy
, sip
, payload
);
1557 sipe_sched_calendar_status_update(struct sipe_account_data
*sip
,
1558 time_t calculate_from
);
1561 sipe_get_availability_by_status(const char* sipe_status_id
, char** activity_token
);
1564 sipe_get_status_by_availability(int avail
,
1565 const char* activity
);
1568 sipe_apply_calendar_status(struct sipe_account_data
*sip
,
1569 struct sipe_buddy
*sbuddy
,
1570 const char *status_id
)
1572 time_t cal_avail_since
;
1573 int cal_status
= sipe_cal_get_status(sbuddy
, time(NULL
), &cal_avail_since
);
1575 gchar
*self_uri
= sip_uri_self(sip
);
1577 if (!sbuddy
) return;
1579 if (cal_status
< SIPE_CAL_NO_DATA
) {
1580 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status
, sbuddy
->name
);
1581 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since
)));
1584 /* scheduled Cal update call */
1586 status_id
= sbuddy
->last_non_cal_status_id
;
1587 g_free(sbuddy
->activity
);
1588 sbuddy
->activity
= g_strdup(sbuddy
->last_non_cal_activity
);
1591 /* adjust to calendar status */
1592 if (cal_status
!= SIPE_CAL_NO_DATA
) {
1593 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy
->user_avail_since
)));
1595 if (cal_status
== SIPE_CAL_BUSY
1596 && cal_avail_since
> sbuddy
->user_avail_since
1597 && 6500 >= sipe_get_availability_by_status(status_id
, NULL
))
1599 status_id
= SIPE_STATUS_ID_IN_MEETING
;
1600 g_free(sbuddy
->activity
);
1601 sbuddy
->activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING
));
1603 avail
= sipe_get_availability_by_status(status_id
, NULL
);
1605 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy
->activity_since
)));
1606 if (cal_avail_since
> sbuddy
->activity_since
) {
1607 if (cal_status
== SIPE_CAL_OOF
1608 && avail
>= 15000) /* 12000 in 2007 */
1610 g_free(sbuddy
->activity
);
1611 sbuddy
->activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF
));
1616 /* then set status_id actually */
1617 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id
, sbuddy
->name
);
1618 purple_prpl_got_user_status(sip
->account
, sbuddy
->name
, status_id
, NULL
);
1620 /* set our account state to the one in roaming (including calendar info) */
1621 if (!strcmp(sbuddy
->name
, self_uri
)) {
1622 PurpleStatus
*status
= purple_account_get_active_status(sip
->account
);
1623 const gchar
*curr_note
= purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
);
1625 g_free(sip
->status
);
1626 if (strcmp(status_id
, SIPE_STATUS_ID_OFFLINE
)) { /* not offline */
1627 sip
->status
= g_strdup(status_id
);
1629 sip
->status
= g_strdup(SIPE_STATUS_ID_INVISIBLE
); /* not not let offline status switch us off */
1632 purple_debug_info("sipe", "sipe_got_user_status: to %s for the account\n", sip
->status
);
1633 purple_prpl_got_account_status(sip
->account
, sip
->status
,
1634 SIPE_STATUS_ATTR_ID_MESSAGE
, curr_note
,
1635 SIPE_STATUS_ATTR_ID_DONT_PUB
, TRUE
,
1642 sipe_got_user_status(struct sipe_account_data
*sip
,
1644 const char *status_id
)
1646 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
1648 if (!sbuddy
) return;
1650 /* Check if on 2005 system contact's calendar,
1651 * then set/preserve it.
1653 if (!sip
->ocs2007
) {
1654 sipe_apply_calendar_status(sip
, sbuddy
, status_id
);
1656 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
1661 update_calendar_status_cb(SIPE_UNUSED_PARAMETER
char *name
,
1662 struct sipe_buddy
*sbuddy
,
1663 struct sipe_account_data
*sip
)
1665 sipe_apply_calendar_status(sip
, sbuddy
, NULL
);
1669 * Updates contact's status
1670 * based on their calendar information.
1672 * Applicability: 2005 systems
1675 update_calendar_status(struct sipe_account_data
*sip
)
1677 purple_debug_info("sipe", "update_calendar_status() started.\n");
1678 g_hash_table_foreach(sip
->buddies
, (GHFunc
)update_calendar_status_cb
, (gpointer
)sip
);
1680 /* repeat scheduling */
1681 sipe_sched_calendar_status_update(sip
, time(NULL
) + 3*60 /* 3 min */);
1685 * Schedules process of contacts' status update
1686 * based on their calendar information.
1687 * Should be scheduled to the beginning of every
1688 * 15 min interval, like:
1689 * 13:00, 13:15, 13:30, 13:45, etc.
1691 * Applicability: 2005 systems
1694 sipe_sched_calendar_status_update(struct sipe_account_data
*sip
,
1695 time_t calculate_from
)
1697 int interval
= 15*60;
1698 /** start of the beginning of closest 15 min interval. */
1699 time_t next_start
= ((time_t)((int)((int)calculate_from
)/interval
+ 1)*interval
);
1701 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1702 asctime(localtime(&calculate_from
)));
1703 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1704 asctime(localtime(&next_start
)));
1706 sipe_schedule_action("<+2005-cal-status>",
1707 (int)(next_start
- time(NULL
)),
1708 (Action
)update_calendar_status
,
1715 * Schedules process of self status publish
1716 * based on own calendar information.
1717 * Should be scheduled to the beginning of every
1718 * 15 min interval, like:
1719 * 13:00, 13:15, 13:30, 13:45, etc.
1721 * Applicability: 2007+ systems
1724 sipe_sched_calendar_status_self_publish(struct sipe_account_data
*sip
,
1725 time_t calculate_from
)
1727 int interval
= 5*60;
1728 /** start of the beginning of closest 5 min interval. */
1729 time_t next_start
= ((time_t)((int)((int)calculate_from
)/interval
+ 1)*interval
);
1731 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1732 asctime(localtime(&calculate_from
)));
1733 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1734 asctime(localtime(&next_start
)));
1736 sipe_schedule_action("<+2007-cal-status>",
1737 (int)(next_start
- time(NULL
)),
1738 (Action
)publish_calendar_status_self
,
1744 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1746 /** Should be g_free()'d
1749 sipe_get_subscription_key(gchar
*event
,
1754 if (is_empty(event
)) return NULL
;
1756 if (event
&& !g_ascii_strcasecmp(event
, "presence")) {
1757 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1758 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, with
);
1760 /* @TODO drop participated buddies' just_added flag */
1762 /* Subscription is identified by <event> key */
1763 key
= g_strdup_printf("<%s>", event
);
1769 gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
1770 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
1772 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
1773 gchar
*event
= sipmsg_find_header(msg
, "Event");
1776 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1778 struct sipmsg
*request_msg
= trans
->msg
;
1779 event
= sipmsg_find_header(request_msg
, "Event");
1782 key
= sipe_get_subscription_key(event
, with
);
1784 /* 200 OK; 481 Call Leg Does Not Exist */
1785 if (key
&& (msg
->response
== 200 || msg
->response
== 481)) {
1786 if (g_hash_table_lookup(sip
->subscriptions
, key
)) {
1787 g_hash_table_remove(sip
->subscriptions
, key
);
1788 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key
);
1792 /* create/store subscription dialog if not yet */
1793 if (msg
->response
== 200) {
1794 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
1795 gchar
*cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
1798 struct sip_subscription
*subscription
= g_new0(struct sip_subscription
, 1);
1799 g_hash_table_insert(sip
->subscriptions
, g_strdup(key
), subscription
);
1801 subscription
->dialog
.callid
= g_strdup(callid
);
1802 subscription
->dialog
.cseq
= atoi(cseq
);
1803 subscription
->dialog
.with
= g_strdup(with
);
1804 subscription
->event
= g_strdup(event
);
1805 sipe_dialog_parse(&subscription
->dialog
, msg
, TRUE
);
1807 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key
);
1816 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1818 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1823 static void sipe_subscribe_resource_uri(const char *name
,
1824 SIPE_UNUSED_PARAMETER gpointer value
,
1825 gchar
**resources_uri
)
1827 gchar
*tmp
= *resources_uri
;
1828 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1832 static void sipe_subscribe_resource_uri_with_context(const char *name
, gpointer value
, gchar
**resources_uri
)
1834 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
1835 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
1836 gchar
*tmp
= *resources_uri
;
1838 if (sbuddy
) sbuddy
->just_added
= FALSE
; /* should be enought to include context one time */
1840 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp
, name
, context
);
1845 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1846 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1847 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1848 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1849 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1852 static void sipe_subscribe_presence_batched_to(struct sipe_account_data
*sip
, gchar
*resources_uri
, gchar
*to
)
1855 gchar
*contact
= get_contact(sip
);
1858 gchar
*require
= "";
1860 gchar
*autoextend
= "";
1861 gchar
*content_type
;
1862 struct sip_dialog
*dialog
;
1865 require
= ", categoryList";
1866 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1867 content_type
= "application/msrtc-adrl-categorylist+xml";
1868 content
= g_strdup_printf(
1869 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1870 "<action name=\"subscribe\" id=\"63792024\">\n"
1871 "<adhocList>\n%s</adhocList>\n"
1872 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1873 "<category name=\"calendarData\"/>\n"
1874 "<category name=\"contactCard\"/>\n"
1875 "<category name=\"note\"/>\n"
1876 "<category name=\"state\"/>\n"
1879 "</batchSub>", sip
->username
, resources_uri
);
1881 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1882 content_type
= "application/adrl+xml";
1883 content
= g_strdup_printf(
1884 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1885 "<create xmlns=\"\">\n%s</create>\n"
1886 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1888 g_free(resources_uri
);
1890 request
= g_strdup_printf(
1891 "Require: adhoclist%s\r\n"
1892 "Supported: eventlist\r\n"
1893 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1894 "Supported: ms-piggyback-first-notify\r\n"
1895 "%sSupported: ms-benotify\r\n"
1896 "Proxy-Require: ms-benotify\r\n"
1897 "Event: presence\r\n"
1898 "Content-Type: %s\r\n"
1899 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
1902 /* subscribe to buddy presence */
1903 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1904 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, to
);
1905 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
1906 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
1908 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, dialog
, process_subscribe_response
);
1916 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
,
1917 SIPE_UNUSED_PARAMETER
void *unused
)
1919 gchar
*to
= sip_uri_self(sip
);
1920 gchar
*resources_uri
= g_strdup("");
1922 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
1924 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1927 sipe_subscribe_presence_batched_to(sip
, resources_uri
, to
);
1930 struct presence_batched_routed
{
1935 static void sipe_subscribe_presence_batched_routed_free(void *payload
)
1937 struct presence_batched_routed
*data
= payload
;
1938 GSList
*buddies
= data
->buddies
;
1940 g_free(buddies
->data
);
1941 buddies
= buddies
->next
;
1943 g_slist_free(data
->buddies
);
1948 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data
*sip
, void *payload
)
1950 struct presence_batched_routed
*data
= payload
;
1951 GSList
*buddies
= data
->buddies
;
1952 gchar
*resources_uri
= g_strdup("");
1954 gchar
*tmp
= resources_uri
;
1955 resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, (char *) buddies
->data
);
1957 buddies
= buddies
->next
;
1959 sipe_subscribe_presence_batched_to(sip
, resources_uri
,
1960 g_strdup(data
->host
));
1964 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1965 * The user sends a single SUBSCRIBE request to the subscribed contact.
1966 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1970 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, void *buddy_name
)
1974 gchar
*to
= sip_uri((char *)buddy_name
);
1975 gchar
*tmp
= get_contact(sip
);
1977 gchar
*content
= NULL
;
1978 gchar
*autoextend
= "";
1979 gchar
*content_type
= "";
1980 struct sip_dialog
*dialog
;
1981 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, to
);
1982 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
1984 if (sbuddy
) sbuddy
->just_added
= FALSE
;
1987 content_type
= "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1989 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1992 request
= g_strdup_printf(
1993 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1994 "Supported: ms-piggyback-first-notify\r\n"
1995 "%s%sSupported: ms-benotify\r\n"
1996 "Proxy-Require: ms-benotify\r\n"
1997 "Event: presence\r\n"
1998 "Contact: %s\r\n", autoextend
, content_type
, tmp
);
2001 content
= g_strdup_printf(
2002 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2003 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2004 "<resource uri=\"%s\"%s\n"
2006 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2007 "<category name=\"calendarData\"/>\n"
2008 "<category name=\"contactCard\"/>\n"
2009 "<category name=\"note\"/>\n"
2010 "<category name=\"state\"/>\n"
2013 "</batchSub>", sip
->username
, to
, context
);
2018 /* subscribe to buddy presence */
2019 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2020 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, to
);
2021 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
2022 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
2024 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, dialog
, process_subscribe_response
);
2032 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
2034 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status
));
2036 if (!purple_status_is_active(status
))
2040 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
2044 const char* status_id
= purple_status_get_id(status
);
2045 gboolean dont_pub
= purple_status_get_attr_boolean(status
, SIPE_STATUS_ATTR_ID_DONT_PUB
);
2049 purple_status_set_attr_boolean(status
, SIPE_STATUS_ATTR_ID_DONT_PUB
, FALSE
);
2050 purple_debug_info("sipe", "sipe_set_status: status¬e has NOT changed, exiting.\n");
2054 g_free(sip
->status
);
2055 sip
->status
= g_strdup(status_id
);
2057 sip
->note
= g_strdup(purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
));
2059 /* schedule 2 sec to capture idle flag */
2060 action_name
= g_strdup_printf("<%s>", "+set-status");
2061 sipe_schedule_action(action_name
, 2, (Action
)send_presence_status
, NULL
, sip
, NULL
);
2062 g_free(action_name
);
2067 sipe_set_idle(PurpleConnection
* gc
,
2070 purple_debug_info("sipe", "sipe_set_idle: time=%d\n", time
);
2073 struct sipe_account_data
*sip
= gc
->proto_data
;
2076 sip
->was_idle
= sip
->is_idle
;
2077 sip
->is_idle
= (time
> 0);
2083 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
,
2084 SIPE_UNUSED_PARAMETER
const char *alias
)
2086 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2087 sipe_group_set_user(sip
, name
);
2091 sipe_group_buddy(PurpleConnection
*gc
,
2093 const char *old_group_name
,
2094 const char *new_group_name
)
2096 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2097 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
2098 struct sipe_group
* old_group
= NULL
;
2099 struct sipe_group
* new_group
;
2101 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2102 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
2104 if(!buddy
) { // buddy not in roaming list
2108 if (old_group_name
) {
2109 old_group
= sipe_group_find_by_name(sip
, old_group_name
);
2111 new_group
= sipe_group_find_by_name(sip
, new_group_name
);
2114 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
2115 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
2119 sipe_group_create(sip
, new_group_name
, who
);
2121 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
2122 sipe_group_set_user(sip
, who
);
2126 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
2128 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
2130 /* libpurple can call us with undefined buddy or group */
2131 if (buddy
&& group
) {
2132 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2134 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2135 gchar
*buddy_name
= g_ascii_strdown(buddy
->name
, -1);
2136 purple_blist_rename_buddy(buddy
, buddy_name
);
2139 /* Prepend sip: if needed */
2140 if (strncmp("sip:", buddy
->name
, 4)) {
2141 gchar
*buf
= sip_uri_from_name(buddy
->name
);
2142 purple_blist_rename_buddy(buddy
, buf
);
2146 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
2147 struct sipe_buddy
*b
= g_new0(struct sipe_buddy
, 1);
2148 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy
->name
);
2149 b
->name
= g_strdup(buddy
->name
);
2150 b
->just_added
= TRUE
;
2151 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
2152 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
2153 /* @TODO should go to callback */
2154 sipe_subscribe_presence_single(sip
, b
->name
);
2156 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy
->name
);
2161 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
2165 * We are calling g_hash_table_foreach_steal(). That means that no
2166 * key/value deallocation functions are called. Therefore the glib
2167 * hash code does not touch the key (buddy->name) or value (buddy)
2168 * of the to-be-deleted hash node at all. It follows that we
2170 * - MUST free the memory for the key ourselves and
2171 * - ARE allowed to do it in this function
2173 * Conclusion: glib must be broken on the Windows platform if sipe
2174 * crashes with SIGTRAP when closing. You'll have to live
2175 * with the memory leak until this is fixed.
2177 g_free(buddy
->name
);
2179 g_free(buddy
->activity
);
2180 g_free(buddy
->meeting_subject
);
2181 g_free(buddy
->meeting_location
);
2182 g_free(buddy
->annotation
);
2184 g_free(buddy
->cal_start_time
);
2185 g_free(buddy
->cal_free_busy_base64
);
2186 g_free(buddy
->cal_free_busy
);
2187 g_free(buddy
->last_non_cal_activity
);
2189 sipe_cal_free_working_hours(buddy
->cal_working_hours
);
2191 g_free(buddy
->device_name
);
2192 g_slist_free(buddy
->groups
);
2197 * Unassociates buddy from group first.
2198 * Then see if no groups left, removes buddy completely.
2199 * Otherwise updates buddy groups on server.
2201 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
2203 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2204 struct sipe_buddy
*b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
2205 struct sipe_group
*g
= NULL
;
2207 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
2212 g
= sipe_group_find_by_name(sip
, group
->name
);
2216 b
->groups
= g_slist_remove(b
->groups
, g
);
2217 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
2220 if (g_slist_length(b
->groups
) < 1) {
2221 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
2222 sipe_cancel_scheduled_action(sip
, action_name
);
2223 g_free(action_name
);
2225 g_hash_table_remove(sip
->buddies
, buddy
->name
);
2228 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
2229 send_soap_request(sip
, body
);
2235 //updates groups on server
2236 sipe_group_set_user(sip
, b
->name
);
2242 sipe_rename_group(PurpleConnection
*gc
,
2243 const char *old_name
,
2245 SIPE_UNUSED_PARAMETER GList
*moved_buddies
)
2247 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2248 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, old_name
);
2250 sipe_group_rename(sip
, s_group
, group
->name
);
2252 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
2257 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
2259 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2260 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
2263 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
2264 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
2265 send_soap_request(sip
, body
);
2268 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
2269 g_free(s_group
->name
);
2272 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
2276 /** All statuses need message attribute to pass Note */
2277 static GList
*sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount
*acc
)
2279 PurpleStatusType
*type
;
2280 GList
*types
= NULL
;
2282 /* Macros to reduce code repetition.
2283 Translators: noun */
2284 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2286 TRUE, user, FALSE, \
2287 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2288 SIPE_STATUS_ATTR_ID_DONT_PUB, _("Don't publish"), purple_value_new(PURPLE_TYPE_BOOLEAN), \
2290 types = g_list_append(types, type);
2293 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE
,
2299 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2300 sipe_activity_map
[SIPE_ACTIVITY_BUSY
].status_id
,
2301 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY
),
2304 /* BusyIdle (not user settable) */
2305 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2306 sipe_activity_map
[SIPE_ACTIVITY_BUSYIDLE
].status_id
,
2307 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE
),
2310 /* Do Not Disturb */
2311 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2312 sipe_activity_map
[SIPE_ACTIVITY_DND
].status_id
,
2316 /* In a meeting (not user settable) */
2317 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2318 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].status_id
,
2319 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING
),
2322 /* In a conference (not user settable) */
2323 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2324 sipe_activity_map
[SIPE_ACTIVITY_IN_CONF
].status_id
,
2325 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF
),
2329 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2330 sipe_activity_map
[SIPE_ACTIVITY_BRB
].status_id
,
2331 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB
),
2335 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2340 /* On The Phone (not user settable) */
2341 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2342 sipe_activity_map
[SIPE_ACTIVITY_ON_PHONE
].status_id
,
2343 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE
),
2346 /* Out To Lunch (not user settable) */
2347 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2348 sipe_activity_map
[SIPE_ACTIVITY_LUNCH
].status_id
,
2349 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH
),
2352 /* Idle/Inactive (not user settable) */
2353 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE
,
2354 sipe_activity_map
[SIPE_ACTIVITY_INACTIVE
].status_id
,
2355 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE
),
2358 /* Appear Offline */
2359 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE
,
2364 /* Offline (not user settable) */
2365 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE
,
2374 * A callback for g_hash_table_foreach
2377 sipe_buddy_subscribe_cb(char *buddy_name
,
2378 SIPE_UNUSED_PARAMETER
struct sipe_buddy
*buddy
,
2379 struct sipe_account_data
*sip
)
2381 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy_name
);
2382 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2383 guint time_range
= (g_hash_table_size(sip
->buddies
) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2384 guint timeout
= ((guint
) rand()) / (RAND_MAX
/ time_range
) + 1; /* random period within the range but never 0! */
2386 sipe_schedule_action_msec(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(buddy_name
));
2387 g_free(action_name
);
2391 * Removes entries from purple buddy list
2392 * that does not correspond ones in the roaming contact list.
2394 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
2395 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
2396 GSList
*entry
= buddies
;
2397 struct sipe_buddy
*buddy
;
2401 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
2402 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
2405 g
= purple_buddy_get_group(b
);
2406 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
2408 gboolean in_sipe_groups
= FALSE
;
2409 GSList
*entry2
= buddy
->groups
;
2411 struct sipe_group
*group
= entry2
->data
;
2412 if (!strcmp(group
->name
, g
->name
)) {
2413 in_sipe_groups
= TRUE
;
2416 entry2
= entry2
->next
;
2418 if(!in_sipe_groups
) {
2419 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
2420 purple_blist_remove_buddy(b
);
2423 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
2424 purple_blist_remove_buddy(b
);
2426 entry
= entry
->next
;
2428 g_slist_free(buddies
);
2431 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2433 int len
= msg
->bodylen
;
2435 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
2438 const gchar
*contacts_delta
;
2439 xmlnode
*group_node
;
2440 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
2444 /* Convert the contact from XML to Purple Buddies */
2445 isc
= xmlnode_from_str(msg
->body
, len
);
2450 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
2451 if (contacts_delta
) {
2452 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2455 if (!strcmp(isc
->name
, "contactList")) {
2458 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
2459 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2460 const char *name
= xmlnode_get_attrib(group_node
, "name");
2462 if (!strncmp(name
, "~", 1)) {
2463 name
= _("Other Contacts");
2465 group
->name
= g_strdup(name
);
2466 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
2468 sipe_group_add(sip
, group
);
2471 // Make sure we have at least one group
2472 if (g_slist_length(sip
->groups
) == 0) {
2473 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2474 PurpleGroup
*purple_group
;
2475 group
->name
= g_strdup(_("Other Contacts"));
2477 purple_group
= purple_group_new(group
->name
);
2478 purple_blist_add_group(purple_group
, NULL
);
2479 sip
->groups
= g_slist_append(sip
->groups
, group
);
2482 /* Parse contacts */
2483 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
2484 const gchar
*uri
= xmlnode_get_attrib(item
, "uri");
2485 const gchar
*name
= xmlnode_get_attrib(item
, "name");
2487 struct sipe_buddy
*buddy
= NULL
;
2489 gchar
**item_groups
;
2492 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2493 tmp
= sip_uri_from_name(uri
);
2494 buddy_name
= g_ascii_strdown(tmp
, -1);
2497 /* assign to group Other Contacts if nothing else received */
2498 tmp
= g_strdup(xmlnode_get_attrib(item
, "groups"));
2499 if(!tmp
|| !strcmp("", tmp
) ) {
2500 struct sipe_group
*group
= sipe_group_find_by_name(sip
, _("Other Contacts"));
2502 tmp
= group
? g_strdup_printf("%d", group
->id
) : g_strdup("1");
2504 item_groups
= g_strsplit(tmp
, " ", 0);
2507 while (item_groups
[i
]) {
2508 struct sipe_group
*group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
2510 // If couldn't find the right group for this contact, just put them in the first group we have
2511 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2512 group
= sip
->groups
->data
;
2515 if (group
!= NULL
) {
2516 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2518 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2519 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2521 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name
, uri
);
2524 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2525 if (name
!= NULL
&& strlen(name
) != 0) {
2526 purple_blist_alias_buddy(b
, name
);
2528 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name
, name
);
2533 buddy
= g_new0(struct sipe_buddy
, 1);
2534 buddy
->name
= g_strdup(b
->name
);
2535 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2538 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2540 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2542 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2547 } // while, contact groups
2548 g_strfreev(item_groups
);
2553 sipe_cleanup_local_blist(sip
);
2555 /* Add self-contact if not there yet. 2005 systems. */
2556 /* This will resemble subscription to roaming_self in 2007 systems */
2557 if (!sip
->ocs2007
) {
2558 gchar
*self_uri
= sip_uri_self(sip
);
2559 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, self_uri
);
2562 buddy
= g_new0(struct sipe_buddy
, 1);
2563 buddy
->name
= g_strdup(self_uri
);
2564 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2571 /* subscribe to buddies */
2572 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2573 if (sip
->batched_support
) {
2574 sipe_subscribe_presence_batched(sip
, NULL
);
2576 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
2578 sip
->subscribed_buddies
= TRUE
;
2580 /* for 2005 systems schedule contacts' status update
2581 * based on their calendar information
2583 if (!sip
->ocs2007
) {
2584 sipe_sched_calendar_status_update(sip
, time(NULL
));
2591 * Subscribe roaming contacts
2593 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
)
2595 gchar
*to
= sip_uri_self(sip
);
2596 gchar
*tmp
= get_contact(sip
);
2597 gchar
*hdr
= g_strdup_printf(
2598 "Event: vnd-microsoft-roaming-contacts\r\n"
2599 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2600 "Supported: com.microsoft.autoextend\r\n"
2601 "Supported: ms-benotify\r\n"
2602 "Proxy-Require: ms-benotify\r\n"
2603 "Supported: ms-piggyback-first-notify\r\n"
2604 "Contact: %s\r\n", tmp
);
2607 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2612 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
,
2613 SIPE_UNUSED_PARAMETER
void *unused
)
2616 struct sip_dialog
*dialog
;
2617 gchar
*to
= sip_uri_self(sip
);
2618 gchar
*tmp
= get_contact(sip
);
2619 gchar
*hdr
= g_strdup_printf(
2620 "Event: presence.wpending\r\n"
2621 "Accept: text/xml+msrtc.wpending\r\n"
2622 "Supported: com.microsoft.autoextend\r\n"
2623 "Supported: ms-benotify\r\n"
2624 "Proxy-Require: ms-benotify\r\n"
2625 "Supported: ms-piggyback-first-notify\r\n"
2626 "Contact: %s\r\n", tmp
);
2629 /* Subscription is identified by <event> key */
2630 key
= g_strdup_printf("<%s>", "presence.wpending");
2631 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
2632 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
2634 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", dialog
, process_subscribe_response
);
2642 * Fires on deregistration event initiated by server.
2643 * [MS-SIPREGE] SIP extension.
2648 // Content-Type: text/registration-event
2649 // subscription-state: terminated;expires=0
2650 // ms-diagnostics-public: 4141;reason="User disabled"
2652 // deregistered;event=rejected
2654 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2656 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2657 gchar
*event
= NULL
;
2658 gchar
*reason
= NULL
;
2659 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
2661 warning
= warning
? warning
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2662 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2664 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2665 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2666 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2667 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2669 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2673 if (warning
!= NULL
) {
2674 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
2675 } else { // for LCS2005
2677 if (event
&& !g_ascii_strcasecmp(event
, "unregistered")) {
2678 error_id
= 4140; // [MS-SIPREGE]
2679 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2680 reason
= g_strdup(_("you are already signed in at another location"));
2681 } else if (event
&& !g_ascii_strcasecmp(event
, "rejected")) {
2683 reason
= g_strdup(_("user disabled")); // [MS-OCER]
2684 } else if (event
&& !g_ascii_strcasecmp(event
, "deactivated")) {
2686 reason
= g_strdup(_("user moved")); // [MS-OCER]
2690 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), reason
? reason
: _("no reason given"));
2693 sip
->gc
->wants_to_die
= TRUE
;
2694 purple_connection_error(sip
->gc
, warning
);
2699 static void sipe_process_provisioning_v2(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2701 xmlnode
*xn_provision_group_list
;
2704 xn_provision_group_list
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2706 /* provisionGroup */
2707 for (node
= xmlnode_get_child(xn_provision_group_list
, "provisionGroup"); node
; node
= xmlnode_get_next_twin(node
)) {
2708 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node
, "name"))) {
2709 g_free(sip
->focus_factory_uri
);
2710 sip
->focus_factory_uri
= xmlnode_get_data(xmlnode_get_child(node
, "focusFactoryUri"));
2711 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2712 sip
->focus_factory_uri
? sip
->focus_factory_uri
: "");
2716 xmlnode_free(xn_provision_group_list
);
2719 /** for 2005 system */
2721 sipe_process_provisioning(struct sipe_account_data
*sip
,
2724 xmlnode
*xn_provision
;
2727 xn_provision
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2728 if ((node
= xmlnode_get_child(xn_provision
, "user"))) {
2729 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node
, "uri"));
2730 if ((node
= xmlnode_get_child(node
, "line"))) {
2731 const gchar
*line_uri
= xmlnode_get_attrib(node
, "uri");
2732 const gchar
*server
= xmlnode_get_attrib(node
, "server");
2733 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri
, server
);
2734 sip_csta_open(sip
, line_uri
, server
);
2737 xmlnode_free(xn_provision
);
2740 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2742 const gchar
*contacts_delta
;
2745 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2751 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2754 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2761 free_container(struct sipe_container
*container
)
2765 if (!container
) return;
2767 entry
= container
->members
;
2769 g_free(entry
->data
);
2770 entry
= g_slist_remove(entry
, entry
->data
);
2776 * Finds locally stored MS-PRES container member
2778 static struct sipe_container_member
*
2779 sipe_find_container_member(struct sipe_container
*container
,
2783 struct sipe_container_member
*member
;
2786 if (container
== NULL
|| type
== NULL
) {
2790 entry
= container
->members
;
2792 member
= entry
->data
;
2793 if (!g_strcasecmp(member
->type
, type
)
2794 && ((!member
->value
&& !value
)
2795 || (value
&& member
->value
&& !g_strcasecmp(member
->value
, value
)))
2799 entry
= entry
->next
;
2805 * Finds locally stored MS-PRES container by id
2807 static struct sipe_container
*
2808 sipe_find_container(struct sipe_account_data
*sip
,
2811 struct sipe_container
*container
;
2818 entry
= sip
->containers
;
2820 container
= entry
->data
;
2821 if (id
== container
->id
) {
2824 entry
= entry
->next
;
2838 sipe_find_access_level(struct sipe_account_data
*sip
,
2842 guint containers
[] = {32000, 400, 300, 200, 100};
2845 for (i
= 0; i
< 5; i
++) {
2846 struct sipe_container_member
*member
;
2847 struct sipe_container
*container
= sipe_find_container(sip
, containers
[i
]);
2848 if (!container
) continue;
2850 member
= sipe_find_container_member(container
, type
, value
);
2852 return containers
[i
];
2860 sipe_send_set_container_members(struct sipe_account_data
*sip
,
2862 guint container_version
,
2863 const gchar
* action
,
2867 gchar
*self
= sip_uri_self(sip
);
2868 gchar
*value_str
= value
? g_strdup_printf(" value=\"%s\"", value
) : g_strdup("");
2871 gchar
*body
= g_strdup_printf(
2872 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2873 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2874 "</setContainerMembers>",
2882 contact
= get_contact(sip
);
2883 hdr
= g_strdup_printf("Contact: %s\r\n"
2884 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact
);
2887 send_sip_request(sip
->gc
, "SERVICE", self
, self
, hdr
, body
, NULL
, NULL
);
2895 free_publication(struct sipe_publication
*publication
)
2897 g_free(publication
->category
);
2898 g_free(publication
->cal_event_hash
);
2899 g_free(publication
->note
);
2901 g_free(publication
->working_hours_xml_str
);
2902 g_free(publication
->fb_start_str
);
2903 g_free(publication
->free_busy_base64
);
2905 g_free(publication
);
2908 /* key is <category><instance><container> */
2910 sipe_is_our_publication(struct sipe_account_data
*sip
,
2915 /* filling keys for our publications if not yet cached */
2916 if (!sip
->our_publication_keys
) {
2917 guint device_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_DEVICE
);
2918 guint machine_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_MACHINE
);
2919 guint user_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_USER
);
2920 guint calendar_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR
);
2921 guint cal_oof_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR_OOF
);
2922 guint cal_data_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_CALENDAR_DATA
);
2923 guint note_oof_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_NOTE_OOF
);
2926 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2927 g_strdup_printf("<%s><%u><%u>", "device", device_instance
, 2));
2929 /* state:machineState */
2930 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2931 g_strdup_printf("<%s><%u><%u>", "state", machine_instance
, 2));
2932 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2933 g_strdup_printf("<%s><%u><%u>", "state", machine_instance
, 3));
2935 /* state:userState */
2936 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2937 g_strdup_printf("<%s><%u><%u>", "state", user_instance
, 2));
2938 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2939 g_strdup_printf("<%s><%u><%u>", "state", user_instance
, 3));
2941 /* state:calendarState */
2942 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2943 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance
, 2));
2944 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2945 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance
, 3));
2947 /* state:calendarState OOF */
2948 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2949 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance
, 2));
2950 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2951 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance
, 3));
2954 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2955 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2956 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2957 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2958 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2959 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2962 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2963 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 200));
2964 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2965 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 300));
2966 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2967 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 400));
2969 /* calendarData:WorkingHours */
2970 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2971 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2972 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2973 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2974 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2975 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2976 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2977 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2978 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2979 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2980 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2981 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2983 /* calendarData:FreeBusy */
2984 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2985 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 1));
2986 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2987 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 100));
2988 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2989 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 200));
2990 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2991 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 300));
2992 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2993 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 400));
2994 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2995 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 32000));
2997 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2998 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3001 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
3003 entry
= sip
->our_publication_keys
;
3005 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
3006 if (!strcmp(entry
->data
, key
)) {
3009 entry
= entry
->next
;
3014 /** Property names to store in blist.xml */
3015 #define ALIAS_PROP "alias"
3016 #define EMAIL_PROP "email"
3017 #define PHONE_PROP "phone"
3018 #define PHONE_DISPLAY_PROP "phone-display"
3019 #define PHONE_MOBILE_PROP "phone-mobile"
3020 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3021 #define PHONE_HOME_PROP "phone-home"
3022 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3023 #define PHONE_OTHER_PROP "phone-other"
3024 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3025 #define PHONE_CUSTOM1_PROP "phone-custom1"
3026 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3027 #define SITE_PROP "site"
3028 #define COMPANY_PROP "company"
3029 #define DEPARTMENT_PROP "department"
3030 #define TITLE_PROP "title"
3031 #define OFFICE_PROP "office"
3032 /** implies work address */
3033 #define ADDRESS_STREET_PROP "address-street"
3034 #define ADDRESS_CITY_PROP "address-city"
3035 #define ADDRESS_STATE_PROP "address-state"
3036 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3037 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3039 * Update user information
3041 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3042 * @param property_name
3043 * @param property_value may be modified to strip white space
3046 sipe_update_user_info(struct sipe_account_data
*sip
,
3048 const char *property_name
,
3049 char *property_value
)
3051 GSList
*buddies
, *entry
;
3053 if (!property_name
|| strlen(property_name
) == 0) return;
3056 property_value
= g_strstrip(property_value
);
3058 entry
= buddies
= purple_find_buddies(sip
->account
, uri
); /* all buddies in different groups */
3060 const char *prop_str
;
3061 const char *server_alias
;
3062 PurpleBuddy
*p_buddy
= entry
->data
;
3064 /* for Display Name */
3065 if (!strcmp(property_name
, ALIAS_PROP
)) {
3066 if (property_value
&& sipe_is_bad_alias(uri
, purple_buddy_get_alias(p_buddy
))) {
3067 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, property_value
);
3068 purple_blist_alias_buddy(p_buddy
, property_value
);
3071 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3072 if (property_value
&& strlen(property_value
) > 0 &&
3073 ( (server_alias
&& strcmp(property_value
, server_alias
))
3074 || !server_alias
|| strlen(server_alias
) == 0 )
3076 purple_blist_server_alias_buddy(p_buddy
, property_value
);
3079 /* for other properties */
3081 if (property_value
&& strlen(property_value
) > 0) {
3082 prop_str
= purple_blist_node_get_string(&p_buddy
->node
, property_name
);
3083 if (!prop_str
|| g_ascii_strcasecmp(prop_str
, property_value
)) {
3084 purple_blist_node_set_string(&p_buddy
->node
, property_name
, property_value
);
3089 entry
= entry
->next
;
3091 g_slist_free(buddies
);
3096 * Suitable for both 2005 and 2007 systems.
3098 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3100 * @param phone may be modified to strip white space
3101 * @param phone_display_string may be modified to strip white space
3104 sipe_update_user_phone(struct sipe_account_data
*sip
,
3106 const gchar
*phone_type
,
3108 gchar
*phone_display_string
)
3110 const char *phone_node
= PHONE_PROP
; /* work phone by default */
3111 const char *phone_display_node
= PHONE_DISPLAY_PROP
; /* work phone by default */
3113 if(!phone
|| strlen(phone
) == 0) return;
3115 if (phone_type
&& (!strcmp(phone_type
, "mobile") || !strcmp(phone_type
, "cell"))) {
3116 phone_node
= PHONE_MOBILE_PROP
;
3117 phone_display_node
= PHONE_MOBILE_DISPLAY_PROP
;
3118 } else if (phone_type
&& !strcmp(phone_type
, "home")) {
3119 phone_node
= PHONE_HOME_PROP
;
3120 phone_display_node
= PHONE_HOME_DISPLAY_PROP
;
3121 } else if (phone_type
&& !strcmp(phone_type
, "other")) {
3122 phone_node
= PHONE_OTHER_PROP
;
3123 phone_display_node
= PHONE_OTHER_DISPLAY_PROP
;
3124 } else if (phone_type
&& !strcmp(phone_type
, "custom1")) {
3125 phone_node
= PHONE_CUSTOM1_PROP
;
3126 phone_display_node
= PHONE_CUSTOM1_DISPLAY_PROP
;
3129 sipe_update_user_info(sip
, uri
, phone_node
, phone
);
3130 if (phone_display_string
) {
3131 sipe_update_user_info(sip
, uri
, phone_display_node
, phone_display_string
);
3136 sipe_update_calendar(struct sipe_account_data
*sip
)
3138 const char* calendar
= purple_account_get_string(sip
->account
, "calendar", "EXCH");
3140 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3142 if (!strcmp(calendar
, "EXCH")) {
3143 sipe_ews_update_calendar(sip
);
3146 /* schedule repeat */
3147 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
3149 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3153 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3154 * by using standard Purple's means of signals and saved statuses.
3156 * Thus all UI elements get updated: Status Button with Note, docklet.
3157 * This is ablolutely important as both our status and note can come
3158 * inbound (roaming) or be updated programmatically (e.g. based on our
3162 sipe_set_purple_account_status_and_note(struct sipe_account_data
*sip
)
3164 PurpleSavedStatus
*saved_status
;
3165 const PurpleStatusType
*acct_status_type
=
3166 purple_status_type_find_with_id(sip
->account
->status_types
, sip
->status
);
3167 PurpleStatusPrimitive primitive
= purple_status_type_get_primitive(acct_status_type
);
3169 saved_status
= purple_savedstatus_find_transient_by_type_and_message(primitive
, sip
->note
);
3171 /* If this type+message is unique then create a new transient saved status
3172 * Ref: gtkstatusbox.c
3174 if (!saved_status
) {
3176 GList
*active_accts
= purple_accounts_get_all_active();
3178 saved_status
= purple_savedstatus_new(NULL
, primitive
);
3179 purple_savedstatus_set_message(saved_status
, sip
->note
);
3181 for (tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
3182 purple_savedstatus_set_substatus(saved_status
,
3183 (PurpleAccount
*)tmp
->data
, acct_status_type
, sip
->note
);
3185 g_list_free(active_accts
);
3188 /* Set the status for each account */
3189 purple_savedstatus_activate(saved_status
);
3193 send_publish_category_initial(struct sipe_account_data
*sip
);
3196 * When we receive some self (BE) NOTIFY with a new subscriber
3197 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3200 static void sipe_process_roaming_self(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3207 char *display_name
= NULL
;
3209 GSList
*category_names
= NULL
;
3210 int aggreg_avail
= 0;
3211 static sipe_activity aggreg_activity
= SIPE_ACTIVITY_UNSET
;
3212 gboolean do_update_status
= FALSE
;
3214 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3216 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3219 contact
= get_contact(sip
);
3220 to
= sip_uri_self(sip
);
3224 /* set list of categories participating in this XML */
3225 for (node
= xmlnode_get_descendant(xml
, "categories", "category", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3226 const gchar
*name
= xmlnode_get_attrib(node
, "name");
3227 category_names
= slist_insert_unique_sorted(category_names
, (gchar
*)name
, (GCompareFunc
)strcmp
);
3229 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3230 category_names
? (int) g_slist_length(category_names
) : -1);
3231 /* drop category information */
3232 if (category_names
) {
3233 GSList
*entry
= category_names
;
3235 GHashTable
*cat_publications
;
3236 const gchar
*category
= entry
->data
;
3237 entry
= entry
->next
;
3238 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category
);
3239 cat_publications
= g_hash_table_lookup(sip
->our_publications
, category
);
3240 if (cat_publications
) {
3241 g_hash_table_remove(sip
->our_publications
, category
);
3242 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category
);
3246 g_slist_free(category_names
);
3247 /* filling our categories reflected in roaming data */
3248 for (node
= xmlnode_get_descendant(xml
, "categories", "category", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3249 const gchar
*name
= xmlnode_get_attrib(node
, "name");
3250 const gchar
*container
= xmlnode_get_attrib(node
, "container");
3251 const gchar
*instance
= xmlnode_get_attrib(node
, "instance");
3252 const gchar
*version
= xmlnode_get_attrib(node
, "version");
3253 guint version_int
= version
? atoi(version
) : 0;
3256 if (!container
|| !instance
) continue;
3258 /* key is <category><instance><container> */
3259 key
= g_strdup_printf("<%s><%s><%s>", name
, instance
, container
);
3260 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key
, version_int
);
3262 /* capture all userState publication for later clean up if required */
3263 if (!strcmp(name
, "state") && (atoi(container
) == 2 || atoi(container
) == 3)) {
3264 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3266 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "userState")) {
3267 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
3268 publication
->category
= g_strdup(name
);
3269 publication
->instance
= atoi(instance
);
3270 publication
->container
= atoi(container
);
3271 publication
->version
= version_int
;
3273 if (!sip
->user_state_publications
) {
3274 sip
->user_state_publications
= g_hash_table_new_full(
3275 g_str_hash
, g_str_equal
,
3276 g_free
, (GDestroyNotify
)free_publication
);
3278 g_hash_table_insert(sip
->user_state_publications
, g_strdup(key
), publication
);
3279 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3284 if (sipe_is_our_publication(sip
, key
)) {
3285 GHashTable
*cat_publications
= g_hash_table_lookup(sip
->our_publications
, name
);
3287 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
3288 publication
->category
= g_strdup(name
);
3289 publication
->instance
= atoi(instance
);
3290 publication
->container
= atoi(container
);
3291 publication
->version
= version_int
;
3292 /* filling publication->availability */
3293 if (!strcmp(name
, "state")) {
3294 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3295 xmlnode
*xn_avail
= xmlnode_get_child(xn_state
, "availability");
3298 gchar
*avail_str
= xmlnode_get_data(xn_avail
);
3300 publication
->availability
= atoi(avail_str
);
3304 /* for calendarState */
3305 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "calendarState")) {
3306 xmlnode
*xn_activity
= xmlnode_get_child(xn_state
, "activity");
3307 struct sipe_cal_event
*event
= g_new0(struct sipe_cal_event
, 1);
3309 event
->start_time
= purple_str_to_time(xmlnode_get_attrib(xn_state
, "startTime"),
3310 FALSE
, NULL
, NULL
, NULL
);
3312 if (!strcmp(xmlnode_get_attrib(xn_activity
, "token"),
3313 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].token
))
3315 event
->is_meeting
= TRUE
;
3318 event
->subject
= xmlnode_get_data(xmlnode_get_child(xn_state
, "meetingSubject"));
3319 event
->location
= xmlnode_get_data(xmlnode_get_child(xn_state
, "meetingLocation"));
3321 publication
->cal_event_hash
= sipe_cal_event_hash(event
);
3322 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3323 publication
->cal_event_hash
);
3324 sipe_cal_event_free(event
);
3327 /* filling publication->note */
3328 if (!strcmp(name
, "note")) {
3329 xmlnode
*xn_body
= xmlnode_get_descendant(node
, "note", "body", NULL
);
3333 publication
->note
= xmlnode_get_data(xn_body
);
3334 sip
->note
= g_strdup(publication
->note
);
3336 do_update_status
= TRUE
;
3339 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3340 if (!strcmp(name
, "calendarData") && (publication
->container
== 300)) {
3341 xmlnode
*xn_free_busy
= xmlnode_get_descendant(node
, "calendarData", "freeBusy", NULL
);
3342 xmlnode
*xn_working_hours
= xmlnode_get_descendant(node
, "calendarData", "WorkingHours", NULL
);
3344 publication
->fb_start_str
= g_strdup(xmlnode_get_attrib(xn_free_busy
, "startTime"));
3345 publication
->free_busy_base64
= xmlnode_get_data(xn_free_busy
);
3347 if (xn_working_hours
) {
3348 publication
->working_hours_xml_str
= xmlnode_to_str(xn_working_hours
, NULL
);
3352 if (!cat_publications
) {
3353 cat_publications
= g_hash_table_new_full(
3354 g_str_hash
, g_str_equal
,
3355 g_free
, (GDestroyNotify
)free_publication
);
3356 g_hash_table_insert(sip
->our_publications
, g_strdup(name
), cat_publications
);
3357 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name
);
3359 g_hash_table_insert(cat_publications
, g_strdup(key
), publication
);
3360 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key
, version_int
);
3364 /* aggregateState (not an our publication) from 2-nd container */
3365 if (!strcmp(name
, "state") && atoi(container
) == 2) {
3366 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3368 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "aggregateState")) {
3369 xmlnode
*xn_avail
= xmlnode_get_child(xn_state
, "availability");
3370 xmlnode
*xn_activity
= xmlnode_get_child(xn_state
, "activity");
3373 gchar
*avail_str
= xmlnode_get_data(xn_avail
);
3375 aggreg_avail
= atoi(avail_str
);
3381 const char *activity_token
= xmlnode_get_attrib(xn_activity
, "token");
3383 aggreg_activity
= sipe_get_activity_by_token(activity_token
);
3386 do_update_status
= TRUE
;
3390 /* userProperties published by server from AD */
3391 if (!sip
->csta
&& !strcmp(name
, "userProperties")) {
3393 /* line, for Remote Call Control (RCC) */
3394 for (line
= xmlnode_get_descendant(node
, "userProperties", "lines", "line", NULL
); line
; line
= xmlnode_get_next_twin(line
)) {
3395 const gchar
*line_server
= xmlnode_get_attrib(line
, "lineServer");
3396 const gchar
*line_type
= xmlnode_get_attrib(line
, "lineType");
3399 if (!line_server
|| (strcmp(line_type
, "Rcc") && strcmp(line_type
, "Dual"))) continue;
3401 line_uri
= xmlnode_get_data(line
);
3403 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri
, line_server
);
3404 sip_csta_open(sip
, line_uri
, line_server
);
3412 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3413 sip
->our_publications
? (int) g_hash_table_size(sip
->our_publications
) : -1);
3416 for (node
= xmlnode_get_descendant(xml
, "containers", "container", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3417 guint id
= atoi(xmlnode_get_attrib(node
, "id"));
3418 struct sipe_container
*container
= sipe_find_container(sip
, id
);
3421 sip
->containers
= g_slist_remove(sip
->containers
, container
);
3422 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container
->id
, container
->version
);
3423 free_container(container
);
3425 container
= g_new0(struct sipe_container
, 1);
3427 container
->version
= atoi(xmlnode_get_attrib(node
, "version"));
3428 sip
->containers
= g_slist_append(sip
->containers
, container
);
3429 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container
->id
, container
->version
);
3431 for (node2
= xmlnode_get_child(node
, "member"); node2
; node2
= xmlnode_get_next_twin(node2
)) {
3432 struct sipe_container_member
*member
= g_new0(struct sipe_container_member
, 1);
3433 member
->type
= xmlnode_get_attrib(node2
, "type");
3434 member
->value
= xmlnode_get_attrib(node2
, "value");
3435 container
->members
= g_slist_append(container
->members
, member
);
3436 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3437 member
->type
, member
->value
? member
->value
: "");
3441 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip
->access_level_set
? "TRUE" : "FALSE");
3442 if (!sip
->access_level_set
&& xmlnode_get_child(xml
, "containers")) {
3443 int sameEnterpriseAL
= sipe_find_access_level(sip
, "sameEnterprise", NULL
);
3444 int federatedAL
= sipe_find_access_level(sip
, "federated", NULL
);
3445 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL
);
3446 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL
);
3447 /* initial set-up to let counterparties see your status */
3448 if (sameEnterpriseAL
< 0) {
3449 struct sipe_container
*container
= sipe_find_container(sip
, 200);
3450 guint version
= container
? container
->version
: 0;
3451 sipe_send_set_container_members(sip
, 200, version
, "add", "sameEnterprise", NULL
);
3453 if (federatedAL
< 0) {
3454 struct sipe_container
*container
= sipe_find_container(sip
, 100);
3455 guint version
= container
? container
->version
: 0;
3456 sipe_send_set_container_members(sip
, 100, version
, "add", "federated", NULL
);
3458 sip
->access_level_set
= TRUE
;
3462 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3464 const char *acknowledged
;
3468 user
= xmlnode_get_attrib(node
, "user"); /* without 'sip:' prefix */
3469 if (!user
) continue;
3470 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
3471 display_name
= g_strdup(xmlnode_get_attrib(node
, "displayName"));
3472 uri
= sip_uri_from_name(user
);
3474 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
3476 acknowledged
= xmlnode_get_attrib(node
, "acknowledged");
3477 if(!g_ascii_strcasecmp(acknowledged
,"false")){
3478 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user
);
3479 if (!purple_find_buddy(sip
->account
, uri
)) {
3480 purple_account_request_add(sip
->account
, uri
, _("you"), display_name
, NULL
);
3483 hdr
= g_strdup_printf(
3485 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
3487 body
= g_strdup_printf(
3488 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3489 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3490 "</setSubscribers>", user
);
3492 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
3496 g_free(display_name
);
3503 /* Publish initial state if not yet.
3504 * Assuming this happens on initial responce to subscription to roaming-self
3505 * so we've already updated our roaming data in full.
3508 if (!sip
->initial_state_published
) {
3509 send_publish_category_initial(sip
);
3510 sip
->initial_state_published
= TRUE
;
3512 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
3513 do_update_status
= FALSE
;
3514 } else if (aggreg_avail
) {
3516 g_free(sip
->status
);
3517 if (aggreg_avail
&& aggreg_avail
< 18000) { /* not offline */
3518 if (aggreg_activity
== SIPE_ACTIVITY_IN_MEETING
||
3519 aggreg_activity
== SIPE_ACTIVITY_IN_CONF
||
3520 aggreg_activity
== SIPE_ACTIVITY_ON_PHONE
)
3522 sip
->status
= g_strdup(sipe_activity_map
[aggreg_activity
].status_id
);
3526 sip
->status
= g_strdup(sipe_get_status_by_availability(aggreg_avail
, NULL
));
3529 sip
->status
= g_strdup(SIPE_STATUS_ID_INVISIBLE
); /* not not let offline status switch us off */
3533 if (do_update_status
) {
3534 purple_debug_info("sipe", "sipe_process_roaming_self: to %s for the account\n", sip
->status
);
3535 sipe_set_purple_account_status_and_note(sip
);
3541 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
)
3543 gchar
*to
= sip_uri_self(sip
);
3544 gchar
*tmp
= get_contact(sip
);
3545 gchar
*hdr
= g_strdup_printf(
3546 "Event: vnd-microsoft-roaming-ACL\r\n"
3547 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3548 "Supported: com.microsoft.autoextend\r\n"
3549 "Supported: ms-benotify\r\n"
3550 "Proxy-Require: ms-benotify\r\n"
3551 "Supported: ms-piggyback-first-notify\r\n"
3552 "Contact: %s\r\n", tmp
);
3555 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
3561 * To request for presence information about the user, access level settings that have already been configured by the user
3562 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3563 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3566 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
)
3568 gchar
*to
= sip_uri_self(sip
);
3569 gchar
*tmp
= get_contact(sip
);
3570 gchar
*hdr
= g_strdup_printf(
3571 "Event: vnd-microsoft-roaming-self\r\n"
3572 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3573 "Supported: ms-benotify\r\n"
3574 "Proxy-Require: ms-benotify\r\n"
3575 "Supported: ms-piggyback-first-notify\r\n"
3577 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
3579 gchar
*body
=g_strdup(
3580 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3581 "<roaming type=\"categories\"/>"
3582 "<roaming type=\"containers\"/>"
3583 "<roaming type=\"subscribers\"/></roamingList>");
3586 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
3595 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
)
3597 gchar
*to
= sip_uri_self(sip
);
3598 gchar
*tmp
= get_contact(sip
);
3599 gchar
*hdr
= g_strdup_printf(
3600 "Event: vnd-microsoft-provisioning\r\n"
3601 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3602 "Supported: com.microsoft.autoextend\r\n"
3603 "Supported: ms-benotify\r\n"
3604 "Proxy-Require: ms-benotify\r\n"
3605 "Supported: ms-piggyback-first-notify\r\n"
3607 "Contact: %s\r\n", tmp
);
3610 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
3615 /** Subscription for provisioning information to help with initial
3616 * configuration. This subscription is a one-time query (denoted by the Expires header,
3617 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3618 * configuration, meeting policies, and policy settings that Communicator must enforce.
3619 * TODO: for what we need this information.
3622 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
)
3624 gchar
*to
= sip_uri_self(sip
);
3625 gchar
*tmp
= get_contact(sip
);
3626 gchar
*hdr
= g_strdup_printf(
3627 "Event: vnd-microsoft-provisioning-v2\r\n"
3628 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3629 "Supported: com.microsoft.autoextend\r\n"
3630 "Supported: ms-benotify\r\n"
3631 "Proxy-Require: ms-benotify\r\n"
3632 "Supported: ms-piggyback-first-notify\r\n"
3635 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
3636 gchar
*body
= g_strdup(
3637 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3638 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3639 "<provisioningGroup name=\"ucPolicy\"/>"
3640 "</provisioningGroupList>");
3643 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
3650 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key
,
3651 gpointer value
, gpointer user_data
)
3653 struct sip_subscription
*subscription
= value
;
3654 struct sip_dialog
*dialog
= &subscription
->dialog
;
3655 struct sipe_account_data
*sip
= user_data
;
3656 gchar
*tmp
= get_contact(sip
);
3657 gchar
*hdr
= g_strdup_printf(
3660 "Contact: %s\r\n", subscription
->event
, tmp
);
3663 /* Rate limit to max. 25 requests per seconds */
3664 g_usleep(1000000 / 25);
3666 send_sip_request(sip
->gc
, "SUBSCRIBE", dialog
->with
, dialog
->with
, hdr
, NULL
, dialog
, NULL
);
3670 /* IM Session (INVITE and MESSAGE methods) */
3672 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3674 get_end_points (struct sipe_account_data
*sip
,
3675 struct sip_session
*session
)
3679 if (session
== NULL
) {
3683 res
= g_strdup_printf("<sip:%s>", sip
->username
);
3685 SIPE_DIALOG_FOREACH
{
3687 res
= g_strdup_printf("%s, <%s>", res
, dialog
->with
);
3690 if (dialog
->theirepid
) {
3692 res
= g_strdup_printf("%s;epid=%s", res
, dialog
->theirepid
);
3695 } SIPE_DIALOG_FOREACH_END
;
3701 process_options_response(SIPE_UNUSED_PARAMETER
struct sipe_account_data
*sip
,
3703 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
3705 gboolean ret
= TRUE
;
3707 if (msg
->response
!= 200) {
3708 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
3712 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
3718 * Asks UA/proxy about its capabilities.
3720 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
3722 gchar
*to
= sip_uri(who
);
3723 gchar
*contact
= get_contact(sip
);
3724 gchar
*request
= g_strdup_printf(
3725 "Accept: application/sdp\r\n"
3726 "Contact: %s\r\n", contact
);
3729 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
3736 sipe_notify_user(struct sipe_account_data
*sip
,
3737 struct sip_session
*session
,
3738 PurpleMessageFlags flags
,
3739 const gchar
*message
)
3741 PurpleConversation
*conv
;
3743 if (!session
->conv
) {
3744 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY
, session
->with
, sip
->account
);
3746 conv
= session
->conv
;
3748 purple_conversation_write(conv
, NULL
, message
, flags
, time(NULL
));
3752 sipe_present_info(struct sipe_account_data
*sip
,
3753 struct sip_session
*session
,
3754 const gchar
*message
)
3756 sipe_notify_user(sip
, session
, PURPLE_MESSAGE_SYSTEM
, message
);
3760 sipe_present_err(struct sipe_account_data
*sip
,
3761 struct sip_session
*session
,
3762 const gchar
*message
)
3764 sipe_notify_user(sip
, session
, PURPLE_MESSAGE_ERROR
, message
);
3768 sipe_present_message_undelivered_err(struct sipe_account_data
*sip
,
3769 struct sip_session
*session
,
3772 const gchar
*message
)
3774 char *msg
, *msg_tmp
, *msg_tmp2
;
3777 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
3778 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
3780 /* Service unavailable; Server Internal Error; Server Time-out */
3781 if (sip_error
== 503 || sip_error
== 500 || sip_error
== 504) {
3782 label
= _("This message was not delivered to %s because the service is not available");
3783 } else if (sip_error
== 486) { /* Busy Here */
3784 label
= _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3786 label
= _("This message was not delivered to %s because one or more recipients are offline");
3789 msg_tmp
= g_strdup_printf( "%s:\n%s" ,
3790 msg_tmp2
= g_strdup_printf(label
, who
? who
: ""), msg
? msg
: "");
3791 sipe_present_err(sip
, session
, msg_tmp
);
3798 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_session
* session
);
3801 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
3802 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
3804 gboolean ret
= TRUE
;
3805 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
3806 struct sip_session
*session
= sipe_session_find_im(sip
, with
);
3807 struct sip_dialog
*dialog
;
3813 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3818 dialog
= sipe_dialog_find(session
, with
);
3820 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3825 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
3826 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
), with
);
3828 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
3830 if (msg
->response
>= 400) {
3831 PurpleBuddy
*pbuddy
;
3832 gchar
*alias
= with
;
3834 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3836 if ((pbuddy
= purple_find_buddy(sip
->account
, with
))) {
3837 alias
= (gchar
*)purple_buddy_get_alias(pbuddy
);
3840 sipe_present_message_undelivered_err(sip
, session
, msg
->response
, alias
, message
);
3843 gchar
*message_id
= sipmsg_find_header(msg
, "Message-Id");
3845 g_hash_table_insert(session
->conf_unconfirmed_messages
, g_strdup(message_id
), g_strdup(message
));
3846 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3847 message_id
, g_hash_table_size(session
->conf_unconfirmed_messages
));
3850 g_hash_table_remove(session
->unconfirmed_messages
, key
);
3851 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3852 key
, g_hash_table_size(session
->unconfirmed_messages
));
3858 if (ret
) sipe_im_process_queue(sip
, session
);
3863 sipe_is_election_finished(struct sip_session
*session
);
3866 sipe_election_result(struct sipe_account_data
*sip
,
3870 process_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
3871 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
3873 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
3874 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3875 struct sip_dialog
*dialog
;
3876 struct sip_session
*session
;
3878 session
= sipe_session_find_chat_by_callid(sip
, callid
);
3880 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid
);
3884 if (msg
->response
== 200 && !strncmp(contenttype
, "application/x-ms-mim", 20)) {
3885 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3886 xmlnode
*xn_request_rm_response
= xmlnode_get_child(xn_action
, "RequestRMResponse");
3887 xmlnode
*xn_set_rm_response
= xmlnode_get_child(xn_action
, "SetRMResponse");
3889 if (xn_request_rm_response
) {
3890 const char *with
= xmlnode_get_attrib(xn_request_rm_response
, "uri");
3891 const char *allow
= xmlnode_get_attrib(xn_request_rm_response
, "allow");
3893 dialog
= sipe_dialog_find(session
, with
);
3895 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with
);
3899 if (allow
&& !g_strcasecmp(allow
, "true")) {
3900 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with
);
3901 dialog
->election_vote
= 1;
3902 } else if (allow
&& !g_strcasecmp(allow
, "false")) {
3903 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with
);
3904 dialog
->election_vote
= -1;
3907 if (sipe_is_election_finished(session
)) {
3908 sipe_election_result(sip
, session
);
3911 } else if (xn_set_rm_response
) {
3914 xmlnode_free(xn_action
);
3921 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_dialog
*dialog
, const char *msg
)
3930 sipe_parse_html(msg
, &msgformat
, &msgtext
);
3931 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat
);
3933 msgr_value
= sipmsg_get_msgr_string(msgformat
);
3936 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
3939 msgr
= g_strdup("");
3942 tmp
= get_contact(sip
);
3943 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3944 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3945 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3946 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp
, msgr
);
3950 send_sip_request(sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
, hdr
, msgtext
, dialog
, process_message_response
);
3957 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_session
* session
)
3959 GSList
*entry2
= session
->outgoing_message_queue
;
3961 char *queued_msg
= entry2
->data
;
3963 /* for multiparty chat or conference */
3964 if (session
->is_multiparty
|| session
->focus_uri
) {
3965 gchar
*who
= sip_uri_self(sip
);
3966 serv_got_chat_in(sip
->gc
, session
->chat_id
, who
,
3967 PURPLE_MESSAGE_SEND
, queued_msg
, time(NULL
));
3971 SIPE_DIALOG_FOREACH
{
3974 if (dialog
->outgoing_invite
) continue; /* do not send messages as INVITE is not responded. */
3976 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog
->callid
, (dialog
->cseq
) + 1, dialog
->with
);
3977 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
3978 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
3979 key
, g_hash_table_size(session
->unconfirmed_messages
));
3982 sipe_send_message(sip
, dialog
, queued_msg
);
3983 } SIPE_DIALOG_FOREACH_END
;
3985 entry2
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
3991 sipe_refer_notify(struct sipe_account_data
*sip
,
3992 struct sip_session
*session
,
3999 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
4001 hdr
= g_strdup_printf(
4003 "Subscription-State: %s\r\n"
4004 "Content-Type: message/sipfrag\r\n",
4005 status
>= 200 ? "terminated" : "active");
4007 body
= g_strdup_printf(
4008 "SIP/2.0 %d %s\r\n",
4011 send_sip_request(sip
->gc
, "NOTIFY", who
, who
, hdr
, body
, dialog
, NULL
);
4018 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
4020 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
4021 struct sip_session
*session
;
4022 struct sip_dialog
*dialog
;
4026 struct sipmsg
*request_msg
= trans
->msg
;
4028 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4031 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4033 session
= sipe_session_find_im(sip
, with
);
4036 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4041 dialog
= sipe_dialog_find(session
, with
);
4043 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4048 sipe_dialog_parse(dialog
, msg
, TRUE
);
4050 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
4051 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
4053 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
4055 if (msg
->response
!= 200) {
4056 PurpleBuddy
*pbuddy
;
4057 gchar
*alias
= with
;
4059 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4061 if ((pbuddy
= purple_find_buddy(sip
->account
, with
))) {
4062 alias
= (gchar
*)purple_buddy_get_alias(pbuddy
);
4066 sipe_present_message_undelivered_err(sip
, session
, msg
->response
, alias
, message
);
4068 gchar
*tmp_msg
= g_strdup_printf(_("Failed to invite %s"), alias
);
4069 sipe_present_err(sip
, session
, tmp_msg
);
4073 sipe_dialog_remove(session
, with
);
4081 send_sip_request(sip
->gc
, "ACK", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
4082 dialog
->outgoing_invite
= NULL
;
4083 dialog
->is_established
= TRUE
;
4085 referred_by
= parse_from(sipmsg_find_header(request_msg
, "Referred-By"));
4087 sipe_refer_notify(sip
, session
, referred_by
, 200, "OK");
4088 g_free(referred_by
);
4091 /* add user to chat if it is a multiparty session */
4092 if (session
->is_multiparty
) {
4093 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4095 PURPLE_CBFLAGS_NONE
, TRUE
);
4098 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
4099 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4100 if (session
->outgoing_message_queue
) {
4101 char *queued_msg
= session
->outgoing_message_queue
->data
;
4102 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
4107 sipe_im_process_queue(sip
, session
);
4109 g_hash_table_remove(session
->unconfirmed_messages
, key
);
4110 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4111 key
, g_hash_table_size(session
->unconfirmed_messages
));
4120 sipe_invite(struct sipe_account_data
*sip
,
4121 struct sip_session
*session
,
4123 const gchar
*msg_body
,
4124 const gchar
*referred_by
,
4125 const gboolean is_triggered
)
4132 char *ms_text_format
= NULL
;
4133 gchar
*roster_manager
;
4135 gchar
*referred_by_str
;
4136 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
4138 if (dialog
&& dialog
->is_established
) {
4139 purple_debug_info("sipe", "session with %s already has a dialog open\n", who
);
4144 dialog
= sipe_dialog_add(session
);
4145 dialog
->callid
= session
->callid
? g_strdup(session
->callid
) : gencallid();
4146 dialog
->with
= g_strdup(who
);
4149 if (!(dialog
->ourtag
)) {
4150 dialog
->ourtag
= gentag();
4163 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
4164 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat
);
4166 msgr_value
= sipmsg_get_msgr_string(msgformat
);
4170 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
4174 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
4175 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
4180 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, (dialog
->cseq
) + 1);
4181 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
4182 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4183 key
, g_hash_table_size(session
->unconfirmed_messages
));
4187 contact
= get_contact(sip
);
4188 end_points
= get_end_points(sip
, session
);
4189 self
= sip_uri_self(sip
);
4190 roster_manager
= g_strdup_printf(
4191 "Roster-Manager: %s\r\n"
4192 "EndPoints: %s\r\n",
4195 referred_by_str
= referred_by
?
4197 "Referred-By: %s\r\n",
4200 hdr
= g_strdup_printf(
4201 "Supported: ms-sender\r\n"
4207 "Content-Type: application/sdp\r\n",
4208 (session
->roster_manager
&& !strcmp(session
->roster_manager
, self
)) ? roster_manager
: "",
4210 is_triggered
? "TriggeredInvite: TRUE\r\n" : "",
4211 is_triggered
|| session
->is_multiparty
? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4213 ms_text_format
? ms_text_format
: "");
4214 g_free(ms_text_format
);
4217 body
= g_strdup_printf(
4219 "o=- 0 0 IN IP4 %s\r\n"
4223 "m=%s %d sip null\r\n"
4224 "a=accept-types:text/plain text/html image/gif "
4225 "multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4226 purple_network_get_my_ip(-1),
4227 purple_network_get_my_ip(-1),
4228 sip
->ocs2007
? "message" : "x-ms-message",
4231 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
4232 to
, to
, hdr
, body
, dialog
, process_invite_response
);
4235 g_free(roster_manager
);
4237 g_free(referred_by_str
);
4244 sipe_refer(struct sipe_account_data
*sip
,
4245 struct sip_session
*session
,
4250 gchar
*epid
= get_epid(sip
);
4251 struct sip_dialog
*dialog
= sipe_dialog_find(session
,
4252 session
->roster_manager
);
4254 contact
= get_contact(sip
);
4255 hdr
= g_strdup_printf(
4257 "Refer-to: <%s>\r\n"
4258 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4259 "Require: com.microsoft.rtc-multiparty\r\n",
4263 dialog
->ourtag
? ";tag=" : "",
4264 dialog
->ourtag
? dialog
->ourtag
: "",
4268 send_sip_request(sip
->gc
, "REFER",
4269 session
->roster_manager
, session
->roster_manager
, hdr
, NULL
, dialog
, NULL
);
4276 sipe_send_election_request_rm(struct sipe_account_data
*sip
,
4277 struct sip_dialog
*dialog
,
4280 const gchar
*hdr
= "Content-Type: application/x-ms-mim\r\n";
4282 gchar
*body
= g_strdup_printf(
4283 "<?xml version=\"1.0\"?>\r\n"
4284 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4285 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4286 sip
->username
, bid
);
4288 send_sip_request(sip
->gc
, "INFO",
4289 dialog
->with
, dialog
->with
, hdr
, body
, dialog
, process_info_response
);
4295 sipe_send_election_set_rm(struct sipe_account_data
*sip
,
4296 struct sip_dialog
*dialog
)
4298 const gchar
*hdr
= "Content-Type: application/x-ms-mim\r\n";
4300 gchar
*body
= g_strdup_printf(
4301 "<?xml version=\"1.0\"?>\r\n"
4302 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4303 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4306 send_sip_request(sip
->gc
, "INFO",
4307 dialog
->with
, dialog
->with
, hdr
, body
, dialog
, process_info_response
);
4313 sipe_session_close(struct sipe_account_data
*sip
,
4314 struct sip_session
* session
)
4316 if (session
&& session
->focus_uri
) {
4317 sipe_conf_immcu_closed(sip
, session
);
4318 conf_session_close(sip
, session
);
4322 SIPE_DIALOG_FOREACH
{
4323 /* @TODO slow down BYE message sending rate */
4324 /* @see single subscription code */
4325 send_sip_request(sip
->gc
, "BYE", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
4326 } SIPE_DIALOG_FOREACH_END
;
4328 sipe_session_remove(sip
, session
);
4333 sipe_session_close_all(struct sipe_account_data
*sip
)
4336 while ((entry
= sip
->sessions
) != NULL
) {
4337 sipe_session_close(sip
, entry
->data
);
4342 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
4344 struct sipe_account_data
*sip
= gc
->proto_data
;
4346 purple_debug_info("sipe", "conversation with %s closed\n", who
);
4347 sipe_session_close(sip
, sipe_session_find_im(sip
, who
));
4351 sipe_chat_leave (PurpleConnection
*gc
, int id
)
4353 struct sipe_account_data
*sip
= gc
->proto_data
;
4354 struct sip_session
*session
= sipe_session_find_chat_by_id(sip
, id
);
4356 sipe_session_close(sip
, session
);
4359 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
,
4360 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
4362 struct sipe_account_data
*sip
= gc
->proto_data
;
4363 struct sip_session
*session
;
4364 struct sip_dialog
*dialog
;
4365 gchar
*uri
= sip_uri(who
);
4367 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
4369 session
= sipe_session_find_or_add_im(sip
, uri
);
4370 dialog
= sipe_dialog_find(session
, uri
);
4372 // Queue the message
4373 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
4375 if (dialog
&& !dialog
->outgoing_invite
) {
4376 sipe_im_process_queue(sip
, session
);
4377 } else if (!dialog
|| !dialog
->outgoing_invite
) {
4378 // Need to send the INVITE to get the outgoing dialog setup
4379 sipe_invite(sip
, session
, uri
, what
, NULL
, FALSE
);
4386 static int sipe_chat_send(PurpleConnection
*gc
, int id
, const char *what
,
4387 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
4389 struct sipe_account_data
*sip
= gc
->proto_data
;
4390 struct sip_session
*session
;
4392 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what
);
4394 session
= sipe_session_find_chat_by_id(sip
, id
);
4396 // Queue the message
4397 if (session
&& session
->dialogs
) {
4398 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
,
4400 sipe_im_process_queue(sip
, session
);
4402 gchar
*chat_name
= purple_find_chat(sip
->gc
, id
)->name
;
4403 const gchar
*proto_chat_id
= sipe_chat_find_name(chat_name
);
4405 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name
? chat_name
: "NULL");
4406 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id
? proto_chat_id
: "NULL");
4409 struct sip_session
*session
= sipe_session_add_chat(sip
);
4411 session
->is_multiparty
= FALSE
;
4412 session
->focus_uri
= g_strdup(proto_chat_id
);
4413 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
,
4415 sipe_invite_conf_focus(sip
, session
);
4422 /* End IM Session (INVITE and MESSAGE methods) */
4424 static void process_incoming_info(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4426 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
4427 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4429 struct sip_session
*session
;
4431 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg
->body
? msg
->body
: "");
4433 /* Call Control protocol */
4434 if (g_str_has_prefix(contenttype
, "application/csta+xml"))
4436 process_incoming_info_csta(sip
, msg
);
4440 from
= parse_from(sipmsg_find_header(msg
, "From"));
4441 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4443 session
= sipe_session_find_im(sip
, from
);
4450 if (g_str_has_prefix(contenttype
, "application/x-ms-mim"))
4452 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4453 xmlnode
*xn_request_rm
= xmlnode_get_child(xn_action
, "RequestRM");
4454 xmlnode
*xn_set_rm
= xmlnode_get_child(xn_action
, "SetRM");
4456 sipmsg_add_header(msg
, "Content-Type", "application/x-ms-mim");
4458 if (xn_request_rm
) {
4459 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4460 int bid
= atoi(xmlnode_get_attrib(xn_request_rm
, "bid"));
4461 gchar
*body
= g_strdup_printf(
4462 "<?xml version=\"1.0\"?>\r\n"
4463 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4464 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4466 session
->bid
< bid
? "true" : "false");
4467 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4469 } else if (xn_set_rm
) {
4471 const char *rm
= xmlnode_get_attrib(xn_set_rm
, "uri");
4472 g_free(session
->roster_manager
);
4473 session
->roster_manager
= g_strdup(rm
);
4475 body
= g_strdup_printf(
4476 "<?xml version=\"1.0\"?>\r\n"
4477 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4478 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4480 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4483 xmlnode_free(xn_action
);
4488 /* looks like purple lacks typing notification for chat */
4489 if (!session
->is_multiparty
&& !session
->focus_uri
) {
4490 xmlnode
*xn_keyboard_activity
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4491 const char *status
= xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity
, "status"),
4493 if (status
&& !strcmp(status
, "type")) {
4494 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
4495 } else if (status
&& !strcmp(status
, "idle")) {
4496 serv_got_typing_stopped(sip
->gc
, from
);
4498 xmlnode_free(xn_keyboard_activity
);
4501 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4506 static void process_incoming_bye(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4508 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4509 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4510 struct sip_session
*session
;
4511 struct sip_dialog
*dialog
;
4513 /* collect dialog identification
4514 * we need callid, ourtag and theirtag to unambiguously identify dialog
4516 /* take data before 'msg' will be modified by send_sip_response */
4517 dialog
= g_new0(struct sip_dialog
, 1);
4518 dialog
->callid
= g_strdup(callid
);
4519 dialog
->cseq
= parse_cseq(sipmsg_find_header(msg
, "CSeq"));
4520 dialog
->with
= g_strdup(from
);
4521 sipe_dialog_parse(dialog
, msg
, FALSE
);
4523 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4525 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4527 session
= sipe_session_find_im(sip
, from
);
4534 if (session
->roster_manager
&& !g_strcasecmp(from
, session
->roster_manager
)) {
4535 g_free(session
->roster_manager
);
4536 session
->roster_manager
= NULL
;
4539 /* This what BYE is essentially for - terminating dialog */
4540 sipe_dialog_remove_3(session
, dialog
);
4541 sipe_dialog_free(dialog
);
4542 if (session
->focus_uri
&& !g_strcasecmp(from
, session
->im_mcu_uri
)) {
4543 sipe_conf_immcu_closed(sip
, session
);
4544 } else if (session
->is_multiparty
) {
4545 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session
->conv
), from
, NULL
);
4551 static void process_incoming_refer(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4553 gchar
*self
= sip_uri_self(sip
);
4554 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4555 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4556 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
4557 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
4558 struct sip_session
*session
;
4559 struct sip_dialog
*dialog
;
4561 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4562 dialog
= sipe_dialog_find(session
, from
);
4564 if (!session
|| !dialog
|| !session
->roster_manager
|| strcmp(session
->roster_manager
, self
)) {
4565 send_sip_response(sip
->gc
, msg
, 500, "Server Internal Error", NULL
);
4567 send_sip_response(sip
->gc
, msg
, 202, "Accepted", NULL
);
4569 sipe_invite(sip
, session
, refer_to
, NULL
, referred_by
, FALSE
);
4575 g_free(referred_by
);
4579 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
4581 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
4582 struct sip_session
*session
;
4583 struct sip_dialog
*dialog
;
4585 if (state
== PURPLE_NOT_TYPING
)
4588 session
= sipe_session_find_im(sip
, who
);
4589 dialog
= sipe_dialog_find(session
, who
);
4591 if (session
&& dialog
&& dialog
->is_established
) {
4592 send_sip_request(gc
, "INFO", who
, who
,
4593 "Content-Type: application/xml\r\n",
4594 SIPE_SEND_TYPING
, dialog
, NULL
);
4596 return SIPE_TYPING_SEND_TIMEOUT
;
4599 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
4601 GSList
*tmp
= sip
->transactions
;
4602 time_t currtime
= time(NULL
);
4604 struct transaction
*trans
= tmp
->data
;
4606 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime
-trans
->time
);
4607 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
4610 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
4612 sendout_sipmsg(sip
, trans
->msg
);
4619 static void do_reauthenticate_cb(struct sipe_account_data
*sip
,
4620 SIPE_UNUSED_PARAMETER
void *unused
)
4622 /* register again when security token expires */
4623 /* we have to start a new authentication as the security token
4624 * is almost expired by sending a not signed REGISTER message */
4625 purple_debug_info("sipe", "do a full reauthentication\n");
4626 sipe_auth_free(&sip
->registrar
);
4627 sipe_auth_free(&sip
->proxy
);
4628 sip
->registerstatus
= 0;
4630 sip
->reauthenticate_set
= FALSE
;
4633 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4637 gboolean found
= FALSE
;
4639 from
= parse_from(sipmsg_find_header(msg
, "From"));
4643 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
4645 contenttype
= sipmsg_find_header(msg
, "Content-Type");
4646 if (!strncmp(contenttype
, "text/plain", 10)
4647 || !strncmp(contenttype
, "text/html", 9)
4648 || !strncmp(contenttype
, "multipart/related", 17)
4649 || !strncmp(contenttype
, "multipart/alternative", 21))
4651 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4652 gchar
*html
= get_html_message(contenttype
, msg
->body
);
4654 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
4656 session
= sipe_session_find_im(sip
, from
);
4659 if (session
&& session
->focus_uri
) { /* a conference */
4660 gchar
*tmp
= parse_from(sipmsg_find_header(msg
, "Ms-Sender"));
4661 gchar
*sender
= parse_from(tmp
);
4663 serv_got_chat_in(sip
->gc
, session
->chat_id
, sender
,
4664 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
4666 } else if (session
&& session
->is_multiparty
) { /* a multiparty chat */
4667 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
4668 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
4670 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
4673 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4676 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
4677 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4682 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4686 state
= xmlnode_get_child(isc
, "state");
4689 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4694 statedata
= xmlnode_get_data(state
);
4696 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
4697 else serv_got_typing_stopped(sip
->gc
, from
);
4702 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4706 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4707 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
4709 session
= sipe_session_find_im(sip
, from
);
4712 gchar
*msg
= g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4714 sipe_present_err(sip
, session
, msg
);
4718 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype
);
4719 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
4724 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4730 gboolean is_multiparty
= FALSE
;
4731 gboolean is_triggered
= FALSE
;
4732 gboolean was_multiparty
= TRUE
;
4733 gboolean just_joined
= FALSE
;
4735 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4736 gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
4737 gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
4738 gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
4739 gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
4740 GSList
*end_points
= NULL
;
4742 struct sip_session
*session
;
4744 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? tmp
= fix_newlines(msg
->body
) : "");
4747 /* Invitation to join conference */
4748 if (!strncmp(content_type
, "application/ms-conf-invite+xml", 30)) {
4749 process_incoming_invite_conf(sip
, msg
);
4753 /* Only accept text invitations */
4754 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
4755 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4759 // TODO There *must* be a better way to clean up the To header to add a tag...
4760 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4761 oldHeader
= sipmsg_find_header(msg
, "To");
4763 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
4764 sipmsg_remove_header_now(msg
, "To");
4765 sipmsg_add_header_now(msg
, "To", newHeader
);
4768 if (end_points_hdr
) {
4769 end_points
= sipmsg_parse_endpoints_header(end_points_hdr
);
4771 if (g_slist_length(end_points
) > 2) {
4772 is_multiparty
= TRUE
;
4775 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
4776 is_triggered
= TRUE
;
4777 is_multiparty
= TRUE
;
4780 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4781 /* Convert to multiparty */
4782 if (session
&& is_multiparty
&& !session
->is_multiparty
) {
4783 g_free(session
->with
);
4784 session
->with
= NULL
;
4785 was_multiparty
= FALSE
;
4786 session
->is_multiparty
= TRUE
;
4787 session
->chat_id
= rand();
4790 if (!session
&& is_multiparty
) {
4791 session
= sipe_session_find_or_add_chat_by_callid(sip
, callid
);
4794 from
= parse_from(sipmsg_find_header(msg
, "From"));
4796 session
= sipe_session_find_or_add_im(sip
, from
);
4799 g_free(session
->callid
);
4800 session
->callid
= g_strdup(callid
);
4802 session
->is_multiparty
= is_multiparty
;
4803 if (roster_manager
) {
4804 session
->roster_manager
= g_strdup(roster_manager
);
4807 if (is_multiparty
&& end_points
) {
4808 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
4809 GSList
*entry
= end_points
;
4811 struct sip_dialog
*dialog
;
4812 struct sipendpoint
*end_point
= entry
->data
;
4813 entry
= entry
->next
;
4815 if (!g_strcasecmp(from
, end_point
->contact
) ||
4816 !g_strcasecmp(to
, end_point
->contact
))
4819 dialog
= sipe_dialog_find(session
, end_point
->contact
);
4821 g_free(dialog
->theirepid
);
4822 dialog
->theirepid
= end_point
->epid
;
4823 end_point
->epid
= NULL
;
4825 dialog
= sipe_dialog_add(session
);
4827 dialog
->callid
= g_strdup(session
->callid
);
4828 dialog
->with
= end_point
->contact
;
4829 end_point
->contact
= NULL
;
4830 dialog
->theirepid
= end_point
->epid
;
4831 end_point
->epid
= NULL
;
4835 /* send triggered INVITE */
4836 sipe_invite(sip
, session
, dialog
->with
, NULL
, NULL
, TRUE
);
4843 GSList
*entry
= end_points
;
4845 struct sipendpoint
*end_point
= entry
->data
;
4846 entry
= entry
->next
;
4847 g_free(end_point
->contact
);
4848 g_free(end_point
->epid
);
4851 g_slist_free(end_points
);
4855 struct sip_dialog
*dialog
= sipe_dialog_find(session
, from
);
4857 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4859 dialog
= sipe_dialog_add(session
);
4861 dialog
->callid
= g_strdup(session
->callid
);
4862 dialog
->with
= g_strdup(from
);
4863 sipe_dialog_parse(dialog
, msg
, FALSE
);
4865 if (!dialog
->ourtag
) {
4866 dialog
->ourtag
= newTag
;
4873 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4877 if (is_multiparty
&& !session
->conv
) {
4878 gchar
*chat_title
= sipe_chat_get_name(callid
);
4879 gchar
*self
= sip_uri_self(sip
);
4880 /* create prpl chat */
4881 session
->conv
= serv_got_joined_chat(sip
->gc
, session
->chat_id
, chat_title
);
4882 session
->chat_title
= g_strdup(chat_title
);
4883 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session
->conv
), self
);
4885 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4887 PURPLE_CBFLAGS_NONE
, FALSE
);
4892 if (is_multiparty
&& !was_multiparty
) {
4893 /* add current IM counterparty to chat */
4894 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4895 sipe_dialog_first(session
)->with
, NULL
,
4896 PURPLE_CBFLAGS_NONE
, FALSE
);
4899 /* add inviting party to chat */
4900 if (just_joined
&& session
->conv
) {
4901 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4903 PURPLE_CBFLAGS_NONE
, TRUE
);
4906 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4908 /* This used only in 2005 official client, not 2007 or Reuters.
4909 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4910 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4912 if (is_multiparty
) {
4913 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4914 gchar
*ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
4915 if (ms_text_format
) {
4916 if (g_str_has_prefix(ms_text_format
, "text/plain") || g_str_has_prefix(ms_text_format
, "text/html")) {
4918 gchar
*html
= get_html_message(ms_text_format
, NULL
);
4920 if (is_multiparty
) {
4921 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
4922 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
4924 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
4927 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
4936 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
4937 sipmsg_add_header(msg
, "User-Agent", sipe_get_useragent(sip
));
4938 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
4940 body
= g_strdup_printf(
4942 "o=- 0 0 IN IP4 %s\r\n"
4946 "m=%s %d sip sip:%s\r\n"
4947 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4948 purple_network_get_my_ip(-1),
4949 purple_network_get_my_ip(-1),
4950 sip
->ocs2007
? "message" : "x-ms-message",
4953 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4957 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4961 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4962 sipmsg_add_header(msg
, "User-Agent", sipe_get_useragent(sip
));
4963 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
4965 body
= g_strdup_printf(
4967 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4969 "c=IN IP4 0.0.0.0\r\n"
4971 "m=%s %d sip sip:%s\r\n"
4972 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4973 sip
->ocs2007
? "message" : "x-ms-message",
4976 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4980 static void sipe_connection_cleanup(struct sipe_account_data
*);
4981 static void create_connection(struct sipe_account_data
*, gchar
*, int);
4983 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
4984 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
4987 const gchar
*expires_header
;
4989 GSList
*hdr
= msg
->headers
;
4990 struct siphdrelement
*elem
;
4992 expires_header
= sipmsg_find_header(msg
, "Expires");
4993 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
4994 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
4996 switch (msg
->response
) {
4999 sip
->registerstatus
= 0;
5001 gchar
*contact_hdr
= NULL
;
5006 gchar
*server_hdr
= sipmsg_find_header(msg
, "Server");
5008 if (!sip
->reregister_set
) {
5009 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
5010 sipe_schedule_action(action_name
, expires
, do_register_cb
, NULL
, sip
, NULL
);
5011 g_free(action_name
);
5012 sip
->reregister_set
= TRUE
;
5015 sip
->registerstatus
= 3;
5017 if (server_hdr
&& !sip
->server_version
) {
5018 sip
->server_version
= g_strdup(server_hdr
);
5024 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5026 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
5029 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
5033 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\n", tmp
);
5034 fill_auth(tmp
, &sip
->registrar
);
5037 if (!sip
->reauthenticate_set
) {
5038 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
5039 guint reauth_timeout
;
5040 if (sip
->registrar
.type
== AUTH_TYPE_KERBEROS
&& sip
->registrar
.expires
> 0) {
5041 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5042 reauth_timeout
= sip
->registrar
.expires
- 300;
5044 /* NTLM: we have to reauthenticate as our security token expires
5045 after eight hours (be five minutes early) */
5046 reauth_timeout
= (8 * 3600) - 300;
5048 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, NULL
, sip
, NULL
);
5049 g_free(action_name
);
5050 sip
->reauthenticate_set
= TRUE
;
5053 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
5055 epid
= get_epid(sip
);
5056 uuid
= generateUUIDfromEPID(epid
);
5059 // There can be multiple Contact headers (one per location where the user is logged in) so
5060 // make sure to only get the one for this uuid
5061 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
5062 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
5063 if (valid_contact
) {
5064 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
5065 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5066 g_free(valid_contact
);
5069 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5074 g_free(sip
->contact
);
5076 sip
->contact
= g_strdup_printf("<%s>", gruu
);
5079 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5080 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
);
5082 sip
->ocs2007
= FALSE
;
5083 sip
->batched_support
= FALSE
;
5088 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
5089 if (!g_ascii_strcasecmp(elem
->value
, "msrtc-event-categories")) {
5090 /* We interpret this as OCS2007+ indicator */
5091 sip
->ocs2007
= TRUE
;
5092 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s (indicates OCS2007+)\n", elem
->value
);
5094 if (!g_ascii_strcasecmp(elem
->value
, "adhoclist")) {
5095 sip
->batched_support
= TRUE
;
5096 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s\n", elem
->value
);
5099 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
5100 gchar
**caps
= g_strsplit(elem
->value
,",",0);
5103 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
5104 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
5109 hdr
= g_slist_next(hdr
);
5112 /* rejoin open chats to be able to use them by continue to send messages */
5113 purple_conversation_foreach(sipe_rejoin_chat
);
5116 if (!sip
->subscribed
) { //do it just once, not every re-register
5118 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-contacts",
5119 (GCompareFunc
)g_ascii_strcasecmp
)) {
5120 sipe_subscribe_roaming_contacts(sip
);
5123 /* For 2007+ it does not make sence to subscribe to:
5124 * vnd-microsoft-roaming-ACL
5125 * vnd-microsoft-provisioning (not v2)
5127 * These are for backward compatibility.
5131 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-self",
5132 (GCompareFunc
)g_ascii_strcasecmp
)) {
5133 sipe_subscribe_roaming_self(sip
);
5135 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-provisioning-v2",
5136 (GCompareFunc
)g_ascii_strcasecmp
)) {
5137 sipe_subscribe_roaming_provisioning_v2(sip
);
5140 /* For 2005- servers */
5143 //sipe_options_request(sip, sip->sipdomain);
5145 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-ACL",
5146 (GCompareFunc
)g_ascii_strcasecmp
)) {
5147 sipe_subscribe_roaming_acl(sip
);
5149 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-provisioning",
5150 (GCompareFunc
)g_ascii_strcasecmp
)) {
5151 sipe_subscribe_roaming_provisioning(sip
);
5153 if (g_slist_find_custom(sip
->allow_events
, "presence.wpending",
5154 (GCompareFunc
)g_ascii_strcasecmp
)) {
5155 sipe_subscribe_presence_wpending(sip
, msg
);
5158 /* For 2007+ we publish our initial statuses and calendar data only after
5159 * received our existing publications in sipe_process_roaming_self()
5160 * Only in this case we know versions of current publications made
5163 /* For 2005- we publish our initial statuses only after
5164 * received our existing UserInfo data in response to
5165 * self subscription.
5166 * Only in this case we won't override existing UserInfo data
5167 * set earlier or by other client on our behalf.
5171 sip
->subscribed
= TRUE
;
5174 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
5175 "timeout=", ";", NULL
);
5176 if (timeout
!= NULL
) {
5177 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
5178 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5179 sip
->keepalive_timeout
);
5183 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip
->cseq
);
5188 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
5190 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
5191 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
5195 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
5198 tmp
= g_strsplit(parts
[0], ":", 0);
5199 hostname
= g_strdup(tmp
[0]);
5200 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
5204 tmp
= g_strsplit(parts
[i
], "=", 0);
5206 if (g_strcasecmp("transport", tmp
[0]) == 0) {
5207 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
5208 transport
= SIPE_TRANSPORT_TCP
;
5209 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
5210 transport
= SIPE_TRANSPORT_UDP
;
5219 /* Close old connection */
5220 sipe_connection_cleanup(sip
);
5222 /* Create new connection */
5223 sip
->transport
= transport
;
5224 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5225 hostname
, port
, TRANSPORT_DESCRIPTOR
);
5226 create_connection(sip
, hostname
, port
);
5232 if (sip
->registerstatus
!= 2) {
5233 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
5234 if (sip
->registrar
.retries
> 3) {
5235 sip
->gc
->wants_to_die
= TRUE
;
5236 purple_connection_error(sip
->gc
, _("Wrong password"));
5240 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5242 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
5245 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
5248 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\n", tmp
);
5249 fill_auth(tmp
, &sip
->registrar
);
5250 sip
->registerstatus
= 2;
5251 if (sip
->account
->disconnecting
) {
5252 do_register_exp(sip
, 0);
5260 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
5261 gchar
**reason
= NULL
;
5262 if (warning
!= NULL
) {
5264 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5266 reason
= g_strsplit(warning
, "\"", 0);
5268 warning
= g_strdup_printf(_("You have been rejected by the server: %s"),
5269 (reason
&& reason
[1]) ? reason
[1] : _("no reason given"));
5272 sip
->gc
->wants_to_die
= TRUE
;
5273 purple_connection_error(sip
->gc
, warning
);
5280 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
5281 gchar
*reason
= NULL
;
5282 if (warning
!= NULL
) {
5283 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
5285 warning
= g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5286 warning
? (reason
? reason
: _("no reason given")) :
5287 _("SIP is either not enabled for the destination URI or it does not exist"));
5290 sip
->gc
->wants_to_die
= TRUE
;
5291 purple_connection_error(sip
->gc
, warning
);
5297 case 504: /* Server time-out */
5299 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
5300 gchar
*reason
= NULL
;
5301 if (warning
!= NULL
) {
5302 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
5304 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
5307 sip
->gc
->wants_to_die
= TRUE
;
5308 purple_connection_error(sip
->gc
, warning
);
5318 * Returns 2005-style activity and Availability.
5320 * @param status Sipe statis id.
5323 sipe_get_act_avail_by_status_2005(const char *status
, int *activity
, int *availability
)
5325 int avail
= 300; /* online */
5326 int act
= 400; /* Available */
5328 if (!strcmp(status
, SIPE_STATUS_ID_AWAY
)) {
5330 } else if (!strcmp(status
, SIPE_STATUS_ID_LUNCH
)) {
5332 } else if (!strcmp(status
, SIPE_STATUS_ID_BRB
)) {
5334 } else if (!strcmp(status
, SIPE_STATUS_ID_AVAILABLE
)) {
5336 } else if (!strcmp(status
, SIPE_STATUS_ID_ON_PHONE
)) {
5338 } else if (!strcmp(status
, SIPE_STATUS_ID_BUSY
) ||
5339 !strcmp(status
, SIPE_STATUS_ID_IN_MEETING
) ||
5340 !strcmp(status
, SIPE_STATUS_ID_IN_CONF
) ||
5341 !strcmp(status
, SIPE_STATUS_ID_DND
)) {
5343 } else if (!strcmp(status
, SIPE_STATUS_ID_INVISIBLE
) ||
5344 !strcmp(status
, SIPE_STATUS_ID_OFFLINE
)) {
5345 avail
= 0; /* offline */
5348 act
= 400; /* Available */
5351 if (activity
) *activity
= act
;
5352 if (availability
) *availability
= avail
;
5358 * @param activity 2005 aggregated activity. Ex.: 600
5359 * @param availablity 2005 aggregated availablity. Ex.: 300
5362 sipe_get_status_by_act_avail_2005(const int activity
,
5363 const int availablity
)
5365 const char *status_id
= NULL
;
5368 status_id
= SIPE_STATUS_ID_AWAY
;
5369 else if (activity
< 200)
5370 status_id
= SIPE_STATUS_ID_LUNCH
;
5371 else if (activity
< 300)
5372 status_id
= SIPE_STATUS_ID_IDLE
;
5373 else if (activity
< 400)
5374 status_id
= SIPE_STATUS_ID_BRB
;
5375 else if (activity
< 500)
5376 status_id
= SIPE_STATUS_ID_AVAILABLE
;
5377 else if (activity
< 600)
5378 status_id
= SIPE_STATUS_ID_ON_PHONE
;
5379 else if (activity
< 700)
5380 status_id
= SIPE_STATUS_ID_BUSY
;
5381 else if (activity
< 800)
5382 status_id
= SIPE_STATUS_ID_AWAY
;
5384 status_id
= SIPE_STATUS_ID_AVAILABLE
;
5386 if (availablity
< 100)
5387 status_id
= SIPE_STATUS_ID_OFFLINE
;
5393 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5396 sipe_get_status_by_availability(int avail
,
5397 const char* activity
)
5402 status
= SIPE_STATUS_ID_OFFLINE
;
5403 else if (avail
< 4500)
5404 status
= SIPE_STATUS_ID_AVAILABLE
;
5405 else if (avail
< 6000)
5406 status
= SIPE_STATUS_ID_IDLE
;
5407 else if (avail
< 7500)
5408 if (activity
&& !strcmp(activity
, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING
))) {
5409 status
= SIPE_STATUS_ID_IN_MEETING
;
5410 } else if (activity
&& !strcmp(activity
, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF
))) {
5411 status
= SIPE_STATUS_ID_IN_CONF
;
5412 } else if (activity
&& !strcmp(activity
, SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE
))) {
5413 status
= SIPE_STATUS_ID_ON_PHONE
;
5415 status
= SIPE_STATUS_ID_BUSY
;
5417 else if (avail
< 9000)
5418 status
= SIPE_STATUS_ID_BUSYIDLE
;
5419 else if (avail
< 12000)
5420 status
= SIPE_STATUS_ID_DND
;
5421 else if (avail
< 15000)
5422 status
= SIPE_STATUS_ID_BRB
;
5423 else if (avail
< 18000)
5424 status
= SIPE_STATUS_ID_AWAY
;
5426 status
= SIPE_STATUS_ID_OFFLINE
;
5432 * Returns 2007-style availability value
5434 * @param sipe_status_id (in)
5435 * @param activity_token (out) Must be g_free()'d after use if consumed.
5438 sipe_get_availability_by_status(const char* sipe_status_id
, char** activity_token
)
5441 sipe_activity activity
= SIPE_ACTIVITY_UNSET
;
5443 if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_AWAY
) ||
5444 !strcmp(sipe_status_id
, SIPE_STATUS_ID_LUNCH
)) {
5445 availability
= 15500;
5446 activity
= SIPE_ACTIVITY_AWAY
;
5447 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_BRB
)) {
5448 availability
= 12500;
5449 activity
= SIPE_ACTIVITY_BRB
;
5450 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_DND
)) {
5451 availability
= 9500;
5452 activity
= SIPE_ACTIVITY_DND
;
5453 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_BUSY
) ||
5454 !strcmp(sipe_status_id
, SIPE_STATUS_ID_IN_MEETING
) ||
5455 !strcmp(sipe_status_id
, SIPE_STATUS_ID_IN_CONF
) ||
5456 !strcmp(sipe_status_id
, SIPE_STATUS_ID_ON_PHONE
)) {
5457 availability
= 6500;
5458 activity
= SIPE_ACTIVITY_BUSY
;
5459 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_AVAILABLE
)) {
5460 availability
= 3500;
5461 activity
= SIPE_ACTIVITY_ONLINE
;
5462 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_UNKNOWN
)) {
5465 // Offline or invisible
5466 availability
= 18500;
5467 activity
= SIPE_ACTIVITY_OFFLINE
;
5470 if (activity_token
) {
5471 *activity_token
= g_strdup(sipe_activity_map
[activity
].token
);
5473 return availability
;
5476 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5479 xmlnode
*xn_categories
;
5480 xmlnode
*xn_category
;
5482 const char *status
= NULL
;
5483 gboolean do_update_status
= FALSE
;
5485 xn_categories
= xmlnode_from_str(data
, len
);
5486 uri
= xmlnode_get_attrib(xn_categories
, "uri"); /* with 'sip:' prefix */
5488 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
5490 xn_category
= xmlnode_get_next_twin(xn_category
) )
5492 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
5495 if (!strcmp(attrVar
, "contactCard"))
5498 /* identity - Display Name and email */
5499 node
= xmlnode_get_descendant(xn_category
, "contactCard", "identity", NULL
);
5501 char* display_name
= xmlnode_get_data(
5502 xmlnode_get_descendant(node
, "name", "displayName", NULL
));
5503 char* email
= xmlnode_get_data(
5504 xmlnode_get_child(node
, "email"));
5506 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
5507 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
5509 g_free(display_name
);
5513 node
= xmlnode_get_descendant(xn_category
, "contactCard", "company", NULL
);
5515 char* company
= xmlnode_get_data(node
);
5516 sipe_update_user_info(sip
, uri
, COMPANY_PROP
, company
);
5520 node
= xmlnode_get_descendant(xn_category
, "contactCard", "department", NULL
);
5522 char* department
= xmlnode_get_data(node
);
5523 sipe_update_user_info(sip
, uri
, DEPARTMENT_PROP
, department
);
5527 node
= xmlnode_get_descendant(xn_category
, "contactCard", "title", NULL
);
5529 char* title
= xmlnode_get_data(node
);
5530 sipe_update_user_info(sip
, uri
, TITLE_PROP
, title
);
5534 node
= xmlnode_get_descendant(xn_category
, "contactCard", "office", NULL
);
5536 char* office
= xmlnode_get_data(node
);
5537 sipe_update_user_info(sip
, uri
, OFFICE_PROP
, office
);
5541 node
= xmlnode_get_descendant(xn_category
, "contactCard", "url", NULL
);
5543 char* site
= xmlnode_get_data(node
);
5544 sipe_update_user_info(sip
, uri
, SITE_PROP
, site
);
5548 for (node
= xmlnode_get_descendant(xn_category
, "contactCard", "phone", NULL
);
5550 node
= xmlnode_get_next_twin(node
))
5552 const char *phone_type
= xmlnode_get_attrib(node
, "type");
5553 char* phone
= xmlnode_get_data(xmlnode_get_child(node
, "uri"));
5554 char* phone_display_string
= xmlnode_get_data(xmlnode_get_child(node
, "displayString"));
5556 sipe_update_user_phone(sip
, uri
, phone_type
, phone
, phone_display_string
);
5559 g_free(phone_display_string
);
5562 for (node
= xmlnode_get_descendant(xn_category
, "contactCard", "address", NULL
);
5564 node
= xmlnode_get_next_twin(node
))
5566 if (!strcmp(xmlnode_get_attrib(node
, "type"), "work")) {
5567 char* street
= xmlnode_get_data(xmlnode_get_child(node
, "street"));
5568 char* city
= xmlnode_get_data(xmlnode_get_child(node
, "city"));
5569 char* state
= xmlnode_get_data(xmlnode_get_child(node
, "state"));
5570 char* zipcode
= xmlnode_get_data(xmlnode_get_child(node
, "zipcode"));
5571 char* country_code
= xmlnode_get_data(xmlnode_get_child(node
, "countryCode"));
5573 sipe_update_user_info(sip
, uri
, ADDRESS_STREET_PROP
, street
);
5574 sipe_update_user_info(sip
, uri
, ADDRESS_CITY_PROP
, city
);
5575 sipe_update_user_info(sip
, uri
, ADDRESS_STATE_PROP
, state
);
5576 sipe_update_user_info(sip
, uri
, ADDRESS_ZIPCODE_PROP
, zipcode
);
5577 sipe_update_user_info(sip
, uri
, ADDRESS_COUNTRYCODE_PROP
, country_code
);
5583 g_free(country_code
);
5590 else if (!strcmp(attrVar
, "note"))
5593 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
5596 /* clean up in case to 'note' element is supplied
5597 * which indicate note removal in client
5599 g_free(sbuddy
->annotation
);
5600 sbuddy
->annotation
= NULL
;
5601 sbuddy
->is_oof_note
= FALSE
;
5603 xn_node
= xmlnode_get_descendant(xn_category
, "note", "body", NULL
);
5605 sbuddy
->annotation
= xmlnode_get_data(xn_node
);
5606 sbuddy
->is_oof_note
= !strcmp(xmlnode_get_attrib(xn_node
, "type"), "OOF");
5608 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",
5609 uri
, sbuddy
->annotation
? sbuddy
->annotation
: "");
5612 /* to trigger UI refresh in case no status info is supplied in this update */
5613 do_update_status
= TRUE
;
5618 else if(!strcmp(attrVar
, "state"))
5622 xmlnode
*xn_availability
;
5623 xmlnode
*xn_activity
;
5624 xmlnode
*xn_meeting_subject
;
5625 xmlnode
*xn_meeting_location
;
5626 struct sipe_buddy
*sbuddy
= uri
? g_hash_table_lookup(sip
->buddies
, uri
) : NULL
;
5628 xn_node
= xmlnode_get_child(xn_category
, "state");
5629 if (!xn_node
) continue;
5630 xn_availability
= xmlnode_get_child(xn_node
, "availability");
5631 if (!xn_availability
) continue;
5632 xn_activity
= xmlnode_get_child(xn_node
, "activity");
5633 xn_meeting_subject
= xmlnode_get_child(xn_node
, "meetingSubject");
5634 xn_meeting_location
= xmlnode_get_child(xn_node
, "meetingLocation");
5636 data
= xmlnode_get_data(xn_availability
);
5637 availability
= atoi(data
);
5640 /* activity, meeting_subject, meeting_location */
5643 g_free(sbuddy
->activity
);
5644 sbuddy
->activity
= NULL
;
5646 const char *token
= xmlnode_get_attrib(xn_activity
, "token");
5647 xmlnode
*xn_custom
= xmlnode_get_child(xn_activity
, "custom");
5650 if (!is_empty(token
)) {
5651 sbuddy
->activity
= g_strdup(sipe_get_activity_desc_by_token(token
));
5653 /* from custom element */
5655 char *custom
= xmlnode_get_data(xn_custom
);
5657 if (!is_empty(custom
)) {
5658 sbuddy
->activity
= custom
;
5664 /* meeting_subject */
5665 g_free(sbuddy
->meeting_subject
);
5666 sbuddy
->meeting_subject
= NULL
;
5667 if (xn_meeting_subject
) {
5668 char *meeting_subject
= xmlnode_get_data(xn_meeting_subject
);
5670 if (!is_empty(meeting_subject
)) {
5671 sbuddy
->meeting_subject
= meeting_subject
;
5672 meeting_subject
= NULL
;
5674 g_free(meeting_subject
);
5676 /* meeting_location */
5677 g_free(sbuddy
->meeting_location
);
5678 sbuddy
->meeting_location
= NULL
;
5679 if (xn_meeting_location
) {
5680 char *meeting_location
= xmlnode_get_data(xn_meeting_location
);
5682 if (!is_empty(meeting_location
)) {
5683 sbuddy
->meeting_location
= meeting_location
;
5684 meeting_location
= NULL
;
5686 g_free(meeting_location
);
5690 status
= sipe_get_status_by_availability(availability
, sbuddy
->activity
);
5691 do_update_status
= TRUE
;
5694 else if(!strcmp(attrVar
, "calendarData"))
5696 struct sipe_buddy
*sbuddy
= uri
? g_hash_table_lookup(sip
->buddies
, uri
) : NULL
;
5697 xmlnode
*xn_free_busy
= xmlnode_get_descendant(xn_category
, "calendarData", "freeBusy", NULL
);
5698 xmlnode
*xn_working_hours
= xmlnode_get_descendant(xn_category
, "calendarData", "WorkingHours", NULL
);
5700 if (sbuddy
&& xn_free_busy
) {
5701 g_free(sbuddy
->cal_start_time
);
5702 sbuddy
->cal_start_time
= g_strdup(xmlnode_get_attrib(xn_free_busy
, "startTime"));
5704 sbuddy
->cal_granularity
= !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy
, "granularity"), "PT15M") ?
5707 g_free(sbuddy
->cal_free_busy_base64
);
5708 sbuddy
->cal_free_busy_base64
= xmlnode_get_data(xn_free_busy
);
5710 g_free(sbuddy
->cal_free_busy
);
5711 sbuddy
->cal_free_busy
= NULL
;
5713 purple_debug_info("sipe", "process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s\n", sbuddy
->cal_start_time
, sbuddy
->cal_granularity
, sbuddy
->cal_free_busy_base64
);
5716 if (sbuddy
&& xn_working_hours
) {
5717 sipe_cal_parse_working_hours(xn_working_hours
, sbuddy
);
5722 if (do_update_status
) {
5723 if (!status
) { /* no status category in this update, using contact's current status */
5724 PurpleBuddy
*pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri
);
5725 const PurplePresence
*presence
= purple_buddy_get_presence(pbuddy
);
5726 const PurpleStatus
*pstatus
= purple_presence_get_active_status(presence
);
5727 status
= purple_status_get_id(pstatus
);
5730 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status
);
5731 sipe_got_user_status(sip
, uri
, status
);
5734 xmlnode_free(xn_categories
);
5737 static void sipe_subscribe_poolfqdn_resource_uri(const char *host
, GSList
*server
, struct sipe_account_data
*sip
)
5739 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
5740 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host
);
5741 payload
->host
= g_strdup(host
);
5742 payload
->buddies
= server
;
5743 sipe_subscribe_presence_batched_routed(sip
, payload
);
5744 sipe_subscribe_presence_batched_routed_free(payload
);
5747 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5750 xmlnode
*xn_resource
;
5751 GHashTable
*servers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
5756 xn_list
= xmlnode_from_str(data
, len
);
5758 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
5760 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
5762 const char *uri
, *state
;
5763 xmlnode
*xn_instance
;
5765 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
5766 if (!xn_instance
) continue;
5768 uri
= xmlnode_get_attrib(xn_resource
, "uri");
5769 state
= xmlnode_get_attrib(xn_instance
, "state");
5770 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri
, state
);
5772 if (strstr(state
, "resubscribe")) {
5773 const char *poolFqdn
= xmlnode_get_attrib(xn_instance
, "poolFqdn");
5775 if (poolFqdn
) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5776 gchar
*user
= g_strdup(uri
);
5777 host
= g_strdup(poolFqdn
);
5778 server
= g_hash_table_lookup(servers
, host
);
5779 server
= g_slist_append(server
, user
);
5780 g_hash_table_insert(servers
, host
, server
);
5782 sipe_subscribe_presence_single(sip
, (void *) uri
);
5787 /* Send out any deferred poolFqdn subscriptions */
5788 g_hash_table_foreach(servers
, (GHFunc
) sipe_subscribe_poolfqdn_resource_uri
, sip
);
5789 g_hash_table_destroy(servers
);
5791 xmlnode_free(xn_list
);
5794 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5798 gchar
*activity
= NULL
;
5800 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
5801 gboolean isonline
= FALSE
;
5802 xmlnode
*display_name_node
;
5804 pidf
= xmlnode_from_str(data
, len
);
5806 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data
);
5810 uri
= sip_uri(xmlnode_get_attrib(pidf
, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5812 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
5814 if ((status
= xmlnode_get_child(tuple
, "status"))) {
5815 basicstatus
= xmlnode_get_child(status
, "basic");
5820 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
5825 getbasic
= xmlnode_get_data(basicstatus
);
5827 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
5832 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic
);
5833 if (strstr(getbasic
, "open")) {
5838 display_name_node
= xmlnode_get_child(pidf
, "display-name");
5839 if (display_name_node
) {
5840 char * display_name
= xmlnode_get_data(display_name_node
);
5842 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
5843 g_free(display_name
);
5846 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
5847 if ((status
= xmlnode_get_child(tuple
, "status"))) {
5848 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
5849 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
5850 activity
= xmlnode_get_data(basicstatus
);
5851 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity
);
5858 const gchar
* status_id
= NULL
;
5860 if (!strcmp(activity
, sipe_activity_map
[SIPE_ACTIVITY_BUSY
].token
)) {
5861 status_id
= SIPE_STATUS_ID_BUSY
;
5862 } else if (!strcmp(activity
, sipe_activity_map
[SIPE_ACTIVITY_AWAY
].token
)) {
5863 status_id
= SIPE_STATUS_ID_AWAY
;
5868 status_id
= SIPE_STATUS_ID_AVAILABLE
;
5871 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id
);
5872 sipe_got_user_status(sip
, uri
, status_id
);
5874 sipe_got_user_status(sip
, uri
, SIPE_STATUS_ID_OFFLINE
);
5884 sipe_user_info_has_updated(struct sipe_account_data
*sip
,
5885 xmlnode
*xn_userinfo
)
5887 if (sip
->user_info
) {
5888 xmlnode_free(sip
->user_info
);
5890 sip
->user_info
= xmlnode_copy(xn_userinfo
);
5892 /* Publish initial state if not yet.
5893 * Assuming this happens on initial responce to self subscription
5894 * so we've already updated our UserInfo.
5896 if (!sip
->initial_state_published
) {
5897 sipe_set_status(sip
->account
, purple_account_get_active_status(sip
->account
));
5899 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
5903 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5905 const char *activity
= NULL
;
5907 const char *status_id
= NULL
;
5910 char *self_uri
= sip_uri_self(sip
);
5913 const char *device_name
= NULL
;
5914 const char *cal_start_time
= NULL
;
5915 const char *cal_granularity
= NULL
;
5916 char *cal_free_busy_base64
= NULL
;
5917 struct sipe_buddy
*sbuddy
;
5919 xmlnode
*xn_presentity
;
5920 xmlnode
*xn_availability
;
5921 xmlnode
*xn_activity
;
5922 xmlnode
*xn_display_name
;
5924 xmlnode
*xn_phone_number
;
5925 xmlnode
*xn_userinfo
;
5929 xmlnode
*xn_contact
;
5931 char *free_activity
;
5933 const char *user_avail_nil
;
5935 time_t user_avail_since
= 0;
5936 time_t activity_since
= 0;
5938 /* fix for Reuters environment on Linux */
5939 if (data
&& strstr(data
, "encoding=\"utf-16\"")) {
5941 tmp_data
= replace(data
, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
5942 xn_presentity
= xmlnode_from_str(tmp_data
, strlen(tmp_data
));
5945 xn_presentity
= xmlnode_from_str(data
, len
);
5948 xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
5949 xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
5950 xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
5951 xn_email
= xmlnode_get_child(xn_presentity
, "email");
5952 xn_phone_number
= xmlnode_get_child(xn_presentity
, "phoneNumber");
5953 xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
5954 xn_oof
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "oof") : NULL
;
5955 xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
): NULL
;
5956 user_avail
= xn_state
? atoi(xmlnode_get_attrib(xn_state
, "avail")) : 0;
5957 user_avail_since
= xn_state
? purple_str_to_time(xmlnode_get_attrib(xn_state
, "since"), FALSE
, NULL
, NULL
, NULL
) : 0;
5958 user_avail_nil
= xn_state
? xmlnode_get_attrib(xn_state
, "nil") : NULL
;
5959 xn_contact
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "contact") : NULL
;
5960 xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
5961 note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
5963 if (user_avail_nil
&& !strcmp(user_avail_nil
, "true")) { /* null-ed */
5965 user_avail_since
= 0;
5968 free_activity
= NULL
;
5970 name
= xmlnode_get_attrib(xn_presentity
, "uri"); /* without 'sip:' prefix */
5971 uri
= sip_uri_from_name(name
);
5972 avl
= atoi(xmlnode_get_attrib(xn_availability
, "aggregate"));
5973 epid
= xmlnode_get_attrib(xn_availability
, "epid");
5974 act
= atoi(xmlnode_get_attrib(xn_activity
, "aggregate"));
5976 status_id
= sipe_get_status_by_act_avail_2005(act
, avl
);
5977 res_avail
= sipe_get_availability_by_status(status_id
, NULL
);
5978 if (user_avail
> res_avail
) {
5979 res_avail
= user_avail
;
5980 status_id
= sipe_get_status_by_availability(user_avail
, NULL
);
5983 if (xn_display_name
) {
5984 char *display_name
= g_strdup(xmlnode_get_attrib(xn_display_name
, "displayName"));
5985 char *email
= xn_email
? g_strdup(xmlnode_get_attrib(xn_email
, "email")) : NULL
;
5986 char *phone_label
= xn_phone_number
? g_strdup(xmlnode_get_attrib(xn_phone_number
, "label")) : NULL
;
5987 char *phone_number
= xn_phone_number
? g_strdup(xmlnode_get_attrib(xn_phone_number
, "number")) : NULL
;
5988 char *tel_uri
= sip_to_tel_uri(phone_number
);
5990 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
5991 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
5992 sipe_update_user_info(sip
, uri
, PHONE_PROP
, tel_uri
);
5993 sipe_update_user_info(sip
, uri
, PHONE_DISPLAY_PROP
, !is_empty(phone_label
) ? phone_label
: phone_number
);
5996 g_free(phone_label
);
5997 g_free(phone_number
);
5999 g_free(display_name
);
6004 for (node
= xmlnode_get_child(xn_contact
, "tel"); node
; node
= xmlnode_get_next_twin(node
))
6006 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6007 const char *phone_type
= xmlnode_get_attrib(node
, "type");
6008 char* phone
= xmlnode_get_data(node
);
6010 sipe_update_user_phone(sip
, uri
, phone_type
, phone
, NULL
);
6016 /* devicePresence */
6017 for (node
= xmlnode_get_descendant(xn_presentity
, "devices", "devicePresence", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
6018 xmlnode
*xn_device_name
;
6019 xmlnode
*xn_calendar_info
;
6024 if (!strcmp(xmlnode_get_attrib(node
, "epid"), epid
)) {
6025 xn_device_name
= xmlnode_get_child(node
, "deviceName");
6026 device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
6030 xn_calendar_info
= xmlnode_get_child(node
, "calendarInfo");
6031 if (xn_calendar_info
) {
6032 const char *cal_start_time_tmp
= xmlnode_get_attrib(xn_calendar_info
, "startTime");
6034 if (cal_start_time
) {
6035 time_t cal_start_time_t
= purple_str_to_time(cal_start_time
, FALSE
, NULL
, NULL
, NULL
);
6036 time_t cal_start_time_t_tmp
= purple_str_to_time(cal_start_time_tmp
, FALSE
, NULL
, NULL
, NULL
);
6038 if (cal_start_time_t_tmp
> cal_start_time_t
) {
6039 cal_start_time
= cal_start_time_tmp
;
6040 cal_granularity
= xmlnode_get_attrib(xn_calendar_info
, "granularity");
6041 cal_free_busy_base64
= xmlnode_get_data(xn_calendar_info
);
6043 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time
, cal_granularity
, cal_free_busy_base64
);
6046 cal_start_time
= cal_start_time_tmp
;
6047 cal_granularity
= xmlnode_get_attrib(xn_calendar_info
, "granularity");
6048 cal_free_busy_base64
= xmlnode_get_data(xn_calendar_info
);
6050 purple_debug_info("sipe", "process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s\n", cal_start_time
, cal_granularity
, cal_free_busy_base64
);
6055 xn_state
= xmlnode_get_descendant(node
, "states", "state", NULL
);
6057 int dev_avail
= atoi(xmlnode_get_attrib(xn_state
, "avail"));
6058 time_t dev_avail_since
= purple_str_to_time(xmlnode_get_attrib(xn_state
, "since"), FALSE
, NULL
, NULL
, NULL
);
6060 state
= xn_state
? xmlnode_get_data(xn_state
) : NULL
;
6061 if (dev_avail_since
> user_avail_since
&&
6062 dev_avail
>= res_avail
)
6064 res_avail
= dev_avail
;
6065 if (!is_empty(state
))
6067 if (!strcmp(state
, sipe_activity_map
[SIPE_ACTIVITY_ON_PHONE
].token
)) {
6068 activity
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE
);
6069 } else if (!strcmp(state
, "presenting")) {
6070 activity
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF
);
6072 activity
= free_activity
= state
;
6075 activity_since
= dev_avail_since
;
6077 status_id
= sipe_get_status_by_availability(res_avail
, activity
);
6084 if (xn_oof
&& res_avail
>= 15000) { /* 12000 in 2007 */
6085 activity
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF
);
6089 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
6092 g_free(sbuddy
->activity
);
6093 sbuddy
->activity
= NULL
;
6094 if (!is_empty(activity
)) { sbuddy
->activity
= g_strdup(activity
); }
6096 sbuddy
->activity_since
= activity_since
;
6098 sbuddy
->user_avail
= user_avail
;
6099 sbuddy
->user_avail_since
= user_avail_since
;
6101 g_free(sbuddy
->annotation
);
6102 sbuddy
->annotation
= NULL
;
6103 if (!is_empty(note
)) { sbuddy
->annotation
= g_strdup(note
); }
6105 sbuddy
->is_oof_note
= (xn_oof
!= NULL
);
6107 g_free(sbuddy
->device_name
);
6108 sbuddy
->device_name
= NULL
;
6109 if (!is_empty(device_name
)) { sbuddy
->device_name
= g_strdup(device_name
); }
6111 if (!is_empty(cal_free_busy_base64
)) {
6112 g_free(sbuddy
->cal_start_time
);
6113 sbuddy
->cal_start_time
= g_strdup(cal_start_time
);
6115 sbuddy
->cal_granularity
= !g_ascii_strcasecmp(cal_granularity
, "PT15M") ? 15 : 0;
6117 g_free(sbuddy
->cal_free_busy_base64
);
6118 sbuddy
->cal_free_busy_base64
= cal_free_busy_base64
;
6120 g_free(sbuddy
->cal_free_busy
);
6121 sbuddy
->cal_free_busy
= NULL
;
6124 sbuddy
->last_non_cal_status_id
= status_id
;
6125 g_free(sbuddy
->last_non_cal_activity
);
6126 sbuddy
->last_non_cal_activity
= g_strdup(sbuddy
->activity
);
6129 if (free_activity
) g_free(free_activity
);
6131 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id
);
6132 sipe_got_user_status(sip
, uri
, status_id
);
6134 if (!sip
->ocs2007
&& !strcmp(self_uri
, uri
)) {
6135 sipe_user_info_has_updated(sip
, xn_userinfo
);
6139 xmlnode_free(xn_presentity
);
6144 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
6146 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
6148 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
6150 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
6151 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
6153 const char *content
= msg
->body
;
6154 unsigned length
= msg
->bodylen
;
6155 PurpleMimeDocument
*mime
= NULL
;
6157 if (strstr(ctype
, "multipart"))
6159 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
6160 const char *content_type
;
6162 mime
= purple_mime_document_parse(doc
);
6163 parts
= purple_mime_document_get_parts(mime
);
6165 content
= purple_mime_part_get_data(parts
->data
);
6166 length
= purple_mime_part_get_length(parts
->data
);
6167 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
6168 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
6170 process_incoming_notify_rlmi_resub(sip
, content
, length
);
6172 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
6174 process_incoming_notify_msrtc(sip
, content
, length
);
6178 process_incoming_notify_rlmi(sip
, content
, length
);
6180 parts
= parts
->next
;
6186 purple_mime_document_free(mime
);
6189 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
6191 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
6193 else if(strstr(ctype
, "application/rlmi+xml"))
6195 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
6198 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
6200 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
6204 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
6208 static void sipe_process_presence_timeout(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gchar
*who
, int timeout
)
6210 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
6211 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
6213 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype
? ctype
: "");
6216 strstr(ctype
, "multipart") &&
6217 (strstr(ctype
, "application/rlmi+xml") ||
6218 strstr(ctype
, "application/msrtc-event-categories+xml"))) {
6219 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
6220 PurpleMimeDocument
*mime
= purple_mime_document_parse(doc
);
6221 GList
*parts
= purple_mime_document_get_parts(mime
);
6222 GSList
*buddies
= NULL
;
6223 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
6226 xmlnode
*xml
= xmlnode_from_str(purple_mime_part_get_data(parts
->data
),
6227 purple_mime_part_get_length(parts
->data
));
6229 if (strcmp(xml
->name
, "list")) {
6230 gchar
*uri
= sip_uri(xmlnode_get_attrib(xml
, "uri"));
6232 buddies
= g_slist_append(buddies
, uri
);
6236 parts
= parts
->next
;
6239 if (mime
) purple_mime_document_free(mime
);
6241 payload
->host
= g_strdup(who
);
6242 payload
->buddies
= buddies
;
6243 sipe_schedule_action(action_name
, timeout
,
6244 sipe_subscribe_presence_batched_routed
,
6245 sipe_subscribe_presence_batched_routed_free
,
6247 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who
, timeout
);
6250 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(who
));
6251 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
, timeout
);
6253 g_free(action_name
);
6257 * Dispatcher for all incoming subscription information
6258 * whether it comes from NOTIFY, BENOTIFY requests or
6259 * piggy-backed to subscription's OK responce.
6261 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6262 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6264 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
6266 gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
6267 gchar
*event
= sipmsg_find_header(msg
, "Event");
6268 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
6272 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6274 tmp
= fix_newlines(msg
->body
));
6276 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state
? subscription_state
: "");
6278 /* implicit subscriptions */
6279 if (content_type
&& purple_str_has_prefix(content_type
, "application/ms-imdn+xml")) {
6280 sipe_process_imdn(sip
, msg
);
6285 const gchar
*expires_header
;
6286 expires_header
= sipmsg_find_header(msg
, "Expires");
6287 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
6288 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout
);
6289 timeout
= (timeout
- 120) > 120 ? (timeout
- 120) : timeout
; // 2 min ahead of expiration
6292 /* for one off subscriptions (send with Expire: 0) */
6293 if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-provisioning-v2"))
6295 sipe_process_provisioning_v2(sip
, msg
);
6297 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-provisioning"))
6299 sipe_process_provisioning(sip
, msg
);
6302 if (!subscription_state
|| strstr(subscription_state
, "active"))
6304 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
6306 sipe_process_presence(sip
, msg
);
6308 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
6310 sipe_process_roaming_contacts(sip
, msg
);
6312 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self"))
6314 sipe_process_roaming_self(sip
, msg
);
6316 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
6318 sipe_process_roaming_acl(sip
, msg
);
6320 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
6322 sipe_process_presence_wpending(sip
, msg
);
6324 else if (event
&& !g_ascii_strcasecmp(event
, "conference"))
6326 sipe_process_conference(sip
, msg
);
6330 /* The server sends status 'terminated' */
6331 if (subscription_state
&& strstr(subscription_state
, "terminated") ) {
6332 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
6333 gchar
*key
= sipe_get_subscription_key(event
, who
);
6335 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who
);
6338 if (g_hash_table_lookup(sip
->subscriptions
, key
)) {
6339 g_hash_table_remove(sip
->subscriptions
, key
);
6340 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key
);
6346 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
6347 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6348 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6350 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6351 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6352 g_free(action_name);
6354 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6355 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6357 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6358 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6359 g_free(action_name);
6362 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
6363 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
6365 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
6366 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, NULL
, sip
, NULL
);
6367 g_free(action_name
);
6369 else if (!g_ascii_strcasecmp(event
, "presence") &&
6370 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
6372 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
6373 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
6374 if (sip
->batched_support
) {
6375 sipe_process_presence_timeout(sip
, msg
, who
, timeout
);
6378 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(who
));
6379 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
, timeout
);
6381 g_free(action_name
);
6386 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
6388 sipe_process_registration_notify(sip
, msg
);
6391 /* The client responses on received a NOTIFY message */
6392 if (request
&& !benotify
)
6394 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
6399 * Whether user manually changed status or
6400 * it was changed automatically due to user
6401 * became inactive/active again
6404 sipe_is_user_state(struct sipe_account_data
*sip
)
6406 gboolean res
= (sip
->was_idle
== sip
->is_idle
);
6411 send_presence_soap0(struct sipe_account_data
*sip
,
6412 gboolean do_publish_calendar
,
6413 gboolean do_reset_status
)
6415 struct sipe_ews
* ews
= sip
->ews
;
6416 int availability
= 0;
6420 gchar
*res_note
= NULL
;
6421 gchar
*res_oof
= NULL
;
6422 const gchar
*note_pub
= NULL
;
6423 gchar
*states
= NULL
;
6424 gchar
*calendar_data
= NULL
;
6425 gchar
*epid
= get_epid(sip
);
6426 time_t now
= time(NULL
);
6427 gchar
*since_time_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&now
)));
6428 const gchar
*oof_note
= sipe_ews_get_oof_note(ews
);
6429 const char *user_input
;
6431 if (!sip
->initial_state_published
||
6434 g_free(sip
->status
);
6435 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
6438 sipe_get_act_avail_by_status_2005(sip
->status
, &activity
, &availability
);
6442 note_pub
= oof_note
;
6443 res_oof
= SIPE_SOAP_SET_PRESENCE_OOF_XML
;
6444 } else if (sip
->note
) {
6445 note_pub
= sip
->note
;
6450 res_note
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML
, note_pub
);
6452 else if (!(ews
&& ews
->is_updated
)) /* preserve existing publication */
6455 if (sip
->user_info
&& (xn_note
= xmlnode_get_child(sip
->user_info
, "note"))) {
6456 res_note
= xmlnode_to_str(xn_note
, NULL
);
6459 if (sip
->user_info
&& xmlnode_get_child(sip
->user_info
, "oof")) {
6460 res_oof
= SIPE_SOAP_SET_PRESENCE_OOF_XML
;
6465 if (!do_reset_status
) {
6466 if (sipe_is_user_state(sip
) && !do_publish_calendar
&& sip
->initial_state_published
)
6468 gchar
*activity_token
= NULL
;
6469 int avail_2007
= sipe_get_availability_by_status(sip
->status
, &activity_token
);
6471 states
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES
,
6476 g_free(activity_token
);
6478 else /* preserve existing publication */
6481 if (sip
->user_info
&& (xn_states
= xmlnode_get_child(sip
->user_info
, "states"))) {
6482 states
= xmlnode_to_str(xn_states
, NULL
);
6486 /* do nothing - then User state will be erased */
6488 sip
->initial_state_published
= TRUE
;
6491 if (ews
&& (!is_empty(ews
->legacy_dn
) || !is_empty(ews
->email
)) && ews
->fb_start
&& !is_empty(ews
->free_busy
))
6493 char *fb_start_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&ews
->fb_start
)));
6494 char *free_busy_base64
= sipe_cal_get_freebusy_base64(ews
->free_busy
);
6495 calendar_data
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR
,
6496 !is_empty(ews
->legacy_dn
) ? ews
->legacy_dn
: ews
->email
,
6499 g_free(fb_start_str
);
6500 g_free(free_busy_base64
);
6503 user_input
= !sipe_is_user_state(sip
) && sip
->status
!= SIPE_STATUS_ID_AVAILABLE
? "idle" : "active";
6505 /* forming resulting XML */
6506 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
,
6510 (tmp
= g_ascii_strup(sipe_get_host_name(), -1)),
6511 res_note
? res_note
: "",
6512 res_oof
? res_oof
: "",
6513 states
? states
: "",
6514 calendar_data
? calendar_data
: "",
6522 g_free(calendar_data
);
6524 send_soap_request(sip
, body
);
6527 g_free(since_time_str
);
6532 send_presence_soap(struct sipe_account_data
*sip
,
6533 gboolean do_publish_calendar
)
6535 return send_presence_soap0(sip
, do_publish_calendar
, FALSE
);
6540 process_send_presence_category_publish_response(struct sipe_account_data
*sip
,
6542 struct transaction
*trans
)
6544 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
6546 if (msg
->response
== 409 && g_str_has_prefix(contenttype
, "application/msrtc-fault+xml")) {
6552 gboolean has_device_publication
= FALSE
;
6554 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
6556 /* test if version mismatch fault */
6557 fault_code
= xmlnode_get_data(xmlnode_get_child(xml
, "Faultcode"));
6558 if (strcmp(fault_code
, "Client.BadCall.WrongDelta")) {
6559 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code
);
6566 /* accumulating information about faulty versions */
6567 faults
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
6568 for (node
= xmlnode_get_descendant(xml
, "details", "operation", NULL
);
6570 node
= xmlnode_get_next_twin(node
))
6572 const gchar
*index
= xmlnode_get_attrib(node
, "index");
6573 const gchar
*curVersion
= xmlnode_get_attrib(node
, "curVersion");
6575 g_hash_table_insert(faults
, g_strdup(index
), g_strdup(curVersion
));
6576 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index
, curVersion
);
6580 /* here we are parsing own request to figure out what publication
6581 * referensed here only by index went wrong
6583 xml
= xmlnode_from_str(trans
->msg
->body
, trans
->msg
->bodylen
);
6586 for (node
= xmlnode_get_descendant(xml
, "publications", "publication", NULL
),
6587 index_our
= 1; /* starts with 1 - our first publication */
6589 node
= xmlnode_get_next_twin(node
), index_our
++)
6591 gchar
*idx
= g_strdup_printf("%d", index_our
);
6592 const gchar
*curVersion
= g_hash_table_lookup(faults
, idx
);
6593 const gchar
*categoryName
= xmlnode_get_attrib(node
, "categoryName");
6596 if (!strcmp("device", categoryName
)) {
6597 has_device_publication
= TRUE
;
6600 if (curVersion
) { /* fault exist on this index */
6601 const gchar
*container
= xmlnode_get_attrib(node
, "container");
6602 const gchar
*instance
= xmlnode_get_attrib(node
, "instance");
6603 /* key is <category><instance><container> */
6604 gchar
*key
= g_strdup_printf("<%s><%s><%s>", categoryName
, instance
, container
);
6605 struct sipe_publication
*publication
=
6606 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, categoryName
), key
);
6608 purple_debug_info("sipe", "key is %s\n", key
);
6611 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6612 key
, curVersion
, publication
->version
);
6613 /* updating publication's version to the correct one */
6614 publication
->version
= atoi(curVersion
);
6620 g_hash_table_destroy(faults
);
6622 /* rebublishing with right versions */
6623 if (has_device_publication
) {
6624 send_publish_category_initial(sip
);
6626 send_presence_status(sip
);
6633 * Returns 'device' XML part for publication.
6634 * Must be g_free'd after use.
6637 sipe_publish_get_category_device(struct sipe_account_data
*sip
)
6641 gchar
*epid
= get_epid(sip
);
6642 gchar
*uuid
= generateUUIDfromEPID(epid
);
6643 guint device_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_DEVICE
);
6644 /* key is <category><instance><container> */
6645 gchar
*key
= g_strdup_printf("<%s><%u><%u>", "device", device_instance
, 2);
6646 struct sipe_publication
*publication
=
6647 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "device"), key
);
6652 uri
= sip_uri_self(sip
);
6653 doc
= g_strdup_printf(SIPE_PUB_XML_DEVICE
,
6655 publication
? publication
->version
: 0,
6658 "00:00:00+01:00", /* @TODO make timezone real*/
6659 sipe_get_host_name()
6669 * A service method - use
6670 * - send_publish_get_category_state_machine and
6671 * - send_publish_get_category_state_user instead.
6672 * Must be g_free'd after use.
6675 sipe_publish_get_category_state(struct sipe_account_data
*sip
,
6676 gboolean is_user_state
)
6678 int availability
= sipe_get_availability_by_status(sip
->status
, NULL
);
6679 guint instance
= is_user_state
? sipe_get_pub_instance(sip
, SIPE_PUB_STATE_USER
) :
6680 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_MACHINE
);
6681 /* key is <category><instance><container> */
6682 gchar
*key_2
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 2);
6683 gchar
*key_3
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 3);
6684 struct sipe_publication
*publication_2
=
6685 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_2
);
6686 struct sipe_publication
*publication_3
=
6687 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_3
);
6692 if (publication_2
&& (publication_2
->availability
== availability
))
6694 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6695 return NULL
; /* nothing to update */
6698 return g_strdup_printf( is_user_state
? SIPE_PUB_XML_STATE_USER
: SIPE_PUB_XML_STATE_MACHINE
,
6700 publication_2
? publication_2
->version
: 0,
6703 publication_3
? publication_3
->version
: 0,
6708 * Only Busy and OOF calendar event are published.
6709 * Different instances are used for that.
6711 * Must be g_free'd after use.
6714 sipe_publish_get_category_state_calendar(struct sipe_account_data
*sip
,
6715 struct sipe_cal_event
*event
,
6719 gchar
*start_time_str
;
6720 int availability
= 0;
6722 guint instance
= (cal_satus
== SIPE_CAL_OOF
) ?
6723 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR_OOF
) :
6724 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR
);
6726 /* key is <category><instance><container> */
6727 gchar
*key_2
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 2);
6728 gchar
*key_3
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 3);
6729 struct sipe_publication
*publication_2
=
6730 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_2
);
6731 struct sipe_publication
*publication_3
=
6732 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_3
);
6737 if (!publication_3
&& !event
) { /* was nothing, have nothing, exiting */
6738 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6739 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus
);
6745 (publication_3
->availability
== availability
) &&
6746 !strcmp(publication_3
->cal_event_hash
, sipe_cal_event_hash(event
)))
6748 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6749 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus
);
6750 return NULL
; /* nothing to update */
6754 (event
->cal_status
== SIPE_CAL_BUSY
||
6755 event
->cal_status
== SIPE_CAL_OOF
))
6757 gchar
*availability_xml_str
= NULL
;
6758 gchar
*activity_xml_str
= NULL
;
6760 if (event
->cal_status
== SIPE_CAL_BUSY
) {
6761 availability_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL
, 6500);
6764 if (event
->cal_status
== SIPE_CAL_BUSY
&& event
->is_meeting
) {
6765 activity_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
,
6766 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].token
,
6767 "minAvailability=\"6500\"",
6768 "maxAvailability=\"8999\"");
6769 } else if (event
->cal_status
== SIPE_CAL_OOF
) {
6770 activity_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
,
6771 sipe_activity_map
[SIPE_ACTIVITY_OOF
].token
,
6772 "minAvailability=\"12000\"",
6775 start_time_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&event
->start_time
)));
6777 res
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR
,
6779 publication_2
? publication_2
->version
: 0,
6782 availability_xml_str
? availability_xml_str
: "",
6783 activity_xml_str
? activity_xml_str
: "",
6784 event
->subject
? event
->subject
: "",
6785 event
->location
? event
->location
: "",
6788 publication_3
? publication_3
->version
: 0,
6791 availability_xml_str
? availability_xml_str
: "",
6792 activity_xml_str
? activity_xml_str
: "",
6793 event
->subject
? event
->subject
: "",
6794 event
->location
? event
->location
: ""
6796 g_free(start_time_str
);
6797 g_free(availability_xml_str
);
6798 g_free(activity_xml_str
);
6801 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6803 res
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR
,
6805 publication_2
? publication_2
->version
: 0,
6808 publication_3
? publication_3
->version
: 0
6816 * Returns 'machineState' XML part for publication.
6817 * Must be g_free'd after use.
6820 sipe_publish_get_category_state_machine(struct sipe_account_data
*sip
)
6822 return sipe_publish_get_category_state(sip
, FALSE
);
6826 * Returns 'userState' XML part for publication.
6827 * Must be g_free'd after use.
6830 sipe_publish_get_category_state_user(struct sipe_account_data
*sip
)
6832 return sipe_publish_get_category_state(sip
, TRUE
);
6836 * Compares two strings even in case both are NULL/empty
6839 sipe_is_equal(const char* n1
, const char* n2
) {
6840 return ((!n1
|| !strlen(n1
)) && (!n2
|| !strlen(n2
))) /* both empty */
6841 || (n1
&& n2
&& !strcmp(n1
, n2
)); /* or not empty and equal */
6845 * Returns 'note' XML part for publication.
6846 * Must be g_free'd after use.
6848 * @param note_type either personal or OOF
6851 sipe_publish_get_category_note(struct sipe_account_data
*sip
,
6853 const char *note_type
)
6855 guint instance
= !strcmp("OOF", note_type
) ? sipe_get_pub_instance(sip
, SIPE_PUB_NOTE_OOF
) : 0;
6856 /* key is <category><instance><container> */
6857 gchar
*key_note_200
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 200);
6858 gchar
*key_note_300
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 300);
6859 gchar
*key_note_400
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 400);
6861 struct sipe_publication
*publication_note_200
=
6862 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_200
);
6863 struct sipe_publication
*publication_note_300
=
6864 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_300
);
6865 struct sipe_publication
*publication_note_400
=
6866 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_400
);
6868 const char *n1
= note
;
6869 const char *n2
= publication_note_200
? publication_note_200
->note
: NULL
;
6871 g_free(key_note_200
);
6872 g_free(key_note_300
);
6873 g_free(key_note_400
);
6875 if (sipe_is_equal(n1
, n2
))
6877 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
6878 return NULL
; /* nothing to update */
6881 return g_markup_printf_escaped(SIPE_PUB_XML_NOTE
,
6883 publication_note_200
? publication_note_200
->version
: 0,
6888 publication_note_300
? publication_note_300
->version
: 0,
6893 publication_note_400
? publication_note_400
->version
: 0,
6899 * Returns 'calendarData' XML part with WorkingHours for publication.
6900 * Must be g_free'd after use.
6903 sipe_publish_get_category_cal_working_hours(struct sipe_account_data
*sip
)
6905 struct sipe_ews
* ews
= sip
->ews
;
6907 /* key is <category><instance><container> */
6908 gchar
*key_cal_1
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
6909 gchar
*key_cal_100
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
6910 gchar
*key_cal_200
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
6911 gchar
*key_cal_300
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
6912 gchar
*key_cal_400
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
6913 gchar
*key_cal_32000
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
6915 struct sipe_publication
*publication_cal_1
=
6916 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_1
);
6917 struct sipe_publication
*publication_cal_100
=
6918 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_100
);
6919 struct sipe_publication
*publication_cal_200
=
6920 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_200
);
6921 struct sipe_publication
*publication_cal_300
=
6922 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_300
);
6923 struct sipe_publication
*publication_cal_400
=
6924 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_400
);
6925 struct sipe_publication
*publication_cal_32000
=
6926 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_32000
);
6928 const char *n1
= ews
->working_hours_xml_str
;
6929 const char *n2
= publication_cal_300
? publication_cal_300
->working_hours_xml_str
: NULL
;
6932 g_free(key_cal_100
);
6933 g_free(key_cal_200
);
6934 g_free(key_cal_300
);
6935 g_free(key_cal_400
);
6936 g_free(key_cal_32000
);
6938 if (!ews
|| is_empty(ews
->email
) || is_empty(ews
->working_hours_xml_str
)) {
6939 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
6943 if (sipe_is_equal(n1
, n2
))
6945 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
6946 return NULL
; /* nothing to update */
6949 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS
,
6951 publication_cal_1
? publication_cal_1
->version
: 0,
6953 ews
->working_hours_xml_str
,
6955 publication_cal_100
? publication_cal_100
->version
: 0,
6957 publication_cal_200
? publication_cal_200
->version
: 0,
6959 ews
->working_hours_xml_str
,
6961 publication_cal_300
? publication_cal_300
->version
: 0,
6963 ews
->working_hours_xml_str
,
6964 /* 400 - Personal */
6965 publication_cal_400
? publication_cal_400
->version
: 0,
6967 ews
->working_hours_xml_str
,
6968 /* 32000 - Blocked */
6969 publication_cal_32000
? publication_cal_32000
->version
: 0
6974 * Returns 'calendarData' XML part with FreeBusy for publication.
6975 * Must be g_free'd after use.
6978 sipe_publish_get_category_cal_free_busy(struct sipe_account_data
*sip
)
6980 struct sipe_ews
* ews
= sip
->ews
;
6981 guint cal_data_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_CALENDAR_DATA
);
6983 char *free_busy_base64
;
6988 /* key is <category><instance><container> */
6989 gchar
*key_cal_1
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 1);
6990 gchar
*key_cal_100
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 100);
6991 gchar
*key_cal_200
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 200);
6992 gchar
*key_cal_300
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 300);
6993 gchar
*key_cal_400
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 400);
6994 gchar
*key_cal_32000
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 32000);
6996 struct sipe_publication
*publication_cal_1
=
6997 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_1
);
6998 struct sipe_publication
*publication_cal_100
=
6999 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_100
);
7000 struct sipe_publication
*publication_cal_200
=
7001 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_200
);
7002 struct sipe_publication
*publication_cal_300
=
7003 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_300
);
7004 struct sipe_publication
*publication_cal_400
=
7005 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_400
);
7006 struct sipe_publication
*publication_cal_32000
=
7007 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_32000
);
7010 g_free(key_cal_100
);
7011 g_free(key_cal_200
);
7012 g_free(key_cal_300
);
7013 g_free(key_cal_400
);
7014 g_free(key_cal_32000
);
7016 if (!ews
|| is_empty(ews
->email
) || !ews
->fb_start
|| is_empty(ews
->free_busy
)) {
7017 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7021 fb_start_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&ews
->fb_start
)));
7022 free_busy_base64
= sipe_cal_get_freebusy_base64(ews
->free_busy
);
7024 st
= publication_cal_300
? publication_cal_300
->fb_start_str
: NULL
;
7025 fb
= publication_cal_300
? publication_cal_300
->free_busy_base64
: NULL
;
7027 if (sipe_is_equal(st
, fb_start_str
) && sipe_is_equal(fb
, free_busy_base64
))
7029 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7030 g_free(fb_start_str
);
7031 g_free(free_busy_base64
);
7032 return NULL
; /* nothing to update */
7035 res
= g_strdup_printf(SIPE_PUB_XML_FREE_BUSY
,
7038 publication_cal_1
? publication_cal_1
->version
: 0,
7041 publication_cal_100
? publication_cal_100
->version
: 0,
7044 publication_cal_200
? publication_cal_200
->version
: 0,
7050 publication_cal_300
? publication_cal_300
->version
: 0,
7054 /* 400 - Personal */
7056 publication_cal_400
? publication_cal_400
->version
: 0,
7060 /* 32000 - Blocked */
7062 publication_cal_32000
? publication_cal_32000
->version
: 0
7065 g_free(fb_start_str
);
7066 g_free(free_busy_base64
);
7070 static void send_presence_publish(struct sipe_account_data
*sip
, const char *publications
)
7077 uri
= sip_uri_self(sip
);
7078 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
,
7082 tmp
= get_contact(sip
);
7083 hdr
= g_strdup_printf("Contact: %s\r\n"
7084 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
7086 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
7095 send_publish_category_initial(struct sipe_account_data
*sip
)
7097 gchar
*pub_device
= sipe_publish_get_category_device(sip
);
7099 gchar
*publications
;
7101 g_free(sip
->status
);
7102 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
); /* our initial state */
7104 pub_machine
= sipe_publish_get_category_state_machine(sip
);
7105 publications
= g_strdup_printf("%s%s",
7107 pub_machine
? pub_machine
: "");
7109 g_free(pub_machine
);
7111 send_presence_publish(sip
, publications
);
7112 g_free(publications
);
7116 send_presence_category_publish(struct sipe_account_data
*sip
,
7119 gchar
*pub_state
= sipe_is_user_state(sip
) ?
7120 sipe_publish_get_category_state_user(sip
) :
7121 sipe_publish_get_category_state_machine(sip
);
7122 gchar
*pub_note
= sipe_publish_get_category_note(sip
, note
, "personal");
7123 gchar
*publications
;
7125 if (!pub_state
&& !pub_note
) {
7126 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7130 publications
= g_strdup_printf("%s%s",
7131 pub_state
? pub_state
: "",
7132 pub_note
? pub_note
: "");
7134 purple_debug_info("sipe", "send_presence_category_publish: sip->status: %s sip->is_idle:%s sip->was_idle:%s\n",
7135 sip
->status
, sip
->is_idle
? "Y" : "N", sip
->was_idle
? "Y" : "N");
7140 send_presence_publish(sip
, publications
);
7141 g_free(publications
);
7145 * Publishes self status
7146 * based on own calendar information.
7151 publish_calendar_status_self(struct sipe_account_data
*sip
)
7153 struct sipe_cal_event
* event
= NULL
;
7154 gchar
*pub_cal_working_hours
= NULL
;
7155 gchar
*pub_cal_free_busy
= NULL
;
7156 gchar
*pub_calendar
= NULL
;
7157 gchar
*pub_calendar2
= NULL
;
7158 gchar
*pub_oof_note
= NULL
;
7159 const gchar
*oof_note
;
7160 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7162 if (sip
->ews
&& sip
->ews
->cal_events
) {
7163 event
= sipe_cal_get_event(sip
->ews
->cal_events
, time(NULL
));
7167 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7169 char *desc
= sipe_cal_event_describe(event
);
7170 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc
? desc
: "");
7176 OOF publish, Busy clean
7178 OOF clean, Busy publish
7180 OOF clean, Busy clean
7182 if (event
&& event
->cal_status
== SIPE_CAL_OOF
) {
7183 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, event
, sip
->ews
->email
, SIPE_CAL_OOF
);
7184 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7185 } else if (event
&& event
->cal_status
== SIPE_CAL_BUSY
) {
7186 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_OOF
);
7187 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, event
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7189 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_OOF
);
7190 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7193 if ((oof_note
= sipe_ews_get_oof_note(sip
->ews
))) {
7194 pub_oof_note
= sipe_publish_get_category_note(sip
, oof_note
, "OOF");
7197 pub_cal_working_hours
= sipe_publish_get_category_cal_working_hours(sip
);
7198 pub_cal_free_busy
= sipe_publish_get_category_cal_free_busy(sip
);
7200 if (!pub_cal_working_hours
&& !pub_cal_free_busy
&& !pub_calendar
&& !pub_calendar2
&& !pub_oof_note
) {
7201 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7203 gchar
*publications
= g_strdup_printf("%s%s%s%s%s",
7204 pub_cal_working_hours
? pub_cal_working_hours
: "",
7205 pub_cal_free_busy
? pub_cal_free_busy
: "",
7206 pub_calendar
? pub_calendar
: "",
7207 pub_calendar2
? pub_calendar2
: "",
7208 pub_oof_note
? pub_oof_note
: "");
7210 send_presence_publish(sip
, publications
);
7211 g_free(publications
);
7214 g_free(pub_cal_working_hours
);
7215 g_free(pub_cal_free_busy
);
7216 g_free(pub_calendar
);
7217 g_free(pub_calendar2
);
7218 g_free(pub_oof_note
);
7220 /* repeat scheduling */
7221 sipe_sched_calendar_status_self_publish(sip
, time(NULL
));
7224 static void send_presence_status(struct sipe_account_data
*sip
)
7226 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
7228 if (!status
) return;
7230 note
= purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
);
7231 purple_debug_info("sipe", "send_presence_status: status: '%s'\n", purple_status_get_id(status
) ? purple_status_get_id(status
) : "");
7232 purple_debug_info("sipe", "send_presence_status: note: '%s'\n", note
? note
: "");
7235 send_presence_category_publish(sip
, note
);
7237 send_presence_soap(sip
, FALSE
);
7241 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
7243 gboolean found
= FALSE
;
7244 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
7245 if (msg
->response
== 0) { /* request */
7246 if (!strcmp(msg
->method
, "MESSAGE")) {
7247 process_incoming_message(sip
, msg
);
7249 } else if (!strcmp(msg
->method
, "NOTIFY")) {
7250 purple_debug_info("sipe","send->process_incoming_notify\n");
7251 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
7253 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
7254 purple_debug_info("sipe","send->process_incoming_benotify\n");
7255 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
7257 } else if (!strcmp(msg
->method
, "INVITE")) {
7258 process_incoming_invite(sip
, msg
);
7260 } else if (!strcmp(msg
->method
, "REFER")) {
7261 process_incoming_refer(sip
, msg
);
7263 } else if (!strcmp(msg
->method
, "OPTIONS")) {
7264 process_incoming_options(sip
, msg
);
7266 } else if (!strcmp(msg
->method
, "INFO")) {
7267 process_incoming_info(sip
, msg
);
7269 } else if (!strcmp(msg
->method
, "ACK")) {
7270 // ACK's don't need any response
7272 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
7273 // LCS 2005 sends us these - just respond 200 OK
7275 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
7276 } else if (!strcmp(msg
->method
, "BYE")) {
7277 process_incoming_bye(sip
, msg
);
7280 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
7282 } else { /* response */
7283 struct transaction
*trans
= transactions_find(sip
, msg
);
7285 if (msg
->response
== 407) {
7286 gchar
*resend
, *auth
, *ptmp
;
7288 if (sip
->proxy
.retries
> 30) return;
7289 sip
->proxy
.retries
++;
7290 /* do proxy authentication */
7292 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
7294 fill_auth(ptmp
, &sip
->proxy
);
7295 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
7296 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
7297 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
7299 resend
= sipmsg_to_string(trans
->msg
);
7300 /* resend request */
7301 sendout_pkt(sip
->gc
, resend
);
7304 if (msg
->response
< 200) {
7305 /* ignore provisional response */
7306 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg
->response
);
7308 sip
->proxy
.retries
= 0;
7309 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
7310 if (msg
->response
== 401)
7312 sip
->registrar
.retries
++;
7316 sip
->registrar
.retries
= 0;
7318 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip
->cseq
);
7320 if (msg
->response
== 401) {
7321 gchar
*resend
, *auth
, *ptmp
;
7323 if (sip
->registrar
.retries
> 4) return;
7324 sip
->registrar
.retries
++;
7327 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
7329 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
7332 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
7336 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\n", ptmp
);
7338 fill_auth(ptmp
, &sip
->registrar
);
7339 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
7340 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
7341 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
7343 //sipmsg_remove_header_now(trans->msg, "Authorization");
7344 //sipmsg_add_header(trans->msg, "Authorization", auth);
7346 resend
= sipmsg_to_string(trans
->msg
);
7347 /* resend request */
7348 sendout_pkt(sip
->gc
, resend
);
7353 if (trans
->callback
) {
7354 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\n");
7355 /* call the callback to process response*/
7356 (trans
->callback
)(sip
, msg
, trans
);
7359 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\n", sip
->cseq
);
7360 transactions_remove(sip
, trans
);
7366 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
7370 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
7374 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
7383 /* according to the RFC remove CRLF at the beginning */
7384 while (*cur
== '\r' || *cur
== '\n') {
7387 if (cur
!= conn
->inbuf
) {
7388 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
7389 conn
->inbufused
= strlen(conn
->inbuf
);
7392 /* Received a full Header? */
7393 sip
->processing_input
= TRUE
;
7394 while (sip
->processing_input
&&
7395 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
7396 time_t currtime
= time(NULL
);
7399 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
7401 msg
= sipmsg_parse_header(conn
->inbuf
);
7404 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
7405 if (msg
&& restlen
>= msg
->bodylen
) {
7406 dummy
= g_malloc(msg
->bodylen
+ 1);
7407 memcpy(dummy
, cur
, msg
->bodylen
);
7408 dummy
[msg
->bodylen
] = '\0';
7410 cur
+= msg
->bodylen
;
7411 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
7412 conn
->inbufused
= strlen(conn
->inbuf
);
7415 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
7422 purple_debug_info("sipe", "body:\n%s", msg->body);
7425 // Verify the signature before processing it
7426 if (sip
->registrar
.gssapi_context
) {
7427 struct sipmsg_breakdown msgbd
;
7428 gchar
*signature_input_str
;
7431 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
7432 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
7434 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
7436 if (rspauth
!= NULL
) {
7437 if (!sip_sec_verify_signature(sip
->registrar
.gssapi_context
, signature_input_str
, rspauth
)) {
7438 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
7439 process_input_message(sip
, msg
);
7441 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid.\n");
7442 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
7443 sip
->gc
->wants_to_die
= TRUE
;
7445 } else if (msg
->response
== 401) {
7446 purple_connection_error(sip
->gc
, _("Wrong password"));
7447 sip
->gc
->wants_to_die
= TRUE
;
7449 g_free(signature_input_str
);
7452 sipmsg_breakdown_free(&msgbd
);
7454 process_input_message(sip
, msg
);
7461 static void sipe_udp_process(gpointer data
, gint source
,
7462 SIPE_UNUSED_PARAMETER PurpleInputCondition con
)
7464 PurpleConnection
*gc
= data
;
7465 struct sipe_account_data
*sip
= gc
->proto_data
;
7470 static char buffer
[65536];
7471 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
7473 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime
), buffer
);
7474 msg
= sipmsg_parse_msg(buffer
);
7475 if (msg
) process_input_message(sip
, msg
);
7479 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
7481 struct sipe_account_data
*sip
= gc
->proto_data
;
7482 PurpleSslConnection
*gsc
= sip
->gsc
;
7484 purple_debug_error("sipe", "%s",debug
);
7485 purple_connection_error(gc
, msg
);
7487 /* Invalidate this connection. Next send will open a new one */
7489 connection_remove(sip
, gsc
->fd
);
7490 purple_ssl_close(gsc
);
7496 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
7497 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7499 PurpleConnection
*gc
= data
;
7500 struct sipe_account_data
*sip
;
7501 struct sip_connection
*conn
;
7503 gboolean firstread
= TRUE
;
7505 /* NOTE: This check *IS* necessary */
7506 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
7507 purple_ssl_close(gsc
);
7511 sip
= gc
->proto_data
;
7512 conn
= connection_find(sip
, gsc
->fd
);
7514 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7515 gc
->wants_to_die
= TRUE
;
7516 purple_connection_error(gc
, _("Connection not found. Please try to connect again"));
7520 /* Read all available data from the SSL connection */
7522 /* Increase input buffer size as needed */
7523 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
7524 conn
->inbuflen
+= SIMPLE_BUF_INC
;
7525 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
7526 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
7529 /* Try to read as much as there is space left in the buffer */
7530 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
7531 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
7533 if (len
< 0 && errno
== EAGAIN
) {
7534 /* Try again later */
7536 } else if (len
< 0) {
7537 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
7539 } else if (firstread
&& (len
== 0)) {
7540 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
7544 conn
->inbufused
+= len
;
7547 /* Equivalence indicates that there is possibly more data to read */
7548 } while (len
== readlen
);
7550 conn
->inbuf
[conn
->inbufused
] = '\0';
7551 process_input(sip
, conn
);
7555 static void sipe_input_cb(gpointer data
, gint source
,
7556 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7558 PurpleConnection
*gc
= data
;
7559 struct sipe_account_data
*sip
= gc
->proto_data
;
7561 struct sip_connection
*conn
= connection_find(sip
, source
);
7563 purple_debug_error("sipe", "Connection not found!\n");
7567 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
7568 conn
->inbuflen
+= SIMPLE_BUF_INC
;
7569 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
7572 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
7574 if (len
< 0 && errno
== EAGAIN
)
7576 else if (len
<= 0) {
7577 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7578 connection_remove(sip
, source
);
7579 if (sip
->fd
== source
) sip
->fd
= -1;
7583 conn
->inbufused
+= len
;
7584 conn
->inbuf
[conn
->inbufused
] = '\0';
7586 process_input(sip
, conn
);
7589 /* Callback for new connections on incoming TCP port */
7590 static void sipe_newconn_cb(gpointer data
, gint source
,
7591 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7593 PurpleConnection
*gc
= data
;
7594 struct sipe_account_data
*sip
= gc
->proto_data
;
7595 struct sip_connection
*conn
;
7597 int newfd
= accept(source
, NULL
, NULL
);
7599 conn
= connection_create(sip
, newfd
);
7601 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
7604 static void login_cb(gpointer data
, gint source
,
7605 SIPE_UNUSED_PARAMETER
const gchar
*error_message
)
7607 PurpleConnection
*gc
= data
;
7608 struct sipe_account_data
*sip
;
7609 struct sip_connection
*conn
;
7611 if (!PURPLE_CONNECTION_IS_VALID(gc
))
7619 purple_connection_error(gc
, _("Could not connect"));
7623 sip
= gc
->proto_data
;
7625 sip
->last_keepalive
= time(NULL
);
7627 conn
= connection_create(sip
, source
);
7631 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
7634 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
7635 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7637 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
7638 if (sip
== NULL
) return;
7643 static guint
sipe_ht_hash_nick(const char *nick
)
7645 char *lc
= g_utf8_strdown(nick
, -1);
7646 guint bucket
= g_str_hash(lc
);
7652 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
7654 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
7657 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
7659 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
7661 sip
->listen_data
= NULL
;
7663 if (listenfd
== -1) {
7664 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
7670 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
7671 sip
->listenfd
= sip
->fd
;
7673 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
7675 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
7679 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
,
7680 SIPE_UNUSED_PARAMETER
const char *error_message
)
7682 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
7684 sip
->query_data
= NULL
;
7686 if (!hosts
|| !hosts
->data
) {
7687 purple_connection_error(sip
->gc
, _("Could not resolve hostname"));
7691 hosts
= g_slist_remove(hosts
, hosts
->data
);
7692 g_free(sip
->serveraddr
);
7693 sip
->serveraddr
= hosts
->data
;
7694 hosts
= g_slist_remove(hosts
, hosts
->data
);
7696 hosts
= g_slist_remove(hosts
, hosts
->data
);
7697 g_free(hosts
->data
);
7698 hosts
= g_slist_remove(hosts
, hosts
->data
);
7701 /* create socket for incoming connections */
7702 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
7703 sipe_udp_host_resolved_listen_cb
, sip
);
7704 if (sip
->listen_data
== NULL
) {
7705 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
7710 static const struct sipe_service_data
*current_service
= NULL
;
7712 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
7713 PurpleSslErrorType error
,
7716 PurpleConnection
*gc
= data
;
7717 struct sipe_account_data
*sip
;
7719 /* If the connection is already disconnected, we don't need to do anything else */
7720 if (!PURPLE_CONNECTION_IS_VALID(gc
))
7723 sip
= gc
->proto_data
;
7724 current_service
= sip
->service_data
;
7725 if (current_service
) {
7726 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
7727 current_service
->transport
? current_service
->transport
: "NULL",
7728 current_service
->service
? current_service
->service
: "NULL");
7735 case PURPLE_SSL_CONNECT_FAILED
:
7736 purple_connection_error(gc
, _("Connection failed"));
7738 case PURPLE_SSL_HANDSHAKE_FAILED
:
7739 purple_connection_error(gc
, _("SSL handshake failed"));
7741 case PURPLE_SSL_CERTIFICATE_INVALID
:
7742 purple_connection_error(gc
, _("SSL certificate invalid"));
7748 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
7750 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
7751 PurpleProxyConnectData
*connect_data
;
7753 sip
->listen_data
= NULL
;
7755 sip
->listenfd
= listenfd
;
7756 if (sip
->listenfd
== -1) {
7757 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
7761 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
7762 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7763 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
7764 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
7765 sipe_newconn_cb
, sip
->gc
);
7766 purple_debug_info("sipe", "connecting to %s port %d\n",
7767 sip
->realhostname
, sip
->realport
);
7768 /* open tcp connection to the server */
7769 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
7770 sip
->realport
, login_cb
, sip
->gc
);
7772 if (connect_data
== NULL
) {
7773 purple_connection_error(sip
->gc
, _("Could not create socket"));
7777 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
7779 PurpleAccount
*account
= sip
->account
;
7780 PurpleConnection
*gc
= sip
->gc
;
7783 port
= (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
7786 sip
->realhostname
= hostname
;
7787 sip
->realport
= port
;
7789 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
7792 /* TODO: is there a good default grow size? */
7793 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
7794 sip
->txbuf
= purple_circ_buffer_new(0);
7796 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
7798 if (!purple_ssl_is_supported()) {
7799 gc
->wants_to_die
= TRUE
;
7800 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
7804 purple_debug_info("sipe", "using SSL\n");
7806 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
7807 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
7808 if (sip
->gsc
== NULL
) {
7809 purple_connection_error(gc
, _("Could not create SSL context"));
7812 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
7814 purple_debug_info("sipe", "using UDP\n");
7816 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
7817 if (sip
->query_data
== NULL
) {
7818 purple_connection_error(gc
, _("Could not resolve hostname"));
7822 purple_debug_info("sipe", "using TCP\n");
7823 /* create socket for incoming connections */
7824 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
7825 sipe_tcp_connect_listen_cb
, sip
);
7826 if (sip
->listen_data
== NULL
) {
7827 purple_connection_error(gc
, _("Could not create listen socket"));
7833 /* Service list for autodection */
7834 static const struct sipe_service_data service_autodetect
[] = {
7835 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
7836 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
7837 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
7838 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
7842 /* Service list for SSL/TLS */
7843 static const struct sipe_service_data service_tls
[] = {
7844 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
7845 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
7849 /* Service list for TCP */
7850 static const struct sipe_service_data service_tcp
[] = {
7851 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
7852 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
7856 /* Service list for UDP */
7857 static const struct sipe_service_data service_udp
[] = {
7858 { "sip", "udp", SIPE_TRANSPORT_UDP
},
7862 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
7863 static void resolve_next_service(struct sipe_account_data
*sip
,
7864 const struct sipe_service_data
*start
)
7867 sip
->service_data
= start
;
7869 sip
->service_data
++;
7870 if (sip
->service_data
->service
== NULL
) {
7872 /* Try connecting to the SIP hostname directly */
7873 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
7874 if (sip
->auto_transport
) {
7875 // If SSL is supported, default to using it; OCS servers aren't configured
7876 // by default to accept TCP
7877 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
7878 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
7879 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
7882 hostname
= g_strdup(sip
->sipdomain
);
7883 create_connection(sip
, hostname
, 0);
7888 /* Try to resolve next service */
7889 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
7890 sip
->service_data
->transport
,
7895 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
7897 struct sipe_account_data
*sip
= data
;
7899 sip
->srv_query_data
= NULL
;
7901 /* find the host to connect to */
7903 gchar
*hostname
= g_strdup(resp
->hostname
);
7904 int port
= resp
->port
;
7905 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
7909 sip
->transport
= sip
->service_data
->type
;
7911 create_connection(sip
, hostname
, port
);
7913 resolve_next_service(sip
, NULL
);
7917 static void sipe_login(PurpleAccount
*account
)
7919 PurpleConnection
*gc
;
7920 struct sipe_account_data
*sip
;
7921 gchar
**signinname_login
, **userserver
;
7922 const char *transport
;
7925 const char *username
= purple_account_get_username(account
);
7926 gc
= purple_account_get_connection(account
);
7928 purple_debug_info("sipe", "sipe_login: username '%s'\n", username
);
7930 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
7931 gc
->wants_to_die
= TRUE
;
7932 purple_connection_error(gc
, _("SIP Exchange user name contains invalid characters"));
7936 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
7937 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
7938 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
7940 sip
->account
= account
;
7941 sip
->reregister_set
= FALSE
;
7942 sip
->reauthenticate_set
= FALSE
;
7943 sip
->subscribed
= FALSE
;
7944 sip
->subscribed_buddies
= FALSE
;
7945 sip
->initial_state_published
= FALSE
;
7947 /* username format: <username>,[<optional login>] */
7948 signinname_login
= g_strsplit(username
, ",", 2);
7949 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login
[0]);
7951 /* ensure that username format is name@domain */
7952 if (!strchr(signinname_login
[0], '@') || g_str_has_prefix(signinname_login
[0], "@") || g_str_has_suffix(signinname_login
[0], "@")) {
7953 g_strfreev(signinname_login
);
7954 gc
->wants_to_die
= TRUE
;
7955 purple_connection_error(gc
, _("User name should be a valid SIP URI\nExample: user@company.com"));
7958 sip
->username
= g_strdup(signinname_login
[0]);
7960 /* ensure that email format is name@domain if provided */
7961 email
= purple_account_get_string(sip
->account
, "email", NULL
);
7962 if (!is_empty(email
) &&
7963 (!strchr(email
, '@') || g_str_has_prefix(email
, "@") || g_str_has_suffix(email
, "@")))
7965 gc
->wants_to_die
= TRUE
;
7966 purple_connection_error(gc
, _("Email address should be valid if provided\nExample: user@company.com"));
7969 sip
->email
= !is_empty(email
) ? g_strdup(email
) : g_strdup(sip
->username
);
7971 /* login name specified? */
7972 if (signinname_login
[1] && strlen(signinname_login
[1])) {
7973 gchar
**domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
7974 gboolean has_domain
= domain_user
[1] != NULL
;
7975 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login
[1]);
7976 sip
->authdomain
= has_domain
? g_strdup(domain_user
[0]) : NULL
;
7977 sip
->authuser
= g_strdup(domain_user
[has_domain
? 1 : 0]);
7978 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
7979 sip
->authdomain
? sip
->authdomain
: "", sip
->authuser
);
7980 g_strfreev(domain_user
);
7983 userserver
= g_strsplit(signinname_login
[0], "@", 2);
7984 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver
[0], userserver
[1]);
7985 purple_connection_set_display_name(gc
, userserver
[0]);
7986 sip
->sipdomain
= g_strdup(userserver
[1]);
7987 g_strfreev(userserver
);
7988 g_strfreev(signinname_login
);
7990 if (strchr(sip
->username
, ' ') != NULL
) {
7991 gc
->wants_to_die
= TRUE
;
7992 purple_connection_error(gc
, _("SIP Exchange user name contains whitespace"));
7996 sip
->password
= g_strdup(purple_connection_get_password(gc
));
7998 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
7999 sip
->our_publications
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8000 g_free
, (GDestroyNotify
)g_hash_table_destroy
);
8001 sip
->subscriptions
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8002 g_free
, (GDestroyNotify
)sipe_subscription_free
);
8004 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
8006 g_free(sip
->status
);
8007 sip
->status
= g_strdup(SIPE_STATUS_ID_UNKNOWN
);
8009 sip
->auto_transport
= FALSE
;
8010 transport
= purple_account_get_string(account
, "transport", "auto");
8011 userserver
= g_strsplit(purple_account_get_string(account
, "server", ""), ":", 2);
8012 if (userserver
[0]) {
8013 /* Use user specified server[:port] */
8017 port
= atoi(userserver
[1]);
8019 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8020 userserver
[0], port
);
8022 if (strcmp(transport
, "auto") == 0) {
8023 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
8024 } else if (strcmp(transport
, "tls") == 0) {
8025 sip
->transport
= SIPE_TRANSPORT_TLS
;
8026 } else if (strcmp(transport
, "tcp") == 0) {
8027 sip
->transport
= SIPE_TRANSPORT_TCP
;
8029 sip
->transport
= SIPE_TRANSPORT_UDP
;
8032 create_connection(sip
, g_strdup(userserver
[0]), port
);
8034 /* Server auto-discovery */
8035 if (strcmp(transport
, "auto") == 0) {
8036 sip
->auto_transport
= TRUE
;
8037 if (current_service
&& current_service
->transport
!= NULL
&& current_service
->service
!= NULL
){
8039 resolve_next_service(sip
, current_service
);
8041 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
8043 } else if (strcmp(transport
, "tls") == 0) {
8044 resolve_next_service(sip
, service_tls
);
8045 } else if (strcmp(transport
, "tcp") == 0) {
8046 resolve_next_service(sip
, service_tcp
);
8048 resolve_next_service(sip
, service_udp
);
8051 g_strfreev(userserver
);
8054 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
8056 connection_free_all(sip
);
8061 if (sip
->query_data
!= NULL
)
8062 purple_dnsquery_destroy(sip
->query_data
);
8063 sip
->query_data
= NULL
;
8065 if (sip
->srv_query_data
!= NULL
)
8066 purple_srv_cancel(sip
->srv_query_data
);
8067 sip
->srv_query_data
= NULL
;
8069 if (sip
->listen_data
!= NULL
)
8070 purple_network_listen_cancel(sip
->listen_data
);
8071 sip
->listen_data
= NULL
;
8073 if (sip
->gsc
!= NULL
)
8074 purple_ssl_close(sip
->gsc
);
8077 sipe_auth_free(&sip
->registrar
);
8078 sipe_auth_free(&sip
->proxy
);
8081 purple_circ_buffer_destroy(sip
->txbuf
);
8084 g_free(sip
->realhostname
);
8085 sip
->realhostname
= NULL
;
8087 g_free(sip
->server_version
);
8088 sip
->server_version
= NULL
;
8091 purple_input_remove(sip
->listenpa
);
8093 if (sip
->tx_handler
)
8094 purple_input_remove(sip
->tx_handler
);
8095 sip
->tx_handler
= 0;
8096 if (sip
->resendtimeout
)
8097 purple_timeout_remove(sip
->resendtimeout
);
8098 sip
->resendtimeout
= 0;
8099 if (sip
->timeouts
) {
8100 GSList
*entry
= sip
->timeouts
;
8102 struct scheduled_action
*sched_action
= entry
->data
;
8103 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
8104 purple_timeout_remove(sched_action
->timeout_handler
);
8105 if (sched_action
->destroy
) {
8106 (*sched_action
->destroy
)(sched_action
->payload
);
8108 g_free(sched_action
->name
);
8109 g_free(sched_action
);
8110 entry
= entry
->next
;
8113 g_slist_free(sip
->timeouts
);
8115 if (sip
->allow_events
) {
8116 GSList
*entry
= sip
->allow_events
;
8118 g_free(entry
->data
);
8119 entry
= entry
->next
;
8122 g_slist_free(sip
->allow_events
);
8124 if (sip
->containers
) {
8125 GSList
*entry
= sip
->containers
;
8127 free_container((struct sipe_container
*)entry
->data
);
8128 entry
= entry
->next
;
8131 g_slist_free(sip
->containers
);
8134 g_free(sip
->contact
);
8135 sip
->contact
= NULL
;
8137 g_free(sip
->regcallid
);
8138 sip
->regcallid
= NULL
;
8140 if (sip
->serveraddr
)
8141 g_free(sip
->serveraddr
);
8142 sip
->serveraddr
= NULL
;
8144 if (sip
->focus_factory_uri
)
8145 g_free(sip
->focus_factory_uri
);
8146 sip
->focus_factory_uri
= NULL
;
8149 sip
->processing_input
= FALSE
;
8152 sipe_ews_free(sip
->ews
);
8158 * A callback for g_hash_table_foreach_remove
8160 static gboolean
sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key
, gpointer buddy
,
8161 SIPE_UNUSED_PARAMETER gpointer user_data
)
8163 sipe_free_buddy((struct sipe_buddy
*) buddy
);
8165 /* We must return TRUE as the key/value have already been deleted */
8169 static void sipe_close(PurpleConnection
*gc
)
8171 struct sipe_account_data
*sip
= gc
->proto_data
;
8174 /* leave all conversations */
8175 sipe_session_close_all(sip
);
8176 sipe_session_remove_all(sip
);
8179 sip_csta_close(sip
);
8182 if (PURPLE_CONNECTION_IS_CONNECTED(sip
->gc
)) {
8183 /* unsubscribe all */
8184 g_hash_table_foreach(sip
->subscriptions
, sipe_unsubscribe_cb
, sip
);
8187 do_register_exp(sip
, 0);
8190 sipe_connection_cleanup(sip
);
8191 g_free(sip
->sipdomain
);
8192 g_free(sip
->username
);
8194 g_free(sip
->password
);
8195 g_free(sip
->authdomain
);
8196 g_free(sip
->authuser
);
8197 g_free(sip
->status
);
8200 g_hash_table_foreach_steal(sip
->buddies
, sipe_buddy_remove
, NULL
);
8201 g_hash_table_destroy(sip
->buddies
);
8202 g_hash_table_destroy(sip
->our_publications
);
8203 g_hash_table_destroy(sip
->user_state_publications
);
8204 g_hash_table_destroy(sip
->subscriptions
);
8207 GSList
*entry
= sip
->groups
;
8209 struct sipe_group
*group
= entry
->data
;
8210 g_free(group
->name
);
8212 entry
= entry
->next
;
8215 g_slist_free(sip
->groups
);
8217 if (sip
->our_publication_keys
) {
8218 GSList
*entry
= sip
->our_publication_keys
;
8220 g_free(entry
->data
);
8221 entry
= entry
->next
;
8224 g_slist_free(sip
->our_publication_keys
);
8226 while (sip
->transactions
)
8227 transactions_remove(sip
, sip
->transactions
->data
);
8229 g_free(gc
->proto_data
);
8230 gc
->proto_data
= NULL
;
8233 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
,
8234 SIPE_UNUSED_PARAMETER
void *user_data
)
8236 PurpleAccount
*acct
= purple_connection_get_account(gc
);
8237 char *id
= sip_uri_from_name((gchar
*)g_list_nth_data(row
, 0));
8238 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
8240 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
8241 purple_conversation_present(conv
);
8245 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
,
8246 SIPE_UNUSED_PARAMETER
void *user_data
)
8249 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
8250 g_list_nth_data(row
, 0), _("Other Contacts"), g_list_nth_data(row
, 1));
8253 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
8254 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
8256 PurpleNotifySearchResults
*results
;
8257 PurpleNotifySearchColumn
*column
;
8258 xmlnode
*searchResults
;
8260 int match_count
= 0;
8261 gboolean more
= FALSE
;
8264 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
8266 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
8267 if (!searchResults
) {
8268 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8272 results
= purple_notify_searchresults_new();
8274 if (results
== NULL
) {
8275 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8276 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results"), NULL
);
8278 xmlnode_free(searchResults
);
8282 column
= purple_notify_searchresults_column_new(_("User name"));
8283 purple_notify_searchresults_column_add(results
, column
);
8285 column
= purple_notify_searchresults_column_new(_("Name"));
8286 purple_notify_searchresults_column_add(results
, column
);
8288 column
= purple_notify_searchresults_column_new(_("Company"));
8289 purple_notify_searchresults_column_add(results
, column
);
8291 column
= purple_notify_searchresults_column_new(_("Country"));
8292 purple_notify_searchresults_column_add(results
, column
);
8294 column
= purple_notify_searchresults_column_new(_("Email"));
8295 purple_notify_searchresults_column_add(results
, column
);
8297 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
8300 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
8301 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
8302 g_strfreev(uri_parts
);
8304 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
8305 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
8306 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
8307 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
8309 purple_notify_searchresults_row_add(results
, row
);
8313 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
8314 char *data
= xmlnode_get_data_unescaped(mrow
);
8315 more
= (g_strcasecmp(data
, "true") == 0);
8319 secondary
= g_strdup_printf(
8320 dngettext(GETTEXT_PACKAGE
,
8321 "Found %d contact%s:",
8322 "Found %d contacts%s:", match_count
),
8323 match_count
, more
? _(" (more matched your query)") : "");
8325 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
8326 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
8327 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
8330 xmlnode_free(searchResults
);
8334 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
8336 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
8337 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
8341 PurpleRequestField
*field
= entries
->data
;
8342 const char *id
= purple_request_field_get_id(field
);
8343 const char *value
= purple_request_field_string_get_value(field
);
8345 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
8347 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
8348 } while ((entries
= g_list_next(entries
)) != NULL
);
8352 struct sipe_account_data
*sip
= gc
->proto_data
;
8353 gchar
*domain_uri
= sip_uri_from_name(sip
->sipdomain
);
8354 gchar
*query
= g_strjoinv(NULL
, attrs
);
8355 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
8356 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
8357 send_soap_request_with_cb(sip
, domain_uri
, body
,
8358 (TransCallback
) process_search_contact_response
, NULL
);
8367 static void sipe_show_find_contact(PurplePluginAction
*action
)
8369 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8370 PurpleRequestFields
*fields
;
8371 PurpleRequestFieldGroup
*group
;
8372 PurpleRequestField
*field
;
8374 fields
= purple_request_fields_new();
8375 group
= purple_request_field_group_new(NULL
);
8376 purple_request_fields_add_group(fields
, group
);
8378 field
= purple_request_field_string_new("givenName", _("First name"), NULL
, FALSE
);
8379 purple_request_field_group_add_field(group
, field
);
8380 field
= purple_request_field_string_new("sn", _("Last name"), NULL
, FALSE
);
8381 purple_request_field_group_add_field(group
, field
);
8382 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
8383 purple_request_field_group_add_field(group
, field
);
8384 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
8385 purple_request_field_group_add_field(group
, field
);
8387 purple_request_fields(gc
,
8389 _("Search for a contact"),
8390 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8392 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
8394 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
8397 static void sipe_show_about_plugin(PurplePluginAction
*action
)
8399 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8401 "<b><font size=\"+1\">Sipe " SIPE_VERSION
"</font></b><br/>"
8403 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
8404 "<li> - MS Office Communications Server 2007 (R2)</li><br/>"
8405 "<li> - MS Live Communications Server 2005/2003</li><br/>"
8406 "<li> - Reuters Messaging</li><br/>"
8408 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8409 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
8410 "License: GPLv2<br/>"
8412 "We support users in the following organizations to mention a few:<br/>"
8414 " - Reuters Messaging network<br/>"
8415 " - Deutsche Bank<br/>"
8416 " - Merrill Lynch<br/>"
8419 " - Alcatel-Lucent<br/>"
8424 "<b>Authors:</b><br/>"
8425 " - Anibal Avelar<br/>"
8426 " - Gabriel Burt<br/>"
8427 " - Stefan Becker<br/>"
8430 purple_notify_formatted(gc
, NULL
, " ", NULL
, txt
, NULL
, NULL
);
8433 static void sipe_republish_calendar(PurplePluginAction
*action
)
8435 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8436 struct sipe_account_data
*sip
= gc
->proto_data
;
8438 sipe_update_calendar(sip
);
8441 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER
const char *name
,
8445 struct sipe_publication
*publication
= value
;
8447 g_string_append_printf( str
,
8448 SIPE_PUB_XML_PUBLICATION_CLEAR
,
8449 publication
->category
,
8450 publication
->instance
,
8451 publication
->container
,
8452 publication
->version
,
8456 static void sipe_reset_status(PurplePluginAction
*action
)
8458 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8459 struct sipe_account_data
*sip
= gc
->proto_data
;
8461 if (sip
->ocs2007
) /* 2007+ */
8463 GString
* str
= g_string_new(NULL
);
8464 gchar
*publications
;
8466 if (!sip
->user_state_publications
|| g_hash_table_size(sip
->user_state_publications
) == 0) {
8467 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8471 g_hash_table_foreach(sip
->user_state_publications
, (GHFunc
)sipe_publish_get_cat_state_user_to_clear
, str
);
8472 publications
= g_string_free(str
, FALSE
);
8474 send_presence_publish(sip
, publications
);
8475 g_free(publications
);
8479 send_presence_soap0(sip
, FALSE
, TRUE
);
8483 GList
*sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
,
8486 PurpleConnection
*gc
= (PurpleConnection
*)context
;
8487 struct sipe_account_data
*sip
= gc
->proto_data
;
8489 PurplePluginAction
*act
;
8490 const char* calendar
= purple_account_get_string(sip
->account
, "calendar", "EXCH");
8492 act
= purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin
);
8493 menu
= g_list_prepend(menu
, act
);
8495 act
= purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact
);
8496 menu
= g_list_prepend(menu
, act
);
8498 if (!strcmp(calendar
, "EXCH")) {
8499 act
= purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar
);
8500 menu
= g_list_prepend(menu
, act
);
8503 act
= purple_plugin_action_new(_("Reset status"), sipe_reset_status
);
8504 menu
= g_list_prepend(menu
, act
);
8506 menu
= g_list_reverse(menu
);
8511 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection
*gc
)
8515 static gboolean
sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
8521 static gboolean
sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
8527 static char *sipe_status_text(PurpleBuddy
*buddy
)
8529 struct sipe_account_data
*sip
;
8530 struct sipe_buddy
*sbuddy
;
8533 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
8534 if (sip
) //happens on pidgin exit
8536 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
8538 if (!is_empty(sbuddy
->activity
) && !is_empty(sbuddy
->annotation
))
8540 text
= g_strdup_printf("%s - %s", sbuddy
->activity
, sbuddy
->annotation
);
8542 else if (!is_empty(sbuddy
->activity
))
8544 text
= g_strdup(sbuddy
->activity
);
8548 text
= g_strdup(sbuddy
->annotation
);
8556 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, SIPE_UNUSED_PARAMETER gboolean full
)
8558 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
8559 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
8560 struct sipe_account_data
*sip
;
8561 struct sipe_buddy
*sbuddy
;
8562 char *annotation
= NULL
;
8563 gboolean is_oof_note
= FALSE
;
8564 char *activity
= NULL
;
8565 char *calendar
= NULL
;
8566 char *meeting_subject
= NULL
;
8567 char *meeting_location
= NULL
;
8569 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
8570 if (sip
) //happens on pidgin exit
8572 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
8575 annotation
= sbuddy
->annotation
? g_strdup(sbuddy
->annotation
) : NULL
;
8576 is_oof_note
= sbuddy
->is_oof_note
;
8577 activity
= sbuddy
->activity
;
8578 calendar
= sipe_cal_get_description(sbuddy
);
8579 meeting_subject
= sbuddy
->meeting_subject
;
8580 meeting_location
= sbuddy
->meeting_location
;
8585 if (purple_presence_is_online(presence
))
8587 const char *status_str
= activity
&& status
&& strcmp(purple_status_get_id(status
), SIPE_STATUS_ID_ON_PHONE
) ?
8589 purple_status_get_name(status
);
8591 purple_notify_user_info_add_pair(user_info
, _("Status"), status_str
);
8593 if (purple_presence_is_online(presence
) &&
8594 !is_empty(calendar
))
8596 purple_notify_user_info_add_pair(user_info
, _("Calendar"), calendar
);
8599 if (!is_empty(meeting_location
))
8601 purple_notify_user_info_add_pair(user_info
, _("Meeting in"), meeting_location
);
8603 if (!is_empty(meeting_subject
))
8605 purple_notify_user_info_add_pair(user_info
, _("Meeting about"), meeting_subject
);
8610 /* Tooltip does not know how to handle markup like <br> */
8611 gchar
*s
= annotation
;
8612 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy
->name
, annotation
);
8613 while ((s
= strchr(s
, '<')) != NULL
) {
8614 if (!g_ascii_strncasecmp(s
, "<br>", 4)) {
8616 strcpy(s
+ 1, s
+ 4);
8620 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy
->name
, annotation
);
8622 purple_notify_user_info_add_pair(user_info
, is_oof_note
? _("Out of office note") : _("Note"), annotation
);
8628 #if PURPLE_VERSION_CHECK(2,5,0)
8630 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount
*account
)
8633 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
8634 g_hash_table_insert(table
, "login_label", (gpointer
)_("user@company.com"));
8639 static PurpleBuddy
*
8640 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
8643 const gchar
*server_alias
, *email
;
8644 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
8646 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
8648 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
8650 server_alias
= purple_buddy_get_server_alias(buddy
);
8652 purple_blist_server_alias_buddy(clone
, server_alias
);
8655 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
8657 purple_blist_node_set_string(&clone
->node
, EMAIL_PROP
, email
);
8660 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
8662 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
8667 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
8669 PurpleBuddy
*buddy
, *b
;
8670 PurpleConnection
*gc
;
8671 PurpleGroup
* group
= purple_find_group(group_name
);
8673 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
8675 buddy
= (PurpleBuddy
*)node
;
8677 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
8678 gc
= purple_account_get_connection(buddy
->account
);
8680 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
8682 b
= purple_blist_add_buddy_clone(group
, buddy
);
8685 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
8689 sipe_buddy_menu_chat_new_cb(PurpleBuddy
*buddy
)
8691 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8693 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy
->name
);
8695 /* 2007+ conference */
8698 sipe_conf_add(sip
, buddy
->name
);
8700 else /* 2005- multiparty chat */
8702 gchar
*self
= sip_uri_self(sip
);
8703 struct sip_session
*session
;
8705 session
= sipe_session_add_chat(sip
);
8706 session
->chat_title
= sipe_chat_get_name(session
->callid
);
8707 session
->roster_manager
= g_strdup(self
);
8709 session
->conv
= serv_got_joined_chat(buddy
->account
->gc
, session
->chat_id
, session
->chat_title
);
8710 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session
->conv
), self
);
8711 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
), self
, NULL
, PURPLE_CBFLAGS_NONE
, FALSE
);
8712 sipe_invite(sip
, session
, buddy
->name
, NULL
, NULL
, FALSE
);
8719 sipe_is_election_finished(struct sip_session
*session
)
8721 gboolean res
= TRUE
;
8723 SIPE_DIALOG_FOREACH
{
8724 if (dialog
->election_vote
== 0) {
8728 } SIPE_DIALOG_FOREACH_END
;
8731 session
->is_voting_in_progress
= FALSE
;
8737 sipe_election_start(struct sipe_account_data
*sip
,
8738 struct sip_session
*session
)
8740 int election_timeout
;
8742 if (session
->is_voting_in_progress
) {
8743 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
8746 session
->is_voting_in_progress
= TRUE
;
8748 session
->bid
= rand();
8750 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session
->bid
);
8752 SIPE_DIALOG_FOREACH
{
8753 /* reset election_vote for each chat participant */
8754 dialog
->election_vote
= 0;
8756 /* send RequestRM to each chat participant*/
8757 sipe_send_election_request_rm(sip
, dialog
, session
->bid
);
8758 } SIPE_DIALOG_FOREACH_END
;
8760 election_timeout
= 15; /* sec */
8761 sipe_schedule_action("<+election-result>", election_timeout
, sipe_election_result
, NULL
, sip
, session
);
8765 * @param who a URI to whom to invite to chat
8768 sipe_invite_to_chat(struct sipe_account_data
*sip
,
8769 struct sip_session
*session
,
8773 if (session
->focus_uri
)
8775 sipe_invite_conf(sip
, session
, who
);
8777 else /* a multi-party chat */
8779 gchar
*self
= sip_uri_self(sip
);
8780 if (session
->roster_manager
) {
8781 if (!strcmp(session
->roster_manager
, self
)) {
8782 sipe_invite(sip
, session
, who
, NULL
, NULL
, FALSE
);
8784 sipe_refer(sip
, session
, who
);
8787 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
8789 session
->pending_invite_queue
= slist_insert_unique_sorted(
8790 session
->pending_invite_queue
, g_strdup(who
), (GCompareFunc
)strcmp
);
8792 sipe_election_start(sip
, session
);
8799 sipe_process_pending_invite_queue(struct sipe_account_data
*sip
,
8800 struct sip_session
*session
)
8803 GSList
*entry
= session
->pending_invite_queue
;
8806 invitee
= entry
->data
;
8807 sipe_invite_to_chat(sip
, session
, invitee
);
8808 entry
= session
->pending_invite_queue
= g_slist_remove(session
->pending_invite_queue
, invitee
);
8814 sipe_election_result(struct sipe_account_data
*sip
,
8817 struct sip_session
*session
= (struct sip_session
*)sess
;
8819 gboolean has_won
= TRUE
;
8821 if (session
->roster_manager
) {
8822 purple_debug_info("sipe",
8823 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session
->roster_manager
);
8827 session
->is_voting_in_progress
= FALSE
;
8829 SIPE_DIALOG_FOREACH
{
8830 if (dialog
->election_vote
< 0) {
8832 rival
= dialog
->with
;
8835 } SIPE_DIALOG_FOREACH_END
;
8838 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
8840 session
->roster_manager
= sip_uri_self(sip
);
8842 SIPE_DIALOG_FOREACH
{
8843 /* send SetRM to each chat participant*/
8844 sipe_send_election_set_rm(sip
, dialog
);
8845 } SIPE_DIALOG_FOREACH_END
;
8847 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival
);
8851 sipe_process_pending_invite_queue(sip
, session
);
8855 * For 2007+ conference only.
8858 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy
*buddy
, const char *chat_title
)
8860 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8861 struct sip_session
*session
;
8863 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy
->name
);
8864 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title
);
8866 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
8868 sipe_conf_modify_user_role(sip
, session
, buddy
->name
);
8872 * For 2007+ conference only.
8875 sipe_buddy_menu_chat_remove_cb(PurpleBuddy
*buddy
, const char *chat_title
)
8877 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8878 struct sip_session
*session
;
8880 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy
->name
);
8881 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title
);
8883 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
8885 sipe_conf_delete_user(sip
, session
, buddy
->name
);
8889 sipe_buddy_menu_chat_invite_cb(PurpleBuddy
*buddy
, char *chat_title
)
8891 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8892 struct sip_session
*session
;
8894 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy
->name
);
8895 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title
);
8897 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
8899 sipe_invite_to_chat(sip
, session
, buddy
->name
);
8903 sipe_buddy_menu_make_call_cb(PurpleBuddy
*buddy
, const char *phone
)
8905 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8907 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy
->name
);
8909 char *tel_uri
= sip_to_tel_uri(phone
);
8911 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri
? tel_uri
: "");
8912 sip_csta_make_call(sip
, tel_uri
);
8919 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
8922 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
8924 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
8927 char *mailto
= g_strdup_printf("mailto:%s", email
);
8928 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
8932 char *const parmList
[] = {"xdg-email", mailto
, NULL
};
8933 if ((pid
= fork()) == -1)
8935 purple_debug_info("sipe", "fork() error\n");
8939 execvp(parmList
[0], parmList
);
8940 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
8948 //@TODO resolve env variable %WINDIR% first
8949 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
8952 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
8961 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
8966 * A menu which appear when right-clicking on buddy in contact list.
8969 sipe_buddy_menu(PurpleBuddy
*buddy
)
8971 PurpleBlistNode
*g_node
;
8972 PurpleGroup
*group
, *gr_parent
;
8973 PurpleMenuAction
*act
;
8975 GList
*menu_groups
= NULL
;
8976 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8979 const char *phone_disp_str
;
8980 gchar
*self
= sip_uri_self(sip
);
8982 SIPE_SESSION_FOREACH
{
8983 if (g_ascii_strcasecmp(self
, buddy
->name
) && session
->chat_title
&& session
->conv
)
8985 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session
->conv
), buddy
->name
))
8987 PurpleConvChatBuddyFlags flags
;
8988 PurpleConvChatBuddyFlags flags_us
;
8990 flags
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), buddy
->name
);
8991 flags_us
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), self
);
8992 if (session
->focus_uri
8993 && PURPLE_CBFLAGS_OP
!= (flags
& PURPLE_CBFLAGS_OP
) /* Not conf OP */
8994 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
8996 gchar
*label
= g_strdup_printf(_("Make leader of '%s'"), session
->chat_title
);
8997 act
= purple_menu_action_new(label
,
8998 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb
),
8999 session
->chat_title
, NULL
);
9001 menu
= g_list_prepend(menu
, act
);
9004 if (session
->focus_uri
9005 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
9007 gchar
*label
= g_strdup_printf(_("Remove from '%s'"), session
->chat_title
);
9008 act
= purple_menu_action_new(label
,
9009 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb
),
9010 session
->chat_title
, NULL
);
9012 menu
= g_list_prepend(menu
, act
);
9017 if (!session
->focus_uri
9018 || (session
->focus_uri
&& !session
->locked
))
9020 gchar
*label
= g_strdup_printf(_("Invite to '%s'"), session
->chat_title
);
9021 act
= purple_menu_action_new(label
,
9022 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb
),
9023 session
->chat_title
, NULL
);
9025 menu
= g_list_prepend(menu
, act
);
9029 } SIPE_SESSION_FOREACH_END
;
9031 act
= purple_menu_action_new(_("New chat"),
9032 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb
),
9034 menu
= g_list_prepend(menu
, act
);
9036 if (sip
->csta
&& !sip
->csta
->line_status
) {
9039 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_PROP
);
9040 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_DISPLAY_PROP
);
9042 gchar
*label
= g_strdup_printf(_("Work %s"),
9043 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9044 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9048 menu
= g_list_prepend(menu
, act
);
9052 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_MOBILE_PROP
);
9053 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_MOBILE_DISPLAY_PROP
);
9055 gchar
*label
= g_strdup_printf(_("Mobile %s"),
9056 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9057 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9061 menu
= g_list_prepend(menu
, act
);
9065 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_HOME_PROP
);
9066 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_HOME_DISPLAY_PROP
);
9068 gchar
*label
= g_strdup_printf(_("Home %s"),
9069 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9070 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9074 menu
= g_list_prepend(menu
, act
);
9078 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_OTHER_PROP
);
9079 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_OTHER_DISPLAY_PROP
);
9081 gchar
*label
= g_strdup_printf(_("Other %s"),
9082 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9083 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9087 menu
= g_list_prepend(menu
, act
);
9091 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_CUSTOM1_PROP
);
9092 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_CUSTOM1_DISPLAY_PROP
);
9094 gchar
*label
= g_strdup_printf(_("Custom1 %s"),
9095 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9096 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9100 menu
= g_list_prepend(menu
, act
);
9104 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
9106 act
= purple_menu_action_new(_("Send email..."),
9107 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
9109 menu
= g_list_prepend(menu
, act
);
9112 gr_parent
= purple_buddy_get_group(buddy
);
9113 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
9114 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
9117 group
= (PurpleGroup
*)g_node
;
9118 if (group
== gr_parent
)
9121 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
9124 act
= purple_menu_action_new(purple_group_get_name(group
),
9125 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
9127 menu_groups
= g_list_prepend(menu_groups
, act
);
9129 menu_groups
= g_list_reverse(menu_groups
);
9131 act
= purple_menu_action_new(_("Copy to"),
9134 menu
= g_list_prepend(menu
, act
);
9135 menu
= g_list_reverse(menu
);
9142 sipe_conf_modify_lock(PurpleChat
*chat
, gboolean locked
)
9144 struct sipe_account_data
*sip
= chat
->account
->gc
->proto_data
;
9145 struct sip_session
*session
;
9147 session
= sipe_session_find_chat_by_title(sip
, (gchar
*)g_hash_table_lookup(chat
->components
, "channel"));
9148 sipe_conf_modify_conference_lock(sip
, session
, locked
);
9152 sipe_chat_menu_unlock_cb(PurpleChat
*chat
)
9154 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9155 sipe_conf_modify_lock(chat
, FALSE
);
9159 sipe_chat_menu_lock_cb(PurpleChat
*chat
)
9161 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9162 sipe_conf_modify_lock(chat
, TRUE
);
9166 sipe_chat_menu(PurpleChat
*chat
)
9168 PurpleMenuAction
*act
;
9169 PurpleConvChatBuddyFlags flags_us
;
9171 struct sipe_account_data
*sip
= chat
->account
->gc
->proto_data
;
9172 struct sip_session
*session
;
9175 session
= sipe_session_find_chat_by_title(sip
, (gchar
*)g_hash_table_lookup(chat
->components
, "channel"));
9176 if (!session
) return NULL
;
9178 self
= sip_uri_self(sip
);
9179 flags_us
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), self
);
9181 if (session
->focus_uri
9182 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
9184 if (session
->locked
) {
9185 act
= purple_menu_action_new(_("Unlock"),
9186 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb
),
9188 menu
= g_list_prepend(menu
, act
);
9190 act
= purple_menu_action_new(_("Lock"),
9191 PURPLE_CALLBACK(sipe_chat_menu_lock_cb
),
9193 menu
= g_list_prepend(menu
, act
);
9197 menu
= g_list_reverse(menu
);
9204 sipe_blist_node_menu(PurpleBlistNode
*node
)
9206 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
9207 return sipe_buddy_menu((PurpleBuddy
*) node
);
9208 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
9209 return sipe_chat_menu((PurpleChat
*)node
);
9216 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
9218 gboolean ret
= TRUE
;
9219 char *uri
= trans
->payload
->data
;
9221 PurpleNotifyUserInfo
*info
= purple_notify_user_info_new();
9222 PurpleBuddy
*pbuddy
;
9223 struct sipe_buddy
*sbuddy
;
9225 char *device_name
= NULL
;
9226 char *server_alias
= NULL
;
9227 char *phone_number
= NULL
;
9231 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri
, sip
->username
);
9233 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri
);
9234 alias
= purple_buddy_get_local_alias(pbuddy
);
9238 //will query buddy UA's capabilities and send answer to log
9239 sipe_options_request(sip
, uri
);
9241 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
9244 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
9248 if (msg
->response
!= 200) {
9249 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
9251 xmlnode
*searchResults
;
9254 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
9255 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
9256 if (!searchResults
) {
9257 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9258 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
9260 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
9261 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
9262 phone_number
= g_strdup(xmlnode_get_attrib(mrow
, "phone"));
9264 /* For 2007 system we will take this from ContactCard -
9265 * it has cleaner tel: URIs at least
9267 if (!sip
->ocs2007
) {
9268 char *tel_uri
= sip_to_tel_uri(phone_number
);
9269 /* trims its parameters, so call first */
9270 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, server_alias
);
9271 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
9272 sipe_update_user_info(sip
, uri
, PHONE_PROP
, tel_uri
);
9273 sipe_update_user_info(sip
, uri
, PHONE_DISPLAY_PROP
, phone_number
);
9277 if (server_alias
&& strlen(server_alias
) > 0) {
9278 purple_notify_user_info_add_pair(info
, _("Display name"), server_alias
);
9280 if ((value
= xmlnode_get_attrib(mrow
, "title")) && strlen(value
) > 0) {
9281 purple_notify_user_info_add_pair(info
, _("Job title"), value
);
9283 if ((value
= xmlnode_get_attrib(mrow
, "office")) && strlen(value
) > 0) {
9284 purple_notify_user_info_add_pair(info
, _("Office"), value
);
9286 if (phone_number
&& strlen(phone_number
) > 0) {
9287 purple_notify_user_info_add_pair(info
, _("Business phone"), phone_number
);
9289 if ((value
= xmlnode_get_attrib(mrow
, "company")) && strlen(value
) > 0) {
9290 purple_notify_user_info_add_pair(info
, _("Company"), value
);
9292 if ((value
= xmlnode_get_attrib(mrow
, "city")) && strlen(value
) > 0) {
9293 purple_notify_user_info_add_pair(info
, _("City"), value
);
9295 if ((value
= xmlnode_get_attrib(mrow
, "state")) && strlen(value
) > 0) {
9296 purple_notify_user_info_add_pair(info
, _("State"), value
);
9298 if ((value
= xmlnode_get_attrib(mrow
, "country")) && strlen(value
) > 0) {
9299 purple_notify_user_info_add_pair(info
, _("Country"), value
);
9301 if (email
&& strlen(email
) > 0) {
9302 purple_notify_user_info_add_pair(info
, _("Email address"), email
);
9306 xmlnode_free(searchResults
);
9309 purple_notify_user_info_add_section_break(info
);
9311 if (!server_alias
|| !strcmp("", server_alias
)) {
9312 g_free(server_alias
);
9313 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
9315 purple_notify_user_info_add_pair(info
, _("Display name"), server_alias
);
9319 /* present alias if it differs from server alias */
9320 if (alias
&& (!server_alias
|| strcmp(alias
, server_alias
)))
9322 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
9325 if (!email
|| !strcmp("", email
)) {
9327 email
= g_strdup(purple_blist_node_get_string(&pbuddy
->node
, EMAIL_PROP
));
9329 purple_notify_user_info_add_pair(info
, _("Email address"), email
);
9333 site
= purple_blist_node_get_string(&pbuddy
->node
, SITE_PROP
);
9335 purple_notify_user_info_add_pair(info
, _("Site"), site
);
9339 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
9342 /* show a buddy's user info in a nice dialog box */
9343 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
9344 uri
, /* buddy's URI */
9346 NULL
, /* callback called when dialog closed */
9347 NULL
); /* userdata for callback */
9349 g_free(phone_number
);
9350 g_free(server_alias
);
9352 g_free(device_name
);
9358 * AD search first, LDAP based
9360 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
9362 struct sipe_account_data
*sip
= gc
->proto_data
;
9363 gchar
*domain_uri
= sip_uri_from_name(sip
->sipdomain
);
9364 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
9365 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
9366 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
9368 payload
->destroy
= g_free
;
9369 payload
->data
= g_strdup(username
);
9371 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
9372 send_soap_request_with_cb(sip
, domain_uri
, body
,
9373 (TransCallback
) process_get_info_response
, payload
);
9379 static PurplePlugin
*my_protocol
= NULL
;
9381 static PurplePluginProtocolInfo prpl_info
=
9383 OPT_PROTO_CHAT_TOPIC
,
9384 NULL
, /* user_splits */
9385 NULL
, /* protocol_options */
9386 NO_BUDDY_ICONS
, /* icon_spec */
9387 sipe_list_icon
, /* list_icon */
9388 NULL
, /* list_emblems */
9389 sipe_status_text
, /* status_text */
9390 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
9391 sipe_status_types
, /* away_states */
9392 sipe_blist_node_menu
, /* blist_node_menu */
9393 NULL
, /* chat_info */
9394 NULL
, /* chat_info_defaults */
9395 sipe_login
, /* login */
9396 sipe_close
, /* close */
9397 sipe_im_send
, /* send_im */
9398 NULL
, /* set_info */ // TODO maybe
9399 sipe_send_typing
, /* send_typing */
9400 sipe_get_info
, /* get_info */
9401 sipe_set_status
, /* set_status */
9402 sipe_set_idle
, /* set_idle */
9403 NULL
, /* change_passwd */
9404 sipe_add_buddy
, /* add_buddy */
9405 NULL
, /* add_buddies */
9406 sipe_remove_buddy
, /* remove_buddy */
9407 NULL
, /* remove_buddies */
9408 sipe_add_permit
, /* add_permit */
9409 sipe_add_deny
, /* add_deny */
9410 sipe_add_deny
, /* rem_permit */
9411 sipe_add_permit
, /* rem_deny */
9412 dummy_permit_deny
, /* set_permit_deny */
9413 NULL
, /* join_chat */
9414 NULL
, /* reject_chat */
9415 NULL
, /* get_chat_name */
9416 sipe_chat_invite
, /* chat_invite */
9417 sipe_chat_leave
, /* chat_leave */
9418 NULL
, /* chat_whisper */
9419 sipe_chat_send
, /* chat_send */
9420 sipe_keep_alive
, /* keepalive */
9421 NULL
, /* register_user */
9422 NULL
, /* get_cb_info */ // deprecated
9423 NULL
, /* get_cb_away */ // deprecated
9424 sipe_alias_buddy
, /* alias_buddy */
9425 sipe_group_buddy
, /* group_buddy */
9426 sipe_rename_group
, /* rename_group */
9427 NULL
, /* buddy_free */
9428 sipe_convo_closed
, /* convo_closed */
9429 purple_normalize_nocase
, /* normalize */
9430 NULL
, /* set_buddy_icon */
9431 sipe_remove_group
, /* remove_group */
9432 NULL
, /* get_cb_real_name */ // TODO?
9433 NULL
, /* set_chat_topic */
9434 NULL
, /* find_blist_chat */
9435 NULL
, /* roomlist_get_list */
9436 NULL
, /* roomlist_cancel */
9437 NULL
, /* roomlist_expand_category */
9438 NULL
, /* can_receive_file */
9439 NULL
, /* send_file */
9440 NULL
, /* new_xfer */
9441 NULL
, /* offline_message */
9442 NULL
, /* whiteboard_prpl_ops */
9443 sipe_send_raw
, /* send_raw */
9444 NULL
, /* roomlist_room_serialize */
9445 NULL
, /* unregister_user */
9446 NULL
, /* send_attention */
9447 NULL
, /* get_attention_types */
9448 #if !PURPLE_VERSION_CHECK(2,5,0)
9449 /* Backward compatibility when compiling against 2.4.x API */
9450 (void (*)(void)) /* _purple_reserved4 */
9452 sizeof(PurplePluginProtocolInfo
), /* struct_size */
9453 #if PURPLE_VERSION_CHECK(2,5,0)
9454 sipe_get_account_text_table
, /* get_account_text_table */
9455 #if PURPLE_VERSION_CHECK(2,6,0)
9456 NULL
, /* initiate_media */
9457 NULL
, /* get_media_caps */
9463 static PurplePluginInfo info
= {
9464 PURPLE_PLUGIN_MAGIC
,
9465 PURPLE_MAJOR_VERSION
,
9466 PURPLE_MINOR_VERSION
,
9467 PURPLE_PLUGIN_PROTOCOL
, /**< type */
9468 NULL
, /**< ui_requirement */
9470 NULL
, /**< dependencies */
9471 PURPLE_PRIORITY_DEFAULT
, /**< priority */
9472 "prpl-sipe", /**< id */
9473 "Office Communicator", /**< name */
9474 SIPE_VERSION
, /**< version */
9475 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9476 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9477 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9478 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9479 "Gabriel Burt <gburt@novell.com>, " /**< author */
9480 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9481 "pier11 <pier11@operamail.com>", /**< author */
9482 "http://sipe.sourceforge.net/", /**< homepage */
9483 sipe_plugin_load
, /**< load */
9484 sipe_plugin_unload
, /**< unload */
9485 sipe_plugin_destroy
, /**< destroy */
9486 NULL
, /**< ui_info */
9487 &prpl_info
, /**< extra_info */
9496 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
9500 entry
= prpl_info
.protocol_options
;
9502 purple_account_option_destroy(entry
->data
);
9503 entry
= g_list_delete_link(entry
, entry
);
9505 prpl_info
.protocol_options
= NULL
;
9507 entry
= prpl_info
.user_splits
;
9509 purple_account_user_split_destroy(entry
->data
);
9510 entry
= g_list_delete_link(entry
, entry
);
9512 prpl_info
.user_splits
= NULL
;
9515 static void init_plugin(PurplePlugin
*plugin
)
9517 PurpleAccountUserSplit
*split
;
9518 PurpleAccountOption
*option
;
9523 purple_debug_info(PACKAGE
, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
9524 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s\n",
9525 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
9526 textdomain(GETTEXT_PACKAGE
);
9529 purple_plugin_register(plugin
);
9531 split
= purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL
, ',');
9532 purple_account_user_split_set_reverse(split
, FALSE
);
9533 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
9535 option
= purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9536 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9538 option
= purple_account_option_list_new(_("Connection type"), "transport", NULL
);
9539 purple_account_option_add_list_item(option
, _("Auto"), "auto");
9540 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
9541 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
9542 purple_account_option_add_list_item(option
, _("UDP"), "udp");
9543 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9545 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9546 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9548 option
= purple_account_option_string_new(_("User Agent"), "useragent", "");
9549 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9552 option
= purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE
);
9553 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9555 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9556 * No login/password is taken into account if this option present,
9557 * instead used default credentials stored in OS.
9559 option
= purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE
);
9560 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9563 option
= purple_account_option_list_new(_("Calendar source"), "calendar", NULL
);
9564 purple_account_option_add_list_item(option
, _("Exchange 2007/2010"), "EXCH");
9565 purple_account_option_add_list_item(option
, _("None"), "NONE");
9566 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9568 /** Example: https://server.company.com/EWS/Exchange.asmx */
9569 option
= purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9570 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9572 option
= purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9573 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9575 /** Example: DOMAIN\user or user@company.com */
9576 option
= purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9577 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9579 option
= purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9580 purple_account_option_set_masked(option
, TRUE
);
9581 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9583 my_protocol
= plugin
;
9586 PURPLE_INIT_PLUGIN(sipe
, init_plugin
, info
);