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_("Busy-Idle") , 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;
795 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
797 if (purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
798 sip
->registrar
.type
= AUTH_TYPE_KERBEROS
;
803 buf
= auth_header(sip
, &sip
->registrar
, msg
);
804 sipmsg_add_header_now_pos(msg
, "Proxy-Authorization", buf
, 5);
807 purple_debug_info("sipe", "not adding auth header to msg w/ method %s\n", method
);
811 void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
812 const char *text
, const char *body
)
816 GString
*outstr
= g_string_new("");
817 struct sipe_account_data
*sip
= gc
->proto_data
;
820 const gchar
*keepers
[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL
};
822 /* Can return NULL! */
823 contact
= get_contact(sip
);
825 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");
899 if (!call_id
|| !cseq
) {
900 purple_debug(PURPLE_DEBUG_ERROR
, "sipe", "transaction_find: no Call-ID or CSeq!\n");
904 key
= g_strdup_printf("<%s><%s>", call_id
, cseq
);
905 while (transactions
) {
906 trans
= transactions
->data
;
907 if (!g_strcasecmp(trans
->key
, key
)) {
911 transactions
= transactions
->next
;
919 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
920 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
921 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
923 struct sipe_account_data
*sip
= gc
->proto_data
;
924 const char *addh
= "";
927 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
928 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
929 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
930 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
931 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
932 gchar
*route
= g_strdup("");
933 gchar
*epid
= get_epid(sip
);
934 int cseq
= dialog
? ++dialog
->cseq
: 1 /* as Call-Id is new in this case */;
935 struct transaction
*trans
= NULL
;
937 if (dialog
&& dialog
->routes
)
939 GSList
*iter
= dialog
->routes
;
944 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
946 iter
= g_slist_next(iter
);
950 if (!ourtag
&& !dialog
) {
954 if (!strcmp(method
, "REGISTER")) {
955 if (sip
->regcallid
) {
957 callid
= g_strdup(sip
->regcallid
);
959 sip
->regcallid
= g_strdup(callid
);
964 if (addheaders
) addh
= addheaders
;
966 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
967 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
968 "From: <sip:%s>%s%s;epid=%s\r\n"
969 "To: <%s>%s%s%s%s\r\n"
970 "Max-Forwards: 70\r\n"
975 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
977 dialog
&& dialog
->request
? dialog
->request
: url
,
978 TRANSPORT_DESCRIPTOR
,
979 purple_network_get_my_ip(-1),
981 branch
? ";branch=" : "",
982 branch
? branch
: "",
984 ourtag
? ";tag=" : "",
985 ourtag
? ourtag
: "",
988 theirtag
? ";tag=" : "",
989 theirtag
? theirtag
: "",
990 theirepid
? ";epid=" : "",
991 theirepid
? theirepid
: "",
994 sipe_get_useragent(sip
),
998 body
? (gsize
) strlen(body
) : 0,
1002 //printf ("parsing msg buf:\n%s\n\n", buf);
1003 msg
= sipmsg_parse_msg(buf
);
1014 sign_outgoing_message (msg
, sip
, method
);
1016 buf
= sipmsg_to_string (msg
);
1018 /* add to ongoing transactions */
1019 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1020 if (strcmp(method
, "ACK")) {
1021 trans
= transactions_add_buf(sip
, msg
, tc
);
1025 sendout_pkt(gc
, buf
);
1032 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1035 send_soap_request_with_cb(struct sipe_account_data
*sip
,
1038 TransCallback callback
,
1039 struct transaction_payload
*payload
)
1041 gchar
*from
= from0
? g_strdup(from0
) : sip_uri_self(sip
);
1042 gchar
*contact
= get_contact(sip
);
1043 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1044 "Content-Type: application/SOAP+xml\r\n",contact
);
1046 struct transaction
*trans
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1047 trans
->payload
= payload
;
1054 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1056 send_soap_request_with_cb(sip
, NULL
, body
, NULL
, NULL
);
1059 static char *get_contact_register(struct sipe_account_data
*sip
)
1061 char *epid
= get_epid(sip
);
1062 char *uuid
= generateUUIDfromEPID(epid
);
1063 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
);
1069 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1077 if (!sip
->sipdomain
) return;
1079 uri
= sip_uri_from_name(sip
->sipdomain
);
1080 expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
1081 to
= sip_uri_self(sip
);
1082 contact
= get_contact_register(sip
);
1083 hdr
= g_strdup_printf("Contact: %s\r\n"
1084 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1085 "Event: registration\r\n"
1086 "Allow-Events: presence\r\n"
1087 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1088 "%s", contact
, expires
);
1092 sip
->registerstatus
= 1;
1094 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1095 process_register_response
);
1102 static void do_register_cb(struct sipe_account_data
*sip
,
1103 SIPE_UNUSED_PARAMETER
void *unused
)
1105 do_register_exp(sip
, -1);
1106 sip
->reregister_set
= FALSE
;
1109 static void do_register(struct sipe_account_data
*sip
)
1111 do_register_exp(sip
, -1);
1115 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1117 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1118 send_soap_request(sip
, body
);
1123 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1126 purple_debug_info("sipe", "Authorizing contact %s\n", who
);
1128 purple_debug_info("sipe", "Blocking contact %s\n", who
);
1131 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1135 void sipe_auth_user_cb(void * data
)
1137 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1140 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1145 void sipe_deny_user_cb(void * data
)
1147 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1150 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1155 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1157 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1158 sipe_contact_allow_deny(sip
, name
, TRUE
);
1162 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1164 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1165 sipe_contact_allow_deny(sip
, name
, FALSE
);
1169 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1171 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1172 sipe_contact_set_acl(sip, name, "");
1176 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1180 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1181 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1183 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| !strcmp(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1185 watchers
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1186 if (!watchers
) return;
1188 for (watcher
= xmlnode_get_child(watchers
, "watcher"); watcher
; watcher
= xmlnode_get_next_twin(watcher
)) {
1189 gchar
* remote_user
= g_strdup(xmlnode_get_attrib(watcher
, "uri"));
1190 gchar
* alias
= g_strdup(xmlnode_get_attrib(watcher
, "displayName"));
1191 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1193 // TODO pull out optional displayName to pass as alias
1195 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1196 job
->who
= remote_user
;
1198 purple_account_request_authorization(
1212 xmlnode_free(watchers
);
1217 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1219 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1220 if (!purple_group
) {
1221 purple_group
= purple_group_new(group
->name
);
1222 purple_blist_add_group(purple_group
, NULL
);
1226 group
->purple_group
= purple_group
;
1227 sip
->groups
= g_slist_append(sip
->groups
, group
);
1228 purple_debug_info("sipe", "added group %s (id %d)\n", group
->name
, group
->id
);
1230 purple_debug_info("sipe", "did not add group %s\n", group
->name
? group
->name
: "");
1234 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1236 struct sipe_group
*group
;
1242 entry
= sip
->groups
;
1244 group
= entry
->data
;
1245 if (group
->id
== id
) {
1248 entry
= entry
->next
;
1253 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, const gchar
* name
)
1255 struct sipe_group
*group
;
1257 if (!sip
|| !name
) {
1261 entry
= sip
->groups
;
1263 group
= entry
->data
;
1264 if (!strcmp(group
->name
, name
)) {
1267 entry
= entry
->next
;
1273 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1276 purple_debug_info("sipe", "Renaming group %s to %s\n", group
->name
, name
);
1277 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1278 send_soap_request(sip
, body
);
1280 g_free(group
->name
);
1281 group
->name
= g_strdup(name
);
1285 * Only appends if no such value already stored.
1288 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1289 GSList
* res
= list
;
1290 if (!g_slist_find_custom(list
, data
, func
)) {
1291 res
= g_slist_insert_sorted(list
, data
, func
);
1297 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1298 return group1
->id
- group2
->id
;
1302 * Returns string like "2 4 7 8" - group ids buddy belong to.
1305 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1308 //creating array from GList, converting int to gchar*
1309 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1310 GSList
*entry
= buddy
->groups
;
1312 struct sipe_group
* group
= entry
->data
;
1313 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1314 entry
= entry
->next
;
1318 res
= g_strjoinv(" ", ids_arr
);
1319 g_strfreev(ids_arr
);
1324 * Sends buddy update to server
1327 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1329 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1330 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1332 if (buddy
&& purple_buddy
) {
1333 const char *alias
= purple_buddy_get_alias(purple_buddy
);
1335 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1336 purple_debug_info("sipe", "Saving buddy %s with alias %s and groups %s\n", who
, alias
, groups
);
1338 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1339 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1341 send_soap_request(sip
, body
);
1347 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
1349 if (msg
->response
== 200) {
1350 struct sipe_group
*group
;
1351 struct group_user_context
*ctx
= trans
->payload
->data
;
1355 struct sipe_buddy
*buddy
;
1357 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
1362 node
= xmlnode_get_descendant(xml
, "Body", "addGroup", "groupID", NULL
);
1368 group_id
= xmlnode_get_data(node
);
1374 group
= g_new0(struct sipe_group
, 1);
1375 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1377 group
->name
= g_strdup(ctx
->group_name
);
1379 sipe_group_add(sip
, group
);
1381 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1383 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1386 sipe_group_set_user(sip
, ctx
->user_name
);
1394 static void sipe_group_context_destroy(gpointer data
)
1396 struct group_user_context
*ctx
= data
;
1397 g_free(ctx
->group_name
);
1398 g_free(ctx
->user_name
);
1402 static void sipe_group_create (struct sipe_account_data
*sip
, const gchar
*name
, const gchar
* who
)
1404 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1405 struct group_user_context
*ctx
= g_new0(struct group_user_context
, 1);
1407 ctx
->group_name
= g_strdup(name
);
1408 ctx
->user_name
= g_strdup(who
);
1409 payload
->destroy
= sipe_group_context_destroy
;
1410 payload
->data
= ctx
;
1412 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1413 send_soap_request_with_cb(sip
, NULL
, body
, process_add_group_response
, payload
);
1418 * Data structure for scheduled actions
1421 struct scheduled_action
{
1424 * Format is <Event>[<Data>...]
1425 * Example: <presence><sip:user@domain.com> or <registration>
1428 guint timeout_handler
;
1429 gboolean repetitive
;
1431 GDestroyNotify destroy
;
1432 struct sipe_account_data
*sip
;
1438 * Should return FALSE if repetitive action is not needed
1440 static gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1443 purple_debug_info("sipe", "sipe_scheduled_exec: executing\n");
1444 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1445 purple_debug_info("sipe", "sip->timeouts count:%d after removal\n",g_slist_length(sched_action
->sip
->timeouts
));
1446 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1447 ret
= sched_action
->repetitive
;
1448 if (sched_action
->destroy
) {
1449 (*sched_action
->destroy
)(sched_action
->payload
);
1451 g_free(sched_action
->name
);
1452 g_free(sched_action
);
1457 * Kills action timer effectively cancelling
1460 * @param name of action
1462 static void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, const gchar
*name
)
1466 if (!sip
->timeouts
|| !name
) return;
1468 entry
= sip
->timeouts
;
1470 struct scheduled_action
*sched_action
= entry
->data
;
1471 if(!strcmp(sched_action
->name
, name
)) {
1472 GSList
*to_delete
= entry
;
1473 entry
= entry
->next
;
1474 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1475 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
1476 purple_timeout_remove(sched_action
->timeout_handler
);
1477 if (sched_action
->destroy
) {
1478 (*sched_action
->destroy
)(sched_action
->payload
);
1480 g_free(sched_action
->name
);
1481 g_free(sched_action
);
1483 entry
= entry
->next
;
1489 sipe_schedule_action0(const gchar
*name
,
1493 GDestroyNotify destroy
,
1494 struct sipe_account_data
*sip
,
1497 struct scheduled_action
*sched_action
;
1499 /* Make sure each action only exists once */
1500 sipe_cancel_scheduled_action(sip
, name
);
1502 purple_debug_info("sipe","scheduling action %s timeout:%d(%s)\n", name
, timeout
, isSeconds
? "sec" : "msec");
1503 sched_action
= g_new0(struct scheduled_action
, 1);
1504 sched_action
->repetitive
= FALSE
;
1505 sched_action
->name
= g_strdup(name
);
1506 sched_action
->action
= action
;
1507 sched_action
->destroy
= destroy
;
1508 sched_action
->sip
= sip
;
1509 sched_action
->payload
= payload
;
1510 sched_action
->timeout_handler
= isSeconds
? purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
) :
1511 purple_timeout_add(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1512 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1513 purple_debug_info("sipe", "sip->timeouts count:%d after addition\n",g_slist_length(sip
->timeouts
));
1517 sipe_schedule_action(const gchar
*name
,
1520 GDestroyNotify destroy
,
1521 struct sipe_account_data
*sip
,
1524 sipe_schedule_action0(name
, timeout
, TRUE
, action
, destroy
, sip
, payload
);
1528 * Same as sipe_schedule_action() but timeout is in milliseconds.
1531 sipe_schedule_action_msec(const gchar
*name
,
1534 GDestroyNotify destroy
,
1535 struct sipe_account_data
*sip
,
1538 sipe_schedule_action0(name
, timeout
, FALSE
, action
, destroy
, sip
, payload
);
1542 sipe_sched_calendar_status_update(struct sipe_account_data
*sip
,
1543 time_t calculate_from
);
1546 sipe_get_availability_by_status(const char* sipe_status_id
, char** activity_token
);
1549 sipe_get_status_by_availability(int avail
,
1553 sipe_set_purple_account_status_and_note(const PurpleAccount
*account
,
1554 const char *status_id
,
1555 const char *message
,
1556 time_t do_not_publish
[]);
1559 sipe_apply_calendar_status(struct sipe_account_data
*sip
,
1560 struct sipe_buddy
*sbuddy
,
1561 const char *status_id
)
1563 time_t cal_avail_since
;
1564 int cal_status
= sipe_cal_get_status(sbuddy
, time(NULL
), &cal_avail_since
);
1566 gchar
*self_uri
= sip_uri_self(sip
);
1568 if (!sbuddy
) return;
1570 if (cal_status
< SIPE_CAL_NO_DATA
) {
1571 purple_debug_info("sipe", "update_calendar_status_cb: cal_status : %d for %s\n", cal_status
, sbuddy
->name
);
1572 purple_debug_info("sipe", "update_calendar_status_cb: cal_avail_since : %s", asctime(localtime(&cal_avail_since
)));
1575 /* scheduled Cal update call */
1577 status_id
= sbuddy
->last_non_cal_status_id
;
1578 g_free(sbuddy
->activity
);
1579 sbuddy
->activity
= g_strdup(sbuddy
->last_non_cal_activity
);
1582 /* adjust to calendar status */
1583 if (cal_status
!= SIPE_CAL_NO_DATA
) {
1584 purple_debug_info("sipe", "update_calendar_status_cb: user_avail_since: %s", asctime(localtime(&sbuddy
->user_avail_since
)));
1586 if (cal_status
== SIPE_CAL_BUSY
1587 && cal_avail_since
> sbuddy
->user_avail_since
1588 && 6500 >= sipe_get_availability_by_status(status_id
, NULL
))
1590 status_id
= SIPE_STATUS_ID_BUSY
;
1591 g_free(sbuddy
->activity
);
1592 sbuddy
->activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING
));
1594 avail
= sipe_get_availability_by_status(status_id
, NULL
);
1596 purple_debug_info("sipe", "update_calendar_status_cb: activity_since : %s", asctime(localtime(&sbuddy
->activity_since
)));
1597 if (cal_avail_since
> sbuddy
->activity_since
) {
1598 if (cal_status
== SIPE_CAL_OOF
1599 && avail
>= 15000) /* 12000 in 2007 */
1601 g_free(sbuddy
->activity
);
1602 sbuddy
->activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF
));
1607 /* then set status_id actually */
1608 purple_debug_info("sipe", "sipe_got_user_status: to %s for %s\n", status_id
? status_id
: "", sbuddy
->name
? sbuddy
->name
: "" );
1609 purple_prpl_got_user_status(sip
->account
, sbuddy
->name
, status_id
, NULL
);
1611 /* set our account state to the one in roaming (including calendar info) */
1612 if (sip
->initial_state_published
&& !strcmp(sbuddy
->name
, self_uri
)) {
1613 if (!strcmp(status_id
, SIPE_STATUS_ID_OFFLINE
)) {
1614 status_id
= g_strdup(SIPE_STATUS_ID_INVISIBLE
); /* not not let offline status switch us off */
1617 purple_debug_info("sipe", "sipe_got_user_status: switch to '%s' for the account\n", sip
->status
);
1618 sipe_set_purple_account_status_and_note(sip
->account
, status_id
, sip
->note
, sip
->do_not_publish
);
1624 sipe_got_user_status(struct sipe_account_data
*sip
,
1626 const char *status_id
)
1628 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
1630 if (!sbuddy
) return;
1632 /* Check if on 2005 system contact's calendar,
1633 * then set/preserve it.
1635 if (!sip
->ocs2007
) {
1636 sipe_apply_calendar_status(sip
, sbuddy
, status_id
);
1638 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
1643 update_calendar_status_cb(SIPE_UNUSED_PARAMETER
char *name
,
1644 struct sipe_buddy
*sbuddy
,
1645 struct sipe_account_data
*sip
)
1647 sipe_apply_calendar_status(sip
, sbuddy
, NULL
);
1651 * Updates contact's status
1652 * based on their calendar information.
1654 * Applicability: 2005 systems
1657 update_calendar_status(struct sipe_account_data
*sip
)
1659 purple_debug_info("sipe", "update_calendar_status() started.\n");
1660 g_hash_table_foreach(sip
->buddies
, (GHFunc
)update_calendar_status_cb
, (gpointer
)sip
);
1662 /* repeat scheduling */
1663 sipe_sched_calendar_status_update(sip
, time(NULL
) + 3*60 /* 3 min */);
1667 * Schedules process of contacts' status update
1668 * based on their calendar information.
1669 * Should be scheduled to the beginning of every
1670 * 15 min interval, like:
1671 * 13:00, 13:15, 13:30, 13:45, etc.
1673 * Applicability: 2005 systems
1676 sipe_sched_calendar_status_update(struct sipe_account_data
*sip
,
1677 time_t calculate_from
)
1679 int interval
= 15*60;
1680 /** start of the beginning of closest 15 min interval. */
1681 time_t next_start
= ((time_t)((int)((int)calculate_from
)/interval
+ 1)*interval
);
1683 purple_debug_info("sipe", "sipe_sched_calendar_status_update: calculate_from time: %s",
1684 asctime(localtime(&calculate_from
)));
1685 purple_debug_info("sipe", "sipe_sched_calendar_status_update: next start time : %s",
1686 asctime(localtime(&next_start
)));
1688 sipe_schedule_action("<+2005-cal-status>",
1689 (int)(next_start
- time(NULL
)),
1690 (Action
)update_calendar_status
,
1697 * Schedules process of self status publish
1698 * based on own calendar information.
1699 * Should be scheduled to the beginning of every
1700 * 15 min interval, like:
1701 * 13:00, 13:15, 13:30, 13:45, etc.
1703 * Applicability: 2007+ systems
1706 sipe_sched_calendar_status_self_publish(struct sipe_account_data
*sip
,
1707 time_t calculate_from
)
1709 int interval
= 5*60;
1710 /** start of the beginning of closest 5 min interval. */
1711 time_t next_start
= ((time_t)((int)((int)calculate_from
)/interval
+ 1)*interval
);
1713 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1714 asctime(localtime(&calculate_from
)));
1715 purple_debug_info("sipe", "sipe_sched_calendar_status_self_publish: next start time : %s",
1716 asctime(localtime(&next_start
)));
1718 sipe_schedule_action("<+2007-cal-status>",
1719 (int)(next_start
- time(NULL
)),
1720 (Action
)publish_calendar_status_self
,
1726 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1728 /** Should be g_free()'d
1731 sipe_get_subscription_key(gchar
*event
,
1736 if (is_empty(event
)) return NULL
;
1738 if (event
&& !g_ascii_strcasecmp(event
, "presence")) {
1739 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1740 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, with
);
1742 /* @TODO drop participated buddies' just_added flag */
1744 /* Subscription is identified by <event> key */
1745 key
= g_strdup_printf("<%s>", event
);
1751 gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
1752 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
1754 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
1755 gchar
*event
= sipmsg_find_header(msg
, "Event");
1758 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1760 struct sipmsg
*request_msg
= trans
->msg
;
1761 event
= sipmsg_find_header(request_msg
, "Event");
1764 key
= sipe_get_subscription_key(event
, with
);
1766 /* 200 OK; 481 Call Leg Does Not Exist */
1767 if (key
&& (msg
->response
== 200 || msg
->response
== 481)) {
1768 if (g_hash_table_lookup(sip
->subscriptions
, key
)) {
1769 g_hash_table_remove(sip
->subscriptions
, key
);
1770 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key
);
1774 /* create/store subscription dialog if not yet */
1775 if (msg
->response
== 200) {
1776 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
1777 gchar
*cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
1780 struct sip_subscription
*subscription
= g_new0(struct sip_subscription
, 1);
1781 g_hash_table_insert(sip
->subscriptions
, g_strdup(key
), subscription
);
1783 subscription
->dialog
.callid
= g_strdup(callid
);
1784 subscription
->dialog
.cseq
= atoi(cseq
);
1785 subscription
->dialog
.with
= g_strdup(with
);
1786 subscription
->event
= g_strdup(event
);
1787 sipe_dialog_parse(&subscription
->dialog
, msg
, TRUE
);
1789 purple_debug_info("sipe", "process_subscribe_response: subscription dialog added for: %s\n", key
);
1798 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1800 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1805 static void sipe_subscribe_resource_uri(const char *name
,
1806 SIPE_UNUSED_PARAMETER gpointer value
,
1807 gchar
**resources_uri
)
1809 gchar
*tmp
= *resources_uri
;
1810 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1814 static void sipe_subscribe_resource_uri_with_context(const char *name
, gpointer value
, gchar
**resources_uri
)
1816 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
1817 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
1818 gchar
*tmp
= *resources_uri
;
1820 if (sbuddy
) sbuddy
->just_added
= FALSE
; /* should be enought to include context one time */
1822 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp
, name
, context
);
1827 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1828 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1829 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1830 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1831 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1834 static void sipe_subscribe_presence_batched_to(struct sipe_account_data
*sip
, gchar
*resources_uri
, gchar
*to
)
1837 gchar
*contact
= get_contact(sip
);
1840 gchar
*require
= "";
1842 gchar
*autoextend
= "";
1843 gchar
*content_type
;
1844 struct sip_dialog
*dialog
;
1847 require
= ", categoryList";
1848 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1849 content_type
= "application/msrtc-adrl-categorylist+xml";
1850 content
= g_strdup_printf(
1851 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1852 "<action name=\"subscribe\" id=\"63792024\">\n"
1853 "<adhocList>\n%s</adhocList>\n"
1854 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1855 "<category name=\"calendarData\"/>\n"
1856 "<category name=\"contactCard\"/>\n"
1857 "<category name=\"note\"/>\n"
1858 "<category name=\"state\"/>\n"
1861 "</batchSub>", sip
->username
, resources_uri
);
1863 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1864 content_type
= "application/adrl+xml";
1865 content
= g_strdup_printf(
1866 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
1867 "<create xmlns=\"\">\n%s</create>\n"
1868 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
1870 g_free(resources_uri
);
1872 request
= g_strdup_printf(
1873 "Require: adhoclist%s\r\n"
1874 "Supported: eventlist\r\n"
1875 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
1876 "Supported: ms-piggyback-first-notify\r\n"
1877 "%sSupported: ms-benotify\r\n"
1878 "Proxy-Require: ms-benotify\r\n"
1879 "Event: presence\r\n"
1880 "Content-Type: %s\r\n"
1881 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
1884 /* subscribe to buddy presence */
1885 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1886 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, to
);
1887 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
1888 purple_debug_info("sipe", "sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
1890 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, dialog
, process_subscribe_response
);
1898 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
,
1899 SIPE_UNUSED_PARAMETER
void *unused
)
1901 gchar
*to
= sip_uri_self(sip
);
1902 gchar
*resources_uri
= g_strdup("");
1904 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
1906 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
1909 sipe_subscribe_presence_batched_to(sip
, resources_uri
, to
);
1912 struct presence_batched_routed
{
1917 static void sipe_subscribe_presence_batched_routed_free(void *payload
)
1919 struct presence_batched_routed
*data
= payload
;
1920 GSList
*buddies
= data
->buddies
;
1922 g_free(buddies
->data
);
1923 buddies
= buddies
->next
;
1925 g_slist_free(data
->buddies
);
1930 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data
*sip
, void *payload
)
1932 struct presence_batched_routed
*data
= payload
;
1933 GSList
*buddies
= data
->buddies
;
1934 gchar
*resources_uri
= g_strdup("");
1936 gchar
*tmp
= resources_uri
;
1937 resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, (char *) buddies
->data
);
1939 buddies
= buddies
->next
;
1941 sipe_subscribe_presence_batched_to(sip
, resources_uri
,
1942 g_strdup(data
->host
));
1946 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
1947 * The user sends a single SUBSCRIBE request to the subscribed contact.
1948 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
1952 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, void *buddy_name
)
1956 gchar
*to
= sip_uri((char *)buddy_name
);
1957 gchar
*tmp
= get_contact(sip
);
1959 gchar
*content
= NULL
;
1960 gchar
*autoextend
= "";
1961 gchar
*content_type
= "";
1962 struct sip_dialog
*dialog
;
1963 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, to
);
1964 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
1966 if (sbuddy
) sbuddy
->just_added
= FALSE
;
1969 content_type
= "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
1971 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1974 request
= g_strdup_printf(
1975 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
1976 "Supported: ms-piggyback-first-notify\r\n"
1977 "%s%sSupported: ms-benotify\r\n"
1978 "Proxy-Require: ms-benotify\r\n"
1979 "Event: presence\r\n"
1980 "Contact: %s\r\n", autoextend
, content_type
, tmp
);
1983 content
= g_strdup_printf(
1984 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
1985 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
1986 "<resource uri=\"%s\"%s\n"
1988 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1989 "<category name=\"calendarData\"/>\n"
1990 "<category name=\"contactCard\"/>\n"
1991 "<category name=\"note\"/>\n"
1992 "<category name=\"state\"/>\n"
1995 "</batchSub>", sip
->username
, to
, context
);
2000 /* subscribe to buddy presence */
2001 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2002 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, to
);
2003 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
2004 purple_debug_info("sipe", "sipe_subscribe_presence_single: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
2006 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, dialog
, process_subscribe_response
);
2014 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
2016 purple_debug_info("sipe", "sipe_set_status: status=%s\n", purple_status_get_id(status
));
2018 if (!purple_status_is_active(status
))
2022 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
2027 time_t now
= time(NULL
);
2028 const char *status_id
= purple_status_get_id(status
);
2029 const char *note
= purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
);
2030 sipe_activity activity
= sipe_get_activity_by_token(status_id
);
2031 gboolean do_not_publish
= ((now
- sip
->do_not_publish
[activity
]) <= 2);
2033 /* when other point of presence clears note, but we are keeping
2034 * state if OOF note.
2036 if (do_not_publish
&& !note
&& sip
->ews
&& sip
->ews
->oof_note
) {
2037 purple_debug_info("sipe", "sipe_set_status: enabling publication as OOF note keepers.\n");
2038 do_not_publish
= FALSE
;
2041 purple_debug_info("sipe", "sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d\n",
2042 status_id
, (int)sip
->do_not_publish
[activity
], (int)now
);
2044 sip
->do_not_publish
[activity
] = 0;
2045 purple_debug_info("sipe", "sipe_set_status: set: sip->do_not_publish[%s]=%d [0]\n",
2046 status_id
, (int)sip
->do_not_publish
[activity
]);
2050 purple_debug_info("sipe", "sipe_set_status: publication was switched off, exiting.\n");
2054 g_free(sip
->status
);
2055 sip
->status
= g_strdup(status_id
);
2057 /* hack to escape apostrof before comparison */
2058 tmp
= note
? purple_strreplace(note
, "'", "'") : NULL
;
2060 /* this will preserve OOF flag as well */
2061 if (!(tmp
&& sip
->note
&& !strcmp(tmp
, sip
->note
))) {
2062 sip
->is_oof_note
= FALSE
;
2064 sip
->note
= g_strdup(note
);
2065 sip
->note_since
= time(NULL
);
2069 /* schedule 2 sec to capture idle flag */
2070 action_name
= g_strdup_printf("<%s>", "+set-status");
2071 sipe_schedule_action(action_name
, SIPE_IDLE_SET_DELAY
, (Action
)send_presence_status
, NULL
, sip
, NULL
);
2072 g_free(action_name
);
2077 sipe_set_idle(PurpleConnection
* gc
,
2080 purple_debug_info("sipe", "sipe_set_idle: interval=%d\n", interval
);
2083 struct sipe_account_data
*sip
= gc
->proto_data
;
2086 sip
->idle_switch
= time(NULL
);
2087 purple_debug_info("sipe", "sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip
->idle_switch
))));
2093 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
,
2094 SIPE_UNUSED_PARAMETER
const char *alias
)
2096 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2097 sipe_group_set_user(sip
, name
);
2101 sipe_group_buddy(PurpleConnection
*gc
,
2103 const char *old_group_name
,
2104 const char *new_group_name
)
2106 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2107 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
2108 struct sipe_group
* old_group
= NULL
;
2109 struct sipe_group
* new_group
;
2111 purple_debug_info("sipe", "sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s\n",
2112 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
2114 if(!buddy
) { // buddy not in roaming list
2118 if (old_group_name
) {
2119 old_group
= sipe_group_find_by_name(sip
, old_group_name
);
2121 new_group
= sipe_group_find_by_name(sip
, new_group_name
);
2124 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
2125 purple_debug_info("sipe", "buddy %s removed from old group %s\n", who
, old_group_name
);
2129 sipe_group_create(sip
, new_group_name
, who
);
2131 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
2132 sipe_group_set_user(sip
, who
);
2136 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
2138 purple_debug_info("sipe", "sipe_add_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
2140 /* libpurple can call us with undefined buddy or group */
2141 if (buddy
&& group
) {
2142 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2144 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2145 gchar
*buddy_name
= g_ascii_strdown(buddy
->name
, -1);
2146 purple_blist_rename_buddy(buddy
, buddy_name
);
2149 /* Prepend sip: if needed */
2150 if (strncmp("sip:", buddy
->name
, 4)) {
2151 gchar
*buf
= sip_uri_from_name(buddy
->name
);
2152 purple_blist_rename_buddy(buddy
, buf
);
2156 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
2157 struct sipe_buddy
*b
= g_new0(struct sipe_buddy
, 1);
2158 purple_debug_info("sipe", "sipe_add_buddy: adding %s\n", buddy
->name
);
2159 b
->name
= g_strdup(buddy
->name
);
2160 b
->just_added
= TRUE
;
2161 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
2162 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
2163 /* @TODO should go to callback */
2164 sipe_subscribe_presence_single(sip
, b
->name
);
2166 purple_debug_info("sipe", "sipe_add_buddy: buddy %s already in internal list\n", buddy
->name
);
2171 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
2175 * We are calling g_hash_table_foreach_steal(). That means that no
2176 * key/value deallocation functions are called. Therefore the glib
2177 * hash code does not touch the key (buddy->name) or value (buddy)
2178 * of the to-be-deleted hash node at all. It follows that we
2180 * - MUST free the memory for the key ourselves and
2181 * - ARE allowed to do it in this function
2183 * Conclusion: glib must be broken on the Windows platform if sipe
2184 * crashes with SIGTRAP when closing. You'll have to live
2185 * with the memory leak until this is fixed.
2187 g_free(buddy
->name
);
2189 g_free(buddy
->activity
);
2190 g_free(buddy
->meeting_subject
);
2191 g_free(buddy
->meeting_location
);
2192 g_free(buddy
->note
);
2194 g_free(buddy
->cal_start_time
);
2195 g_free(buddy
->cal_free_busy_base64
);
2196 g_free(buddy
->cal_free_busy
);
2197 g_free(buddy
->last_non_cal_activity
);
2199 sipe_cal_free_working_hours(buddy
->cal_working_hours
);
2201 g_free(buddy
->device_name
);
2202 g_slist_free(buddy
->groups
);
2207 * Unassociates buddy from group first.
2208 * Then see if no groups left, removes buddy completely.
2209 * Otherwise updates buddy groups on server.
2211 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
2213 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2214 struct sipe_buddy
*b
;
2215 struct sipe_group
*g
= NULL
;
2217 purple_debug_info("sipe", "sipe_remove_buddy[CB]: buddy:%s group:%s\n", buddy
? buddy
->name
: "", group
? group
->name
: "");
2220 b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
2224 g
= sipe_group_find_by_name(sip
, group
->name
);
2228 b
->groups
= g_slist_remove(b
->groups
, g
);
2229 purple_debug_info("sipe", "buddy %s removed from group %s\n", buddy
->name
, g
->name
);
2232 if (g_slist_length(b
->groups
) < 1) {
2233 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
2234 sipe_cancel_scheduled_action(sip
, action_name
);
2235 g_free(action_name
);
2237 g_hash_table_remove(sip
->buddies
, buddy
->name
);
2240 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
2241 send_soap_request(sip
, body
);
2247 //updates groups on server
2248 sipe_group_set_user(sip
, b
->name
);
2254 sipe_rename_group(PurpleConnection
*gc
,
2255 const char *old_name
,
2257 SIPE_UNUSED_PARAMETER GList
*moved_buddies
)
2259 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2260 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, old_name
);
2262 sipe_group_rename(sip
, s_group
, group
->name
);
2264 purple_debug_info("sipe", "Cannot find group %s to rename\n", old_name
);
2269 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
2271 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2272 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
2275 purple_debug_info("sipe", "Deleting group %s\n", group
->name
);
2276 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
2277 send_soap_request(sip
, body
);
2280 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
2281 g_free(s_group
->name
);
2284 purple_debug_info("sipe", "Cannot find group %s to delete\n", group
->name
);
2288 /** All statuses need message attribute to pass Note */
2289 static GList
*sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount
*acc
)
2291 PurpleStatusType
*type
;
2292 GList
*types
= NULL
;
2294 /* Macros to reduce code repetition.
2295 Translators: noun */
2296 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2298 TRUE, user, FALSE, \
2299 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2301 types = g_list_append(types, type);
2304 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE
,
2310 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2311 sipe_activity_map
[SIPE_ACTIVITY_BUSY
].status_id
,
2312 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY
),
2315 /* Do Not Disturb */
2316 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2317 sipe_activity_map
[SIPE_ACTIVITY_DND
].status_id
,
2322 /* Goes first in the list as
2323 * purple picks the first status with the AWAY type
2326 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2332 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2333 sipe_activity_map
[SIPE_ACTIVITY_BRB
].status_id
,
2334 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB
),
2337 /* Appear Offline */
2338 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE
,
2343 /* Offline (not user settable) */
2344 SIPE_ADD_STATUS(PURPLE_STATUS_OFFLINE
,
2353 * A callback for g_hash_table_foreach
2356 sipe_buddy_subscribe_cb(char *buddy_name
,
2357 SIPE_UNUSED_PARAMETER
struct sipe_buddy
*buddy
,
2358 struct sipe_account_data
*sip
)
2360 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy_name
);
2361 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2362 guint time_range
= (g_hash_table_size(sip
->buddies
) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2363 guint timeout
= ((guint
) rand()) / (RAND_MAX
/ time_range
) + 1; /* random period within the range but never 0! */
2365 sipe_schedule_action_msec(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(buddy_name
));
2366 g_free(action_name
);
2370 * Removes entries from purple buddy list
2371 * that does not correspond ones in the roaming contact list.
2373 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
2374 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
2375 GSList
*entry
= buddies
;
2376 struct sipe_buddy
*buddy
;
2380 purple_debug_info("sipe", "sipe_cleanup_local_blist: overall %d Purple buddies (including clones)\n", g_slist_length(buddies
));
2381 purple_debug_info("sipe", "sipe_cleanup_local_blist: %d sipe buddies (unique)\n", g_hash_table_size(sip
->buddies
));
2384 g
= purple_buddy_get_group(b
);
2385 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
2387 gboolean in_sipe_groups
= FALSE
;
2388 GSList
*entry2
= buddy
->groups
;
2390 struct sipe_group
*group
= entry2
->data
;
2391 if (!strcmp(group
->name
, g
->name
)) {
2392 in_sipe_groups
= TRUE
;
2395 entry2
= entry2
->next
;
2397 if(!in_sipe_groups
) {
2398 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as not having this group in roaming list\n", b
->name
, g
->name
);
2399 purple_blist_remove_buddy(b
);
2402 purple_debug_info("sipe", "*** REMOVING %s from Purple group: %s as this buddy not in roaming list\n", b
->name
, g
->name
);
2403 purple_blist_remove_buddy(b
);
2405 entry
= entry
->next
;
2407 g_slist_free(buddies
);
2410 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2412 int len
= msg
->bodylen
;
2414 gchar
*tmp
= sipmsg_find_header(msg
, "Event");
2417 const gchar
*contacts_delta
;
2418 xmlnode
*group_node
;
2419 if (!tmp
|| strncmp(tmp
, "vnd-microsoft-roaming-contacts", 30)) {
2423 /* Convert the contact from XML to Purple Buddies */
2424 isc
= xmlnode_from_str(msg
->body
, len
);
2429 contacts_delta
= xmlnode_get_attrib(isc
, "deltaNum");
2430 if (contacts_delta
) {
2431 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2434 if (!strcmp(isc
->name
, "contactList")) {
2437 for (group_node
= xmlnode_get_child(isc
, "group"); group_node
; group_node
= xmlnode_get_next_twin(group_node
)) {
2438 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2439 const char *name
= xmlnode_get_attrib(group_node
, "name");
2441 if (!strncmp(name
, "~", 1)) {
2442 name
= _("Other Contacts");
2444 group
->name
= g_strdup(name
);
2445 group
->id
= (int)g_ascii_strtod(xmlnode_get_attrib(group_node
, "id"), NULL
);
2447 sipe_group_add(sip
, group
);
2450 // Make sure we have at least one group
2451 if (g_slist_length(sip
->groups
) == 0) {
2452 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2453 PurpleGroup
*purple_group
;
2454 group
->name
= g_strdup(_("Other Contacts"));
2456 purple_group
= purple_group_new(group
->name
);
2457 purple_blist_add_group(purple_group
, NULL
);
2458 sip
->groups
= g_slist_append(sip
->groups
, group
);
2461 /* Parse contacts */
2462 for (item
= xmlnode_get_child(isc
, "contact"); item
; item
= xmlnode_get_next_twin(item
)) {
2463 const gchar
*uri
= xmlnode_get_attrib(item
, "uri");
2464 const gchar
*name
= xmlnode_get_attrib(item
, "name");
2466 struct sipe_buddy
*buddy
= NULL
;
2468 gchar
**item_groups
;
2471 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2472 tmp
= sip_uri_from_name(uri
);
2473 buddy_name
= g_ascii_strdown(tmp
, -1);
2476 /* assign to group Other Contacts if nothing else received */
2477 tmp
= g_strdup(xmlnode_get_attrib(item
, "groups"));
2478 if(!tmp
|| !strcmp("", tmp
) ) {
2479 struct sipe_group
*group
= sipe_group_find_by_name(sip
, _("Other Contacts"));
2481 tmp
= group
? g_strdup_printf("%d", group
->id
) : g_strdup("1");
2483 item_groups
= g_strsplit(tmp
, " ", 0);
2486 while (item_groups
[i
]) {
2487 struct sipe_group
*group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
2489 // If couldn't find the right group for this contact, just put them in the first group we have
2490 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2491 group
= sip
->groups
->data
;
2494 if (group
!= NULL
) {
2495 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2497 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2498 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2500 purple_debug_info("sipe", "Created new buddy %s with alias %s\n", buddy_name
, uri
);
2503 if (!g_ascii_strcasecmp(uri
, purple_buddy_get_alias(b
))) {
2504 if (name
!= NULL
&& strlen(name
) != 0) {
2505 purple_blist_alias_buddy(b
, name
);
2507 purple_debug_info("sipe", "Replaced buddy %s alias with %s\n", buddy_name
, name
);
2512 buddy
= g_new0(struct sipe_buddy
, 1);
2513 buddy
->name
= g_strdup(b
->name
);
2514 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2517 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2519 purple_debug_info("sipe", "Added buddy %s to group %s\n", b
->name
, group
->name
);
2521 purple_debug_info("sipe", "No group found for contact %s! Unable to add to buddy list\n",
2526 } // while, contact groups
2527 g_strfreev(item_groups
);
2532 sipe_cleanup_local_blist(sip
);
2534 /* Add self-contact if not there yet. 2005 systems. */
2535 /* This will resemble subscription to roaming_self in 2007 systems */
2536 if (!sip
->ocs2007
) {
2537 gchar
*self_uri
= sip_uri_self(sip
);
2538 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, self_uri
);
2541 buddy
= g_new0(struct sipe_buddy
, 1);
2542 buddy
->name
= g_strdup(self_uri
);
2543 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2550 /* subscribe to buddies */
2551 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2552 if (sip
->batched_support
) {
2553 sipe_subscribe_presence_batched(sip
, NULL
);
2555 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
2557 sip
->subscribed_buddies
= TRUE
;
2559 /* for 2005 systems schedule contacts' status update
2560 * based on their calendar information
2562 if (!sip
->ocs2007
) {
2563 sipe_sched_calendar_status_update(sip
, time(NULL
));
2570 * Subscribe roaming contacts
2572 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
)
2574 gchar
*to
= sip_uri_self(sip
);
2575 gchar
*tmp
= get_contact(sip
);
2576 gchar
*hdr
= g_strdup_printf(
2577 "Event: vnd-microsoft-roaming-contacts\r\n"
2578 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2579 "Supported: com.microsoft.autoextend\r\n"
2580 "Supported: ms-benotify\r\n"
2581 "Proxy-Require: ms-benotify\r\n"
2582 "Supported: ms-piggyback-first-notify\r\n"
2583 "Contact: %s\r\n", tmp
);
2586 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2591 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
,
2592 SIPE_UNUSED_PARAMETER
void *unused
)
2595 struct sip_dialog
*dialog
;
2596 gchar
*to
= sip_uri_self(sip
);
2597 gchar
*tmp
= get_contact(sip
);
2598 gchar
*hdr
= g_strdup_printf(
2599 "Event: presence.wpending\r\n"
2600 "Accept: text/xml+msrtc.wpending\r\n"
2601 "Supported: com.microsoft.autoextend\r\n"
2602 "Supported: ms-benotify\r\n"
2603 "Proxy-Require: ms-benotify\r\n"
2604 "Supported: ms-piggyback-first-notify\r\n"
2605 "Contact: %s\r\n", tmp
);
2608 /* Subscription is identified by <event> key */
2609 key
= g_strdup_printf("<%s>", "presence.wpending");
2610 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
2611 purple_debug_info("sipe", "sipe_subscribe_presence_wpending: subscription dialog for: %s is %s\n", key
, dialog
? "Not NULL" : "NULL");
2613 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", dialog
, process_subscribe_response
);
2621 * Fires on deregistration event initiated by server.
2622 * [MS-SIPREGE] SIP extension.
2627 // Content-Type: text/registration-event
2628 // subscription-state: terminated;expires=0
2629 // ms-diagnostics-public: 4141;reason="User disabled"
2631 // deregistered;event=rejected
2633 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2635 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2636 gchar
*event
= NULL
;
2637 gchar
*reason
= NULL
;
2638 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
2640 warning
= warning
? warning
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2641 purple_debug_info("sipe", "sipe_process_registration_notify: deregistration received.\n");
2643 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2644 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2645 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2646 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2648 purple_debug_info("sipe", "sipe_process_registration_notify: unknown content type, exiting.\n");
2652 if (warning
!= NULL
) {
2653 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
2654 } else { // for LCS2005
2656 if (event
&& !g_ascii_strcasecmp(event
, "unregistered")) {
2657 error_id
= 4140; // [MS-SIPREGE]
2658 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2659 reason
= g_strdup(_("you are already signed in at another location"));
2660 } else if (event
&& !g_ascii_strcasecmp(event
, "rejected")) {
2662 reason
= g_strdup(_("user disabled")); // [MS-OCER]
2663 } else if (event
&& !g_ascii_strcasecmp(event
, "deactivated")) {
2665 reason
= g_strdup(_("user moved")); // [MS-OCER]
2669 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), reason
? reason
: _("no reason given"));
2672 sip
->gc
->wants_to_die
= TRUE
;
2673 purple_connection_error(sip
->gc
, warning
);
2678 static void sipe_process_provisioning_v2(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2680 xmlnode
*xn_provision_group_list
;
2683 xn_provision_group_list
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2685 /* provisionGroup */
2686 for (node
= xmlnode_get_child(xn_provision_group_list
, "provisionGroup"); node
; node
= xmlnode_get_next_twin(node
)) {
2687 if (!strcmp("ServerConfiguration", xmlnode_get_attrib(node
, "name"))) {
2688 g_free(sip
->focus_factory_uri
);
2689 sip
->focus_factory_uri
= xmlnode_get_data(xmlnode_get_child(node
, "focusFactoryUri"));
2690 purple_debug_info("sipe", "sipe_process_provisioning_v2: sip->focus_factory_uri=%s\n",
2691 sip
->focus_factory_uri
? sip
->focus_factory_uri
: "");
2695 xmlnode_free(xn_provision_group_list
);
2698 /** for 2005 system */
2700 sipe_process_provisioning(struct sipe_account_data
*sip
,
2703 xmlnode
*xn_provision
;
2706 xn_provision
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2707 if ((node
= xmlnode_get_child(xn_provision
, "user"))) {
2708 purple_debug_info("sipe", "sipe_process_provisioning: uri=%s\n", xmlnode_get_attrib(node
, "uri"));
2709 if ((node
= xmlnode_get_child(node
, "line"))) {
2710 const gchar
*line_uri
= xmlnode_get_attrib(node
, "uri");
2711 const gchar
*server
= xmlnode_get_attrib(node
, "server");
2712 purple_debug_info("sipe", "sipe_process_provisioning: line_uri=%s server=%s\n", line_uri
, server
);
2713 sip_csta_open(sip
, line_uri
, server
);
2716 xmlnode_free(xn_provision
);
2719 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2721 const gchar
*contacts_delta
;
2724 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
2730 contacts_delta
= xmlnode_get_attrib(xml
, "deltaNum");
2733 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2740 free_container(struct sipe_container
*container
)
2744 if (!container
) return;
2746 entry
= container
->members
;
2748 g_free(entry
->data
);
2749 entry
= g_slist_remove(entry
, entry
->data
);
2755 * Finds locally stored MS-PRES container member
2757 static struct sipe_container_member
*
2758 sipe_find_container_member(struct sipe_container
*container
,
2762 struct sipe_container_member
*member
;
2765 if (container
== NULL
|| type
== NULL
) {
2769 entry
= container
->members
;
2771 member
= entry
->data
;
2772 if (!g_strcasecmp(member
->type
, type
)
2773 && ((!member
->value
&& !value
)
2774 || (value
&& member
->value
&& !g_strcasecmp(member
->value
, value
)))
2778 entry
= entry
->next
;
2784 * Finds locally stored MS-PRES container by id
2786 static struct sipe_container
*
2787 sipe_find_container(struct sipe_account_data
*sip
,
2790 struct sipe_container
*container
;
2797 entry
= sip
->containers
;
2799 container
= entry
->data
;
2800 if (id
== container
->id
) {
2803 entry
= entry
->next
;
2817 sipe_find_access_level(struct sipe_account_data
*sip
,
2821 guint containers
[] = {32000, 400, 300, 200, 100};
2824 for (i
= 0; i
< 5; i
++) {
2825 struct sipe_container_member
*member
;
2826 struct sipe_container
*container
= sipe_find_container(sip
, containers
[i
]);
2827 if (!container
) continue;
2829 member
= sipe_find_container_member(container
, type
, value
);
2831 return containers
[i
];
2839 sipe_send_set_container_members(struct sipe_account_data
*sip
,
2841 guint container_version
,
2842 const gchar
* action
,
2846 gchar
*self
= sip_uri_self(sip
);
2847 gchar
*value_str
= value
? g_strdup_printf(" value=\"%s\"", value
) : g_strdup("");
2850 gchar
*body
= g_strdup_printf(
2851 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2852 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>"
2853 "</setContainerMembers>",
2861 contact
= get_contact(sip
);
2862 hdr
= g_strdup_printf("Contact: %s\r\n"
2863 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact
);
2866 send_sip_request(sip
->gc
, "SERVICE", self
, self
, hdr
, body
, NULL
, NULL
);
2874 free_publication(struct sipe_publication
*publication
)
2876 g_free(publication
->category
);
2877 g_free(publication
->cal_event_hash
);
2878 g_free(publication
->note
);
2880 g_free(publication
->working_hours_xml_str
);
2881 g_free(publication
->fb_start_str
);
2882 g_free(publication
->free_busy_base64
);
2884 g_free(publication
);
2887 /* key is <category><instance><container> */
2889 sipe_is_our_publication(struct sipe_account_data
*sip
,
2894 /* filling keys for our publications if not yet cached */
2895 if (!sip
->our_publication_keys
) {
2896 guint device_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_DEVICE
);
2897 guint machine_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_MACHINE
);
2898 guint user_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_USER
);
2899 guint calendar_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR
);
2900 guint cal_oof_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR_OOF
);
2901 guint cal_data_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_CALENDAR_DATA
);
2902 guint note_oof_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_NOTE_OOF
);
2904 purple_debug_info("sipe", "* Our Publication Instances *\n");
2905 purple_debug_info("sipe", "\tDevice : %u\t0x%08X\n", device_instance
, device_instance
);
2906 purple_debug_info("sipe", "\tMachine State : %u\t0x%08X\n", machine_instance
, machine_instance
);
2907 purple_debug_info("sipe", "\tUser Stare : %u\t0x%08X\n", user_instance
, user_instance
);
2908 purple_debug_info("sipe", "\tCalendar State : %u\t0x%08X\n", calendar_instance
, calendar_instance
);
2909 purple_debug_info("sipe", "\tCalendar OOF State : %u\t0x%08X\n", cal_oof_instance
, cal_oof_instance
);
2910 purple_debug_info("sipe", "\tCalendar FreeBusy : %u\t0x%08X\n", cal_data_instance
, cal_data_instance
);
2911 purple_debug_info("sipe", "\tOOF Note : %u\t0x%08X\n", note_oof_instance
, note_oof_instance
);
2912 purple_debug_info("sipe", "\tNote : %u\n", 0);
2913 purple_debug_info("sipe", "\tCalendar WorkingHours: %u\n", 0);
2916 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2917 g_strdup_printf("<%s><%u><%u>", "device", device_instance
, 2));
2919 /* state:machineState */
2920 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2921 g_strdup_printf("<%s><%u><%u>", "state", machine_instance
, 2));
2922 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2923 g_strdup_printf("<%s><%u><%u>", "state", machine_instance
, 3));
2925 /* state:userState */
2926 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2927 g_strdup_printf("<%s><%u><%u>", "state", user_instance
, 2));
2928 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2929 g_strdup_printf("<%s><%u><%u>", "state", user_instance
, 3));
2931 /* state:calendarState */
2932 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2933 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance
, 2));
2934 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2935 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance
, 3));
2937 /* state:calendarState OOF */
2938 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2939 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance
, 2));
2940 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2941 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance
, 3));
2944 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2945 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
2946 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2947 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
2948 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2949 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
2952 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2953 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 200));
2954 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2955 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 300));
2956 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2957 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 400));
2959 /* calendarData:WorkingHours */
2960 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2961 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
2962 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2963 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
2964 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2965 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
2966 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2967 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
2968 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2969 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
2970 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2971 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
2973 /* calendarData:FreeBusy */
2974 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2975 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 1));
2976 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2977 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 100));
2978 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2979 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 200));
2980 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2981 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 300));
2982 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2983 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 400));
2984 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
2985 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 32000));
2987 //purple_debug_info("sipe", "sipe_is_our_publication: sip->our_publication_keys length=%d\n",
2988 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
2991 //purple_debug_info("sipe", "sipe_is_our_publication: key=%s\n", key);
2993 entry
= sip
->our_publication_keys
;
2995 //purple_debug_info("sipe", " sipe_is_our_publication: entry->data=%s\n", entry->data);
2996 if (!strcmp(entry
->data
, key
)) {
2999 entry
= entry
->next
;
3004 /** Property names to store in blist.xml */
3005 #define ALIAS_PROP "alias"
3006 #define EMAIL_PROP "email"
3007 #define PHONE_PROP "phone"
3008 #define PHONE_DISPLAY_PROP "phone-display"
3009 #define PHONE_MOBILE_PROP "phone-mobile"
3010 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3011 #define PHONE_HOME_PROP "phone-home"
3012 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3013 #define PHONE_OTHER_PROP "phone-other"
3014 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3015 #define PHONE_CUSTOM1_PROP "phone-custom1"
3016 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3017 #define SITE_PROP "site"
3018 #define COMPANY_PROP "company"
3019 #define DEPARTMENT_PROP "department"
3020 #define TITLE_PROP "title"
3021 #define OFFICE_PROP "office"
3022 /** implies work address */
3023 #define ADDRESS_STREET_PROP "address-street"
3024 #define ADDRESS_CITY_PROP "address-city"
3025 #define ADDRESS_STATE_PROP "address-state"
3026 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3027 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3030 * Tries to figure out user first and last name
3031 * based on Display Name and email properties.
3033 * Allocates memory - must be g_free()'d
3035 * Examples to parse:
3037 * First Last - Company Name
3040 * Last, First (C)(STP) (Company)
3041 * first.last@company.com (preprocessed as "first last")
3042 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3044 * Unusable examples:
3045 * user@company.com (preprocessed as "user")
3046 * first.m.last@company.com (preprocessed as "first m last")
3047 * user.company.com@reuters.net (preprocessed as "user company com")
3050 sipe_get_first_last_names(struct sipe_account_data
*sip
,
3055 PurpleBuddy
*p_buddy
;
3058 const char *first
, *last
;
3061 gboolean has_comma
= FALSE
;
3063 if (!sip
|| !uri
) return;
3065 p_buddy
= purple_find_buddy(sip
->account
, uri
);
3067 if (!p_buddy
) return;
3069 display_name
= g_strdup(purple_buddy_get_alias(p_buddy
));
3070 email
= purple_blist_node_get_string(&p_buddy
->node
, EMAIL_PROP
);
3072 if (!display_name
&& !email
) return;
3074 /* if no display name, make "first last anything_else" out of email */
3075 if (email
&& !display_name
) {
3076 display_name
= g_strndup(email
, strstr(email
, "@") - email
);
3077 display_name
= purple_strreplace((tmp
= display_name
), ".", " ");
3081 has_comma
= (strstr(display_name
, ",") != NULL
);
3084 display_name
= purple_strreplace((tmp
= display_name
), ", ", " ");
3086 display_name
= purple_strreplace((tmp
= display_name
), ",", " ");
3090 parts
= g_strsplit(display_name
, " ", 0);
3092 if (!parts
[0] || !parts
[1]) {
3093 g_free(display_name
);
3107 *first_name
= g_strstrip(g_strdup(first
));
3111 *last_name
= g_strstrip(g_strdup(last
));
3114 g_free(display_name
);
3119 * Update user information
3121 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3122 * @param property_name
3123 * @param property_value may be modified to strip white space
3126 sipe_update_user_info(struct sipe_account_data
*sip
,
3128 const char *property_name
,
3129 char *property_value
)
3131 GSList
*buddies
, *entry
;
3133 if (!property_name
|| strlen(property_name
) == 0) return;
3136 property_value
= g_strstrip(property_value
);
3138 entry
= buddies
= purple_find_buddies(sip
->account
, uri
); /* all buddies in different groups */
3140 const char *prop_str
;
3141 const char *server_alias
;
3142 PurpleBuddy
*p_buddy
= entry
->data
;
3144 /* for Display Name */
3145 if (!strcmp(property_name
, ALIAS_PROP
)) {
3146 if (property_value
&& sipe_is_bad_alias(uri
, purple_buddy_get_alias(p_buddy
))) {
3147 purple_debug_info("sipe", "Replacing alias for %s with %s\n", uri
, property_value
);
3148 purple_blist_alias_buddy(p_buddy
, property_value
);
3151 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3152 if (property_value
&& strlen(property_value
) > 0 &&
3153 ( (server_alias
&& strcmp(property_value
, server_alias
))
3154 || !server_alias
|| strlen(server_alias
) == 0 )
3156 purple_blist_server_alias_buddy(p_buddy
, property_value
);
3159 /* for other properties */
3161 if (property_value
&& strlen(property_value
) > 0) {
3162 prop_str
= purple_blist_node_get_string(&p_buddy
->node
, property_name
);
3163 if (!prop_str
|| g_ascii_strcasecmp(prop_str
, property_value
)) {
3164 purple_blist_node_set_string(&p_buddy
->node
, property_name
, property_value
);
3169 entry
= entry
->next
;
3171 g_slist_free(buddies
);
3176 * Suitable for both 2005 and 2007 systems.
3178 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3180 * @param phone may be modified to strip white space
3181 * @param phone_display_string may be modified to strip white space
3184 sipe_update_user_phone(struct sipe_account_data
*sip
,
3186 const gchar
*phone_type
,
3188 gchar
*phone_display_string
)
3190 const char *phone_node
= PHONE_PROP
; /* work phone by default */
3191 const char *phone_display_node
= PHONE_DISPLAY_PROP
; /* work phone by default */
3193 if(!phone
|| strlen(phone
) == 0) return;
3195 if (phone_type
&& (!strcmp(phone_type
, "mobile") || !strcmp(phone_type
, "cell"))) {
3196 phone_node
= PHONE_MOBILE_PROP
;
3197 phone_display_node
= PHONE_MOBILE_DISPLAY_PROP
;
3198 } else if (phone_type
&& !strcmp(phone_type
, "home")) {
3199 phone_node
= PHONE_HOME_PROP
;
3200 phone_display_node
= PHONE_HOME_DISPLAY_PROP
;
3201 } else if (phone_type
&& !strcmp(phone_type
, "other")) {
3202 phone_node
= PHONE_OTHER_PROP
;
3203 phone_display_node
= PHONE_OTHER_DISPLAY_PROP
;
3204 } else if (phone_type
&& !strcmp(phone_type
, "custom1")) {
3205 phone_node
= PHONE_CUSTOM1_PROP
;
3206 phone_display_node
= PHONE_CUSTOM1_DISPLAY_PROP
;
3209 sipe_update_user_info(sip
, uri
, phone_node
, phone
);
3210 if (phone_display_string
) {
3211 sipe_update_user_info(sip
, uri
, phone_display_node
, phone_display_string
);
3216 sipe_update_calendar(struct sipe_account_data
*sip
)
3218 const char* calendar
= purple_account_get_string(sip
->account
, "calendar", "EXCH");
3220 purple_debug_info("sipe", "sipe_update_calendar: started.\n");
3222 if (!strcmp(calendar
, "EXCH")) {
3223 sipe_ews_update_calendar(sip
);
3226 /* schedule repeat */
3227 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
3229 purple_debug_info("sipe", "sipe_update_calendar: finished.\n");
3233 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3234 * by using standard Purple's means of signals and saved statuses.
3236 * Thus all UI elements get updated: Status Button with Note, docklet.
3237 * This is ablolutely important as both our status and note can come
3238 * inbound (roaming) or be updated programmatically (e.g. based on our
3242 sipe_set_purple_account_status_and_note(const PurpleAccount
*account
,
3243 const char *status_id
,
3244 const char *message
,
3245 time_t do_not_publish
[])
3247 PurpleStatus
*status
= purple_account_get_active_status(account
);
3248 gboolean changed
= TRUE
;
3250 if (g_str_equal(status_id
, purple_status_get_id(status
)) &&
3251 purple_strequal(message
, purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
)))
3257 PurpleSavedStatus
*saved_status
;
3258 const PurpleStatusType
*acct_status_type
=
3259 purple_status_type_find_with_id(account
->status_types
, status_id
);
3260 PurpleStatusPrimitive primitive
= purple_status_type_get_primitive(acct_status_type
);
3261 sipe_activity activity
= sipe_get_activity_by_token(status_id
);
3263 saved_status
= purple_savedstatus_find_transient_by_type_and_message(primitive
, message
);
3265 purple_savedstatus_set_substatus(saved_status
, account
, acct_status_type
, message
);
3268 /* If this type+message is unique then create a new transient saved status
3269 * Ref: gtkstatusbox.c
3271 if (!saved_status
) {
3273 GList
*active_accts
= purple_accounts_get_all_active();
3275 saved_status
= purple_savedstatus_new(NULL
, primitive
);
3276 purple_savedstatus_set_message(saved_status
, message
);
3278 for (tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
3279 purple_savedstatus_set_substatus(saved_status
,
3280 (PurpleAccount
*)tmp
->data
, acct_status_type
, message
);
3282 g_list_free(active_accts
);
3285 do_not_publish
[activity
] = time(NULL
);
3286 purple_debug_info("sipe", "sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]\n",
3287 status_id
, (int)do_not_publish
[activity
]);
3289 /* Set the status for each account */
3290 purple_savedstatus_activate(saved_status
);
3294 struct hash_table_delete_payload
{
3295 GHashTable
*hash_table
;
3300 sipe_remove_category_container_publications_cb(const char *name
,
3301 struct sipe_publication
*publication
,
3302 struct hash_table_delete_payload
*payload
)
3304 if (publication
->container
== payload
->container
) {
3305 g_hash_table_remove(payload
->hash_table
, name
);
3309 sipe_remove_category_container_publications(GHashTable
*our_publications
,
3310 const char *category
,
3313 struct hash_table_delete_payload payload
;
3314 payload
.hash_table
= g_hash_table_lookup(our_publications
, category
);
3316 if (!payload
.hash_table
) return;
3318 payload
.container
= container
;
3319 g_hash_table_foreach(payload
.hash_table
, (GHFunc
)sipe_remove_category_container_publications_cb
, &payload
);
3323 send_publish_category_initial(struct sipe_account_data
*sip
);
3326 * When we receive some self (BE) NOTIFY with a new subscriber
3327 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3330 static void sipe_process_roaming_self(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3337 char *display_name
= NULL
;
3339 GSList
*category_names
= NULL
;
3340 int aggreg_avail
= 0;
3341 static sipe_activity aggreg_activity
= SIPE_ACTIVITY_UNSET
;
3342 gboolean do_update_status
= FALSE
;
3343 gboolean has_note_cleaned
= FALSE
;
3345 purple_debug_info("sipe", "sipe_process_roaming_self\n");
3347 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
3350 contact
= get_contact(sip
);
3351 to
= sip_uri_self(sip
);
3355 /* set list of categories participating in this XML */
3356 for (node
= xmlnode_get_descendant(xml
, "categories", "category", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3357 const gchar
*name
= xmlnode_get_attrib(node
, "name");
3358 category_names
= slist_insert_unique_sorted(category_names
, (gchar
*)name
, (GCompareFunc
)strcmp
);
3360 purple_debug_info("sipe", "sipe_process_roaming_self: category_names length=%d\n",
3361 category_names
? (int) g_slist_length(category_names
) : -1);
3362 /* drop category information */
3363 if (category_names
) {
3364 GSList
*entry
= category_names
;
3366 GHashTable
*cat_publications
;
3367 const gchar
*category
= entry
->data
;
3368 entry
= entry
->next
;
3369 purple_debug_info("sipe", "sipe_process_roaming_self: dropping category: %s\n", category
);
3370 cat_publications
= g_hash_table_lookup(sip
->our_publications
, category
);
3371 if (cat_publications
) {
3372 g_hash_table_remove(sip
->our_publications
, category
);
3373 purple_debug_info("sipe", " sipe_process_roaming_self: dropped category: %s\n", category
);
3377 g_slist_free(category_names
);
3378 /* filling our categories reflected in roaming data */
3379 for (node
= xmlnode_get_descendant(xml
, "categories", "category", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3381 const gchar
*name
= xmlnode_get_attrib(node
, "name");
3382 guint container
= xmlnode_get_int_attrib(node
, "container", -1);
3383 guint instance
= xmlnode_get_int_attrib(node
, "instance", -1);
3384 guint version
= xmlnode_get_int_attrib(node
, "version", 0);
3385 time_t publish_time
= (tmp
= xmlnode_get_attrib(node
, "publishTime")) ?
3386 sipe_utils_str_to_time(tmp
) : 0;
3388 GHashTable
*cat_publications
= g_hash_table_lookup(sip
->our_publications
, name
);
3390 /* Ex. clear note: <category name="note"/> */
3391 if (container
== (guint
)-1) {
3394 do_update_status
= TRUE
;
3398 /* Ex. clear note: <category name="note" container="200"/> */
3399 if (instance
== (guint
)-1) {
3400 if (container
== 200) {
3403 do_update_status
= TRUE
;
3405 purple_debug_info("sipe", "sipe_process_roaming_self: removing publications for: %s/%u\n", name
, container
);
3406 sipe_remove_category_container_publications(
3407 sip
->our_publications
, name
, container
);
3411 /* key is <category><instance><container> */
3412 key
= g_strdup_printf("<%s><%u><%u>", name
, instance
, container
);
3413 purple_debug_info("sipe", "sipe_process_roaming_self: key=%s version=%d\n", key
, version
);
3415 /* capture all userState publication for later clean up if required */
3416 if (!strcmp(name
, "state") && (container
== 2 || container
== 3)) {
3417 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3419 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "userState")) {
3420 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
3421 publication
->category
= g_strdup(name
);
3422 publication
->instance
= instance
;
3423 publication
->container
= container
;
3424 publication
->version
= version
;
3426 if (!sip
->user_state_publications
) {
3427 sip
->user_state_publications
= g_hash_table_new_full(
3428 g_str_hash
, g_str_equal
,
3429 g_free
, (GDestroyNotify
)free_publication
);
3431 g_hash_table_insert(sip
->user_state_publications
, g_strdup(key
), publication
);
3432 purple_debug_info("sipe", "sipe_process_roaming_self: added to user_state_publications key=%s version=%d\n",
3437 if (sipe_is_our_publication(sip
, key
)) {
3438 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
3440 publication
->category
= g_strdup(name
);
3441 publication
->instance
= instance
;
3442 publication
->container
= container
;
3443 publication
->version
= version
;
3445 /* filling publication->availability */
3446 if (!strcmp(name
, "state")) {
3447 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3448 xmlnode
*xn_avail
= xmlnode_get_child(xn_state
, "availability");
3451 gchar
*avail_str
= xmlnode_get_data(xn_avail
);
3453 publication
->availability
= atoi(avail_str
);
3457 /* for calendarState */
3458 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "calendarState")) {
3459 xmlnode
*xn_activity
= xmlnode_get_child(xn_state
, "activity");
3460 struct sipe_cal_event
*event
= g_new0(struct sipe_cal_event
, 1);
3462 event
->start_time
= sipe_utils_str_to_time(xmlnode_get_attrib(xn_state
, "startTime"));
3464 if (!strcmp(xmlnode_get_attrib(xn_activity
, "token"),
3465 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].token
))
3467 event
->is_meeting
= TRUE
;
3470 event
->subject
= xmlnode_get_data(xmlnode_get_child(xn_state
, "meetingSubject"));
3471 event
->location
= xmlnode_get_data(xmlnode_get_child(xn_state
, "meetingLocation"));
3473 publication
->cal_event_hash
= sipe_cal_event_hash(event
);
3474 purple_debug_info("sipe", "sipe_process_roaming_self: hash=%s\n",
3475 publication
->cal_event_hash
);
3476 sipe_cal_event_free(event
);
3479 /* filling publication->note */
3480 if (!strcmp(name
, "note")) {
3481 xmlnode
*xn_body
= xmlnode_get_descendant(node
, "note", "body", NULL
);
3483 if (!has_note_cleaned
) {
3484 has_note_cleaned
= TRUE
;
3488 sip
->note_since
= publish_time
;
3490 do_update_status
= TRUE
;
3493 g_free(publication
->note
);
3494 publication
->note
= NULL
;
3498 publication
->note
= g_markup_escape_text((tmp
= xmlnode_get_data(xn_body
)), -1);
3500 if (publish_time
>= sip
->note_since
) {
3502 sip
->note
= g_strdup(publication
->note
);
3503 sip
->note_since
= publish_time
;
3504 sip
->is_oof_note
= !strcmp(xmlnode_get_attrib(xn_body
, "type"), "OOF");
3506 do_update_status
= TRUE
;
3511 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3512 if (!strcmp(name
, "calendarData") && (publication
->container
== 300)) {
3513 xmlnode
*xn_free_busy
= xmlnode_get_descendant(node
, "calendarData", "freeBusy", NULL
);
3514 xmlnode
*xn_working_hours
= xmlnode_get_descendant(node
, "calendarData", "WorkingHours", NULL
);
3516 publication
->fb_start_str
= g_strdup(xmlnode_get_attrib(xn_free_busy
, "startTime"));
3517 publication
->free_busy_base64
= xmlnode_get_data(xn_free_busy
);
3519 if (xn_working_hours
) {
3520 publication
->working_hours_xml_str
= xmlnode_to_str(xn_working_hours
, NULL
);
3524 if (!cat_publications
) {
3525 cat_publications
= g_hash_table_new_full(
3526 g_str_hash
, g_str_equal
,
3527 g_free
, (GDestroyNotify
)free_publication
);
3528 g_hash_table_insert(sip
->our_publications
, g_strdup(name
), cat_publications
);
3529 purple_debug_info("sipe", "sipe_process_roaming_self: added GHashTable cat=%s\n", name
);
3531 g_hash_table_insert(cat_publications
, g_strdup(key
), publication
);
3532 purple_debug_info("sipe", "sipe_process_roaming_self: added key=%s version=%d\n", key
, version
);
3536 /* aggregateState (not an our publication) from 2-nd container */
3537 if (!strcmp(name
, "state") && container
== 2) {
3538 xmlnode
*xn_state
= xmlnode_get_child(node
, "state");
3540 if (xn_state
&& !strcmp(xmlnode_get_attrib(xn_state
, "type"), "aggregateState")) {
3541 xmlnode
*xn_avail
= xmlnode_get_child(xn_state
, "availability");
3542 xmlnode
*xn_activity
= xmlnode_get_child(xn_state
, "activity");
3545 gchar
*avail_str
= xmlnode_get_data(xn_avail
);
3547 aggreg_avail
= atoi(avail_str
);
3553 const char *activity_token
= xmlnode_get_attrib(xn_activity
, "token");
3555 aggreg_activity
= sipe_get_activity_by_token(activity_token
);
3558 do_update_status
= TRUE
;
3562 /* userProperties published by server from AD */
3563 if (!sip
->csta
&& !strcmp(name
, "userProperties")) {
3565 /* line, for Remote Call Control (RCC) */
3566 for (line
= xmlnode_get_descendant(node
, "userProperties", "lines", "line", NULL
); line
; line
= xmlnode_get_next_twin(line
)) {
3567 const gchar
*line_server
= xmlnode_get_attrib(line
, "lineServer");
3568 const gchar
*line_type
= xmlnode_get_attrib(line
, "lineType");
3571 if (!line_server
|| (strcmp(line_type
, "Rcc") && strcmp(line_type
, "Dual"))) continue;
3573 line_uri
= xmlnode_get_data(line
);
3575 purple_debug_info("sipe", "sipe_process_roaming_self: line_uri=%s server=%s\n", line_uri
, line_server
);
3576 sip_csta_open(sip
, line_uri
, line_server
);
3584 purple_debug_info("sipe", "sipe_process_roaming_self: sip->our_publications size=%d\n",
3585 sip
->our_publications
? (int) g_hash_table_size(sip
->our_publications
) : -1);
3588 for (node
= xmlnode_get_descendant(xml
, "containers", "container", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3589 guint id
= xmlnode_get_int_attrib(node
, "id", 0);
3590 struct sipe_container
*container
= sipe_find_container(sip
, id
);
3593 sip
->containers
= g_slist_remove(sip
->containers
, container
);
3594 purple_debug_info("sipe", "sipe_process_roaming_self: removed existing container id=%d v%d\n", container
->id
, container
->version
);
3595 free_container(container
);
3597 container
= g_new0(struct sipe_container
, 1);
3599 container
->version
= xmlnode_get_int_attrib(node
, "version", 0);
3600 sip
->containers
= g_slist_append(sip
->containers
, container
);
3601 purple_debug_info("sipe", "sipe_process_roaming_self: added container id=%d v%d\n", container
->id
, container
->version
);
3603 for (node2
= xmlnode_get_child(node
, "member"); node2
; node2
= xmlnode_get_next_twin(node2
)) {
3604 struct sipe_container_member
*member
= g_new0(struct sipe_container_member
, 1);
3605 member
->type
= xmlnode_get_attrib(node2
, "type");
3606 member
->value
= xmlnode_get_attrib(node2
, "value");
3607 container
->members
= g_slist_append(container
->members
, member
);
3608 purple_debug_info("sipe", "sipe_process_roaming_self: added container member type=%s value=%s\n",
3609 member
->type
, member
->value
? member
->value
: "");
3613 purple_debug_info("sipe", "sipe_process_roaming_self: sip->access_level_set=%s\n", sip
->access_level_set
? "TRUE" : "FALSE");
3614 if (!sip
->access_level_set
&& xmlnode_get_child(xml
, "containers")) {
3615 int sameEnterpriseAL
= sipe_find_access_level(sip
, "sameEnterprise", NULL
);
3616 int federatedAL
= sipe_find_access_level(sip
, "federated", NULL
);
3617 purple_debug_info("sipe", "sipe_process_roaming_self: sameEnterpriseAL=%d\n", sameEnterpriseAL
);
3618 purple_debug_info("sipe", "sipe_process_roaming_self: federatedAL=%d\n", federatedAL
);
3619 /* initial set-up to let counterparties see your status */
3620 if (sameEnterpriseAL
< 0) {
3621 struct sipe_container
*container
= sipe_find_container(sip
, 200);
3622 guint version
= container
? container
->version
: 0;
3623 sipe_send_set_container_members(sip
, 200, version
, "add", "sameEnterprise", NULL
);
3625 if (federatedAL
< 0) {
3626 struct sipe_container
*container
= sipe_find_container(sip
, 100);
3627 guint version
= container
? container
->version
: 0;
3628 sipe_send_set_container_members(sip
, 100, version
, "add", "federated", NULL
);
3630 sip
->access_level_set
= TRUE
;
3634 for (node
= xmlnode_get_descendant(xml
, "subscribers", "subscriber", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
3636 const char *acknowledged
;
3640 user
= xmlnode_get_attrib(node
, "user"); /* without 'sip:' prefix */
3641 if (!user
) continue;
3642 purple_debug_info("sipe", "sipe_process_roaming_self: user %s\n", user
);
3643 display_name
= g_strdup(xmlnode_get_attrib(node
, "displayName"));
3644 uri
= sip_uri_from_name(user
);
3646 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
3648 acknowledged
= xmlnode_get_attrib(node
, "acknowledged");
3649 if(!g_ascii_strcasecmp(acknowledged
,"false")){
3650 purple_debug_info("sipe", "sipe_process_roaming_self: user added you %s\n", user
);
3651 if (!purple_find_buddy(sip
->account
, uri
)) {
3652 purple_account_request_add(sip
->account
, uri
, _("you"), display_name
, NULL
);
3655 hdr
= g_strdup_printf(
3657 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
3659 body
= g_strdup_printf(
3660 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
3661 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
3662 "</setSubscribers>", user
);
3664 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
3668 g_free(display_name
);
3675 /* Publish initial state if not yet.
3676 * Assuming this happens on initial responce to subscription to roaming-self
3677 * so we've already updated our roaming data in full.
3680 if (!sip
->initial_state_published
) {
3681 send_publish_category_initial(sip
);
3682 sip
->initial_state_published
= TRUE
;
3684 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
3685 do_update_status
= FALSE
;
3686 } else if (aggreg_avail
) {
3688 g_free(sip
->status
);
3689 if (aggreg_avail
&& aggreg_avail
< 18000) { /* not offline */
3690 sip
->status
= g_strdup(sipe_get_status_by_availability(aggreg_avail
, NULL
));
3692 sip
->status
= g_strdup(SIPE_STATUS_ID_INVISIBLE
); /* not not let offline status switch us off */
3696 if (do_update_status
) {
3697 purple_debug_info("sipe", "sipe_process_roaming_self: switch to '%s' for the account\n", sip
->status
);
3698 sipe_set_purple_account_status_and_note(sip
->account
, sip
->status
, sip
->note
, sip
->do_not_publish
);
3704 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
)
3706 gchar
*to
= sip_uri_self(sip
);
3707 gchar
*tmp
= get_contact(sip
);
3708 gchar
*hdr
= g_strdup_printf(
3709 "Event: vnd-microsoft-roaming-ACL\r\n"
3710 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
3711 "Supported: com.microsoft.autoextend\r\n"
3712 "Supported: ms-benotify\r\n"
3713 "Proxy-Require: ms-benotify\r\n"
3714 "Supported: ms-piggyback-first-notify\r\n"
3715 "Contact: %s\r\n", tmp
);
3718 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
3724 * To request for presence information about the user, access level settings that have already been configured by the user
3725 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
3726 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
3729 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
)
3731 gchar
*to
= sip_uri_self(sip
);
3732 gchar
*tmp
= get_contact(sip
);
3733 gchar
*hdr
= g_strdup_printf(
3734 "Event: vnd-microsoft-roaming-self\r\n"
3735 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
3736 "Supported: ms-benotify\r\n"
3737 "Proxy-Require: ms-benotify\r\n"
3738 "Supported: ms-piggyback-first-notify\r\n"
3740 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
3742 gchar
*body
=g_strdup(
3743 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
3744 "<roaming type=\"categories\"/>"
3745 "<roaming type=\"containers\"/>"
3746 "<roaming type=\"subscribers\"/></roamingList>");
3749 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
3758 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
)
3760 gchar
*to
= sip_uri_self(sip
);
3761 gchar
*tmp
= get_contact(sip
);
3762 gchar
*hdr
= g_strdup_printf(
3763 "Event: vnd-microsoft-provisioning\r\n"
3764 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
3765 "Supported: com.microsoft.autoextend\r\n"
3766 "Supported: ms-benotify\r\n"
3767 "Proxy-Require: ms-benotify\r\n"
3768 "Supported: ms-piggyback-first-notify\r\n"
3770 "Contact: %s\r\n", tmp
);
3773 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
3778 /** Subscription for provisioning information to help with initial
3779 * configuration. This subscription is a one-time query (denoted by the Expires header,
3780 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
3781 * configuration, meeting policies, and policy settings that Communicator must enforce.
3782 * TODO: for what we need this information.
3785 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
)
3787 gchar
*to
= sip_uri_self(sip
);
3788 gchar
*tmp
= get_contact(sip
);
3789 gchar
*hdr
= g_strdup_printf(
3790 "Event: vnd-microsoft-provisioning-v2\r\n"
3791 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
3792 "Supported: com.microsoft.autoextend\r\n"
3793 "Supported: ms-benotify\r\n"
3794 "Proxy-Require: ms-benotify\r\n"
3795 "Supported: ms-piggyback-first-notify\r\n"
3798 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
3799 gchar
*body
= g_strdup(
3800 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
3801 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
3802 "<provisioningGroup name=\"ucPolicy\"/>"
3803 "</provisioningGroupList>");
3806 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
3813 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key
,
3814 gpointer value
, gpointer user_data
)
3816 struct sip_subscription
*subscription
= value
;
3817 struct sip_dialog
*dialog
= &subscription
->dialog
;
3818 struct sipe_account_data
*sip
= user_data
;
3819 gchar
*tmp
= get_contact(sip
);
3820 gchar
*hdr
= g_strdup_printf(
3823 "Contact: %s\r\n", subscription
->event
, tmp
);
3826 /* Rate limit to max. 25 requests per seconds */
3827 g_usleep(1000000 / 25);
3829 send_sip_request(sip
->gc
, "SUBSCRIBE", dialog
->with
, dialog
->with
, hdr
, NULL
, dialog
, NULL
);
3833 /* IM Session (INVITE and MESSAGE methods) */
3835 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
3837 get_end_points (struct sipe_account_data
*sip
,
3838 struct sip_session
*session
)
3842 if (session
== NULL
) {
3846 res
= g_strdup_printf("<sip:%s>", sip
->username
);
3848 SIPE_DIALOG_FOREACH
{
3850 res
= g_strdup_printf("%s, <%s>", res
, dialog
->with
);
3853 if (dialog
->theirepid
) {
3855 res
= g_strdup_printf("%s;epid=%s", res
, dialog
->theirepid
);
3858 } SIPE_DIALOG_FOREACH_END
;
3864 process_options_response(SIPE_UNUSED_PARAMETER
struct sipe_account_data
*sip
,
3866 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
3868 gboolean ret
= TRUE
;
3870 if (msg
->response
!= 200) {
3871 purple_debug_info("sipe", "process_options_response: OPTIONS response is %d\n", msg
->response
);
3875 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
3881 * Asks UA/proxy about its capabilities.
3883 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
3885 gchar
*to
= sip_uri(who
);
3886 gchar
*contact
= get_contact(sip
);
3887 gchar
*request
= g_strdup_printf(
3888 "Accept: application/sdp\r\n"
3889 "Contact: %s\r\n", contact
);
3892 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
3899 sipe_notify_user(struct sipe_account_data
*sip
,
3900 struct sip_session
*session
,
3901 PurpleMessageFlags flags
,
3902 const gchar
*message
)
3904 PurpleConversation
*conv
;
3906 if (!session
->conv
) {
3907 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY
, session
->with
, sip
->account
);
3909 conv
= session
->conv
;
3911 purple_conversation_write(conv
, NULL
, message
, flags
, time(NULL
));
3915 sipe_present_info(struct sipe_account_data
*sip
,
3916 struct sip_session
*session
,
3917 const gchar
*message
)
3919 sipe_notify_user(sip
, session
, PURPLE_MESSAGE_SYSTEM
, message
);
3923 sipe_present_err(struct sipe_account_data
*sip
,
3924 struct sip_session
*session
,
3925 const gchar
*message
)
3927 sipe_notify_user(sip
, session
, PURPLE_MESSAGE_ERROR
, message
);
3931 sipe_present_message_undelivered_err(struct sipe_account_data
*sip
,
3932 struct sip_session
*session
,
3935 const gchar
*message
)
3937 char *msg
, *msg_tmp
, *msg_tmp2
;
3940 msg_tmp
= message
? purple_markup_strip_html(message
) : NULL
;
3941 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
3943 /* Service unavailable; Server Internal Error; Server Time-out */
3944 if (sip_error
== 503 || sip_error
== 500 || sip_error
== 504) {
3945 label
= _("This message was not delivered to %s because the service is not available");
3946 } else if (sip_error
== 486) { /* Busy Here */
3947 label
= _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
3949 label
= _("This message was not delivered to %s because one or more recipients are offline");
3952 msg_tmp
= g_strdup_printf( "%s:\n%s" ,
3953 msg_tmp2
= g_strdup_printf(label
, who
? who
: ""), msg
? msg
: "");
3954 sipe_present_err(sip
, session
, msg_tmp
);
3961 static void sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_session
* session
);
3964 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
3965 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
3967 gboolean ret
= TRUE
;
3968 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
3969 struct sip_session
*session
= sipe_session_find_im(sip
, with
);
3970 struct sip_dialog
*dialog
;
3976 purple_debug_info("sipe", "process_message_response: unable to find IM session\n");
3981 dialog
= sipe_dialog_find(session
, with
);
3983 purple_debug_info("sipe", "process_message_response: session outgoing dialog is NULL\n");
3988 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
3989 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
), with
);
3991 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
3993 if (msg
->response
>= 400) {
3994 PurpleBuddy
*pbuddy
;
3995 const char *alias
= with
;
3997 purple_debug_info("sipe", "process_message_response: MESSAGE response >= 400\n");
3999 if ((pbuddy
= purple_find_buddy(sip
->account
, with
))) {
4000 alias
= purple_buddy_get_alias(pbuddy
);
4003 sipe_present_message_undelivered_err(sip
, session
, msg
->response
, alias
, message
);
4006 gchar
*message_id
= sipmsg_find_header(msg
, "Message-Id");
4008 g_hash_table_insert(session
->conf_unconfirmed_messages
, g_strdup(message_id
), g_strdup(message
));
4009 purple_debug_info("sipe", "process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)\n",
4010 message_id
, g_hash_table_size(session
->conf_unconfirmed_messages
));
4013 g_hash_table_remove(session
->unconfirmed_messages
, key
);
4014 purple_debug_info("sipe", "process_message_response: removed message %s from unconfirmed_messages(count=%d)\n",
4015 key
, g_hash_table_size(session
->unconfirmed_messages
));
4021 if (ret
) sipe_im_process_queue(sip
, session
);
4026 sipe_is_election_finished(struct sip_session
*session
);
4029 sipe_election_result(struct sipe_account_data
*sip
,
4033 process_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
4034 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
4036 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
4037 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4038 struct sip_dialog
*dialog
;
4039 struct sip_session
*session
;
4041 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4043 purple_debug_info("sipe", "process_info_response: failed find dialog for callid %s, exiting.", callid
);
4047 if (msg
->response
== 200 && !strncmp(contenttype
, "application/x-ms-mim", 20)) {
4048 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4049 xmlnode
*xn_request_rm_response
= xmlnode_get_child(xn_action
, "RequestRMResponse");
4050 xmlnode
*xn_set_rm_response
= xmlnode_get_child(xn_action
, "SetRMResponse");
4052 if (xn_request_rm_response
) {
4053 const char *with
= xmlnode_get_attrib(xn_request_rm_response
, "uri");
4054 const char *allow
= xmlnode_get_attrib(xn_request_rm_response
, "allow");
4056 dialog
= sipe_dialog_find(session
, with
);
4058 purple_debug_info("sipe", "process_info_response: failed find dialog for %s, exiting.\n", with
);
4062 if (allow
&& !g_strcasecmp(allow
, "true")) {
4063 purple_debug_info("sipe", "process_info_response: %s has voted PRO\n", with
);
4064 dialog
->election_vote
= 1;
4065 } else if (allow
&& !g_strcasecmp(allow
, "false")) {
4066 purple_debug_info("sipe", "process_info_response: %s has voted CONTRA\n", with
);
4067 dialog
->election_vote
= -1;
4070 if (sipe_is_election_finished(session
)) {
4071 sipe_election_result(sip
, session
);
4074 } else if (xn_set_rm_response
) {
4077 xmlnode_free(xn_action
);
4084 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_dialog
*dialog
, const char *msg
)
4093 sipe_parse_html(msg
, &msgformat
, &msgtext
);
4094 purple_debug_info("sipe", "sipe_send_message: msgformat=%s\n", msgformat
);
4096 msgr_value
= sipmsg_get_msgr_string(msgformat
);
4099 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
4102 msgr
= g_strdup("");
4105 tmp
= get_contact(sip
);
4106 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4107 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4108 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4109 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: text/plain; charset=UTF-8%s\r\n", tmp
, msgr
);
4113 send_sip_request(sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
, hdr
, msgtext
, dialog
, process_message_response
);
4120 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_session
* session
)
4122 GSList
*entry2
= session
->outgoing_message_queue
;
4124 char *queued_msg
= entry2
->data
;
4126 /* for multiparty chat or conference */
4127 if (session
->is_multiparty
|| session
->focus_uri
) {
4128 gchar
*who
= sip_uri_self(sip
);
4129 serv_got_chat_in(sip
->gc
, session
->chat_id
, who
,
4130 PURPLE_MESSAGE_SEND
, queued_msg
, time(NULL
));
4134 SIPE_DIALOG_FOREACH
{
4137 if (dialog
->outgoing_invite
) continue; /* do not send messages as INVITE is not responded. */
4139 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog
->callid
, (dialog
->cseq
) + 1, dialog
->with
);
4140 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(queued_msg
));
4141 purple_debug_info("sipe", "sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)\n",
4142 key
, g_hash_table_size(session
->unconfirmed_messages
));
4145 sipe_send_message(sip
, dialog
, queued_msg
);
4146 } SIPE_DIALOG_FOREACH_END
;
4148 entry2
= session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
4154 sipe_refer_notify(struct sipe_account_data
*sip
,
4155 struct sip_session
*session
,
4162 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
4164 hdr
= g_strdup_printf(
4166 "Subscription-State: %s\r\n"
4167 "Content-Type: message/sipfrag\r\n",
4168 status
>= 200 ? "terminated" : "active");
4170 body
= g_strdup_printf(
4171 "SIP/2.0 %d %s\r\n",
4174 send_sip_request(sip
->gc
, "NOTIFY", who
, who
, hdr
, body
, dialog
, NULL
);
4181 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
4183 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
4184 struct sip_session
*session
;
4185 struct sip_dialog
*dialog
;
4189 struct sipmsg
*request_msg
= trans
->msg
;
4191 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4194 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4196 session
= sipe_session_find_im(sip
, with
);
4199 purple_debug_info("sipe", "process_invite_response: unable to find IM session\n");
4204 dialog
= sipe_dialog_find(session
, with
);
4206 purple_debug_info("sipe", "process_invite_response: session outgoing dialog is NULL\n");
4211 sipe_dialog_parse(dialog
, msg
, TRUE
);
4213 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
4214 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
4216 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
4218 if (msg
->response
!= 200) {
4219 PurpleBuddy
*pbuddy
;
4220 const char *alias
= with
;
4222 purple_debug_info("sipe", "process_invite_response: INVITE response not 200\n");
4224 if ((pbuddy
= purple_find_buddy(sip
->account
, with
))) {
4225 alias
= purple_buddy_get_alias(pbuddy
);
4229 sipe_present_message_undelivered_err(sip
, session
, msg
->response
, alias
, message
);
4231 gchar
*tmp_msg
= g_strdup_printf(_("Failed to invite %s"), alias
);
4232 sipe_present_err(sip
, session
, tmp_msg
);
4236 sipe_dialog_remove(session
, with
);
4244 send_sip_request(sip
->gc
, "ACK", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
4245 dialog
->outgoing_invite
= NULL
;
4246 dialog
->is_established
= TRUE
;
4248 referred_by
= parse_from(sipmsg_find_header(request_msg
, "Referred-By"));
4250 sipe_refer_notify(sip
, session
, referred_by
, 200, "OK");
4251 g_free(referred_by
);
4254 /* add user to chat if it is a multiparty session */
4255 if (session
->is_multiparty
) {
4256 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4258 PURPLE_CBFLAGS_NONE
, TRUE
);
4261 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
4262 purple_debug_info("sipe", "process_invite_response: remote system accepted message in INVITE\n");
4263 if (session
->outgoing_message_queue
) {
4264 char *queued_msg
= session
->outgoing_message_queue
->data
;
4265 session
->outgoing_message_queue
= g_slist_remove(session
->outgoing_message_queue
, queued_msg
);
4270 sipe_im_process_queue(sip
, session
);
4272 g_hash_table_remove(session
->unconfirmed_messages
, key
);
4273 purple_debug_info("sipe", "process_invite_response: removed message %s from unconfirmed_messages(count=%d)\n",
4274 key
, g_hash_table_size(session
->unconfirmed_messages
));
4283 sipe_invite(struct sipe_account_data
*sip
,
4284 struct sip_session
*session
,
4286 const gchar
*msg_body
,
4287 const gchar
*referred_by
,
4288 const gboolean is_triggered
)
4295 char *ms_text_format
= NULL
;
4296 gchar
*roster_manager
;
4298 gchar
*referred_by_str
;
4299 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
4301 if (dialog
&& dialog
->is_established
) {
4302 purple_debug_info("sipe", "session with %s already has a dialog open\n", who
);
4307 dialog
= sipe_dialog_add(session
);
4308 dialog
->callid
= session
->callid
? g_strdup(session
->callid
) : gencallid();
4309 dialog
->with
= g_strdup(who
);
4312 if (!(dialog
->ourtag
)) {
4313 dialog
->ourtag
= gentag();
4326 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
4327 purple_debug_info("sipe", "sipe_invite: msgformat=%s\n", msgformat
);
4329 msgr_value
= sipmsg_get_msgr_string(msgformat
);
4333 msgr
= g_strdup_printf(";msgr=%s", msgr_value
);
4337 base64_msg
= purple_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
4338 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
, msgr
, base64_msg
);
4343 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, (dialog
->cseq
) + 1);
4344 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), g_strdup(msg_body
));
4345 purple_debug_info("sipe", "sipe_invite: added message %s to unconfirmed_messages(count=%d)\n",
4346 key
, g_hash_table_size(session
->unconfirmed_messages
));
4350 contact
= get_contact(sip
);
4351 end_points
= get_end_points(sip
, session
);
4352 self
= sip_uri_self(sip
);
4353 roster_manager
= g_strdup_printf(
4354 "Roster-Manager: %s\r\n"
4355 "EndPoints: %s\r\n",
4358 referred_by_str
= referred_by
?
4360 "Referred-By: %s\r\n",
4363 hdr
= g_strdup_printf(
4364 "Supported: ms-sender\r\n"
4370 "Content-Type: application/sdp\r\n",
4371 (session
->roster_manager
&& !strcmp(session
->roster_manager
, self
)) ? roster_manager
: "",
4373 is_triggered
? "TriggeredInvite: TRUE\r\n" : "",
4374 is_triggered
|| session
->is_multiparty
? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4376 ms_text_format
? ms_text_format
: "");
4377 g_free(ms_text_format
);
4380 body
= g_strdup_printf(
4382 "o=- 0 0 IN IP4 %s\r\n"
4386 "m=%s %d sip null\r\n"
4387 "a=accept-types:text/plain text/html image/gif "
4388 "multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
4389 purple_network_get_my_ip(-1),
4390 purple_network_get_my_ip(-1),
4391 sip
->ocs2007
? "message" : "x-ms-message",
4394 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
4395 to
, to
, hdr
, body
, dialog
, process_invite_response
);
4398 g_free(roster_manager
);
4400 g_free(referred_by_str
);
4407 sipe_refer(struct sipe_account_data
*sip
,
4408 struct sip_session
*session
,
4413 gchar
*epid
= get_epid(sip
);
4414 struct sip_dialog
*dialog
= sipe_dialog_find(session
,
4415 session
->roster_manager
);
4416 const char *ourtag
= dialog
&& dialog
->ourtag
? dialog
->ourtag
: NULL
;
4418 contact
= get_contact(sip
);
4419 hdr
= g_strdup_printf(
4421 "Refer-to: <%s>\r\n"
4422 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4423 "Require: com.microsoft.rtc-multiparty\r\n",
4427 ourtag
? ";tag=" : "",
4428 ourtag
? ourtag
: "",
4432 send_sip_request(sip
->gc
, "REFER",
4433 session
->roster_manager
, session
->roster_manager
, hdr
, NULL
, dialog
, NULL
);
4440 sipe_send_election_request_rm(struct sipe_account_data
*sip
,
4441 struct sip_dialog
*dialog
,
4444 const gchar
*hdr
= "Content-Type: application/x-ms-mim\r\n";
4446 gchar
*body
= g_strdup_printf(
4447 "<?xml version=\"1.0\"?>\r\n"
4448 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4449 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4450 sip
->username
, bid
);
4452 send_sip_request(sip
->gc
, "INFO",
4453 dialog
->with
, dialog
->with
, hdr
, body
, dialog
, process_info_response
);
4459 sipe_send_election_set_rm(struct sipe_account_data
*sip
,
4460 struct sip_dialog
*dialog
)
4462 const gchar
*hdr
= "Content-Type: application/x-ms-mim\r\n";
4464 gchar
*body
= g_strdup_printf(
4465 "<?xml version=\"1.0\"?>\r\n"
4466 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4467 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4470 send_sip_request(sip
->gc
, "INFO",
4471 dialog
->with
, dialog
->with
, hdr
, body
, dialog
, process_info_response
);
4477 sipe_session_close(struct sipe_account_data
*sip
,
4478 struct sip_session
* session
)
4480 if (session
&& session
->focus_uri
) {
4481 sipe_conf_immcu_closed(sip
, session
);
4482 conf_session_close(sip
, session
);
4486 SIPE_DIALOG_FOREACH
{
4487 /* @TODO slow down BYE message sending rate */
4488 /* @see single subscription code */
4489 send_sip_request(sip
->gc
, "BYE", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
4490 } SIPE_DIALOG_FOREACH_END
;
4492 sipe_session_remove(sip
, session
);
4497 sipe_session_close_all(struct sipe_account_data
*sip
)
4500 while ((entry
= sip
->sessions
) != NULL
) {
4501 sipe_session_close(sip
, entry
->data
);
4506 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
4508 struct sipe_account_data
*sip
= gc
->proto_data
;
4510 purple_debug_info("sipe", "conversation with %s closed\n", who
);
4511 sipe_session_close(sip
, sipe_session_find_im(sip
, who
));
4515 sipe_chat_leave (PurpleConnection
*gc
, int id
)
4517 struct sipe_account_data
*sip
= gc
->proto_data
;
4518 struct sip_session
*session
= sipe_session_find_chat_by_id(sip
, id
);
4520 sipe_session_close(sip
, session
);
4523 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
,
4524 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
4526 struct sipe_account_data
*sip
= gc
->proto_data
;
4527 struct sip_session
*session
;
4528 struct sip_dialog
*dialog
;
4529 gchar
*uri
= sip_uri(who
);
4531 purple_debug_info("sipe", "sipe_im_send what='%s'\n", what
);
4533 session
= sipe_session_find_or_add_im(sip
, uri
);
4534 dialog
= sipe_dialog_find(session
, uri
);
4536 // Queue the message
4537 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
, g_strdup(what
));
4539 if (dialog
&& !dialog
->outgoing_invite
) {
4540 sipe_im_process_queue(sip
, session
);
4541 } else if (!dialog
|| !dialog
->outgoing_invite
) {
4542 // Need to send the INVITE to get the outgoing dialog setup
4543 sipe_invite(sip
, session
, uri
, what
, NULL
, FALSE
);
4550 static int sipe_chat_send(PurpleConnection
*gc
, int id
, const char *what
,
4551 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
4553 struct sipe_account_data
*sip
= gc
->proto_data
;
4554 struct sip_session
*session
;
4556 purple_debug_info("sipe", "sipe_chat_send what='%s'\n", what
);
4558 session
= sipe_session_find_chat_by_id(sip
, id
);
4560 // Queue the message
4561 if (session
&& session
->dialogs
) {
4562 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
,
4564 sipe_im_process_queue(sip
, session
);
4566 gchar
*chat_name
= purple_find_chat(sip
->gc
, id
)->name
;
4567 const gchar
*proto_chat_id
= sipe_chat_find_name(chat_name
);
4569 purple_debug_info("sipe", "sipe_chat_send: chat_name='%s'\n", chat_name
? chat_name
: "NULL");
4570 purple_debug_info("sipe", "sipe_chat_send: proto_chat_id='%s'\n", proto_chat_id
? proto_chat_id
: "NULL");
4573 struct sip_session
*session
= sipe_session_add_chat(sip
);
4575 session
->is_multiparty
= FALSE
;
4576 session
->focus_uri
= g_strdup(proto_chat_id
);
4577 session
->outgoing_message_queue
= g_slist_append(session
->outgoing_message_queue
,
4579 sipe_invite_conf_focus(sip
, session
);
4586 /* End IM Session (INVITE and MESSAGE methods) */
4588 static void process_incoming_info(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4590 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
4591 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4593 struct sip_session
*session
;
4595 purple_debug_info("sipe", "process_incoming_info: \n%s\n", msg
->body
? msg
->body
: "");
4597 /* Call Control protocol */
4598 if (g_str_has_prefix(contenttype
, "application/csta+xml"))
4600 process_incoming_info_csta(sip
, msg
);
4604 from
= parse_from(sipmsg_find_header(msg
, "From"));
4605 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4607 session
= sipe_session_find_im(sip
, from
);
4614 if (g_str_has_prefix(contenttype
, "application/x-ms-mim"))
4616 xmlnode
*xn_action
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4617 xmlnode
*xn_request_rm
= xmlnode_get_child(xn_action
, "RequestRM");
4618 xmlnode
*xn_set_rm
= xmlnode_get_child(xn_action
, "SetRM");
4620 sipmsg_add_header(msg
, "Content-Type", "application/x-ms-mim");
4622 if (xn_request_rm
) {
4623 //const char *rm = xmlnode_get_attrib(xn_request_rm, "uri");
4624 int bid
= xmlnode_get_int_attrib(xn_request_rm
, "bid", 0);
4625 gchar
*body
= g_strdup_printf(
4626 "<?xml version=\"1.0\"?>\r\n"
4627 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4628 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
4630 session
->bid
< bid
? "true" : "false");
4631 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4633 } else if (xn_set_rm
) {
4635 const char *rm
= xmlnode_get_attrib(xn_set_rm
, "uri");
4636 g_free(session
->roster_manager
);
4637 session
->roster_manager
= g_strdup(rm
);
4639 body
= g_strdup_printf(
4640 "<?xml version=\"1.0\"?>\r\n"
4641 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4642 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
4644 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
4647 xmlnode_free(xn_action
);
4652 /* looks like purple lacks typing notification for chat */
4653 if (!session
->is_multiparty
&& !session
->focus_uri
) {
4654 xmlnode
*xn_keyboard_activity
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4655 const char *status
= xmlnode_get_attrib(xmlnode_get_child(xn_keyboard_activity
, "status"),
4657 if (status
&& !strcmp(status
, "type")) {
4658 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
4659 } else if (status
&& !strcmp(status
, "idle")) {
4660 serv_got_typing_stopped(sip
->gc
, from
);
4662 xmlnode_free(xn_keyboard_activity
);
4665 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4670 static void process_incoming_bye(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4672 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4673 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4674 struct sip_session
*session
;
4675 struct sip_dialog
*dialog
;
4677 /* collect dialog identification
4678 * we need callid, ourtag and theirtag to unambiguously identify dialog
4680 /* take data before 'msg' will be modified by send_sip_response */
4681 dialog
= g_new0(struct sip_dialog
, 1);
4682 dialog
->callid
= g_strdup(callid
);
4683 dialog
->cseq
= parse_cseq(sipmsg_find_header(msg
, "CSeq"));
4684 dialog
->with
= g_strdup(from
);
4685 sipe_dialog_parse(dialog
, msg
, FALSE
);
4687 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4689 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4691 session
= sipe_session_find_im(sip
, from
);
4694 sipe_dialog_free(dialog
);
4699 if (session
->roster_manager
&& !g_strcasecmp(from
, session
->roster_manager
)) {
4700 g_free(session
->roster_manager
);
4701 session
->roster_manager
= NULL
;
4704 /* This what BYE is essentially for - terminating dialog */
4705 sipe_dialog_remove_3(session
, dialog
);
4706 sipe_dialog_free(dialog
);
4707 if (session
->focus_uri
&& !g_strcasecmp(from
, session
->im_mcu_uri
)) {
4708 sipe_conf_immcu_closed(sip
, session
);
4709 } else if (session
->is_multiparty
) {
4710 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session
->conv
), from
, NULL
);
4716 static void process_incoming_refer(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4718 gchar
*self
= sip_uri_self(sip
);
4719 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4720 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
4721 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
4722 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
4723 struct sip_session
*session
;
4724 struct sip_dialog
*dialog
;
4726 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4727 dialog
= sipe_dialog_find(session
, from
);
4729 if (!session
|| !dialog
|| !session
->roster_manager
|| strcmp(session
->roster_manager
, self
)) {
4730 send_sip_response(sip
->gc
, msg
, 500, "Server Internal Error", NULL
);
4732 send_sip_response(sip
->gc
, msg
, 202, "Accepted", NULL
);
4734 sipe_invite(sip
, session
, refer_to
, NULL
, referred_by
, FALSE
);
4740 g_free(referred_by
);
4744 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
4746 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
4747 struct sip_session
*session
;
4748 struct sip_dialog
*dialog
;
4750 if (state
== PURPLE_NOT_TYPING
)
4753 session
= sipe_session_find_im(sip
, who
);
4754 dialog
= sipe_dialog_find(session
, who
);
4756 if (session
&& dialog
&& dialog
->is_established
) {
4757 send_sip_request(gc
, "INFO", who
, who
,
4758 "Content-Type: application/xml\r\n",
4759 SIPE_SEND_TYPING
, dialog
, NULL
);
4761 return SIPE_TYPING_SEND_TIMEOUT
;
4764 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
4766 GSList
*tmp
= sip
->transactions
;
4767 time_t currtime
= time(NULL
);
4769 struct transaction
*trans
= tmp
->data
;
4771 purple_debug_info("sipe", "have open transaction age: %ld\n", (long int)currtime
-trans
->time
);
4772 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
4775 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
4777 sendout_sipmsg(sip
, trans
->msg
);
4784 static void do_reauthenticate_cb(struct sipe_account_data
*sip
,
4785 SIPE_UNUSED_PARAMETER
void *unused
)
4787 /* register again when security token expires */
4788 /* we have to start a new authentication as the security token
4789 * is almost expired by sending a not signed REGISTER message */
4790 purple_debug_info("sipe", "do a full reauthentication\n");
4791 sipe_auth_free(&sip
->registrar
);
4792 sipe_auth_free(&sip
->proxy
);
4793 sip
->registerstatus
= 0;
4795 sip
->reauthenticate_set
= FALSE
;
4798 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4802 gboolean found
= FALSE
;
4804 from
= parse_from(sipmsg_find_header(msg
, "From"));
4808 purple_debug_info("sipe", "got message from %s: %s\n", from
, msg
->body
);
4810 contenttype
= sipmsg_find_header(msg
, "Content-Type");
4811 if (!strncmp(contenttype
, "text/plain", 10)
4812 || !strncmp(contenttype
, "text/html", 9)
4813 || !strncmp(contenttype
, "multipart/related", 17)
4814 || !strncmp(contenttype
, "multipart/alternative", 21))
4816 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4817 gchar
*html
= get_html_message(contenttype
, msg
->body
);
4819 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
4821 session
= sipe_session_find_im(sip
, from
);
4824 if (session
&& session
->focus_uri
) { /* a conference */
4825 gchar
*tmp
= parse_from(sipmsg_find_header(msg
, "Ms-Sender"));
4826 gchar
*sender
= parse_from(tmp
);
4828 serv_got_chat_in(sip
->gc
, session
->chat_id
, sender
,
4829 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
4831 } else if (session
&& session
->is_multiparty
) { /* a multiparty chat */
4832 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
4833 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
4835 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
4838 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4841 } else if (!strncmp(contenttype
, "application/im-iscomposing+xml", 30)) {
4842 xmlnode
*isc
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
4847 purple_debug_info("sipe", "process_incoming_message: can not parse iscomposing\n");
4851 state
= xmlnode_get_child(isc
, "state");
4854 purple_debug_info("sipe", "process_incoming_message: no state found\n");
4859 statedata
= xmlnode_get_data(state
);
4861 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
4862 else serv_got_typing_stopped(sip
->gc
, from
);
4867 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
4871 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4872 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
4874 session
= sipe_session_find_im(sip
, from
);
4877 gchar
*msg
= g_strdup_printf(_("Received a message with unrecognized contents from %s"),
4879 sipe_present_err(sip
, session
, msg
);
4883 purple_debug_info("sipe", "got unknown mime-type '%s'\n", contenttype
);
4884 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
4889 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
4895 gboolean is_multiparty
= FALSE
;
4896 gboolean is_triggered
= FALSE
;
4897 gboolean was_multiparty
= TRUE
;
4898 gboolean just_joined
= FALSE
;
4900 gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4901 gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
4902 gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
4903 gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
4904 gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
4905 GSList
*end_points
= NULL
;
4907 struct sip_session
*session
;
4909 purple_debug_info("sipe", "process_incoming_invite: body:\n%s!\n", msg
->body
? tmp
= fix_newlines(msg
->body
) : "");
4912 /* Invitation to join conference */
4913 if (!strncmp(content_type
, "application/ms-conf-invite+xml", 30)) {
4914 process_incoming_invite_conf(sip
, msg
);
4918 /* Only accept text invitations */
4919 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
4920 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
4924 // TODO There *must* be a better way to clean up the To header to add a tag...
4925 purple_debug_info("sipe", "Adding a Tag to the To Header on Invite Request...\n");
4926 oldHeader
= sipmsg_find_header(msg
, "To");
4928 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
4929 sipmsg_remove_header_now(msg
, "To");
4930 sipmsg_add_header_now(msg
, "To", newHeader
);
4933 if (end_points_hdr
) {
4934 end_points
= sipmsg_parse_endpoints_header(end_points_hdr
);
4936 if (g_slist_length(end_points
) > 2) {
4937 is_multiparty
= TRUE
;
4940 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
4941 is_triggered
= TRUE
;
4942 is_multiparty
= TRUE
;
4945 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4946 /* Convert to multiparty */
4947 if (session
&& is_multiparty
&& !session
->is_multiparty
) {
4948 g_free(session
->with
);
4949 session
->with
= NULL
;
4950 was_multiparty
= FALSE
;
4951 session
->is_multiparty
= TRUE
;
4952 session
->chat_id
= rand();
4955 if (!session
&& is_multiparty
) {
4956 session
= sipe_session_find_or_add_chat_by_callid(sip
, callid
);
4959 from
= parse_from(sipmsg_find_header(msg
, "From"));
4961 session
= sipe_session_find_or_add_im(sip
, from
);
4965 g_free(session
->callid
);
4966 session
->callid
= g_strdup(callid
);
4968 session
->is_multiparty
= is_multiparty
;
4969 if (roster_manager
) {
4970 session
->roster_manager
= g_strdup(roster_manager
);
4974 if (is_multiparty
&& end_points
) {
4975 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
4976 GSList
*entry
= end_points
;
4978 struct sip_dialog
*dialog
;
4979 struct sipendpoint
*end_point
= entry
->data
;
4980 entry
= entry
->next
;
4982 if (!g_strcasecmp(from
, end_point
->contact
) ||
4983 !g_strcasecmp(to
, end_point
->contact
))
4986 dialog
= sipe_dialog_find(session
, end_point
->contact
);
4988 g_free(dialog
->theirepid
);
4989 dialog
->theirepid
= end_point
->epid
;
4990 end_point
->epid
= NULL
;
4992 dialog
= sipe_dialog_add(session
);
4994 dialog
->callid
= g_strdup(session
->callid
);
4995 dialog
->with
= end_point
->contact
;
4996 end_point
->contact
= NULL
;
4997 dialog
->theirepid
= end_point
->epid
;
4998 end_point
->epid
= NULL
;
5002 /* send triggered INVITE */
5003 sipe_invite(sip
, session
, dialog
->with
, NULL
, NULL
, TRUE
);
5010 GSList
*entry
= end_points
;
5012 struct sipendpoint
*end_point
= entry
->data
;
5013 entry
= entry
->next
;
5014 g_free(end_point
->contact
);
5015 g_free(end_point
->epid
);
5018 g_slist_free(end_points
);
5022 struct sip_dialog
*dialog
= sipe_dialog_find(session
, from
);
5024 purple_debug_info("sipe", "process_incoming_invite, session already has dialog!\n");
5026 dialog
= sipe_dialog_add(session
);
5028 dialog
->callid
= g_strdup(session
->callid
);
5029 dialog
->with
= g_strdup(from
);
5030 sipe_dialog_parse(dialog
, msg
, FALSE
);
5032 if (!dialog
->ourtag
) {
5033 dialog
->ourtag
= newTag
;
5040 purple_debug_info("sipe", "process_incoming_invite, failed to find or create IM session\n");
5044 if (is_multiparty
&& !session
->conv
) {
5045 gchar
*chat_title
= sipe_chat_get_name(callid
);
5046 gchar
*self
= sip_uri_self(sip
);
5047 /* create prpl chat */
5048 session
->conv
= serv_got_joined_chat(sip
->gc
, session
->chat_id
, chat_title
);
5049 session
->chat_title
= g_strdup(chat_title
);
5050 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session
->conv
), self
);
5052 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
5054 PURPLE_CBFLAGS_NONE
, FALSE
);
5059 if (is_multiparty
&& !was_multiparty
) {
5060 /* add current IM counterparty to chat */
5061 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
5062 sipe_dialog_first(session
)->with
, NULL
,
5063 PURPLE_CBFLAGS_NONE
, FALSE
);
5066 /* add inviting party to chat */
5067 if (just_joined
&& session
->conv
) {
5068 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
5070 PURPLE_CBFLAGS_NONE
, TRUE
);
5073 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5075 /* This used only in 2005 official client, not 2007 or Reuters.
5076 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5077 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5079 if (is_multiparty
) {
5080 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5081 gchar
*ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
5082 if (ms_text_format
) {
5083 if (g_str_has_prefix(ms_text_format
, "text/plain") || g_str_has_prefix(ms_text_format
, "text/html")) {
5085 gchar
*html
= get_html_message(ms_text_format
, NULL
);
5087 if (is_multiparty
) {
5088 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
5089 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
5091 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
5094 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
5103 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
5104 sipmsg_add_header(msg
, "User-Agent", sipe_get_useragent(sip
));
5105 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
5107 body
= g_strdup_printf(
5109 "o=- 0 0 IN IP4 %s\r\n"
5113 "m=%s %d sip sip:%s\r\n"
5114 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5115 purple_network_get_my_ip(-1),
5116 purple_network_get_my_ip(-1),
5117 sip
->ocs2007
? "message" : "x-ms-message",
5120 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
5124 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
5128 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5129 sipmsg_add_header(msg
, "User-Agent", sipe_get_useragent(sip
));
5130 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
5132 body
= g_strdup_printf(
5134 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5136 "c=IN IP4 0.0.0.0\r\n"
5138 "m=%s %d sip sip:%s\r\n"
5139 "a=accept-types:text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml\r\n",
5140 sip
->ocs2007
? "message" : "x-ms-message",
5143 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
5148 sipe_get_auth_scheme_name(struct sipe_account_data
*sip
)
5150 const char *res
= "NTLM";
5152 if (purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5156 (void) sip
; /* make compiler happy */
5161 static void sipe_connection_cleanup(struct sipe_account_data
*);
5162 static void create_connection(struct sipe_account_data
*, gchar
*, int);
5164 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
5165 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
5168 const gchar
*expires_header
;
5170 GSList
*hdr
= msg
->headers
;
5171 struct siphdrelement
*elem
;
5173 expires_header
= sipmsg_find_header(msg
, "Expires");
5174 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
5175 purple_debug_info("sipe", "process_register_response: got response to REGISTER; expires = %d\n", expires
);
5177 switch (msg
->response
) {
5180 sip
->registerstatus
= 0;
5182 gchar
*contact_hdr
= NULL
;
5187 gchar
*server_hdr
= sipmsg_find_header(msg
, "Server");
5188 const char *auth_scheme
;
5190 if (!sip
->reregister_set
) {
5191 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
5192 sipe_schedule_action(action_name
, expires
, do_register_cb
, NULL
, sip
, NULL
);
5193 g_free(action_name
);
5194 sip
->reregister_set
= TRUE
;
5197 sip
->registerstatus
= 3;
5199 if (server_hdr
&& !sip
->server_version
) {
5200 sip
->server_version
= g_strdup(server_hdr
);
5205 auth_scheme
= sipe_get_auth_scheme_name(sip
);
5206 tmp
= sipmsg_find_auth_header(msg
, auth_scheme
);
5209 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\n", tmp
? tmp
: "");
5210 fill_auth(tmp
, &sip
->registrar
);
5213 if (!sip
->reauthenticate_set
) {
5214 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
5215 guint reauth_timeout
;
5216 if (sip
->registrar
.type
== AUTH_TYPE_KERBEROS
&& sip
->registrar
.expires
> 0) {
5217 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5218 reauth_timeout
= sip
->registrar
.expires
- 300;
5220 /* NTLM: we have to reauthenticate as our security token expires
5221 after eight hours (be five minutes early) */
5222 reauth_timeout
= (8 * 3600) - 300;
5224 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, NULL
, sip
, NULL
);
5225 g_free(action_name
);
5226 sip
->reauthenticate_set
= TRUE
;
5229 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
5231 epid
= get_epid(sip
);
5232 uuid
= generateUUIDfromEPID(epid
);
5235 // There can be multiple Contact headers (one per location where the user is logged in) so
5236 // make sure to only get the one for this uuid
5237 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
5238 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
5239 if (valid_contact
) {
5240 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
5241 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "got gruu %s from contact hdr w/ right uuid: %s\n", gruu, contact_hdr);
5242 g_free(valid_contact
);
5245 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "ignoring contact hdr b/c not right uuid: %s\n", contact_hdr);
5250 g_free(sip
->contact
);
5252 sip
->contact
= g_strdup_printf("<%s>", gruu
);
5255 //purple_debug(PURPLE_DEBUG_MISC, "sipe", "didn't find gruu in a Contact hdr\n");
5256 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
);
5258 sip
->ocs2007
= FALSE
;
5259 sip
->batched_support
= FALSE
;
5264 if (!g_ascii_strcasecmp(elem
->name
, "Supported")) {
5265 if (!g_ascii_strcasecmp(elem
->value
, "msrtc-event-categories")) {
5266 /* We interpret this as OCS2007+ indicator */
5267 sip
->ocs2007
= TRUE
;
5268 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s (indicates OCS2007+)\n", elem
->value
);
5270 if (!g_ascii_strcasecmp(elem
->value
, "adhoclist")) {
5271 sip
->batched_support
= TRUE
;
5272 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Supported: %s\n", elem
->value
);
5275 if (!g_ascii_strcasecmp(elem
->name
, "Allow-Events")){
5276 gchar
**caps
= g_strsplit(elem
->value
,",",0);
5279 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
5280 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "Allow-Events: %s\n", caps
[i
]);
5285 hdr
= g_slist_next(hdr
);
5288 /* rejoin open chats to be able to use them by continue to send messages */
5289 purple_conversation_foreach(sipe_rejoin_chat
);
5292 if (!sip
->subscribed
) { //do it just once, not every re-register
5294 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-contacts",
5295 (GCompareFunc
)g_ascii_strcasecmp
)) {
5296 sipe_subscribe_roaming_contacts(sip
);
5299 /* For 2007+ it does not make sence to subscribe to:
5300 * vnd-microsoft-roaming-ACL
5301 * vnd-microsoft-provisioning (not v2)
5303 * These are for backward compatibility.
5307 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-self",
5308 (GCompareFunc
)g_ascii_strcasecmp
)) {
5309 sipe_subscribe_roaming_self(sip
);
5311 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-provisioning-v2",
5312 (GCompareFunc
)g_ascii_strcasecmp
)) {
5313 sipe_subscribe_roaming_provisioning_v2(sip
);
5316 /* For 2005- servers */
5319 //sipe_options_request(sip, sip->sipdomain);
5321 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-ACL",
5322 (GCompareFunc
)g_ascii_strcasecmp
)) {
5323 sipe_subscribe_roaming_acl(sip
);
5325 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-provisioning",
5326 (GCompareFunc
)g_ascii_strcasecmp
)) {
5327 sipe_subscribe_roaming_provisioning(sip
);
5329 if (g_slist_find_custom(sip
->allow_events
, "presence.wpending",
5330 (GCompareFunc
)g_ascii_strcasecmp
)) {
5331 sipe_subscribe_presence_wpending(sip
, msg
);
5334 /* For 2007+ we publish our initial statuses and calendar data only after
5335 * received our existing publications in sipe_process_roaming_self()
5336 * Only in this case we know versions of current publications made
5339 /* For 2005- we publish our initial statuses only after
5340 * received our existing UserInfo data in response to
5341 * self subscription.
5342 * Only in this case we won't override existing UserInfo data
5343 * set earlier or by other client on our behalf.
5347 sip
->subscribed
= TRUE
;
5350 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
5351 "timeout=", ";", NULL
);
5352 if (timeout
!= NULL
) {
5353 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
5354 purple_debug_info("sipe", "server determined keep alive timeout is %u seconds\n",
5355 sip
->keepalive_timeout
);
5359 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - got 200, removing CSeq: %d\n", sip
->cseq
);
5364 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
5366 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
5367 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
5371 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
5374 tmp
= g_strsplit(parts
[0], ":", 0);
5375 hostname
= g_strdup(tmp
[0]);
5376 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
5380 tmp
= g_strsplit(parts
[i
], "=", 0);
5382 if (g_strcasecmp("transport", tmp
[0]) == 0) {
5383 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
5384 transport
= SIPE_TRANSPORT_TCP
;
5385 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
5386 transport
= SIPE_TRANSPORT_UDP
;
5395 /* Close old connection */
5396 sipe_connection_cleanup(sip
);
5398 /* Create new connection */
5399 sip
->transport
= transport
;
5400 purple_debug_info("sipe", "process_register_response: redirected to host %s port %d transport %s\n",
5401 hostname
, port
, TRANSPORT_DESCRIPTOR
);
5402 create_connection(sip
, hostname
, port
);
5408 if (sip
->registerstatus
!= 2) {
5409 const char *auth_scheme
;
5410 purple_debug_info("sipe", "REGISTER retries %d\n", sip
->registrar
.retries
);
5411 if (sip
->registrar
.retries
> 3) {
5412 sip
->gc
->wants_to_die
= TRUE
;
5413 purple_connection_error(sip
->gc
, _("Wrong password"));
5417 auth_scheme
= sipe_get_auth_scheme_name(sip
);
5418 tmp
= sipmsg_find_auth_header(msg
, auth_scheme
);
5420 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_register_response - Auth header: %s\n", tmp
? tmp
: "");
5422 char *tmp2
= g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme
, NULL
);
5423 sip
->gc
->wants_to_die
= TRUE
;
5424 purple_connection_error(sip
->gc
, tmp2
);
5428 fill_auth(tmp
, &sip
->registrar
);
5429 sip
->registerstatus
= 2;
5430 if (sip
->account
->disconnecting
) {
5431 do_register_exp(sip
, 0);
5439 gchar
*warning
= sipmsg_find_header(msg
, "Warning");
5440 gchar
**reason
= NULL
;
5441 if (warning
!= NULL
) {
5443 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
5445 reason
= g_strsplit(warning
, "\"", 0);
5447 warning
= g_strdup_printf(_("You have been rejected by the server: %s"),
5448 (reason
&& reason
[1]) ? reason
[1] : _("no reason given"));
5451 sip
->gc
->wants_to_die
= TRUE
;
5452 purple_connection_error(sip
->gc
, warning
);
5459 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
5460 gchar
*reason
= NULL
;
5461 if (warning
!= NULL
) {
5462 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
5464 warning
= g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
5465 warning
? (reason
? reason
: _("no reason given")) :
5466 _("SIP is either not enabled for the destination URI or it does not exist"));
5469 sip
->gc
->wants_to_die
= TRUE
;
5470 purple_connection_error(sip
->gc
, warning
);
5476 case 504: /* Server time-out */
5478 gchar
*warning
= sipmsg_find_header(msg
, "ms-diagnostics");
5479 gchar
*reason
= NULL
;
5480 if (warning
!= NULL
) {
5481 reason
= sipmsg_find_part_of_header(warning
, "reason=\"", "\"", NULL
);
5483 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: "<a href=\"http://www.reuters.com\">http://www.reuters.com</a>"/*_("no reason given")*/);
5486 sip
->gc
->wants_to_die
= TRUE
;
5487 purple_connection_error(sip
->gc
, warning
);
5497 * Returns 2005-style activity and Availability.
5499 * @param status Sipe statis id.
5502 sipe_get_act_avail_by_status_2005(const char *status
,
5506 int avail
= 300; /* online */
5507 int act
= 400; /* Available */
5509 if (!strcmp(status
, SIPE_STATUS_ID_AWAY
)) {
5511 //} else if (!strcmp(status, SIPE_STATUS_ID_LUNCH)) {
5513 } else if (!strcmp(status
, SIPE_STATUS_ID_BRB
)) {
5515 } else if (!strcmp(status
, SIPE_STATUS_ID_AVAILABLE
)) {
5517 //} else if (!strcmp(status, SIPE_STATUS_ID_ON_PHONE)) {
5519 } else if (!strcmp(status
, SIPE_STATUS_ID_BUSY
) ||
5520 !strcmp(status
, SIPE_STATUS_ID_DND
)) {
5522 } else if (!strcmp(status
, SIPE_STATUS_ID_INVISIBLE
) ||
5523 !strcmp(status
, SIPE_STATUS_ID_OFFLINE
)) {
5524 avail
= 0; /* offline */
5527 act
= 400; /* Available */
5530 if (activity
) *activity
= act
;
5531 if (availability
) *availability
= avail
;
5537 * @param activity 2005 aggregated activity. Ex.: 600
5538 * @param availablity 2005 aggregated availablity. Ex.: 300
5541 sipe_get_status_by_act_avail_2005(const int activity
,
5542 const int availablity
,
5543 char **activity_desc
)
5545 const char *status_id
= NULL
;
5546 const char *act
= NULL
;
5548 if (activity
< 150) {
5549 status_id
= SIPE_STATUS_ID_AWAY
;
5550 } else if (activity
< 200) {
5551 //status_id = SIPE_STATUS_ID_LUNCH;
5552 status_id
= SIPE_STATUS_ID_AWAY
;
5553 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH
);
5554 } else if (activity
< 300) {
5555 //status_id = SIPE_STATUS_ID_IDLE;
5556 status_id
= SIPE_STATUS_ID_AWAY
;
5557 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE
);
5558 } else if (activity
< 400) {
5559 status_id
= SIPE_STATUS_ID_BRB
;
5560 } else if (activity
< 500) {
5561 status_id
= SIPE_STATUS_ID_AVAILABLE
;
5562 } else if (activity
< 600) {
5563 //status_id = SIPE_STATUS_ID_ON_PHONE;
5564 status_id
= SIPE_STATUS_ID_BUSY
;
5565 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE
);
5566 } else if (activity
< 700) {
5567 status_id
= SIPE_STATUS_ID_BUSY
;
5568 } else if (activity
< 800) {
5569 status_id
= SIPE_STATUS_ID_AWAY
;
5571 status_id
= SIPE_STATUS_ID_AVAILABLE
;
5574 if (availablity
< 100)
5575 status_id
= SIPE_STATUS_ID_OFFLINE
;
5577 if (activity_desc
&& act
) {
5578 g_free(*activity_desc
);
5579 *activity_desc
= g_strdup(act
);
5586 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
5589 sipe_get_status_by_availability(int avail
,
5590 char** activity_desc
)
5593 const char *act
= NULL
;
5596 status
= SIPE_STATUS_ID_OFFLINE
;
5597 } else if (avail
< 4500) {
5598 status
= SIPE_STATUS_ID_AVAILABLE
;
5599 } else if (avail
< 6000) {
5600 //status = SIPE_STATUS_ID_IDLE;
5601 status
= SIPE_STATUS_ID_AVAILABLE
;
5602 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE
);
5603 } else if (avail
< 7500) {
5604 status
= SIPE_STATUS_ID_BUSY
;
5605 } else if (avail
< 9000) {
5606 //status = SIPE_STATUS_ID_BUSYIDLE;
5607 status
= SIPE_STATUS_ID_BUSY
;
5608 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE
);
5609 } else if (avail
< 12000) {
5610 status
= SIPE_STATUS_ID_DND
;
5611 } else if (avail
< 15000) {
5612 status
= SIPE_STATUS_ID_BRB
;
5613 } else if (avail
< 18000) {
5614 status
= SIPE_STATUS_ID_AWAY
;
5616 status
= SIPE_STATUS_ID_OFFLINE
;
5619 if (activity_desc
&& act
) {
5620 g_free(*activity_desc
);
5621 *activity_desc
= g_strdup(act
);
5628 * Returns 2007-style availability value
5630 * @param sipe_status_id (in)
5631 * @param activity_token (out) Must be g_free()'d after use if consumed.
5634 sipe_get_availability_by_status(const char* sipe_status_id
, char** activity_token
)
5637 sipe_activity activity
= SIPE_ACTIVITY_UNSET
;
5639 if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_AWAY
)) {
5640 availability
= 15500;
5641 if (!activity_token
|| !(*activity_token
)) {
5642 activity
= SIPE_ACTIVITY_AWAY
;
5644 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_BRB
)) {
5645 availability
= 12500;
5646 activity
= SIPE_ACTIVITY_BRB
;
5647 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_DND
)) {
5648 availability
= 9500;
5649 activity
= SIPE_ACTIVITY_DND
;
5650 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_BUSY
)) {
5651 availability
= 6500;
5652 if (!activity_token
|| !(*activity_token
)) {
5653 activity
= SIPE_ACTIVITY_BUSY
;
5655 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_AVAILABLE
)) {
5656 availability
= 3500;
5657 activity
= SIPE_ACTIVITY_ONLINE
;
5658 } else if (!strcmp(sipe_status_id
, SIPE_STATUS_ID_UNKNOWN
)) {
5661 // Offline or invisible
5662 availability
= 18500;
5663 activity
= SIPE_ACTIVITY_OFFLINE
;
5666 if (activity_token
) {
5667 *activity_token
= g_strdup(sipe_activity_map
[activity
].token
);
5669 return availability
;
5672 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5675 xmlnode
*xn_categories
;
5676 xmlnode
*xn_category
;
5678 const char *status
= NULL
;
5679 gboolean do_update_status
= FALSE
;
5680 gboolean has_note_cleaned
= FALSE
;
5681 gboolean has_free_busy_cleaned
= FALSE
;
5683 xn_categories
= xmlnode_from_str(data
, len
);
5684 uri
= xmlnode_get_attrib(xn_categories
, "uri"); /* with 'sip:' prefix */
5686 for (xn_category
= xmlnode_get_child(xn_categories
, "category");
5688 xn_category
= xmlnode_get_next_twin(xn_category
) )
5691 const char *attrVar
= xmlnode_get_attrib(xn_category
, "name");
5692 time_t publish_time
= (tmp
= xmlnode_get_attrib(xn_category
, "publishTime")) ?
5693 sipe_utils_str_to_time(tmp
) : 0;
5696 if (!strcmp(attrVar
, "contactCard"))
5699 /* identity - Display Name and email */
5700 node
= xmlnode_get_descendant(xn_category
, "contactCard", "identity", NULL
);
5702 char* display_name
= xmlnode_get_data(
5703 xmlnode_get_descendant(node
, "name", "displayName", NULL
));
5704 char* email
= xmlnode_get_data(
5705 xmlnode_get_child(node
, "email"));
5707 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
5708 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
5710 g_free(display_name
);
5714 node
= xmlnode_get_descendant(xn_category
, "contactCard", "company", NULL
);
5716 char* company
= xmlnode_get_data(node
);
5717 sipe_update_user_info(sip
, uri
, COMPANY_PROP
, company
);
5721 node
= xmlnode_get_descendant(xn_category
, "contactCard", "department", NULL
);
5723 char* department
= xmlnode_get_data(node
);
5724 sipe_update_user_info(sip
, uri
, DEPARTMENT_PROP
, department
);
5728 node
= xmlnode_get_descendant(xn_category
, "contactCard", "title", NULL
);
5730 char* title
= xmlnode_get_data(node
);
5731 sipe_update_user_info(sip
, uri
, TITLE_PROP
, title
);
5735 node
= xmlnode_get_descendant(xn_category
, "contactCard", "office", NULL
);
5737 char* office
= xmlnode_get_data(node
);
5738 sipe_update_user_info(sip
, uri
, OFFICE_PROP
, office
);
5742 node
= xmlnode_get_descendant(xn_category
, "contactCard", "url", NULL
);
5744 char* site
= xmlnode_get_data(node
);
5745 sipe_update_user_info(sip
, uri
, SITE_PROP
, site
);
5749 for (node
= xmlnode_get_descendant(xn_category
, "contactCard", "phone", NULL
);
5751 node
= xmlnode_get_next_twin(node
))
5753 const char *phone_type
= xmlnode_get_attrib(node
, "type");
5754 char* phone
= xmlnode_get_data(xmlnode_get_child(node
, "uri"));
5755 char* phone_display_string
= xmlnode_get_data(xmlnode_get_child(node
, "displayString"));
5757 sipe_update_user_phone(sip
, uri
, phone_type
, phone
, phone_display_string
);
5760 g_free(phone_display_string
);
5763 for (node
= xmlnode_get_descendant(xn_category
, "contactCard", "address", NULL
);
5765 node
= xmlnode_get_next_twin(node
))
5767 if (!strcmp(xmlnode_get_attrib(node
, "type"), "work")) {
5768 char* street
= xmlnode_get_data(xmlnode_get_child(node
, "street"));
5769 char* city
= xmlnode_get_data(xmlnode_get_child(node
, "city"));
5770 char* state
= xmlnode_get_data(xmlnode_get_child(node
, "state"));
5771 char* zipcode
= xmlnode_get_data(xmlnode_get_child(node
, "zipcode"));
5772 char* country_code
= xmlnode_get_data(xmlnode_get_child(node
, "countryCode"));
5774 sipe_update_user_info(sip
, uri
, ADDRESS_STREET_PROP
, street
);
5775 sipe_update_user_info(sip
, uri
, ADDRESS_CITY_PROP
, city
);
5776 sipe_update_user_info(sip
, uri
, ADDRESS_STATE_PROP
, state
);
5777 sipe_update_user_info(sip
, uri
, ADDRESS_ZIPCODE_PROP
, zipcode
);
5778 sipe_update_user_info(sip
, uri
, ADDRESS_COUNTRYCODE_PROP
, country_code
);
5784 g_free(country_code
);
5791 else if (!strcmp(attrVar
, "note"))
5794 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
5796 if (!has_note_cleaned
) {
5797 has_note_cleaned
= TRUE
;
5799 g_free(sbuddy
->note
);
5800 sbuddy
->note
= NULL
;
5801 sbuddy
->is_oof_note
= FALSE
;
5802 sbuddy
->note_since
= publish_time
;
5804 do_update_status
= TRUE
;
5806 if (sbuddy
&& (publish_time
>= sbuddy
->note_since
)) {
5807 /* clean up in case no 'note' element is supplied
5808 * which indicate note removal in client
5810 g_free(sbuddy
->note
);
5811 sbuddy
->note
= NULL
;
5812 sbuddy
->is_oof_note
= FALSE
;
5813 sbuddy
->note_since
= publish_time
;
5815 xn_node
= xmlnode_get_descendant(xn_category
, "note", "body", NULL
);
5818 sbuddy
->note
= g_markup_escape_text((tmp
= xmlnode_get_data(xn_node
)), -1);
5820 sbuddy
->is_oof_note
= !strcmp(xmlnode_get_attrib(xn_node
, "type"), "OOF");
5821 sbuddy
->note_since
= publish_time
;
5823 purple_debug_info("sipe", "process_incoming_notify_rlmi: uri(%s), note(%s)\n",
5824 uri
, sbuddy
->note
? sbuddy
->note
: "");
5826 /* to trigger UI refresh in case no status info is supplied in this update */
5827 do_update_status
= TRUE
;
5832 else if(!strcmp(attrVar
, "state"))
5836 xmlnode
*xn_availability
;
5837 xmlnode
*xn_activity
;
5838 xmlnode
*xn_meeting_subject
;
5839 xmlnode
*xn_meeting_location
;
5840 struct sipe_buddy
*sbuddy
= uri
? g_hash_table_lookup(sip
->buddies
, uri
) : NULL
;
5842 xn_node
= xmlnode_get_child(xn_category
, "state");
5843 if (!xn_node
) continue;
5844 xn_availability
= xmlnode_get_child(xn_node
, "availability");
5845 if (!xn_availability
) continue;
5846 xn_activity
= xmlnode_get_child(xn_node
, "activity");
5847 xn_meeting_subject
= xmlnode_get_child(xn_node
, "meetingSubject");
5848 xn_meeting_location
= xmlnode_get_child(xn_node
, "meetingLocation");
5850 data
= xmlnode_get_data(xn_availability
);
5851 availability
= atoi(data
);
5854 /* activity, meeting_subject, meeting_location */
5859 g_free(sbuddy
->activity
);
5860 sbuddy
->activity
= NULL
;
5862 const char *token
= xmlnode_get_attrib(xn_activity
, "token");
5863 xmlnode
*xn_custom
= xmlnode_get_child(xn_activity
, "custom");
5866 if (!is_empty(token
)) {
5867 sbuddy
->activity
= g_strdup(sipe_get_activity_desc_by_token(token
));
5869 /* from custom element */
5871 char *custom
= xmlnode_get_data(xn_custom
);
5873 if (!is_empty(custom
)) {
5874 sbuddy
->activity
= custom
;
5880 /* meeting_subject */
5881 g_free(sbuddy
->meeting_subject
);
5882 sbuddy
->meeting_subject
= NULL
;
5883 if (xn_meeting_subject
) {
5884 char *meeting_subject
= xmlnode_get_data(xn_meeting_subject
);
5886 if (!is_empty(meeting_subject
)) {
5887 sbuddy
->meeting_subject
= meeting_subject
;
5888 meeting_subject
= NULL
;
5890 g_free(meeting_subject
);
5892 /* meeting_location */
5893 g_free(sbuddy
->meeting_location
);
5894 sbuddy
->meeting_location
= NULL
;
5895 if (xn_meeting_location
) {
5896 char *meeting_location
= xmlnode_get_data(xn_meeting_location
);
5898 if (!is_empty(meeting_location
)) {
5899 sbuddy
->meeting_location
= meeting_location
;
5900 meeting_location
= NULL
;
5902 g_free(meeting_location
);
5905 status
= sipe_get_status_by_availability(availability
, &tmp
);
5906 if (sbuddy
->activity
&& tmp
) {
5907 char *tmp2
= sbuddy
->activity
;
5909 sbuddy
->activity
= g_strdup_printf("%s, %s", sbuddy
->activity
, tmp
);
5913 sbuddy
->activity
= tmp
;
5917 do_update_status
= TRUE
;
5920 else if(!strcmp(attrVar
, "calendarData"))
5922 struct sipe_buddy
*sbuddy
= uri
? g_hash_table_lookup(sip
->buddies
, uri
) : NULL
;
5923 xmlnode
*xn_free_busy
= xmlnode_get_descendant(xn_category
, "calendarData", "freeBusy", NULL
);
5924 xmlnode
*xn_working_hours
= xmlnode_get_descendant(xn_category
, "calendarData", "WorkingHours", NULL
);
5926 if (sbuddy
&& xn_free_busy
) {
5927 if (!has_free_busy_cleaned
) {
5928 has_free_busy_cleaned
= TRUE
;
5930 g_free(sbuddy
->cal_start_time
);
5931 sbuddy
->cal_start_time
= NULL
;
5933 g_free(sbuddy
->cal_free_busy_base64
);
5934 sbuddy
->cal_free_busy_base64
= NULL
;
5936 g_free(sbuddy
->cal_free_busy
);
5937 sbuddy
->cal_free_busy
= NULL
;
5939 sbuddy
->cal_free_busy_published
= publish_time
;
5942 if (publish_time
>= sbuddy
->cal_free_busy_published
) {
5943 g_free(sbuddy
->cal_start_time
);
5944 sbuddy
->cal_start_time
= g_strdup(xmlnode_get_attrib(xn_free_busy
, "startTime"));
5946 sbuddy
->cal_granularity
= !g_ascii_strcasecmp(xmlnode_get_attrib(xn_free_busy
, "granularity"), "PT15M") ?
5949 g_free(sbuddy
->cal_free_busy_base64
);
5950 sbuddy
->cal_free_busy_base64
= xmlnode_get_data(xn_free_busy
);
5952 g_free(sbuddy
->cal_free_busy
);
5953 sbuddy
->cal_free_busy
= NULL
;
5955 sbuddy
->cal_free_busy_published
= publish_time
;
5957 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
);
5961 if (sbuddy
&& xn_working_hours
) {
5962 sipe_cal_parse_working_hours(xn_working_hours
, sbuddy
);
5967 if (do_update_status
) {
5968 if (!status
) { /* no status category in this update, using contact's current status */
5969 PurpleBuddy
*pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri
);
5970 const PurplePresence
*presence
= purple_buddy_get_presence(pbuddy
);
5971 const PurpleStatus
*pstatus
= purple_presence_get_active_status(presence
);
5972 status
= purple_status_get_id(pstatus
);
5975 purple_debug_info("sipe", "process_incoming_notify_rlmi: %s\n", status
);
5976 sipe_got_user_status(sip
, uri
, status
);
5979 xmlnode_free(xn_categories
);
5982 static void sipe_subscribe_poolfqdn_resource_uri(const char *host
, GSList
*server
, struct sipe_account_data
*sip
)
5984 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
5985 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: pool(%s)\n", host
);
5986 payload
->host
= g_strdup(host
);
5987 payload
->buddies
= server
;
5988 sipe_subscribe_presence_batched_routed(sip
, payload
);
5989 sipe_subscribe_presence_batched_routed_free(payload
);
5992 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
5995 xmlnode
*xn_resource
;
5996 GHashTable
*servers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
6001 xn_list
= xmlnode_from_str(data
, len
);
6003 for (xn_resource
= xmlnode_get_child(xn_list
, "resource");
6005 xn_resource
= xmlnode_get_next_twin(xn_resource
) )
6007 const char *uri
, *state
;
6008 xmlnode
*xn_instance
;
6010 xn_instance
= xmlnode_get_child(xn_resource
, "instance");
6011 if (!xn_instance
) continue;
6013 uri
= xmlnode_get_attrib(xn_resource
, "uri");
6014 state
= xmlnode_get_attrib(xn_instance
, "state");
6015 purple_debug_info("sipe", "process_incoming_notify_rlmi_resub: uri(%s),state(%s)\n", uri
, state
);
6017 if (strstr(state
, "resubscribe")) {
6018 const char *poolFqdn
= xmlnode_get_attrib(xn_instance
, "poolFqdn");
6020 if (poolFqdn
) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6021 gchar
*user
= g_strdup(uri
);
6022 host
= g_strdup(poolFqdn
);
6023 server
= g_hash_table_lookup(servers
, host
);
6024 server
= g_slist_append(server
, user
);
6025 g_hash_table_insert(servers
, host
, server
);
6027 sipe_subscribe_presence_single(sip
, (void *) uri
);
6032 /* Send out any deferred poolFqdn subscriptions */
6033 g_hash_table_foreach(servers
, (GHFunc
) sipe_subscribe_poolfqdn_resource_uri
, sip
);
6034 g_hash_table_destroy(servers
);
6036 xmlnode_free(xn_list
);
6039 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
6043 gchar
*activity
= NULL
;
6045 xmlnode
*basicstatus
= NULL
, *tuple
, *status
;
6046 gboolean isonline
= FALSE
;
6047 xmlnode
*display_name_node
;
6049 pidf
= xmlnode_from_str(data
, len
);
6051 purple_debug_info("sipe", "process_incoming_notify_pidf: no parseable pidf:%s\n",data
);
6055 uri
= sip_uri(xmlnode_get_attrib(pidf
, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6057 if ((tuple
= xmlnode_get_child(pidf
, "tuple")))
6059 if ((status
= xmlnode_get_child(tuple
, "status"))) {
6060 basicstatus
= xmlnode_get_child(status
, "basic");
6065 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic found\n");
6070 getbasic
= xmlnode_get_data(basicstatus
);
6072 purple_debug_info("sipe", "process_incoming_notify_pidf: no basic data found\n");
6077 purple_debug_info("sipe", "process_incoming_notify_pidf: basic-status(%s)\n", getbasic
);
6078 if (strstr(getbasic
, "open")) {
6083 display_name_node
= xmlnode_get_child(pidf
, "display-name");
6084 if (display_name_node
) {
6085 char * display_name
= xmlnode_get_data(display_name_node
);
6087 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
6088 g_free(display_name
);
6091 if ((tuple
= xmlnode_get_child(pidf
, "tuple"))) {
6092 if ((status
= xmlnode_get_child(tuple
, "status"))) {
6093 if ((basicstatus
= xmlnode_get_child(status
, "activities"))) {
6094 if ((basicstatus
= xmlnode_get_child(basicstatus
, "activity"))) {
6095 activity
= xmlnode_get_data(basicstatus
);
6096 purple_debug_info("sipe", "process_incoming_notify_pidf: activity(%s)\n", activity
);
6103 const gchar
* status_id
= NULL
;
6105 if (!strcmp(activity
, sipe_activity_map
[SIPE_ACTIVITY_BUSY
].token
)) {
6106 status_id
= SIPE_STATUS_ID_BUSY
;
6107 } else if (!strcmp(activity
, sipe_activity_map
[SIPE_ACTIVITY_AWAY
].token
)) {
6108 status_id
= SIPE_STATUS_ID_AWAY
;
6113 status_id
= SIPE_STATUS_ID_AVAILABLE
;
6116 purple_debug_info("sipe", "process_incoming_notify_pidf: status_id(%s)\n", status_id
);
6117 sipe_got_user_status(sip
, uri
, status_id
);
6119 sipe_got_user_status(sip
, uri
, SIPE_STATUS_ID_OFFLINE
);
6129 sipe_user_info_has_updated(struct sipe_account_data
*sip
,
6130 xmlnode
*xn_userinfo
)
6132 if (sip
->user_info
) {
6133 xmlnode_free(sip
->user_info
);
6135 sip
->user_info
= xmlnode_copy(xn_userinfo
);
6137 /* Publish initial state if not yet.
6138 * Assuming this happens on initial responce to self subscription
6139 * so we've already updated our UserInfo.
6141 if (!sip
->initial_state_published
) {
6142 send_presence_soap(sip
, FALSE
);
6144 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
6148 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
6150 char *activity
= NULL
;
6152 const char *status_id
= NULL
;
6155 char *self_uri
= sip_uri_self(sip
);
6158 const char *device_name
= NULL
;
6159 const char *cal_start_time
= NULL
;
6160 const char *cal_granularity
= NULL
;
6161 char *cal_free_busy_base64
= NULL
;
6162 struct sipe_buddy
*sbuddy
;
6164 xmlnode
*xn_presentity
;
6165 xmlnode
*xn_availability
;
6166 xmlnode
*xn_activity
;
6167 xmlnode
*xn_display_name
;
6169 xmlnode
*xn_phone_number
;
6170 xmlnode
*xn_userinfo
;
6174 xmlnode
*xn_contact
;
6176 char *free_activity
;
6178 const char *user_avail_nil
;
6180 time_t user_avail_since
= 0;
6181 time_t activity_since
= 0;
6183 /* fix for Reuters environment on Linux */
6184 if (data
&& strstr(data
, "encoding=\"utf-16\"")) {
6186 tmp_data
= replace(data
, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6187 xn_presentity
= xmlnode_from_str(tmp_data
, strlen(tmp_data
));
6190 xn_presentity
= xmlnode_from_str(data
, len
);
6193 xn_availability
= xmlnode_get_child(xn_presentity
, "availability");
6194 xn_activity
= xmlnode_get_child(xn_presentity
, "activity");
6195 xn_display_name
= xmlnode_get_child(xn_presentity
, "displayName");
6196 xn_email
= xmlnode_get_child(xn_presentity
, "email");
6197 xn_phone_number
= xmlnode_get_child(xn_presentity
, "phoneNumber");
6198 xn_userinfo
= xmlnode_get_child(xn_presentity
, "userInfo");
6199 xn_oof
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "oof") : NULL
;
6200 xn_state
= xn_userinfo
? xmlnode_get_descendant(xn_userinfo
, "states", "state", NULL
): NULL
;
6201 user_avail
= xn_state
? xmlnode_get_int_attrib(xn_state
, "avail", 0) : 0;
6202 user_avail_since
= xn_state
? sipe_utils_str_to_time(xmlnode_get_attrib(xn_state
, "since")) : 0;
6203 user_avail_nil
= xn_state
? xmlnode_get_attrib(xn_state
, "nil") : NULL
;
6204 xn_contact
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "contact") : NULL
;
6205 xn_note
= xn_userinfo
? xmlnode_get_child(xn_userinfo
, "note") : NULL
;
6206 note
= xn_note
? xmlnode_get_data(xn_note
) : NULL
;
6208 if (user_avail_nil
&& !strcmp(user_avail_nil
, "true")) { /* null-ed */
6210 user_avail_since
= 0;
6213 free_activity
= NULL
;
6215 name
= xmlnode_get_attrib(xn_presentity
, "uri"); /* without 'sip:' prefix */
6216 uri
= sip_uri_from_name(name
);
6217 avl
= xmlnode_get_int_attrib(xn_availability
, "aggregate", 0);
6218 epid
= xmlnode_get_attrib(xn_availability
, "epid");
6219 act
= xmlnode_get_int_attrib(xn_activity
, "aggregate", 0);
6221 status_id
= sipe_get_status_by_act_avail_2005(act
, avl
, &activity
);
6222 res_avail
= sipe_get_availability_by_status(status_id
, NULL
);
6223 if (user_avail
> res_avail
) {
6224 res_avail
= user_avail
;
6225 status_id
= sipe_get_status_by_availability(user_avail
, NULL
);
6228 if (xn_display_name
) {
6229 char *display_name
= g_strdup(xmlnode_get_attrib(xn_display_name
, "displayName"));
6230 char *email
= xn_email
? g_strdup(xmlnode_get_attrib(xn_email
, "email")) : NULL
;
6231 char *phone_label
= xn_phone_number
? g_strdup(xmlnode_get_attrib(xn_phone_number
, "label")) : NULL
;
6232 char *phone_number
= xn_phone_number
? g_strdup(xmlnode_get_attrib(xn_phone_number
, "number")) : NULL
;
6233 char *tel_uri
= sip_to_tel_uri(phone_number
);
6235 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
6236 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
6237 sipe_update_user_info(sip
, uri
, PHONE_PROP
, tel_uri
);
6238 sipe_update_user_info(sip
, uri
, PHONE_DISPLAY_PROP
, !is_empty(phone_label
) ? phone_label
: phone_number
);
6241 g_free(phone_label
);
6242 g_free(phone_number
);
6244 g_free(display_name
);
6249 for (node
= xmlnode_get_child(xn_contact
, "tel"); node
; node
= xmlnode_get_next_twin(node
))
6251 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6252 const char *phone_type
= xmlnode_get_attrib(node
, "type");
6253 char* phone
= xmlnode_get_data(node
);
6255 sipe_update_user_phone(sip
, uri
, phone_type
, phone
, NULL
);
6261 /* devicePresence */
6262 for (node
= xmlnode_get_descendant(xn_presentity
, "devices", "devicePresence", NULL
); node
; node
= xmlnode_get_next_twin(node
)) {
6263 xmlnode
*xn_device_name
;
6264 xmlnode
*xn_calendar_info
;
6269 if (!strcmp(xmlnode_get_attrib(node
, "epid"), epid
)) {
6270 xn_device_name
= xmlnode_get_child(node
, "deviceName");
6271 device_name
= xn_device_name
? xmlnode_get_attrib(xn_device_name
, "name") : NULL
;
6275 xn_calendar_info
= xmlnode_get_child(node
, "calendarInfo");
6276 if (xn_calendar_info
) {
6277 const char *cal_start_time_tmp
= xmlnode_get_attrib(xn_calendar_info
, "startTime");
6279 if (cal_start_time
) {
6280 time_t cal_start_time_t
= sipe_utils_str_to_time(cal_start_time
);
6281 time_t cal_start_time_t_tmp
= sipe_utils_str_to_time(cal_start_time_tmp
);
6283 if (cal_start_time_t_tmp
> cal_start_time_t
) {
6284 cal_start_time
= cal_start_time_tmp
;
6285 cal_granularity
= xmlnode_get_attrib(xn_calendar_info
, "granularity");
6286 cal_free_busy_base64
= xmlnode_get_data(xn_calendar_info
);
6288 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
);
6291 cal_start_time
= cal_start_time_tmp
;
6292 cal_granularity
= xmlnode_get_attrib(xn_calendar_info
, "granularity");
6293 cal_free_busy_base64
= xmlnode_get_data(xn_calendar_info
);
6295 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
);
6300 xn_state
= xmlnode_get_descendant(node
, "states", "state", NULL
);
6302 int dev_avail
= xmlnode_get_int_attrib(xn_state
, "avail", 0);
6303 time_t dev_avail_since
= sipe_utils_str_to_time(xmlnode_get_attrib(xn_state
, "since"));
6305 state
= xmlnode_get_data(xn_state
);
6306 if (dev_avail_since
> user_avail_since
&&
6307 dev_avail
>= res_avail
)
6309 res_avail
= dev_avail
;
6310 if (!is_empty(state
))
6312 if (!strcmp(state
, sipe_activity_map
[SIPE_ACTIVITY_ON_PHONE
].token
)) {
6313 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE
));
6314 } else if (!strcmp(state
, "presenting")) {
6315 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF
));
6319 activity_since
= dev_avail_since
;
6321 status_id
= sipe_get_status_by_availability(res_avail
, &activity
);
6328 if (xn_oof
&& res_avail
>= 15000) { /* 12000 in 2007 */
6329 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF
));
6333 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
6336 g_free(sbuddy
->activity
);
6337 sbuddy
->activity
= activity
;
6339 sbuddy
->activity_since
= activity_since
;
6341 sbuddy
->user_avail
= user_avail
;
6342 sbuddy
->user_avail_since
= user_avail_since
;
6344 g_free(sbuddy
->note
);
6345 sbuddy
->note
= NULL
;
6346 if (!is_empty(note
)) { sbuddy
->note
= g_markup_escape_text(note
, -1); }
6348 sbuddy
->is_oof_note
= (xn_oof
!= NULL
);
6350 g_free(sbuddy
->device_name
);
6351 sbuddy
->device_name
= NULL
;
6352 if (!is_empty(device_name
)) { sbuddy
->device_name
= g_strdup(device_name
); }
6354 if (!is_empty(cal_free_busy_base64
)) {
6355 g_free(sbuddy
->cal_start_time
);
6356 sbuddy
->cal_start_time
= g_strdup(cal_start_time
);
6358 sbuddy
->cal_granularity
= !g_ascii_strcasecmp(cal_granularity
, "PT15M") ? 15 : 0;
6360 g_free(sbuddy
->cal_free_busy_base64
);
6361 sbuddy
->cal_free_busy_base64
= cal_free_busy_base64
;
6363 g_free(sbuddy
->cal_free_busy
);
6364 sbuddy
->cal_free_busy
= NULL
;
6367 sbuddy
->last_non_cal_status_id
= status_id
;
6368 g_free(sbuddy
->last_non_cal_activity
);
6369 sbuddy
->last_non_cal_activity
= g_strdup(sbuddy
->activity
);
6371 if (!strcmp(sbuddy
->name
, self_uri
)) {
6372 if (!(sbuddy
->note
&& sip
->note
&& !strcmp(sbuddy
->note
, sip
->note
))) /* not same */
6374 sip
->is_oof_note
= sbuddy
->is_oof_note
;
6377 sip
->note
= g_strdup(sbuddy
->note
);
6379 sip
->note_since
= time(NULL
);
6382 g_free(sip
->status
);
6383 sip
->status
= g_strdup(sbuddy
->last_non_cal_status_id
);
6387 purple_debug_info("sipe", "process_incoming_notify_msrtc: status(%s)\n", status_id
);
6388 sipe_got_user_status(sip
, uri
, status_id
);
6390 if (!sip
->ocs2007
&& !strcmp(self_uri
, uri
)) {
6391 sipe_user_info_has_updated(sip
, xn_userinfo
);
6395 xmlnode_free(xn_presentity
);
6400 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
6402 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
6404 purple_debug_info("sipe", "sipe_process_presence: Content-Type: %s\n", ctype
? ctype
: "");
6406 if ( ctype
&& ( strstr(ctype
, "application/rlmi+xml")
6407 || strstr(ctype
, "application/msrtc-event-categories+xml") ) )
6409 const char *content
= msg
->body
;
6410 unsigned length
= msg
->bodylen
;
6411 PurpleMimeDocument
*mime
= NULL
;
6413 if (strstr(ctype
, "multipart"))
6415 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
6416 const char *content_type
;
6418 mime
= purple_mime_document_parse(doc
);
6419 parts
= purple_mime_document_get_parts(mime
);
6421 content
= purple_mime_part_get_data(parts
->data
);
6422 length
= purple_mime_part_get_length(parts
->data
);
6423 content_type
=purple_mime_part_get_field(parts
->data
,"Content-Type");
6424 if(content_type
&& strstr(content_type
,"application/rlmi+xml"))
6426 process_incoming_notify_rlmi_resub(sip
, content
, length
);
6428 else if(content_type
&& strstr(content_type
, "text/xml+msrtc.pidf"))
6430 process_incoming_notify_msrtc(sip
, content
, length
);
6434 process_incoming_notify_rlmi(sip
, content
, length
);
6436 parts
= parts
->next
;
6442 purple_mime_document_free(mime
);
6445 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
6447 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
6449 else if(strstr(ctype
, "application/rlmi+xml"))
6451 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
6454 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
6456 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
6460 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
6464 static void sipe_process_presence_timeout(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gchar
*who
, int timeout
)
6466 char *ctype
= sipmsg_find_header(msg
, "Content-Type");
6467 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
6469 purple_debug_info("sipe", "sipe_process_presence_timeout: Content-Type: %s\n", ctype
? ctype
: "");
6472 strstr(ctype
, "multipart") &&
6473 (strstr(ctype
, "application/rlmi+xml") ||
6474 strstr(ctype
, "application/msrtc-event-categories+xml"))) {
6475 char *doc
= g_strdup_printf("Content-Type: %s\r\n\r\n%s", ctype
, msg
->body
);
6476 PurpleMimeDocument
*mime
= purple_mime_document_parse(doc
);
6477 GList
*parts
= purple_mime_document_get_parts(mime
);
6478 GSList
*buddies
= NULL
;
6479 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
6482 xmlnode
*xml
= xmlnode_from_str(purple_mime_part_get_data(parts
->data
),
6483 purple_mime_part_get_length(parts
->data
));
6485 if (strcmp(xml
->name
, "list")) {
6486 gchar
*uri
= sip_uri(xmlnode_get_attrib(xml
, "uri"));
6488 buddies
= g_slist_append(buddies
, uri
);
6492 parts
= parts
->next
;
6495 if (mime
) purple_mime_document_free(mime
);
6497 payload
->host
= g_strdup(who
);
6498 payload
->buddies
= buddies
;
6499 sipe_schedule_action(action_name
, timeout
,
6500 sipe_subscribe_presence_batched_routed
,
6501 sipe_subscribe_presence_batched_routed_free
,
6503 purple_debug_info("sipe", "Resubscription multiple contacts with batched support & route(%s) in %d\n", who
, timeout
);
6506 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(who
));
6507 purple_debug_info("sipe", "Resubscription single contact with batched support(%s) in %d\n", who
, timeout
);
6509 g_free(action_name
);
6513 * Dispatcher for all incoming subscription information
6514 * whether it comes from NOTIFY, BENOTIFY requests or
6515 * piggy-backed to subscription's OK responce.
6517 * @param request whether initiated from BE/NOTIFY request or OK-response message.
6518 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
6520 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
6522 gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
6523 gchar
*event
= sipmsg_find_header(msg
, "Event");
6524 gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
6528 purple_debug_info("sipe", "process_incoming_notify: Event: %s\n\n%s\n",
6530 tmp
= fix_newlines(msg
->body
));
6532 purple_debug_info("sipe", "process_incoming_notify: subscription_state: %s\n", subscription_state
? subscription_state
: "");
6534 /* implicit subscriptions */
6535 if (content_type
&& purple_str_has_prefix(content_type
, "application/ms-imdn+xml")) {
6536 sipe_process_imdn(sip
, msg
);
6541 const gchar
*expires_header
;
6542 expires_header
= sipmsg_find_header(msg
, "Expires");
6543 timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
6544 purple_debug_info("sipe", "process_incoming_notify: subscription expires:%d\n", timeout
);
6545 timeout
= (timeout
- 120) > 120 ? (timeout
- 120) : timeout
; // 2 min ahead of expiration
6548 /* for one off subscriptions (send with Expire: 0) */
6549 if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-provisioning-v2"))
6551 sipe_process_provisioning_v2(sip
, msg
);
6553 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-provisioning"))
6555 sipe_process_provisioning(sip
, msg
);
6558 if (!subscription_state
|| strstr(subscription_state
, "active"))
6560 if (event
&& !g_ascii_strcasecmp(event
, "presence"))
6562 sipe_process_presence(sip
, msg
);
6564 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-contacts"))
6566 sipe_process_roaming_contacts(sip
, msg
);
6568 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-self"))
6570 sipe_process_roaming_self(sip
, msg
);
6572 else if (event
&& !g_ascii_strcasecmp(event
, "vnd-microsoft-roaming-ACL"))
6574 sipe_process_roaming_acl(sip
, msg
);
6576 else if (event
&& !g_ascii_strcasecmp(event
, "presence.wpending"))
6578 sipe_process_presence_wpending(sip
, msg
);
6580 else if (event
&& !g_ascii_strcasecmp(event
, "conference"))
6582 sipe_process_conference(sip
, msg
);
6586 /* The server sends status 'terminated' */
6587 if (subscription_state
&& strstr(subscription_state
, "terminated") ) {
6588 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
6589 gchar
*key
= sipe_get_subscription_key(event
, who
);
6591 purple_debug_info("sipe", "process_incoming_notify: server says that subscription to %s was terminated.\n", who
);
6594 if (g_hash_table_lookup(sip
->subscriptions
, key
)) {
6595 g_hash_table_remove(sip
->subscriptions
, key
);
6596 purple_debug_info("sipe", "process_subscribe_response: subscription dialog removed for: %s\n", key
);
6602 if (timeout
&& event
) {// For LSC 2005 and OCS 2007
6603 /*if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-contacts") &&
6604 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-contacts", (GCompareFunc)g_ascii_strcasecmp))
6606 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-contacts");
6607 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_contacts, NULL, sip, msg);
6608 g_free(action_name);
6610 else if (!g_ascii_strcasecmp(event, "vnd-microsoft-roaming-ACL") &&
6611 g_slist_find_custom(sip->allow_events, "vnd-microsoft-roaming-ACL", (GCompareFunc)g_ascii_strcasecmp))
6613 gchar *action_name = g_strdup_printf("<%s>", "vnd-microsoft-roaming-ACL");
6614 sipe_schedule_action(action_name, timeout, sipe_subscribe_roaming_acl, NULL, sip, msg);
6615 g_free(action_name);
6618 if (!g_ascii_strcasecmp(event
, "presence.wpending") &&
6619 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
6621 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
6622 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, NULL
, sip
, NULL
);
6623 g_free(action_name
);
6625 else if (!g_ascii_strcasecmp(event
, "presence") &&
6626 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
6628 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
6629 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
6630 if (sip
->batched_support
) {
6631 sipe_process_presence_timeout(sip
, msg
, who
, timeout
);
6634 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(who
));
6635 purple_debug_info("sipe", "Resubscription single contact (%s) in %d\n", who
, timeout
);
6637 g_free(action_name
);
6642 if (event
&& !g_ascii_strcasecmp(event
, "registration-notify"))
6644 sipe_process_registration_notify(sip
, msg
);
6647 /* The client responses on received a NOTIFY message */
6648 if (request
&& !benotify
)
6650 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
6655 * Whether user manually changed status or
6656 * it was changed automatically due to user
6657 * became inactive/active again
6660 sipe_is_user_state(struct sipe_account_data
*sip
)
6663 time_t now
= time(NULL
);
6665 purple_debug_info("sipe", "sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip
->idle_switch
))));
6666 purple_debug_info("sipe", "sipe_is_user_state: now : %s", asctime(localtime(&now
)));
6668 res
= ((now
- SIPE_IDLE_SET_DELAY
* 2) >= sip
->idle_switch
);
6670 purple_debug_info("sipe", "sipe_is_user_state: res = %s\n", res
? "USER" : "MACHINE");
6675 send_presence_soap0(struct sipe_account_data
*sip
,
6676 gboolean do_publish_calendar
,
6677 gboolean do_reset_status
)
6679 struct sipe_ews
* ews
= sip
->ews
;
6680 int availability
= 0;
6685 gchar
*res_note
= NULL
;
6686 gchar
*res_oof
= NULL
;
6687 const gchar
*note_pub
= NULL
;
6688 gchar
*states
= NULL
;
6689 gchar
*calendar_data
= NULL
;
6690 gchar
*epid
= get_epid(sip
);
6691 time_t now
= time(NULL
);
6692 gchar
*since_time_str
= sipe_utils_time_to_str(now
);
6693 const gchar
*oof_note
= ews
? sipe_ews_get_oof_note(ews
) : NULL
;
6694 const char *user_input
;
6695 gboolean pub_oof
= ews
&& oof_note
&& (!sip
->note
|| ews
->updated
> sip
->note_since
);
6697 if (oof_note
&& sip
->note
) {
6698 purple_debug_info("sipe", "ews->oof_start : %s", asctime(localtime(&(ews
->oof_start
))));
6699 purple_debug_info("sipe", "sip->note_since : %s", asctime(localtime(&(sip
->note_since
))));
6702 purple_debug_info("sipe", "sip->note : %s", sip
->note
? sip
->note
: "");
6704 if (!sip
->initial_state_published
||
6707 g_free(sip
->status
);
6708 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
6711 sipe_get_act_avail_by_status_2005(sip
->status
, &activity
, &availability
);
6715 note_pub
= oof_note
;
6716 res_oof
= SIPE_SOAP_SET_PRESENCE_OOF_XML
;
6717 ews
->published
= TRUE
;
6718 } else if (sip
->note
) {
6719 if (sip
->is_oof_note
&& !oof_note
) { /* stale OOF note, as it's not present in ews already */
6722 sip
->is_oof_note
= FALSE
;
6723 sip
->note_since
= 0;
6725 note_pub
= sip
->note
;
6726 res_oof
= sip
->is_oof_note
? SIPE_SOAP_SET_PRESENCE_OOF_XML
: "";
6732 /* to protocol internal plain text format */
6733 tmp
= purple_markup_strip_html(note_pub
);
6734 res_note
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML
, tmp
);
6739 if (!do_reset_status
) {
6740 if (sipe_is_user_state(sip
) && !do_publish_calendar
&& sip
->initial_state_published
)
6742 gchar
*activity_token
= NULL
;
6743 int avail_2007
= sipe_get_availability_by_status(sip
->status
, &activity_token
);
6745 states
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES
,
6750 g_free(activity_token
);
6752 else /* preserve existing publication */
6755 if (sip
->user_info
&& (xn_states
= xmlnode_get_child(sip
->user_info
, "states"))) {
6756 states
= xmlnode_to_str(xn_states
, NULL
);
6757 /* this is a hack-around to remove added newline after inner element,
6758 * state in this case, where it shouldn't be.
6759 * After several use of xmlnode_to_str, amount of added newlines
6760 * grows significantly.
6762 purple_str_strip_char(states
, '\n');
6763 //purple_str_strip_char(states, '\r');
6767 /* do nothing - then User state will be erased */
6769 sip
->initial_state_published
= TRUE
;
6772 if (ews
&& (!is_empty(ews
->legacy_dn
) || !is_empty(ews
->email
)) && ews
->fb_start
&& !is_empty(ews
->free_busy
))
6774 char *fb_start_str
= sipe_utils_time_to_str(ews
->fb_start
);
6775 char *free_busy_base64
= sipe_cal_get_freebusy_base64(ews
->free_busy
);
6776 calendar_data
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR
,
6777 !is_empty(ews
->legacy_dn
) ? ews
->legacy_dn
: ews
->email
,
6780 g_free(fb_start_str
);
6781 g_free(free_busy_base64
);
6784 user_input
= !sipe_is_user_state(sip
) && sip
->status
!= SIPE_STATUS_ID_AVAILABLE
? "idle" : "active";
6786 /* forming resulting XML */
6787 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
,
6791 (tmp
= g_ascii_strup(sipe_get_host_name(), -1)),
6792 res_note
? res_note
: "",
6793 res_oof
? res_oof
: "",
6794 states
? states
: "",
6795 calendar_data
? calendar_data
: "",
6804 g_free(calendar_data
);
6806 send_soap_request(sip
, body
);
6809 g_free(since_time_str
);
6814 send_presence_soap(struct sipe_account_data
*sip
,
6815 gboolean do_publish_calendar
)
6817 return send_presence_soap0(sip
, do_publish_calendar
, FALSE
);
6822 process_send_presence_category_publish_response(struct sipe_account_data
*sip
,
6824 struct transaction
*trans
)
6826 gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
6828 if (msg
->response
== 409 && g_str_has_prefix(contenttype
, "application/msrtc-fault+xml")) {
6834 gboolean has_device_publication
= FALSE
;
6836 xml
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
6838 /* test if version mismatch fault */
6839 fault_code
= xmlnode_get_data(xmlnode_get_child(xml
, "Faultcode"));
6840 if (strcmp(fault_code
, "Client.BadCall.WrongDelta")) {
6841 purple_debug_info("sipe", "process_send_presence_category_publish_response: unsupported fault code:%s returning.\n", fault_code
);
6848 /* accumulating information about faulty versions */
6849 faults
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
6850 for (node
= xmlnode_get_descendant(xml
, "details", "operation", NULL
);
6852 node
= xmlnode_get_next_twin(node
))
6854 const gchar
*index
= xmlnode_get_attrib(node
, "index");
6855 const gchar
*curVersion
= xmlnode_get_attrib(node
, "curVersion");
6857 g_hash_table_insert(faults
, g_strdup(index
), g_strdup(curVersion
));
6858 purple_debug_info("sipe", "fault added: index:%s curVersion:%s\n", index
, curVersion
);
6862 /* here we are parsing own request to figure out what publication
6863 * referensed here only by index went wrong
6865 xml
= xmlnode_from_str(trans
->msg
->body
, trans
->msg
->bodylen
);
6868 for (node
= xmlnode_get_descendant(xml
, "publications", "publication", NULL
),
6869 index_our
= 1; /* starts with 1 - our first publication */
6871 node
= xmlnode_get_next_twin(node
), index_our
++)
6873 gchar
*idx
= g_strdup_printf("%d", index_our
);
6874 const gchar
*curVersion
= g_hash_table_lookup(faults
, idx
);
6875 const gchar
*categoryName
= xmlnode_get_attrib(node
, "categoryName");
6878 if (!strcmp("device", categoryName
)) {
6879 has_device_publication
= TRUE
;
6882 if (curVersion
) { /* fault exist on this index */
6883 const gchar
*container
= xmlnode_get_attrib(node
, "container");
6884 const gchar
*instance
= xmlnode_get_attrib(node
, "instance");
6885 /* key is <category><instance><container> */
6886 gchar
*key
= g_strdup_printf("<%s><%s><%s>", categoryName
, instance
, container
);
6887 struct sipe_publication
*publication
=
6888 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, categoryName
), key
);
6890 purple_debug_info("sipe", "key is %s\n", key
);
6893 purple_debug_info("sipe", "Updating %s with version %s. Was %d before.\n",
6894 key
, curVersion
, publication
->version
);
6895 /* updating publication's version to the correct one */
6896 publication
->version
= atoi(curVersion
);
6902 g_hash_table_destroy(faults
);
6904 /* rebublishing with right versions */
6905 if (has_device_publication
) {
6906 send_publish_category_initial(sip
);
6908 send_presence_status(sip
);
6915 * Returns 'device' XML part for publication.
6916 * Must be g_free'd after use.
6919 sipe_publish_get_category_device(struct sipe_account_data
*sip
)
6923 gchar
*epid
= get_epid(sip
);
6924 gchar
*uuid
= generateUUIDfromEPID(epid
);
6925 guint device_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_DEVICE
);
6926 /* key is <category><instance><container> */
6927 gchar
*key
= g_strdup_printf("<%s><%u><%u>", "device", device_instance
, 2);
6928 struct sipe_publication
*publication
=
6929 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "device"), key
);
6934 uri
= sip_uri_self(sip
);
6935 doc
= g_strdup_printf(SIPE_PUB_XML_DEVICE
,
6937 publication
? publication
->version
: 0,
6940 "00:00:00+01:00", /* @TODO make timezone real*/
6941 sipe_get_host_name()
6951 * A service method - use
6952 * - send_publish_get_category_state_machine and
6953 * - send_publish_get_category_state_user instead.
6954 * Must be g_free'd after use.
6957 sipe_publish_get_category_state(struct sipe_account_data
*sip
,
6958 gboolean is_user_state
)
6960 int availability
= sipe_get_availability_by_status(sip
->status
, NULL
);
6961 guint instance
= is_user_state
? sipe_get_pub_instance(sip
, SIPE_PUB_STATE_USER
) :
6962 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_MACHINE
);
6963 /* key is <category><instance><container> */
6964 gchar
*key_2
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 2);
6965 gchar
*key_3
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 3);
6966 struct sipe_publication
*publication_2
=
6967 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_2
);
6968 struct sipe_publication
*publication_3
=
6969 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_3
);
6974 if (publication_2
&& (publication_2
->availability
== availability
))
6976 purple_debug_info("sipe", "sipe_publish_get_category_state: state has NOT changed. Exiting.\n");
6977 return NULL
; /* nothing to update */
6980 return g_strdup_printf( is_user_state
? SIPE_PUB_XML_STATE_USER
: SIPE_PUB_XML_STATE_MACHINE
,
6982 publication_2
? publication_2
->version
: 0,
6985 publication_3
? publication_3
->version
: 0,
6990 * Only Busy and OOF calendar event are published.
6991 * Different instances are used for that.
6993 * Must be g_free'd after use.
6996 sipe_publish_get_category_state_calendar(struct sipe_account_data
*sip
,
6997 struct sipe_cal_event
*event
,
7001 gchar
*start_time_str
;
7002 int availability
= 0;
7004 guint instance
= (cal_satus
== SIPE_CAL_OOF
) ?
7005 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR_OOF
) :
7006 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR
);
7008 /* key is <category><instance><container> */
7009 gchar
*key_2
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 2);
7010 gchar
*key_3
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 3);
7011 struct sipe_publication
*publication_2
=
7012 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_2
);
7013 struct sipe_publication
*publication_3
=
7014 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_3
);
7019 if (!publication_3
&& !event
) { /* was nothing, have nothing, exiting */
7020 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7021 "Exiting as no publication and no event for cal_satus:%d\n", cal_satus
);
7027 (publication_3
->availability
== availability
) &&
7028 !strcmp(publication_3
->cal_event_hash
, sipe_cal_event_hash(event
)))
7030 purple_debug_info("sipe", "sipe_publish_get_category_state_calendar: "
7031 "cal state has NOT changed for cal_satus:%d. Exiting.\n", cal_satus
);
7032 return NULL
; /* nothing to update */
7036 (event
->cal_status
== SIPE_CAL_BUSY
||
7037 event
->cal_status
== SIPE_CAL_OOF
))
7039 gchar
*availability_xml_str
= NULL
;
7040 gchar
*activity_xml_str
= NULL
;
7042 if (event
->cal_status
== SIPE_CAL_BUSY
) {
7043 availability_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL
, 6500);
7046 if (event
->cal_status
== SIPE_CAL_BUSY
&& event
->is_meeting
) {
7047 activity_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
,
7048 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].token
,
7049 "minAvailability=\"6500\"",
7050 "maxAvailability=\"8999\"");
7051 } else if (event
->cal_status
== SIPE_CAL_OOF
) {
7052 activity_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
,
7053 sipe_activity_map
[SIPE_ACTIVITY_OOF
].token
,
7054 "minAvailability=\"12000\"",
7057 start_time_str
= sipe_utils_time_to_str(event
->start_time
);
7059 res
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR
,
7061 publication_2
? publication_2
->version
: 0,
7064 availability_xml_str
? availability_xml_str
: "",
7065 activity_xml_str
? activity_xml_str
: "",
7066 event
->subject
? event
->subject
: "",
7067 event
->location
? event
->location
: "",
7070 publication_3
? publication_3
->version
: 0,
7073 availability_xml_str
? availability_xml_str
: "",
7074 activity_xml_str
? activity_xml_str
: "",
7075 event
->subject
? event
->subject
: "",
7076 event
->location
? event
->location
: ""
7078 g_free(start_time_str
);
7079 g_free(availability_xml_str
);
7080 g_free(activity_xml_str
);
7083 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7085 res
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR
,
7087 publication_2
? publication_2
->version
: 0,
7090 publication_3
? publication_3
->version
: 0
7098 * Returns 'machineState' XML part for publication.
7099 * Must be g_free'd after use.
7102 sipe_publish_get_category_state_machine(struct sipe_account_data
*sip
)
7104 return sipe_publish_get_category_state(sip
, FALSE
);
7108 * Returns 'userState' XML part for publication.
7109 * Must be g_free'd after use.
7112 sipe_publish_get_category_state_user(struct sipe_account_data
*sip
)
7114 return sipe_publish_get_category_state(sip
, TRUE
);
7118 * Compares two strings even in case both are NULL/empty
7121 sipe_is_equal(const char* n1
, const char* n2
) {
7122 return ((!n1
|| !strlen(n1
)) && (!n2
|| !strlen(n2
))) /* both empty */
7123 || (n1
&& n2
&& !strcmp(n1
, n2
)); /* or not empty and equal */
7127 * Returns 'note' XML part for publication.
7128 * Must be g_free'd after use.
7130 * Protocol format for Note is plain text.
7132 * @param note a note in Sipe internal HTML format
7133 * @param note_type either personal or OOF
7136 sipe_publish_get_category_note(struct sipe_account_data
*sip
,
7137 const char *note
, /* html */
7138 const char *note_type
,
7142 guint instance
= !strcmp("OOF", note_type
) ? sipe_get_pub_instance(sip
, SIPE_PUB_NOTE_OOF
) : 0;
7143 /* key is <category><instance><container> */
7144 gchar
*key_note_200
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 200);
7145 gchar
*key_note_300
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 300);
7146 gchar
*key_note_400
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 400);
7148 struct sipe_publication
*publication_note_200
=
7149 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_200
);
7150 struct sipe_publication
*publication_note_300
=
7151 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_300
);
7152 struct sipe_publication
*publication_note_400
=
7153 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_400
);
7155 char *tmp
= note
? purple_markup_strip_html(note
) : NULL
;
7156 char *n1
= tmp
? g_markup_escape_text(tmp
, -1) : NULL
;
7157 const char *n2
= publication_note_200
? publication_note_200
->note
: NULL
;
7158 char *res
, *tmp1
, *tmp2
, *tmp3
;
7159 char *start_time_attr
;
7160 char *end_time_attr
;
7164 g_free(key_note_200
);
7165 g_free(key_note_300
);
7166 g_free(key_note_400
);
7168 /* we even need to republish empty note */
7169 if (n1
&& n2
&& !strcmp(n1
, n2
))
7171 purple_debug_info("sipe", "sipe_publish_get_category_note: note has NOT changed. Exiting.\n");
7173 return NULL
; /* nothing to update */
7176 start_time_attr
= note_start
? g_strdup_printf(" startTime=\"%s\"", (tmp
= sipe_utils_time_to_str(note_start
))) : NULL
;
7179 end_time_attr
= note_end
? g_strdup_printf(" endTime=\"%s\"", (tmp
= sipe_utils_time_to_str(note_end
))) : NULL
;
7183 tmp1
= g_strdup_printf(SIPE_PUB_XML_NOTE
,
7186 publication_note_200
? publication_note_200
->version
: 0,
7188 start_time_attr
? start_time_attr
: "",
7189 end_time_attr
? end_time_attr
: "",
7192 tmp2
= g_strdup_printf(SIPE_PUB_XML_NOTE
,
7195 publication_note_300
? publication_note_300
->version
: 0,
7197 start_time_attr
? start_time_attr
: "",
7198 end_time_attr
? end_time_attr
: "",
7201 tmp3
= g_strdup_printf(SIPE_PUB_XML_NOTE
,
7204 publication_note_400
? publication_note_400
->version
: 0,
7206 start_time_attr
? start_time_attr
: "",
7207 end_time_attr
? end_time_attr
: "",
7210 tmp1
= g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR
,
7214 publication_note_200
? publication_note_200
->version
: 0,
7216 tmp2
= g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR
,
7220 publication_note_200
? publication_note_200
->version
: 0,
7222 tmp3
= g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR
,
7226 publication_note_200
? publication_note_200
->version
: 0,
7229 res
= g_strconcat(tmp1
, tmp2
, tmp3
, NULL
);
7231 g_free(start_time_attr
);
7232 g_free(end_time_attr
);
7242 * Returns 'calendarData' XML part with WorkingHours for publication.
7243 * Must be g_free'd after use.
7246 sipe_publish_get_category_cal_working_hours(struct sipe_account_data
*sip
)
7248 struct sipe_ews
* ews
= sip
->ews
;
7250 /* key is <category><instance><container> */
7251 gchar
*key_cal_1
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7252 gchar
*key_cal_100
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7253 gchar
*key_cal_200
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7254 gchar
*key_cal_300
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7255 gchar
*key_cal_400
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7256 gchar
*key_cal_32000
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7258 struct sipe_publication
*publication_cal_1
=
7259 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_1
);
7260 struct sipe_publication
*publication_cal_100
=
7261 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_100
);
7262 struct sipe_publication
*publication_cal_200
=
7263 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_200
);
7264 struct sipe_publication
*publication_cal_300
=
7265 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_300
);
7266 struct sipe_publication
*publication_cal_400
=
7267 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_400
);
7268 struct sipe_publication
*publication_cal_32000
=
7269 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_32000
);
7271 const char *n1
= ews
? ews
->working_hours_xml_str
: NULL
;
7272 const char *n2
= publication_cal_300
? publication_cal_300
->working_hours_xml_str
: NULL
;
7275 g_free(key_cal_100
);
7276 g_free(key_cal_200
);
7277 g_free(key_cal_300
);
7278 g_free(key_cal_400
);
7279 g_free(key_cal_32000
);
7281 if (!ews
|| is_empty(ews
->email
) || is_empty(ews
->working_hours_xml_str
)) {
7282 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: no data to publish, exiting\n");
7286 if (sipe_is_equal(n1
, n2
))
7288 purple_debug_info("sipe", "sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.\n");
7289 return NULL
; /* nothing to update */
7292 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS
,
7294 publication_cal_1
? publication_cal_1
->version
: 0,
7296 ews
->working_hours_xml_str
,
7298 publication_cal_100
? publication_cal_100
->version
: 0,
7300 publication_cal_200
? publication_cal_200
->version
: 0,
7302 ews
->working_hours_xml_str
,
7304 publication_cal_300
? publication_cal_300
->version
: 0,
7306 ews
->working_hours_xml_str
,
7307 /* 400 - Personal */
7308 publication_cal_400
? publication_cal_400
->version
: 0,
7310 ews
->working_hours_xml_str
,
7311 /* 32000 - Blocked */
7312 publication_cal_32000
? publication_cal_32000
->version
: 0
7317 * Returns 'calendarData' XML part with FreeBusy for publication.
7318 * Must be g_free'd after use.
7321 sipe_publish_get_category_cal_free_busy(struct sipe_account_data
*sip
)
7323 struct sipe_ews
* ews
= sip
->ews
;
7324 guint cal_data_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_CALENDAR_DATA
);
7326 char *free_busy_base64
;
7331 /* key is <category><instance><container> */
7332 gchar
*key_cal_1
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 1);
7333 gchar
*key_cal_100
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 100);
7334 gchar
*key_cal_200
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 200);
7335 gchar
*key_cal_300
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 300);
7336 gchar
*key_cal_400
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 400);
7337 gchar
*key_cal_32000
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 32000);
7339 struct sipe_publication
*publication_cal_1
=
7340 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_1
);
7341 struct sipe_publication
*publication_cal_100
=
7342 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_100
);
7343 struct sipe_publication
*publication_cal_200
=
7344 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_200
);
7345 struct sipe_publication
*publication_cal_300
=
7346 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_300
);
7347 struct sipe_publication
*publication_cal_400
=
7348 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_400
);
7349 struct sipe_publication
*publication_cal_32000
=
7350 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_32000
);
7353 g_free(key_cal_100
);
7354 g_free(key_cal_200
);
7355 g_free(key_cal_300
);
7356 g_free(key_cal_400
);
7357 g_free(key_cal_32000
);
7359 if (!ews
|| is_empty(ews
->email
) || !ews
->fb_start
|| is_empty(ews
->free_busy
)) {
7360 purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: no data to publish, exiting\n");
7364 fb_start_str
= sipe_utils_time_to_str(ews
->fb_start
);
7365 free_busy_base64
= sipe_cal_get_freebusy_base64(ews
->free_busy
);
7367 st
= publication_cal_300
? publication_cal_300
->fb_start_str
: NULL
;
7368 fb
= publication_cal_300
? publication_cal_300
->free_busy_base64
: NULL
;
7370 /* we will rebuplish the same data to refresh publication time,
7371 * so if data from multiple sources, most recent will be choosen
7373 //if (sipe_is_equal(st, fb_start_str) && sipe_is_equal(fb, free_busy_base64))
7375 // purple_debug_info("sipe", "sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.\n");
7376 // g_free(fb_start_str);
7377 // g_free(free_busy_base64);
7378 // return NULL; /* nothing to update */
7381 res
= g_strdup_printf(SIPE_PUB_XML_FREE_BUSY
,
7384 publication_cal_1
? publication_cal_1
->version
: 0,
7387 publication_cal_100
? publication_cal_100
->version
: 0,
7390 publication_cal_200
? publication_cal_200
->version
: 0,
7396 publication_cal_300
? publication_cal_300
->version
: 0,
7400 /* 400 - Personal */
7402 publication_cal_400
? publication_cal_400
->version
: 0,
7406 /* 32000 - Blocked */
7408 publication_cal_32000
? publication_cal_32000
->version
: 0
7411 g_free(fb_start_str
);
7412 g_free(free_busy_base64
);
7416 static void send_presence_publish(struct sipe_account_data
*sip
, const char *publications
)
7423 uri
= sip_uri_self(sip
);
7424 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
,
7428 tmp
= get_contact(sip
);
7429 hdr
= g_strdup_printf("Contact: %s\r\n"
7430 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
7432 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
7441 send_publish_category_initial(struct sipe_account_data
*sip
)
7443 gchar
*pub_device
= sipe_publish_get_category_device(sip
);
7445 gchar
*publications
;
7447 g_free(sip
->status
);
7448 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
); /* our initial state */
7450 pub_machine
= sipe_publish_get_category_state_machine(sip
);
7451 publications
= g_strdup_printf("%s%s",
7453 pub_machine
? pub_machine
: "");
7455 g_free(pub_machine
);
7457 send_presence_publish(sip
, publications
);
7458 g_free(publications
);
7462 send_presence_category_publish(struct sipe_account_data
*sip
)
7464 gchar
*pub_state
= sipe_is_user_state(sip
) ?
7465 sipe_publish_get_category_state_user(sip
) :
7466 sipe_publish_get_category_state_machine(sip
);
7467 gchar
*pub_note
= sipe_publish_get_category_note(sip
,
7469 sip
->is_oof_note
? "OOF" : "personal",
7472 gchar
*publications
;
7474 if (!pub_state
&& !pub_note
) {
7475 purple_debug_info("sipe", "send_presence_category_publish: nothing has changed. Exiting.\n");
7479 publications
= g_strdup_printf("%s%s",
7480 pub_state
? pub_state
: "",
7481 pub_note
? pub_note
: "");
7486 send_presence_publish(sip
, publications
);
7487 g_free(publications
);
7491 * Publishes self status
7492 * based on own calendar information.
7497 publish_calendar_status_self(struct sipe_account_data
*sip
)
7499 struct sipe_cal_event
* event
= NULL
;
7500 gchar
*pub_cal_working_hours
= NULL
;
7501 gchar
*pub_cal_free_busy
= NULL
;
7502 gchar
*pub_calendar
= NULL
;
7503 gchar
*pub_calendar2
= NULL
;
7504 gchar
*pub_oof_note
= NULL
;
7505 const gchar
*oof_note
;
7506 time_t oof_start
= 0;
7510 purple_debug_info("sipe", "publish_calendar_status_self() no calendar data.\n");
7514 purple_debug_info("sipe", "publish_calendar_status_self() started.\n");
7515 if (sip
->ews
->cal_events
) {
7516 event
= sipe_cal_get_event(sip
->ews
->cal_events
, time(NULL
));
7520 purple_debug_info("sipe", "publish_calendar_status_self: current event is NULL\n");
7522 char *desc
= sipe_cal_event_describe(event
);
7523 purple_debug_info("sipe", "publish_calendar_status_self: current event is:\n%s", desc
? desc
: "");
7529 OOF publish, Busy clean
7531 OOF clean, Busy publish
7533 OOF clean, Busy clean
7535 if (event
&& event
->cal_status
== SIPE_CAL_OOF
) {
7536 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, event
, sip
->ews
->email
, SIPE_CAL_OOF
);
7537 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7538 } else if (event
&& event
->cal_status
== SIPE_CAL_BUSY
) {
7539 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_OOF
);
7540 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, event
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7542 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_OOF
);
7543 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_BUSY
);
7546 oof_note
= sipe_ews_get_oof_note(sip
->ews
);
7547 if (!strcmp("Scheduled", sip
->ews
->oof_state
)) {
7548 oof_start
= sip
->ews
->oof_start
;
7549 oof_end
= sip
->ews
->oof_end
;
7551 pub_oof_note
= sipe_publish_get_category_note(sip
, oof_note
, "OOF", oof_start
, oof_end
);
7553 pub_cal_working_hours
= sipe_publish_get_category_cal_working_hours(sip
);
7554 pub_cal_free_busy
= sipe_publish_get_category_cal_free_busy(sip
);
7556 if (!pub_cal_working_hours
&& !pub_cal_free_busy
&& !pub_calendar
&& !pub_calendar2
&& !pub_oof_note
) {
7557 purple_debug_info("sipe", "publish_calendar_status_self: nothing has changed.\n");
7559 gchar
*publications
= g_strdup_printf("%s%s%s%s%s",
7560 pub_cal_working_hours
? pub_cal_working_hours
: "",
7561 pub_cal_free_busy
? pub_cal_free_busy
: "",
7562 pub_calendar
? pub_calendar
: "",
7563 pub_calendar2
? pub_calendar2
: "",
7564 pub_oof_note
? pub_oof_note
: "");
7566 send_presence_publish(sip
, publications
);
7567 g_free(publications
);
7570 g_free(pub_cal_working_hours
);
7571 g_free(pub_cal_free_busy
);
7572 g_free(pub_calendar
);
7573 g_free(pub_calendar2
);
7574 g_free(pub_oof_note
);
7576 /* repeat scheduling */
7577 sipe_sched_calendar_status_self_publish(sip
, time(NULL
));
7580 static void send_presence_status(struct sipe_account_data
*sip
)
7582 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
7584 if (!status
) return;
7586 purple_debug_info("sipe", "send_presence_status: status: %s (%s)\n",
7587 purple_status_get_id(status
) ? purple_status_get_id(status
) : "",
7588 sipe_is_user_state(sip
) ? "USER" : "MACHINE");
7591 send_presence_category_publish(sip
);
7593 send_presence_soap(sip
, FALSE
);
7597 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
7599 gboolean found
= FALSE
;
7600 const char *method
= msg
->method
? msg
->method
: "NOT FOUND";
7601 purple_debug_info("sipe", "msg->response(%d),msg->method(%s)\n",msg
->response
,method
);
7602 if (msg
->response
== 0) { /* request */
7603 if (!strcmp(method
, "MESSAGE")) {
7604 process_incoming_message(sip
, msg
);
7606 } else if (!strcmp(method
, "NOTIFY")) {
7607 purple_debug_info("sipe","send->process_incoming_notify\n");
7608 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
7610 } else if (!strcmp(method
, "BENOTIFY")) {
7611 purple_debug_info("sipe","send->process_incoming_benotify\n");
7612 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
7614 } else if (!strcmp(method
, "INVITE")) {
7615 process_incoming_invite(sip
, msg
);
7617 } else if (!strcmp(method
, "REFER")) {
7618 process_incoming_refer(sip
, msg
);
7620 } else if (!strcmp(method
, "OPTIONS")) {
7621 process_incoming_options(sip
, msg
);
7623 } else if (!strcmp(method
, "INFO")) {
7624 process_incoming_info(sip
, msg
);
7626 } else if (!strcmp(method
, "ACK")) {
7627 // ACK's don't need any response
7629 } else if (!strcmp(method
, "SUBSCRIBE")) {
7630 // LCS 2005 sends us these - just respond 200 OK
7632 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
7633 } else if (!strcmp(method
, "BYE")) {
7634 process_incoming_bye(sip
, msg
);
7637 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
7639 } else { /* response */
7640 struct transaction
*trans
= transactions_find(sip
, msg
);
7642 if (msg
->response
== 407) {
7643 gchar
*resend
, *auth
, *ptmp
;
7645 if (sip
->proxy
.retries
> 30) return;
7646 sip
->proxy
.retries
++;
7647 /* do proxy authentication */
7649 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
7651 fill_auth(ptmp
, &sip
->proxy
);
7652 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
7653 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
7654 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
7656 resend
= sipmsg_to_string(trans
->msg
);
7657 /* resend request */
7658 sendout_pkt(sip
->gc
, resend
);
7661 if (msg
->response
< 200) {
7662 /* ignore provisional response */
7663 purple_debug_info("sipe", "got provisional (%d) response, ignoring\n", msg
->response
);
7665 sip
->proxy
.retries
= 0;
7666 if (!strcmp(trans
->msg
->method
, "REGISTER")) {
7667 if (msg
->response
== 401)
7669 sip
->registrar
.retries
++;
7673 sip
->registrar
.retries
= 0;
7675 purple_debug_info("sipe", "RE-REGISTER CSeq: %d\n", sip
->cseq
);
7677 if (msg
->response
== 401) {
7678 gchar
*resend
, *auth
, *ptmp
;
7679 const char* auth_scheme
;
7681 if (sip
->registrar
.retries
> 4) return;
7682 sip
->registrar
.retries
++;
7684 auth_scheme
= sipe_get_auth_scheme_name(sip
);
7685 ptmp
= sipmsg_find_auth_header(msg
, auth_scheme
);
7687 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - Auth header: %s\n", ptmp
? ptmp
: "");
7689 char *tmp2
= g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme
, NULL
);
7690 sip
->gc
->wants_to_die
= TRUE
;
7691 purple_connection_error(sip
->gc
, tmp2
);
7696 fill_auth(ptmp
, &sip
->registrar
);
7697 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
7698 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
7699 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
7701 //sipmsg_remove_header_now(trans->msg, "Authorization");
7702 //sipmsg_add_header(trans->msg, "Authorization", auth);
7704 resend
= sipmsg_to_string(trans
->msg
);
7705 /* resend request */
7706 sendout_pkt(sip
->gc
, resend
);
7711 if (trans
->callback
) {
7712 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - we have a transaction callback\n");
7713 /* call the callback to process response*/
7714 (trans
->callback
)(sip
, msg
, trans
);
7717 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "process_input_message - removing CSeq %d\n", sip
->cseq
);
7718 transactions_remove(sip
, trans
);
7724 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received response to unknown transaction\n");
7728 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "received a unknown sip message with method %s and response %d\n", method
, msg
->response
);
7732 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
7741 /* according to the RFC remove CRLF at the beginning */
7742 while (*cur
== '\r' || *cur
== '\n') {
7745 if (cur
!= conn
->inbuf
) {
7746 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
7747 conn
->inbufused
= strlen(conn
->inbuf
);
7750 /* Received a full Header? */
7751 sip
->processing_input
= TRUE
;
7752 while (sip
->processing_input
&&
7753 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
7754 time_t currtime
= time(NULL
);
7757 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
7759 msg
= sipmsg_parse_header(conn
->inbuf
);
7762 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
7763 if (msg
&& restlen
>= msg
->bodylen
) {
7764 dummy
= g_malloc(msg
->bodylen
+ 1);
7765 memcpy(dummy
, cur
, msg
->bodylen
);
7766 dummy
[msg
->bodylen
] = '\0';
7768 cur
+= msg
->bodylen
;
7769 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
7770 conn
->inbufused
= strlen(conn
->inbuf
);
7773 purple_debug_info("sipe", "process_input: body too short (%d < %d, strlen %d) - ignoring message\n", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
7780 purple_debug_info("sipe", "body:\n%s", msg->body);
7783 // Verify the signature before processing it
7784 if (sip
->registrar
.gssapi_context
) {
7785 struct sipmsg_breakdown msgbd
;
7786 gchar
*signature_input_str
;
7789 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
7790 signature_input_str
= sipmsg_breakdown_get_string(&msgbd
);
7792 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
7794 if (rspauth
!= NULL
) {
7795 if (!sip_sec_verify_signature(sip
->registrar
.gssapi_context
, signature_input_str
, rspauth
)) {
7796 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature validated\n");
7797 process_input_message(sip
, msg
);
7799 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "incoming message's signature is invalid.\n");
7800 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
7801 sip
->gc
->wants_to_die
= TRUE
;
7803 } else if (msg
->response
== 401) {
7804 purple_connection_error(sip
->gc
, _("Wrong password"));
7805 sip
->gc
->wants_to_die
= TRUE
;
7807 g_free(signature_input_str
);
7810 sipmsg_breakdown_free(&msgbd
);
7812 process_input_message(sip
, msg
);
7819 static void sipe_udp_process(gpointer data
, gint source
,
7820 SIPE_UNUSED_PARAMETER PurpleInputCondition con
)
7822 PurpleConnection
*gc
= data
;
7823 struct sipe_account_data
*sip
= gc
->proto_data
;
7826 static char buffer
[65536];
7827 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
7828 time_t currtime
= time(NULL
);
7831 purple_debug_info("sipe", "received - %s######\n%s\n#######\n", ctime(&currtime
), buffer
);
7832 msg
= sipmsg_parse_msg(buffer
);
7833 if (msg
) process_input_message(sip
, msg
);
7837 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
7839 struct sipe_account_data
*sip
= gc
->proto_data
;
7840 PurpleSslConnection
*gsc
= sip
->gsc
;
7842 purple_debug_error("sipe", "%s",debug
);
7843 purple_connection_error(gc
, msg
);
7845 /* Invalidate this connection. Next send will open a new one */
7847 connection_remove(sip
, gsc
->fd
);
7848 purple_ssl_close(gsc
);
7854 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
7855 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7857 PurpleConnection
*gc
= data
;
7858 struct sipe_account_data
*sip
;
7859 struct sip_connection
*conn
;
7861 gboolean firstread
= TRUE
;
7863 /* NOTE: This check *IS* necessary */
7864 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
7865 purple_ssl_close(gsc
);
7869 sip
= gc
->proto_data
;
7870 conn
= connection_find(sip
, gsc
->fd
);
7872 purple_debug_error("sipe", "Connection not found; Please try to connect again.\n");
7873 gc
->wants_to_die
= TRUE
;
7874 purple_connection_error(gc
, _("Connection not found. Please try to connect again"));
7878 /* Read all available data from the SSL connection */
7880 /* Increase input buffer size as needed */
7881 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
7882 conn
->inbuflen
+= SIMPLE_BUF_INC
;
7883 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
7884 purple_debug_info("sipe", "sipe_input_cb_ssl: new input buffer length %d\n", conn
->inbuflen
);
7887 /* Try to read as much as there is space left in the buffer */
7888 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
7889 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
7891 if (len
< 0 && errno
== EAGAIN
) {
7892 /* Try again later */
7894 } else if (len
< 0) {
7895 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
7897 } else if (firstread
&& (len
== 0)) {
7898 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
7902 conn
->inbufused
+= len
;
7905 /* Equivalence indicates that there is possibly more data to read */
7906 } while (len
== readlen
);
7908 conn
->inbuf
[conn
->inbufused
] = '\0';
7909 process_input(sip
, conn
);
7913 static void sipe_input_cb(gpointer data
, gint source
,
7914 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7916 PurpleConnection
*gc
= data
;
7917 struct sipe_account_data
*sip
= gc
->proto_data
;
7919 struct sip_connection
*conn
= connection_find(sip
, source
);
7921 purple_debug_error("sipe", "Connection not found!\n");
7925 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
7926 conn
->inbuflen
+= SIMPLE_BUF_INC
;
7927 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
7930 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
7932 if (len
< 0 && errno
== EAGAIN
)
7934 else if (len
<= 0) {
7935 purple_debug_info("sipe", "sipe_input_cb: read error\n");
7936 connection_remove(sip
, source
);
7937 if (sip
->fd
== source
) sip
->fd
= -1;
7941 conn
->inbufused
+= len
;
7942 conn
->inbuf
[conn
->inbufused
] = '\0';
7944 process_input(sip
, conn
);
7947 /* Callback for new connections on incoming TCP port */
7948 static void sipe_newconn_cb(gpointer data
, gint source
,
7949 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7951 PurpleConnection
*gc
= data
;
7952 struct sipe_account_data
*sip
= gc
->proto_data
;
7953 struct sip_connection
*conn
;
7955 int newfd
= accept(source
, NULL
, NULL
);
7957 conn
= connection_create(sip
, newfd
);
7959 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
7962 static void login_cb(gpointer data
, gint source
,
7963 SIPE_UNUSED_PARAMETER
const gchar
*error_message
)
7965 PurpleConnection
*gc
= data
;
7966 struct sipe_account_data
*sip
;
7967 struct sip_connection
*conn
;
7969 if (!PURPLE_CONNECTION_IS_VALID(gc
))
7977 purple_connection_error(gc
, _("Could not connect"));
7981 sip
= gc
->proto_data
;
7983 sip
->last_keepalive
= time(NULL
);
7985 conn
= connection_create(sip
, source
);
7989 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
7992 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
7993 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
7995 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
7996 if (sip
== NULL
) return;
8001 static guint
sipe_ht_hash_nick(const char *nick
)
8003 char *lc
= g_utf8_strdown(nick
, -1);
8004 guint bucket
= g_str_hash(lc
);
8010 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
8012 return (purple_utf8_strcasecmp(nick1
, nick2
) == 0);
8015 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
8017 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
8019 sip
->listen_data
= NULL
;
8021 if (listenfd
== -1) {
8022 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
8028 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
8029 sip
->listenfd
= sip
->fd
;
8031 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
8033 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
8037 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
,
8038 SIPE_UNUSED_PARAMETER
const char *error_message
)
8040 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
8042 sip
->query_data
= NULL
;
8044 if (!hosts
|| !hosts
->data
) {
8045 purple_connection_error(sip
->gc
, _("Could not resolve hostname"));
8049 hosts
= g_slist_remove(hosts
, hosts
->data
);
8050 g_free(sip
->serveraddr
);
8051 sip
->serveraddr
= hosts
->data
;
8052 hosts
= g_slist_remove(hosts
, hosts
->data
);
8054 hosts
= g_slist_remove(hosts
, hosts
->data
);
8055 g_free(hosts
->data
);
8056 hosts
= g_slist_remove(hosts
, hosts
->data
);
8059 /* create socket for incoming connections */
8060 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
8061 sipe_udp_host_resolved_listen_cb
, sip
);
8062 if (sip
->listen_data
== NULL
) {
8063 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
8068 static const struct sipe_service_data
*current_service
= NULL
;
8070 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
8071 PurpleSslErrorType error
,
8074 PurpleConnection
*gc
= data
;
8075 struct sipe_account_data
*sip
;
8077 /* If the connection is already disconnected, we don't need to do anything else */
8078 if (!PURPLE_CONNECTION_IS_VALID(gc
))
8081 sip
= gc
->proto_data
;
8082 current_service
= sip
->service_data
;
8083 if (current_service
) {
8084 purple_debug_info("sipe", "current_service: transport '%s' service '%s'\n",
8085 current_service
->transport
? current_service
->transport
: "NULL",
8086 current_service
->service
? current_service
->service
: "NULL");
8093 case PURPLE_SSL_CONNECT_FAILED
:
8094 purple_connection_error(gc
, _("Connection failed"));
8096 case PURPLE_SSL_HANDSHAKE_FAILED
:
8097 purple_connection_error(gc
, _("SSL handshake failed"));
8099 case PURPLE_SSL_CERTIFICATE_INVALID
:
8100 purple_connection_error(gc
, _("SSL certificate invalid"));
8106 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
8108 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
8109 PurpleProxyConnectData
*connect_data
;
8111 sip
->listen_data
= NULL
;
8113 sip
->listenfd
= listenfd
;
8114 if (sip
->listenfd
== -1) {
8115 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
8119 purple_debug_info("sipe", "listenfd: %d\n", sip
->listenfd
);
8120 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8121 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
8122 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
8123 sipe_newconn_cb
, sip
->gc
);
8124 purple_debug_info("sipe", "connecting to %s port %d\n",
8125 sip
->realhostname
, sip
->realport
);
8126 /* open tcp connection to the server */
8127 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
8128 sip
->realport
, login_cb
, sip
->gc
);
8130 if (connect_data
== NULL
) {
8131 purple_connection_error(sip
->gc
, _("Could not create socket"));
8135 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
8137 PurpleAccount
*account
= sip
->account
;
8138 PurpleConnection
*gc
= sip
->gc
;
8141 port
= (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
8144 sip
->realhostname
= hostname
;
8145 sip
->realport
= port
;
8147 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "create_connection - hostname: %s port: %d\n",
8150 /* TODO: is there a good default grow size? */
8151 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
8152 sip
->txbuf
= purple_circ_buffer_new(0);
8154 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
8156 if (!purple_ssl_is_supported()) {
8157 gc
->wants_to_die
= TRUE
;
8158 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8162 purple_debug_info("sipe", "using SSL\n");
8164 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
8165 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
8166 if (sip
->gsc
== NULL
) {
8167 purple_connection_error(gc
, _("Could not create SSL context"));
8170 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
8172 purple_debug_info("sipe", "using UDP\n");
8174 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
8175 if (sip
->query_data
== NULL
) {
8176 purple_connection_error(gc
, _("Could not resolve hostname"));
8180 purple_debug_info("sipe", "using TCP\n");
8181 /* create socket for incoming connections */
8182 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
8183 sipe_tcp_connect_listen_cb
, sip
);
8184 if (sip
->listen_data
== NULL
) {
8185 purple_connection_error(gc
, _("Could not create listen socket"));
8191 /* Service list for autodection */
8192 static const struct sipe_service_data service_autodetect
[] = {
8193 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
8194 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
8195 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
8196 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
8200 /* Service list for SSL/TLS */
8201 static const struct sipe_service_data service_tls
[] = {
8202 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
8203 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
8207 /* Service list for TCP */
8208 static const struct sipe_service_data service_tcp
[] = {
8209 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
8210 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
8214 /* Service list for UDP */
8215 static const struct sipe_service_data service_udp
[] = {
8216 { "sip", "udp", SIPE_TRANSPORT_UDP
},
8220 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
8221 static void resolve_next_service(struct sipe_account_data
*sip
,
8222 const struct sipe_service_data
*start
)
8225 sip
->service_data
= start
;
8227 sip
->service_data
++;
8228 if (sip
->service_data
->service
== NULL
) {
8230 /* Try connecting to the SIP hostname directly */
8231 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "no SRV records found; using SIP domain as fallback\n");
8232 if (sip
->auto_transport
) {
8233 // If SSL is supported, default to using it; OCS servers aren't configured
8234 // by default to accept TCP
8235 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8236 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
8237 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "set transport type..\n");
8240 hostname
= g_strdup(sip
->sipdomain
);
8241 create_connection(sip
, hostname
, 0);
8246 /* Try to resolve next service */
8247 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
8248 sip
->service_data
->transport
,
8253 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
8255 struct sipe_account_data
*sip
= data
;
8257 sip
->srv_query_data
= NULL
;
8259 /* find the host to connect to */
8261 gchar
*hostname
= g_strdup(resp
->hostname
);
8262 int port
= resp
->port
;
8263 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "srvresolved - SRV hostname: %s port: %d\n",
8267 sip
->transport
= sip
->service_data
->type
;
8269 create_connection(sip
, hostname
, port
);
8271 resolve_next_service(sip
, NULL
);
8275 static void sipe_login(PurpleAccount
*account
)
8277 PurpleConnection
*gc
;
8278 struct sipe_account_data
*sip
;
8279 gchar
**signinname_login
, **userserver
;
8280 const char *transport
;
8283 const char *username
= purple_account_get_username(account
);
8284 gc
= purple_account_get_connection(account
);
8286 purple_debug_info("sipe", "sipe_login: username '%s'\n", username
);
8288 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
8289 gc
->wants_to_die
= TRUE
;
8290 purple_connection_error(gc
, _("SIP Exchange user name contains invalid characters"));
8294 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
8295 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
8296 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
8298 sip
->account
= account
;
8299 sip
->reregister_set
= FALSE
;
8300 sip
->reauthenticate_set
= FALSE
;
8301 sip
->subscribed
= FALSE
;
8302 sip
->subscribed_buddies
= FALSE
;
8303 sip
->initial_state_published
= FALSE
;
8305 /* username format: <username>,[<optional login>] */
8306 signinname_login
= g_strsplit(username
, ",", 2);
8307 purple_debug_info("sipe", "sipe_login: signinname[0] '%s'\n", signinname_login
[0]);
8309 /* ensure that username format is name@domain */
8310 if (!strchr(signinname_login
[0], '@') || g_str_has_prefix(signinname_login
[0], "@") || g_str_has_suffix(signinname_login
[0], "@")) {
8311 g_strfreev(signinname_login
);
8312 gc
->wants_to_die
= TRUE
;
8313 purple_connection_error(gc
, _("User name should be a valid SIP URI\nExample: user@company.com"));
8316 sip
->username
= g_strdup(signinname_login
[0]);
8318 /* ensure that email format is name@domain if provided */
8319 email
= purple_account_get_string(sip
->account
, "email", NULL
);
8320 if (!is_empty(email
) &&
8321 (!strchr(email
, '@') || g_str_has_prefix(email
, "@") || g_str_has_suffix(email
, "@")))
8323 gc
->wants_to_die
= TRUE
;
8324 purple_connection_error(gc
, _("Email address should be valid if provided\nExample: user@company.com"));
8327 sip
->email
= !is_empty(email
) ? g_strdup(email
) : g_strdup(sip
->username
);
8329 /* login name specified? */
8330 if (signinname_login
[1] && strlen(signinname_login
[1])) {
8331 gchar
**domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
8332 gboolean has_domain
= domain_user
[1] != NULL
;
8333 purple_debug_info("sipe", "sipe_login: signinname[1] '%s'\n", signinname_login
[1]);
8334 sip
->authdomain
= has_domain
? g_strdup(domain_user
[0]) : NULL
;
8335 sip
->authuser
= g_strdup(domain_user
[has_domain
? 1 : 0]);
8336 purple_debug_info("sipe", "sipe_login: auth domain '%s' user '%s'\n",
8337 sip
->authdomain
? sip
->authdomain
: "", sip
->authuser
);
8338 g_strfreev(domain_user
);
8341 userserver
= g_strsplit(signinname_login
[0], "@", 2);
8342 purple_debug_info("sipe", "sipe_login: user '%s' server '%s'\n", userserver
[0], userserver
[1]);
8343 purple_connection_set_display_name(gc
, userserver
[0]);
8344 sip
->sipdomain
= g_strdup(userserver
[1]);
8345 g_strfreev(userserver
);
8346 g_strfreev(signinname_login
);
8348 if (strchr(sip
->username
, ' ') != NULL
) {
8349 gc
->wants_to_die
= TRUE
;
8350 purple_connection_error(gc
, _("SIP Exchange user name contains whitespace"));
8354 sip
->password
= g_strdup(purple_connection_get_password(gc
));
8356 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
8357 sip
->our_publications
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8358 g_free
, (GDestroyNotify
)g_hash_table_destroy
);
8359 sip
->subscriptions
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8360 g_free
, (GDestroyNotify
)sipe_subscription_free
);
8362 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
8364 g_free(sip
->status
);
8365 sip
->status
= g_strdup(SIPE_STATUS_ID_UNKNOWN
);
8367 sip
->auto_transport
= FALSE
;
8368 transport
= purple_account_get_string(account
, "transport", "auto");
8369 userserver
= g_strsplit(purple_account_get_string(account
, "server", ""), ":", 2);
8370 if (userserver
[0]) {
8371 /* Use user specified server[:port] */
8375 port
= atoi(userserver
[1]);
8377 purple_debug(PURPLE_DEBUG_MISC
, "sipe", "sipe_login: user specified SIP server %s:%d\n",
8378 userserver
[0], port
);
8380 if (strcmp(transport
, "auto") == 0) {
8381 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
8382 } else if (strcmp(transport
, "tls") == 0) {
8383 sip
->transport
= SIPE_TRANSPORT_TLS
;
8384 } else if (strcmp(transport
, "tcp") == 0) {
8385 sip
->transport
= SIPE_TRANSPORT_TCP
;
8387 sip
->transport
= SIPE_TRANSPORT_UDP
;
8390 create_connection(sip
, g_strdup(userserver
[0]), port
);
8392 /* Server auto-discovery */
8393 if (strcmp(transport
, "auto") == 0) {
8394 sip
->auto_transport
= TRUE
;
8395 if (current_service
&& current_service
->transport
!= NULL
&& current_service
->service
!= NULL
){
8397 resolve_next_service(sip
, current_service
);
8399 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
8401 } else if (strcmp(transport
, "tls") == 0) {
8402 resolve_next_service(sip
, service_tls
);
8403 } else if (strcmp(transport
, "tcp") == 0) {
8404 resolve_next_service(sip
, service_tcp
);
8406 resolve_next_service(sip
, service_udp
);
8409 g_strfreev(userserver
);
8412 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
8414 connection_free_all(sip
);
8419 if (sip
->query_data
!= NULL
)
8420 purple_dnsquery_destroy(sip
->query_data
);
8421 sip
->query_data
= NULL
;
8423 if (sip
->srv_query_data
!= NULL
)
8424 purple_srv_cancel(sip
->srv_query_data
);
8425 sip
->srv_query_data
= NULL
;
8427 if (sip
->listen_data
!= NULL
)
8428 purple_network_listen_cancel(sip
->listen_data
);
8429 sip
->listen_data
= NULL
;
8431 if (sip
->gsc
!= NULL
)
8432 purple_ssl_close(sip
->gsc
);
8435 sipe_auth_free(&sip
->registrar
);
8436 sipe_auth_free(&sip
->proxy
);
8439 purple_circ_buffer_destroy(sip
->txbuf
);
8442 g_free(sip
->realhostname
);
8443 sip
->realhostname
= NULL
;
8445 g_free(sip
->server_version
);
8446 sip
->server_version
= NULL
;
8449 purple_input_remove(sip
->listenpa
);
8451 if (sip
->tx_handler
)
8452 purple_input_remove(sip
->tx_handler
);
8453 sip
->tx_handler
= 0;
8454 if (sip
->resendtimeout
)
8455 purple_timeout_remove(sip
->resendtimeout
);
8456 sip
->resendtimeout
= 0;
8457 if (sip
->timeouts
) {
8458 GSList
*entry
= sip
->timeouts
;
8460 struct scheduled_action
*sched_action
= entry
->data
;
8461 purple_debug_info("sipe", "purple_timeout_remove: action name=%s\n", sched_action
->name
);
8462 purple_timeout_remove(sched_action
->timeout_handler
);
8463 if (sched_action
->destroy
) {
8464 (*sched_action
->destroy
)(sched_action
->payload
);
8466 g_free(sched_action
->name
);
8467 g_free(sched_action
);
8468 entry
= entry
->next
;
8471 g_slist_free(sip
->timeouts
);
8473 if (sip
->allow_events
) {
8474 GSList
*entry
= sip
->allow_events
;
8476 g_free(entry
->data
);
8477 entry
= entry
->next
;
8480 g_slist_free(sip
->allow_events
);
8482 if (sip
->containers
) {
8483 GSList
*entry
= sip
->containers
;
8485 free_container((struct sipe_container
*)entry
->data
);
8486 entry
= entry
->next
;
8489 g_slist_free(sip
->containers
);
8492 g_free(sip
->contact
);
8493 sip
->contact
= NULL
;
8495 g_free(sip
->regcallid
);
8496 sip
->regcallid
= NULL
;
8498 if (sip
->serveraddr
)
8499 g_free(sip
->serveraddr
);
8500 sip
->serveraddr
= NULL
;
8502 if (sip
->focus_factory_uri
)
8503 g_free(sip
->focus_factory_uri
);
8504 sip
->focus_factory_uri
= NULL
;
8507 sip
->processing_input
= FALSE
;
8510 sipe_ews_free(sip
->ews
);
8516 * A callback for g_hash_table_foreach_remove
8518 static gboolean
sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key
, gpointer buddy
,
8519 SIPE_UNUSED_PARAMETER gpointer user_data
)
8521 sipe_free_buddy((struct sipe_buddy
*) buddy
);
8523 /* We must return TRUE as the key/value have already been deleted */
8527 static void sipe_close(PurpleConnection
*gc
)
8529 struct sipe_account_data
*sip
= gc
->proto_data
;
8532 /* leave all conversations */
8533 sipe_session_close_all(sip
);
8534 sipe_session_remove_all(sip
);
8537 sip_csta_close(sip
);
8540 if (PURPLE_CONNECTION_IS_CONNECTED(sip
->gc
)) {
8541 /* unsubscribe all */
8542 g_hash_table_foreach(sip
->subscriptions
, sipe_unsubscribe_cb
, sip
);
8545 do_register_exp(sip
, 0);
8548 sipe_connection_cleanup(sip
);
8549 g_free(sip
->sipdomain
);
8550 g_free(sip
->username
);
8552 g_free(sip
->password
);
8553 g_free(sip
->authdomain
);
8554 g_free(sip
->authuser
);
8555 g_free(sip
->status
);
8558 g_hash_table_foreach_steal(sip
->buddies
, sipe_buddy_remove
, NULL
);
8559 g_hash_table_destroy(sip
->buddies
);
8560 g_hash_table_destroy(sip
->our_publications
);
8561 g_hash_table_destroy(sip
->user_state_publications
);
8562 g_hash_table_destroy(sip
->subscriptions
);
8565 GSList
*entry
= sip
->groups
;
8567 struct sipe_group
*group
= entry
->data
;
8568 g_free(group
->name
);
8570 entry
= entry
->next
;
8573 g_slist_free(sip
->groups
);
8575 if (sip
->our_publication_keys
) {
8576 GSList
*entry
= sip
->our_publication_keys
;
8578 g_free(entry
->data
);
8579 entry
= entry
->next
;
8582 g_slist_free(sip
->our_publication_keys
);
8584 while (sip
->transactions
)
8585 transactions_remove(sip
, sip
->transactions
->data
);
8587 g_free(gc
->proto_data
);
8588 gc
->proto_data
= NULL
;
8591 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
,
8592 SIPE_UNUSED_PARAMETER
void *user_data
)
8594 PurpleAccount
*acct
= purple_connection_get_account(gc
);
8595 char *id
= sip_uri_from_name((gchar
*)g_list_nth_data(row
, 0));
8596 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
8598 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
8599 purple_conversation_present(conv
);
8603 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
,
8604 SIPE_UNUSED_PARAMETER
void *user_data
)
8607 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
8608 g_list_nth_data(row
, 0), _("Other Contacts"), g_list_nth_data(row
, 1));
8611 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
8612 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
8614 PurpleNotifySearchResults
*results
;
8615 PurpleNotifySearchColumn
*column
;
8616 xmlnode
*searchResults
;
8618 int match_count
= 0;
8619 gboolean more
= FALSE
;
8622 purple_debug_info("sipe", "process_search_contact_response: body:\n%s n", msg
->body
? msg
->body
: "");
8624 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
8625 if (!searchResults
) {
8626 purple_debug_info("sipe", "process_search_contact_response: no parseable searchResults\n");
8630 results
= purple_notify_searchresults_new();
8632 if (results
== NULL
) {
8633 purple_debug_error("sipe", "purple_parse_searchreply: Unable to display the search results.\n");
8634 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results"), NULL
);
8636 xmlnode_free(searchResults
);
8640 column
= purple_notify_searchresults_column_new(_("User name"));
8641 purple_notify_searchresults_column_add(results
, column
);
8643 column
= purple_notify_searchresults_column_new(_("Name"));
8644 purple_notify_searchresults_column_add(results
, column
);
8646 column
= purple_notify_searchresults_column_new(_("Company"));
8647 purple_notify_searchresults_column_add(results
, column
);
8649 column
= purple_notify_searchresults_column_new(_("Country"));
8650 purple_notify_searchresults_column_add(results
, column
);
8652 column
= purple_notify_searchresults_column_new(_("Email"));
8653 purple_notify_searchresults_column_add(results
, column
);
8655 for (mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
); mrow
; mrow
= xmlnode_get_next_twin(mrow
)) {
8658 gchar
**uri_parts
= g_strsplit(xmlnode_get_attrib(mrow
, "uri"), ":", 2);
8659 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
8660 g_strfreev(uri_parts
);
8662 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "displayName")));
8663 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "company")));
8664 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "country")));
8665 row
= g_list_append(row
, g_strdup(xmlnode_get_attrib(mrow
, "email")));
8667 purple_notify_searchresults_row_add(results
, row
);
8671 if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "directorySearch", "moreAvailable", NULL
)) != NULL
) {
8672 char *data
= xmlnode_get_data_unescaped(mrow
);
8673 more
= (g_strcasecmp(data
, "true") == 0);
8677 secondary
= g_strdup_printf(
8678 dngettext(GETTEXT_PACKAGE
,
8679 "Found %d contact%s:",
8680 "Found %d contacts%s:", match_count
),
8681 match_count
, more
? _(" (more matched your query)") : "");
8683 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
8684 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
8685 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
8688 xmlnode_free(searchResults
);
8692 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
8694 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
8695 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
8699 PurpleRequestField
*field
= entries
->data
;
8700 const char *id
= purple_request_field_get_id(field
);
8701 const char *value
= purple_request_field_string_get_value(field
);
8703 purple_debug_info("sipe", "sipe_search_contact_with_cb: %s = '%s'\n", id
, value
? value
: "");
8705 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
8706 } while ((entries
= g_list_next(entries
)) != NULL
);
8710 struct sipe_account_data
*sip
= gc
->proto_data
;
8711 gchar
*domain_uri
= sip_uri_from_name(sip
->sipdomain
);
8712 gchar
*query
= g_strjoinv(NULL
, attrs
);
8713 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
8714 purple_debug_info("sipe", "sipe_search_contact_with_cb: body:\n%s n", body
? body
: "");
8715 send_soap_request_with_cb(sip
, domain_uri
, body
,
8716 (TransCallback
) process_search_contact_response
, NULL
);
8725 static void sipe_show_find_contact(PurplePluginAction
*action
)
8727 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8728 PurpleRequestFields
*fields
;
8729 PurpleRequestFieldGroup
*group
;
8730 PurpleRequestField
*field
;
8732 fields
= purple_request_fields_new();
8733 group
= purple_request_field_group_new(NULL
);
8734 purple_request_fields_add_group(fields
, group
);
8736 field
= purple_request_field_string_new("givenName", _("First name"), NULL
, FALSE
);
8737 purple_request_field_group_add_field(group
, field
);
8738 field
= purple_request_field_string_new("sn", _("Last name"), NULL
, FALSE
);
8739 purple_request_field_group_add_field(group
, field
);
8740 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
8741 purple_request_field_group_add_field(group
, field
);
8742 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
8743 purple_request_field_group_add_field(group
, field
);
8745 purple_request_fields(gc
,
8747 _("Search for a contact"),
8748 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
8750 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
8752 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
8755 static void sipe_show_about_plugin(PurplePluginAction
*action
)
8757 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8758 char *tmp
= g_strdup_printf(
8760 * Non-translatable parts, like markup, are hard-coded
8761 * into the format string. This requires more translatable
8762 * texts but it makes the translations less error prone.
8764 "<b><font size=\"+1\">SIPE " SIPE_VERSION
" </font></b><br/>"
8767 "<li> - MS Office Communications Server 2007 R2</li><br/>"
8768 "<li> - MS Office Communications Server 2007</li><br/>"
8769 "<li> - MS Live Communications Server 2005</li><br/>"
8770 "<li> - MS Live Communications Server 2003</li><br/>"
8771 "<li> - Reuters Messaging</li><br/>"
8773 /* 2 */ "%s: <a href=\"http://sipe.sourceforge.net\">http://sipe.sourceforge.net</a><br/>"
8774 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
8775 /* 5 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
8776 /* 6 */ "%s: GPLv2+<br/>"
8780 " - Reuters Messaging network<br/>"
8781 " - Deutsche Bank<br/>"
8782 " - Merrill Lynch<br/>"
8788 " - Alcatel-Lucent<br/>"
8791 /* 8,9 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
8793 /* 10 */ "<b>%s:</b><br/>"
8794 " - Anibal Avelar<br/>"
8795 " - Gabriel Burt<br/>"
8796 " - Stefan Becker<br/>"
8801 /* The next 11 texts make up the SIPE about note text */
8802 /* About note, part 1/11: introduction */
8803 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
8804 /* About note, part 2/11: home page URL (label) */
8806 /* About note, part 3/11: support forum URL (label) */
8808 /* About note, part 4/11: support forum name (hyperlink text) */
8810 /* About note, part 5/11: translation service URL (label) */
8812 /* About note, part 6/11: license type (label) */
8814 /* About note, part 7/11: known users */
8815 _("We support users in such organizations as"),
8816 /* About note, part 8/11: translation request, text before Transifex.net URL */
8817 /* append a space if text is not empty */
8818 _("Please help us to translate SIPE to your native language here at "),
8819 /* About note, part 9/11: translation request, text after Transifex.net URL */
8820 /* start with a space if text is not empty */
8821 _(" using convenient web interface"),
8822 /* About note, part 10/11: author list (header) */
8824 /* About note, part 11/11: Localization credit */
8825 /* PLEASE NOTE: do *NOT* simply translate the english original */
8826 /* but write something similar to the following sentence: */
8827 /* "Localization for <language name> (<language code>): <name>" */
8828 _("Original texts in English (en): SIPE developers")
8830 purple_notify_formatted(gc
, NULL
, " ", NULL
, tmp
, NULL
, NULL
);
8834 static void sipe_republish_calendar(PurplePluginAction
*action
)
8836 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8837 struct sipe_account_data
*sip
= gc
->proto_data
;
8839 sipe_update_calendar(sip
);
8842 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER
const char *name
,
8846 struct sipe_publication
*publication
= value
;
8848 g_string_append_printf( str
,
8849 SIPE_PUB_XML_PUBLICATION_CLEAR
,
8850 publication
->category
,
8851 publication
->instance
,
8852 publication
->container
,
8853 publication
->version
,
8857 static void sipe_reset_status(PurplePluginAction
*action
)
8859 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
8860 struct sipe_account_data
*sip
= gc
->proto_data
;
8862 if (sip
->ocs2007
) /* 2007+ */
8864 GString
* str
= g_string_new(NULL
);
8865 gchar
*publications
;
8867 if (!sip
->user_state_publications
|| g_hash_table_size(sip
->user_state_publications
) == 0) {
8868 purple_debug_info("sipe", "sipe_reset_status: no userState publications, exiting.\n");
8872 g_hash_table_foreach(sip
->user_state_publications
, (GHFunc
)sipe_publish_get_cat_state_user_to_clear
, str
);
8873 publications
= g_string_free(str
, FALSE
);
8875 send_presence_publish(sip
, publications
);
8876 g_free(publications
);
8880 send_presence_soap0(sip
, FALSE
, TRUE
);
8884 GList
*sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
,
8887 PurpleConnection
*gc
= (PurpleConnection
*)context
;
8888 struct sipe_account_data
*sip
= gc
->proto_data
;
8890 PurplePluginAction
*act
;
8891 const char* calendar
= purple_account_get_string(sip
->account
, "calendar", "EXCH");
8893 act
= purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin
);
8894 menu
= g_list_prepend(menu
, act
);
8896 act
= purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact
);
8897 menu
= g_list_prepend(menu
, act
);
8899 if (!strcmp(calendar
, "EXCH")) {
8900 act
= purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar
);
8901 menu
= g_list_prepend(menu
, act
);
8904 act
= purple_plugin_action_new(_("Reset status"), sipe_reset_status
);
8905 menu
= g_list_prepend(menu
, act
);
8907 menu
= g_list_reverse(menu
);
8912 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection
*gc
)
8916 static gboolean
sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
8922 static gboolean
sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
8928 static char *sipe_status_text(PurpleBuddy
*buddy
)
8930 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
8931 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
8932 const char *status_id
= purple_status_get_id(status
);
8933 struct sipe_account_data
*sip
= (struct sipe_account_data
*)buddy
->account
->gc
->proto_data
;
8934 struct sipe_buddy
*sbuddy
;
8937 if (!sip
) return NULL
; /* happens on pidgin exit */
8939 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
8941 const char *activity_str
= sbuddy
->activity
?
8943 !strcmp(status_id
, SIPE_STATUS_ID_BUSY
) || !strcmp(status_id
, SIPE_STATUS_ID_BRB
) ?
8944 purple_status_get_name(status
) : NULL
;
8946 if (activity_str
&& sbuddy
->note
)
8948 text
= g_strdup_printf("%s - <i>%s</i>", activity_str
, sbuddy
->note
);
8950 else if (activity_str
)
8952 text
= g_strdup(activity_str
);
8954 else if (sbuddy
->note
)
8956 text
= g_strdup_printf("<i>%s</i>", sbuddy
->note
);
8963 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, SIPE_UNUSED_PARAMETER gboolean full
)
8965 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
8966 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
8967 struct sipe_account_data
*sip
;
8968 struct sipe_buddy
*sbuddy
;
8970 gboolean is_oof_note
= FALSE
;
8971 char *activity
= NULL
;
8972 char *calendar
= NULL
;
8973 char *meeting_subject
= NULL
;
8974 char *meeting_location
= NULL
;
8976 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
8977 if (sip
) //happens on pidgin exit
8979 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
8982 note
= sbuddy
->note
;
8983 is_oof_note
= sbuddy
->is_oof_note
;
8984 activity
= sbuddy
->activity
;
8985 calendar
= sipe_cal_get_description(sbuddy
);
8986 meeting_subject
= sbuddy
->meeting_subject
;
8987 meeting_location
= sbuddy
->meeting_location
;
8992 if (purple_presence_is_online(presence
))
8994 const char *status_str
= activity
? activity
: purple_status_get_name(status
);
8996 purple_notify_user_info_add_pair(user_info
, _("Status"), status_str
);
8998 if (purple_presence_is_online(presence
) &&
8999 !is_empty(calendar
))
9001 purple_notify_user_info_add_pair(user_info
, _("Calendar"), calendar
);
9004 if (!is_empty(meeting_location
))
9006 purple_notify_user_info_add_pair(user_info
, _("Meeting in"), meeting_location
);
9008 if (!is_empty(meeting_subject
))
9010 purple_notify_user_info_add_pair(user_info
, _("Meeting about"), meeting_subject
);
9015 char *tmp
= g_strdup_printf("<i>%s</i>", note
);
9016 purple_debug_info("sipe", "sipe_tooltip_text: %s note: '%s'\n", buddy
->name
, note
);
9018 purple_notify_user_info_add_pair(user_info
, is_oof_note
? _("Out of office note") : _("Note"), tmp
);
9024 #if PURPLE_VERSION_CHECK(2,5,0)
9026 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount
*account
)
9029 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
9030 g_hash_table_insert(table
, "login_label", (gpointer
)_("user@company.com"));
9035 static PurpleBuddy
*
9036 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
9039 const gchar
*server_alias
, *email
;
9040 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
9042 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
9044 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
9046 server_alias
= purple_buddy_get_server_alias(buddy
);
9048 purple_blist_server_alias_buddy(clone
, server_alias
);
9051 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
9053 purple_blist_node_set_string(&clone
->node
, EMAIL_PROP
, email
);
9056 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
9058 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
9063 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
9065 PurpleBuddy
*buddy
, *b
;
9066 PurpleConnection
*gc
;
9067 PurpleGroup
* group
= purple_find_group(group_name
);
9069 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
9071 buddy
= (PurpleBuddy
*)node
;
9073 purple_debug_info("sipe", "sipe_buddy_menu_copy_to_cb: copying %s to %s\n", buddy
->name
, group_name
);
9074 gc
= purple_account_get_connection(buddy
->account
);
9076 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
9078 purple_blist_add_buddy_clone(group
, buddy
);
9081 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
9085 sipe_buddy_menu_chat_new_cb(PurpleBuddy
*buddy
)
9087 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9089 purple_debug_info("sipe", "sipe_buddy_menu_chat_new_cb: buddy->name=%s\n", buddy
->name
);
9091 /* 2007+ conference */
9094 sipe_conf_add(sip
, buddy
->name
);
9096 else /* 2005- multiparty chat */
9098 gchar
*self
= sip_uri_self(sip
);
9099 struct sip_session
*session
;
9101 session
= sipe_session_add_chat(sip
);
9102 session
->chat_title
= sipe_chat_get_name(session
->callid
);
9103 session
->roster_manager
= g_strdup(self
);
9105 session
->conv
= serv_got_joined_chat(buddy
->account
->gc
, session
->chat_id
, session
->chat_title
);
9106 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session
->conv
), self
);
9107 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
), self
, NULL
, PURPLE_CBFLAGS_NONE
, FALSE
);
9108 sipe_invite(sip
, session
, buddy
->name
, NULL
, NULL
, FALSE
);
9115 sipe_is_election_finished(struct sip_session
*session
)
9117 gboolean res
= TRUE
;
9119 SIPE_DIALOG_FOREACH
{
9120 if (dialog
->election_vote
== 0) {
9124 } SIPE_DIALOG_FOREACH_END
;
9127 session
->is_voting_in_progress
= FALSE
;
9133 sipe_election_start(struct sipe_account_data
*sip
,
9134 struct sip_session
*session
)
9136 int election_timeout
;
9138 if (session
->is_voting_in_progress
) {
9139 purple_debug_info("sipe", "sipe_election_start: other election is in progress, exiting.\n");
9142 session
->is_voting_in_progress
= TRUE
;
9144 session
->bid
= rand();
9146 purple_debug_info("sipe", "sipe_election_start: RM election has initiated. Our bid=%d\n", session
->bid
);
9148 SIPE_DIALOG_FOREACH
{
9149 /* reset election_vote for each chat participant */
9150 dialog
->election_vote
= 0;
9152 /* send RequestRM to each chat participant*/
9153 sipe_send_election_request_rm(sip
, dialog
, session
->bid
);
9154 } SIPE_DIALOG_FOREACH_END
;
9156 election_timeout
= 15; /* sec */
9157 sipe_schedule_action("<+election-result>", election_timeout
, sipe_election_result
, NULL
, sip
, session
);
9161 * @param who a URI to whom to invite to chat
9164 sipe_invite_to_chat(struct sipe_account_data
*sip
,
9165 struct sip_session
*session
,
9169 if (session
->focus_uri
)
9171 sipe_invite_conf(sip
, session
, who
);
9173 else /* a multi-party chat */
9175 gchar
*self
= sip_uri_self(sip
);
9176 if (session
->roster_manager
) {
9177 if (!strcmp(session
->roster_manager
, self
)) {
9178 sipe_invite(sip
, session
, who
, NULL
, NULL
, FALSE
);
9180 sipe_refer(sip
, session
, who
);
9183 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite: no RM available\n");
9185 session
->pending_invite_queue
= slist_insert_unique_sorted(
9186 session
->pending_invite_queue
, g_strdup(who
), (GCompareFunc
)strcmp
);
9188 sipe_election_start(sip
, session
);
9195 sipe_process_pending_invite_queue(struct sipe_account_data
*sip
,
9196 struct sip_session
*session
)
9199 GSList
*entry
= session
->pending_invite_queue
;
9202 invitee
= entry
->data
;
9203 sipe_invite_to_chat(sip
, session
, invitee
);
9204 entry
= session
->pending_invite_queue
= g_slist_remove(session
->pending_invite_queue
, invitee
);
9210 sipe_election_result(struct sipe_account_data
*sip
,
9213 struct sip_session
*session
= (struct sip_session
*)sess
;
9215 gboolean has_won
= TRUE
;
9217 if (session
->roster_manager
) {
9218 purple_debug_info("sipe",
9219 "sipe_election_result: RM has already been elected in the meantime. It is %s\n", session
->roster_manager
);
9223 session
->is_voting_in_progress
= FALSE
;
9225 SIPE_DIALOG_FOREACH
{
9226 if (dialog
->election_vote
< 0) {
9228 rival
= dialog
->with
;
9231 } SIPE_DIALOG_FOREACH_END
;
9234 purple_debug_info("sipe", "sipe_election_result: we have won RM election!\n");
9236 session
->roster_manager
= sip_uri_self(sip
);
9238 SIPE_DIALOG_FOREACH
{
9239 /* send SetRM to each chat participant*/
9240 sipe_send_election_set_rm(sip
, dialog
);
9241 } SIPE_DIALOG_FOREACH_END
;
9243 purple_debug_info("sipe", "sipe_election_result: we loose RM election to %s\n", rival
);
9247 sipe_process_pending_invite_queue(sip
, session
);
9251 * For 2007+ conference only.
9254 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy
*buddy
, const char *chat_title
)
9256 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9257 struct sip_session
*session
;
9259 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s\n", buddy
->name
);
9260 purple_debug_info("sipe", "sipe_buddy_menu_chat_make_leader_cb: chat_title=%s\n", chat_title
);
9262 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
9264 sipe_conf_modify_user_role(sip
, session
, buddy
->name
);
9268 * For 2007+ conference only.
9271 sipe_buddy_menu_chat_remove_cb(PurpleBuddy
*buddy
, const char *chat_title
)
9273 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9274 struct sip_session
*session
;
9276 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: buddy->name=%s\n", buddy
->name
);
9277 purple_debug_info("sipe", "sipe_buddy_menu_chat_remove_cb: chat_title=%s\n", chat_title
);
9279 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
9281 sipe_conf_delete_user(sip
, session
, buddy
->name
);
9285 sipe_buddy_menu_chat_invite_cb(PurpleBuddy
*buddy
, char *chat_title
)
9287 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9288 struct sip_session
*session
;
9290 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: buddy->name=%s\n", buddy
->name
);
9291 purple_debug_info("sipe", "sipe_buddy_menu_chat_invite_cb: chat_title=%s\n", chat_title
);
9293 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
9295 sipe_invite_to_chat(sip
, session
, buddy
->name
);
9299 sipe_buddy_menu_make_call_cb(PurpleBuddy
*buddy
, const char *phone
)
9301 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9303 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: buddy->name=%s\n", buddy
->name
);
9305 char *tel_uri
= sip_to_tel_uri(phone
);
9307 purple_debug_info("sipe", "sipe_buddy_menu_make_call_cb: going to call number: %s\n", tel_uri
? tel_uri
: "");
9308 sip_csta_make_call(sip
, tel_uri
);
9315 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
9318 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: buddy->name=%s\n", buddy
->name
);
9320 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
9323 char *mailto
= g_strdup_printf("mailto:%s", email
);
9324 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s\n", email
);
9328 char *const parmList
[] = {"xdg-email", mailto
, NULL
};
9329 if ((pid
= fork()) == -1)
9331 purple_debug_info("sipe", "fork() error\n");
9335 execvp(parmList
[0], parmList
);
9336 purple_debug_info("sipe", "Return not expected. Must be an execvp() error.\n");
9344 //@TODO resolve env variable %WINDIR% first
9345 ret
= spawnl(_P_NOWAIT
, "c:/WINDOWS/system32/cmd", "/c", "start", mailto
, NULL
);
9348 purple_debug_info("sipe", "spawnl returned (%s)!\n", strerror(errno
));
9357 purple_debug_info("sipe", "sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s\n", buddy
->name
);
9362 * A menu which appear when right-clicking on buddy in contact list.
9365 sipe_buddy_menu(PurpleBuddy
*buddy
)
9367 PurpleBlistNode
*g_node
;
9368 PurpleGroup
*group
, *gr_parent
;
9369 PurpleMenuAction
*act
;
9371 GList
*menu_groups
= NULL
;
9372 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9375 const char *phone_disp_str
;
9376 gchar
*self
= sip_uri_self(sip
);
9378 SIPE_SESSION_FOREACH
{
9379 if (g_ascii_strcasecmp(self
, buddy
->name
) && session
->chat_title
&& session
->conv
)
9381 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session
->conv
), buddy
->name
))
9383 PurpleConvChatBuddyFlags flags
;
9384 PurpleConvChatBuddyFlags flags_us
;
9386 flags
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), buddy
->name
);
9387 flags_us
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), self
);
9388 if (session
->focus_uri
9389 && PURPLE_CBFLAGS_OP
!= (flags
& PURPLE_CBFLAGS_OP
) /* Not conf OP */
9390 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
9392 gchar
*label
= g_strdup_printf(_("Make leader of '%s'"), session
->chat_title
);
9393 act
= purple_menu_action_new(label
,
9394 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb
),
9395 session
->chat_title
, NULL
);
9397 menu
= g_list_prepend(menu
, act
);
9400 if (session
->focus_uri
9401 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
9403 gchar
*label
= g_strdup_printf(_("Remove from '%s'"), session
->chat_title
);
9404 act
= purple_menu_action_new(label
,
9405 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb
),
9406 session
->chat_title
, NULL
);
9408 menu
= g_list_prepend(menu
, act
);
9413 if (!session
->focus_uri
9414 || (session
->focus_uri
&& !session
->locked
))
9416 gchar
*label
= g_strdup_printf(_("Invite to '%s'"), session
->chat_title
);
9417 act
= purple_menu_action_new(label
,
9418 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb
),
9419 session
->chat_title
, NULL
);
9421 menu
= g_list_prepend(menu
, act
);
9425 } SIPE_SESSION_FOREACH_END
;
9427 act
= purple_menu_action_new(_("New chat"),
9428 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb
),
9430 menu
= g_list_prepend(menu
, act
);
9432 if (sip
->csta
&& !sip
->csta
->line_status
) {
9435 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_PROP
);
9436 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_DISPLAY_PROP
);
9438 gchar
*label
= g_strdup_printf(_("Work %s"),
9439 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9440 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9444 menu
= g_list_prepend(menu
, act
);
9448 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_MOBILE_PROP
);
9449 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_MOBILE_DISPLAY_PROP
);
9451 gchar
*label
= g_strdup_printf(_("Mobile %s"),
9452 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9453 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9457 menu
= g_list_prepend(menu
, act
);
9461 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_HOME_PROP
);
9462 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_HOME_DISPLAY_PROP
);
9464 gchar
*label
= g_strdup_printf(_("Home %s"),
9465 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9466 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9470 menu
= g_list_prepend(menu
, act
);
9474 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_OTHER_PROP
);
9475 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_OTHER_DISPLAY_PROP
);
9477 gchar
*label
= g_strdup_printf(_("Other %s"),
9478 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9479 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9483 menu
= g_list_prepend(menu
, act
);
9487 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_CUSTOM1_PROP
);
9488 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_CUSTOM1_DISPLAY_PROP
);
9490 gchar
*label
= g_strdup_printf(_("Custom1 %s"),
9491 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
9492 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
9496 menu
= g_list_prepend(menu
, act
);
9500 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
9502 act
= purple_menu_action_new(_("Send email..."),
9503 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
9505 menu
= g_list_prepend(menu
, act
);
9508 gr_parent
= purple_buddy_get_group(buddy
);
9509 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
9510 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
9513 group
= (PurpleGroup
*)g_node
;
9514 if (group
== gr_parent
)
9517 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
9520 act
= purple_menu_action_new(purple_group_get_name(group
),
9521 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
9523 menu_groups
= g_list_prepend(menu_groups
, act
);
9525 menu_groups
= g_list_reverse(menu_groups
);
9527 act
= purple_menu_action_new(_("Copy to"),
9530 menu
= g_list_prepend(menu
, act
);
9531 menu
= g_list_reverse(menu
);
9538 sipe_conf_modify_lock(PurpleChat
*chat
, gboolean locked
)
9540 struct sipe_account_data
*sip
= chat
->account
->gc
->proto_data
;
9541 struct sip_session
*session
;
9543 session
= sipe_session_find_chat_by_title(sip
, (gchar
*)g_hash_table_lookup(chat
->components
, "channel"));
9544 sipe_conf_modify_conference_lock(sip
, session
, locked
);
9548 sipe_chat_menu_unlock_cb(PurpleChat
*chat
)
9550 purple_debug_info("sipe", "sipe_chat_menu_unlock_cb() called\n");
9551 sipe_conf_modify_lock(chat
, FALSE
);
9555 sipe_chat_menu_lock_cb(PurpleChat
*chat
)
9557 purple_debug_info("sipe", "sipe_chat_menu_lock_cb() called\n");
9558 sipe_conf_modify_lock(chat
, TRUE
);
9562 sipe_chat_menu(PurpleChat
*chat
)
9564 PurpleMenuAction
*act
;
9565 PurpleConvChatBuddyFlags flags_us
;
9567 struct sipe_account_data
*sip
= chat
->account
->gc
->proto_data
;
9568 struct sip_session
*session
;
9571 session
= sipe_session_find_chat_by_title(sip
, (gchar
*)g_hash_table_lookup(chat
->components
, "channel"));
9572 if (!session
) return NULL
;
9574 self
= sip_uri_self(sip
);
9575 flags_us
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), self
);
9577 if (session
->focus_uri
9578 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
9580 if (session
->locked
) {
9581 act
= purple_menu_action_new(_("Unlock"),
9582 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb
),
9584 menu
= g_list_prepend(menu
, act
);
9586 act
= purple_menu_action_new(_("Lock"),
9587 PURPLE_CALLBACK(sipe_chat_menu_lock_cb
),
9589 menu
= g_list_prepend(menu
, act
);
9593 menu
= g_list_reverse(menu
);
9600 sipe_blist_node_menu(PurpleBlistNode
*node
)
9602 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
9603 return sipe_buddy_menu((PurpleBuddy
*) node
);
9604 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
9605 return sipe_chat_menu((PurpleChat
*)node
);
9612 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
9614 char *uri
= trans
->payload
->data
;
9616 PurpleNotifyUserInfo
*info
;
9617 PurpleBuddy
*pbuddy
= NULL
;
9618 struct sipe_buddy
*sbuddy
;
9619 const char *alias
= NULL
;
9620 char *device_name
= NULL
;
9621 char *server_alias
= NULL
;
9622 char *phone_number
= NULL
;
9625 char *first_name
= NULL
;
9626 char *last_name
= NULL
;
9628 if (!sip
) return FALSE
;
9630 purple_debug_info("sipe", "Fetching %s's user info for %s\n", uri
, sip
->username
);
9632 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri
);
9633 alias
= purple_buddy_get_local_alias(pbuddy
);
9635 //will query buddy UA's capabilities and send answer to log
9636 sipe_options_request(sip
, uri
);
9638 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
9640 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
9643 info
= purple_notify_user_info_new();
9645 if (msg
->response
!= 200) {
9646 purple_debug_info("sipe", "process_options_response: SERVICE response is %d\n", msg
->response
);
9648 xmlnode
*searchResults
;
9651 purple_debug_info("sipe", "process_options_response: body:\n%s\n", msg
->body
? msg
->body
: "");
9652 searchResults
= xmlnode_from_str(msg
->body
, msg
->bodylen
);
9653 if (!searchResults
) {
9654 purple_debug_info("sipe", "process_get_info_response: no parseable searchResults\n");
9655 } else if ((mrow
= xmlnode_get_descendant(searchResults
, "Body", "Array", "row", NULL
))) {
9657 server_alias
= g_strdup(xmlnode_get_attrib(mrow
, "displayName"));
9658 email
= g_strdup(xmlnode_get_attrib(mrow
, "email"));
9659 phone_number
= g_strdup(xmlnode_get_attrib(mrow
, "phone"));
9661 /* For 2007 system we will take this from ContactCard -
9662 * it has cleaner tel: URIs at least
9664 if (!sip
->ocs2007
) {
9665 char *tel_uri
= sip_to_tel_uri(phone_number
);
9666 /* trims its parameters, so call first */
9667 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, server_alias
);
9668 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
9669 sipe_update_user_info(sip
, uri
, PHONE_PROP
, tel_uri
);
9670 sipe_update_user_info(sip
, uri
, PHONE_DISPLAY_PROP
, phone_number
);
9674 if (server_alias
&& strlen(server_alias
) > 0) {
9675 purple_notify_user_info_add_pair(info
, _("Display name"), server_alias
);
9677 if ((value
= xmlnode_get_attrib(mrow
, "title")) && strlen(value
) > 0) {
9678 purple_notify_user_info_add_pair(info
, _("Job title"), value
);
9680 if ((value
= xmlnode_get_attrib(mrow
, "office")) && strlen(value
) > 0) {
9681 purple_notify_user_info_add_pair(info
, _("Office"), value
);
9683 if (phone_number
&& strlen(phone_number
) > 0) {
9684 purple_notify_user_info_add_pair(info
, _("Business phone"), phone_number
);
9686 if ((value
= xmlnode_get_attrib(mrow
, "company")) && strlen(value
) > 0) {
9687 purple_notify_user_info_add_pair(info
, _("Company"), value
);
9689 if ((value
= xmlnode_get_attrib(mrow
, "city")) && strlen(value
) > 0) {
9690 purple_notify_user_info_add_pair(info
, _("City"), value
);
9692 if ((value
= xmlnode_get_attrib(mrow
, "state")) && strlen(value
) > 0) {
9693 purple_notify_user_info_add_pair(info
, _("State"), value
);
9695 if ((value
= xmlnode_get_attrib(mrow
, "country")) && strlen(value
) > 0) {
9696 purple_notify_user_info_add_pair(info
, _("Country"), value
);
9698 if (email
&& strlen(email
) > 0) {
9699 purple_notify_user_info_add_pair(info
, _("Email address"), email
);
9703 xmlnode_free(searchResults
);
9706 purple_notify_user_info_add_section_break(info
);
9708 if (!server_alias
|| !strcmp("", server_alias
)) {
9709 g_free(server_alias
);
9710 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
9712 purple_notify_user_info_add_pair(info
, _("Display name"), server_alias
);
9716 /* present alias if it differs from server alias */
9717 if (alias
&& (!server_alias
|| strcmp(alias
, server_alias
)))
9719 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
9722 if (!email
|| !strcmp("", email
)) {
9724 email
= g_strdup(purple_blist_node_get_string(&pbuddy
->node
, EMAIL_PROP
));
9726 purple_notify_user_info_add_pair(info
, _("Email address"), email
);
9730 site
= purple_blist_node_get_string(&pbuddy
->node
, SITE_PROP
);
9732 purple_notify_user_info_add_pair(info
, _("Site"), site
);
9735 sipe_get_first_last_names(sip
, uri
, &first_name
, &last_name
);
9736 if (first_name
&& last_name
) {
9737 char *link
= g_strconcat("http://www.linkedin.com/pub/dir/", first_name
, "/", last_name
, NULL
);
9739 purple_notify_user_info_add_pair(info
, _("Find on LinkedIn"), link
);
9746 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
9749 /* show a buddy's user info in a nice dialog box */
9750 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
9751 uri
, /* buddy's URI */
9753 NULL
, /* callback called when dialog closed */
9754 NULL
); /* userdata for callback */
9756 g_free(phone_number
);
9757 g_free(server_alias
);
9759 g_free(device_name
);
9765 * AD search first, LDAP based
9767 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
9769 struct sipe_account_data
*sip
= gc
->proto_data
;
9770 gchar
*domain_uri
= sip_uri_from_name(sip
->sipdomain
);
9771 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
9772 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
9773 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
9775 payload
->destroy
= g_free
;
9776 payload
->data
= g_strdup(username
);
9778 purple_debug_info("sipe", "sipe_get_contact_data: body:\n%s\n", body
? body
: "");
9779 send_soap_request_with_cb(sip
, domain_uri
, body
,
9780 (TransCallback
) process_get_info_response
, payload
);
9786 static PurplePlugin
*my_protocol
= NULL
;
9788 static PurplePluginProtocolInfo prpl_info
=
9790 OPT_PROTO_CHAT_TOPIC
,
9791 NULL
, /* user_splits */
9792 NULL
, /* protocol_options */
9793 NO_BUDDY_ICONS
, /* icon_spec */
9794 sipe_list_icon
, /* list_icon */
9795 NULL
, /* list_emblems */
9796 sipe_status_text
, /* status_text */
9797 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
9798 sipe_status_types
, /* away_states */
9799 sipe_blist_node_menu
, /* blist_node_menu */
9800 NULL
, /* chat_info */
9801 NULL
, /* chat_info_defaults */
9802 sipe_login
, /* login */
9803 sipe_close
, /* close */
9804 sipe_im_send
, /* send_im */
9805 NULL
, /* set_info */ // TODO maybe
9806 sipe_send_typing
, /* send_typing */
9807 sipe_get_info
, /* get_info */
9808 sipe_set_status
, /* set_status */
9809 sipe_set_idle
, /* set_idle */
9810 NULL
, /* change_passwd */
9811 sipe_add_buddy
, /* add_buddy */
9812 NULL
, /* add_buddies */
9813 sipe_remove_buddy
, /* remove_buddy */
9814 NULL
, /* remove_buddies */
9815 sipe_add_permit
, /* add_permit */
9816 sipe_add_deny
, /* add_deny */
9817 sipe_add_deny
, /* rem_permit */
9818 sipe_add_permit
, /* rem_deny */
9819 dummy_permit_deny
, /* set_permit_deny */
9820 NULL
, /* join_chat */
9821 NULL
, /* reject_chat */
9822 NULL
, /* get_chat_name */
9823 sipe_chat_invite
, /* chat_invite */
9824 sipe_chat_leave
, /* chat_leave */
9825 NULL
, /* chat_whisper */
9826 sipe_chat_send
, /* chat_send */
9827 sipe_keep_alive
, /* keepalive */
9828 NULL
, /* register_user */
9829 NULL
, /* get_cb_info */ // deprecated
9830 NULL
, /* get_cb_away */ // deprecated
9831 sipe_alias_buddy
, /* alias_buddy */
9832 sipe_group_buddy
, /* group_buddy */
9833 sipe_rename_group
, /* rename_group */
9834 NULL
, /* buddy_free */
9835 sipe_convo_closed
, /* convo_closed */
9836 purple_normalize_nocase
, /* normalize */
9837 NULL
, /* set_buddy_icon */
9838 sipe_remove_group
, /* remove_group */
9839 NULL
, /* get_cb_real_name */ // TODO?
9840 NULL
, /* set_chat_topic */
9841 NULL
, /* find_blist_chat */
9842 NULL
, /* roomlist_get_list */
9843 NULL
, /* roomlist_cancel */
9844 NULL
, /* roomlist_expand_category */
9845 NULL
, /* can_receive_file */
9846 NULL
, /* send_file */
9847 NULL
, /* new_xfer */
9848 NULL
, /* offline_message */
9849 NULL
, /* whiteboard_prpl_ops */
9850 sipe_send_raw
, /* send_raw */
9851 NULL
, /* roomlist_room_serialize */
9852 NULL
, /* unregister_user */
9853 NULL
, /* send_attention */
9854 NULL
, /* get_attention_types */
9855 #if !PURPLE_VERSION_CHECK(2,5,0)
9856 /* Backward compatibility when compiling against 2.4.x API */
9857 (void (*)(void)) /* _purple_reserved4 */
9859 sizeof(PurplePluginProtocolInfo
), /* struct_size */
9860 #if PURPLE_VERSION_CHECK(2,5,0)
9861 sipe_get_account_text_table
, /* get_account_text_table */
9862 #if PURPLE_VERSION_CHECK(2,6,0)
9863 NULL
, /* initiate_media */
9864 NULL
, /* get_media_caps */
9870 static PurplePluginInfo info
= {
9871 PURPLE_PLUGIN_MAGIC
,
9872 PURPLE_MAJOR_VERSION
,
9873 PURPLE_MINOR_VERSION
,
9874 PURPLE_PLUGIN_PROTOCOL
, /**< type */
9875 NULL
, /**< ui_requirement */
9877 NULL
, /**< dependencies */
9878 PURPLE_PRIORITY_DEFAULT
, /**< priority */
9879 "prpl-sipe", /**< id */
9880 "Office Communicator", /**< name */
9881 SIPE_VERSION
, /**< version */
9882 "Microsoft Office Communicator Protocol Plugin", /**< summary */
9883 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
9884 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
9885 "Anibal Avelar <avelar@gmail.com>, " /**< author */
9886 "Gabriel Burt <gburt@novell.com>, " /**< author */
9887 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
9888 "pier11 <pier11@operamail.com>", /**< author */
9889 "http://sipe.sourceforge.net/", /**< homepage */
9890 sipe_plugin_load
, /**< load */
9891 sipe_plugin_unload
, /**< unload */
9892 sipe_plugin_destroy
, /**< destroy */
9893 NULL
, /**< ui_info */
9894 &prpl_info
, /**< extra_info */
9903 static void sipe_plugin_destroy(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
9907 entry
= prpl_info
.protocol_options
;
9909 purple_account_option_destroy(entry
->data
);
9910 entry
= g_list_delete_link(entry
, entry
);
9912 prpl_info
.protocol_options
= NULL
;
9914 entry
= prpl_info
.user_splits
;
9916 purple_account_user_split_destroy(entry
->data
);
9917 entry
= g_list_delete_link(entry
, entry
);
9919 prpl_info
.user_splits
= NULL
;
9922 static void init_plugin(PurplePlugin
*plugin
)
9924 PurpleAccountUserSplit
*split
;
9925 PurpleAccountOption
*option
;
9930 purple_debug_info(PACKAGE
, "bindtextdomain = %s\n", bindtextdomain(GETTEXT_PACKAGE
, LOCALEDIR
));
9931 purple_debug_info(PACKAGE
, "bind_textdomain_codeset = %s\n",
9932 bind_textdomain_codeset(GETTEXT_PACKAGE
, "UTF-8"));
9933 textdomain(GETTEXT_PACKAGE
);
9936 purple_plugin_register(plugin
);
9938 split
= purple_account_user_split_new(_("Login\n user or DOMAIN\\user or\n user@company.com"), NULL
, ',');
9939 purple_account_user_split_set_reverse(split
, FALSE
);
9940 prpl_info
.user_splits
= g_list_append(prpl_info
.user_splits
, split
);
9942 option
= purple_account_option_string_new(_("Server[:Port]\n(leave empty for auto-discovery)"), "server", "");
9943 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9945 option
= purple_account_option_list_new(_("Connection type"), "transport", NULL
);
9946 purple_account_option_add_list_item(option
, _("Auto"), "auto");
9947 purple_account_option_add_list_item(option
, _("SSL/TLS"), "tls");
9948 purple_account_option_add_list_item(option
, _("TCP"), "tcp");
9949 purple_account_option_add_list_item(option
, _("UDP"), "udp");
9950 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9952 /*option = purple_account_option_bool_new(_("Publish status (note: everyone may watch you)"), "doservice", TRUE);
9953 prpl_info.protocol_options = g_list_append(prpl_info.protocol_options, option);*/
9955 option
= purple_account_option_string_new(_("User Agent"), "useragent", "");
9956 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9959 option
= purple_account_option_bool_new(_("Use Kerberos"), "krb5", FALSE
);
9960 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9962 /* Suitable for sspi/NTLM, sspi/Kerberos and krb5 security mechanisms
9963 * No login/password is taken into account if this option present,
9964 * instead used default credentials stored in OS.
9966 option
= purple_account_option_bool_new(_("Use Single Sign-On"), "sso", TRUE
);
9967 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9970 option
= purple_account_option_list_new(_("Calendar source"), "calendar", NULL
);
9971 purple_account_option_add_list_item(option
, _("Exchange 2007/2010"), "EXCH");
9972 purple_account_option_add_list_item(option
, _("None"), "NONE");
9973 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9975 /** Example: https://server.company.com/EWS/Exchange.asmx */
9976 option
= purple_account_option_string_new(_("Email services URL\n(leave empty for auto-discovery)"), "email_url", "");
9977 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9979 option
= purple_account_option_string_new(_("Email address\n(if different from Username)"), "email", "");
9980 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9982 /** Example: DOMAIN\user or user@company.com */
9983 option
= purple_account_option_string_new(_("Email login\n(if different from Login)"), "email_login", "");
9984 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9986 option
= purple_account_option_string_new(_("Email password\n(if different from Password)"), "email_password", "");
9987 purple_account_option_set_masked(option
, TRUE
);
9988 prpl_info
.protocol_options
= g_list_append(prpl_info
.protocol_options
, option
);
9990 my_protocol
= plugin
;
9993 PURPLE_INIT_PLUGIN(sipe
, init_plugin
, info
);