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 SIPE_IDLE_SET_DELAY 1 /* 1 sec */
97 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
98 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
100 /* Keep in sync with sipe_transport_type! */
101 static const char *transport_descriptor
[] = { "tls", "tcp", "udp" };
102 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
104 /* Status identifiers (see also: sipe_status_types()) */
105 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
106 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
107 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
108 /* PURPLE_STATUS_UNAVAILABLE: */
109 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
110 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
111 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
112 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
113 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
114 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
115 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
116 /* PURPLE_STATUS_AWAY: */
117 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
118 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
119 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
120 /** Reuters status (user settable) */
121 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
122 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
123 /* ??? PURPLE_STATUS_MOBILE */
124 /* ??? PURPLE_STATUS_TUNE */
126 /* Status attributes (see also sipe_status_types() */
127 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
129 static struct sipe_activity_map_struct
134 const char *status_id
;
136 } const sipe_activity_map
[] =
138 /* This has nothing to do with Availability numbers, like 3500 (online).
139 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
141 { SIPE_ACTIVITY_UNSET
, "unset", NULL
, NULL
},
142 { SIPE_ACTIVITY_ONLINE
, "online", NULL
, NULL
},
143 { SIPE_ACTIVITY_INACTIVE
, SIPE_STATUS_ID_IDLE
, N_("Inactive") , NULL
},
144 { SIPE_ACTIVITY_BUSY
, SIPE_STATUS_ID_BUSY
, N_("Busy") , SIPE_STATUS_ID_BUSY
},
145 { SIPE_ACTIVITY_BUSYIDLE
, SIPE_STATUS_ID_BUSYIDLE
, N_("BusyIdle") , NULL
},
146 { SIPE_ACTIVITY_DND
, SIPE_STATUS_ID_DND
, NULL
, SIPE_STATUS_ID_DND
},
147 { SIPE_ACTIVITY_BRB
, SIPE_STATUS_ID_BRB
, N_("Be right back") , SIPE_STATUS_ID_BRB
},
148 { SIPE_ACTIVITY_AWAY
, "away", NULL
, NULL
},
149 { SIPE_ACTIVITY_LUNCH
, SIPE_STATUS_ID_LUNCH
, N_("Out to lunch") , NULL
},
150 { SIPE_ACTIVITY_OFFLINE
, "offline", NULL
, NULL
},
151 { SIPE_ACTIVITY_ON_PHONE
, SIPE_STATUS_ID_ON_PHONE
, N_("In a call") , NULL
},
152 { SIPE_ACTIVITY_IN_CONF
, SIPE_STATUS_ID_IN_CONF
, N_("In a conference") , NULL
},
153 { SIPE_ACTIVITY_IN_MEETING
, SIPE_STATUS_ID_IN_MEETING
, N_("In a meeting") , NULL
},
154 { SIPE_ACTIVITY_OOF
, "out-of-office", N_("Out of office") , NULL
},
155 { SIPE_ACTIVITY_URGENT_ONLY
, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL
}
157 /** @param x is sipe_activity */
158 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
161 /* Action name templates */
162 #define ACTION_NAME_PRESENCE "<presence><%s>"
165 sipe_get_activity_by_token(const char *token
)
169 for (i
= 0; i
< SIPE_ACTIVITY_NUM_TYPES
; i
++)
171 if (!strcmp(token
, sipe_activity_map
[i
].token
))
172 return sipe_activity_map
[i
].type
;
175 return sipe_activity_map
[0].type
;
179 sipe_get_activity_desc_by_token(const char *token
)
181 if (!token
) return NULL
;
183 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token
));
186 /** Allows to send typed messages from chat window again after account reinstantiation. */
188 sipe_rejoin_chat(PurpleConversation
*conv
)
190 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
&&
191 PURPLE_CONV_CHAT(conv
)->left
)
193 PURPLE_CONV_CHAT(conv
)->left
= FALSE
;
194 purple_conversation_update(conv
, PURPLE_CONV_UPDATE_CHATLEFT
);
198 static char *genbranch()
200 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
201 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
202 rand() & 0xFFFF, rand() & 0xFFFF);
206 static char *default_ua
= NULL
;
208 sipe_get_useragent(struct sipe_account_data
*sip
)
210 const char *useragent
= purple_account_get_string(sip
->account
, "useragent", "");
211 if (is_empty(useragent
)) {
213 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
215 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
216 #define SIPE_TARGET_PLATFORM "linux"
217 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
218 #define SIPE_TARGET_PLATFORM "bsd"
219 # elif defined(__APPLE__) || defined(__MACOS__)
220 #define SIPE_TARGET_PLATFORM "macosx"
221 #elif defined(__solaris__) || defined(__sun)
222 #define SIPE_TARGET_PLATFORM "sun"
223 #elif defined(_WIN32)
224 #define SIPE_TARGET_PLATFORM "win"
226 #define SIPE_TARGET_PLATFORM "generic"
229 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
230 #define SIPE_TARGET_ARCH "x86_64"
231 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
232 #define SIPE_TARGET_ARCH "i386"
233 #elif defined(__ppc64__)
234 #define SIPE_TARGET_ARCH "ppc64"
235 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
236 #define SIPE_TARGET_ARCH "ppc"
238 #define SIPE_TARGET_ARCH "other"
241 default_ua
= g_strdup_printf("Purple/%s Sipe/%s (%s-%s; %s)",
242 purple_core_get_version(),
244 SIPE_TARGET_PLATFORM
,
246 sip
->server_version
? sip
->server_version
: "");
248 useragent
= default_ua
;
253 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount
*a
,
254 SIPE_UNUSED_PARAMETER PurpleBuddy
*b
)
259 static void sipe_plugin_destroy(PurplePlugin
*plugin
);
261 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
);
263 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
264 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
267 static void sipe_close(PurpleConnection
*gc
);
269 static void send_presence_status(struct sipe_account_data
*sip
);
271 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
273 static void sipe_keep_alive(PurpleConnection
*gc
)
275 struct sipe_account_data
*sip
= gc
->proto_data
;
276 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
277 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
278 gchar buf
[2] = {0, 0};
279 purple_debug_info("sipe", "sending keep alive\n");
280 sendto(sip
->fd
, buf
, 1, 0, sip
->serveraddr
, sizeof(struct sockaddr_in
));
282 time_t now
= time(NULL
);
283 if ((sip
->keepalive_timeout
> 0) &&
284 ((guint
) (now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
)
285 #if PURPLE_VERSION_CHECK(2,4,0)
286 && ((guint
) (now
- gc
->last_received
) >= sip
->keepalive_timeout
)
289 purple_debug_info("sipe", "sending keep alive %d\n",sip
->keepalive_timeout
);
290 sendout_pkt(gc
, "\r\n\r\n");
291 sip
->last_keepalive
= now
;
296 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
298 struct sip_connection
*ret
= NULL
;
299 GSList
*entry
= sip
->openconns
;
302 if (ret
->fd
== fd
) return ret
;
308 static void sipe_auth_free(struct sip_auth
*auth
)
310 g_free(auth
->opaque
);
314 g_free(auth
->target
);
316 auth
->type
= AUTH_TYPE_UNSET
;
319 g_free(auth
->gssapi_data
);
320 auth
->gssapi_data
= NULL
;
321 sip_sec_destroy_context(auth
->gssapi_context
);
322 auth
->gssapi_context
= NULL
;
325 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
327 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
329 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
333 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
335 struct sip_connection
*conn
= connection_find(sip
, fd
);
337 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
338 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
344 static void connection_free_all(struct sipe_account_data
*sip
)
346 struct sip_connection
*ret
= NULL
;
347 GSList
*entry
= sip
->openconns
;
350 connection_remove(sip
, ret
->fd
);
351 entry
= sip
->openconns
;
355 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
358 const char *authuser
= sip
->authuser
;
362 if (!authuser
|| strlen(authuser
) < 1) {
363 authuser
= sip
->username
;
366 if (auth
->type
== AUTH_TYPE_NTLM
|| auth
->type
== AUTH_TYPE_KERBEROS
) { /* NTLM or Kerberos */
367 gchar
*auth_protocol
= (auth
->type
== AUTH_TYPE_NTLM
? "NTLM" : "Kerberos");
369 // If we have a signature for the message, include that
370 if (msg
->signature
) {
371 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
);
374 if ((auth
->type
== AUTH_TYPE_NTLM
&& auth
->nc
== 3 && auth
->gssapi_data
&& auth
->gssapi_context
== NULL
)
375 || (auth
->type
== AUTH_TYPE_KERBEROS
&& auth
->nc
== 3)) {
379 gssapi_data
= sip_sec_init_context(&(auth
->gssapi_context
),
382 purple_account_get_bool(sip
->account
, "sso", TRUE
),
383 sip
->authdomain
? sip
->authdomain
: "",
388 if (!gssapi_data
|| !auth
->gssapi_context
) {
389 sip
->gc
->wants_to_die
= TRUE
;
390 purple_connection_error(sip
->gc
, _("Failed to authenticate to server"));
394 opaque
= (auth
->type
== AUTH_TYPE_NTLM
? g_strdup_printf(", opaque=\"%s\"", auth
->opaque
) : g_strdup(""));
395 ret
= g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"", auth_protocol
, opaque
, auth
->realm
, auth
->target
, gssapi_data
);
401 return g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"", auth_protocol
, auth
->realm
, auth
->target
);
403 } else { /* Digest */
405 /* Calculate new session key */
407 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Digest nonce: %s realm: %s\n", auth
->gssapi_data
, auth
->realm
);
408 auth
->opaque
= purple_cipher_http_digest_calculate_session_key("md5",
409 authuser
, auth
->realm
, sip
->password
,
410 auth
->gssapi_data
, NULL
);
413 sprintf(noncecount
, "%08d", auth
->nc
++);
414 response
= purple_cipher_http_digest_calculate_response("md5",
415 msg
->method
, msg
->target
, NULL
, NULL
,
416 auth
->gssapi_data
, noncecount
, NULL
,
418 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Digest response %s\n", response
);
420 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
);
426 static char *parse_attribute(const char *attrname
, const char *source
)
428 const char *tmp
, *tmp2
;
430 int len
= strlen(attrname
);
432 if (!strncmp(source
, attrname
, len
)) {
434 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
436 retval
= g_strndup(tmp
, tmp2
- tmp
);
438 retval
= g_strdup(tmp
);
444 static void fill_auth(gchar
*hdr
, struct sip_auth
*auth
)
450 purple_debug_error("sipe", "fill_auth: hdr==NULL\n");
454 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
455 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type NTLM\n");
456 auth
->type
= AUTH_TYPE_NTLM
;
459 } else if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
460 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type Kerberos\n");
461 auth
->type
= AUTH_TYPE_KERBEROS
;
465 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "fill_auth: type Digest\n");
466 auth
->type
= AUTH_TYPE_DIGEST
;
470 parts
= g_strsplit(hdr
, "\", ", 0);
471 for (i
= 0; parts
[i
]; i
++) {
474 //purple_debug_info("sipe", "parts[i] %s\n", parts[i]);
476 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
477 g_free(auth
->gssapi_data
);
478 auth
->gssapi_data
= tmp
;
480 if (auth
->type
== AUTH_TYPE_NTLM
) {
481 /* NTLM module extracts nonce from gssapi-data */
485 } else if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
486 /* Only used with AUTH_TYPE_DIGEST */
487 g_free(auth
->gssapi_data
);
488 auth
->gssapi_data
= tmp
;
489 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
490 g_free(auth
->opaque
);
492 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
496 if (auth
->type
== AUTH_TYPE_DIGEST
) {
497 /* Throw away old session key */
498 g_free(auth
->opaque
);
503 } else if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
504 g_free(auth
->target
);
513 static void sipe_canwrite_cb(gpointer data
,
514 SIPE_UNUSED_PARAMETER gint source
,
515 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
517 PurpleConnection
*gc
= data
;
518 struct sipe_account_data
*sip
= gc
->proto_data
;
522 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
524 if (max_write
== 0) {
525 if (sip
->tx_handler
!= 0){
526 purple_input_remove(sip
->tx_handler
);
532 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
534 if (written
< 0 && errno
== EAGAIN
)
536 else if (written
<= 0) {
537 /*TODO: do we really want to disconnect on a failure to write?*/
538 purple_connection_error(gc
, _("Could not write"));
542 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
545 static void sipe_canwrite_cb_ssl(gpointer data
,
546 SIPE_UNUSED_PARAMETER gint src
,
547 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
549 PurpleConnection
*gc
= data
;
550 struct sipe_account_data
*sip
= gc
->proto_data
;
554 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
556 if (max_write
== 0) {
557 if (sip
->tx_handler
!= 0) {
558 purple_input_remove(sip
->tx_handler
);
564 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
566 if (written
< 0 && errno
== EAGAIN
)
568 else if (written
<= 0) {
569 /*TODO: do we really want to disconnect on a failure to write?*/
570 purple_connection_error(gc
, _("Could not write"));
574 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
577 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
579 static void send_later_cb(gpointer data
, gint source
,
580 SIPE_UNUSED_PARAMETER
const gchar
*error
)
582 PurpleConnection
*gc
= data
;
583 struct sipe_account_data
*sip
;
584 struct sip_connection
*conn
;
586 if (!PURPLE_CONNECTION_IS_VALID(gc
))
594 purple_connection_error(gc
, _("Could not connect"));
598 sip
= gc
->proto_data
;
600 sip
->connecting
= FALSE
;
601 sip
->last_keepalive
= time(NULL
);
603 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
605 /* If there is more to write now, we need to register a handler */
606 if (sip
->txbuf
->bufused
> 0)
607 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
609 conn
= connection_create(sip
, source
);
610 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
613 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
615 struct sipe_account_data
*sip
;
617 if (!PURPLE_CONNECTION_IS_VALID(gc
))
619 if (gsc
) purple_ssl_close(gsc
);
623 sip
= gc
->proto_data
;
626 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
627 sip
->connecting
= FALSE
;
628 sip
->last_keepalive
= time(NULL
);
630 connection_create(sip
, gsc
->fd
);
632 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
637 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
638 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
640 PurpleConnection
*gc
= data
;
641 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
642 if (sip
== NULL
) return;
644 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
646 /* If there is more to write now */
647 if (sip
->txbuf
->bufused
> 0) {
648 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
653 static void sendlater(PurpleConnection
*gc
, const char *buf
)
655 struct sipe_account_data
*sip
= gc
->proto_data
;
657 if (!sip
->connecting
) {
658 purple_debug_info("sipe", "connecting to %s port %d\n", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
659 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
660 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
662 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
663 purple_connection_error(gc
, _("Could not create socket"));
666 sip
->connecting
= TRUE
;
669 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
670 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
672 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
675 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
677 struct sipe_account_data
*sip
= gc
->proto_data
;
678 time_t currtime
= time(NULL
);
679 int writelen
= strlen(buf
);
682 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sending - %s######\n%s######\n", ctime(&currtime
), tmp
= fix_newlines(buf
));
684 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
685 if (sendto(sip
->fd
, buf
, writelen
, 0, sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
686 purple_debug_info("sipe", "could not send packet\n");
695 if (sip
->tx_handler
) {
700 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
702 ret
= write(sip
->fd
, buf
, writelen
);
706 if (ret
< 0 && errno
== EAGAIN
)
708 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
713 if (ret
< writelen
) {
714 if (!sip
->tx_handler
){
716 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
719 sip
->tx_handler
= purple_input_add(sip
->fd
,
720 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
725 /* XXX: is it OK to do this? You might get part of a request sent
726 with part of another. */
727 if (sip
->txbuf
->bufused
> 0)
728 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
730 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
736 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
738 sendout_pkt(gc
, buf
);
742 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
744 GSList
*tmp
= msg
->headers
;
747 GString
*outstr
= g_string_new("");
748 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
750 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
751 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
752 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
753 tmp
= g_slist_next(tmp
);
755 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
756 sendout_pkt(sip
->gc
, outstr
->str
);
757 g_string_free(outstr
, TRUE
);
760 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
764 if (sip
->registrar
.type
== AUTH_TYPE_UNSET
) {
768 if (sip
->registrar
.gssapi_context
) {
769 struct sipmsg_breakdown msgbd
;
770 gchar
*signature_input_str
;
772 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
773 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
774 sip
->registrar
.ntlm_num
++;
775 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
776 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
777 if (signature_input_str
!= NULL
) {
778 char *signature_hex
= sip_sec_make_signature(sip
->registrar
.gssapi_context
, signature_input_str
);
779 msg
->signature
= signature_hex
;
780 msg
->rand
= g_strdup(msgbd
.rand
);
781 msg
->num
= g_strdup(msgbd
.num
);
782 g_free(signature_input_str
);
784 sipmsg_breakdown_free(&msgbd
);
787 if (sip
->registrar
.type
&& !strcmp(method
, "REGISTER")) {
788 buf
= auth_header(sip
, &sip
->registrar
, msg
);
790 sipmsg_add_header_now_pos(msg
, "Authorization", buf
, 5);
793 } 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")) {
794 sip
->registrar
.nc
= 3;
796 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
798 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
801 sip
->registrar
.type
= AUTH_TYPE_KERBEROS
;
806 buf
= auth_header(sip
, &sip
->registrar
, msg
);
807 sipmsg_add_header_now_pos(msg
, "Proxy-Authorization", buf
, 5);
810 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
814 void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
815 const char *text
, const char *body
)
819 GString
*outstr
= g_string_new("");
820 struct sipe_account_data
*sip
= gc
->proto_data
;
823 const gchar
*keepers
[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL
};
825 contact
= get_contact(sip
);
826 sipmsg_add_header(msg
, "Contact", contact
);
831 sprintf(len
, "%" G_GSIZE_FORMAT
, (gsize
) strlen(body
));
832 sipmsg_add_header(msg
, "Content-Length", len
);
834 sipmsg_add_header(msg
, "Content-Length", "0");
837 msg
->response
= code
;
839 sipmsg_strip_headers(msg
, keepers
);
840 sipmsg_merge_new_headers(msg
);
841 sign_outgoing_message(msg
, sip
, msg
->method
);
843 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
846 name
= ((struct siphdrelement
*) (tmp
->data
))->name
;
847 value
= ((struct siphdrelement
*) (tmp
->data
))->value
;
849 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
850 tmp
= g_slist_next(tmp
);
852 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
853 sendout_pkt(gc
, outstr
->str
);
854 g_string_free(outstr
, TRUE
);
857 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
859 if (sip
->transactions
) {
860 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
861 purple_debug_info("sipe", "sip->transactions count:%d after removal\n", g_slist_length(sip
->transactions
));
863 if (trans
->msg
) sipmsg_free(trans
->msg
);
864 if (trans
->payload
) {
865 (*trans
->payload
->destroy
)(trans
->payload
->data
);
866 g_free(trans
->payload
);
873 static struct transaction
*
874 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
876 gchar
*call_id
= NULL
;
878 struct transaction
*trans
= g_new0(struct transaction
, 1);
880 trans
->time
= time(NULL
);
881 trans
->msg
= (struct sipmsg
*)msg
;
882 call_id
= sipmsg_find_header(trans
->msg
, "Call-ID");
883 cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
884 trans
->key
= g_strdup_printf("<%s><%s>", call_id
, cseq
);
885 trans
->callback
= callback
;
886 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
887 purple_debug_info("sipe", "sip->transactions count:%d after addition\n", g_slist_length(sip
->transactions
));
891 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
893 struct transaction
*trans
;
894 GSList
*transactions
= sip
->transactions
;
895 gchar
*call_id
= sipmsg_find_header(msg
, "Call-ID");
896 gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
897 gchar
*key
= g_strdup_printf("<%s><%s>", call_id
, cseq
);
899 while (transactions
) {
900 trans
= transactions
->data
;
901 if (!g_strcasecmp(trans
->key
, key
)) {
905 transactions
= transactions
->next
;
913 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
914 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
915 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
917 struct sipe_account_data
*sip
= gc
->proto_data
;
918 const char *addh
= "";
921 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
922 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
923 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
924 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
925 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
926 gchar
*route
= g_strdup("");
927 gchar
*epid
= get_epid(sip
);
928 int cseq
= dialog
? ++dialog
->cseq
: 1 /* as Call-Id is new in this case */;
929 struct transaction
*trans
= NULL
;
931 if (dialog
&& dialog
->routes
)
933 GSList
*iter
= dialog
->routes
;
938 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
940 iter
= g_slist_next(iter
);
944 if (!ourtag
&& !dialog
) {
948 if (!strcmp(method
, "REGISTER")) {
949 if (sip
->regcallid
) {
951 callid
= g_strdup(sip
->regcallid
);
953 sip
->regcallid
= g_strdup(callid
);
958 if (addheaders
) addh
= addheaders
;
960 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
961 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
962 "From: <sip:%s>%s%s;epid=%s\r\n"
963 "To: <%s>%s%s%s%s\r\n"
964 "Max-Forwards: 70\r\n"
969 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
971 dialog
&& dialog
->request
? dialog
->request
: url
,
972 TRANSPORT_DESCRIPTOR
,
973 purple_network_get_my_ip(-1),
975 branch
? ";branch=" : "",
976 branch
? branch
: "",
978 ourtag
? ";tag=" : "",
979 ourtag
? ourtag
: "",
982 theirtag
? ";tag=" : "",
983 theirtag
? theirtag
: "",
984 theirepid
? ";epid=" : "",
985 theirepid
? theirepid
: "",
988 sipe_get_useragent(sip
),
992 body
? (gsize
) strlen(body
) : 0,
996 //printf ("parsing msg buf:\n%s\n\n", buf);
997 msg
= sipmsg_parse_msg(buf
);
1008 sign_outgoing_message (msg
, sip
, method
);
1010 buf
= sipmsg_to_string (msg
);
1012 /* add to ongoing transactions */
1013 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1014 if (strcmp(method
, "ACK")) {
1015 trans
= transactions_add_buf(sip
, msg
, tc
);
1019 sendout_pkt(gc
, buf
);
1026 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1029 send_soap_request_with_cb(struct sipe_account_data
*sip
,
1032 TransCallback callback
,
1033 struct transaction_payload
*payload
)
1035 gchar
*from
= from0
? g_strdup(from0
) : sip_uri_self(sip
);
1036 gchar
*contact
= get_contact(sip
);
1037 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1038 "Content-Type: application/SOAP+xml\r\n",contact
);
1040 struct transaction
*trans
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1041 trans
->payload
= payload
;
1048 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1050 send_soap_request_with_cb(sip
, NULL
, body
, NULL
, NULL
);
1053 static char *get_contact_register(struct sipe_account_data
*sip
)
1055 char *epid
= get_epid(sip
);
1056 char *uuid
= generateUUIDfromEPID(epid
);
1057 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
);
1063 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1071 if (!sip
->sipdomain
) return;
1073 uri
= sip_uri_from_name(sip
->sipdomain
);
1074 expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
1075 to
= sip_uri_self(sip
);
1076 contact
= get_contact_register(sip
);
1077 hdr
= g_strdup_printf("Contact: %s\r\n"
1078 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1079 "Event: registration\r\n"
1080 "Allow-Events: presence\r\n"
1081 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1082 "%s", contact
, expires
);
1086 sip
->registerstatus
= 1;
1088 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1089 process_register_response
);
1096 static void do_register_cb(struct sipe_account_data
*sip
,
1097 SIPE_UNUSED_PARAMETER
void *unused
)
1099 do_register_exp(sip
, -1);
1100 sip
->reregister_set
= FALSE
;
1103 static void do_register(struct sipe_account_data
*sip
)
1105 do_register_exp(sip
, -1);
1109 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1111 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1112 send_soap_request(sip
, body
);
1117 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1120 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1122 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1125 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1129 void sipe_auth_user_cb(void * data
)
1131 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1134 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1139 void sipe_deny_user_cb(void * data
)
1141 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1144 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1149 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1151 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1152 sipe_contact_allow_deny(sip
, name
, TRUE
);
1156 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1158 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1159 sipe_contact_allow_deny(sip
, name
, FALSE
);
1163 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1165 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1166 sipe_contact_set_acl(sip, name, "");
1170 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1174 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1175 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1177 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1179 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1180 if (!watchers
) return;
1182 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1183 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1184 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1185 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1187 // TODO pull out optional displayName to pass as alias
1189 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1190 job
->who
= remote_user
;
1192 purple_account_request_authorization(
1206 xmlnode_free(watchers
);
1211 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1213 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1214 if (!purple_group
) {
1215 purple_group
= purple_group_new(group
->name
);
1216 purple_blist_add_group(purple_group
, NULL
);
1220 group
->purple_group
= purple_group
;
1221 sip
->groups
= g_slist_append(sip
->groups
, group
);
1222 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1224 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1228 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1230 struct sipe_group
*group
;
1236 entry
= sip
->groups
;
1238 group
= entry
->data
;
1239 if (group
->id
== id
) {
1242 entry
= entry
->next
;
1247 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, const gchar
* name
)
1249 struct sipe_group
*group
;
1251 if (!sip
|| !name
) {
1255 entry
= sip
->groups
;
1257 group
= entry
->data
;
1258 if (!strcmp(group
->name
, name
)) {
1261 entry
= entry
->next
;
1267 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1270 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1271 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1272 send_soap_request(sip
, body
);
1274 g_free(group
->name
);
1275 group
->name
= g_strdup(name
);
1279 * Only appends if no such value already stored.
1282 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1283 GSList
* res
= list
;
1284 if (!g_slist_find_custom(list
, data
, func
)) {
1285 res
= g_slist_insert_sorted(list
, data
, func
);
1291 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1292 return group1
->id
- group2
->id
;
1296 * Returns string like "2 4 7 8" - group ids buddy belong to.
1299 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1302 //creating array from GList, converting int to gchar*
1303 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1304 GSList
*entry
= buddy
->groups
;
1306 struct sipe_group
* group
= entry
->data
;
1307 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1308 entry
= entry
->next
;
1312 res
= g_strjoinv(" ", ids_arr
);
1313 g_strfreev(ids_arr
);
1318 * Sends buddy update to server
1321 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1323 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1324 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1326 if (buddy
&& purple_buddy
) {
1327 gchar
*alias
= (gchar
*)purple_buddy_get_alias(purple_buddy
);
1329 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1330 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1332 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1333 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1335 send_soap_request(sip
, body
);
1341 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
1343 if (msg
->response
== 200) {
1344 struct sipe_group
*group
;
1345 struct group_user_context
*ctx
= trans
->payload
->data
;
1349 struct sipe_buddy
*buddy
;
1351 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1356 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1362 group_id
= xmlnode_get_data(node
);
1368 group
= g_new0(struct sipe_group
, 1);
1369 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1371 group
->name
= g_strdup(ctx
->group_name
);
1373 sipe_group_add(sip
, group
);
1375 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1377 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1380 sipe_group_set_user(sip
, ctx
->user_name
);
1388 static void sipe_group_context_destroy(gpointer data
)
1390 struct group_user_context
*ctx
= data
;
1391 g_free(ctx
->group_name
);
1392 g_free(ctx
->user_name
);
1396 static void sipe_group_create (struct sipe_account_data
*sip
, const gchar
*name
, const gchar
* who
)
1398 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1399 struct group_user_context
*ctx
= g_new0(struct group_user_context
, 1);
1401 ctx
->group_name
= g_strdup(name
);
1402 ctx
->user_name
= g_strdup(who
);
1403 payload
->destroy
= sipe_group_context_destroy
;
1404 payload
->data
= ctx
;
1406 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1407 send_soap_request_with_cb(sip
, NULL
, body
, process_add_group_response
, payload
);
1412 * Data structure for scheduled actions
1415 struct scheduled_action
{
1418 * Format is <Event>[<Data>...]
1419 * Example: <presence><sip:user@domain.com> or <registration>
1422 guint timeout_handler
;
1423 gboolean repetitive
;
1425 GDestroyNotify destroy
;
1426 struct sipe_account_data
*sip
;
1432 * Should return FALSE if repetitive action is not needed
1434 static gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1437 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1438 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1439 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1440 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1441 ret
= sched_action
->repetitive
;
1442 if (sched_action
->destroy
) {
1443 (*sched_action
->destroy
)(sched_action
->payload
);
1445 g_free(sched_action
->name
);
1446 g_free(sched_action
);
1451 * Kills action timer effectively cancelling
1454 * @param name of action
1456 static void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, const gchar
*name
)
1460 if (!sip
->timeouts
|| !name
) return;
1462 entry
= sip
->timeouts
;
1464 struct scheduled_action
*sched_action
= entry
->data
;
1465 if(!strcmp(sched_action
->name
, name
)) {
1466 GSList
*to_delete
= entry
;
1467 entry
= entry
->next
;
1468 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1469 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1470 purple_timeout_remove(sched_action
->timeout_handler
);
1471 if (sched_action
->destroy
) {
1472 (*sched_action
->destroy
)(sched_action
->payload
);
1474 g_free(sched_action
->name
);
1475 g_free(sched_action
);
1477 entry
= entry
->next
;
1483 sipe_schedule_action0(const gchar
*name
,
1487 GDestroyNotify destroy
,
1488 struct sipe_account_data
*sip
,
1491 struct scheduled_action
*sched_action
;
1493 /* Make sure each action only exists once */
1494 sipe_cancel_scheduled_action(sip
, name
);
1496 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name
, timeout
, isSeconds
? "sec" : "msec");
1497 sched_action
= g_new0(struct scheduled_action
, 1);
1498 sched_action
->repetitive
= FALSE
;
1499 sched_action
->name
= g_strdup(name
);
1500 sched_action
->action
= action
;
1501 sched_action
->destroy
= destroy
;
1502 sched_action
->sip
= sip
;
1503 sched_action
->payload
= payload
;
1504 sched_action
->timeout_handler
= isSeconds
? purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
) :
1505 purple_timeout_add(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1506 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1507 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1511 sipe_schedule_action(const gchar
*name
,
1514 GDestroyNotify destroy
,
1515 struct sipe_account_data
*sip
,
1518 sipe_schedule_action0(name
, timeout
, TRUE
, action
, destroy
, sip
, payload
);
1522 * Same as sipe_schedule_action() but timeout is in milliseconds.
1525 sipe_schedule_action_msec(const gchar
*name
,
1528 GDestroyNotify destroy
,
1529 struct sipe_account_data
*sip
,
1532 sipe_schedule_action0(name
, timeout
, FALSE
, action
, destroy
, sip
, payload
);
1536 sipe_sched_calendar_status_update(struct sipe_account_data
*sip
,
1537 time_t calculate_from
);
1540 sipe_get_availability_by_status(const char* sipe_status_id
, char** activity_token
);
1543 sipe_get_status_by_availability(int avail
,
1547 sipe_set_purple_account_status_and_note(const PurpleAccount
*account
,
1548 const char *status_id
,
1549 const char *message
,
1550 time_t do_not_publish
[]);
1553 sipe_apply_calendar_status(struct sipe_account_data
*sip
,
1554 struct sipe_buddy
*sbuddy
,
1555 const char *status_id
)
1557 time_t cal_avail_since
;
1558 int cal_status
= sipe_cal_get_status(sbuddy
, time(NULL
), &cal_avail_since
);
1560 gchar
*self_uri
= sip_uri_self(sip
);
1562 if (!sbuddy
) return;
1564 if (cal_status
< SIPE_CAL_NO_DATA
) {
1565 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status
, sbuddy
->name
);
1566 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since
)));
1569 /* scheduled Cal update call */
1571 status_id
= sbuddy
->last_non_cal_status_id
;
1572 g_free(sbuddy
->activity
);
1573 sbuddy
->activity
= g_strdup(sbuddy
->last_non_cal_activity
);
1576 /* adjust to calendar status */
1577 if (cal_status
!= SIPE_CAL_NO_DATA
) {
1578 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy
->user_avail_since
)));
1580 if (cal_status
== SIPE_CAL_BUSY
1581 && cal_avail_since
> sbuddy
->user_avail_since
1582 && 6500 >= sipe_get_availability_by_status(status_id
, NULL
))
1584 status_id
= SIPE_STATUS_ID_BUSY
;
1585 g_free(sbuddy
->activity
);
1586 sbuddy
->activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING
));
1588 avail
= sipe_get_availability_by_status(status_id
, NULL
);
1590 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy
->activity_since
)));
1591 if (cal_avail_since
> sbuddy
->activity_since
) {
1592 if (cal_status
== SIPE_CAL_OOF
1593 && avail
>= 15000) /* 12000 in 2007 */
1595 g_free(sbuddy
->activity
);
1596 sbuddy
->activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF
));
1601 /* then set status_id actually */
1602 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id
, sbuddy
->name
);
1603 purple_prpl_got_user_status(sip
->account
, sbuddy
->name
, status_id
, NULL
);
1605 /* set our account state to the one in roaming (including calendar info) */
1606 if (sip
->initial_state_published
&& !strcmp(sbuddy
->name
, self_uri
)) {
1607 if (!strcmp(status_id
, SIPE_STATUS_ID_OFFLINE
)) {
1608 status_id
= g_strdup(SIPE_STATUS_ID_INVISIBLE
); /* not not let offline status switch us off */
1611 purple_debug_info("sipe", "sipe_got_user_status: to %s for the account\n", sip
->status
);
1612 sipe_set_purple_account_status_and_note(sip
->account
, status_id
, sip
->note
, sip
->do_not_publish
);
1618 sipe_got_user_status(struct sipe_account_data
*sip
,
1620 const char *status_id
)
1622 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
1624 if (!sbuddy
) return;
1626 /* Check if on 2005 system contact's calendar,
1627 * then set/preserve it.
1629 if (!sip
->ocs2007
) {
1630 sipe_apply_calendar_status(sip
, sbuddy
, status_id
);
1632 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
1637 update_calendar_status_cb(SIPE_UNUSED_PARAMETER
char *name
,
1638 struct sipe_buddy
*sbuddy
,
1639 struct sipe_account_data
*sip
)
1641 sipe_apply_calendar_status(sip
, sbuddy
, NULL
);
1645 * Updates contact's status
1646 * based on their calendar information.
1648 * Applicability: 2005 systems
1651 update_calendar_status(struct sipe_account_data
*sip
)
1653 purple_debug_info("sipe", "update_calendar_status() started.\n");
1654 g_hash_table_foreach(sip
->buddies
, (GHFunc
)update_calendar_status_cb
, (gpointer
)sip
);
1656 /* repeat scheduling */
1657 sipe_sched_calendar_status_update(sip
, time(NULL
) + 3*60 /* 3 min */);
1661 * Schedules process of contacts' status update
1662 * based on their calendar information.
1663 * Should be scheduled to the beginning of every
1664 * 15 min interval, like:
1665 * 13:00, 13:15, 13:30, 13:45, etc.
1667 * Applicability: 2005 systems
1670 sipe_sched_calendar_status_update(struct sipe_account_data
*sip
,
1671 time_t calculate_from
)
1673 int interval
= 15*60;
1674 /** start of the beginning of closest 15 min interval. */
1675 time_t next_start
= ((time_t)((int)((int)calculate_from
)/interval
+ 1)*interval
);
1677 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1678 asctime(localtime(&calculate_from
)));
1679 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1680 asctime(localtime(&next_start
)));
1682 sipe_schedule_action("<+2005-cal-status>",
1683 (int)(next_start
- time(NULL
)),
1684 (Action
)update_calendar_status
,
1691 * Schedules process of self status publish
1692 * based on own calendar information.
1693 * Should be scheduled to the beginning of every
1694 * 15 min interval, like:
1695 * 13:00, 13:15, 13:30, 13:45, etc.
1697 * Applicability: 2007+ systems
1700 sipe_sched_calendar_status_self_publish(struct sipe_account_data
*sip
,
1701 time_t calculate_from
)
1703 int interval
= 5*60;
1704 /** start of the beginning of closest 5 min interval. */
1705 time_t next_start
= ((time_t)((int)((int)calculate_from
)/interval
+ 1)*interval
);
1707 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1708 asctime(localtime(&calculate_from
)));
1709 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1710 asctime(localtime(&next_start
)));
1712 sipe_schedule_action("<+2007-cal-status>",
1713 (int)(next_start
- time(NULL
)),
1714 (Action
)publish_calendar_status_self
,
1720 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1722 /** Should be g_free()'d
1725 sipe_get_subscription_key(gchar
*event
,
1730 if (is_empty(event
)) return NULL
;
1732 if (event
&& !g_ascii_strcasecmp(event
, "presence")) {
1733 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1734 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, with
);
1736 /* @TODO drop participated buddies' just_added flag */
1738 /* Subscription is identified by <event> key */
1739 key
= g_strdup_printf("<%s>", event
);
1745 gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
1746 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
1748 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
1749 gchar
*event
= sipmsg_find_header(msg
, "Event");
1752 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1754 struct sipmsg
*request_msg
= trans
->msg
;
1755 event
= sipmsg_find_header(request_msg
, "Event");
1758 key
= sipe_get_subscription_key(event
, with
);
1760 /* 200 OK; 481 Call Leg Does Not Exist */
1761 if (key
&& (msg
->response
== 200 || msg
->response
== 481)) {
1762 if (g_hash_table_lookup(sip
->subscriptions
, key
)) {
1763 g_hash_table_remove(sip
->subscriptions
, key
);
1764 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key
);
1768 /* create/store subscription dialog if not yet */
1769 if (msg
->response
== 200) {
1770 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
1771 gchar
*cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
1774 struct sip_subscription
*subscription
= g_new0(struct sip_subscription
, 1);
1775 g_hash_table_insert(sip
->subscriptions
, g_strdup(key
), subscription
);
1777 subscription
->dialog
.callid
= g_strdup(callid
);
1778 subscription
->dialog
.cseq
= atoi(cseq
);
1779 subscription
->dialog
.with
= g_strdup(with
);
1780 subscription
->event
= g_strdup(event
);
1781 sipe_dialog_parse(&subscription
->dialog
, msg
, TRUE
);
1783 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key
);
1792 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1794 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1799 static void sipe_subscribe_resource_uri(const char *name
,
1800 SIPE_UNUSED_PARAMETER gpointer value
,
1801 gchar
**resources_uri
)
1803 gchar
*tmp
= *resources_uri
;
1804 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1808 static void sipe_subscribe_resource_uri_with_context(const char *name
, gpointer value
, gchar
**resources_uri
)
1810 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
1811 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
1812 gchar
*tmp
= *resources_uri
;
1814 if (sbuddy
) sbuddy
->just_added
= FALSE
; /* should be enought to include context one time */
1816 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp
, name
, context
);
1821 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1822 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1823 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1824 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1825 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1828 static void sipe_subscribe_presence_batched_to(struct sipe_account_data
*sip
, gchar
*resources_uri
, gchar
*to
)
1831 gchar
*contact
= get_contact(sip
);
1834 gchar
*require
= "";
1836 gchar
*autoextend
= "";
1837 gchar
*content_type
;
1838 struct sip_dialog
*dialog
;
1841 require
= ", categoryList";
1842 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1843 content_type
= "application/msrtc-adrl-categorylist+xml";
1844 content
= g_strdup_printf(
1845 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1846 "<action name=\"subscribe\" id=\"63792024\">\n"
1847 "<adhocList>\n%s</adhocList>\n"
1848 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1849 "<category name=\"calendarData\"/>\n"
1850 "<category name=\"contactCard\"/>\n"
1851 "<category name=\"note\"/>\n"
1852 "<category name=\"state\"/>\n"
1855 "</batchSub>", sip
->username
, resources_uri
);
1857 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1858 content_type
= "application/adrl+xml";
1859 content
= g_strdup_printf(
1860 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1861 "<create xmlns=\"\">\n%s</create>\n"
1862 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1864 g_free(resources_uri
);
1866 request
= g_strdup_printf(
1867 "Require: adhoclist%s\r\n"
1868 "Supported: eventlist\r\n"
1869 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1870 "Supported: ms-piggyback-first-notify\r\n"
1871 "%sSupported: ms-benotify\r\n"
1872 "Proxy-Require: ms-benotify\r\n"
1873 "Event: presence\r\n"
1874 "Content-Type: %s\r\n"
1875 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
1878 /* subscribe to buddy presence */
1879 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1880 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, to
);
1881 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
1882 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
1884 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, dialog
, process_subscribe_response
);
1892 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
,
1893 SIPE_UNUSED_PARAMETER
void *unused
)
1895 gchar
*to
= sip_uri_self(sip
);
1896 gchar
*resources_uri
= g_strdup("");
1898 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
1900 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1903 sipe_subscribe_presence_batched_to(sip
, resources_uri
, to
);
1906 struct presence_batched_routed
{
1911 static void sipe_subscribe_presence_batched_routed_free(void *payload
)
1913 struct presence_batched_routed
*data
= payload
;
1914 GSList
*buddies
= data
->buddies
;
1916 g_free(buddies
->data
);
1917 buddies
= buddies
->next
;
1919 g_slist_free(data
->buddies
);
1924 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data
*sip
, void *payload
)
1926 struct presence_batched_routed
*data
= payload
;
1927 GSList
*buddies
= data
->buddies
;
1928 gchar
*resources_uri
= g_strdup("");
1930 gchar
*tmp
= resources_uri
;
1931 resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, (char *) buddies
->data
);
1933 buddies
= buddies
->next
;
1935 sipe_subscribe_presence_batched_to(sip
, resources_uri
,
1936 g_strdup(data
->host
));
1940 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1941 * The user sends a single SUBSCRIBE request to the subscribed contact.
1942 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1946 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, void *buddy_name
)
1950 gchar
*to
= sip_uri((char *)buddy_name
);
1951 gchar
*tmp
= get_contact(sip
);
1953 gchar
*content
= NULL
;
1954 gchar
*autoextend
= "";
1955 gchar
*content_type
= "";
1956 struct sip_dialog
*dialog
;
1957 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, to
);
1958 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
1960 if (sbuddy
) sbuddy
->just_added
= FALSE
;
1963 content_type
= "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1965 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1968 request
= g_strdup_printf(
1969 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1970 "Supported: ms-piggyback-first-notify\r\n"
1971 "%s%sSupported: ms-benotify\r\n"
1972 "Proxy-Require: ms-benotify\r\n"
1973 "Event: presence\r\n"
1974 "Contact: %s\r\n", autoextend
, content_type
, tmp
);
1977 content
= g_strdup_printf(
1978 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1979 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1980 "<resource uri=\"%s\"%s\n"
1982 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1983 "<category name=\"calendarData\"/>\n"
1984 "<category name=\"contactCard\"/>\n"
1985 "<category name=\"note\"/>\n"
1986 "<category name=\"state\"/>\n"
1989 "</batchSub>", sip
->username
, to
, context
);
1994 /* subscribe to buddy presence */
1995 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1996 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, to
);
1997 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
1998 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
2000 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, dialog
, process_subscribe_response
);
2008 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
2010 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status
));
2012 if (!purple_status_is_active(status
))
2016 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
2020 time_t now
= time(NULL
);
2021 const char *status_id
= purple_status_get_id(status
);
2022 const char *note
= purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
);
2023 sipe_activity activity
= sipe_get_activity_by_token(status_id
);
2024 gboolean do_not_publish
= ((now
- sip
->do_not_publish
[activity
]) <= 2);
2027 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2028 status_id
, (int)sip
->do_not_publish
[activity
], (int)now
);
2030 sip
->do_not_publish
[activity
] = 0;
2031 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2032 status_id
, (int)sip
->do_not_publish
[activity
]);
2036 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2040 g_free(sip
->status
);
2041 sip
->status
= g_strdup(status_id
);
2043 /* this will preserve OOF flag as well */
2044 if (!(note
&& sip
->note
&& !strcmp(note
, sip
->note
))) {
2045 sip
->is_oof_note
= FALSE
;
2047 sip
->note
= g_strdup(note
);
2048 sip
->note_since
= time(NULL
);
2051 /* schedule 2 sec to capture idle flag */
2052 action_name
= g_strdup_printf("<%s>", "+set-status");
2053 sipe_schedule_action(action_name
, SIPE_IDLE_SET_DELAY
, (Action
)send_presence_status
, NULL
, sip
, NULL
);
2054 g_free(action_name
);
2059 sipe_set_idle(PurpleConnection
* gc
,
2062 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval
);
2065 struct sipe_account_data
*sip
= gc
->proto_data
;
2068 sip
->idle_switch
= time(NULL
);
2069 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip
->idle_switch
))));
2075 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
,
2076 SIPE_UNUSED_PARAMETER
const char *alias
)
2078 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2079 sipe_group_set_user(sip
, name
);
2083 sipe_group_buddy(PurpleConnection
*gc
,
2085 const char *old_group_name
,
2086 const char *new_group_name
)
2088 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2089 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
2090 struct sipe_group
* old_group
= NULL
;
2091 struct sipe_group
* new_group
;
2093 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2094 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
2096 if(!buddy
) { // buddy not in roaming list
2100 if (old_group_name
) {
2101 old_group
= sipe_group_find_by_name(sip
, old_group_name
);
2103 new_group
= sipe_group_find_by_name(sip
, new_group_name
);
2106 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
2107 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
2111 sipe_group_create(sip
, new_group_name
, who
);
2113 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
2114 sipe_group_set_user(sip
, who
);
2118 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
2120 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
2122 /* libpurple can call us with undefined buddy or group */
2123 if (buddy
&& group
) {
2124 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2126 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2127 gchar
*buddy_name
= g_ascii_strdown(buddy
->name
, -1);
2128 purple_blist_rename_buddy(buddy
, buddy_name
);
2131 /* Prepend sip: if needed */
2132 if (strncmp("sip:", buddy
->name
, 4)) {
2133 gchar
*buf
= sip_uri_from_name(buddy
->name
);
2134 purple_blist_rename_buddy(buddy
, buf
);
2138 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
2139 struct sipe_buddy
*b
= g_new0(struct sipe_buddy
, 1);
2140 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy
->name
);
2141 b
->name
= g_strdup(buddy
->name
);
2142 b
->just_added
= TRUE
;
2143 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
2144 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
2145 /* @TODO should go to callback */
2146 sipe_subscribe_presence_single(sip
, b
->name
);
2148 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy
->name
);
2153 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
2157 * We are calling g_hash_table_foreach_steal(). That means that no
2158 * key/value deallocation functions are called. Therefore the glib
2159 * hash code does not touch the key (buddy->name) or value (buddy)
2160 * of the to-be-deleted hash node at all. It follows that we
2162 * - MUST free the memory for the key ourselves and
2163 * - ARE allowed to do it in this function
2165 * Conclusion: glib must be broken on the Windows platform if sipe
2166 * crashes with SIGTRAP when closing. You'll have to live
2167 * with the memory leak until this is fixed.
2169 g_free(buddy
->name
);
2171 g_free(buddy
->activity
);
2172 g_free(buddy
->meeting_subject
);
2173 g_free(buddy
->meeting_location
);
2174 g_free(buddy
->note
);
2176 g_free(buddy
->cal_start_time
);
2177 g_free(buddy
->cal_free_busy_base64
);
2178 g_free(buddy
->cal_free_busy
);
2179 g_free(buddy
->last_non_cal_activity
);
2181 sipe_cal_free_working_hours(buddy
->cal_working_hours
);
2183 g_free(buddy
->device_name
);
2184 g_slist_free(buddy
->groups
);
2189 * Unassociates buddy from group first.
2190 * Then see if no groups left, removes buddy completely.
2191 * Otherwise updates buddy groups on server.
2193 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
2195 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2196 struct sipe_buddy
*b
;
2197 struct sipe_group
*g
= NULL
;
2199 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
2202 b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
2206 g
= sipe_group_find_by_name(sip
, group
->name
);
2210 b
->groups
= g_slist_remove(b
->groups
, g
);
2211 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
2214 if (g_slist_length(b
->groups
) < 1) {
2215 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
2216 sipe_cancel_scheduled_action(sip
, action_name
);
2217 g_free(action_name
);
2219 g_hash_table_remove(sip
->buddies
, buddy
->name
);
2222 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
2223 send_soap_request(sip
, body
);
2229 //updates groups on server
2230 sipe_group_set_user(sip
, b
->name
);
2236 sipe_rename_group(PurpleConnection
*gc
,
2237 const char *old_name
,
2239 SIPE_UNUSED_PARAMETER GList
*moved_buddies
)
2241 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2242 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, old_name
);
2244 sipe_group_rename(sip
, s_group
, group
->name
);
2246 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
2251 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
2253 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2254 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
2257 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
2258 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
2259 send_soap_request(sip
, body
);
2262 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
2263 g_free(s_group
->name
);
2266 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
2270 /** All statuses need message attribute to pass Note */
2271 static GList
*sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount
*acc
)
2273 PurpleStatusType
*type
;
2274 GList
*types
= NULL
;
2276 /* Macros to reduce code repetition.
2277 Translators: noun */
2278 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2280 TRUE, user, FALSE, \
2281 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2283 types = g_list_append(types, type);
2286 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE
,
2292 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2293 sipe_activity_map
[SIPE_ACTIVITY_BUSY
].status_id
,
2294 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY
),
2297 /* Do Not Disturb */
2298 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2299 sipe_activity_map
[SIPE_ACTIVITY_DND
].status_id
,
2304 /* Goes first in the list as
2305 * purple picks the first status with the AWAY type
2308 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2314 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2315 sipe_activity_map
[SIPE_ACTIVITY_BRB
].status_id
,
2316 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB
),
2319 /* Appear Offline */
2320 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE
,
2325 /* Offline (not user settable) */
2326 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE
,
2335 * A callback for g_hash_table_foreach
2338 sipe_buddy_subscribe_cb(char *buddy_name
,
2339 SIPE_UNUSED_PARAMETER
struct sipe_buddy
*buddy
,
2340 struct sipe_account_data
*sip
)
2342 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy_name
);
2343 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2344 guint time_range
= (g_hash_table_size(sip
->buddies
) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2345 guint timeout
= ((guint
) rand()) / (RAND_MAX
/ time_range
) + 1; /* random period within the range but never 0! */
2347 sipe_schedule_action_msec(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(buddy_name
));
2348 g_free(action_name
);
2352 * Removes entries from purple buddy list
2353 * that does not correspond ones in the roaming contact list.
2355 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
2356 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
2357 GSList
*entry
= buddies
;
2358 struct sipe_buddy
*buddy
;
2362 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
2363 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
2366 g
= purple_buddy_get_group(b
);
2367 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
2369 gboolean in_sipe_groups
= FALSE
;
2370 GSList
*entry2
= buddy
->groups
;
2372 struct sipe_group
*group
= entry2
->data
;
2373 if (!strcmp(group
->name
, g
->name
)) {
2374 in_sipe_groups
= TRUE
;
2377 entry2
= entry2
->next
;
2379 if(!in_sipe_groups
) {
2380 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
2381 purple_blist_remove_buddy(b
);
2384 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
2385 purple_blist_remove_buddy(b
);
2387 entry
= entry
->next
;
2389 g_slist_free(buddies
);
2392 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2394 int len
= msg
->bodylen
;
2396 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
2399 const gchar
*contacts_delta
;
2400 xmlnode
*group_node
;
2401 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
2405 /* Convert the contact from XML to Purple Buddies */
2406 isc
= xmlnode_from_str(msg
->body
, len
);
2411 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
2412 if (contacts_delta
) {
2413 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2416 if (!strcmp(isc
->name
, "contactList")) {
2419 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
2420 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2421 const char *name
= xmlnode_get_attrib(group_node
, "name");
2423 if (!strncmp(name
, "~", 1)) {
2424 name
= _("Other Contacts");
2426 group
->name
= g_strdup(name
);
2427 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
2429 sipe_group_add(sip
, group
);
2432 // Make sure we have at least one group
2433 if (g_slist_length(sip
->groups
) == 0) {
2434 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2435 PurpleGroup
*purple_group
;
2436 group
->name
= g_strdup(_("Other Contacts"));
2438 purple_group
= purple_group_new(group
->name
);
2439 purple_blist_add_group(purple_group
, NULL
);
2440 sip
->groups
= g_slist_append(sip
->groups
, group
);
2443 /* Parse contacts */
2444 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
2445 const gchar
*uri
= xmlnode_get_attrib(item
, "uri");
2446 const gchar
*name
= xmlnode_get_attrib(item
, "name");
2448 struct sipe_buddy
*buddy
= NULL
;
2450 gchar
**item_groups
;
2453 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2454 tmp
= sip_uri_from_name(uri
);
2455 buddy_name
= g_ascii_strdown(tmp
, -1);
2458 /* assign to group Other Contacts if nothing else received */
2459 tmp
= g_strdup(xmlnode_get_attrib(item
, "groups"));
2460 if(!tmp
|| !strcmp("", tmp
) ) {
2461 struct sipe_group
*group
= sipe_group_find_by_name(sip
, _("Other Contacts"));
2463 tmp
= group
? g_strdup_printf("%d", group
->id
) : g_strdup("1");
2465 item_groups
= g_strsplit(tmp
, " ", 0);
2468 while (item_groups
[i
]) {
2469 struct sipe_group
*group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
2471 // If couldn't find the right group for this contact, just put them in the first group we have
2472 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2473 group
= sip
->groups
->data
;
2476 if (group
!= NULL
) {
2477 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2479 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2480 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2482 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name
, uri
);
2485 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2486 if (name
!= NULL
&& strlen(name
) != 0) {
2487 purple_blist_alias_buddy(b
, name
);
2489 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name
, name
);
2494 buddy
= g_new0(struct sipe_buddy
, 1);
2495 buddy
->name
= g_strdup(b
->name
);
2496 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2499 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2501 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2503 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2508 } // while, contact groups
2509 g_strfreev(item_groups
);
2514 sipe_cleanup_local_blist(sip
);
2516 /* Add self-contact if not there yet. 2005 systems. */
2517 /* This will resemble subscription to roaming_self in 2007 systems */
2518 if (!sip
->ocs2007
) {
2519 gchar
*self_uri
= sip_uri_self(sip
);
2520 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, self_uri
);
2523 buddy
= g_new0(struct sipe_buddy
, 1);
2524 buddy
->name
= g_strdup(self_uri
);
2525 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2532 /* subscribe to buddies */
2533 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2534 if (sip
->batched_support
) {
2535 sipe_subscribe_presence_batched(sip
, NULL
);
2537 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
2539 sip
->subscribed_buddies
= TRUE
;
2541 /* for 2005 systems schedule contacts' status update
2542 * based on their calendar information
2544 if (!sip
->ocs2007
) {
2545 sipe_sched_calendar_status_update(sip
, time(NULL
));
2552 * Subscribe roaming contacts
2554 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
)
2556 gchar
*to
= sip_uri_self(sip
);
2557 gchar
*tmp
= get_contact(sip
);
2558 gchar
*hdr
= g_strdup_printf(
2559 "Event: vnd-microsoft-roaming-contacts\r\n"
2560 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2561 "Supported: com.microsoft.autoextend\r\n"
2562 "Supported: ms-benotify\r\n"
2563 "Proxy-Require: ms-benotify\r\n"
2564 "Supported: ms-piggyback-first-notify\r\n"
2565 "Contact: %s\r\n", tmp
);
2568 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2573 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
,
2574 SIPE_UNUSED_PARAMETER
void *unused
)
2577 struct sip_dialog
*dialog
;
2578 gchar
*to
= sip_uri_self(sip
);
2579 gchar
*tmp
= get_contact(sip
);
2580 gchar
*hdr
= g_strdup_printf(
2581 "Event: presence.wpending\r\n"
2582 "Accept: text/xml+msrtc.wpending\r\n"
2583 "Supported: com.microsoft.autoextend\r\n"
2584 "Supported: ms-benotify\r\n"
2585 "Proxy-Require: ms-benotify\r\n"
2586 "Supported: ms-piggyback-first-notify\r\n"
2587 "Contact: %s\r\n", tmp
);
2590 /* Subscription is identified by <event> key */
2591 key
= g_strdup_printf("<%s>", "presence.wpending");
2592 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
2593 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
2595 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", dialog
, process_subscribe_response
);
2603 * Fires on deregistration event initiated by server.
2604 * [MS-SIPREGE] SIP extension.
2609 // Content-Type: text/registration-event
2610 // subscription-state: terminated;expires=0
2611 // ms-diagnostics-public: 4141;reason="User disabled"
2613 // deregistered;event=rejected
2615 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2617 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2618 gchar
*event
= NULL
;
2619 gchar
*reason
= NULL
;
2620 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
2622 warning
= warning
? warning
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2623 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2625 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2626 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2627 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2628 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2630 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2634 if (warning
!= NULL
) {
2635 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
2636 } else { // for LCS2005
2638 if (event
&& !g_ascii_strcasecmp(event
, "unregistered")) {
2639 error_id
= 4140; // [MS-SIPREGE]
2640 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2641 reason
= g_strdup(_("you are already signed in at another location"));
2642 } else if (event
&& !g_ascii_strcasecmp(event
, "rejected")) {
2644 reason
= g_strdup(_("user disabled")); // [MS-OCER]
2645 } else if (event
&& !g_ascii_strcasecmp(event
, "deactivated")) {
2647 reason
= g_strdup(_("user moved")); // [MS-OCER]
2651 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), reason
? reason
: _("no reason given"));
2654 sip
->gc
->wants_to_die
= TRUE
;
2655 purple_connection_error(sip
->gc
, warning
);
2660 static void sipe_process_provisioning_v2(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2662 xmlnode
*xn_provision_group_list
;
2665 xn_provision_group_list
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2667 /* provisionGroup */
2668 for (node
= xmlnode_get_child(xn_provision_group_list
, "provisionGroup"); node
; node
= xmlnode_get_next_twin(node
)) {
2669 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node
, "name"))) {
2670 g_free(sip
->focus_factory_uri
);
2671 sip
->focus_factory_uri
= xmlnode_get_data(xmlnode_get_child(node
, "focusFactoryUri"));
2672 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2673 sip
->focus_factory_uri
? sip
->focus_factory_uri
: "");
2677 xmlnode_free(xn_provision_group_list
);
2680 /** for 2005 system */
2682 sipe_process_provisioning(struct sipe_account_data
*sip
,
2685 xmlnode
*xn_provision
;
2688 xn_provision
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2689 if ((node
= xmlnode_get_child(xn_provision
, "user"))) {
2690 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node
, "uri"));
2691 if ((node
= xmlnode_get_child(node
, "line"))) {
2692 const gchar
*line_uri
= xmlnode_get_attrib(node
, "uri");
2693 const gchar
*server
= xmlnode_get_attrib(node
, "server");
2694 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri
, server
);
2695 sip_csta_open(sip
, line_uri
, server
);
2698 xmlnode_free(xn_provision
);
2701 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2703 const gchar
*contacts_delta
;
2706 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2712 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2715 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2722 free_container(struct sipe_container
*container
)
2726 if (!container
) return;
2728 entry
= container
->members
;
2730 g_free(entry
->data
);
2731 entry
= g_slist_remove(entry
, entry
->data
);
2737 * Finds locally stored MS-PRES container member
2739 static struct sipe_container_member
*
2740 sipe_find_container_member(struct sipe_container
*container
,
2744 struct sipe_container_member
*member
;
2747 if (container
== NULL
|| type
== NULL
) {
2751 entry
= container
->members
;
2753 member
= entry
->data
;
2754 if (!g_strcasecmp(member
->type
, type
)
2755 && ((!member
->value
&& !value
)
2756 || (value
&& member
->value
&& !g_strcasecmp(member
->value
, value
)))
2760 entry
= entry
->next
;
2766 * Finds locally stored MS-PRES container by id
2768 static struct sipe_container
*
2769 sipe_find_container(struct sipe_account_data
*sip
,
2772 struct sipe_container
*container
;
2779 entry
= sip
->containers
;
2781 container
= entry
->data
;
2782 if (id
== container
->id
) {
2785 entry
= entry
->next
;
2799 sipe_find_access_level(struct sipe_account_data
*sip
,
2803 guint containers
[] = {32000, 400, 300, 200, 100};
2806 for (i
= 0; i
< 5; i
++) {
2807 struct sipe_container_member
*member
;
2808 struct sipe_container
*container
= sipe_find_container(sip
, containers
[i
]);
2809 if (!container
) continue;
2811 member
= sipe_find_container_member(container
, type
, value
);
2813 return containers
[i
];
2821 sipe_send_set_container_members(struct sipe_account_data
*sip
,
2823 guint container_version
,
2824 const gchar
* action
,
2828 gchar
*self
= sip_uri_self(sip
);
2829 gchar
*value_str
= value
? g_strdup_printf(" value=\"%s\"", value
) : g_strdup("");
2832 gchar
*body
= g_strdup_printf(
2833 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2834 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2835 "</setContainerMembers>",
2843 contact
= get_contact(sip
);
2844 hdr
= g_strdup_printf("Contact: %s\r\n"
2845 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact
);
2848 send_sip_request(sip
->gc
, "SERVICE", self
, self
, hdr
, body
, NULL
, NULL
);
2856 free_publication(struct sipe_publication
*publication
)
2858 g_free(publication
->category
);
2859 g_free(publication
->cal_event_hash
);
2860 g_free(publication
->note
);
2862 g_free(publication
->working_hours_xml_str
);
2863 g_free(publication
->fb_start_str
);
2864 g_free(publication
->free_busy_base64
);
2866 g_free(publication
);
2869 /* key is <category><instance><container> */
2871 sipe_is_our_publication(struct sipe_account_data
*sip
,
2876 /* filling keys for our publications if not yet cached */
2877 if (!sip
->our_publication_keys
) {
2878 guint device_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_DEVICE
);
2879 guint machine_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_MACHINE
);
2880 guint user_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_USER
);
2881 guint calendar_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR
);
2882 guint cal_oof_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR_OOF
);
2883 guint cal_data_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_CALENDAR_DATA
);
2884 guint note_oof_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_NOTE_OOF
);
2887 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2888 g_strdup_printf("<%s><%u><%u>", "device", device_instance
, 2));
2890 /* state:machineState */
2891 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2892 g_strdup_printf("<%s><%u><%u>", "state", machine_instance
, 2));
2893 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2894 g_strdup_printf("<%s><%u><%u>", "state", machine_instance
, 3));
2896 /* state:userState */
2897 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2898 g_strdup_printf("<%s><%u><%u>", "state", user_instance
, 2));
2899 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2900 g_strdup_printf("<%s><%u><%u>", "state", user_instance
, 3));
2902 /* state:calendarState */
2903 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2904 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance
, 2));
2905 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2906 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance
, 3));
2908 /* state:calendarState OOF */
2909 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2910 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance
, 2));
2911 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2912 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance
, 3));
2915 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2916 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2917 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2918 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2919 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2920 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2923 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2924 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 200));
2925 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2926 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 300));
2927 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2928 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 400));
2930 /* calendarData:WorkingHours */
2931 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2932 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2933 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2934 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2935 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2936 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2937 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2938 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2939 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2940 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2941 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2942 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2944 /* calendarData:FreeBusy */
2945 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2946 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 1));
2947 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2948 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 100));
2949 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2950 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 200));
2951 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2952 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 300));
2953 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2954 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 400));
2955 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2956 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 32000));
2958 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2959 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2962 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2964 entry
= sip
->our_publication_keys
;
2966 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2967 if (!strcmp(entry
->data
, key
)) {
2970 entry
= entry
->next
;
2975 /** Property names to store in blist.xml */
2976 #define ALIAS_PROP "alias"
2977 #define EMAIL_PROP "email"
2978 #define PHONE_PROP "phone"
2979 #define PHONE_DISPLAY_PROP "phone-display"
2980 #define PHONE_MOBILE_PROP "phone-mobile"
2981 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
2982 #define PHONE_HOME_PROP "phone-home"
2983 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
2984 #define PHONE_OTHER_PROP "phone-other"
2985 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
2986 #define PHONE_CUSTOM1_PROP "phone-custom1"
2987 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
2988 #define SITE_PROP "site"
2989 #define COMPANY_PROP "company"
2990 #define DEPARTMENT_PROP "department"
2991 #define TITLE_PROP "title"
2992 #define OFFICE_PROP "office"
2993 /** implies work address */
2994 #define ADDRESS_STREET_PROP "address-street"
2995 #define ADDRESS_CITY_PROP "address-city"
2996 #define ADDRESS_STATE_PROP "address-state"
2997 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
2998 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3000 * Update user information
3002 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3003 * @param property_name
3004 * @param property_value may be modified to strip white space
3007 sipe_update_user_info(struct sipe_account_data
*sip
,
3009 const char *property_name
,
3010 char *property_value
)
3012 GSList
*buddies
, *entry
;
3014 if (!property_name
|| strlen(property_name
) == 0) return;
3017 property_value
= g_strstrip(property_value
);
3019 entry
= buddies
= purple_find_buddies(sip
->account
, uri
); /* all buddies in different groups */
3021 const char *prop_str
;
3022 const char *server_alias
;
3023 PurpleBuddy
*p_buddy
= entry
->data
;
3025 /* for Display Name */
3026 if (!strcmp(property_name
, ALIAS_PROP
)) {
3027 if (property_value
&& sipe_is_bad_alias(uri
, purple_buddy_get_alias(p_buddy
))) {
3028 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, property_value
);
3029 purple_blist_alias_buddy(p_buddy
, property_value
);
3032 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3033 if (property_value
&& strlen(property_value
) > 0 &&
3034 ( (server_alias
&& strcmp(property_value
, server_alias
))
3035 || !server_alias
|| strlen(server_alias
) == 0 )
3037 purple_blist_server_alias_buddy(p_buddy
, property_value
);
3040 /* for other properties */
3042 if (property_value
&& strlen(property_value
) > 0) {
3043 prop_str
= purple_blist_node_get_string(&p_buddy
->node
, property_name
);
3044 if (!prop_str
|| g_ascii_strcasecmp(prop_str
, property_value
)) {
3045 purple_blist_node_set_string(&p_buddy
->node
, property_name
, property_value
);
3050 entry
= entry
->next
;
3052 g_slist_free(buddies
);
3057 * Suitable for both 2005 and 2007 systems.
3059 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3061 * @param phone may be modified to strip white space
3062 * @param phone_display_string may be modified to strip white space
3065 sipe_update_user_phone(struct sipe_account_data
*sip
,
3067 const gchar
*phone_type
,
3069 gchar
*phone_display_string
)
3071 const char *phone_node
= PHONE_PROP
; /* work phone by default */
3072 const char *phone_display_node
= PHONE_DISPLAY_PROP
; /* work phone by default */
3074 if(!phone
|| strlen(phone
) == 0) return;
3076 if (phone_type
&& (!strcmp(phone_type
, "mobile") || !strcmp(phone_type
, "cell"))) {
3077 phone_node
= PHONE_MOBILE_PROP
;
3078 phone_display_node
= PHONE_MOBILE_DISPLAY_PROP
;
3079 } else if (phone_type
&& !strcmp(phone_type
, "home")) {
3080 phone_node
= PHONE_HOME_PROP
;
3081 phone_display_node
= PHONE_HOME_DISPLAY_PROP
;
3082 } else if (phone_type
&& !strcmp(phone_type
, "other")) {
3083 phone_node
= PHONE_OTHER_PROP
;
3084 phone_display_node
= PHONE_OTHER_DISPLAY_PROP
;
3085 } else if (phone_type
&& !strcmp(phone_type
, "custom1")) {
3086 phone_node
= PHONE_CUSTOM1_PROP
;
3087 phone_display_node
= PHONE_CUSTOM1_DISPLAY_PROP
;
3090 sipe_update_user_info(sip
, uri
, phone_node
, phone
);
3091 if (phone_display_string
) {
3092 sipe_update_user_info(sip
, uri
, phone_display_node
, phone_display_string
);
3097 sipe_update_calendar(struct sipe_account_data
*sip
)
3099 const char* calendar
= purple_account_get_string(sip
->account
, "calendar", "EXCH");
3101 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3103 if (!strcmp(calendar
, "EXCH")) {
3104 sipe_ews_update_calendar(sip
);
3107 /* schedule repeat */
3108 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
3110 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3114 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3115 * by using standard Purple's means of signals and saved statuses.
3117 * Thus all UI elements get updated: Status Button with Note, docklet.
3118 * This is ablolutely important as both our status and note can come
3119 * inbound (roaming) or be updated programmatically (e.g. based on our
3123 sipe_set_purple_account_status_and_note(const PurpleAccount
*account
,
3124 const char *status_id
,
3125 const char *message
,
3126 time_t do_not_publish
[])
3128 PurpleStatus
*status
= purple_account_get_active_status(account
);
3129 gboolean changed
= TRUE
;
3131 if (g_str_equal(status_id
, purple_status_get_id(status
)) &&
3132 purple_strequal(message
, purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
)))
3138 PurpleSavedStatus
*saved_status
;
3139 const PurpleStatusType
*acct_status_type
=
3140 purple_status_type_find_with_id(account
->status_types
, status_id
);
3141 PurpleStatusPrimitive primitive
= purple_status_type_get_primitive(acct_status_type
);
3142 sipe_activity activity
= sipe_get_activity_by_token(status_id
);
3144 saved_status
= purple_savedstatus_find_transient_by_type_and_message(primitive
, message
);
3146 purple_savedstatus_set_substatus(saved_status
, account
, acct_status_type
, message
);
3149 /* If this type+message is unique then create a new transient saved status
3150 * Ref: gtkstatusbox.c
3152 if (!saved_status
) {
3154 GList
*active_accts
= purple_accounts_get_all_active();
3156 saved_status
= purple_savedstatus_new(NULL
, primitive
);
3157 purple_savedstatus_set_message(saved_status
, message
);
3159 for (tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
3160 purple_savedstatus_set_substatus(saved_status
,
3161 (PurpleAccount
*)tmp
->data
, acct_status_type
, message
);
3163 g_list_free(active_accts
);
3166 do_not_publish
[activity
] = time(NULL
);
3167 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3168 status_id
, (int)do_not_publish
[activity
]);
3170 /* Set the status for each account */
3171 purple_savedstatus_activate(saved_status
);
3176 send_publish_category_initial(struct sipe_account_data
*sip
);
3179 * When we receive some self (BE) NOTIFY with a new subscriber
3180 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3183 static void sipe_process_roaming_self(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3190 char *display_name
= NULL
;
3192 GSList
*category_names
= NULL
;
3193 int aggreg_avail
= 0;
3194 static sipe_activity aggreg_activity
= SIPE_ACTIVITY_UNSET
;
3195 gboolean do_update_status
= FALSE
;
3197 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3199 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3202 contact
= get_contact(sip
);
3203 to
= sip_uri_self(sip
);
3207 /* set list of categories participating in this XML */
3208 for (node
= xmlnode_get_descendant(xml
, "categories", "category", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3209 const gchar
*name
= xmlnode_get_attrib(node
, "name");
3210 category_names
= slist_insert_unique_sorted(category_names
, (gchar
*)name
, (GCompareFunc
)strcmp
);
3212 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3213 category_names
? (int) g_slist_length(category_names
) : -1);
3214 /* drop category information */
3215 if (category_names
) {
3216 GSList
*entry
= category_names
;
3218 GHashTable
*cat_publications
;
3219 const gchar
*category
= entry
->data
;
3220 entry
= entry
->next
;
3221 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category
);
3222 cat_publications
= g_hash_table_lookup(sip
->our_publications
, category
);
3223 if (cat_publications
) {
3224 g_hash_table_remove(sip
->our_publications
, category
);
3225 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category
);
3229 g_slist_free(category_names
);
3230 /* filling our categories reflected in roaming data */
3231 for (node
= xmlnode_get_descendant(xml
, "categories", "category", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3232 const gchar
*name
= xmlnode_get_attrib(node
, "name");
3233 const gchar
*container
= xmlnode_get_attrib(node
, "container");
3234 const gchar
*instance
= xmlnode_get_attrib(node
, "instance");
3235 const gchar
*version
= xmlnode_get_attrib(node
, "version");
3236 guint version_int
= version
? atoi(version
) : 0;
3239 if (!container
|| !instance
) continue;
3241 /* key is <category><instance><container> */
3242 key
= g_strdup_printf("<%s><%s><%s>", name
, instance
, container
);
3243 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key
, version_int
);
3245 /* capture all userState publication for later clean up if required */
3246 if (!strcmp(name
, "state") && (atoi(container
) == 2 || atoi(container
) == 3)) {
3247 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3249 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "userState")) {
3250 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
3251 publication
->category
= g_strdup(name
);
3252 publication
->instance
= atoi(instance
);
3253 publication
->container
= atoi(container
);
3254 publication
->version
= version_int
;
3256 if (!sip
->user_state_publications
) {
3257 sip
->user_state_publications
= g_hash_table_new_full(
3258 g_str_hash
, g_str_equal
,
3259 g_free
, (GDestroyNotify
)free_publication
);
3261 g_hash_table_insert(sip
->user_state_publications
, g_strdup(key
), publication
);
3262 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3267 if (sipe_is_our_publication(sip
, key
)) {
3268 GHashTable
*cat_publications
= g_hash_table_lookup(sip
->our_publications
, name
);
3270 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
3271 publication
->category
= g_strdup(name
);
3272 publication
->instance
= atoi(instance
);
3273 publication
->container
= atoi(container
);
3274 publication
->version
= version_int
;
3275 /* filling publication->availability */
3276 if (!strcmp(name
, "state")) {
3277 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3278 xmlnode
*xn_avail
= xmlnode_get_child(xn_state
, "availability");
3281 gchar
*avail_str
= xmlnode_get_data(xn_avail
);
3283 publication
->availability
= atoi(avail_str
);
3287 /* for calendarState */
3288 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "calendarState")) {
3289 xmlnode
*xn_activity
= xmlnode_get_child(xn_state
, "activity");
3290 struct sipe_cal_event
*event
= g_new0(struct sipe_cal_event
, 1);
3292 event
->start_time
= purple_str_to_time(xmlnode_get_attrib(xn_state
, "startTime"),
3293 FALSE
, NULL
, NULL
, NULL
);
3295 if (!strcmp(xmlnode_get_attrib(xn_activity
, "token"),
3296 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].token
))
3298 event
->is_meeting
= TRUE
;
3301 event
->subject
= xmlnode_get_data(xmlnode_get_child(xn_state
, "meetingSubject"));
3302 event
->location
= xmlnode_get_data(xmlnode_get_child(xn_state
, "meetingLocation"));
3304 publication
->cal_event_hash
= sipe_cal_event_hash(event
);
3305 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3306 publication
->cal_event_hash
);
3307 sipe_cal_event_free(event
);
3310 /* filling publication->note */
3311 if (!strcmp(name
, "note")) {
3312 xmlnode
*xn_body
= xmlnode_get_descendant(node
, "note", "body", NULL
);
3318 publication
->note
= g_markup_escape_text((tmp
= xmlnode_get_data(xn_body
)), -1);
3320 sip
->note
= g_strdup(publication
->note
);
3322 do_update_status
= TRUE
;
3325 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3326 if (!strcmp(name
, "calendarData") && (publication
->container
== 300)) {
3327 xmlnode
*xn_free_busy
= xmlnode_get_descendant(node
, "calendarData", "freeBusy", NULL
);
3328 xmlnode
*xn_working_hours
= xmlnode_get_descendant(node
, "calendarData", "WorkingHours", NULL
);
3330 publication
->fb_start_str
= g_strdup(xmlnode_get_attrib(xn_free_busy
, "startTime"));
3331 publication
->free_busy_base64
= xmlnode_get_data(xn_free_busy
);
3333 if (xn_working_hours
) {
3334 publication
->working_hours_xml_str
= xmlnode_to_str(xn_working_hours
, NULL
);
3338 if (!cat_publications
) {
3339 cat_publications
= g_hash_table_new_full(
3340 g_str_hash
, g_str_equal
,
3341 g_free
, (GDestroyNotify
)free_publication
);
3342 g_hash_table_insert(sip
->our_publications
, g_strdup(name
), cat_publications
);
3343 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name
);
3345 g_hash_table_insert(cat_publications
, g_strdup(key
), publication
);
3346 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key
, version_int
);
3350 /* aggregateState (not an our publication) from 2-nd container */
3351 if (!strcmp(name
, "state") && atoi(container
) == 2) {
3352 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3354 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "aggregateState")) {
3355 xmlnode
*xn_avail
= xmlnode_get_child(xn_state
, "availability");
3356 xmlnode
*xn_activity
= xmlnode_get_child(xn_state
, "activity");
3359 gchar
*avail_str
= xmlnode_get_data(xn_avail
);
3361 aggreg_avail
= atoi(avail_str
);
3367 const char *activity_token
= xmlnode_get_attrib(xn_activity
, "token");
3369 aggreg_activity
= sipe_get_activity_by_token(activity_token
);
3372 do_update_status
= TRUE
;
3376 /* userProperties published by server from AD */
3377 if (!sip
->csta
&& !strcmp(name
, "userProperties")) {
3379 /* line, for Remote Call Control (RCC) */
3380 for (line
= xmlnode_get_descendant(node
, "userProperties", "lines", "line", NULL
); line
; line
= xmlnode_get_next_twin(line
)) {
3381 const gchar
*line_server
= xmlnode_get_attrib(line
, "lineServer");
3382 const gchar
*line_type
= xmlnode_get_attrib(line
, "lineType");
3385 if (!line_server
|| (strcmp(line_type
, "Rcc") && strcmp(line_type
, "Dual"))) continue;
3387 line_uri
= xmlnode_get_data(line
);
3389 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri
, line_server
);
3390 sip_csta_open(sip
, line_uri
, line_server
);
3398 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3399 sip
->our_publications
? (int) g_hash_table_size(sip
->our_publications
) : -1);
3402 for (node
= xmlnode_get_descendant(xml
, "containers", "container", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3403 guint id
= atoi(xmlnode_get_attrib(node
, "id"));
3404 struct sipe_container
*container
= sipe_find_container(sip
, id
);
3407 sip
->containers
= g_slist_remove(sip
->containers
, container
);
3408 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container
->id
, container
->version
);
3409 free_container(container
);
3411 container
= g_new0(struct sipe_container
, 1);
3413 container
->version
= atoi(xmlnode_get_attrib(node
, "version"));
3414 sip
->containers
= g_slist_append(sip
->containers
, container
);
3415 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container
->id
, container
->version
);
3417 for (node2
= xmlnode_get_child(node
, "member"); node2
; node2
= xmlnode_get_next_twin(node2
)) {
3418 struct sipe_container_member
*member
= g_new0(struct sipe_container_member
, 1);
3419 member
->type
= xmlnode_get_attrib(node2
, "type");
3420 member
->value
= xmlnode_get_attrib(node2
, "value");
3421 container
->members
= g_slist_append(container
->members
, member
);
3422 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3423 member
->type
, member
->value
? member
->value
: "");
3427 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip
->access_level_set
? "TRUE" : "FALSE");
3428 if (!sip
->access_level_set
&& xmlnode_get_child(xml
, "containers")) {
3429 int sameEnterpriseAL
= sipe_find_access_level(sip
, "sameEnterprise", NULL
);
3430 int federatedAL
= sipe_find_access_level(sip
, "federated", NULL
);
3431 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL
);
3432 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL
);
3433 /* initial set-up to let counterparties see your status */
3434 if (sameEnterpriseAL
< 0) {
3435 struct sipe_container
*container
= sipe_find_container(sip
, 200);
3436 guint version
= container
? container
->version
: 0;
3437 sipe_send_set_container_members(sip
, 200, version
, "add", "sameEnterprise", NULL
);
3439 if (federatedAL
< 0) {
3440 struct sipe_container
*container
= sipe_find_container(sip
, 100);
3441 guint version
= container
? container
->version
: 0;
3442 sipe_send_set_container_members(sip
, 100, version
, "add", "federated", NULL
);
3444 sip
->access_level_set
= TRUE
;
3448 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3450 const char *acknowledged
;
3454 user
= xmlnode_get_attrib(node
, "user"); /* without 'sip:' prefix */
3455 if (!user
) continue;
3456 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
3457 display_name
= g_strdup(xmlnode_get_attrib(node
, "displayName"));
3458 uri
= sip_uri_from_name(user
);
3460 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
3462 acknowledged
= xmlnode_get_attrib(node
, "acknowledged");
3463 if(!g_ascii_strcasecmp(acknowledged
,"false")){
3464 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user
);
3465 if (!purple_find_buddy(sip
->account
, uri
)) {
3466 purple_account_request_add(sip
->account
, uri
, _("you"), display_name
, NULL
);
3469 hdr
= g_strdup_printf(
3471 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
3473 body
= g_strdup_printf(
3474 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3475 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3476 "</setSubscribers>", user
);
3478 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
3482 g_free(display_name
);
3489 /* Publish initial state if not yet.
3490 * Assuming this happens on initial responce to subscription to roaming-self
3491 * so we've already updated our roaming data in full.
3494 if (!sip
->initial_state_published
) {
3495 send_publish_category_initial(sip
);
3496 sip
->initial_state_published
= TRUE
;
3498 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
3499 do_update_status
= FALSE
;
3500 } else if (aggreg_avail
) {
3502 g_free(sip
->status
);
3503 if (aggreg_avail
&& aggreg_avail
< 18000) { /* not offline */
3504 sip
->status
= g_strdup(sipe_get_status_by_availability(aggreg_avail
, NULL
));
3506 sip
->status
= g_strdup(SIPE_STATUS_ID_INVISIBLE
); /* not not let offline status switch us off */
3510 if (do_update_status
) {
3511 purple_debug_info("sipe", "sipe_process_roaming_self: to %s for the account\n", sip
->status
);
3512 sipe_set_purple_account_status_and_note(sip
->account
, sip
->status
, sip
->note
, sip
->do_not_publish
);
3518 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
)
3520 gchar
*to
= sip_uri_self(sip
);
3521 gchar
*tmp
= get_contact(sip
);
3522 gchar
*hdr
= g_strdup_printf(
3523 "Event: vnd-microsoft-roaming-ACL\r\n"
3524 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3525 "Supported: com.microsoft.autoextend\r\n"
3526 "Supported: ms-benotify\r\n"
3527 "Proxy-Require: ms-benotify\r\n"
3528 "Supported: ms-piggyback-first-notify\r\n"
3529 "Contact: %s\r\n", tmp
);
3532 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
3538 * To request for presence information about the user, access level settings that have already been configured by the user
3539 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3540 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3543 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
)
3545 gchar
*to
= sip_uri_self(sip
);
3546 gchar
*tmp
= get_contact(sip
);
3547 gchar
*hdr
= g_strdup_printf(
3548 "Event: vnd-microsoft-roaming-self\r\n"
3549 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3550 "Supported: ms-benotify\r\n"
3551 "Proxy-Require: ms-benotify\r\n"
3552 "Supported: ms-piggyback-first-notify\r\n"
3554 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
3556 gchar
*body
=g_strdup(
3557 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3558 "<roaming type=\"categories\"/>"
3559 "<roaming type=\"containers\"/>"
3560 "<roaming type=\"subscribers\"/></roamingList>");
3563 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
3572 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
)
3574 gchar
*to
= sip_uri_self(sip
);
3575 gchar
*tmp
= get_contact(sip
);
3576 gchar
*hdr
= g_strdup_printf(
3577 "Event: vnd-microsoft-provisioning\r\n"
3578 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3579 "Supported: com.microsoft.autoextend\r\n"
3580 "Supported: ms-benotify\r\n"
3581 "Proxy-Require: ms-benotify\r\n"
3582 "Supported: ms-piggyback-first-notify\r\n"
3584 "Contact: %s\r\n", tmp
);
3587 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
3592 /** Subscription for provisioning information to help with initial
3593 * configuration. This subscription is a one-time query (denoted by the Expires header,
3594 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3595 * configuration, meeting policies, and policy settings that Communicator must enforce.
3596 * TODO: for what we need this information.
3599 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
)
3601 gchar
*to
= sip_uri_self(sip
);
3602 gchar
*tmp
= get_contact(sip
);
3603 gchar
*hdr
= g_strdup_printf(
3604 "Event: vnd-microsoft-provisioning-v2\r\n"
3605 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3606 "Supported: com.microsoft.autoextend\r\n"
3607 "Supported: ms-benotify\r\n"
3608 "Proxy-Require: ms-benotify\r\n"
3609 "Supported: ms-piggyback-first-notify\r\n"
3612 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
3613 gchar
*body
= g_strdup(
3614 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3615 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3616 "<provisioningGroup name=\"ucPolicy\"/>"
3617 "</provisioningGroupList>");
3620 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
3627 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key
,
3628 gpointer value
, gpointer user_data
)
3630 struct sip_subscription
*subscription
= value
;
3631 struct sip_dialog
*dialog
= &subscription
->dialog
;
3632 struct sipe_account_data
*sip
= user_data
;
3633 gchar
*tmp
= get_contact(sip
);
3634 gchar
*hdr
= g_strdup_printf(
3637 "Contact: %s\r\n", subscription
->event
, tmp
);
3640 /* Rate limit to max. 25 requests per seconds */
3641 g_usleep(1000000 / 25);
3643 send_sip_request(sip
->gc
, "SUBSCRIBE", dialog
->with
, dialog
->with
, hdr
, NULL
, dialog
, NULL
);
3647 /* IM Session (INVITE and MESSAGE methods) */
3649 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3651 get_end_points (struct sipe_account_data
*sip
,
3652 struct sip_session
*session
)
3656 if (session
== NULL
) {
3660 res
= g_strdup_printf("<sip:%s>", sip
->username
);
3662 SIPE_DIALOG_FOREACH
{
3664 res
= g_strdup_printf("%s, <%s>", res
, dialog
->with
);
3667 if (dialog
->theirepid
) {
3669 res
= g_strdup_printf("%s;epid=%s", res
, dialog
->theirepid
);
3672 } SIPE_DIALOG_FOREACH_END
;
3678 process_options_response(SIPE_UNUSED_PARAMETER
struct sipe_account_data
*sip
,
3680 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
3682 gboolean ret
= TRUE
;
3684 if (msg
->response
!= 200) {
3685 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
3689 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
3695 * Asks UA/proxy about its capabilities.
3697 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
3699 gchar
*to
= sip_uri(who
);
3700 gchar
*contact
= get_contact(sip
);
3701 gchar
*request
= g_strdup_printf(
3702 "Accept: application/sdp\r\n"
3703 "Contact: %s\r\n", contact
);
3706 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
3713 sipe_notify_user(struct sipe_account_data
*sip
,
3714 struct sip_session
*session
,
3715 PurpleMessageFlags flags
,
3716 const gchar
*message
)
3718 PurpleConversation
*conv
;
3720 if (!session
->conv
) {
3721 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY
, session
->with
, sip
->account
);
3723 conv
= session
->conv
;
3725 purple_conversation_write(conv
, NULL
, message
, flags
, time(NULL
));
3729 sipe_present_info(struct sipe_account_data
*sip
,
3730 struct sip_session
*session
,
3731 const gchar
*message
)
3733 sipe_notify_user(sip
, session
, PURPLE_MESSAGE_SYSTEM
, message
);
3737 sipe_present_err(struct sipe_account_data
*sip
,
3738 struct sip_session
*session
,
3739 const gchar
*message
)
3741 sipe_notify_user(sip
, session
, PURPLE_MESSAGE_ERROR
, message
);
3745 sipe_present_message_undelivered_err(struct sipe_account_data
*sip
,
3746 struct sip_session
*session
,
3749 const gchar
*message
)
3751 char *msg
, *msg_tmp
, *msg_tmp2
;
3754 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
3755 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
3757 /* Service unavailable; Server Internal Error; Server Time-out */
3758 if (sip_error
== 503 || sip_error
== 500 || sip_error
== 504) {
3759 label
= _("This message was not delivered to %s because the service is not available");
3760 } else if (sip_error
== 486) { /* Busy Here */
3761 label
= _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3763 label
= _("This message was not delivered to %s because one or more recipients are offline");
3766 msg_tmp
= g_strdup_printf( "%s:\n%s" ,
3767 msg_tmp2
= g_strdup_printf(label
, who
? who
: ""), msg
? msg
: "");
3768 sipe_present_err(sip
, session
, msg_tmp
);
3775 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_session
* session
);
3778 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
3779 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
3781 gboolean ret
= TRUE
;
3782 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
3783 struct sip_session
*session
= sipe_session_find_im(sip
, with
);
3784 struct sip_dialog
*dialog
;
3790 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3795 dialog
= sipe_dialog_find(session
, with
);
3797 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3802 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
3803 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
), with
);
3805 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
3807 if (msg
->response
>= 400) {
3808 PurpleBuddy
*pbuddy
;
3809 gchar
*alias
= with
;
3811 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3813 if ((pbuddy
= purple_find_buddy(sip
->account
, with
))) {
3814 alias
= (gchar
*)purple_buddy_get_alias(pbuddy
);
3817 sipe_present_message_undelivered_err(sip
, session
, msg
->response
, alias
, message
);
3820 gchar
*message_id
= sipmsg_find_header(msg
, "Message-Id");
3822 g_hash_table_insert(session
->conf_unconfirmed_messages
, g_strdup(message_id
), g_strdup(message
));
3823 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
3824 message_id
, g_hash_table_size(session
->conf_unconfirmed_messages
));
3827 g_hash_table_remove(session
->unconfirmed_messages
, key
);
3828 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
3829 key
, g_hash_table_size(session
->unconfirmed_messages
));
3835 if (ret
) sipe_im_process_queue(sip
, session
);
3840 sipe_is_election_finished(struct sip_session
*session
);
3843 sipe_election_result(struct sipe_account_data
*sip
,
3847 process_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
3848 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
3850 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
3851 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
3852 struct sip_dialog
*dialog
;
3853 struct sip_session
*session
;
3855 session
= sipe_session_find_chat_by_callid(sip
, callid
);
3857 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid
);
3861 if (msg
->response
== 200 && !strncmp(contenttype
, "application/x-ms-mim", 20)) {
3862 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3863 xmlnode
*xn_request_rm_response
= xmlnode_get_child(xn_action
, "RequestRMResponse");
3864 xmlnode
*xn_set_rm_response
= xmlnode_get_child(xn_action
, "SetRMResponse");
3866 if (xn_request_rm_response
) {
3867 const char *with
= xmlnode_get_attrib(xn_request_rm_response
, "uri");
3868 const char *allow
= xmlnode_get_attrib(xn_request_rm_response
, "allow");
3870 dialog
= sipe_dialog_find(session
, with
);
3872 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with
);
3876 if (allow
&& !g_strcasecmp(allow
, "true")) {
3877 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with
);
3878 dialog
->election_vote
= 1;
3879 } else if (allow
&& !g_strcasecmp(allow
, "false")) {
3880 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with
);
3881 dialog
->election_vote
= -1;
3884 if (sipe_is_election_finished(session
)) {
3885 sipe_election_result(sip
, session
);
3888 } else if (xn_set_rm_response
) {
3891 xmlnode_free(xn_action
);
3898 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_dialog
*dialog
, const char *msg
)
3907 sipe_parse_html(msg
, &msgformat
, &msgtext
);
3908 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat
);
3910 msgr_value
= sipmsg_get_msgr_string(msgformat
);
3913 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
3916 msgr
= g_strdup("");
3919 tmp
= get_contact(sip
);
3920 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
3921 //hdr = g_strdup("Content-Type: text/rtf\r\n");
3922 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
3923 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp
, msgr
);
3927 send_sip_request(sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
, hdr
, msgtext
, dialog
, process_message_response
);
3934 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_session
* session
)
3936 GSList
*entry2
= session
->outgoing_message_queue
;
3938 char *queued_msg
= entry2
->data
;
3940 /* for multiparty chat or conference */
3941 if (session
->is_multiparty
|| session
->focus_uri
) {
3942 gchar
*who
= sip_uri_self(sip
);
3943 serv_got_chat_in(sip
->gc
, session
->chat_id
, who
,
3944 PURPLE_MESSAGE_SEND
, queued_msg
, time(NULL
));
3948 SIPE_DIALOG_FOREACH
{
3951 if (dialog
->outgoing_invite
) continue; /* do not send messages as INVITE is not responded. */
3953 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog
->callid
, (dialog
->cseq
) + 1, dialog
->with
);
3954 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
3955 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
3956 key
, g_hash_table_size(session
->unconfirmed_messages
));
3959 sipe_send_message(sip
, dialog
, queued_msg
);
3960 } SIPE_DIALOG_FOREACH_END
;
3962 entry2
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
3968 sipe_refer_notify(struct sipe_account_data
*sip
,
3969 struct sip_session
*session
,
3976 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
3978 hdr
= g_strdup_printf(
3980 "Subscription-State: %s\r\n"
3981 "Content-Type: message/sipfrag\r\n",
3982 status
>= 200 ? "terminated" : "active");
3984 body
= g_strdup_printf(
3985 "SIP/2.0 %d %s\r\n",
3988 send_sip_request(sip
->gc
, "NOTIFY", who
, who
, hdr
, body
, dialog
, NULL
);
3995 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
3997 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
3998 struct sip_session
*session
;
3999 struct sip_dialog
*dialog
;
4003 struct sipmsg
*request_msg
= trans
->msg
;
4005 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4008 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4010 session
= sipe_session_find_im(sip
, with
);
4013 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4018 dialog
= sipe_dialog_find(session
, with
);
4020 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4025 sipe_dialog_parse(dialog
, msg
, TRUE
);
4027 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
4028 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
4030 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
4032 if (msg
->response
!= 200) {
4033 PurpleBuddy
*pbuddy
;
4034 gchar
*alias
= with
;
4036 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4038 if ((pbuddy
= purple_find_buddy(sip
->account
, with
))) {
4039 alias
= (gchar
*)purple_buddy_get_alias(pbuddy
);
4043 sipe_present_message_undelivered_err(sip
, session
, msg
->response
, alias
, message
);
4045 gchar
*tmp_msg
= g_strdup_printf(_("Failed to invite %s"), alias
);
4046 sipe_present_err(sip
, session
, tmp_msg
);
4050 sipe_dialog_remove(session
, with
);
4058 send_sip_request(sip
->gc
, "ACK", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
4059 dialog
->outgoing_invite
= NULL
;
4060 dialog
->is_established
= TRUE
;
4062 referred_by
= parse_from(sipmsg_find_header(request_msg
, "Referred-By"));
4064 sipe_refer_notify(sip
, session
, referred_by
, 200, "OK");
4065 g_free(referred_by
);
4068 /* add user to chat if it is a multiparty session */
4069 if (session
->is_multiparty
) {
4070 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4072 PURPLE_CBFLAGS_NONE
, TRUE
);
4075 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
4076 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4077 if (session
->outgoing_message_queue
) {
4078 char *queued_msg
= session
->outgoing_message_queue
->data
;
4079 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
4084 sipe_im_process_queue(sip
, session
);
4086 g_hash_table_remove(session
->unconfirmed_messages
, key
);
4087 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4088 key
, g_hash_table_size(session
->unconfirmed_messages
));
4097 sipe_invite(struct sipe_account_data
*sip
,
4098 struct sip_session
*session
,
4100 const gchar
*msg_body
,
4101 const gchar
*referred_by
,
4102 const gboolean is_triggered
)
4109 char *ms_text_format
= NULL
;
4110 gchar
*roster_manager
;
4112 gchar
*referred_by_str
;
4113 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
4115 if (dialog
&& dialog
->is_established
) {
4116 purple_debug_info("sipe", "session with %s already has a dialog open\n", who
);
4121 dialog
= sipe_dialog_add(session
);
4122 dialog
->callid
= session
->callid
? g_strdup(session
->callid
) : gencallid();
4123 dialog
->with
= g_strdup(who
);
4126 if (!(dialog
->ourtag
)) {
4127 dialog
->ourtag
= gentag();
4140 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
4141 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat
);
4143 msgr_value
= sipmsg_get_msgr_string(msgformat
);
4147 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
4151 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
4152 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
4157 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, (dialog
->cseq
) + 1);
4158 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
4159 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4160 key
, g_hash_table_size(session
->unconfirmed_messages
));
4164 contact
= get_contact(sip
);
4165 end_points
= get_end_points(sip
, session
);
4166 self
= sip_uri_self(sip
);
4167 roster_manager
= g_strdup_printf(
4168 "Roster-Manager: %s\r\n"
4169 "EndPoints: %s\r\n",
4172 referred_by_str
= referred_by
?
4174 "Referred-By: %s\r\n",
4177 hdr
= g_strdup_printf(
4178 "Supported: ms-sender\r\n"
4184 "Content-Type: application/sdp\r\n",
4185 (session
->roster_manager
&& !strcmp(session
->roster_manager
, self
)) ? roster_manager
: "",
4187 is_triggered
? "TriggeredInvite: TRUE\r\n" : "",
4188 is_triggered
|| session
->is_multiparty
? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4190 ms_text_format
? ms_text_format
: "");
4191 g_free(ms_text_format
);
4194 body
= g_strdup_printf(
4196 "o=- 0 0 IN IP4 %s\r\n"
4200 "m=%s %d sip null\r\n"
4201 "a=accept-types:text/plain text/html image/gif "
4202 "multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4203 purple_network_get_my_ip(-1),
4204 purple_network_get_my_ip(-1),
4205 sip
->ocs2007
? "message" : "x-ms-message",
4208 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
4209 to
, to
, hdr
, body
, dialog
, process_invite_response
);
4212 g_free(roster_manager
);
4214 g_free(referred_by_str
);
4221 sipe_refer(struct sipe_account_data
*sip
,
4222 struct sip_session
*session
,
4227 gchar
*epid
= get_epid(sip
);
4228 struct sip_dialog
*dialog
= sipe_dialog_find(session
,
4229 session
->roster_manager
);
4230 const char *ourtag
= dialog
&& dialog
->ourtag
? dialog
->ourtag
: NULL
;
4232 contact
= get_contact(sip
);
4233 hdr
= g_strdup_printf(
4235 "Refer-to: <%s>\r\n"
4236 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4237 "Require: com.microsoft.rtc-multiparty\r\n",
4241 ourtag
? ";tag=" : "",
4242 ourtag
? ourtag
: "",
4246 send_sip_request(sip
->gc
, "REFER",
4247 session
->roster_manager
, session
->roster_manager
, hdr
, NULL
, dialog
, NULL
);
4254 sipe_send_election_request_rm(struct sipe_account_data
*sip
,
4255 struct sip_dialog
*dialog
,
4258 const gchar
*hdr
= "Content-Type: application/x-ms-mim\r\n";
4260 gchar
*body
= g_strdup_printf(
4261 "<?xml version=\"1.0\"?>\r\n"
4262 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4263 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4264 sip
->username
, bid
);
4266 send_sip_request(sip
->gc
, "INFO",
4267 dialog
->with
, dialog
->with
, hdr
, body
, dialog
, process_info_response
);
4273 sipe_send_election_set_rm(struct sipe_account_data
*sip
,
4274 struct sip_dialog
*dialog
)
4276 const gchar
*hdr
= "Content-Type: application/x-ms-mim\r\n";
4278 gchar
*body
= g_strdup_printf(
4279 "<?xml version=\"1.0\"?>\r\n"
4280 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4281 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4284 send_sip_request(sip
->gc
, "INFO",
4285 dialog
->with
, dialog
->with
, hdr
, body
, dialog
, process_info_response
);
4291 sipe_session_close(struct sipe_account_data
*sip
,
4292 struct sip_session
* session
)
4294 if (session
&& session
->focus_uri
) {
4295 sipe_conf_immcu_closed(sip
, session
);
4296 conf_session_close(sip
, session
);
4300 SIPE_DIALOG_FOREACH
{
4301 /* @TODO slow down BYE message sending rate */
4302 /* @see single subscription code */
4303 send_sip_request(sip
->gc
, "BYE", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
4304 } SIPE_DIALOG_FOREACH_END
;
4306 sipe_session_remove(sip
, session
);
4311 sipe_session_close_all(struct sipe_account_data
*sip
)
4314 while ((entry
= sip
->sessions
) != NULL
) {
4315 sipe_session_close(sip
, entry
->data
);
4320 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
4322 struct sipe_account_data
*sip
= gc
->proto_data
;
4324 purple_debug_info("sipe", "conversation with %s closed\n", who
);
4325 sipe_session_close(sip
, sipe_session_find_im(sip
, who
));
4329 sipe_chat_leave (PurpleConnection
*gc
, int id
)
4331 struct sipe_account_data
*sip
= gc
->proto_data
;
4332 struct sip_session
*session
= sipe_session_find_chat_by_id(sip
, id
);
4334 sipe_session_close(sip
, session
);
4337 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
,
4338 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
4340 struct sipe_account_data
*sip
= gc
->proto_data
;
4341 struct sip_session
*session
;
4342 struct sip_dialog
*dialog
;
4343 gchar
*uri
= sip_uri(who
);
4345 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
4347 session
= sipe_session_find_or_add_im(sip
, uri
);
4348 dialog
= sipe_dialog_find(session
, uri
);
4350 // Queue the message
4351 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
4353 if (dialog
&& !dialog
->outgoing_invite
) {
4354 sipe_im_process_queue(sip
, session
);
4355 } else if (!dialog
|| !dialog
->outgoing_invite
) {
4356 // Need to send the INVITE to get the outgoing dialog setup
4357 sipe_invite(sip
, session
, uri
, what
, NULL
, FALSE
);
4364 static int sipe_chat_send(PurpleConnection
*gc
, int id
, const char *what
,
4365 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
4367 struct sipe_account_data
*sip
= gc
->proto_data
;
4368 struct sip_session
*session
;
4370 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what
);
4372 session
= sipe_session_find_chat_by_id(sip
, id
);
4374 // Queue the message
4375 if (session
&& session
->dialogs
) {
4376 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
,
4378 sipe_im_process_queue(sip
, session
);
4380 gchar
*chat_name
= purple_find_chat(sip
->gc
, id
)->name
;
4381 const gchar
*proto_chat_id
= sipe_chat_find_name(chat_name
);
4383 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name
? chat_name
: "NULL");
4384 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id
? proto_chat_id
: "NULL");
4387 struct sip_session
*session
= sipe_session_add_chat(sip
);
4389 session
->is_multiparty
= FALSE
;
4390 session
->focus_uri
= g_strdup(proto_chat_id
);
4391 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
,
4393 sipe_invite_conf_focus(sip
, session
);
4400 /* End IM Session (INVITE and MESSAGE methods) */
4402 static void process_incoming_info(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4404 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
4405 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4407 struct sip_session
*session
;
4409 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg
->body
? msg
->body
: "");
4411 /* Call Control protocol */
4412 if (g_str_has_prefix(contenttype
, "application/csta+xml"))
4414 process_incoming_info_csta(sip
, msg
);
4418 from
= parse_from(sipmsg_find_header(msg
, "From"));
4419 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4421 session
= sipe_session_find_im(sip
, from
);
4428 if (g_str_has_prefix(contenttype
, "application/x-ms-mim"))
4430 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4431 xmlnode
*xn_request_rm
= xmlnode_get_child(xn_action
, "RequestRM");
4432 xmlnode
*xn_set_rm
= xmlnode_get_child(xn_action
, "SetRM");
4434 sipmsg_add_header(msg
, "Content-Type", "application/x-ms-mim");
4436 if (xn_request_rm
) {
4437 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4438 int bid
= atoi(xmlnode_get_attrib(xn_request_rm
, "bid"));
4439 gchar
*body
= g_strdup_printf(
4440 "<?xml version=\"1.0\"?>\r\n"
4441 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4442 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4444 session
->bid
< bid
? "true" : "false");
4445 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4447 } else if (xn_set_rm
) {
4449 const char *rm
= xmlnode_get_attrib(xn_set_rm
, "uri");
4450 g_free(session
->roster_manager
);
4451 session
->roster_manager
= g_strdup(rm
);
4453 body
= g_strdup_printf(
4454 "<?xml version=\"1.0\"?>\r\n"
4455 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4456 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4458 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4461 xmlnode_free(xn_action
);
4466 /* looks like purple lacks typing notification for chat */
4467 if (!session
->is_multiparty
&& !session
->focus_uri
) {
4468 xmlnode
*xn_keyboard_activity
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4469 const char *status
= xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity
, "status"),
4471 if (status
&& !strcmp(status
, "type")) {
4472 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
4473 } else if (status
&& !strcmp(status
, "idle")) {
4474 serv_got_typing_stopped(sip
->gc
, from
);
4476 xmlnode_free(xn_keyboard_activity
);
4479 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4484 static void process_incoming_bye(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4486 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4487 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4488 struct sip_session
*session
;
4489 struct sip_dialog
*dialog
;
4491 /* collect dialog identification
4492 * we need callid, ourtag and theirtag to unambiguously identify dialog
4494 /* take data before 'msg' will be modified by send_sip_response */
4495 dialog
= g_new0(struct sip_dialog
, 1);
4496 dialog
->callid
= g_strdup(callid
);
4497 dialog
->cseq
= parse_cseq(sipmsg_find_header(msg
, "CSeq"));
4498 dialog
->with
= g_strdup(from
);
4499 sipe_dialog_parse(dialog
, msg
, FALSE
);
4501 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4503 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4505 session
= sipe_session_find_im(sip
, from
);
4512 if (session
->roster_manager
&& !g_strcasecmp(from
, session
->roster_manager
)) {
4513 g_free(session
->roster_manager
);
4514 session
->roster_manager
= NULL
;
4517 /* This what BYE is essentially for - terminating dialog */
4518 sipe_dialog_remove_3(session
, dialog
);
4519 sipe_dialog_free(dialog
);
4520 if (session
->focus_uri
&& !g_strcasecmp(from
, session
->im_mcu_uri
)) {
4521 sipe_conf_immcu_closed(sip
, session
);
4522 } else if (session
->is_multiparty
) {
4523 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session
->conv
), from
, NULL
);
4529 static void process_incoming_refer(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4531 gchar
*self
= sip_uri_self(sip
);
4532 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4533 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4534 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
4535 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
4536 struct sip_session
*session
;
4537 struct sip_dialog
*dialog
;
4539 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4540 dialog
= sipe_dialog_find(session
, from
);
4542 if (!session
|| !dialog
|| !session
->roster_manager
|| strcmp(session
->roster_manager
, self
)) {
4543 send_sip_response(sip
->gc
, msg
, 500, "Server Internal Error", NULL
);
4545 send_sip_response(sip
->gc
, msg
, 202, "Accepted", NULL
);
4547 sipe_invite(sip
, session
, refer_to
, NULL
, referred_by
, FALSE
);
4553 g_free(referred_by
);
4557 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
4559 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
4560 struct sip_session
*session
;
4561 struct sip_dialog
*dialog
;
4563 if (state
== PURPLE_NOT_TYPING
)
4566 session
= sipe_session_find_im(sip
, who
);
4567 dialog
= sipe_dialog_find(session
, who
);
4569 if (session
&& dialog
&& dialog
->is_established
) {
4570 send_sip_request(gc
, "INFO", who
, who
,
4571 "Content-Type: application/xml\r\n",
4572 SIPE_SEND_TYPING
, dialog
, NULL
);
4574 return SIPE_TYPING_SEND_TIMEOUT
;
4577 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
4579 GSList
*tmp
= sip
->transactions
;
4580 time_t currtime
= time(NULL
);
4582 struct transaction
*trans
= tmp
->data
;
4584 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime
-trans
->time
);
4585 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
4588 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
4590 sendout_sipmsg(sip
, trans
->msg
);
4597 static void do_reauthenticate_cb(struct sipe_account_data
*sip
,
4598 SIPE_UNUSED_PARAMETER
void *unused
)
4600 /* register again when security token expires */
4601 /* we have to start a new authentication as the security token
4602 * is almost expired by sending a not signed REGISTER message */
4603 purple_debug_info("sipe", "do a full reauthentication\n");
4604 sipe_auth_free(&sip
->registrar
);
4605 sipe_auth_free(&sip
->proxy
);
4606 sip
->registerstatus
= 0;
4608 sip
->reauthenticate_set
= FALSE
;
4611 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4615 gboolean found
= FALSE
;
4617 from
= parse_from(sipmsg_find_header(msg
, "From"));
4621 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
4623 contenttype
= sipmsg_find_header(msg
, "Content-Type");
4624 if (!strncmp(contenttype
, "text/plain", 10)
4625 || !strncmp(contenttype
, "text/html", 9)
4626 || !strncmp(contenttype
, "multipart/related", 17)
4627 || !strncmp(contenttype
, "multipart/alternative", 21))
4629 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4630 gchar
*html
= get_html_message(contenttype
, msg
->body
);
4632 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
4634 session
= sipe_session_find_im(sip
, from
);
4637 if (session
&& session
->focus_uri
) { /* a conference */
4638 gchar
*tmp
= parse_from(sipmsg_find_header(msg
, "Ms-Sender"));
4639 gchar
*sender
= parse_from(tmp
);
4641 serv_got_chat_in(sip
->gc
, session
->chat_id
, sender
,
4642 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
4644 } else if (session
&& session
->is_multiparty
) { /* a multiparty chat */
4645 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
4646 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
4648 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
4651 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4654 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
4655 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4660 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4664 state
= xmlnode_get_child(isc
, "state");
4667 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4672 statedata
= xmlnode_get_data(state
);
4674 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
4675 else serv_got_typing_stopped(sip
->gc
, from
);
4680 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4684 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4685 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
4687 session
= sipe_session_find_im(sip
, from
);
4690 gchar
*msg
= g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4692 sipe_present_err(sip
, session
, msg
);
4696 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype
);
4697 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
4702 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4708 gboolean is_multiparty
= FALSE
;
4709 gboolean is_triggered
= FALSE
;
4710 gboolean was_multiparty
= TRUE
;
4711 gboolean just_joined
= FALSE
;
4713 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4714 gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
4715 gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
4716 gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
4717 gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
4718 GSList
*end_points
= NULL
;
4720 struct sip_session
*session
;
4722 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? tmp
= fix_newlines(msg
->body
) : "");
4725 /* Invitation to join conference */
4726 if (!strncmp(content_type
, "application/ms-conf-invite+xml", 30)) {
4727 process_incoming_invite_conf(sip
, msg
);
4731 /* Only accept text invitations */
4732 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
4733 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4737 // TODO There *must* be a better way to clean up the To header to add a tag...
4738 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4739 oldHeader
= sipmsg_find_header(msg
, "To");
4741 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
4742 sipmsg_remove_header_now(msg
, "To");
4743 sipmsg_add_header_now(msg
, "To", newHeader
);
4746 if (end_points_hdr
) {
4747 end_points
= sipmsg_parse_endpoints_header(end_points_hdr
);
4749 if (g_slist_length(end_points
) > 2) {
4750 is_multiparty
= TRUE
;
4753 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
4754 is_triggered
= TRUE
;
4755 is_multiparty
= TRUE
;
4758 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4759 /* Convert to multiparty */
4760 if (session
&& is_multiparty
&& !session
->is_multiparty
) {
4761 g_free(session
->with
);
4762 session
->with
= NULL
;
4763 was_multiparty
= FALSE
;
4764 session
->is_multiparty
= TRUE
;
4765 session
->chat_id
= rand();
4768 if (!session
&& is_multiparty
) {
4769 session
= sipe_session_find_or_add_chat_by_callid(sip
, callid
);
4772 from
= parse_from(sipmsg_find_header(msg
, "From"));
4774 session
= sipe_session_find_or_add_im(sip
, from
);
4778 g_free(session
->callid
);
4779 session
->callid
= g_strdup(callid
);
4781 session
->is_multiparty
= is_multiparty
;
4782 if (roster_manager
) {
4783 session
->roster_manager
= g_strdup(roster_manager
);
4787 if (is_multiparty
&& end_points
) {
4788 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
4789 GSList
*entry
= end_points
;
4791 struct sip_dialog
*dialog
;
4792 struct sipendpoint
*end_point
= entry
->data
;
4793 entry
= entry
->next
;
4795 if (!g_strcasecmp(from
, end_point
->contact
) ||
4796 !g_strcasecmp(to
, end_point
->contact
))
4799 dialog
= sipe_dialog_find(session
, end_point
->contact
);
4801 g_free(dialog
->theirepid
);
4802 dialog
->theirepid
= end_point
->epid
;
4803 end_point
->epid
= NULL
;
4805 dialog
= sipe_dialog_add(session
);
4807 dialog
->callid
= g_strdup(session
->callid
);
4808 dialog
->with
= end_point
->contact
;
4809 end_point
->contact
= NULL
;
4810 dialog
->theirepid
= end_point
->epid
;
4811 end_point
->epid
= NULL
;
4815 /* send triggered INVITE */
4816 sipe_invite(sip
, session
, dialog
->with
, NULL
, NULL
, TRUE
);
4823 GSList
*entry
= end_points
;
4825 struct sipendpoint
*end_point
= entry
->data
;
4826 entry
= entry
->next
;
4827 g_free(end_point
->contact
);
4828 g_free(end_point
->epid
);
4831 g_slist_free(end_points
);
4835 struct sip_dialog
*dialog
= sipe_dialog_find(session
, from
);
4837 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
4839 dialog
= sipe_dialog_add(session
);
4841 dialog
->callid
= g_strdup(session
->callid
);
4842 dialog
->with
= g_strdup(from
);
4843 sipe_dialog_parse(dialog
, msg
, FALSE
);
4845 if (!dialog
->ourtag
) {
4846 dialog
->ourtag
= newTag
;
4853 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
4857 if (is_multiparty
&& !session
->conv
) {
4858 gchar
*chat_title
= sipe_chat_get_name(callid
);
4859 gchar
*self
= sip_uri_self(sip
);
4860 /* create prpl chat */
4861 session
->conv
= serv_got_joined_chat(sip
->gc
, session
->chat_id
, chat_title
);
4862 session
->chat_title
= g_strdup(chat_title
);
4863 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session
->conv
), self
);
4865 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4867 PURPLE_CBFLAGS_NONE
, FALSE
);
4872 if (is_multiparty
&& !was_multiparty
) {
4873 /* add current IM counterparty to chat */
4874 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4875 sipe_dialog_first(session
)->with
, NULL
,
4876 PURPLE_CBFLAGS_NONE
, FALSE
);
4879 /* add inviting party to chat */
4880 if (just_joined
&& session
->conv
) {
4881 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4883 PURPLE_CBFLAGS_NONE
, TRUE
);
4886 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
4888 /* This used only in 2005 official client, not 2007 or Reuters.
4889 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
4890 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
4892 if (is_multiparty
) {
4893 /* please do not optimize logic inside as this code may be re-enabled for other cases */
4894 gchar
*ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
4895 if (ms_text_format
) {
4896 if (g_str_has_prefix(ms_text_format
, "text/plain") || g_str_has_prefix(ms_text_format
, "text/html")) {
4898 gchar
*html
= get_html_message(ms_text_format
, NULL
);
4900 if (is_multiparty
) {
4901 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
4902 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
4904 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
4907 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
4916 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
4917 sipmsg_add_header(msg
, "User-Agent", sipe_get_useragent(sip
));
4918 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
4920 body
= g_strdup_printf(
4922 "o=- 0 0 IN IP4 %s\r\n"
4926 "m=%s %d sip sip:%s\r\n"
4927 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4928 purple_network_get_my_ip(-1),
4929 purple_network_get_my_ip(-1),
4930 sip
->ocs2007
? "message" : "x-ms-message",
4933 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4937 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4941 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
4942 sipmsg_add_header(msg
, "User-Agent", sipe_get_useragent(sip
));
4943 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
4945 body
= g_strdup_printf(
4947 "o=- 0 0 IN IP4 0.0.0.0\r\n"
4949 "c=IN IP4 0.0.0.0\r\n"
4951 "m=%s %d sip sip:%s\r\n"
4952 "a=accept-types:text/plain text/html image/gif multipart/related multipart/alternative application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4953 sip
->ocs2007
? "message" : "x-ms-message",
4956 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4960 static void sipe_connection_cleanup(struct sipe_account_data
*);
4961 static void create_connection(struct sipe_account_data
*, gchar
*, int);
4963 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
4964 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
4967 const gchar
*expires_header
;
4969 GSList
*hdr
= msg
->headers
;
4970 struct siphdrelement
*elem
;
4972 expires_header
= sipmsg_find_header(msg
, "Expires");
4973 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
4974 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
4976 switch (msg
->response
) {
4979 sip
->registerstatus
= 0;
4981 gchar
*contact_hdr
= NULL
;
4986 gchar
*server_hdr
= sipmsg_find_header(msg
, "Server");
4988 if (!sip
->reregister_set
) {
4989 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
4990 sipe_schedule_action(action_name
, expires
, do_register_cb
, NULL
, sip
, NULL
);
4991 g_free(action_name
);
4992 sip
->reregister_set
= TRUE
;
4995 sip
->registerstatus
= 3;
4997 if (server_hdr
&& !sip
->server_version
) {
4998 sip
->server_version
= g_strdup(server_hdr
);
5004 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5006 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
5009 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
5013 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\n", tmp
);
5014 fill_auth(tmp
, &sip
->registrar
);
5017 if (!sip
->reauthenticate_set
) {
5018 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
5019 guint reauth_timeout
;
5020 if (sip
->registrar
.type
== AUTH_TYPE_KERBEROS
&& sip
->registrar
.expires
> 0) {
5021 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5022 reauth_timeout
= sip
->registrar
.expires
- 300;
5024 /* NTLM: we have to reauthenticate as our security token expires
5025 after eight hours (be five minutes early) */
5026 reauth_timeout
= (8 * 3600) - 300;
5028 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, NULL
, sip
, NULL
);
5029 g_free(action_name
);
5030 sip
->reauthenticate_set
= TRUE
;
5033 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
5035 epid
= get_epid(sip
);
5036 uuid
= generateUUIDfromEPID(epid
);
5039 // There can be multiple Contact headers (one per location where the user is logged in) so
5040 // make sure to only get the one for this uuid
5041 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
5042 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
5043 if (valid_contact
) {
5044 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
5045 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5046 g_free(valid_contact
);
5049 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5054 g_free(sip
->contact
);
5056 sip
->contact
= g_strdup_printf("<%s>", gruu
);
5059 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5060 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
);
5062 sip
->ocs2007
= FALSE
;
5063 sip
->batched_support
= FALSE
;
5068 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
5069 if (!g_ascii_strcasecmp(elem
->value
, "msrtc-event-categories")) {
5070 /* We interpret this as OCS2007+ indicator */
5071 sip
->ocs2007
= TRUE
;
5072 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s (indicates OCS2007+)\n", elem
->value
);
5074 if (!g_ascii_strcasecmp(elem
->value
, "adhoclist")) {
5075 sip
->batched_support
= TRUE
;
5076 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s\n", elem
->value
);
5079 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
5080 gchar
**caps
= g_strsplit(elem
->value
,",",0);
5083 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
5084 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
5089 hdr
= g_slist_next(hdr
);
5092 /* rejoin open chats to be able to use them by continue to send messages */
5093 purple_conversation_foreach(sipe_rejoin_chat
);
5096 if (!sip
->subscribed
) { //do it just once, not every re-register
5098 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-contacts",
5099 (GCompareFunc
)g_ascii_strcasecmp
)) {
5100 sipe_subscribe_roaming_contacts(sip
);
5103 /* For 2007+ it does not make sence to subscribe to:
5104 * vnd-microsoft-roaming-ACL
5105 * vnd-microsoft-provisioning (not v2)
5107 * These are for backward compatibility.
5111 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-self",
5112 (GCompareFunc
)g_ascii_strcasecmp
)) {
5113 sipe_subscribe_roaming_self(sip
);
5115 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-provisioning-v2",
5116 (GCompareFunc
)g_ascii_strcasecmp
)) {
5117 sipe_subscribe_roaming_provisioning_v2(sip
);
5120 /* For 2005- servers */
5123 //sipe_options_request(sip, sip->sipdomain);
5125 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-ACL",
5126 (GCompareFunc
)g_ascii_strcasecmp
)) {
5127 sipe_subscribe_roaming_acl(sip
);
5129 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-provisioning",
5130 (GCompareFunc
)g_ascii_strcasecmp
)) {
5131 sipe_subscribe_roaming_provisioning(sip
);
5133 if (g_slist_find_custom(sip
->allow_events
, "presence.wpending",
5134 (GCompareFunc
)g_ascii_strcasecmp
)) {
5135 sipe_subscribe_presence_wpending(sip
, msg
);
5138 /* For 2007+ we publish our initial statuses and calendar data only after
5139 * received our existing publications in sipe_process_roaming_self()
5140 * Only in this case we know versions of current publications made
5143 /* For 2005- we publish our initial statuses only after
5144 * received our existing UserInfo data in response to
5145 * self subscription.
5146 * Only in this case we won't override existing UserInfo data
5147 * set earlier or by other client on our behalf.
5151 sip
->subscribed
= TRUE
;
5154 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
5155 "timeout=", ";", NULL
);
5156 if (timeout
!= NULL
) {
5157 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
5158 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5159 sip
->keepalive_timeout
);
5163 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip
->cseq
);
5168 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
5170 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
5171 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
5175 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
5178 tmp
= g_strsplit(parts
[0], ":", 0);
5179 hostname
= g_strdup(tmp
[0]);
5180 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
5184 tmp
= g_strsplit(parts
[i
], "=", 0);
5186 if (g_strcasecmp("transport", tmp
[0]) == 0) {
5187 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
5188 transport
= SIPE_TRANSPORT_TCP
;
5189 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
5190 transport
= SIPE_TRANSPORT_UDP
;
5199 /* Close old connection */
5200 sipe_connection_cleanup(sip
);
5202 /* Create new connection */
5203 sip
->transport
= transport
;
5204 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5205 hostname
, port
, TRANSPORT_DESCRIPTOR
);
5206 create_connection(sip
, hostname
, port
);
5212 if (sip
->registerstatus
!= 2) {
5213 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
5214 if (sip
->registrar
.retries
> 3) {
5215 sip
->gc
->wants_to_die
= TRUE
;
5216 purple_connection_error(sip
->gc
, _("Wrong password"));
5220 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5222 tmp
= sipmsg_find_auth_header(msg
, "NTLM");
5225 tmp
= sipmsg_find_auth_header(msg
, "Kerberos");
5228 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\n", tmp
);
5229 fill_auth(tmp
, &sip
->registrar
);
5230 sip
->registerstatus
= 2;
5231 if (sip
->account
->disconnecting
) {
5232 do_register_exp(sip
, 0);
5240 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
5241 gchar
**reason
= NULL
;
5242 if (warning
!= NULL
) {
5244 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5246 reason
= g_strsplit(warning
, "\"", 0);
5248 warning
= g_strdup_printf(_("You have been rejected by the server: %s"),
5249 (reason
&& reason
[1]) ? reason
[1] : _("no reason given"));
5252 sip
->gc
->wants_to_die
= TRUE
;
5253 purple_connection_error(sip
->gc
, warning
);
5260 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
5261 gchar
*reason
= NULL
;
5262 if (warning
!= NULL
) {
5263 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
5265 warning
= g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5266 warning
? (reason
? reason
: _("no reason given")) :
5267 _("SIP is either not enabled for the destination URI or it does not exist"));
5270 sip
->gc
->wants_to_die
= TRUE
;
5271 purple_connection_error(sip
->gc
, warning
);
5277 case 504: /* Server time-out */
5279 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
5280 gchar
*reason
= NULL
;
5281 if (warning
!= NULL
) {
5282 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
5284 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: "<a href=\"http://www.reuters.com\">http://www.reuters.com</a>"/*_("no reason given")*/);
5287 sip
->gc
->wants_to_die
= TRUE
;
5288 purple_connection_error(sip
->gc
, warning
);
5298 * Returns 2005-style activity and Availability.
5300 * @param status Sipe statis id.
5303 sipe_get_act_avail_by_status_2005(const char *status
,
5307 int avail
= 300; /* online */
5308 int act
= 400; /* Available */
5310 if (!strcmp(status
, SIPE_STATUS_ID_AWAY
)) {
5312 //} else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5314 } else if (!strcmp(status
, SIPE_STATUS_ID_BRB
)) {
5316 } else if (!strcmp(status
, SIPE_STATUS_ID_AVAILABLE
)) {
5318 //} else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5320 } else if (!strcmp(status
, SIPE_STATUS_ID_BUSY
) ||
5321 !strcmp(status
, SIPE_STATUS_ID_DND
)) {
5323 } else if (!strcmp(status
, SIPE_STATUS_ID_INVISIBLE
) ||
5324 !strcmp(status
, SIPE_STATUS_ID_OFFLINE
)) {
5325 avail
= 0; /* offline */
5328 act
= 400; /* Available */
5331 if (activity
) *activity
= act
;
5332 if (availability
) *availability
= avail
;
5338 * @param activity 2005 aggregated activity. Ex.: 600
5339 * @param availablity 2005 aggregated availablity. Ex.: 300
5342 sipe_get_status_by_act_avail_2005(const int activity
,
5343 const int availablity
,
5344 char **activity_desc
)
5346 const char *status_id
= NULL
;
5347 const char *act
= NULL
;
5349 if (activity
< 150) {
5350 status_id
= SIPE_STATUS_ID_AWAY
;
5351 } else if (activity
< 200) {
5352 //status_id = SIPE_STATUS_ID_LUNCH;
5353 status_id
= SIPE_STATUS_ID_AWAY
;
5354 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH
);
5355 } else if (activity
< 300) {
5356 //status_id = SIPE_STATUS_ID_IDLE;
5357 status_id
= SIPE_STATUS_ID_AWAY
;
5358 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE
);
5359 } else if (activity
< 400) {
5360 status_id
= SIPE_STATUS_ID_BRB
;
5361 } else if (activity
< 500) {
5362 status_id
= SIPE_STATUS_ID_AVAILABLE
;
5363 } else if (activity
< 600) {
5364 status_id
= SIPE_STATUS_ID_ON_PHONE
;
5365 } else if (activity
< 700) {
5366 status_id
= SIPE_STATUS_ID_BUSY
;
5367 } else if (activity
< 800) {
5368 status_id
= SIPE_STATUS_ID_AWAY
;
5370 status_id
= SIPE_STATUS_ID_AVAILABLE
;
5373 if (availablity
< 100)
5374 status_id
= SIPE_STATUS_ID_OFFLINE
;
5376 if (activity_desc
&& act
) {
5377 g_free(*activity_desc
);
5378 *activity_desc
= g_strdup(act
);
5385 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5388 sipe_get_status_by_availability(int avail
,
5389 char** activity_desc
)
5392 const char *act
= NULL
;
5395 status
= SIPE_STATUS_ID_OFFLINE
;
5396 } else if (avail
< 4500) {
5397 status
= SIPE_STATUS_ID_AVAILABLE
;
5398 } else if (avail
< 6000) {
5399 //status = SIPE_STATUS_ID_IDLE;
5400 status
= SIPE_STATUS_ID_AVAILABLE
;
5401 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE
);
5402 } else if (avail
< 7500) {
5403 status
= SIPE_STATUS_ID_BUSY
;
5404 } else if (avail
< 9000) {
5405 //status = SIPE_STATUS_ID_BUSYIDLE;
5406 status
= SIPE_STATUS_ID_BUSY
;
5407 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE
);
5408 } else if (avail
< 12000) {
5409 status
= SIPE_STATUS_ID_DND
;
5410 } else if (avail
< 15000) {
5411 status
= SIPE_STATUS_ID_BRB
;
5412 } else if (avail
< 18000) {
5413 status
= SIPE_STATUS_ID_AWAY
;
5415 status
= SIPE_STATUS_ID_OFFLINE
;
5418 if (activity_desc
&& act
) {
5419 g_free(*activity_desc
);
5420 *activity_desc
= g_strdup(act
);
5427 * Returns 2007-style availability value
5429 * @param sipe_status_id (in)
5430 * @param activity_token (out) Must be g_free()'d after use if consumed.
5433 sipe_get_availability_by_status(const char* sipe_status_id
, char** activity_token
)
5436 sipe_activity activity
= SIPE_ACTIVITY_UNSET
;
5438 if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_AWAY
)) {
5439 availability
= 15500;
5440 if (!activity_token
|| !(*activity_token
)) {
5441 activity
= SIPE_ACTIVITY_AWAY
;
5443 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_BRB
)) {
5444 availability
= 12500;
5445 activity
= SIPE_ACTIVITY_BRB
;
5446 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_DND
)) {
5447 availability
= 9500;
5448 activity
= SIPE_ACTIVITY_DND
;
5449 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_BUSY
)) {
5450 availability
= 6500;
5451 if (!activity_token
|| !(*activity_token
)) {
5452 activity
= SIPE_ACTIVITY_BUSY
;
5454 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_AVAILABLE
)) {
5455 availability
= 3500;
5456 activity
= SIPE_ACTIVITY_ONLINE
;
5457 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_UNKNOWN
)) {
5460 // Offline or invisible
5461 availability
= 18500;
5462 activity
= SIPE_ACTIVITY_OFFLINE
;
5465 if (activity_token
) {
5466 *activity_token
= g_strdup(sipe_activity_map
[activity
].token
);
5468 return availability
;
5471 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5474 xmlnode
*xn_categories
;
5475 xmlnode
*xn_category
;
5477 const char *status
= NULL
;
5478 gboolean do_update_status
= FALSE
;
5480 xn_categories
= xmlnode_from_str(data
, len
);
5481 uri
= xmlnode_get_attrib(xn_categories
, "uri"); /* with 'sip:' prefix */
5483 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
5485 xn_category
= xmlnode_get_next_twin(xn_category
) )
5487 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
5490 if (!strcmp(attrVar
, "contactCard"))
5493 /* identity - Display Name and email */
5494 node
= xmlnode_get_descendant(xn_category
, "contactCard", "identity", NULL
);
5496 char* display_name
= xmlnode_get_data(
5497 xmlnode_get_descendant(node
, "name", "displayName", NULL
));
5498 char* email
= xmlnode_get_data(
5499 xmlnode_get_child(node
, "email"));
5501 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
5502 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
5504 g_free(display_name
);
5508 node
= xmlnode_get_descendant(xn_category
, "contactCard", "company", NULL
);
5510 char* company
= xmlnode_get_data(node
);
5511 sipe_update_user_info(sip
, uri
, COMPANY_PROP
, company
);
5515 node
= xmlnode_get_descendant(xn_category
, "contactCard", "department", NULL
);
5517 char* department
= xmlnode_get_data(node
);
5518 sipe_update_user_info(sip
, uri
, DEPARTMENT_PROP
, department
);
5522 node
= xmlnode_get_descendant(xn_category
, "contactCard", "title", NULL
);
5524 char* title
= xmlnode_get_data(node
);
5525 sipe_update_user_info(sip
, uri
, TITLE_PROP
, title
);
5529 node
= xmlnode_get_descendant(xn_category
, "contactCard", "office", NULL
);
5531 char* office
= xmlnode_get_data(node
);
5532 sipe_update_user_info(sip
, uri
, OFFICE_PROP
, office
);
5536 node
= xmlnode_get_descendant(xn_category
, "contactCard", "url", NULL
);
5538 char* site
= xmlnode_get_data(node
);
5539 sipe_update_user_info(sip
, uri
, SITE_PROP
, site
);
5543 for (node
= xmlnode_get_descendant(xn_category
, "contactCard", "phone", NULL
);
5545 node
= xmlnode_get_next_twin(node
))
5547 const char *phone_type
= xmlnode_get_attrib(node
, "type");
5548 char* phone
= xmlnode_get_data(xmlnode_get_child(node
, "uri"));
5549 char* phone_display_string
= xmlnode_get_data(xmlnode_get_child(node
, "displayString"));
5551 sipe_update_user_phone(sip
, uri
, phone_type
, phone
, phone_display_string
);
5554 g_free(phone_display_string
);
5557 for (node
= xmlnode_get_descendant(xn_category
, "contactCard", "address", NULL
);
5559 node
= xmlnode_get_next_twin(node
))
5561 if (!strcmp(xmlnode_get_attrib(node
, "type"), "work")) {
5562 char* street
= xmlnode_get_data(xmlnode_get_child(node
, "street"));
5563 char* city
= xmlnode_get_data(xmlnode_get_child(node
, "city"));
5564 char* state
= xmlnode_get_data(xmlnode_get_child(node
, "state"));
5565 char* zipcode
= xmlnode_get_data(xmlnode_get_child(node
, "zipcode"));
5566 char* country_code
= xmlnode_get_data(xmlnode_get_child(node
, "countryCode"));
5568 sipe_update_user_info(sip
, uri
, ADDRESS_STREET_PROP
, street
);
5569 sipe_update_user_info(sip
, uri
, ADDRESS_CITY_PROP
, city
);
5570 sipe_update_user_info(sip
, uri
, ADDRESS_STATE_PROP
, state
);
5571 sipe_update_user_info(sip
, uri
, ADDRESS_ZIPCODE_PROP
, zipcode
);
5572 sipe_update_user_info(sip
, uri
, ADDRESS_COUNTRYCODE_PROP
, country_code
);
5578 g_free(country_code
);
5585 else if (!strcmp(attrVar
, "note"))
5588 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
5591 /* clean up in case no 'note' element is supplied
5592 * which indicate note removal in client
5594 g_free(sbuddy
->note
);
5595 sbuddy
->note
= NULL
;
5596 sbuddy
->is_oof_note
= FALSE
;
5598 xn_node
= xmlnode_get_descendant(xn_category
, "note", "body", NULL
);
5601 sbuddy
->note
= g_markup_escape_text((tmp
= xmlnode_get_data(xn_node
)), -1);
5603 sbuddy
->is_oof_note
= !strcmp(xmlnode_get_attrib(xn_node
, "type"), "OOF");
5605 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s),note(%s)\n",
5606 uri
, sbuddy
->note
? sbuddy
->note
: "");
5609 /* to trigger UI refresh in case no status info is supplied in this update */
5610 do_update_status
= TRUE
;
5615 else if(!strcmp(attrVar
, "state"))
5619 xmlnode
*xn_availability
;
5620 xmlnode
*xn_activity
;
5621 xmlnode
*xn_meeting_subject
;
5622 xmlnode
*xn_meeting_location
;
5623 struct sipe_buddy
*sbuddy
= uri
? g_hash_table_lookup(sip
->buddies
, uri
) : NULL
;
5625 xn_node
= xmlnode_get_child(xn_category
, "state");
5626 if (!xn_node
) continue;
5627 xn_availability
= xmlnode_get_child(xn_node
, "availability");
5628 if (!xn_availability
) continue;
5629 xn_activity
= xmlnode_get_child(xn_node
, "activity");
5630 xn_meeting_subject
= xmlnode_get_child(xn_node
, "meetingSubject");
5631 xn_meeting_location
= xmlnode_get_child(xn_node
, "meetingLocation");
5633 data
= xmlnode_get_data(xn_availability
);
5634 availability
= atoi(data
);
5637 /* activity, meeting_subject, meeting_location */
5640 g_free(sbuddy
->activity
);
5641 sbuddy
->activity
= NULL
;
5643 const char *token
= xmlnode_get_attrib(xn_activity
, "token");
5644 xmlnode
*xn_custom
= xmlnode_get_child(xn_activity
, "custom");
5647 if (!is_empty(token
)) {
5648 sbuddy
->activity
= g_strdup(sipe_get_activity_desc_by_token(token
));
5650 /* from custom element */
5652 char *custom
= xmlnode_get_data(xn_custom
);
5654 if (!is_empty(custom
)) {
5655 sbuddy
->activity
= custom
;
5661 /* meeting_subject */
5662 g_free(sbuddy
->meeting_subject
);
5663 sbuddy
->meeting_subject
= NULL
;
5664 if (xn_meeting_subject
) {
5665 char *meeting_subject
= xmlnode_get_data(xn_meeting_subject
);
5667 if (!is_empty(meeting_subject
)) {
5668 sbuddy
->meeting_subject
= meeting_subject
;
5669 meeting_subject
= NULL
;
5671 g_free(meeting_subject
);
5673 /* meeting_location */
5674 g_free(sbuddy
->meeting_location
);
5675 sbuddy
->meeting_location
= NULL
;
5676 if (xn_meeting_location
) {
5677 char *meeting_location
= xmlnode_get_data(xn_meeting_location
);
5679 if (!is_empty(meeting_location
)) {
5680 sbuddy
->meeting_location
= meeting_location
;
5681 meeting_location
= NULL
;
5683 g_free(meeting_location
);
5686 status
= sipe_get_status_by_availability(availability
, &(sbuddy
->activity
));
5689 do_update_status
= TRUE
;
5692 else if(!strcmp(attrVar
, "calendarData"))
5694 struct sipe_buddy
*sbuddy
= uri
? g_hash_table_lookup(sip
->buddies
, uri
) : NULL
;
5695 xmlnode
*xn_free_busy
= xmlnode_get_descendant(xn_category
, "calendarData", "freeBusy", NULL
);
5696 xmlnode
*xn_working_hours
= xmlnode_get_descendant(xn_category
, "calendarData", "WorkingHours", NULL
);
5698 if (sbuddy
&& xn_free_busy
) {
5699 g_free(sbuddy
->cal_start_time
);
5700 sbuddy
->cal_start_time
= g_strdup(xmlnode_get_attrib(xn_free_busy
, "startTime"));
5702 sbuddy
->cal_granularity
= !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy
, "granularity"), "PT15M") ?
5705 g_free(sbuddy
->cal_free_busy_base64
);
5706 sbuddy
->cal_free_busy_base64
= xmlnode_get_data(xn_free_busy
);
5708 g_free(sbuddy
->cal_free_busy
);
5709 sbuddy
->cal_free_busy
= NULL
;
5711 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
);
5714 if (sbuddy
&& xn_working_hours
) {
5715 sipe_cal_parse_working_hours(xn_working_hours
, sbuddy
);
5720 if (do_update_status
) {
5721 if (!status
) { /* no status category in this update, using contact's current status */
5722 PurpleBuddy
*pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri
);
5723 const PurplePresence
*presence
= purple_buddy_get_presence(pbuddy
);
5724 const PurpleStatus
*pstatus
= purple_presence_get_active_status(presence
);
5725 status
= purple_status_get_id(pstatus
);
5728 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status
);
5729 sipe_got_user_status(sip
, uri
, status
);
5732 xmlnode_free(xn_categories
);
5735 static void sipe_subscribe_poolfqdn_resource_uri(const char *host
, GSList
*server
, struct sipe_account_data
*sip
)
5737 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
5738 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host
);
5739 payload
->host
= g_strdup(host
);
5740 payload
->buddies
= server
;
5741 sipe_subscribe_presence_batched_routed(sip
, payload
);
5742 sipe_subscribe_presence_batched_routed_free(payload
);
5745 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5748 xmlnode
*xn_resource
;
5749 GHashTable
*servers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
5754 xn_list
= xmlnode_from_str(data
, len
);
5756 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
5758 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
5760 const char *uri
, *state
;
5761 xmlnode
*xn_instance
;
5763 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
5764 if (!xn_instance
) continue;
5766 uri
= xmlnode_get_attrib(xn_resource
, "uri");
5767 state
= xmlnode_get_attrib(xn_instance
, "state");
5768 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri
, state
);
5770 if (strstr(state
, "resubscribe")) {
5771 const char *poolFqdn
= xmlnode_get_attrib(xn_instance
, "poolFqdn");
5773 if (poolFqdn
) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
5774 gchar
*user
= g_strdup(uri
);
5775 host
= g_strdup(poolFqdn
);
5776 server
= g_hash_table_lookup(servers
, host
);
5777 server
= g_slist_append(server
, user
);
5778 g_hash_table_insert(servers
, host
, server
);
5780 sipe_subscribe_presence_single(sip
, (void *) uri
);
5785 /* Send out any deferred poolFqdn subscriptions */
5786 g_hash_table_foreach(servers
, (GHFunc
) sipe_subscribe_poolfqdn_resource_uri
, sip
);
5787 g_hash_table_destroy(servers
);
5789 xmlnode_free(xn_list
);
5792 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5796 gchar
*activity
= NULL
;
5798 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
5799 gboolean isonline
= FALSE
;
5800 xmlnode
*display_name_node
;
5802 pidf
= xmlnode_from_str(data
, len
);
5804 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data
);
5808 uri
= sip_uri(xmlnode_get_attrib(pidf
, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
5810 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
5812 if ((status
= xmlnode_get_child(tuple
, "status"))) {
5813 basicstatus
= xmlnode_get_child(status
, "basic");
5818 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
5823 getbasic
= xmlnode_get_data(basicstatus
);
5825 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
5830 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic
);
5831 if (strstr(getbasic
, "open")) {
5836 display_name_node
= xmlnode_get_child(pidf
, "display-name");
5837 if (display_name_node
) {
5838 char * display_name
= xmlnode_get_data(display_name_node
);
5840 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
5841 g_free(display_name
);
5844 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
5845 if ((status
= xmlnode_get_child(tuple
, "status"))) {
5846 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
5847 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
5848 activity
= xmlnode_get_data(basicstatus
);
5849 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity
);
5856 const gchar
* status_id
= NULL
;
5858 if (!strcmp(activity
, sipe_activity_map
[SIPE_ACTIVITY_BUSY
].token
)) {
5859 status_id
= SIPE_STATUS_ID_BUSY
;
5860 } else if (!strcmp(activity
, sipe_activity_map
[SIPE_ACTIVITY_AWAY
].token
)) {
5861 status_id
= SIPE_STATUS_ID_AWAY
;
5866 status_id
= SIPE_STATUS_ID_AVAILABLE
;
5869 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id
);
5870 sipe_got_user_status(sip
, uri
, status_id
);
5872 sipe_got_user_status(sip
, uri
, SIPE_STATUS_ID_OFFLINE
);
5882 sipe_user_info_has_updated(struct sipe_account_data
*sip
,
5883 xmlnode
*xn_userinfo
)
5885 if (sip
->user_info
) {
5886 xmlnode_free(sip
->user_info
);
5888 sip
->user_info
= xmlnode_copy(xn_userinfo
);
5890 /* Publish initial state if not yet.
5891 * Assuming this happens on initial responce to self subscription
5892 * so we've already updated our UserInfo.
5894 if (!sip
->initial_state_published
) {
5895 send_presence_soap(sip
, FALSE
);
5897 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
5901 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5903 char *activity
= NULL
;
5905 const char *status_id
= NULL
;
5908 char *self_uri
= sip_uri_self(sip
);
5911 const char *device_name
= NULL
;
5912 const char *cal_start_time
= NULL
;
5913 const char *cal_granularity
= NULL
;
5914 char *cal_free_busy_base64
= NULL
;
5915 struct sipe_buddy
*sbuddy
;
5917 xmlnode
*xn_presentity
;
5918 xmlnode
*xn_availability
;
5919 xmlnode
*xn_activity
;
5920 xmlnode
*xn_display_name
;
5922 xmlnode
*xn_phone_number
;
5923 xmlnode
*xn_userinfo
;
5927 xmlnode
*xn_contact
;
5929 char *free_activity
;
5931 const char *user_avail_nil
;
5933 time_t user_avail_since
= 0;
5934 time_t activity_since
= 0;
5936 /* fix for Reuters environment on Linux */
5937 if (data
&& strstr(data
, "encoding=\"utf-16\"")) {
5939 tmp_data
= replace(data
, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
5940 xn_presentity
= xmlnode_from_str(tmp_data
, strlen(tmp_data
));
5943 xn_presentity
= xmlnode_from_str(data
, len
);
5946 xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
5947 xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
5948 xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
5949 xn_email
= xmlnode_get_child(xn_presentity
, "email");
5950 xn_phone_number
= xmlnode_get_child(xn_presentity
, "phoneNumber");
5951 xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
5952 xn_oof
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "oof") : NULL
;
5953 xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
): NULL
;
5954 user_avail
= xn_state
? atoi(xmlnode_get_attrib(xn_state
, "avail")) : 0;
5955 user_avail_since
= xn_state
? purple_str_to_time(xmlnode_get_attrib(xn_state
, "since"), FALSE
, NULL
, NULL
, NULL
) : 0;
5956 user_avail_nil
= xn_state
? xmlnode_get_attrib(xn_state
, "nil") : NULL
;
5957 xn_contact
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "contact") : NULL
;
5958 xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
5959 note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
5961 if (user_avail_nil
&& !strcmp(user_avail_nil
, "true")) { /* null-ed */
5963 user_avail_since
= 0;
5966 free_activity
= NULL
;
5968 name
= xmlnode_get_attrib(xn_presentity
, "uri"); /* without 'sip:' prefix */
5969 uri
= sip_uri_from_name(name
);
5970 avl
= atoi(xmlnode_get_attrib(xn_availability
, "aggregate"));
5971 epid
= xmlnode_get_attrib(xn_availability
, "epid");
5972 act
= atoi(xmlnode_get_attrib(xn_activity
, "aggregate"));
5974 status_id
= sipe_get_status_by_act_avail_2005(act
, avl
, &activity
);
5975 res_avail
= sipe_get_availability_by_status(status_id
, NULL
);
5976 if (user_avail
> res_avail
) {
5977 res_avail
= user_avail
;
5978 status_id
= sipe_get_status_by_availability(user_avail
, NULL
);
5981 if (xn_display_name
) {
5982 char *display_name
= g_strdup(xmlnode_get_attrib(xn_display_name
, "displayName"));
5983 char *email
= xn_email
? g_strdup(xmlnode_get_attrib(xn_email
, "email")) : NULL
;
5984 char *phone_label
= xn_phone_number
? g_strdup(xmlnode_get_attrib(xn_phone_number
, "label")) : NULL
;
5985 char *phone_number
= xn_phone_number
? g_strdup(xmlnode_get_attrib(xn_phone_number
, "number")) : NULL
;
5986 char *tel_uri
= sip_to_tel_uri(phone_number
);
5988 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
5989 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
5990 sipe_update_user_info(sip
, uri
, PHONE_PROP
, tel_uri
);
5991 sipe_update_user_info(sip
, uri
, PHONE_DISPLAY_PROP
, !is_empty(phone_label
) ? phone_label
: phone_number
);
5994 g_free(phone_label
);
5995 g_free(phone_number
);
5997 g_free(display_name
);
6002 for (node
= xmlnode_get_child(xn_contact
, "tel"); node
; node
= xmlnode_get_next_twin(node
))
6004 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6005 const char *phone_type
= xmlnode_get_attrib(node
, "type");
6006 char* phone
= xmlnode_get_data(node
);
6008 sipe_update_user_phone(sip
, uri
, phone_type
, phone
, NULL
);
6014 /* devicePresence */
6015 for (node
= xmlnode_get_descendant(xn_presentity
, "devices", "devicePresence", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
6016 xmlnode
*xn_device_name
;
6017 xmlnode
*xn_calendar_info
;
6022 if (!strcmp(xmlnode_get_attrib(node
, "epid"), epid
)) {
6023 xn_device_name
= xmlnode_get_child(node
, "deviceName");
6024 device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
6028 xn_calendar_info
= xmlnode_get_child(node
, "calendarInfo");
6029 if (xn_calendar_info
) {
6030 const char *cal_start_time_tmp
= xmlnode_get_attrib(xn_calendar_info
, "startTime");
6032 if (cal_start_time
) {
6033 time_t cal_start_time_t
= purple_str_to_time(cal_start_time
, FALSE
, NULL
, NULL
, NULL
);
6034 time_t cal_start_time_t_tmp
= purple_str_to_time(cal_start_time_tmp
, FALSE
, NULL
, NULL
, NULL
);
6036 if (cal_start_time_t_tmp
> cal_start_time_t
) {
6037 cal_start_time
= cal_start_time_tmp
;
6038 cal_granularity
= xmlnode_get_attrib(xn_calendar_info
, "granularity");
6039 cal_free_busy_base64
= xmlnode_get_data(xn_calendar_info
);
6041 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
);
6044 cal_start_time
= cal_start_time_tmp
;
6045 cal_granularity
= xmlnode_get_attrib(xn_calendar_info
, "granularity");
6046 cal_free_busy_base64
= xmlnode_get_data(xn_calendar_info
);
6048 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
);
6053 xn_state
= xmlnode_get_descendant(node
, "states", "state", NULL
);
6055 int dev_avail
= atoi(xmlnode_get_attrib(xn_state
, "avail"));
6056 time_t dev_avail_since
= purple_str_to_time(xmlnode_get_attrib(xn_state
, "since"), FALSE
, NULL
, NULL
, NULL
);
6058 state
= xmlnode_get_data(xn_state
);
6059 if (dev_avail_since
> user_avail_since
&&
6060 dev_avail
>= res_avail
)
6062 res_avail
= dev_avail
;
6063 if (!is_empty(state
))
6065 if (!strcmp(state
, sipe_activity_map
[SIPE_ACTIVITY_ON_PHONE
].token
)) {
6066 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE
));
6067 } else if (!strcmp(state
, "presenting")) {
6068 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF
));
6072 activity_since
= dev_avail_since
;
6074 status_id
= sipe_get_status_by_availability(res_avail
, &activity
);
6081 if (xn_oof
&& res_avail
>= 15000) { /* 12000 in 2007 */
6082 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF
));
6086 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
6089 g_free(sbuddy
->activity
);
6090 sbuddy
->activity
= activity
;
6092 sbuddy
->activity_since
= activity_since
;
6094 sbuddy
->user_avail
= user_avail
;
6095 sbuddy
->user_avail_since
= user_avail_since
;
6097 g_free(sbuddy
->note
);
6098 sbuddy
->note
= NULL
;
6099 if (!is_empty(note
)) { sbuddy
->note
= g_markup_escape_text(note
, -1); }
6101 sbuddy
->is_oof_note
= (xn_oof
!= NULL
);
6103 g_free(sbuddy
->device_name
);
6104 sbuddy
->device_name
= NULL
;
6105 if (!is_empty(device_name
)) { sbuddy
->device_name
= g_strdup(device_name
); }
6107 if (!is_empty(cal_free_busy_base64
)) {
6108 g_free(sbuddy
->cal_start_time
);
6109 sbuddy
->cal_start_time
= g_strdup(cal_start_time
);
6111 sbuddy
->cal_granularity
= !g_ascii_strcasecmp(cal_granularity
, "PT15M") ? 15 : 0;
6113 g_free(sbuddy
->cal_free_busy_base64
);
6114 sbuddy
->cal_free_busy_base64
= cal_free_busy_base64
;
6116 g_free(sbuddy
->cal_free_busy
);
6117 sbuddy
->cal_free_busy
= NULL
;
6120 sbuddy
->last_non_cal_status_id
= status_id
;
6121 g_free(sbuddy
->last_non_cal_activity
);
6122 sbuddy
->last_non_cal_activity
= g_strdup(sbuddy
->activity
);
6124 if (!strcmp(sbuddy
->name
, self_uri
)) {
6125 if (!(sbuddy
->note
&& sip
->note
&& !strcmp(sbuddy
->note
, sip
->note
))) /* not same */
6127 sip
->is_oof_note
= sbuddy
->is_oof_note
;
6130 sip
->note
= g_strdup(sbuddy
->note
);
6132 sip
->note_since
= time(NULL
);
6135 g_free(sip
->status
);
6136 sip
->status
= g_strdup(sbuddy
->last_non_cal_status_id
);
6140 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id
);
6141 sipe_got_user_status(sip
, uri
, status_id
);
6143 if (!sip
->ocs2007
&& !strcmp(self_uri
, uri
)) {
6144 sipe_user_info_has_updated(sip
, xn_userinfo
);
6148 xmlnode_free(xn_presentity
);
6153 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
6155 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
6157 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
6159 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
6160 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
6162 const char *content
= msg
->body
;
6163 unsigned length
= msg
->bodylen
;
6164 PurpleMimeDocument
*mime
= NULL
;
6166 if (strstr(ctype
, "multipart"))
6168 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
6169 const char *content_type
;
6171 mime
= purple_mime_document_parse(doc
);
6172 parts
= purple_mime_document_get_parts(mime
);
6174 content
= purple_mime_part_get_data(parts
->data
);
6175 length
= purple_mime_part_get_length(parts
->data
);
6176 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
6177 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
6179 process_incoming_notify_rlmi_resub(sip
, content
, length
);
6181 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
6183 process_incoming_notify_msrtc(sip
, content
, length
);
6187 process_incoming_notify_rlmi(sip
, content
, length
);
6189 parts
= parts
->next
;
6195 purple_mime_document_free(mime
);
6198 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
6200 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
6202 else if(strstr(ctype
, "application/rlmi+xml"))
6204 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
6207 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
6209 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
6213 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
6217 static void sipe_process_presence_timeout(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gchar
*who
, int timeout
)
6219 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
6220 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
6222 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype
? ctype
: "");
6225 strstr(ctype
, "multipart") &&
6226 (strstr(ctype
, "application/rlmi+xml") ||
6227 strstr(ctype
, "application/msrtc-event-categories+xml"))) {
6228 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
6229 PurpleMimeDocument
*mime
= purple_mime_document_parse(doc
);
6230 GList
*parts
= purple_mime_document_get_parts(mime
);
6231 GSList
*buddies
= NULL
;
6232 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
6235 xmlnode
*xml
= xmlnode_from_str(purple_mime_part_get_data(parts
->data
),
6236 purple_mime_part_get_length(parts
->data
));
6238 if (strcmp(xml
->name
, "list")) {
6239 gchar
*uri
= sip_uri(xmlnode_get_attrib(xml
, "uri"));
6241 buddies
= g_slist_append(buddies
, uri
);
6245 parts
= parts
->next
;
6248 if (mime
) purple_mime_document_free(mime
);
6250 payload
->host
= g_strdup(who
);
6251 payload
->buddies
= buddies
;
6252 sipe_schedule_action(action_name
, timeout
,
6253 sipe_subscribe_presence_batched_routed
,
6254 sipe_subscribe_presence_batched_routed_free
,
6256 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who
, timeout
);
6259 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(who
));
6260 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
, timeout
);
6262 g_free(action_name
);
6266 * Dispatcher for all incoming subscription information
6267 * whether it comes from NOTIFY, BENOTIFY requests or
6268 * piggy-backed to subscription's OK responce.
6270 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6271 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6273 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
6275 gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
6276 gchar
*event
= sipmsg_find_header(msg
, "Event");
6277 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
6281 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6283 tmp
= fix_newlines(msg
->body
));
6285 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state
? subscription_state
: "");
6287 /* implicit subscriptions */
6288 if (content_type
&& purple_str_has_prefix(content_type
, "application/ms-imdn+xml")) {
6289 sipe_process_imdn(sip
, msg
);
6294 const gchar
*expires_header
;
6295 expires_header
= sipmsg_find_header(msg
, "Expires");
6296 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
6297 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout
);
6298 timeout
= (timeout
- 120) > 120 ? (timeout
- 120) : timeout
; // 2 min ahead of expiration
6301 /* for one off subscriptions (send with Expire: 0) */
6302 if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-provisioning-v2"))
6304 sipe_process_provisioning_v2(sip
, msg
);
6306 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-provisioning"))
6308 sipe_process_provisioning(sip
, msg
);
6311 if (!subscription_state
|| strstr(subscription_state
, "active"))
6313 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
6315 sipe_process_presence(sip
, msg
);
6317 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
6319 sipe_process_roaming_contacts(sip
, msg
);
6321 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self"))
6323 sipe_process_roaming_self(sip
, msg
);
6325 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
6327 sipe_process_roaming_acl(sip
, msg
);
6329 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
6331 sipe_process_presence_wpending(sip
, msg
);
6333 else if (event
&& !g_ascii_strcasecmp(event
, "conference"))
6335 sipe_process_conference(sip
, msg
);
6339 /* The server sends status 'terminated' */
6340 if (subscription_state
&& strstr(subscription_state
, "terminated") ) {
6341 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
6342 gchar
*key
= sipe_get_subscription_key(event
, who
);
6344 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who
);
6347 if (g_hash_table_lookup(sip
->subscriptions
, key
)) {
6348 g_hash_table_remove(sip
->subscriptions
, key
);
6349 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key
);
6355 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
6356 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6357 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6359 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6360 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6361 g_free(action_name);
6363 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6364 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6366 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6367 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6368 g_free(action_name);
6371 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
6372 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
6374 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
6375 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, NULL
, sip
, NULL
);
6376 g_free(action_name
);
6378 else if (!g_ascii_strcasecmp(event
, "presence") &&
6379 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
6381 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
6382 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
6383 if (sip
->batched_support
) {
6384 sipe_process_presence_timeout(sip
, msg
, who
, timeout
);
6387 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(who
));
6388 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
, timeout
);
6390 g_free(action_name
);
6395 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
6397 sipe_process_registration_notify(sip
, msg
);
6400 /* The client responses on received a NOTIFY message */
6401 if (request
&& !benotify
)
6403 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
6408 * Whether user manually changed status or
6409 * it was changed automatically due to user
6410 * became inactive/active again
6413 sipe_is_user_state(struct sipe_account_data
*sip
)
6416 time_t now
= time(NULL
);
6418 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip
->idle_switch
))));
6419 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now
)));
6421 res
= ((now
- SIPE_IDLE_SET_DELAY
* 2) >= sip
->idle_switch
);
6423 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res
? "USER" : "MACHINE");
6428 send_presence_soap0(struct sipe_account_data
*sip
,
6429 gboolean do_publish_calendar
,
6430 gboolean do_reset_status
)
6432 struct sipe_ews
* ews
= sip
->ews
;
6433 int availability
= 0;
6438 gchar
*res_note
= NULL
;
6439 gchar
*res_oof
= NULL
;
6440 const gchar
*note_pub
= NULL
;
6441 gchar
*states
= NULL
;
6442 gchar
*calendar_data
= NULL
;
6443 gchar
*epid
= get_epid(sip
);
6444 time_t now
= time(NULL
);
6445 gchar
*since_time_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&now
)));
6446 const gchar
*oof_note
= sipe_ews_get_oof_note(ews
);
6447 const char *user_input
;
6448 gboolean pub_oof
= oof_note
&& (!sip
->note
|| sip
->note_since
< ews
->oof_start
);
6450 if (oof_note
&& sip
->note
) {
6451 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews
->oof_start
))));
6452 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip
->note_since
))));
6455 purple_debug_info("sipe", "sip->note : %s", sip
->note
? sip
->note
: "");
6457 if (!sip
->initial_state_published
||
6460 g_free(sip
->status
);
6461 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
6464 sipe_get_act_avail_by_status_2005(sip
->status
, &activity
, &availability
);
6468 note_pub
= oof_note
;
6469 res_oof
= SIPE_SOAP_SET_PRESENCE_OOF_XML
;
6470 } else if (sip
->note
) {
6471 if (sip
->is_oof_note
) { /* stale OOF note, as it's not present in ews already (oof_note == NULL) */
6474 sip
->is_oof_note
= FALSE
;
6475 sip
->note_since
= 0;
6477 note_pub
= sip
->note
;
6483 /* to protocol internal plain text format */
6484 tmp
= purple_markup_strip_html(note_pub
);
6485 res_note
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML
, tmp
);
6490 if (!do_reset_status
) {
6491 if (sipe_is_user_state(sip
) && !do_publish_calendar
&& sip
->initial_state_published
)
6493 gchar
*activity_token
= NULL
;
6494 int avail_2007
= sipe_get_availability_by_status(sip
->status
, &activity_token
);
6496 states
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES
,
6501 g_free(activity_token
);
6503 else /* preserve existing publication */
6506 if (sip
->user_info
&& (xn_states
= xmlnode_get_child(sip
->user_info
, "states"))) {
6507 states
= xmlnode_to_str(xn_states
, NULL
);
6508 /* this is a hack-around to remove added newline after inner element,
6509 * state in this case, where it shouldn't be.
6510 * After several use of xmlnode_to_str, amount of added newlines
6511 * grows significantly.
6513 purple_str_strip_char(states
, '\n');
6514 //purple_str_strip_char(states, '\r');
6518 /* do nothing - then User state will be erased */
6520 sip
->initial_state_published
= TRUE
;
6523 if (ews
&& (!is_empty(ews
->legacy_dn
) || !is_empty(ews
->email
)) && ews
->fb_start
&& !is_empty(ews
->free_busy
))
6525 char *fb_start_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&ews
->fb_start
)));
6526 char *free_busy_base64
= sipe_cal_get_freebusy_base64(ews
->free_busy
);
6527 calendar_data
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR
,
6528 !is_empty(ews
->legacy_dn
) ? ews
->legacy_dn
: ews
->email
,
6531 g_free(fb_start_str
);
6532 g_free(free_busy_base64
);
6535 user_input
= !sipe_is_user_state(sip
) && sip
->status
!= SIPE_STATUS_ID_AVAILABLE
? "idle" : "active";
6537 /* forming resulting XML */
6538 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
,
6542 (tmp
= g_ascii_strup(sipe_get_host_name(), -1)),
6543 res_note
? res_note
: "",
6544 res_oof
? res_oof
: "",
6545 states
? states
: "",
6546 calendar_data
? calendar_data
: "",
6555 g_free(calendar_data
);
6557 send_soap_request(sip
, body
);
6560 g_free(since_time_str
);
6565 send_presence_soap(struct sipe_account_data
*sip
,
6566 gboolean do_publish_calendar
)
6568 return send_presence_soap0(sip
, do_publish_calendar
, FALSE
);
6573 process_send_presence_category_publish_response(struct sipe_account_data
*sip
,
6575 struct transaction
*trans
)
6577 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
6579 if (msg
->response
== 409 && g_str_has_prefix(contenttype
, "application/msrtc-fault+xml")) {
6585 gboolean has_device_publication
= FALSE
;
6587 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
6589 /* test if version mismatch fault */
6590 fault_code
= xmlnode_get_data(xmlnode_get_child(xml
, "Faultcode"));
6591 if (strcmp(fault_code
, "Client.BadCall.WrongDelta")) {
6592 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code
);
6599 /* accumulating information about faulty versions */
6600 faults
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
6601 for (node
= xmlnode_get_descendant(xml
, "details", "operation", NULL
);
6603 node
= xmlnode_get_next_twin(node
))
6605 const gchar
*index
= xmlnode_get_attrib(node
, "index");
6606 const gchar
*curVersion
= xmlnode_get_attrib(node
, "curVersion");
6608 g_hash_table_insert(faults
, g_strdup(index
), g_strdup(curVersion
));
6609 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index
, curVersion
);
6613 /* here we are parsing own request to figure out what publication
6614 * referensed here only by index went wrong
6616 xml
= xmlnode_from_str(trans
->msg
->body
, trans
->msg
->bodylen
);
6619 for (node
= xmlnode_get_descendant(xml
, "publications", "publication", NULL
),
6620 index_our
= 1; /* starts with 1 - our first publication */
6622 node
= xmlnode_get_next_twin(node
), index_our
++)
6624 gchar
*idx
= g_strdup_printf("%d", index_our
);
6625 const gchar
*curVersion
= g_hash_table_lookup(faults
, idx
);
6626 const gchar
*categoryName
= xmlnode_get_attrib(node
, "categoryName");
6629 if (!strcmp("device", categoryName
)) {
6630 has_device_publication
= TRUE
;
6633 if (curVersion
) { /* fault exist on this index */
6634 const gchar
*container
= xmlnode_get_attrib(node
, "container");
6635 const gchar
*instance
= xmlnode_get_attrib(node
, "instance");
6636 /* key is <category><instance><container> */
6637 gchar
*key
= g_strdup_printf("<%s><%s><%s>", categoryName
, instance
, container
);
6638 struct sipe_publication
*publication
=
6639 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, categoryName
), key
);
6641 purple_debug_info("sipe", "key is %s\n", key
);
6644 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6645 key
, curVersion
, publication
->version
);
6646 /* updating publication's version to the correct one */
6647 publication
->version
= atoi(curVersion
);
6653 g_hash_table_destroy(faults
);
6655 /* rebublishing with right versions */
6656 if (has_device_publication
) {
6657 send_publish_category_initial(sip
);
6659 send_presence_status(sip
);
6666 * Returns 'device' XML part for publication.
6667 * Must be g_free'd after use.
6670 sipe_publish_get_category_device(struct sipe_account_data
*sip
)
6674 gchar
*epid
= get_epid(sip
);
6675 gchar
*uuid
= generateUUIDfromEPID(epid
);
6676 guint device_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_DEVICE
);
6677 /* key is <category><instance><container> */
6678 gchar
*key
= g_strdup_printf("<%s><%u><%u>", "device", device_instance
, 2);
6679 struct sipe_publication
*publication
=
6680 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "device"), key
);
6685 uri
= sip_uri_self(sip
);
6686 doc
= g_strdup_printf(SIPE_PUB_XML_DEVICE
,
6688 publication
? publication
->version
: 0,
6691 "00:00:00+01:00", /* @TODO make timezone real*/
6692 sipe_get_host_name()
6702 * A service method - use
6703 * - send_publish_get_category_state_machine and
6704 * - send_publish_get_category_state_user instead.
6705 * Must be g_free'd after use.
6708 sipe_publish_get_category_state(struct sipe_account_data
*sip
,
6709 gboolean is_user_state
)
6711 int availability
= sipe_get_availability_by_status(sip
->status
, NULL
);
6712 guint instance
= is_user_state
? sipe_get_pub_instance(sip
, SIPE_PUB_STATE_USER
) :
6713 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_MACHINE
);
6714 /* key is <category><instance><container> */
6715 gchar
*key_2
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 2);
6716 gchar
*key_3
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 3);
6717 struct sipe_publication
*publication_2
=
6718 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_2
);
6719 struct sipe_publication
*publication_3
=
6720 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_3
);
6725 if (publication_2
&& (publication_2
->availability
== availability
))
6727 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6728 return NULL
; /* nothing to update */
6731 return g_strdup_printf( is_user_state
? SIPE_PUB_XML_STATE_USER
: SIPE_PUB_XML_STATE_MACHINE
,
6733 publication_2
? publication_2
->version
: 0,
6736 publication_3
? publication_3
->version
: 0,
6741 * Only Busy and OOF calendar event are published.
6742 * Different instances are used for that.
6744 * Must be g_free'd after use.
6747 sipe_publish_get_category_state_calendar(struct sipe_account_data
*sip
,
6748 struct sipe_cal_event
*event
,
6752 gchar
*start_time_str
;
6753 int availability
= 0;
6755 guint instance
= (cal_satus
== SIPE_CAL_OOF
) ?
6756 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR_OOF
) :
6757 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR
);
6759 /* key is <category><instance><container> */
6760 gchar
*key_2
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 2);
6761 gchar
*key_3
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 3);
6762 struct sipe_publication
*publication_2
=
6763 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_2
);
6764 struct sipe_publication
*publication_3
=
6765 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_3
);
6770 if (!publication_3
&& !event
) { /* was nothing, have nothing, exiting */
6771 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6772 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus
);
6778 (publication_3
->availability
== availability
) &&
6779 !strcmp(publication_3
->cal_event_hash
, sipe_cal_event_hash(event
)))
6781 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
6782 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus
);
6783 return NULL
; /* nothing to update */
6787 (event
->cal_status
== SIPE_CAL_BUSY
||
6788 event
->cal_status
== SIPE_CAL_OOF
))
6790 gchar
*availability_xml_str
= NULL
;
6791 gchar
*activity_xml_str
= NULL
;
6793 if (event
->cal_status
== SIPE_CAL_BUSY
) {
6794 availability_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL
, 6500);
6797 if (event
->cal_status
== SIPE_CAL_BUSY
&& event
->is_meeting
) {
6798 activity_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
,
6799 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].token
,
6800 "minAvailability=\"6500\"",
6801 "maxAvailability=\"8999\"");
6802 } else if (event
->cal_status
== SIPE_CAL_OOF
) {
6803 activity_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
,
6804 sipe_activity_map
[SIPE_ACTIVITY_OOF
].token
,
6805 "minAvailability=\"12000\"",
6808 start_time_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&event
->start_time
)));
6810 res
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR
,
6812 publication_2
? publication_2
->version
: 0,
6815 availability_xml_str
? availability_xml_str
: "",
6816 activity_xml_str
? activity_xml_str
: "",
6817 event
->subject
? event
->subject
: "",
6818 event
->location
? event
->location
: "",
6821 publication_3
? publication_3
->version
: 0,
6824 availability_xml_str
? availability_xml_str
: "",
6825 activity_xml_str
? activity_xml_str
: "",
6826 event
->subject
? event
->subject
: "",
6827 event
->location
? event
->location
: ""
6829 g_free(start_time_str
);
6830 g_free(availability_xml_str
);
6831 g_free(activity_xml_str
);
6834 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
6836 res
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR
,
6838 publication_2
? publication_2
->version
: 0,
6841 publication_3
? publication_3
->version
: 0
6849 * Returns 'machineState' XML part for publication.
6850 * Must be g_free'd after use.
6853 sipe_publish_get_category_state_machine(struct sipe_account_data
*sip
)
6855 return sipe_publish_get_category_state(sip
, FALSE
);
6859 * Returns 'userState' XML part for publication.
6860 * Must be g_free'd after use.
6863 sipe_publish_get_category_state_user(struct sipe_account_data
*sip
)
6865 return sipe_publish_get_category_state(sip
, TRUE
);
6869 * Compares two strings even in case both are NULL/empty
6872 sipe_is_equal(const char* n1
, const char* n2
) {
6873 return ((!n1
|| !strlen(n1
)) && (!n2
|| !strlen(n2
))) /* both empty */
6874 || (n1
&& n2
&& !strcmp(n1
, n2
)); /* or not empty and equal */
6878 * Returns 'note' XML part for publication.
6879 * Must be g_free'd after use.
6881 * Protocol format for Note is plain text.
6883 * @param note a note in Sipe internal HTML format
6884 * @param note_type either personal or OOF
6887 sipe_publish_get_category_note(struct sipe_account_data
*sip
,
6888 const char *note
, /* html */
6889 const char *note_type
)
6891 guint instance
= !strcmp("OOF", note_type
) ? sipe_get_pub_instance(sip
, SIPE_PUB_NOTE_OOF
) : 0;
6892 /* key is <category><instance><container> */
6893 gchar
*key_note_200
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 200);
6894 gchar
*key_note_300
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 300);
6895 gchar
*key_note_400
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 400);
6897 struct sipe_publication
*publication_note_200
=
6898 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_200
);
6899 struct sipe_publication
*publication_note_300
=
6900 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_300
);
6901 struct sipe_publication
*publication_note_400
=
6902 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_400
);
6904 char *n1
= note
? purple_markup_strip_html(note
) : NULL
;
6905 const char *n2
= publication_note_200
? publication_note_200
->note
: NULL
;
6907 g_free(key_note_200
);
6908 g_free(key_note_300
);
6909 g_free(key_note_400
);
6911 if (sipe_is_equal(n1
, n2
))
6913 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
6914 return NULL
; /* nothing to update */
6917 return g_markup_printf_escaped(SIPE_PUB_XML_NOTE
,
6919 publication_note_200
? publication_note_200
->version
: 0,
6924 publication_note_300
? publication_note_300
->version
: 0,
6929 publication_note_400
? publication_note_400
->version
: 0,
6936 * Returns 'calendarData' XML part with WorkingHours for publication.
6937 * Must be g_free'd after use.
6940 sipe_publish_get_category_cal_working_hours(struct sipe_account_data
*sip
)
6942 struct sipe_ews
* ews
= sip
->ews
;
6944 /* key is <category><instance><container> */
6945 gchar
*key_cal_1
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
6946 gchar
*key_cal_100
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
6947 gchar
*key_cal_200
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
6948 gchar
*key_cal_300
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
6949 gchar
*key_cal_400
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
6950 gchar
*key_cal_32000
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
6952 struct sipe_publication
*publication_cal_1
=
6953 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_1
);
6954 struct sipe_publication
*publication_cal_100
=
6955 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_100
);
6956 struct sipe_publication
*publication_cal_200
=
6957 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_200
);
6958 struct sipe_publication
*publication_cal_300
=
6959 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_300
);
6960 struct sipe_publication
*publication_cal_400
=
6961 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_400
);
6962 struct sipe_publication
*publication_cal_32000
=
6963 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_32000
);
6965 const char *n1
= ews
? ews
->working_hours_xml_str
: NULL
;
6966 const char *n2
= publication_cal_300
? publication_cal_300
->working_hours_xml_str
: NULL
;
6969 g_free(key_cal_100
);
6970 g_free(key_cal_200
);
6971 g_free(key_cal_300
);
6972 g_free(key_cal_400
);
6973 g_free(key_cal_32000
);
6975 if (!ews
|| is_empty(ews
->email
) || is_empty(ews
->working_hours_xml_str
)) {
6976 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
6980 if (sipe_is_equal(n1
, n2
))
6982 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
6983 return NULL
; /* nothing to update */
6986 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS
,
6988 publication_cal_1
? publication_cal_1
->version
: 0,
6990 ews
->working_hours_xml_str
,
6992 publication_cal_100
? publication_cal_100
->version
: 0,
6994 publication_cal_200
? publication_cal_200
->version
: 0,
6996 ews
->working_hours_xml_str
,
6998 publication_cal_300
? publication_cal_300
->version
: 0,
7000 ews
->working_hours_xml_str
,
7001 /* 400 - Personal */
7002 publication_cal_400
? publication_cal_400
->version
: 0,
7004 ews
->working_hours_xml_str
,
7005 /* 32000 - Blocked */
7006 publication_cal_32000
? publication_cal_32000
->version
: 0
7011 * Returns 'calendarData' XML part with FreeBusy for publication.
7012 * Must be g_free'd after use.
7015 sipe_publish_get_category_cal_free_busy(struct sipe_account_data
*sip
)
7017 struct sipe_ews
* ews
= sip
->ews
;
7018 guint cal_data_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_CALENDAR_DATA
);
7020 char *free_busy_base64
;
7025 /* key is <category><instance><container> */
7026 gchar
*key_cal_1
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 1);
7027 gchar
*key_cal_100
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 100);
7028 gchar
*key_cal_200
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 200);
7029 gchar
*key_cal_300
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 300);
7030 gchar
*key_cal_400
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 400);
7031 gchar
*key_cal_32000
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 32000);
7033 struct sipe_publication
*publication_cal_1
=
7034 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_1
);
7035 struct sipe_publication
*publication_cal_100
=
7036 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_100
);
7037 struct sipe_publication
*publication_cal_200
=
7038 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_200
);
7039 struct sipe_publication
*publication_cal_300
=
7040 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_300
);
7041 struct sipe_publication
*publication_cal_400
=
7042 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_400
);
7043 struct sipe_publication
*publication_cal_32000
=
7044 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_32000
);
7047 g_free(key_cal_100
);
7048 g_free(key_cal_200
);
7049 g_free(key_cal_300
);
7050 g_free(key_cal_400
);
7051 g_free(key_cal_32000
);
7053 if (!ews
|| is_empty(ews
->email
) || !ews
->fb_start
|| is_empty(ews
->free_busy
)) {
7054 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7058 fb_start_str
= g_strdup(purple_utf8_strftime(SIPE_XML_DATE_PATTERN
, gmtime(&ews
->fb_start
)));
7059 free_busy_base64
= sipe_cal_get_freebusy_base64(ews
->free_busy
);
7061 st
= publication_cal_300
? publication_cal_300
->fb_start_str
: NULL
;
7062 fb
= publication_cal_300
? publication_cal_300
->free_busy_base64
: NULL
;
7064 if (sipe_is_equal(st
, fb_start_str
) && sipe_is_equal(fb
, free_busy_base64
))
7066 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7067 g_free(fb_start_str
);
7068 g_free(free_busy_base64
);
7069 return NULL
; /* nothing to update */
7072 res
= g_strdup_printf(SIPE_PUB_XML_FREE_BUSY
,
7075 publication_cal_1
? publication_cal_1
->version
: 0,
7078 publication_cal_100
? publication_cal_100
->version
: 0,
7081 publication_cal_200
? publication_cal_200
->version
: 0,
7087 publication_cal_300
? publication_cal_300
->version
: 0,
7091 /* 400 - Personal */
7093 publication_cal_400
? publication_cal_400
->version
: 0,
7097 /* 32000 - Blocked */
7099 publication_cal_32000
? publication_cal_32000
->version
: 0
7102 g_free(fb_start_str
);
7103 g_free(free_busy_base64
);
7107 static void send_presence_publish(struct sipe_account_data
*sip
, const char *publications
)
7114 uri
= sip_uri_self(sip
);
7115 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
,
7119 tmp
= get_contact(sip
);
7120 hdr
= g_strdup_printf("Contact: %s\r\n"
7121 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
7123 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
7132 send_publish_category_initial(struct sipe_account_data
*sip
)
7134 gchar
*pub_device
= sipe_publish_get_category_device(sip
);
7136 gchar
*publications
;
7138 g_free(sip
->status
);
7139 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
); /* our initial state */
7141 pub_machine
= sipe_publish_get_category_state_machine(sip
);
7142 publications
= g_strdup_printf("%s%s",
7144 pub_machine
? pub_machine
: "");
7146 g_free(pub_machine
);
7148 send_presence_publish(sip
, publications
);
7149 g_free(publications
);
7153 send_presence_category_publish(struct sipe_account_data
*sip
,
7156 gchar
*pub_state
= sipe_is_user_state(sip
) ?
7157 sipe_publish_get_category_state_user(sip
) :
7158 sipe_publish_get_category_state_machine(sip
);
7159 gchar
*pub_note
= sipe_publish_get_category_note(sip
, note
, "personal");
7160 gchar
*publications
;
7162 if (!pub_state
&& !pub_note
) {
7163 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7167 publications
= g_strdup_printf("%s%s",
7168 pub_state
? pub_state
: "",
7169 pub_note
? pub_note
: "");
7174 send_presence_publish(sip
, publications
);
7175 g_free(publications
);
7179 * Publishes self status
7180 * based on own calendar information.
7185 publish_calendar_status_self(struct sipe_account_data
*sip
)
7187 struct sipe_cal_event
* event
= NULL
;
7188 gchar
*pub_cal_working_hours
= NULL
;
7189 gchar
*pub_cal_free_busy
= NULL
;
7190 gchar
*pub_calendar
= NULL
;
7191 gchar
*pub_calendar2
= NULL
;
7192 gchar
*pub_oof_note
= NULL
;
7193 const gchar
*oof_note
;
7196 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7200 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7201 if (sip
->ews
->cal_events
) {
7202 event
= sipe_cal_get_event(sip
->ews
->cal_events
, time(NULL
));
7206 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7208 char *desc
= sipe_cal_event_describe(event
);
7209 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc
? desc
: "");
7215 OOF publish, Busy clean
7217 OOF clean, Busy publish
7219 OOF clean, Busy clean
7221 if (event
&& event
->cal_status
== SIPE_CAL_OOF
) {
7222 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, event
, sip
->ews
->email
, SIPE_CAL_OOF
);
7223 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7224 } else if (event
&& event
->cal_status
== SIPE_CAL_BUSY
) {
7225 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_OOF
);
7226 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, event
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7228 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_OOF
);
7229 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7232 if ((oof_note
= sipe_ews_get_oof_note(sip
->ews
))) {
7233 pub_oof_note
= sipe_publish_get_category_note(sip
, oof_note
, "OOF");
7236 pub_cal_working_hours
= sipe_publish_get_category_cal_working_hours(sip
);
7237 pub_cal_free_busy
= sipe_publish_get_category_cal_free_busy(sip
);
7239 if (!pub_cal_working_hours
&& !pub_cal_free_busy
&& !pub_calendar
&& !pub_calendar2
&& !pub_oof_note
) {
7240 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7242 gchar
*publications
= g_strdup_printf("%s%s%s%s%s",
7243 pub_cal_working_hours
? pub_cal_working_hours
: "",
7244 pub_cal_free_busy
? pub_cal_free_busy
: "",
7245 pub_calendar
? pub_calendar
: "",
7246 pub_calendar2
? pub_calendar2
: "",
7247 pub_oof_note
? pub_oof_note
: "");
7249 send_presence_publish(sip
, publications
);
7250 g_free(publications
);
7253 g_free(pub_cal_working_hours
);
7254 g_free(pub_cal_free_busy
);
7255 g_free(pub_calendar
);
7256 g_free(pub_calendar2
);
7257 g_free(pub_oof_note
);
7259 /* repeat scheduling */
7260 sipe_sched_calendar_status_self_publish(sip
, time(NULL
));
7263 static void send_presence_status(struct sipe_account_data
*sip
)
7265 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
7267 if (!status
) return;
7269 note
= purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
);
7270 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7271 purple_status_get_id(status
) ? purple_status_get_id(status
) : "",
7272 sipe_is_user_state(sip
) ? "USER" : "MACHINE");
7273 purple_debug_info("sipe", "send_presence_status: note: '%s'\n", note
? note
: "");
7276 send_presence_category_publish(sip
, note
);
7278 send_presence_soap(sip
, FALSE
);
7282 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
7284 gboolean found
= FALSE
;
7285 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,msg
->method
);
7286 if (msg
->response
== 0) { /* request */
7287 if (!strcmp(msg
->method
, "MESSAGE")) {
7288 process_incoming_message(sip
, msg
);
7290 } else if (!strcmp(msg
->method
, "NOTIFY")) {
7291 purple_debug_info("sipe","send->process_incoming_notify\n");
7292 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
7294 } else if (!strcmp(msg
->method
, "BENOTIFY")) {
7295 purple_debug_info("sipe","send->process_incoming_benotify\n");
7296 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
7298 } else if (!strcmp(msg
->method
, "INVITE")) {
7299 process_incoming_invite(sip
, msg
);
7301 } else if (!strcmp(msg
->method
, "REFER")) {
7302 process_incoming_refer(sip
, msg
);
7304 } else if (!strcmp(msg
->method
, "OPTIONS")) {
7305 process_incoming_options(sip
, msg
);
7307 } else if (!strcmp(msg
->method
, "INFO")) {
7308 process_incoming_info(sip
, msg
);
7310 } else if (!strcmp(msg
->method
, "ACK")) {
7311 // ACK's don't need any response
7313 } else if (!strcmp(msg
->method
, "SUBSCRIBE")) {
7314 // LCS 2005 sends us these - just respond 200 OK
7316 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
7317 } else if (!strcmp(msg
->method
, "BYE")) {
7318 process_incoming_bye(sip
, msg
);
7321 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
7323 } else { /* response */
7324 struct transaction
*trans
= transactions_find(sip
, msg
);
7326 if (msg
->response
== 407) {
7327 gchar
*resend
, *auth
, *ptmp
;
7329 if (sip
->proxy
.retries
> 30) return;
7330 sip
->proxy
.retries
++;
7331 /* do proxy authentication */
7333 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
7335 fill_auth(ptmp
, &sip
->proxy
);
7336 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
7337 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
7338 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
7340 resend
= sipmsg_to_string(trans
->msg
);
7341 /* resend request */
7342 sendout_pkt(sip
->gc
, resend
);
7345 if (msg
->response
< 200) {
7346 /* ignore provisional response */
7347 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg
->response
);
7349 sip
->proxy
.retries
= 0;
7350 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
7351 if (msg
->response
== 401)
7353 sip
->registrar
.retries
++;
7357 sip
->registrar
.retries
= 0;
7359 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip
->cseq
);
7361 if (msg
->response
== 401) {
7362 gchar
*resend
, *auth
, *ptmp
;
7364 if (sip
->registrar
.retries
> 4) return;
7365 sip
->registrar
.retries
++;
7368 if (!purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
7370 ptmp
= sipmsg_find_auth_header(msg
, "NTLM");
7373 ptmp
= sipmsg_find_auth_header(msg
, "Kerberos");
7377 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\n", ptmp
);
7379 fill_auth(ptmp
, &sip
->registrar
);
7380 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
7381 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
7382 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
7384 //sipmsg_remove_header_now(trans->msg, "Authorization");
7385 //sipmsg_add_header(trans->msg, "Authorization", auth);
7387 resend
= sipmsg_to_string(trans
->msg
);
7388 /* resend request */
7389 sendout_pkt(sip
->gc
, resend
);
7394 if (trans
->callback
) {
7395 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\n");
7396 /* call the callback to process response*/
7397 (trans
->callback
)(sip
, msg
, trans
);
7400 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\n", sip
->cseq
);
7401 transactions_remove(sip
, trans
);
7407 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
7411 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", msg
->method
, msg
->response
);
7415 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
7424 /* according to the RFC remove CRLF at the beginning */
7425 while (*cur
== '\r' || *cur
== '\n') {
7428 if (cur
!= conn
->inbuf
) {
7429 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
7430 conn
->inbufused
= strlen(conn
->inbuf
);
7433 /* Received a full Header? */
7434 sip
->processing_input
= TRUE
;
7435 while (sip
->processing_input
&&
7436 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
7437 time_t currtime
= time(NULL
);
7440 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
7442 msg
= sipmsg_parse_header(conn
->inbuf
);
7445 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
7446 if (msg
&& restlen
>= msg
->bodylen
) {
7447 dummy
= g_malloc(msg
->bodylen
+ 1);
7448 memcpy(dummy
, cur
, msg
->bodylen
);
7449 dummy
[msg
->bodylen
] = '\0';
7451 cur
+= msg
->bodylen
;
7452 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
7453 conn
->inbufused
= strlen(conn
->inbuf
);
7456 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
7463 purple_debug_info("sipe", "body:\n%s", msg->body);
7466 // Verify the signature before processing it
7467 if (sip
->registrar
.gssapi_context
) {
7468 struct sipmsg_breakdown msgbd
;
7469 gchar
*signature_input_str
;
7472 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
7473 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
7475 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
7477 if (rspauth
!= NULL
) {
7478 if (!sip_sec_verify_signature(sip
->registrar
.gssapi_context
, signature_input_str
, rspauth
)) {
7479 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
7480 process_input_message(sip
, msg
);
7482 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid.\n");
7483 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
7484 sip
->gc
->wants_to_die
= TRUE
;
7486 } else if (msg
->response
== 401) {
7487 purple_connection_error(sip
->gc
, _("Wrong password"));
7488 sip
->gc
->wants_to_die
= TRUE
;
7490 g_free(signature_input_str
);
7493 sipmsg_breakdown_free(&msgbd
);
7495 process_input_message(sip
, msg
);
7502 static void sipe_udp_process(gpointer data
, gint source
,
7503 SIPE_UNUSED_PARAMETER PurpleInputCondition con
)
7505 PurpleConnection
*gc
= data
;
7506 struct sipe_account_data
*sip
= gc
->proto_data
;
7509 static char buffer
[65536];
7510 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
7511 time_t currtime
= time(NULL
);
7514 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime
), buffer
);
7515 msg
= sipmsg_parse_msg(buffer
);
7516 if (msg
) process_input_message(sip
, msg
);
7520 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
7522 struct sipe_account_data
*sip
= gc
->proto_data
;
7523 PurpleSslConnection
*gsc
= sip
->gsc
;
7525 purple_debug_error("sipe", "%s",debug
);
7526 purple_connection_error(gc
, msg
);
7528 /* Invalidate this connection. Next send will open a new one */
7530 connection_remove(sip
, gsc
->fd
);
7531 purple_ssl_close(gsc
);
7537 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
7538 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7540 PurpleConnection
*gc
= data
;
7541 struct sipe_account_data
*sip
;
7542 struct sip_connection
*conn
;
7544 gboolean firstread
= TRUE
;
7546 /* NOTE: This check *IS* necessary */
7547 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
7548 purple_ssl_close(gsc
);
7552 sip
= gc
->proto_data
;
7553 conn
= connection_find(sip
, gsc
->fd
);
7555 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7556 gc
->wants_to_die
= TRUE
;
7557 purple_connection_error(gc
, _("Connection not found. Please try to connect again"));
7561 /* Read all available data from the SSL connection */
7563 /* Increase input buffer size as needed */
7564 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
7565 conn
->inbuflen
+= SIMPLE_BUF_INC
;
7566 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
7567 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
7570 /* Try to read as much as there is space left in the buffer */
7571 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
7572 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
7574 if (len
< 0 && errno
== EAGAIN
) {
7575 /* Try again later */
7577 } else if (len
< 0) {
7578 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
7580 } else if (firstread
&& (len
== 0)) {
7581 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
7585 conn
->inbufused
+= len
;
7588 /* Equivalence indicates that there is possibly more data to read */
7589 } while (len
== readlen
);
7591 conn
->inbuf
[conn
->inbufused
] = '\0';
7592 process_input(sip
, conn
);
7596 static void sipe_input_cb(gpointer data
, gint source
,
7597 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7599 PurpleConnection
*gc
= data
;
7600 struct sipe_account_data
*sip
= gc
->proto_data
;
7602 struct sip_connection
*conn
= connection_find(sip
, source
);
7604 purple_debug_error("sipe", "Connection not found!\n");
7608 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
7609 conn
->inbuflen
+= SIMPLE_BUF_INC
;
7610 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
7613 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
7615 if (len
< 0 && errno
== EAGAIN
)
7617 else if (len
<= 0) {
7618 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7619 connection_remove(sip
, source
);
7620 if (sip
->fd
== source
) sip
->fd
= -1;
7624 conn
->inbufused
+= len
;
7625 conn
->inbuf
[conn
->inbufused
] = '\0';
7627 process_input(sip
, conn
);
7630 /* Callback for new connections on incoming TCP port */
7631 static void sipe_newconn_cb(gpointer data
, gint source
,
7632 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7634 PurpleConnection
*gc
= data
;
7635 struct sipe_account_data
*sip
= gc
->proto_data
;
7636 struct sip_connection
*conn
;
7638 int newfd
= accept(source
, NULL
, NULL
);
7640 conn
= connection_create(sip
, newfd
);
7642 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
7645 static void login_cb(gpointer data
, gint source
,
7646 SIPE_UNUSED_PARAMETER
const gchar
*error_message
)
7648 PurpleConnection
*gc
= data
;
7649 struct sipe_account_data
*sip
;
7650 struct sip_connection
*conn
;
7652 if (!PURPLE_CONNECTION_IS_VALID(gc
))
7660 purple_connection_error(gc
, _("Could not connect"));
7664 sip
= gc
->proto_data
;
7666 sip
->last_keepalive
= time(NULL
);
7668 conn
= connection_create(sip
, source
);
7672 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
7675 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
7676 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7678 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
7679 if (sip
== NULL
) return;
7684 static guint
sipe_ht_hash_nick(const char *nick
)
7686 char *lc
= g_utf8_strdown(nick
, -1);
7687 guint bucket
= g_str_hash(lc
);
7693 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
7695 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
7698 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
7700 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
7702 sip
->listen_data
= NULL
;
7704 if (listenfd
== -1) {
7705 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
7711 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
7712 sip
->listenfd
= sip
->fd
;
7714 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
7716 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
7720 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
,
7721 SIPE_UNUSED_PARAMETER
const char *error_message
)
7723 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
7725 sip
->query_data
= NULL
;
7727 if (!hosts
|| !hosts
->data
) {
7728 purple_connection_error(sip
->gc
, _("Could not resolve hostname"));
7732 hosts
= g_slist_remove(hosts
, hosts
->data
);
7733 g_free(sip
->serveraddr
);
7734 sip
->serveraddr
= hosts
->data
;
7735 hosts
= g_slist_remove(hosts
, hosts
->data
);
7737 hosts
= g_slist_remove(hosts
, hosts
->data
);
7738 g_free(hosts
->data
);
7739 hosts
= g_slist_remove(hosts
, hosts
->data
);
7742 /* create socket for incoming connections */
7743 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
7744 sipe_udp_host_resolved_listen_cb
, sip
);
7745 if (sip
->listen_data
== NULL
) {
7746 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
7751 static const struct sipe_service_data
*current_service
= NULL
;
7753 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
7754 PurpleSslErrorType error
,
7757 PurpleConnection
*gc
= data
;
7758 struct sipe_account_data
*sip
;
7760 /* If the connection is already disconnected, we don't need to do anything else */
7761 if (!PURPLE_CONNECTION_IS_VALID(gc
))
7764 sip
= gc
->proto_data
;
7765 current_service
= sip
->service_data
;
7766 if (current_service
) {
7767 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
7768 current_service
->transport
? current_service
->transport
: "NULL",
7769 current_service
->service
? current_service
->service
: "NULL");
7776 case PURPLE_SSL_CONNECT_FAILED
:
7777 purple_connection_error(gc
, _("Connection failed"));
7779 case PURPLE_SSL_HANDSHAKE_FAILED
:
7780 purple_connection_error(gc
, _("SSL handshake failed"));
7782 case PURPLE_SSL_CERTIFICATE_INVALID
:
7783 purple_connection_error(gc
, _("SSL certificate invalid"));
7789 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
7791 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
7792 PurpleProxyConnectData
*connect_data
;
7794 sip
->listen_data
= NULL
;
7796 sip
->listenfd
= listenfd
;
7797 if (sip
->listenfd
== -1) {
7798 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
7802 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
7803 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
7804 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
7805 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
7806 sipe_newconn_cb
, sip
->gc
);
7807 purple_debug_info("sipe", "connecting to %s port %d\n",
7808 sip
->realhostname
, sip
->realport
);
7809 /* open tcp connection to the server */
7810 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
7811 sip
->realport
, login_cb
, sip
->gc
);
7813 if (connect_data
== NULL
) {
7814 purple_connection_error(sip
->gc
, _("Could not create socket"));
7818 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
7820 PurpleAccount
*account
= sip
->account
;
7821 PurpleConnection
*gc
= sip
->gc
;
7824 port
= (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
7827 sip
->realhostname
= hostname
;
7828 sip
->realport
= port
;
7830 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
7833 /* TODO: is there a good default grow size? */
7834 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
7835 sip
->txbuf
= purple_circ_buffer_new(0);
7837 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
7839 if (!purple_ssl_is_supported()) {
7840 gc
->wants_to_die
= TRUE
;
7841 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
7845 purple_debug_info("sipe", "using SSL\n");
7847 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
7848 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
7849 if (sip
->gsc
== NULL
) {
7850 purple_connection_error(gc
, _("Could not create SSL context"));
7853 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
7855 purple_debug_info("sipe", "using UDP\n");
7857 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
7858 if (sip
->query_data
== NULL
) {
7859 purple_connection_error(gc
, _("Could not resolve hostname"));
7863 purple_debug_info("sipe", "using TCP\n");
7864 /* create socket for incoming connections */
7865 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
7866 sipe_tcp_connect_listen_cb
, sip
);
7867 if (sip
->listen_data
== NULL
) {
7868 purple_connection_error(gc
, _("Could not create listen socket"));
7874 /* Service list for autodection */
7875 static const struct sipe_service_data service_autodetect
[] = {
7876 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
7877 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
7878 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
7879 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
7883 /* Service list for SSL/TLS */
7884 static const struct sipe_service_data service_tls
[] = {
7885 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
7886 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
7890 /* Service list for TCP */
7891 static const struct sipe_service_data service_tcp
[] = {
7892 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
7893 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
7897 /* Service list for UDP */
7898 static const struct sipe_service_data service_udp
[] = {
7899 { "sip", "udp", SIPE_TRANSPORT_UDP
},
7903 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
7904 static void resolve_next_service(struct sipe_account_data
*sip
,
7905 const struct sipe_service_data
*start
)
7908 sip
->service_data
= start
;
7910 sip
->service_data
++;
7911 if (sip
->service_data
->service
== NULL
) {
7913 /* Try connecting to the SIP hostname directly */
7914 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
7915 if (sip
->auto_transport
) {
7916 // If SSL is supported, default to using it; OCS servers aren't configured
7917 // by default to accept TCP
7918 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
7919 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
7920 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
7923 hostname
= g_strdup(sip
->sipdomain
);
7924 create_connection(sip
, hostname
, 0);
7929 /* Try to resolve next service */
7930 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
7931 sip
->service_data
->transport
,
7936 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
7938 struct sipe_account_data
*sip
= data
;
7940 sip
->srv_query_data
= NULL
;
7942 /* find the host to connect to */
7944 gchar
*hostname
= g_strdup(resp
->hostname
);
7945 int port
= resp
->port
;
7946 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
7950 sip
->transport
= sip
->service_data
->type
;
7952 create_connection(sip
, hostname
, port
);
7954 resolve_next_service(sip
, NULL
);
7958 static void sipe_login(PurpleAccount
*account
)
7960 PurpleConnection
*gc
;
7961 struct sipe_account_data
*sip
;
7962 gchar
**signinname_login
, **userserver
;
7963 const char *transport
;
7966 const char *username
= purple_account_get_username(account
);
7967 gc
= purple_account_get_connection(account
);
7969 purple_debug_info("sipe", "sipe_login: username '%s'\n", username
);
7971 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
7972 gc
->wants_to_die
= TRUE
;
7973 purple_connection_error(gc
, _("SIP Exchange user name contains invalid characters"));
7977 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
7978 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
7979 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
7981 sip
->account
= account
;
7982 sip
->reregister_set
= FALSE
;
7983 sip
->reauthenticate_set
= FALSE
;
7984 sip
->subscribed
= FALSE
;
7985 sip
->subscribed_buddies
= FALSE
;
7986 sip
->initial_state_published
= FALSE
;
7988 /* username format: <username>,[<optional login>] */
7989 signinname_login
= g_strsplit(username
, ",", 2);
7990 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login
[0]);
7992 /* ensure that username format is name@domain */
7993 if (!strchr(signinname_login
[0], '@') || g_str_has_prefix(signinname_login
[0], "@") || g_str_has_suffix(signinname_login
[0], "@")) {
7994 g_strfreev(signinname_login
);
7995 gc
->wants_to_die
= TRUE
;
7996 purple_connection_error(gc
, _("User name should be a valid SIP URI\nExample: user@company.com"));
7999 sip
->username
= g_strdup(signinname_login
[0]);
8001 /* ensure that email format is name@domain if provided */
8002 email
= purple_account_get_string(sip
->account
, "email", NULL
);
8003 if (!is_empty(email
) &&
8004 (!strchr(email
, '@') || g_str_has_prefix(email
, "@") || g_str_has_suffix(email
, "@")))
8006 gc
->wants_to_die
= TRUE
;
8007 purple_connection_error(gc
, _("Email address should be valid if provided\nExample: user@company.com"));
8010 sip
->email
= !is_empty(email
) ? g_strdup(email
) : g_strdup(sip
->username
);
8012 /* login name specified? */
8013 if (signinname_login
[1] && strlen(signinname_login
[1])) {
8014 gchar
**domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
8015 gboolean has_domain
= domain_user
[1] != NULL
;
8016 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login
[1]);
8017 sip
->authdomain
= has_domain
? g_strdup(domain_user
[0]) : NULL
;
8018 sip
->authuser
= g_strdup(domain_user
[has_domain
? 1 : 0]);
8019 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8020 sip
->authdomain
? sip
->authdomain
: "", sip
->authuser
);
8021 g_strfreev(domain_user
);
8024 userserver
= g_strsplit(signinname_login
[0], "@", 2);
8025 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver
[0], userserver
[1]);
8026 purple_connection_set_display_name(gc
, userserver
[0]);
8027 sip
->sipdomain
= g_strdup(userserver
[1]);
8028 g_strfreev(userserver
);
8029 g_strfreev(signinname_login
);
8031 if (strchr(sip
->username
, ' ') != NULL
) {
8032 gc
->wants_to_die
= TRUE
;
8033 purple_connection_error(gc
, _("SIP Exchange user name contains whitespace"));
8037 sip
->password
= g_strdup(purple_connection_get_password(gc
));
8039 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
8040 sip
->our_publications
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8041 g_free
, (GDestroyNotify
)g_hash_table_destroy
);
8042 sip
->subscriptions
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8043 g_free
, (GDestroyNotify
)sipe_subscription_free
);
8045 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
8047 g_free(sip
->status
);
8048 sip
->status
= g_strdup(SIPE_STATUS_ID_UNKNOWN
);
8050 sip
->auto_transport
= FALSE
;
8051 transport
= purple_account_get_string(account
, "transport", "auto");
8052 userserver
= g_strsplit(purple_account_get_string(account
, "server", ""), ":", 2);
8053 if (userserver
[0]) {
8054 /* Use user specified server[:port] */
8058 port
= atoi(userserver
[1]);
8060 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8061 userserver
[0], port
);
8063 if (strcmp(transport
, "auto") == 0) {
8064 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
8065 } else if (strcmp(transport
, "tls") == 0) {
8066 sip
->transport
= SIPE_TRANSPORT_TLS
;
8067 } else if (strcmp(transport
, "tcp") == 0) {
8068 sip
->transport
= SIPE_TRANSPORT_TCP
;
8070 sip
->transport
= SIPE_TRANSPORT_UDP
;
8073 create_connection(sip
, g_strdup(userserver
[0]), port
);
8075 /* Server auto-discovery */
8076 if (strcmp(transport
, "auto") == 0) {
8077 sip
->auto_transport
= TRUE
;
8078 if (current_service
&& current_service
->transport
!= NULL
&& current_service
->service
!= NULL
){
8080 resolve_next_service(sip
, current_service
);
8082 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
8084 } else if (strcmp(transport
, "tls") == 0) {
8085 resolve_next_service(sip
, service_tls
);
8086 } else if (strcmp(transport
, "tcp") == 0) {
8087 resolve_next_service(sip
, service_tcp
);
8089 resolve_next_service(sip
, service_udp
);
8092 g_strfreev(userserver
);
8095 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
8097 connection_free_all(sip
);
8102 if (sip
->query_data
!= NULL
)
8103 purple_dnsquery_destroy(sip
->query_data
);
8104 sip
->query_data
= NULL
;
8106 if (sip
->srv_query_data
!= NULL
)
8107 purple_srv_cancel(sip
->srv_query_data
);
8108 sip
->srv_query_data
= NULL
;
8110 if (sip
->listen_data
!= NULL
)
8111 purple_network_listen_cancel(sip
->listen_data
);
8112 sip
->listen_data
= NULL
;
8114 if (sip
->gsc
!= NULL
)
8115 purple_ssl_close(sip
->gsc
);
8118 sipe_auth_free(&sip
->registrar
);
8119 sipe_auth_free(&sip
->proxy
);
8122 purple_circ_buffer_destroy(sip
->txbuf
);
8125 g_free(sip
->realhostname
);
8126 sip
->realhostname
= NULL
;
8128 g_free(sip
->server_version
);
8129 sip
->server_version
= NULL
;
8132 purple_input_remove(sip
->listenpa
);
8134 if (sip
->tx_handler
)
8135 purple_input_remove(sip
->tx_handler
);
8136 sip
->tx_handler
= 0;
8137 if (sip
->resendtimeout
)
8138 purple_timeout_remove(sip
->resendtimeout
);
8139 sip
->resendtimeout
= 0;
8140 if (sip
->timeouts
) {
8141 GSList
*entry
= sip
->timeouts
;
8143 struct scheduled_action
*sched_action
= entry
->data
;
8144 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
8145 purple_timeout_remove(sched_action
->timeout_handler
);
8146 if (sched_action
->destroy
) {
8147 (*sched_action
->destroy
)(sched_action
->payload
);
8149 g_free(sched_action
->name
);
8150 g_free(sched_action
);
8151 entry
= entry
->next
;
8154 g_slist_free(sip
->timeouts
);
8156 if (sip
->allow_events
) {
8157 GSList
*entry
= sip
->allow_events
;
8159 g_free(entry
->data
);
8160 entry
= entry
->next
;
8163 g_slist_free(sip
->allow_events
);
8165 if (sip
->containers
) {
8166 GSList
*entry
= sip
->containers
;
8168 free_container((struct sipe_container
*)entry
->data
);
8169 entry
= entry
->next
;
8172 g_slist_free(sip
->containers
);
8175 g_free(sip
->contact
);
8176 sip
->contact
= NULL
;
8178 g_free(sip
->regcallid
);
8179 sip
->regcallid
= NULL
;
8181 if (sip
->serveraddr
)
8182 g_free(sip
->serveraddr
);
8183 sip
->serveraddr
= NULL
;
8185 if (sip
->focus_factory_uri
)
8186 g_free(sip
->focus_factory_uri
);
8187 sip
->focus_factory_uri
= NULL
;
8190 sip
->processing_input
= FALSE
;
8193 sipe_ews_free(sip
->ews
);
8199 * A callback for g_hash_table_foreach_remove
8201 static gboolean
sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key
, gpointer buddy
,
8202 SIPE_UNUSED_PARAMETER gpointer user_data
)
8204 sipe_free_buddy((struct sipe_buddy
*) buddy
);
8206 /* We must return TRUE as the key/value have already been deleted */
8210 static void sipe_close(PurpleConnection
*gc
)
8212 struct sipe_account_data
*sip
= gc
->proto_data
;
8215 /* leave all conversations */
8216 sipe_session_close_all(sip
);
8217 sipe_session_remove_all(sip
);
8220 sip_csta_close(sip
);
8223 if (PURPLE_CONNECTION_IS_CONNECTED(sip
->gc
)) {
8224 /* unsubscribe all */
8225 g_hash_table_foreach(sip
->subscriptions
, sipe_unsubscribe_cb
, sip
);
8228 do_register_exp(sip
, 0);
8231 sipe_connection_cleanup(sip
);
8232 g_free(sip
->sipdomain
);
8233 g_free(sip
->username
);
8235 g_free(sip
->password
);
8236 g_free(sip
->authdomain
);
8237 g_free(sip
->authuser
);
8238 g_free(sip
->status
);
8241 g_hash_table_foreach_steal(sip
->buddies
, sipe_buddy_remove
, NULL
);
8242 g_hash_table_destroy(sip
->buddies
);
8243 g_hash_table_destroy(sip
->our_publications
);
8244 g_hash_table_destroy(sip
->user_state_publications
);
8245 g_hash_table_destroy(sip
->subscriptions
);
8248 GSList
*entry
= sip
->groups
;
8250 struct sipe_group
*group
= entry
->data
;
8251 g_free(group
->name
);
8253 entry
= entry
->next
;
8256 g_slist_free(sip
->groups
);
8258 if (sip
->our_publication_keys
) {
8259 GSList
*entry
= sip
->our_publication_keys
;
8261 g_free(entry
->data
);
8262 entry
= entry
->next
;
8265 g_slist_free(sip
->our_publication_keys
);
8267 while (sip
->transactions
)
8268 transactions_remove(sip
, sip
->transactions
->data
);
8270 g_free(gc
->proto_data
);
8271 gc
->proto_data
= NULL
;
8274 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
,
8275 SIPE_UNUSED_PARAMETER
void *user_data
)
8277 PurpleAccount
*acct
= purple_connection_get_account(gc
);
8278 char *id
= sip_uri_from_name((gchar
*)g_list_nth_data(row
, 0));
8279 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
8281 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
8282 purple_conversation_present(conv
);
8286 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
,
8287 SIPE_UNUSED_PARAMETER
void *user_data
)
8290 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
8291 g_list_nth_data(row
, 0), _("Other Contacts"), g_list_nth_data(row
, 1));
8294 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
8295 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
8297 PurpleNotifySearchResults
*results
;
8298 PurpleNotifySearchColumn
*column
;
8299 xmlnode
*searchResults
;
8301 int match_count
= 0;
8302 gboolean more
= FALSE
;
8305 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
8307 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
8308 if (!searchResults
) {
8309 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8313 results
= purple_notify_searchresults_new();
8315 if (results
== NULL
) {
8316 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8317 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results"), NULL
);
8319 xmlnode_free(searchResults
);
8323 column
= purple_notify_searchresults_column_new(_("User name"));
8324 purple_notify_searchresults_column_add(results
, column
);
8326 column
= purple_notify_searchresults_column_new(_("Name"));
8327 purple_notify_searchresults_column_add(results
, column
);
8329 column
= purple_notify_searchresults_column_new(_("Company"));
8330 purple_notify_searchresults_column_add(results
, column
);
8332 column
= purple_notify_searchresults_column_new(_("Country"));
8333 purple_notify_searchresults_column_add(results
, column
);
8335 column
= purple_notify_searchresults_column_new(_("Email"));
8336 purple_notify_searchresults_column_add(results
, column
);
8338 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
8341 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
8342 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
8343 g_strfreev(uri_parts
);
8345 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
8346 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
8347 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
8348 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
8350 purple_notify_searchresults_row_add(results
, row
);
8354 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
8355 char *data
= xmlnode_get_data_unescaped(mrow
);
8356 more
= (g_strcasecmp(data
, "true") == 0);
8360 secondary
= g_strdup_printf(
8361 dngettext(GETTEXT_PACKAGE
,
8362 "Found %d contact%s:",
8363 "Found %d contacts%s:", match_count
),
8364 match_count
, more
? _(" (more matched your query)") : "");
8366 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
8367 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
8368 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
8371 xmlnode_free(searchResults
);
8375 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
8377 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
8378 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
8382 PurpleRequestField
*field
= entries
->data
;
8383 const char *id
= purple_request_field_get_id(field
);
8384 const char *value
= purple_request_field_string_get_value(field
);
8386 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
8388 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
8389 } while ((entries
= g_list_next(entries
)) != NULL
);
8393 struct sipe_account_data
*sip
= gc
->proto_data
;
8394 gchar
*domain_uri
= sip_uri_from_name(sip
->sipdomain
);
8395 gchar
*query
= g_strjoinv(NULL
, attrs
);
8396 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
8397 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
8398 send_soap_request_with_cb(sip
, domain_uri
, body
,
8399 (TransCallback
) process_search_contact_response
, NULL
);
8408 static void sipe_show_find_contact(PurplePluginAction
*action
)
8410 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8411 PurpleRequestFields
*fields
;
8412 PurpleRequestFieldGroup
*group
;
8413 PurpleRequestField
*field
;
8415 fields
= purple_request_fields_new();
8416 group
= purple_request_field_group_new(NULL
);
8417 purple_request_fields_add_group(fields
, group
);
8419 field
= purple_request_field_string_new("givenName", _("First name"), NULL
, FALSE
);
8420 purple_request_field_group_add_field(group
, field
);
8421 field
= purple_request_field_string_new("sn", _("Last name"), NULL
, FALSE
);
8422 purple_request_field_group_add_field(group
, field
);
8423 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
8424 purple_request_field_group_add_field(group
, field
);
8425 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
8426 purple_request_field_group_add_field(group
, field
);
8428 purple_request_fields(gc
,
8430 _("Search for a contact"),
8431 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8433 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
8435 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
8438 static void sipe_show_about_plugin(PurplePluginAction
*action
)
8440 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8442 "<b><font size=\"+1\">Sipe " SIPE_VERSION
"</font></b><br/>"
8444 "A third-party plugin implementing extended version of SIP/SIMPLE used by various products:<br/>"
8445 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8446 "<li> - MS Office Communications Server 2007</li><br/>"
8447 "<li> - MS Live Communications Server 2005</li><br/>"
8448 "<li> - MS Live Communications Server 2003</li><br/>"
8449 "<li> - Reuters Messaging</li><br/>"
8451 "Home: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8452 "Support: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">Help Forum</a><br/>"
8453 "License: GPLv2+<br/>"
8455 "We support users in the following organizations to mention a few:<br/>"
8457 " - Reuters Messaging network<br/>"
8458 " - Deutsche Bank<br/>"
8459 " - Merrill Lynch<br/>"
8465 " - Alcatel-Lucent<br/>"
8468 "<b>Authors:</b><br/>"
8469 " - Anibal Avelar<br/>"
8470 " - Gabriel Burt<br/>"
8471 " - Stefan Becker<br/>"
8474 purple_notify_formatted(gc
, NULL
, " ", NULL
, txt
, NULL
, NULL
);
8477 static void sipe_republish_calendar(PurplePluginAction
*action
)
8479 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8480 struct sipe_account_data
*sip
= gc
->proto_data
;
8482 sipe_update_calendar(sip
);
8485 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER
const char *name
,
8489 struct sipe_publication
*publication
= value
;
8491 g_string_append_printf( str
,
8492 SIPE_PUB_XML_PUBLICATION_CLEAR
,
8493 publication
->category
,
8494 publication
->instance
,
8495 publication
->container
,
8496 publication
->version
,
8500 static void sipe_reset_status(PurplePluginAction
*action
)
8502 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8503 struct sipe_account_data
*sip
= gc
->proto_data
;
8505 if (sip
->ocs2007
) /* 2007+ */
8507 GString
* str
= g_string_new(NULL
);
8508 gchar
*publications
;
8510 if (!sip
->user_state_publications
|| g_hash_table_size(sip
->user_state_publications
) == 0) {
8511 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8515 g_hash_table_foreach(sip
->user_state_publications
, (GHFunc
)sipe_publish_get_cat_state_user_to_clear
, str
);
8516 publications
= g_string_free(str
, FALSE
);
8518 send_presence_publish(sip
, publications
);
8519 g_free(publications
);
8523 send_presence_soap0(sip
, FALSE
, TRUE
);
8527 GList
*sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
,
8530 PurpleConnection
*gc
= (PurpleConnection
*)context
;
8531 struct sipe_account_data
*sip
= gc
->proto_data
;
8533 PurplePluginAction
*act
;
8534 const char* calendar
= purple_account_get_string(sip
->account
, "calendar", "EXCH");
8536 act
= purple_plugin_action_new(_("About SIPE plugin"), sipe_show_about_plugin
);
8537 menu
= g_list_prepend(menu
, act
);
8539 act
= purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact
);
8540 menu
= g_list_prepend(menu
, act
);
8542 if (!strcmp(calendar
, "EXCH")) {
8543 act
= purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar
);
8544 menu
= g_list_prepend(menu
, act
);
8547 act
= purple_plugin_action_new(_("Reset status"), sipe_reset_status
);
8548 menu
= g_list_prepend(menu
, act
);
8550 menu
= g_list_reverse(menu
);
8555 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection
*gc
)
8559 static gboolean
sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
8565 static gboolean
sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
8571 static char *sipe_status_text(PurpleBuddy
*buddy
)
8573 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
8574 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
8575 const char *status_id
= purple_status_get_id(status
);
8576 struct sipe_account_data
*sip
= (struct sipe_account_data
*)buddy
->account
->gc
->proto_data
;
8577 struct sipe_buddy
*sbuddy
;
8580 if (sip
) //happens on pidgin exit
8582 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
8584 const char *activity_str
= sbuddy
->activity
?
8586 !strcmp(status_id
, SIPE_STATUS_ID_DND
) || !strcmp(status_id
, SIPE_STATUS_ID_BRB
) ?
8587 purple_status_get_name(status
) : NULL
;
8589 if (activity_str
&& sbuddy
->note
)
8591 text
= g_strdup_printf("%s - %s", activity_str
, sbuddy
->note
);
8593 else if (activity_str
)
8595 text
= g_strdup(activity_str
);
8599 text
= g_strdup(sbuddy
->note
);
8607 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, SIPE_UNUSED_PARAMETER gboolean full
)
8609 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
8610 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
8611 struct sipe_account_data
*sip
;
8612 struct sipe_buddy
*sbuddy
;
8614 gboolean is_oof_note
= FALSE
;
8615 char *activity
= NULL
;
8616 char *calendar
= NULL
;
8617 char *meeting_subject
= NULL
;
8618 char *meeting_location
= NULL
;
8620 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
8621 if (sip
) //happens on pidgin exit
8623 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
8626 note
= sbuddy
->note
;
8627 is_oof_note
= sbuddy
->is_oof_note
;
8628 activity
= sbuddy
->activity
;
8629 calendar
= sipe_cal_get_description(sbuddy
);
8630 meeting_subject
= sbuddy
->meeting_subject
;
8631 meeting_location
= sbuddy
->meeting_location
;
8636 if (purple_presence_is_online(presence
))
8638 const char *status_str
= activity
? activity
: purple_status_get_name(status
);
8640 purple_notify_user_info_add_pair(user_info
, _("Status"), status_str
);
8642 if (purple_presence_is_online(presence
) &&
8643 !is_empty(calendar
))
8645 purple_notify_user_info_add_pair(user_info
, _("Calendar"), calendar
);
8648 if (!is_empty(meeting_location
))
8650 purple_notify_user_info_add_pair(user_info
, _("Meeting in"), meeting_location
);
8652 if (!is_empty(meeting_subject
))
8654 purple_notify_user_info_add_pair(user_info
, _("Meeting about"), meeting_subject
);
8659 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy
->name
, note
);
8661 purple_notify_user_info_add_pair(user_info
, is_oof_note
? _("Out of office note") : _("Note"), note
);
8666 #if PURPLE_VERSION_CHECK(2,5,0)
8668 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount
*account
)
8671 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
8672 g_hash_table_insert(table
, "login_label", (gpointer
)_("user@company.com"));
8677 static PurpleBuddy
*
8678 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
8681 const gchar
*server_alias
, *email
;
8682 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
8684 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
8686 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
8688 server_alias
= purple_buddy_get_server_alias(buddy
);
8690 purple_blist_server_alias_buddy(clone
, server_alias
);
8693 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
8695 purple_blist_node_set_string(&clone
->node
, EMAIL_PROP
, email
);
8698 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
8700 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
8705 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
8707 PurpleBuddy
*buddy
, *b
;
8708 PurpleConnection
*gc
;
8709 PurpleGroup
* group
= purple_find_group(group_name
);
8711 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
8713 buddy
= (PurpleBuddy
*)node
;
8715 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
8716 gc
= purple_account_get_connection(buddy
->account
);
8718 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
8720 purple_blist_add_buddy_clone(group
, buddy
);
8723 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
8727 sipe_buddy_menu_chat_new_cb(PurpleBuddy
*buddy
)
8729 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8731 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy
->name
);
8733 /* 2007+ conference */
8736 sipe_conf_add(sip
, buddy
->name
);
8738 else /* 2005- multiparty chat */
8740 gchar
*self
= sip_uri_self(sip
);
8741 struct sip_session
*session
;
8743 session
= sipe_session_add_chat(sip
);
8744 session
->chat_title
= sipe_chat_get_name(session
->callid
);
8745 session
->roster_manager
= g_strdup(self
);
8747 session
->conv
= serv_got_joined_chat(buddy
->account
->gc
, session
->chat_id
, session
->chat_title
);
8748 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session
->conv
), self
);
8749 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
), self
, NULL
, PURPLE_CBFLAGS_NONE
, FALSE
);
8750 sipe_invite(sip
, session
, buddy
->name
, NULL
, NULL
, FALSE
);
8757 sipe_is_election_finished(struct sip_session
*session
)
8759 gboolean res
= TRUE
;
8761 SIPE_DIALOG_FOREACH
{
8762 if (dialog
->election_vote
== 0) {
8766 } SIPE_DIALOG_FOREACH_END
;
8769 session
->is_voting_in_progress
= FALSE
;
8775 sipe_election_start(struct sipe_account_data
*sip
,
8776 struct sip_session
*session
)
8778 int election_timeout
;
8780 if (session
->is_voting_in_progress
) {
8781 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
8784 session
->is_voting_in_progress
= TRUE
;
8786 session
->bid
= rand();
8788 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session
->bid
);
8790 SIPE_DIALOG_FOREACH
{
8791 /* reset election_vote for each chat participant */
8792 dialog
->election_vote
= 0;
8794 /* send RequestRM to each chat participant*/
8795 sipe_send_election_request_rm(sip
, dialog
, session
->bid
);
8796 } SIPE_DIALOG_FOREACH_END
;
8798 election_timeout
= 15; /* sec */
8799 sipe_schedule_action("<+election-result>", election_timeout
, sipe_election_result
, NULL
, sip
, session
);
8803 * @param who a URI to whom to invite to chat
8806 sipe_invite_to_chat(struct sipe_account_data
*sip
,
8807 struct sip_session
*session
,
8811 if (session
->focus_uri
)
8813 sipe_invite_conf(sip
, session
, who
);
8815 else /* a multi-party chat */
8817 gchar
*self
= sip_uri_self(sip
);
8818 if (session
->roster_manager
) {
8819 if (!strcmp(session
->roster_manager
, self
)) {
8820 sipe_invite(sip
, session
, who
, NULL
, NULL
, FALSE
);
8822 sipe_refer(sip
, session
, who
);
8825 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
8827 session
->pending_invite_queue
= slist_insert_unique_sorted(
8828 session
->pending_invite_queue
, g_strdup(who
), (GCompareFunc
)strcmp
);
8830 sipe_election_start(sip
, session
);
8837 sipe_process_pending_invite_queue(struct sipe_account_data
*sip
,
8838 struct sip_session
*session
)
8841 GSList
*entry
= session
->pending_invite_queue
;
8844 invitee
= entry
->data
;
8845 sipe_invite_to_chat(sip
, session
, invitee
);
8846 entry
= session
->pending_invite_queue
= g_slist_remove(session
->pending_invite_queue
, invitee
);
8852 sipe_election_result(struct sipe_account_data
*sip
,
8855 struct sip_session
*session
= (struct sip_session
*)sess
;
8857 gboolean has_won
= TRUE
;
8859 if (session
->roster_manager
) {
8860 purple_debug_info("sipe",
8861 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session
->roster_manager
);
8865 session
->is_voting_in_progress
= FALSE
;
8867 SIPE_DIALOG_FOREACH
{
8868 if (dialog
->election_vote
< 0) {
8870 rival
= dialog
->with
;
8873 } SIPE_DIALOG_FOREACH_END
;
8876 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
8878 session
->roster_manager
= sip_uri_self(sip
);
8880 SIPE_DIALOG_FOREACH
{
8881 /* send SetRM to each chat participant*/
8882 sipe_send_election_set_rm(sip
, dialog
);
8883 } SIPE_DIALOG_FOREACH_END
;
8885 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival
);
8889 sipe_process_pending_invite_queue(sip
, session
);
8893 * For 2007+ conference only.
8896 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy
*buddy
, const char *chat_title
)
8898 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8899 struct sip_session
*session
;
8901 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy
->name
);
8902 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title
);
8904 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
8906 sipe_conf_modify_user_role(sip
, session
, buddy
->name
);
8910 * For 2007+ conference only.
8913 sipe_buddy_menu_chat_remove_cb(PurpleBuddy
*buddy
, const char *chat_title
)
8915 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8916 struct sip_session
*session
;
8918 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy
->name
);
8919 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title
);
8921 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
8923 sipe_conf_delete_user(sip
, session
, buddy
->name
);
8927 sipe_buddy_menu_chat_invite_cb(PurpleBuddy
*buddy
, char *chat_title
)
8929 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8930 struct sip_session
*session
;
8932 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy
->name
);
8933 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title
);
8935 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
8937 sipe_invite_to_chat(sip
, session
, buddy
->name
);
8941 sipe_buddy_menu_make_call_cb(PurpleBuddy
*buddy
, const char *phone
)
8943 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
8945 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy
->name
);
8947 char *tel_uri
= sip_to_tel_uri(phone
);
8949 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri
? tel_uri
: "");
8950 sip_csta_make_call(sip
, tel_uri
);
8957 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
8960 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
8962 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
8965 char *mailto
= g_strdup_printf("mailto:%s", email
);
8966 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
8970 char *const parmList
[] = {"xdg-email", mailto
, NULL
};
8971 if ((pid
= fork()) == -1)
8973 purple_debug_info("sipe", "fork() error\n");
8977 execvp(parmList
[0], parmList
);
8978 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
8986 //@TODO resolve env variable %WINDIR% first
8987 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
8990 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
8999 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
9004 * A menu which appear when right-clicking on buddy in contact list.
9007 sipe_buddy_menu(PurpleBuddy
*buddy
)
9009 PurpleBlistNode
*g_node
;
9010 PurpleGroup
*group
, *gr_parent
;
9011 PurpleMenuAction
*act
;
9013 GList
*menu_groups
= NULL
;
9014 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9017 const char *phone_disp_str
;
9018 gchar
*self
= sip_uri_self(sip
);
9020 SIPE_SESSION_FOREACH
{
9021 if (g_ascii_strcasecmp(self
, buddy
->name
) && session
->chat_title
&& session
->conv
)
9023 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session
->conv
), buddy
->name
))
9025 PurpleConvChatBuddyFlags flags
;
9026 PurpleConvChatBuddyFlags flags_us
;
9028 flags
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), buddy
->name
);
9029 flags_us
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), self
);
9030 if (session
->focus_uri
9031 && PURPLE_CBFLAGS_OP
!= (flags
& PURPLE_CBFLAGS_OP
) /* Not conf OP */
9032 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
9034 gchar
*label
= g_strdup_printf(_("Make leader of '%s'"), session
->chat_title
);
9035 act
= purple_menu_action_new(label
,
9036 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb
),
9037 session
->chat_title
, NULL
);
9039 menu
= g_list_prepend(menu
, act
);
9042 if (session
->focus_uri
9043 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
9045 gchar
*label
= g_strdup_printf(_("Remove from '%s'"), session
->chat_title
);
9046 act
= purple_menu_action_new(label
,
9047 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb
),
9048 session
->chat_title
, NULL
);
9050 menu
= g_list_prepend(menu
, act
);
9055 if (!session
->focus_uri
9056 || (session
->focus_uri
&& !session
->locked
))
9058 gchar
*label
= g_strdup_printf(_("Invite to '%s'"), session
->chat_title
);
9059 act
= purple_menu_action_new(label
,
9060 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb
),
9061 session
->chat_title
, NULL
);
9063 menu
= g_list_prepend(menu
, act
);
9067 } SIPE_SESSION_FOREACH_END
;
9069 act
= purple_menu_action_new(_("New chat"),
9070 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb
),
9072 menu
= g_list_prepend(menu
, act
);
9074 if (sip
->csta
&& !sip
->csta
->line_status
) {
9077 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_PROP
);
9078 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_DISPLAY_PROP
);
9080 gchar
*label
= g_strdup_printf(_("Work %s"),
9081 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9082 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9086 menu
= g_list_prepend(menu
, act
);
9090 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_MOBILE_PROP
);
9091 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_MOBILE_DISPLAY_PROP
);
9093 gchar
*label
= g_strdup_printf(_("Mobile %s"),
9094 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9095 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9099 menu
= g_list_prepend(menu
, act
);
9103 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_HOME_PROP
);
9104 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_HOME_DISPLAY_PROP
);
9106 gchar
*label
= g_strdup_printf(_("Home %s"),
9107 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9108 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9112 menu
= g_list_prepend(menu
, act
);
9116 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_OTHER_PROP
);
9117 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_OTHER_DISPLAY_PROP
);
9119 gchar
*label
= g_strdup_printf(_("Other %s"),
9120 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9121 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9125 menu
= g_list_prepend(menu
, act
);
9129 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_CUSTOM1_PROP
);
9130 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_CUSTOM1_DISPLAY_PROP
);
9132 gchar
*label
= g_strdup_printf(_("Custom1 %s"),
9133 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9134 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9138 menu
= g_list_prepend(menu
, act
);
9142 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
9144 act
= purple_menu_action_new(_("Send email..."),
9145 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
9147 menu
= g_list_prepend(menu
, act
);
9150 gr_parent
= purple_buddy_get_group(buddy
);
9151 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
9152 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
9155 group
= (PurpleGroup
*)g_node
;
9156 if (group
== gr_parent
)
9159 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
9162 act
= purple_menu_action_new(purple_group_get_name(group
),
9163 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
9165 menu_groups
= g_list_prepend(menu_groups
, act
);
9167 menu_groups
= g_list_reverse(menu_groups
);
9169 act
= purple_menu_action_new(_("Copy to"),
9172 menu
= g_list_prepend(menu
, act
);
9173 menu
= g_list_reverse(menu
);
9180 sipe_conf_modify_lock(PurpleChat
*chat
, gboolean locked
)
9182 struct sipe_account_data
*sip
= chat
->account
->gc
->proto_data
;
9183 struct sip_session
*session
;
9185 session
= sipe_session_find_chat_by_title(sip
, (gchar
*)g_hash_table_lookup(chat
->components
, "channel"));
9186 sipe_conf_modify_conference_lock(sip
, session
, locked
);
9190 sipe_chat_menu_unlock_cb(PurpleChat
*chat
)
9192 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9193 sipe_conf_modify_lock(chat
, FALSE
);
9197 sipe_chat_menu_lock_cb(PurpleChat
*chat
)
9199 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9200 sipe_conf_modify_lock(chat
, TRUE
);
9204 sipe_chat_menu(PurpleChat
*chat
)
9206 PurpleMenuAction
*act
;
9207 PurpleConvChatBuddyFlags flags_us
;
9209 struct sipe_account_data
*sip
= chat
->account
->gc
->proto_data
;
9210 struct sip_session
*session
;
9213 session
= sipe_session_find_chat_by_title(sip
, (gchar
*)g_hash_table_lookup(chat
->components
, "channel"));
9214 if (!session
) return NULL
;
9216 self
= sip_uri_self(sip
);
9217 flags_us
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), self
);
9219 if (session
->focus_uri
9220 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
9222 if (session
->locked
) {
9223 act
= purple_menu_action_new(_("Unlock"),
9224 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb
),
9226 menu
= g_list_prepend(menu
, act
);
9228 act
= purple_menu_action_new(_("Lock"),
9229 PURPLE_CALLBACK(sipe_chat_menu_lock_cb
),
9231 menu
= g_list_prepend(menu
, act
);
9235 menu
= g_list_reverse(menu
);
9242 sipe_blist_node_menu(PurpleBlistNode
*node
)
9244 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
9245 return sipe_buddy_menu((PurpleBuddy
*) node
);
9246 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
9247 return sipe_chat_menu((PurpleChat
*)node
);
9254 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
9256 char *uri
= trans
->payload
->data
;
9258 PurpleNotifyUserInfo
*info
;
9259 PurpleBuddy
*pbuddy
= NULL
;
9260 struct sipe_buddy
*sbuddy
;
9261 const char *alias
= NULL
;
9262 char *device_name
= NULL
;
9263 char *server_alias
= NULL
;
9264 char *phone_number
= NULL
;
9268 if (!sip
) return FALSE
;
9270 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri
, sip
->username
);
9272 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri
);
9273 alias
= purple_buddy_get_local_alias(pbuddy
);
9275 //will query buddy UA's capabilities and send answer to log
9276 sipe_options_request(sip
, uri
);
9278 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
9280 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
9283 info
= purple_notify_user_info_new();
9285 if (msg
->response
!= 200) {
9286 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
9288 xmlnode
*searchResults
;
9291 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
9292 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
9293 if (!searchResults
) {
9294 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9295 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
9297 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
9298 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
9299 phone_number
= g_strdup(xmlnode_get_attrib(mrow
, "phone"));
9301 /* For 2007 system we will take this from ContactCard -
9302 * it has cleaner tel: URIs at least
9304 if (!sip
->ocs2007
) {
9305 char *tel_uri
= sip_to_tel_uri(phone_number
);
9306 /* trims its parameters, so call first */
9307 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, server_alias
);
9308 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
9309 sipe_update_user_info(sip
, uri
, PHONE_PROP
, tel_uri
);
9310 sipe_update_user_info(sip
, uri
, PHONE_DISPLAY_PROP
, phone_number
);
9314 if (server_alias
&& strlen(server_alias
) > 0) {
9315 purple_notify_user_info_add_pair(info
, _("Display name"), server_alias
);
9317 if ((value
= xmlnode_get_attrib(mrow
, "title")) && strlen(value
) > 0) {
9318 purple_notify_user_info_add_pair(info
, _("Job title"), value
);
9320 if ((value
= xmlnode_get_attrib(mrow
, "office")) && strlen(value
) > 0) {
9321 purple_notify_user_info_add_pair(info
, _("Office"), value
);
9323 if (phone_number
&& strlen(phone_number
) > 0) {
9324 purple_notify_user_info_add_pair(info
, _("Business phone"), phone_number
);
9326 if ((value
= xmlnode_get_attrib(mrow
, "company")) && strlen(value
) > 0) {
9327 purple_notify_user_info_add_pair(info
, _("Company"), value
);
9329 if ((value
= xmlnode_get_attrib(mrow
, "city")) && strlen(value
) > 0) {
9330 purple_notify_user_info_add_pair(info
, _("City"), value
);
9332 if ((value
= xmlnode_get_attrib(mrow
, "state")) && strlen(value
) > 0) {
9333 purple_notify_user_info_add_pair(info
, _("State"), value
);
9335 if ((value
= xmlnode_get_attrib(mrow
, "country")) && strlen(value
) > 0) {
9336 purple_notify_user_info_add_pair(info
, _("Country"), value
);
9338 if (email
&& strlen(email
) > 0) {
9339 purple_notify_user_info_add_pair(info
, _("Email address"), email
);
9343 xmlnode_free(searchResults
);
9346 purple_notify_user_info_add_section_break(info
);
9348 if (!server_alias
|| !strcmp("", server_alias
)) {
9349 g_free(server_alias
);
9350 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
9352 purple_notify_user_info_add_pair(info
, _("Display name"), server_alias
);
9356 /* present alias if it differs from server alias */
9357 if (alias
&& (!server_alias
|| strcmp(alias
, server_alias
)))
9359 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
9362 if (!email
|| !strcmp("", email
)) {
9364 email
= g_strdup(purple_blist_node_get_string(&pbuddy
->node
, EMAIL_PROP
));
9366 purple_notify_user_info_add_pair(info
, _("Email address"), email
);
9370 site
= purple_blist_node_get_string(&pbuddy
->node
, SITE_PROP
);
9372 purple_notify_user_info_add_pair(info
, _("Site"), site
);
9376 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
9379 /* show a buddy's user info in a nice dialog box */
9380 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
9381 uri
, /* buddy's URI */
9383 NULL
, /* callback called when dialog closed */
9384 NULL
); /* userdata for callback */
9386 g_free(phone_number
);
9387 g_free(server_alias
);
9389 g_free(device_name
);
9395 * AD search first, LDAP based
9397 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
9399 struct sipe_account_data
*sip
= gc
->proto_data
;
9400 gchar
*domain_uri
= sip_uri_from_name(sip
->sipdomain
);
9401 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
9402 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
9403 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
9405 payload
->destroy
= g_free
;
9406 payload
->data
= g_strdup(username
);
9408 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
9409 send_soap_request_with_cb(sip
, domain_uri
, body
,
9410 (TransCallback
) process_get_info_response
, payload
);
9416 static PurplePlugin
*my_protocol
= NULL
;
9418 static PurplePluginProtocolInfo prpl_info
=
9420 OPT_PROTO_CHAT_TOPIC
,
9421 NULL
, /* user_splits */
9422 NULL
, /* protocol_options */
9423 NO_BUDDY_ICONS
, /* icon_spec */
9424 sipe_list_icon
, /* list_icon */
9425 NULL
, /* list_emblems */
9426 sipe_status_text
, /* status_text */
9427 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
9428 sipe_status_types
, /* away_states */
9429 sipe_blist_node_menu
, /* blist_node_menu */
9430 NULL
, /* chat_info */
9431 NULL
, /* chat_info_defaults */
9432 sipe_login
, /* login */
9433 sipe_close
, /* close */
9434 sipe_im_send
, /* send_im */
9435 NULL
, /* set_info */ // TODO maybe
9436 sipe_send_typing
, /* send_typing */
9437 sipe_get_info
, /* get_info */
9438 sipe_set_status
, /* set_status */
9439 sipe_set_idle
, /* set_idle */
9440 NULL
, /* change_passwd */
9441 sipe_add_buddy
, /* add_buddy */
9442 NULL
, /* add_buddies */
9443 sipe_remove_buddy
, /* remove_buddy */
9444 NULL
, /* remove_buddies */
9445 sipe_add_permit
, /* add_permit */
9446 sipe_add_deny
, /* add_deny */
9447 sipe_add_deny
, /* rem_permit */
9448 sipe_add_permit
, /* rem_deny */
9449 dummy_permit_deny
, /* set_permit_deny */
9450 NULL
, /* join_chat */
9451 NULL
, /* reject_chat */
9452 NULL
, /* get_chat_name */
9453 sipe_chat_invite
, /* chat_invite */
9454 sipe_chat_leave
, /* chat_leave */
9455 NULL
, /* chat_whisper */
9456 sipe_chat_send
, /* chat_send */
9457 sipe_keep_alive
, /* keepalive */
9458 NULL
, /* register_user */
9459 NULL
, /* get_cb_info */ // deprecated
9460 NULL
, /* get_cb_away */ // deprecated
9461 sipe_alias_buddy
, /* alias_buddy */
9462 sipe_group_buddy
, /* group_buddy */
9463 sipe_rename_group
, /* rename_group */
9464 NULL
, /* buddy_free */
9465 sipe_convo_closed
, /* convo_closed */
9466 purple_normalize_nocase
, /* normalize */
9467 NULL
, /* set_buddy_icon */
9468 sipe_remove_group
, /* remove_group */
9469 NULL
, /* get_cb_real_name */ // TODO?
9470 NULL
, /* set_chat_topic */
9471 NULL
, /* find_blist_chat */
9472 NULL
, /* roomlist_get_list */
9473 NULL
, /* roomlist_cancel */
9474 NULL
, /* roomlist_expand_category */
9475 NULL
, /* can_receive_file */
9476 NULL
, /* send_file */
9477 NULL
, /* new_xfer */
9478 NULL
, /* offline_message */
9479 NULL
, /* whiteboard_prpl_ops */
9480 sipe_send_raw
, /* send_raw */
9481 NULL
, /* roomlist_room_serialize */
9482 NULL
, /* unregister_user */
9483 NULL
, /* send_attention */
9484 NULL
, /* get_attention_types */
9485 #if !PURPLE_VERSION_CHECK(2,5,0)
9486 /* Backward compatibility when compiling against 2.4.x API */
9487 (void (*)(void)) /* _purple_reserved4 */
9489 sizeof(PurplePluginProtocolInfo
), /* struct_size */
9490 #if PURPLE_VERSION_CHECK(2,5,0)
9491 sipe_get_account_text_table
, /* get_account_text_table */
9492 #if PURPLE_VERSION_CHECK(2,6,0)
9493 NULL
, /* initiate_media */
9494 NULL
, /* get_media_caps */
9500 static PurplePluginInfo info
= {
9501 PURPLE_PLUGIN_MAGIC
,
9502 PURPLE_MAJOR_VERSION
,
9503 PURPLE_MINOR_VERSION
,
9504 PURPLE_PLUGIN_PROTOCOL
, /**< type */
9505 NULL
, /**< ui_requirement */
9507 NULL
, /**< dependencies */
9508 PURPLE_PRIORITY_DEFAULT
, /**< priority */
9509 "prpl-sipe", /**< id */
9510 "Office Communicator", /**< name */
9511 SIPE_VERSION
, /**< version */
9512 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9513 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9514 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9515 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9516 "Gabriel Burt <gburt@novell.com>, " /**< author */
9517 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9518 "pier11 <pier11@operamail.com>", /**< author */
9519 "http://sipe.sourceforge.net/", /**< homepage */
9520 sipe_plugin_load
, /**< load */
9521 sipe_plugin_unload
, /**< unload */
9522 sipe_plugin_destroy
, /**< destroy */
9523 NULL
, /**< ui_info */
9524 &prpl_info
, /**< extra_info */
9533 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
9537 entry
= prpl_info
.protocol_options
;
9539 purple_account_option_destroy(entry
->data
);
9540 entry
= g_list_delete_link(entry
, entry
);
9542 prpl_info
.protocol_options
= NULL
;
9544 entry
= prpl_info
.user_splits
;
9546 purple_account_user_split_destroy(entry
->data
);
9547 entry
= g_list_delete_link(entry
, entry
);
9549 prpl_info
.user_splits
= NULL
;
9552 static void init_plugin(PurplePlugin
*plugin
)
9554 PurpleAccountUserSplit
*split
;
9555 PurpleAccountOption
*option
;
9560 purple_debug_info(PACKAGE
, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
9561 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s\n",
9562 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
9563 textdomain(GETTEXT_PACKAGE
);
9566 purple_plugin_register(plugin
);
9568 split
= purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL
, ',');
9569 purple_account_user_split_set_reverse(split
, FALSE
);
9570 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
9572 option
= purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9573 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9575 option
= purple_account_option_list_new(_("Connection type"), "transport", NULL
);
9576 purple_account_option_add_list_item(option
, _("Auto"), "auto");
9577 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
9578 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
9579 purple_account_option_add_list_item(option
, _("UDP"), "udp");
9580 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9582 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9583 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9585 option
= purple_account_option_string_new(_("User Agent"), "useragent", "");
9586 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9589 option
= purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE
);
9590 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9592 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9593 * No login/password is taken into account if this option present,
9594 * instead used default credentials stored in OS.
9596 option
= purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE
);
9597 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9600 option
= purple_account_option_list_new(_("Calendar source"), "calendar", NULL
);
9601 purple_account_option_add_list_item(option
, _("Exchange 2007/2010"), "EXCH");
9602 purple_account_option_add_list_item(option
, _("None"), "NONE");
9603 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9605 /** Example: https://server.company.com/EWS/Exchange.asmx */
9606 option
= purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9607 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9609 option
= purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9610 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9612 /** Example: DOMAIN\user or user@company.com */
9613 option
= purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9614 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9616 option
= purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9617 purple_account_option_set_masked(option
, TRUE
);
9618 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9620 my_protocol
= plugin
;
9623 PURPLE_INIT_PLUGIN(sipe
, init_plugin
, info
);