6 * Copyright (C) 2010 SIPE Project <http://sipe.sourceforge.net/>
7 * Copyright (C) 2010 pier11 <pier11@operamail.com>
8 * Copyright (C) 2009 Anibal Avelar <debianmx@gmail.com>
9 * Copyright (C) 2009 pier11 <pier11@operamail.com>
10 * Copyright (C) 2008 Novell, Inc., Anibal Avelar <debianmx@gmail.com>
11 * Copyright (C) 2007 Anibal Avelar <debianmx@gmail.com>
12 * Copyright (C) 2005 Thomas Butter <butter@uni-mannheim.de>
15 * Thanks to Google's Summer of Code Program and the helpful mentors
18 * Session-based SIP MESSAGE documentation:
19 * http://tools.ietf.org/html/draft-ietf-simple-im-session-00
21 * This program is free software; you can redistribute it and/or modify
22 * it under the terms of the GNU General Public License as published by
23 * the Free Software Foundation; either version 2 of the License, or
24 * (at your option) any later version.
26 * This program is distributed in the hope that it will be useful,
27 * but WITHOUT ANY WARRANTY; without even the implied warranty of
28 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
29 * GNU General Public License for more details.
31 * You should have received a copy of the GNU General Public License
32 * along with this program; if not, write to the Free Software
33 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
41 #include "win32dep.h" /* for LOCALEDIR */
45 #define _LIBC_INTERNAL_
48 #include "libc_interface.h"
50 #include <sys/types.h>
51 #include <sys/socket.h>
52 #include <netinet/in.h>
64 #include <gmime/gmime.h>
67 #include "sipe-common.h"
71 #include "connection.h"
72 #include "conversation.h"
74 #include "circbuffer.h"
83 #include "savedstatuses.h"
87 #include "core-depurple.h" /* Temporary for the core de-purple transition */
92 #include "sipe-backend.h"
94 #include "sipe-chat.h"
95 #include "sipe-conf.h"
96 #include "sipe-core.h"
97 #include "sipe-dialog.h"
100 #include "sipe-mime.h"
101 #include "sipe-nls.h"
102 #include "sipe-session.h"
103 #include "sipe-sign.h"
104 #include "sipe-utils.h"
105 #include "sipe-xml.h"
106 #include "http-conn.h"
110 /* Backward compatibility when compiling against 2.4.x API */
111 #if !PURPLE_VERSION_CHECK(2,5,0)
112 #define PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY 0x0100
115 #define SIPE_IDLE_SET_DELAY 1 /* 1 sec */
117 #define UPDATE_CALENDAR_DELAY 1*60 /* 1 min */
118 #define UPDATE_CALENDAR_INTERVAL 30*60 /* 30 min */
120 /* Keep in sync with sipe_transport_type! */
121 static const char *transport_descriptor
[] = { "tls", "tcp", "udp" };
122 #define TRANSPORT_DESCRIPTOR (transport_descriptor[sip->transport])
124 /* Status identifiers (see also: sipe_status_types()) */
125 #define SIPE_STATUS_ID_UNKNOWN purple_primitive_get_id_from_type(PURPLE_STATUS_UNSET) /* Unset (primitive) */
126 #define SIPE_STATUS_ID_OFFLINE purple_primitive_get_id_from_type(PURPLE_STATUS_OFFLINE) /* Offline (primitive) */
127 #define SIPE_STATUS_ID_AVAILABLE purple_primitive_get_id_from_type(PURPLE_STATUS_AVAILABLE) /* Online */
128 /* PURPLE_STATUS_UNAVAILABLE: */
129 #define SIPE_STATUS_ID_BUSY "busy" /* Busy */
130 #define SIPE_STATUS_ID_BUSYIDLE "busyidle" /* BusyIdle */
131 #define SIPE_STATUS_ID_DND "do-not-disturb" /* Do Not Disturb */
132 #define SIPE_STATUS_ID_IN_MEETING "in-a-meeting" /* In a meeting */
133 #define SIPE_STATUS_ID_IN_CONF "in-a-conference" /* In a conference */
134 #define SIPE_STATUS_ID_ON_PHONE "on-the-phone" /* On the phone */
135 #define SIPE_STATUS_ID_INVISIBLE purple_primitive_get_id_from_type(PURPLE_STATUS_INVISIBLE) /* Appear Offline */
136 /* PURPLE_STATUS_AWAY: */
137 #define SIPE_STATUS_ID_IDLE "idle" /* Idle/Inactive */
138 #define SIPE_STATUS_ID_BRB "be-right-back" /* Be Right Back */
139 #define SIPE_STATUS_ID_AWAY purple_primitive_get_id_from_type(PURPLE_STATUS_AWAY) /* Away (primitive) */
140 /** Reuters status (user settable) */
141 #define SIPE_STATUS_ID_LUNCH "out-to-lunch" /* Out To Lunch */
142 /* ??? PURPLE_STATUS_EXTENDED_AWAY */
143 /* ??? PURPLE_STATUS_MOBILE */
144 /* ??? PURPLE_STATUS_TUNE */
146 /* Status attributes (see also sipe_status_types() */
147 #define SIPE_STATUS_ATTR_ID_MESSAGE "message"
149 #define SDP_ACCEPT_TYPES "text/plain text/html image/gif multipart/related application/im-iscomposing+xml application/ms-imdn+xml text/x-msmsgsinvite"
151 static struct sipe_activity_map_struct
156 const char *status_id
;
158 } const sipe_activity_map
[] =
160 /* This has nothing to do with Availability numbers, like 3500 (online).
161 * Just a mapping of Communicator Activities to Purple statuses to be able display them in Pidgin.
163 { SIPE_ACTIVITY_UNSET
, "unset", NULL
, NULL
},
164 { SIPE_ACTIVITY_ONLINE
, "online", NULL
, NULL
},
165 { SIPE_ACTIVITY_INACTIVE
, SIPE_STATUS_ID_IDLE
, N_("Inactive") , NULL
},
166 { SIPE_ACTIVITY_BUSY
, SIPE_STATUS_ID_BUSY
, N_("Busy") , SIPE_STATUS_ID_BUSY
},
167 { SIPE_ACTIVITY_BUSYIDLE
, SIPE_STATUS_ID_BUSYIDLE
, N_("Busy-Idle") , NULL
},
168 { SIPE_ACTIVITY_DND
, SIPE_STATUS_ID_DND
, NULL
, SIPE_STATUS_ID_DND
},
169 { SIPE_ACTIVITY_BRB
, SIPE_STATUS_ID_BRB
, N_("Be right back") , SIPE_STATUS_ID_BRB
},
170 { SIPE_ACTIVITY_AWAY
, "away", NULL
, NULL
},
171 { SIPE_ACTIVITY_LUNCH
, SIPE_STATUS_ID_LUNCH
, N_("Out to lunch") , NULL
},
172 { SIPE_ACTIVITY_OFFLINE
, "offline", NULL
, NULL
},
173 { SIPE_ACTIVITY_ON_PHONE
, SIPE_STATUS_ID_ON_PHONE
, N_("In a call") , NULL
},
174 { SIPE_ACTIVITY_IN_CONF
, SIPE_STATUS_ID_IN_CONF
, N_("In a conference") , NULL
},
175 { SIPE_ACTIVITY_IN_MEETING
, SIPE_STATUS_ID_IN_MEETING
, N_("In a meeting") , NULL
},
176 { SIPE_ACTIVITY_OOF
, "out-of-office", N_("Out of office") , NULL
},
177 { SIPE_ACTIVITY_URGENT_ONLY
, "urgent-interruptions-only", N_("Urgent interruptions only") , NULL
}
179 /** @param x is sipe_activity */
180 #define SIPE_ACTIVITY_I18N(x) gettext(sipe_activity_map[x].desc)
183 /* Action name templates */
184 #define ACTION_NAME_PRESENCE "<presence><%s>"
187 sipe_get_activity_by_token(const char *token
)
191 for (i
= 0; i
< SIPE_ACTIVITY_NUM_TYPES
; i
++)
193 if (sipe_strequal(token
, sipe_activity_map
[i
].token
))
194 return sipe_activity_map
[i
].type
;
197 return sipe_activity_map
[0].type
;
201 sipe_get_activity_desc_by_token(const char *token
)
203 if (!token
) return NULL
;
205 return SIPE_ACTIVITY_I18N(sipe_get_activity_by_token(token
));
208 /** Allows to send typed messages from chat window again after account reinstantiation. */
210 sipe_rejoin_chat(PurpleConversation
*conv
)
212 if (purple_conversation_get_type(conv
) == PURPLE_CONV_TYPE_CHAT
&&
213 PURPLE_CONV_CHAT(conv
)->left
)
215 PURPLE_CONV_CHAT(conv
)->left
= FALSE
;
216 purple_conversation_update(conv
, PURPLE_CONV_UPDATE_CHATLEFT
);
220 static char *genbranch()
222 return g_strdup_printf("z9hG4bK%04X%04X%04X%04X%04X",
223 rand() & 0xFFFF, rand() & 0xFFFF, rand() & 0xFFFF,
224 rand() & 0xFFFF, rand() & 0xFFFF);
228 static char *default_ua
= NULL
;
230 sipe_get_useragent(struct sipe_account_data
*sip
)
232 const char *useragent
= purple_account_get_string(sip
->account
, "useragent", "");
233 if (is_empty(useragent
)) {
235 /*@TODO: better approach to define _user_ OS, it's version and host architecture */
237 #if defined(__linux__) || defined(__linux) || defined(__LINUX__)
238 #define SIPE_TARGET_PLATFORM "linux"
239 #elif defined(__NetBSD__) ||defined( __OpenBSD__) || defined(__FreeBSD__)
240 #define SIPE_TARGET_PLATFORM "bsd"
241 #elif defined(__APPLE__) || defined(__MACOS__)
242 #define SIPE_TARGET_PLATFORM "macosx"
243 #elif defined(_AIX) || defined(__AIX__) || defined(__aix__)
244 #define SIPE_TARGET_PLATFORM "aix"
245 #elif defined(__solaris__) || defined(__sun)
246 #define SIPE_TARGET_PLATFORM "sun"
247 #elif defined(_WIN32)
248 #define SIPE_TARGET_PLATFORM "win"
249 #elif defined(__CYGWIN__)
250 #define SIPE_TARGET_PLATFORM "cygwin"
251 #elif defined(__hpux__)
252 #define SIPE_TARGET_PLATFORM "hpux"
253 #elif defined(__sgi__)
254 #define SIPE_TARGET_PLATFORM "irix"
256 #define SIPE_TARGET_PLATFORM "unknown"
259 #if defined(__amd64__) || defined(__x86_64__) || defined(_M_AMD64)
260 #define SIPE_TARGET_ARCH "x86_64"
261 #elif defined(__386__) || defined(__i386__) || defined(__i386) || defined(_M_IX86) || defined(_M_I386)
262 #define SIPE_TARGET_ARCH "i386"
263 #elif defined(__ppc64__)
264 #define SIPE_TARGET_ARCH "ppc64"
265 #elif defined(__powerpc__) || defined(__powerpc) || defined(__ppc__) || defined(__PPC__) || defined(_M_PPC) || defined(_ARCH_PPC) || defined(_ARCH_PWR)
266 #define SIPE_TARGET_ARCH "ppc"
267 #elif defined(__hppa__) || defined(__hppa)
268 #define SIPE_TARGET_ARCH "hppa"
269 #elif defined(__mips__) || defined(__mips) || defined(_MIPS_ARCH) || defined(_M_MRX000)
270 #define SIPE_TARGET_ARCH "mips"
271 #elif defined(__s390__) || defined(__s390) || defined(__s390x__) || defined(__s390x)
272 #define SIPE_TARGET_ARCH "s390"
273 #elif defined(__sparc__) || defined(__sparc) || defined(__sparcv8)
274 #define SIPE_TARGET_ARCH "sparc"
275 #elif defined(__arm__)
276 #define SIPE_TARGET_ARCH "arm"
278 #define SIPE_TARGET_ARCH "other"
281 default_ua
= g_strdup_printf("Purple/%s Sipe/" PACKAGE_VERSION
" (" SIPE_TARGET_PLATFORM
"-" SIPE_TARGET_ARCH
"; %s)",
282 purple_core_get_version(),
283 sip
->server_version
? sip
->server_version
: "");
285 useragent
= default_ua
;
290 static const char *sipe_list_icon(SIPE_UNUSED_PARAMETER PurpleAccount
*a
,
291 SIPE_UNUSED_PARAMETER PurpleBuddy
*b
)
296 static gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
);
298 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
, PurpleInputCondition cond
);
299 static void sipe_ssl_connect_failure(PurpleSslConnection
*gsc
, PurpleSslErrorType error
,
302 static void sipe_close(PurpleConnection
*gc
);
304 static void send_presence_status(struct sipe_account_data
*sip
);
306 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
);
308 static void sipe_keep_alive(PurpleConnection
*gc
)
310 struct sipe_account_data
*sip
= gc
->proto_data
;
311 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
312 /* in case of UDP send a packet only with a 0 byte to remain in the NAT table */
313 gchar buf
[2] = {0, 0};
314 SIPE_DEBUG_INFO_NOFORMAT("sending keep alive");
315 sendto(sip
->fd
, buf
, 1, 0, sip
->serveraddr
, sizeof(struct sockaddr_in
));
317 time_t now
= time(NULL
);
318 if ((sip
->keepalive_timeout
> 0) &&
319 ((guint
) (now
- sip
->last_keepalive
) >= sip
->keepalive_timeout
) &&
320 ((guint
) (now
- gc
->last_received
) >= sip
->keepalive_timeout
)
322 SIPE_DEBUG_INFO("sending keep alive %d", sip
->keepalive_timeout
);
323 sendout_pkt(gc
, "\r\n\r\n");
324 sip
->last_keepalive
= now
;
329 static struct sip_connection
*connection_find(struct sipe_account_data
*sip
, int fd
)
331 struct sip_connection
*ret
= NULL
;
332 GSList
*entry
= sip
->openconns
;
335 if (ret
->fd
== fd
) return ret
;
341 static void sipe_auth_free(struct sip_auth
*auth
)
343 g_free(auth
->opaque
);
347 g_free(auth
->target
);
350 auth
->type
= AUTH_TYPE_UNSET
;
353 g_free(auth
->gssapi_data
);
354 auth
->gssapi_data
= NULL
;
355 sip_sec_destroy_context(auth
->gssapi_context
);
356 auth
->gssapi_context
= NULL
;
359 static struct sip_connection
*connection_create(struct sipe_account_data
*sip
, int fd
)
361 struct sip_connection
*ret
= g_new0(struct sip_connection
, 1);
363 sip
->openconns
= g_slist_append(sip
->openconns
, ret
);
367 static void connection_remove(struct sipe_account_data
*sip
, int fd
)
369 struct sip_connection
*conn
= connection_find(sip
, fd
);
371 sip
->openconns
= g_slist_remove(sip
->openconns
, conn
);
372 if (conn
->inputhandler
) purple_input_remove(conn
->inputhandler
);
378 static void connection_free_all(struct sipe_account_data
*sip
)
380 struct sip_connection
*ret
= NULL
;
381 GSList
*entry
= sip
->openconns
;
384 connection_remove(sip
, ret
->fd
);
385 entry
= sip
->openconns
;
390 sipe_make_signature(struct sipe_account_data
*sip
,
393 static gchar
*auth_header(struct sipe_account_data
*sip
, struct sip_auth
*auth
, struct sipmsg
* msg
)
395 const char *authuser
= sip
->authuser
;
398 if (!authuser
|| strlen(authuser
) < 1) {
399 authuser
= sip
->username
;
402 if (auth
->type
== AUTH_TYPE_NTLM
|| auth
->type
== AUTH_TYPE_KERBEROS
) { /* NTLM or Kerberos */
403 gchar
*auth_protocol
= (auth
->type
== AUTH_TYPE_NTLM
? "NTLM" : "Kerberos");
406 // If we have a signature for the message, include that
407 if (msg
->signature
) {
408 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
);
411 if ((auth
->type
== AUTH_TYPE_NTLM
&& auth
->nc
== 3 && auth
->gssapi_data
&& auth
->gssapi_context
== NULL
)
412 || (auth
->type
== AUTH_TYPE_KERBEROS
&& auth
->nc
== 3)) {
415 gchar
*sign_str
= NULL
;
417 gssapi_data
= sip_sec_init_context(&(auth
->gssapi_context
),
420 purple_account_get_bool(sip
->account
, "sso", TRUE
),
421 sip
->authdomain
? sip
->authdomain
: "",
426 if (!gssapi_data
|| !auth
->gssapi_context
) {
427 sip
->gc
->wants_to_die
= TRUE
;
428 purple_connection_error(sip
->gc
, _("Failed to authenticate to server"));
432 if (auth
->version
> 3) {
433 sipe_make_signature(sip
, msg
);
434 sign_str
= g_strdup_printf(", crand=\"%s\", cnum=\"%s\", response=\"%s\"",
435 msg
->rand
, msg
->num
, msg
->signature
);
437 sign_str
= g_strdup("");
440 opaque
= (auth
->type
== AUTH_TYPE_NTLM
? g_strdup_printf(", opaque=\"%s\"", auth
->opaque
) : g_strdup(""));
441 version_str
= auth
->version
> 2 ? g_strdup_printf(", version=%d", auth
->version
) : g_strdup("");
442 ret
= g_strdup_printf("%s qop=\"auth\"%s, realm=\"%s\", targetname=\"%s\", gssapi-data=\"%s\"%s%s", auth_protocol
, opaque
, auth
->realm
, auth
->target
, gssapi_data
, version_str
, sign_str
);
450 version_str
= auth
->version
> 2 ? g_strdup_printf(", version=%d", auth
->version
) : g_strdup("");
451 ret
= g_strdup_printf("%s qop=\"auth\", realm=\"%s\", targetname=\"%s\", gssapi-data=\"\"%s", auth_protocol
, auth
->realm
, auth
->target
, version_str
);
455 } else { /* Digest */
458 guchar digest
[SIPE_DIGEST_MD5_LENGTH
];
460 /* Calculate new session key */
462 SIPE_DEBUG_INFO("Digest nonce: %s realm: %s", auth
->gssapi_data
, auth
->realm
);
465 * Calculate a session key for HTTP MD5 Digest authentation
467 * See RFC 2617 for more information.
469 string
= g_strdup_printf("%s:%s:%s",
473 sipe_backend_digest_md5((guchar
*)string
, strlen(string
), digest
);
475 auth
->opaque
= buff_to_hex_str(digest
, sizeof(digest
));
480 * Calculate a response for HTTP MD5 Digest authentication
482 * See RFC 2617 for more information.
484 string
= g_strdup_printf("%s:%s", msg
->method
, msg
->target
);
485 sipe_backend_digest_md5((guchar
*)string
, strlen(string
), digest
);
488 hex_digest
= buff_to_hex_str(digest
, sizeof(digest
));
489 string
= g_strdup_printf("%s:%s:%s", auth
->opaque
, auth
->gssapi_data
, hex_digest
);
491 sipe_backend_digest_md5((guchar
*)string
, strlen(string
), digest
);
494 hex_digest
= buff_to_hex_str(digest
, sizeof(digest
));
495 SIPE_DEBUG_INFO("Digest response %s", hex_digest
);
496 ret
= g_strdup_printf("Digest username=\"%s\", realm=\"%s\", nonce=\"%s\", uri=\"%s\", nc=\"%08d\", response=\"%s\"", authuser
, auth
->realm
, auth
->gssapi_data
, msg
->target
, auth
->nc
++, hex_digest
);
502 static char *parse_attribute(const char *attrname
, const char *source
)
504 const char *tmp
, *tmp2
;
506 int len
= strlen(attrname
);
508 if (g_str_has_prefix(source
, attrname
)) {
510 tmp2
= g_strstr_len(tmp
, strlen(tmp
), "\"");
512 retval
= g_strndup(tmp
, tmp2
- tmp
);
514 retval
= g_strdup(tmp
);
520 static void fill_auth(const gchar
*hdr
, struct sip_auth
*auth
)
526 SIPE_DEBUG_ERROR_NOFORMAT("fill_auth: hdr==NULL");
530 if (!g_strncasecmp(hdr
, "NTLM", 4)) {
531 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type NTLM");
532 auth
->type
= AUTH_TYPE_NTLM
;
535 } else if (!g_strncasecmp(hdr
, "Kerberos", 8)) {
536 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Kerberos");
537 auth
->type
= AUTH_TYPE_KERBEROS
;
541 SIPE_DEBUG_INFO_NOFORMAT("fill_auth: type Digest");
542 auth
->type
= AUTH_TYPE_DIGEST
;
546 parts
= g_strsplit(hdr
, "\", ", 0);
547 for (i
= 0; parts
[i
]; i
++) {
550 //SIPE_DEBUG_INFO("parts[i] %s", parts[i]);
552 if ((tmp
= parse_attribute("gssapi-data=\"", parts
[i
]))) {
553 g_free(auth
->gssapi_data
);
554 auth
->gssapi_data
= tmp
;
556 if (auth
->type
== AUTH_TYPE_NTLM
) {
557 /* NTLM module extracts nonce from gssapi-data */
561 } else if ((tmp
= parse_attribute("nonce=\"", parts
[i
]))) {
562 /* Only used with AUTH_TYPE_DIGEST */
563 g_free(auth
->gssapi_data
);
564 auth
->gssapi_data
= tmp
;
565 } else if ((tmp
= parse_attribute("opaque=\"", parts
[i
]))) {
566 g_free(auth
->opaque
);
568 } else if ((tmp
= parse_attribute("realm=\"", parts
[i
]))) {
572 if (auth
->type
== AUTH_TYPE_DIGEST
) {
573 /* Throw away old session key */
574 g_free(auth
->opaque
);
578 } else if ((tmp
= parse_attribute("targetname=\"", parts
[i
]))) {
579 g_free(auth
->target
);
581 } else if ((tmp
= parse_attribute("version=", parts
[i
]))) {
582 auth
->version
= atoi(tmp
);
585 // uncomment to revert to previous functionality if version 3+ does not work.
586 // auth->version = 2;
593 static void sipe_canwrite_cb(gpointer data
,
594 SIPE_UNUSED_PARAMETER gint source
,
595 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
597 PurpleConnection
*gc
= data
;
598 struct sipe_account_data
*sip
= gc
->proto_data
;
602 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
604 if (max_write
== 0) {
605 if (sip
->tx_handler
!= 0){
606 purple_input_remove(sip
->tx_handler
);
612 written
= write(sip
->fd
, sip
->txbuf
->outptr
, max_write
);
614 if (written
< 0 && errno
== EAGAIN
)
616 else if (written
<= 0) {
617 /*TODO: do we really want to disconnect on a failure to write?*/
618 purple_connection_error(gc
, _("Could not write"));
622 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
625 static void sipe_canwrite_cb_ssl(gpointer data
,
626 SIPE_UNUSED_PARAMETER gint src
,
627 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
629 PurpleConnection
*gc
= data
;
630 struct sipe_account_data
*sip
= gc
->proto_data
;
634 max_write
= purple_circ_buffer_get_max_read(sip
->txbuf
);
636 if (max_write
== 0) {
637 if (sip
->tx_handler
!= 0) {
638 purple_input_remove(sip
->tx_handler
);
644 written
= purple_ssl_write(sip
->gsc
, sip
->txbuf
->outptr
, max_write
);
646 if (written
< 0 && errno
== EAGAIN
)
648 else if (written
<= 0) {
649 /*TODO: do we really want to disconnect on a failure to write?*/
650 purple_connection_error(gc
, _("Could not write"));
654 purple_circ_buffer_mark_read(sip
->txbuf
, written
);
657 static void sipe_input_cb(gpointer data
, gint source
, PurpleInputCondition cond
);
659 static void send_later_cb(gpointer data
, gint source
,
660 SIPE_UNUSED_PARAMETER
const gchar
*error
)
662 PurpleConnection
*gc
= data
;
663 struct sipe_account_data
*sip
;
664 struct sip_connection
*conn
;
666 if (!PURPLE_CONNECTION_IS_VALID(gc
))
674 purple_connection_error(gc
, _("Could not connect"));
678 sip
= gc
->proto_data
;
680 sip
->connecting
= FALSE
;
681 sip
->last_keepalive
= time(NULL
);
683 sipe_canwrite_cb(gc
, sip
->fd
, PURPLE_INPUT_WRITE
);
685 /* If there is more to write now, we need to register a handler */
686 if (sip
->txbuf
->bufused
> 0)
687 sip
->tx_handler
= purple_input_add(sip
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb
, gc
);
689 conn
= connection_create(sip
, source
);
690 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
693 static struct sipe_account_data
*sipe_setup_ssl(PurpleConnection
*gc
, PurpleSslConnection
*gsc
)
695 struct sipe_account_data
*sip
;
697 if (!PURPLE_CONNECTION_IS_VALID(gc
))
699 if (gsc
) purple_ssl_close(gsc
);
703 sip
= gc
->proto_data
;
706 sip
->listenport
= purple_network_get_port_from_fd(gsc
->fd
);
707 sip
->connecting
= FALSE
;
708 sip
->last_keepalive
= time(NULL
);
710 connection_create(sip
, gsc
->fd
);
712 purple_ssl_input_add(gsc
, sipe_input_cb_ssl
, gc
);
717 static void send_later_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
718 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
720 PurpleConnection
*gc
= data
;
721 struct sipe_account_data
*sip
= sipe_setup_ssl(gc
, gsc
);
722 if (sip
== NULL
) return;
724 sipe_canwrite_cb_ssl(gc
, gsc
->fd
, PURPLE_INPUT_WRITE
);
726 /* If there is more to write now */
727 if (sip
->txbuf
->bufused
> 0) {
728 sip
->tx_handler
= purple_input_add(gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
733 static void sendlater(PurpleConnection
*gc
, const char *buf
)
735 struct sipe_account_data
*sip
= gc
->proto_data
;
737 if (!sip
->connecting
) {
738 SIPE_DEBUG_INFO("connecting to %s port %d", sip
->realhostname
? sip
->realhostname
: "{NULL}", sip
->realport
);
739 if (sip
->transport
== SIPE_TRANSPORT_TLS
){
740 sip
->gsc
= purple_ssl_connect(sip
->account
,sip
->realhostname
, sip
->realport
, send_later_cb_ssl
, sipe_ssl_connect_failure
, sip
->gc
);
742 if (purple_proxy_connect(gc
, sip
->account
, sip
->realhostname
, sip
->realport
, send_later_cb
, gc
) == NULL
) {
743 purple_connection_error(gc
, _("Could not create socket"));
746 sip
->connecting
= TRUE
;
749 if (purple_circ_buffer_get_max_read(sip
->txbuf
) > 0)
750 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
752 purple_circ_buffer_append(sip
->txbuf
, buf
, strlen(buf
));
755 static void sendout_pkt(PurpleConnection
*gc
, const char *buf
)
757 struct sipe_account_data
*sip
= gc
->proto_data
;
758 time_t currtime
= time(NULL
);
759 int writelen
= strlen(buf
);
762 SIPE_DEBUG_INFO("sending - %s######\n%s######", ctime(&currtime
), tmp
= fix_newlines(buf
));
764 if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
765 if (sendto(sip
->fd
, buf
, writelen
, 0, sip
->serveraddr
, sizeof(struct sockaddr_in
)) < writelen
) {
766 SIPE_DEBUG_INFO_NOFORMAT("could not send packet");
775 if (sip
->tx_handler
) {
780 ret
= purple_ssl_write(sip
->gsc
, buf
, writelen
);
782 ret
= write(sip
->fd
, buf
, writelen
);
786 if (ret
< 0 && errno
== EAGAIN
)
788 else if (ret
<= 0) { /* XXX: When does this happen legitimately? */
793 if (ret
< writelen
) {
794 if (!sip
->tx_handler
){
796 sip
->tx_handler
= purple_input_add(sip
->gsc
->fd
, PURPLE_INPUT_WRITE
, sipe_canwrite_cb_ssl
, gc
);
799 sip
->tx_handler
= purple_input_add(sip
->fd
,
800 PURPLE_INPUT_WRITE
, sipe_canwrite_cb
,
805 /* XXX: is it OK to do this? You might get part of a request sent
806 with part of another. */
807 if (sip
->txbuf
->bufused
> 0)
808 purple_circ_buffer_append(sip
->txbuf
, "\r\n", 2);
810 purple_circ_buffer_append(sip
->txbuf
, buf
+ ret
,
816 static int sipe_send_raw(PurpleConnection
*gc
, const char *buf
, int len
)
818 sendout_pkt(gc
, buf
);
822 static void sendout_sipmsg(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
824 GSList
*tmp
= msg
->headers
;
827 GString
*outstr
= g_string_new("");
828 g_string_append_printf(outstr
, "%s %s SIP/2.0\r\n", msg
->method
, msg
->target
);
830 name
= ((struct sipnameval
*) (tmp
->data
))->name
;
831 value
= ((struct sipnameval
*) (tmp
->data
))->value
;
832 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
833 tmp
= g_slist_next(tmp
);
835 g_string_append_printf(outstr
, "\r\n%s", msg
->body
? msg
->body
: "");
836 sendout_pkt(sip
->gc
, outstr
->str
);
837 g_string_free(outstr
, TRUE
);
841 sipe_make_signature(struct sipe_account_data
*sip
,
844 if (sip
->registrar
.gssapi_context
) {
845 struct sipmsg_breakdown msgbd
;
846 gchar
*signature_input_str
;
848 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
849 msgbd
.rand
= g_strdup_printf("%08x", g_random_int());
850 sip
->registrar
.ntlm_num
++;
851 msgbd
.num
= g_strdup_printf("%d", sip
->registrar
.ntlm_num
);
852 signature_input_str
= sipmsg_breakdown_get_string(sip
->registrar
.version
, &msgbd
);
853 if (signature_input_str
!= NULL
) {
854 char *signature_hex
= sip_sec_make_signature(sip
->registrar
.gssapi_context
, signature_input_str
);
855 msg
->signature
= signature_hex
;
856 msg
->rand
= g_strdup(msgbd
.rand
);
857 msg
->num
= g_strdup(msgbd
.num
);
858 g_free(signature_input_str
);
860 sipmsg_breakdown_free(&msgbd
);
864 static void sign_outgoing_message (struct sipmsg
* msg
, struct sipe_account_data
*sip
, const gchar
*method
)
868 if (sip
->registrar
.type
== AUTH_TYPE_UNSET
) {
872 sipe_make_signature(sip
, msg
);
874 if (sip
->registrar
.type
&& sipe_strequal(method
, "REGISTER")) {
875 buf
= auth_header(sip
, &sip
->registrar
, msg
);
877 sipmsg_add_header_now_pos(msg
, "Authorization", buf
, 5);
880 } else if (sipe_strequal(method
,"SUBSCRIBE") || sipe_strequal(method
,"SERVICE") || sipe_strequal(method
,"MESSAGE") || sipe_strequal(method
,"INVITE") || sipe_strequal(method
, "ACK") || sipe_strequal(method
, "NOTIFY") || sipe_strequal(method
, "BYE") || sipe_strequal(method
, "INFO") || sipe_strequal(method
, "OPTIONS") || sipe_strequal(method
, "REFER")) {
881 sip
->registrar
.nc
= 3;
882 sip
->registrar
.type
= AUTH_TYPE_NTLM
;
884 if (purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
885 sip
->registrar
.type
= AUTH_TYPE_KERBEROS
;
890 buf
= auth_header(sip
, &sip
->registrar
, msg
);
891 sipmsg_add_header_now_pos(msg
, "Authorization", buf
, 5);
894 SIPE_DEBUG_INFO("not adding auth header to msg w/ method %s", method
);
898 void send_sip_response(PurpleConnection
*gc
, struct sipmsg
*msg
, int code
,
899 const char *text
, const char *body
)
903 GString
*outstr
= g_string_new("");
904 struct sipe_account_data
*sip
= gc
->proto_data
;
907 const gchar
*keepers
[] = { "To", "From", "Call-ID", "CSeq", "Via", "Record-Route", NULL
};
909 /* Can return NULL! */
910 contact
= get_contact(sip
);
912 sipmsg_add_header(msg
, "Contact", contact
);
917 gchar
*len
= g_strdup_printf("%" G_GSIZE_FORMAT
, (gsize
) strlen(body
));
918 sipmsg_add_header(msg
, "Content-Length", len
);
921 sipmsg_add_header(msg
, "Content-Length", "0");
924 msg
->response
= code
;
926 sipmsg_strip_headers(msg
, keepers
);
927 sipmsg_merge_new_headers(msg
);
928 sign_outgoing_message(msg
, sip
, msg
->method
);
930 g_string_append_printf(outstr
, "SIP/2.0 %d %s\r\n", code
, text
);
933 name
= ((struct sipnameval
*) (tmp
->data
))->name
;
934 value
= ((struct sipnameval
*) (tmp
->data
))->value
;
936 g_string_append_printf(outstr
, "%s: %s\r\n", name
, value
);
937 tmp
= g_slist_next(tmp
);
939 g_string_append_printf(outstr
, "\r\n%s", body
? body
: "");
940 sendout_pkt(gc
, outstr
->str
);
941 g_string_free(outstr
, TRUE
);
944 static void transactions_remove(struct sipe_account_data
*sip
, struct transaction
*trans
)
946 if (sip
->transactions
) {
947 sip
->transactions
= g_slist_remove(sip
->transactions
, trans
);
948 SIPE_DEBUG_INFO("sip->transactions count:%d after removal", g_slist_length(sip
->transactions
));
950 if (trans
->msg
) sipmsg_free(trans
->msg
);
951 if (trans
->payload
) {
952 (*trans
->payload
->destroy
)(trans
->payload
->data
);
953 g_free(trans
->payload
);
960 static struct transaction
*
961 transactions_add_buf(struct sipe_account_data
*sip
, const struct sipmsg
*msg
, void *callback
)
963 const gchar
*call_id
;
965 struct transaction
*trans
= g_new0(struct transaction
, 1);
967 trans
->time
= time(NULL
);
968 trans
->msg
= (struct sipmsg
*)msg
;
969 call_id
= sipmsg_find_header(trans
->msg
, "Call-ID");
970 cseq
= sipmsg_find_header(trans
->msg
, "CSeq");
971 trans
->key
= g_strdup_printf("<%s><%s>", call_id
, cseq
);
972 trans
->callback
= callback
;
973 sip
->transactions
= g_slist_append(sip
->transactions
, trans
);
974 SIPE_DEBUG_INFO("sip->transactions count:%d after addition", g_slist_length(sip
->transactions
));
978 static struct transaction
*transactions_find(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
980 struct transaction
*trans
;
981 GSList
*transactions
= sip
->transactions
;
982 const gchar
*call_id
= sipmsg_find_header(msg
, "Call-ID");
983 const gchar
*cseq
= sipmsg_find_header(msg
, "CSeq");
986 if (!call_id
|| !cseq
) {
987 SIPE_DEBUG_ERROR_NOFORMAT("transaction_find: no Call-ID or CSeq!");
991 key
= g_strdup_printf("<%s><%s>", call_id
, cseq
);
992 while (transactions
) {
993 trans
= transactions
->data
;
994 if (!g_strcasecmp(trans
->key
, key
)) {
998 transactions
= transactions
->next
;
1005 struct transaction
*
1006 send_sip_request(PurpleConnection
*gc
, const gchar
*method
,
1007 const gchar
*url
, const gchar
*to
, const gchar
*addheaders
,
1008 const gchar
*body
, struct sip_dialog
*dialog
, TransCallback tc
)
1010 struct sipe_account_data
*sip
= gc
->proto_data
;
1011 const char *addh
= "";
1014 gchar
*ourtag
= dialog
&& dialog
->ourtag
? g_strdup(dialog
->ourtag
) : NULL
;
1015 gchar
*theirtag
= dialog
&& dialog
->theirtag
? g_strdup(dialog
->theirtag
) : NULL
;
1016 gchar
*theirepid
= dialog
&& dialog
->theirepid
? g_strdup(dialog
->theirepid
) : NULL
;
1017 gchar
*callid
= dialog
&& dialog
->callid
? g_strdup(dialog
->callid
) : gencallid();
1018 gchar
*branch
= dialog
&& dialog
->callid
? NULL
: genbranch();
1019 gchar
*route
= g_strdup("");
1020 gchar
*epid
= get_epid(sip
);
1021 int cseq
= dialog
? ++dialog
->cseq
: 1 /* as Call-Id is new in this case */;
1022 struct transaction
*trans
= NULL
;
1024 if (dialog
&& dialog
->routes
)
1026 GSList
*iter
= dialog
->routes
;
1031 route
= g_strdup_printf("%sRoute: <%s>\r\n", route
, (char *)iter
->data
);
1033 iter
= g_slist_next(iter
);
1037 if (!ourtag
&& !dialog
) {
1041 if (sipe_strequal(method
, "REGISTER")) {
1042 if (sip
->regcallid
) {
1044 callid
= g_strdup(sip
->regcallid
);
1046 sip
->regcallid
= g_strdup(callid
);
1051 if (addheaders
) addh
= addheaders
;
1053 buf
= g_strdup_printf("%s %s SIP/2.0\r\n"
1054 "Via: SIP/2.0/%s %s:%d%s%s\r\n"
1055 "From: <sip:%s>%s%s;epid=%s\r\n"
1056 "To: <%s>%s%s%s%s\r\n"
1057 "Max-Forwards: 70\r\n"
1059 "User-Agent: %s\r\n"
1062 "Content-Length: %" G_GSIZE_FORMAT
"\r\n\r\n%s",
1064 dialog
&& dialog
->request
? dialog
->request
: url
,
1065 TRANSPORT_DESCRIPTOR
,
1066 sipe_backend_network_ip_address(),
1068 branch
? ";branch=" : "",
1069 branch
? branch
: "",
1071 ourtag
? ";tag=" : "",
1072 ourtag
? ourtag
: "",
1075 theirtag
? ";tag=" : "",
1076 theirtag
? theirtag
: "",
1077 theirepid
? ";epid=" : "",
1078 theirepid
? theirepid
: "",
1081 sipe_get_useragent(sip
),
1085 body
? (gsize
) strlen(body
) : 0,
1089 //printf ("parsing msg buf:\n%s\n\n", buf);
1090 msg
= sipmsg_parse_msg(buf
);
1101 sign_outgoing_message (msg
, sip
, method
);
1103 buf
= sipmsg_to_string (msg
);
1105 /* add to ongoing transactions */
1106 /* ACK isn't supposed to be answered ever. So we do not keep transaction for it. */
1107 if (!sipe_strequal(method
, "ACK")) {
1108 trans
= transactions_add_buf(sip
, msg
, tc
);
1112 sendout_pkt(gc
, buf
);
1119 * @param from0 from URI (with 'sip:' prefix). Will be filled with self-URI if NULL passed.
1122 send_soap_request_with_cb(struct sipe_account_data
*sip
,
1125 TransCallback callback
,
1126 struct transaction_payload
*payload
)
1128 gchar
*from
= from0
? g_strdup(from0
) : sip_uri_self(sip
);
1129 gchar
*contact
= get_contact(sip
);
1130 gchar
*hdr
= g_strdup_printf("Contact: %s\r\n"
1131 "Content-Type: application/SOAP+xml\r\n",contact
);
1133 struct transaction
*trans
= send_sip_request(sip
->gc
, "SERVICE", from
, from
, hdr
, body
, NULL
, callback
);
1134 trans
->payload
= payload
;
1141 static void send_soap_request(struct sipe_account_data
*sip
, gchar
*body
)
1143 send_soap_request_with_cb(sip
, NULL
, body
, NULL
, NULL
);
1146 static char *get_contact_register(struct sipe_account_data
*sip
)
1148 char *epid
= get_epid(sip
);
1149 char *uuid
= generateUUIDfromEPID(epid
);
1150 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>\"", sipe_backend_network_ip_address(), sip
->listenport
, TRANSPORT_DESCRIPTOR
, uuid
);
1156 static void do_register_exp(struct sipe_account_data
*sip
, int expire
)
1164 if (!sip
->sipdomain
) return;
1166 uri
= sip_uri_from_name(sip
->sipdomain
);
1167 expires
= expire
>= 0 ? g_strdup_printf("Expires: %d\r\n", expire
) : g_strdup("");
1168 to
= sip_uri_self(sip
);
1169 contact
= get_contact_register(sip
);
1170 hdr
= g_strdup_printf("Contact: %s\r\n"
1171 "Supported: gruu-10, adhoclist, msrtc-event-categories, com.microsoft.msrtc.presence\r\n"
1172 "Event: registration\r\n"
1173 "Allow-Events: presence\r\n"
1174 "ms-keep-alive: UAC;hop-hop=yes\r\n"
1175 "%s", contact
, expires
);
1179 sip
->registerstatus
= 1;
1181 send_sip_request(sip
->gc
, "REGISTER", uri
, to
, hdr
, "", NULL
,
1182 process_register_response
);
1189 static void do_register_cb(struct sipe_account_data
*sip
,
1190 SIPE_UNUSED_PARAMETER
void *unused
)
1192 do_register_exp(sip
, -1);
1193 sip
->reregister_set
= FALSE
;
1196 static void do_register(struct sipe_account_data
*sip
)
1198 do_register_exp(sip
, -1);
1202 * Returns pointer to URI without sip: prefix if any
1204 * @param sip_uri SIP URI possibly with sip: prefix. Example: sip:first.last@hq.company.com
1205 * @return pointer to URL without sip: prefix. Coresponding example: first.last@hq.company.com
1207 * Doesn't allocate memory
1210 sipe_get_no_sip_uri(const char *sip_uri
)
1212 const char *prefix
= "sip:";
1213 if (!sip_uri
) return NULL
;
1215 if (g_str_has_prefix(sip_uri
, prefix
)) {
1216 return (sip_uri
+strlen(prefix
));
1223 sipe_contact_set_acl (struct sipe_account_data
*sip
, const gchar
* who
, gchar
* rights
)
1225 gchar
* body
= g_strdup_printf(SIPE_SOAP_ALLOW_DENY
, who
, rights
, sip
->acl_delta
++);
1226 send_soap_request(sip
, body
);
1231 sipe_change_access_level(struct sipe_account_data
*sip
,
1232 const int container_id
,
1234 const gchar
*value
);
1237 sipe_contact_allow_deny (struct sipe_account_data
*sip
, const gchar
* who
, gboolean allow
)
1240 SIPE_DEBUG_INFO("Authorizing contact %s", who
);
1242 SIPE_DEBUG_INFO("Blocking contact %s", who
);
1246 sipe_change_access_level(sip
, (allow
? -1 : 32000), "user", sipe_get_no_sip_uri(who
));
1248 sipe_contact_set_acl (sip
, who
, allow
? "AA" : "BD");
1253 void sipe_auth_user_cb(void * data
)
1255 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1258 sipe_contact_allow_deny (job
->sip
, job
->who
, TRUE
);
1263 void sipe_deny_user_cb(void * data
)
1265 struct sipe_auth_job
* job
= (struct sipe_auth_job
*) data
;
1268 sipe_contact_allow_deny (job
->sip
, job
->who
, FALSE
);
1273 sipe_add_permit(PurpleConnection
*gc
, const char *name
)
1275 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1277 sipe_contact_allow_deny(sip
, name
, TRUE
);
1281 sipe_add_deny(PurpleConnection
*gc
, const char *name
)
1283 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
1284 sipe_contact_allow_deny(sip
, name
, FALSE
);
1288 sipe_remove_permit_deny(PurpleConnection *gc, const char *name)
1290 struct sipe_account_data *sip = (struct sipe_account_data *)gc->proto_data;
1291 sipe_contact_set_acl(sip, name, "");
1294 /** @applicable: 2005-
1297 sipe_process_presence_wpending (struct sipe_account_data
*sip
, struct sipmsg
* msg
)
1300 const sipe_xml
*watcher
;
1301 // Ensure it's either not a response (eg it's a BENOTIFY) or that it's a 200 OK response
1302 if (msg
->response
!= 0 && msg
->response
!= 200) return;
1304 if (msg
->bodylen
== 0 || msg
->body
== NULL
|| sipe_strequal(sipmsg_find_header(msg
, "Event"), "msrtc.wpending")) return;
1306 watchers
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1307 if (!watchers
) return;
1309 for (watcher
= sipe_xml_child(watchers
, "watcher"); watcher
; watcher
= sipe_xml_twin(watcher
)) {
1310 gchar
* remote_user
= g_strdup(sipe_xml_attribute(watcher
, "uri"));
1311 gchar
* alias
= g_strdup(sipe_xml_attribute(watcher
, "displayName"));
1312 gboolean on_list
= g_hash_table_lookup(sip
->buddies
, remote_user
) != NULL
;
1314 // TODO pull out optional displayName to pass as alias
1316 struct sipe_auth_job
* job
= g_new0(struct sipe_auth_job
, 1);
1317 job
->who
= remote_user
;
1319 purple_account_request_authorization(
1333 sipe_xml_free(watchers
);
1338 sipe_group_add (struct sipe_account_data
*sip
, struct sipe_group
* group
)
1340 PurpleGroup
* purple_group
= purple_find_group(group
->name
);
1341 if (!purple_group
) {
1342 purple_group
= purple_group_new(group
->name
);
1343 purple_blist_add_group(purple_group
, NULL
);
1347 group
->purple_group
= purple_group
;
1348 sip
->groups
= g_slist_append(sip
->groups
, group
);
1349 SIPE_DEBUG_INFO("added group %s (id %d)", group
->name
, group
->id
);
1351 SIPE_DEBUG_INFO("did not add group %s", group
->name
? group
->name
: "");
1355 static struct sipe_group
* sipe_group_find_by_id (struct sipe_account_data
*sip
, int id
)
1357 struct sipe_group
*group
;
1363 entry
= sip
->groups
;
1365 group
= entry
->data
;
1366 if (group
->id
== id
) {
1369 entry
= entry
->next
;
1374 static struct sipe_group
* sipe_group_find_by_name (struct sipe_account_data
*sip
, const gchar
* name
)
1376 struct sipe_group
*group
;
1378 if (!sip
|| !name
) {
1382 entry
= sip
->groups
;
1384 group
= entry
->data
;
1385 if (sipe_strequal(group
->name
, name
)) {
1388 entry
= entry
->next
;
1394 sipe_group_rename (struct sipe_account_data
*sip
, struct sipe_group
* group
, gchar
* name
)
1397 SIPE_DEBUG_INFO("Renaming group %s to %s", group
->name
, name
);
1398 body
= g_markup_printf_escaped(SIPE_SOAP_MOD_GROUP
, group
->id
, name
, sip
->contacts_delta
++);
1399 send_soap_request(sip
, body
);
1401 g_free(group
->name
);
1402 group
->name
= g_strdup(name
);
1406 * Only appends if no such value already stored.
1409 GSList
* slist_insert_unique_sorted(GSList
*list
, gpointer data
, GCompareFunc func
) {
1410 GSList
* res
= list
;
1411 if (!g_slist_find_custom(list
, data
, func
)) {
1412 res
= g_slist_insert_sorted(list
, data
, func
);
1418 sipe_group_compare(struct sipe_group
*group1
, struct sipe_group
*group2
) {
1419 return group1
->id
- group2
->id
;
1423 * Returns string like "2 4 7 8" - group ids buddy belong to.
1426 sipe_get_buddy_groups_string (struct sipe_buddy
*buddy
) {
1429 //creating array from GList, converting int to gchar*
1430 gchar
**ids_arr
= g_new(gchar
*, g_slist_length(buddy
->groups
) + 1);
1431 GSList
*entry
= buddy
->groups
;
1433 if (!ids_arr
) return NULL
;
1436 struct sipe_group
* group
= entry
->data
;
1437 ids_arr
[i
] = g_strdup_printf("%d", group
->id
);
1438 entry
= entry
->next
;
1442 res
= g_strjoinv(" ", ids_arr
);
1443 g_strfreev(ids_arr
);
1448 * Sends buddy update to server
1451 sipe_group_set_user (struct sipe_account_data
*sip
, const gchar
* who
)
1453 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, who
);
1454 PurpleBuddy
*purple_buddy
= purple_find_buddy (sip
->account
, who
);
1456 if (buddy
&& purple_buddy
) {
1457 const char *alias
= purple_buddy_get_alias(purple_buddy
);
1458 gchar
*groups
= sipe_get_buddy_groups_string(buddy
);
1461 SIPE_DEBUG_INFO("Saving buddy %s with alias %s and groups %s", who
, alias
, groups
);
1463 body
= g_markup_printf_escaped(SIPE_SOAP_SET_CONTACT
,
1464 alias
, groups
, "true", buddy
->name
, sip
->contacts_delta
++
1466 send_soap_request(sip
, body
);
1473 static gboolean
process_add_group_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
1475 if (msg
->response
== 200) {
1476 struct sipe_group
*group
;
1477 struct group_user_context
*ctx
= trans
->payload
->data
;
1479 const sipe_xml
*node
;
1481 struct sipe_buddy
*buddy
;
1483 xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
1488 node
= sipe_xml_child(xml
, "Body/addGroup/groupID");
1494 group_id
= sipe_xml_data(node
);
1500 group
= g_new0(struct sipe_group
, 1);
1501 group
->id
= (int)g_ascii_strtod(group_id
, NULL
);
1503 group
->name
= g_strdup(ctx
->group_name
);
1505 sipe_group_add(sip
, group
);
1507 buddy
= g_hash_table_lookup(sip
->buddies
, ctx
->user_name
);
1509 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
1512 sipe_group_set_user(sip
, ctx
->user_name
);
1520 static void sipe_group_context_destroy(gpointer data
)
1522 struct group_user_context
*ctx
= data
;
1523 g_free(ctx
->group_name
);
1524 g_free(ctx
->user_name
);
1528 static void sipe_group_create (struct sipe_account_data
*sip
, const gchar
*name
, const gchar
* who
)
1530 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
1531 struct group_user_context
*ctx
= g_new0(struct group_user_context
, 1);
1533 ctx
->group_name
= g_strdup(name
);
1534 ctx
->user_name
= g_strdup(who
);
1535 payload
->destroy
= sipe_group_context_destroy
;
1536 payload
->data
= ctx
;
1538 body
= g_markup_printf_escaped(SIPE_SOAP_ADD_GROUP
, name
, sip
->contacts_delta
++);
1539 send_soap_request_with_cb(sip
, NULL
, body
, process_add_group_response
, payload
);
1544 * Data structure for scheduled actions
1547 struct scheduled_action
{
1550 * Format is <Event>[<Data>...]
1551 * Example: <presence><sip:user@domain.com> or <registration>
1554 guint timeout_handler
;
1555 gboolean repetitive
;
1557 GDestroyNotify destroy
;
1558 struct sipe_account_data
*sip
;
1564 * Should return FALSE if repetitive action is not needed
1566 static gboolean
sipe_scheduled_exec(struct scheduled_action
*sched_action
)
1569 SIPE_DEBUG_INFO_NOFORMAT("sipe_scheduled_exec: executing");
1570 sched_action
->sip
->timeouts
= g_slist_remove(sched_action
->sip
->timeouts
, sched_action
);
1571 SIPE_DEBUG_INFO("sip->timeouts count:%d after removal", g_slist_length(sched_action
->sip
->timeouts
));
1572 (sched_action
->action
)(sched_action
->sip
, sched_action
->payload
);
1573 ret
= sched_action
->repetitive
;
1574 if (sched_action
->destroy
) {
1575 (*sched_action
->destroy
)(sched_action
->payload
);
1577 g_free(sched_action
->name
);
1578 g_free(sched_action
);
1583 * Kills action timer effectively cancelling
1586 * @param name of action
1588 static void sipe_cancel_scheduled_action(struct sipe_account_data
*sip
, const gchar
*name
)
1592 if (!sip
->timeouts
|| !name
) return;
1594 entry
= sip
->timeouts
;
1596 struct scheduled_action
*sched_action
= entry
->data
;
1597 if(sipe_strequal(sched_action
->name
, name
)) {
1598 GSList
*to_delete
= entry
;
1599 entry
= entry
->next
;
1600 sip
->timeouts
= g_slist_delete_link(sip
->timeouts
, to_delete
);
1601 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action
->name
);
1602 purple_timeout_remove(sched_action
->timeout_handler
);
1603 if (sched_action
->destroy
) {
1604 (*sched_action
->destroy
)(sched_action
->payload
);
1606 g_free(sched_action
->name
);
1607 g_free(sched_action
);
1609 entry
= entry
->next
;
1615 sipe_schedule_action0(const gchar
*name
,
1619 GDestroyNotify destroy
,
1620 struct sipe_account_data
*sip
,
1623 struct scheduled_action
*sched_action
;
1625 /* Make sure each action only exists once */
1626 sipe_cancel_scheduled_action(sip
, name
);
1628 SIPE_DEBUG_INFO("scheduling action %s timeout:%d(%s)", name
, timeout
, isSeconds
? "sec" : "msec");
1629 sched_action
= g_new0(struct scheduled_action
, 1);
1630 sched_action
->repetitive
= FALSE
;
1631 sched_action
->name
= g_strdup(name
);
1632 sched_action
->action
= action
;
1633 sched_action
->destroy
= destroy
;
1634 sched_action
->sip
= sip
;
1635 sched_action
->payload
= payload
;
1636 sched_action
->timeout_handler
= isSeconds
? purple_timeout_add_seconds(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
) :
1637 purple_timeout_add(timeout
, (GSourceFunc
) sipe_scheduled_exec
, sched_action
);
1638 sip
->timeouts
= g_slist_append(sip
->timeouts
, sched_action
);
1639 SIPE_DEBUG_INFO("sip->timeouts count:%d after addition", g_slist_length(sip
->timeouts
));
1643 sipe_schedule_action(const gchar
*name
,
1646 GDestroyNotify destroy
,
1647 struct sipe_account_data
*sip
,
1650 sipe_schedule_action0(name
, timeout
, TRUE
, action
, destroy
, sip
, payload
);
1654 * Same as sipe_schedule_action() but timeout is in milliseconds.
1657 sipe_schedule_action_msec(const gchar
*name
,
1660 GDestroyNotify destroy
,
1661 struct sipe_account_data
*sip
,
1664 sipe_schedule_action0(name
, timeout
, FALSE
, action
, destroy
, sip
, payload
);
1668 sipe_sched_calendar_status_update(struct sipe_account_data
*sip
,
1669 time_t calculate_from
);
1672 sipe_get_availability_by_status(const char* sipe_status_id
, char** activity_token
);
1675 sipe_get_status_by_availability(int avail
,
1679 sipe_set_purple_account_status_and_note(const PurpleAccount
*account
,
1680 const char *status_id
,
1681 const char *message
,
1682 time_t do_not_publish
[]);
1685 sipe_apply_calendar_status(struct sipe_account_data
*sip
,
1686 struct sipe_buddy
*sbuddy
,
1687 const char *status_id
)
1689 time_t cal_avail_since
;
1690 int cal_status
= sipe_cal_get_status(sbuddy
, time(NULL
), &cal_avail_since
);
1694 if (!sbuddy
) return;
1696 if (cal_status
< SIPE_CAL_NO_DATA
) {
1697 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_status : %d for %s", cal_status
, sbuddy
->name
);
1698 SIPE_DEBUG_INFO("sipe_apply_calendar_status: cal_avail_since : %s", asctime(localtime(&cal_avail_since
)));
1701 /* scheduled Cal update call */
1703 status_id
= sbuddy
->last_non_cal_status_id
;
1704 g_free(sbuddy
->activity
);
1705 sbuddy
->activity
= g_strdup(sbuddy
->last_non_cal_activity
);
1709 SIPE_DEBUG_INFO("sipe_apply_calendar_status: status_id is NULL for %s, exiting.",
1710 sbuddy
->name
? sbuddy
->name
: "" );
1714 /* adjust to calendar status */
1715 if (cal_status
!= SIPE_CAL_NO_DATA
) {
1716 SIPE_DEBUG_INFO("sipe_apply_calendar_status: user_avail_since: %s", asctime(localtime(&sbuddy
->user_avail_since
)));
1718 if (cal_status
== SIPE_CAL_BUSY
1719 && cal_avail_since
> sbuddy
->user_avail_since
1720 && 6500 >= sipe_get_availability_by_status(status_id
, NULL
))
1722 status_id
= SIPE_STATUS_ID_BUSY
;
1723 g_free(sbuddy
->activity
);
1724 sbuddy
->activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_MEETING
));
1726 avail
= sipe_get_availability_by_status(status_id
, NULL
);
1728 SIPE_DEBUG_INFO("sipe_apply_calendar_status: activity_since : %s", asctime(localtime(&sbuddy
->activity_since
)));
1729 if (cal_avail_since
> sbuddy
->activity_since
) {
1730 if (cal_status
== SIPE_CAL_OOF
1731 && avail
>= 15000) /* 12000 in 2007 */
1733 g_free(sbuddy
->activity
);
1734 sbuddy
->activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF
));
1739 /* then set status_id actually */
1740 SIPE_DEBUG_INFO("sipe_apply_calendar_status: to %s for %s", status_id
, sbuddy
->name
? sbuddy
->name
: "" );
1741 purple_prpl_got_user_status(sip
->account
, sbuddy
->name
, status_id
, NULL
);
1743 /* set our account state to the one in roaming (including calendar info) */
1744 self_uri
= sip_uri_self(sip
);
1745 if (sip
->initial_state_published
&& sipe_strcase_equal(sbuddy
->name
, self_uri
)) {
1746 if (sipe_strequal(status_id
, SIPE_STATUS_ID_OFFLINE
)) {
1747 status_id
= g_strdup(SIPE_STATUS_ID_INVISIBLE
); /* not not let offline status switch us off */
1750 SIPE_DEBUG_INFO("sipe_apply_calendar_status: switch to '%s' for the account", sip
->status
);
1751 sipe_set_purple_account_status_and_note(sip
->account
, status_id
, sip
->note
, sip
->do_not_publish
);
1757 sipe_got_user_status(struct sipe_account_data
*sip
,
1759 const char *status_id
)
1761 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
1763 if (!sbuddy
) return;
1765 /* Check if on 2005 system contact's calendar,
1766 * then set/preserve it.
1768 if (!sip
->ocs2007
) {
1769 sipe_apply_calendar_status(sip
, sbuddy
, status_id
);
1771 purple_prpl_got_user_status(sip
->account
, uri
, status_id
, NULL
);
1776 update_calendar_status_cb(SIPE_UNUSED_PARAMETER
char *name
,
1777 struct sipe_buddy
*sbuddy
,
1778 struct sipe_account_data
*sip
)
1780 sipe_apply_calendar_status(sip
, sbuddy
, NULL
);
1784 * Updates contact's status
1785 * based on their calendar information.
1787 * Applicability: 2005 systems
1790 update_calendar_status(struct sipe_account_data
*sip
)
1792 SIPE_DEBUG_INFO_NOFORMAT("update_calendar_status() started.");
1793 g_hash_table_foreach(sip
->buddies
, (GHFunc
)update_calendar_status_cb
, (gpointer
)sip
);
1795 /* repeat scheduling */
1796 sipe_sched_calendar_status_update(sip
, time(NULL
) + 3*60 /* 3 min */);
1800 * Schedules process of contacts' status update
1801 * based on their calendar information.
1802 * Should be scheduled to the beginning of every
1803 * 15 min interval, like:
1804 * 13:00, 13:15, 13:30, 13:45, etc.
1806 * Applicability: 2005 systems
1809 sipe_sched_calendar_status_update(struct sipe_account_data
*sip
,
1810 time_t calculate_from
)
1812 int interval
= 15*60;
1813 /** start of the beginning of closest 15 min interval. */
1814 time_t next_start
= ((time_t)((int)((int)calculate_from
)/interval
+ 1)*interval
);
1816 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: calculate_from time: %s",
1817 asctime(localtime(&calculate_from
)));
1818 SIPE_DEBUG_INFO("sipe_sched_calendar_status_update: next start time : %s",
1819 asctime(localtime(&next_start
)));
1821 sipe_schedule_action("<+2005-cal-status>",
1822 (int)(next_start
- time(NULL
)),
1823 (Action
)update_calendar_status
,
1830 * Schedules process of self status publish
1831 * based on own calendar information.
1832 * Should be scheduled to the beginning of every
1833 * 15 min interval, like:
1834 * 13:00, 13:15, 13:30, 13:45, etc.
1836 * Applicability: 2007+ systems
1839 sipe_sched_calendar_status_self_publish(struct sipe_account_data
*sip
,
1840 time_t calculate_from
)
1842 int interval
= 5*60;
1843 /** start of the beginning of closest 5 min interval. */
1844 time_t next_start
= ((time_t)((int)((int)calculate_from
)/interval
+ 1)*interval
);
1846 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: calculate_from time: %s",
1847 asctime(localtime(&calculate_from
)));
1848 SIPE_DEBUG_INFO("sipe_sched_calendar_status_self_publish: next start time : %s",
1849 asctime(localtime(&next_start
)));
1851 sipe_schedule_action("<+2007-cal-status>",
1852 (int)(next_start
- time(NULL
)),
1853 (Action
)publish_calendar_status_self
,
1859 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
);
1861 /** Should be g_free()'d
1864 sipe_get_subscription_key(const gchar
*event
,
1869 if (is_empty(event
)) return NULL
;
1871 if (event
&& sipe_strcase_equal(event
, "presence")) {
1872 /* Subscription is identified by ACTION_NAME_PRESENCE key */
1873 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, with
);
1875 /* @TODO drop participated buddies' just_added flag */
1877 /* Subscription is identified by <event> key */
1878 key
= g_strdup_printf("<%s>", event
);
1884 gboolean
process_subscribe_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
1885 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
1887 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
1888 const gchar
*event
= sipmsg_find_header(msg
, "Event");
1891 /* The case with 2005 Public IM Connectivity (PIC) - no Event header */
1893 struct sipmsg
*request_msg
= trans
->msg
;
1894 event
= sipmsg_find_header(request_msg
, "Event");
1897 key
= sipe_get_subscription_key(event
, with
);
1899 /* 200 OK; 481 Call Leg Does Not Exist */
1900 if (key
&& (msg
->response
== 200 || msg
->response
== 481)) {
1901 if (g_hash_table_lookup(sip
->subscriptions
, key
)) {
1902 g_hash_table_remove(sip
->subscriptions
, key
);
1903 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key
);
1907 /* create/store subscription dialog if not yet */
1908 if (msg
->response
== 200) {
1909 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
1910 gchar
*cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
1913 struct sip_subscription
*subscription
= g_new0(struct sip_subscription
, 1);
1914 g_hash_table_insert(sip
->subscriptions
, g_strdup(key
), subscription
);
1916 subscription
->dialog
.callid
= g_strdup(callid
);
1917 subscription
->dialog
.cseq
= atoi(cseq
);
1918 subscription
->dialog
.with
= g_strdup(with
);
1919 subscription
->event
= g_strdup(event
);
1920 sipe_dialog_parse(&subscription
->dialog
, msg
, TRUE
);
1922 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog added for: %s", key
);
1931 if (sipmsg_find_header(msg
, "ms-piggyback-cseq"))
1933 process_incoming_notify(sip
, msg
, FALSE
, FALSE
);
1938 static void sipe_subscribe_resource_uri(const char *name
,
1939 SIPE_UNUSED_PARAMETER gpointer value
,
1940 gchar
**resources_uri
)
1942 gchar
*tmp
= *resources_uri
;
1943 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, name
);
1947 static void sipe_subscribe_resource_uri_with_context(const char *name
, gpointer value
, gchar
**resources_uri
)
1949 struct sipe_buddy
*sbuddy
= (struct sipe_buddy
*)value
;
1950 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
1951 gchar
*tmp
= *resources_uri
;
1953 if (sbuddy
) sbuddy
->just_added
= FALSE
; /* should be enought to include context one time */
1955 *resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"%s\n", tmp
, name
, context
);
1960 * Support for Batch Category SUBSCRIBE [MS-PRES] - msrtc-event-categories+xml OCS 2007
1961 * Support for Batch Category SUBSCRIBE [MS-SIP] - adrl+xml LCS 2005
1962 * The user sends an initial batched category SUBSCRIBE request against all contacts on his roaming list in only a request
1963 * A batch category SUBSCRIBE request MUST have the same To-URI and From-URI.
1964 * This header will be send only if adhoclist there is a "Supported: adhoclist" in REGISTER answer else will be send a Single Category SUBSCRIBE
1967 static void sipe_subscribe_presence_batched_to(struct sipe_account_data
*sip
, gchar
*resources_uri
, gchar
*to
)
1970 gchar
*contact
= get_contact(sip
);
1973 gchar
*require
= "";
1975 gchar
*autoextend
= "";
1976 gchar
*content_type
;
1977 struct sip_dialog
*dialog
;
1980 require
= ", categoryList";
1981 accept
= ", application/msrtc-event-categories+xml, application/xpidf+xml, application/pidf+xml";
1982 content_type
= "application/msrtc-adrl-categorylist+xml";
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\">\n"
1986 "<adhocList>\n%s</adhocList>\n"
1987 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
1988 "<category name=\"calendarData\"/>\n"
1989 "<category name=\"contactCard\"/>\n"
1990 "<category name=\"note\"/>\n"
1991 "<category name=\"state\"/>\n"
1994 "</batchSub>", sip
->username
, resources_uri
);
1996 autoextend
= "Supported: com.microsoft.autoextend\r\n";
1997 content_type
= "application/adrl+xml";
1998 content
= g_strdup_printf(
1999 "<adhoclist xmlns=\"urn:ietf:params:xml:ns:adrl\" uri=\"sip:%s\" name=\"sip:%s\">\n"
2000 "<create xmlns=\"\">\n%s</create>\n"
2001 "</adhoclist>\n", sip
->username
, sip
->username
, resources_uri
);
2003 g_free(resources_uri
);
2005 request
= g_strdup_printf(
2006 "Require: adhoclist%s\r\n"
2007 "Supported: eventlist\r\n"
2008 "Accept: application/rlmi+xml, multipart/related, text/xml+msrtc.pidf%s\r\n"
2009 "Supported: ms-piggyback-first-notify\r\n"
2010 "%sSupported: ms-benotify\r\n"
2011 "Proxy-Require: ms-benotify\r\n"
2012 "Event: presence\r\n"
2013 "Content-Type: %s\r\n"
2014 "Contact: %s\r\n", require
, accept
, autoextend
, content_type
, contact
);
2017 /* subscribe to buddy presence */
2018 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2019 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, to
);
2020 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
2021 SIPE_DEBUG_INFO("sipe_subscribe_presence_batched_to: subscription dialog for: %s is %s", key
, dialog
? "Not NULL" : "NULL");
2023 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, dialog
, process_subscribe_response
);
2031 static void sipe_subscribe_presence_batched(struct sipe_account_data
*sip
,
2032 SIPE_UNUSED_PARAMETER
void *unused
)
2034 gchar
*to
= sip_uri_self(sip
);
2035 gchar
*resources_uri
= g_strdup("");
2037 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri_with_context
, &resources_uri
);
2039 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_subscribe_resource_uri
, &resources_uri
);
2042 sipe_subscribe_presence_batched_to(sip
, resources_uri
, to
);
2045 struct presence_batched_routed
{
2050 static void sipe_subscribe_presence_batched_routed_free(void *payload
)
2052 struct presence_batched_routed
*data
= payload
;
2053 GSList
*buddies
= data
->buddies
;
2055 g_free(buddies
->data
);
2056 buddies
= buddies
->next
;
2058 g_slist_free(data
->buddies
);
2063 static void sipe_subscribe_presence_batched_routed(struct sipe_account_data
*sip
, void *payload
)
2065 struct presence_batched_routed
*data
= payload
;
2066 GSList
*buddies
= data
->buddies
;
2067 gchar
*resources_uri
= g_strdup("");
2069 gchar
*tmp
= resources_uri
;
2070 resources_uri
= g_strdup_printf("%s<resource uri=\"%s\"/>\n", tmp
, (char *) buddies
->data
);
2072 buddies
= buddies
->next
;
2074 sipe_subscribe_presence_batched_to(sip
, resources_uri
,
2075 g_strdup(data
->host
));
2079 * Single Category SUBSCRIBE [MS-PRES] ; To send when the server returns a 200 OK message with state="resubscribe" in response.
2080 * The user sends a single SUBSCRIBE request to the subscribed contact.
2081 * The To-URI and the URI listed in the resource list MUST be the same for a single category SUBSCRIBE request.
2085 static void sipe_subscribe_presence_single(struct sipe_account_data
*sip
, void *buddy_name
)
2089 gchar
*to
= sip_uri((char *)buddy_name
);
2090 gchar
*tmp
= get_contact(sip
);
2092 gchar
*content
= NULL
;
2093 gchar
*autoextend
= "";
2094 gchar
*content_type
= "";
2095 struct sip_dialog
*dialog
;
2096 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, to
);
2097 gchar
*context
= sbuddy
&& sbuddy
->just_added
? "><context/></resource>" : "/>";
2099 if (sbuddy
) sbuddy
->just_added
= FALSE
;
2102 content_type
= "Content-Type: application/msrtc-adrl-categorylist+xml\r\n";
2104 autoextend
= "Supported: com.microsoft.autoextend\r\n";
2107 request
= g_strdup_printf(
2108 "Accept: application/msrtc-event-categories+xml, text/xml+msrtc.pidf, application/xpidf+xml, application/pidf+xml, application/rlmi+xml, multipart/related\r\n"
2109 "Supported: ms-piggyback-first-notify\r\n"
2110 "%s%sSupported: ms-benotify\r\n"
2111 "Proxy-Require: ms-benotify\r\n"
2112 "Event: presence\r\n"
2113 "Contact: %s\r\n", autoextend
, content_type
, tmp
);
2116 content
= g_strdup_printf(
2117 "<batchSub xmlns=\"http://schemas.microsoft.com/2006/01/sip/batch-subscribe\" uri=\"sip:%s\" name=\"\">\n"
2118 "<action name=\"subscribe\" id=\"63792024\"><adhocList>\n"
2119 "<resource uri=\"%s\"%s\n"
2121 "<categoryList xmlns=\"http://schemas.microsoft.com/2006/09/sip/categorylist\">\n"
2122 "<category name=\"calendarData\"/>\n"
2123 "<category name=\"contactCard\"/>\n"
2124 "<category name=\"note\"/>\n"
2125 "<category name=\"state\"/>\n"
2128 "</batchSub>", sip
->username
, to
, context
);
2133 /* subscribe to buddy presence */
2134 /* Subscription is identified by ACTION_NAME_PRESENCE key */
2135 key
= g_strdup_printf(ACTION_NAME_PRESENCE
, to
);
2136 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
2137 SIPE_DEBUG_INFO("sipe_subscribe_presence_single: subscription dialog for: %s is %s", key
, dialog
? "Not NULL" : "NULL");
2139 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, request
, content
, dialog
, process_subscribe_response
);
2147 static void sipe_set_status(PurpleAccount
*account
, PurpleStatus
*status
)
2149 SIPE_DEBUG_INFO("sipe_set_status: status=%s", purple_status_get_id(status
));
2151 if (!purple_status_is_active(status
))
2155 struct sipe_account_data
*sip
= account
->gc
->proto_data
;
2160 time_t now
= time(NULL
);
2161 const char *status_id
= purple_status_get_id(status
);
2162 const char *note
= purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
);
2163 sipe_activity activity
= sipe_get_activity_by_token(status_id
);
2164 gboolean do_not_publish
= ((now
- sip
->do_not_publish
[activity
]) <= 2);
2166 /* when other point of presence clears note, but we are keeping
2167 * state if OOF note.
2169 if (do_not_publish
&& !note
&& sip
->ews
&& sip
->ews
->oof_note
) {
2170 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: enabling publication as OOF note keepers.");
2171 do_not_publish
= FALSE
;
2174 SIPE_DEBUG_INFO("sipe_set_status: was: sip->do_not_publish[%s]=%d [?] now(time)=%d",
2175 status_id
, (int)sip
->do_not_publish
[activity
], (int)now
);
2177 sip
->do_not_publish
[activity
] = 0;
2178 SIPE_DEBUG_INFO("sipe_set_status: set: sip->do_not_publish[%s]=%d [0]",
2179 status_id
, (int)sip
->do_not_publish
[activity
]);
2183 SIPE_DEBUG_INFO_NOFORMAT("sipe_set_status: publication was switched off, exiting.");
2187 g_free(sip
->status
);
2188 sip
->status
= g_strdup(status_id
);
2190 /* hack to escape apostrof before comparison */
2191 tmp
= note
? sipe_utils_str_replace(note
, "'", "'") : NULL
;
2193 /* this will preserve OOF flag as well */
2194 if (!sipe_strequal(tmp
, sip
->note
)) {
2195 sip
->is_oof_note
= FALSE
;
2197 sip
->note
= g_strdup(note
);
2198 sip
->note_since
= time(NULL
);
2202 /* schedule 2 sec to capture idle flag */
2203 action_name
= g_strdup_printf("<%s>", "+set-status");
2204 sipe_schedule_action(action_name
, SIPE_IDLE_SET_DELAY
, (Action
)send_presence_status
, NULL
, sip
, NULL
);
2205 g_free(action_name
);
2210 sipe_set_idle(PurpleConnection
* gc
,
2213 SIPE_DEBUG_INFO("sipe_set_idle: interval=%d", interval
);
2216 struct sipe_account_data
*sip
= gc
->proto_data
;
2219 sip
->idle_switch
= time(NULL
);
2220 SIPE_DEBUG_INFO("sipe_set_idle: sip->idle_switch : %s", asctime(localtime(&(sip
->idle_switch
))));
2226 sipe_alias_buddy(PurpleConnection
*gc
, const char *name
,
2227 SIPE_UNUSED_PARAMETER
const char *alias
)
2229 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2230 sipe_group_set_user(sip
, name
);
2234 sipe_group_buddy(PurpleConnection
*gc
,
2236 const char *old_group_name
,
2237 const char *new_group_name
)
2239 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2240 struct sipe_buddy
* buddy
= g_hash_table_lookup(sip
->buddies
, who
);
2241 struct sipe_group
* old_group
= NULL
;
2242 struct sipe_group
* new_group
;
2244 SIPE_DEBUG_INFO("sipe_group_buddy[CB]: who:%s old_group_name:%s new_group_name:%s",
2245 who
? who
: "", old_group_name
? old_group_name
: "", new_group_name
? new_group_name
: "");
2247 if(!buddy
) { // buddy not in roaming list
2251 if (old_group_name
) {
2252 old_group
= sipe_group_find_by_name(sip
, old_group_name
);
2254 new_group
= sipe_group_find_by_name(sip
, new_group_name
);
2257 buddy
->groups
= g_slist_remove(buddy
->groups
, old_group
);
2258 SIPE_DEBUG_INFO("buddy %s removed from old group %s", who
, old_group_name
);
2262 sipe_group_create(sip
, new_group_name
, who
);
2264 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, new_group
, (GCompareFunc
)sipe_group_compare
);
2265 sipe_group_set_user(sip
, who
);
2269 static void sipe_add_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
2271 SIPE_DEBUG_INFO("sipe_add_buddy[CB]: buddy:%s group:%s", buddy
? buddy
->name
: "", group
? group
->name
: "");
2273 /* libpurple can call us with undefined buddy or group */
2274 if (buddy
&& group
) {
2275 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2277 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2278 gchar
*buddy_name
= g_ascii_strdown(buddy
->name
, -1);
2279 purple_blist_rename_buddy(buddy
, buddy_name
);
2282 /* Prepend sip: if needed */
2283 if (!g_str_has_prefix(buddy
->name
, "sip:")) {
2284 gchar
*buf
= sip_uri_from_name(buddy
->name
);
2285 purple_blist_rename_buddy(buddy
, buf
);
2289 if (!g_hash_table_lookup(sip
->buddies
, buddy
->name
)) {
2290 struct sipe_buddy
*b
= g_new0(struct sipe_buddy
, 1);
2291 SIPE_DEBUG_INFO("sipe_add_buddy: adding %s", buddy
->name
);
2292 b
->name
= g_strdup(buddy
->name
);
2293 b
->just_added
= TRUE
;
2294 g_hash_table_insert(sip
->buddies
, b
->name
, b
);
2295 sipe_group_buddy(gc
, b
->name
, NULL
, group
->name
);
2296 /* @TODO should go to callback */
2297 sipe_subscribe_presence_single(sip
, b
->name
);
2299 SIPE_DEBUG_INFO("sipe_add_buddy: buddy %s already in internal list", buddy
->name
);
2304 static void sipe_free_buddy(struct sipe_buddy
*buddy
)
2308 * We are calling g_hash_table_foreach_steal(). That means that no
2309 * key/value deallocation functions are called. Therefore the glib
2310 * hash code does not touch the key (buddy->name) or value (buddy)
2311 * of the to-be-deleted hash node at all. It follows that we
2313 * - MUST free the memory for the key ourselves and
2314 * - ARE allowed to do it in this function
2316 * Conclusion: glib must be broken on the Windows platform if sipe
2317 * crashes with SIGTRAP when closing. You'll have to live
2318 * with the memory leak until this is fixed.
2320 g_free(buddy
->name
);
2322 g_free(buddy
->activity
);
2323 g_free(buddy
->meeting_subject
);
2324 g_free(buddy
->meeting_location
);
2325 g_free(buddy
->note
);
2327 g_free(buddy
->cal_start_time
);
2328 g_free(buddy
->cal_free_busy_base64
);
2329 g_free(buddy
->cal_free_busy
);
2330 g_free(buddy
->last_non_cal_activity
);
2332 sipe_cal_free_working_hours(buddy
->cal_working_hours
);
2334 g_free(buddy
->device_name
);
2335 g_slist_free(buddy
->groups
);
2340 * Unassociates buddy from group first.
2341 * Then see if no groups left, removes buddy completely.
2342 * Otherwise updates buddy groups on server.
2344 static void sipe_remove_buddy(PurpleConnection
*gc
, PurpleBuddy
*buddy
, PurpleGroup
*group
)
2346 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2347 struct sipe_buddy
*b
;
2348 struct sipe_group
*g
= NULL
;
2350 SIPE_DEBUG_INFO("sipe_remove_buddy[CB]: buddy:%s group:%s", buddy
? buddy
->name
: "", group
? group
->name
: "");
2353 b
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
2357 g
= sipe_group_find_by_name(sip
, group
->name
);
2361 b
->groups
= g_slist_remove(b
->groups
, g
);
2362 SIPE_DEBUG_INFO("buddy %s removed from group %s", buddy
->name
, g
->name
);
2365 if (g_slist_length(b
->groups
) < 1) {
2366 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy
->name
);
2367 sipe_cancel_scheduled_action(sip
, action_name
);
2368 g_free(action_name
);
2370 g_hash_table_remove(sip
->buddies
, buddy
->name
);
2373 gchar
* body
= g_strdup_printf(SIPE_SOAP_DEL_CONTACT
, b
->name
, sip
->contacts_delta
++);
2374 send_soap_request(sip
, body
);
2380 //updates groups on server
2381 sipe_group_set_user(sip
, b
->name
);
2387 sipe_rename_group(PurpleConnection
*gc
,
2388 const char *old_name
,
2390 SIPE_UNUSED_PARAMETER GList
*moved_buddies
)
2392 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2393 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, old_name
);
2395 sipe_group_rename(sip
, s_group
, group
->name
);
2397 SIPE_DEBUG_INFO("Cannot find group %s to rename", old_name
);
2402 sipe_remove_group(PurpleConnection
*gc
, PurpleGroup
*group
)
2404 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
2405 struct sipe_group
* s_group
= sipe_group_find_by_name(sip
, group
->name
);
2408 SIPE_DEBUG_INFO("Deleting group %s", group
->name
);
2409 body
= g_strdup_printf(SIPE_SOAP_DEL_GROUP
, s_group
->id
, sip
->contacts_delta
++);
2410 send_soap_request(sip
, body
);
2413 sip
->groups
= g_slist_remove(sip
->groups
, s_group
);
2414 g_free(s_group
->name
);
2417 SIPE_DEBUG_INFO("Cannot find group %s to delete", group
->name
);
2421 /** All statuses need message attribute to pass Note */
2422 static GList
*sipe_status_types(SIPE_UNUSED_PARAMETER PurpleAccount
*acc
)
2424 PurpleStatusType
*type
;
2425 GList
*types
= NULL
;
2427 /* Macros to reduce code repetition.
2428 Translators: noun */
2429 #define SIPE_ADD_STATUS(prim,id,name,user) type = purple_status_type_new_with_attrs( \
2431 TRUE, user, FALSE, \
2432 SIPE_STATUS_ATTR_ID_MESSAGE, _("Message"), purple_value_new(PURPLE_TYPE_STRING), \
2434 types = g_list_append(types, type);
2437 SIPE_ADD_STATUS(PURPLE_STATUS_AVAILABLE
,
2443 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2444 sipe_activity_map
[SIPE_ACTIVITY_BUSY
].status_id
,
2445 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSY
),
2448 /* Do Not Disturb */
2449 SIPE_ADD_STATUS(PURPLE_STATUS_UNAVAILABLE
,
2450 sipe_activity_map
[SIPE_ACTIVITY_DND
].status_id
,
2455 /* Goes first in the list as
2456 * purple picks the first status with the AWAY type
2459 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2465 SIPE_ADD_STATUS(PURPLE_STATUS_AWAY
,
2466 sipe_activity_map
[SIPE_ACTIVITY_BRB
].status_id
,
2467 SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BRB
),
2470 /* Appear Offline */
2471 SIPE_ADD_STATUS(PURPLE_STATUS_INVISIBLE
,
2477 type
= purple_status_type_new(PURPLE_STATUS_OFFLINE
,
2481 types
= g_list_append(types
, type
);
2487 * A callback for g_hash_table_foreach
2490 sipe_buddy_subscribe_cb(char *buddy_name
,
2491 SIPE_UNUSED_PARAMETER
struct sipe_buddy
*buddy
,
2492 struct sipe_account_data
*sip
)
2494 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, buddy_name
);
2495 /* g_hash_table_size() can never return 0, otherwise this function wouldn't be called :-) */
2496 guint time_range
= (g_hash_table_size(sip
->buddies
) * 1000) / 25; /* time interval for 25 requests per sec. In msec. */
2497 guint timeout
= ((guint
) rand()) / (RAND_MAX
/ time_range
) + 1; /* random period within the range but never 0! */
2499 sipe_schedule_action_msec(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(buddy_name
));
2500 g_free(action_name
);
2504 * Removes entries from purple buddy list
2505 * that does not correspond ones in the roaming contact list.
2507 static void sipe_cleanup_local_blist(struct sipe_account_data
*sip
) {
2508 GSList
*buddies
= purple_find_buddies(sip
->account
, NULL
);
2509 GSList
*entry
= buddies
;
2510 struct sipe_buddy
*buddy
;
2514 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: overall %d Purple buddies (including clones)", g_slist_length(buddies
));
2515 SIPE_DEBUG_INFO("sipe_cleanup_local_blist: %d sipe buddies (unique)", g_hash_table_size(sip
->buddies
));
2518 g
= purple_buddy_get_group(b
);
2519 buddy
= g_hash_table_lookup(sip
->buddies
, b
->name
);
2521 gboolean in_sipe_groups
= FALSE
;
2522 GSList
*entry2
= buddy
->groups
;
2524 struct sipe_group
*group
= entry2
->data
;
2525 if (sipe_strequal(group
->name
, g
->name
)) {
2526 in_sipe_groups
= TRUE
;
2529 entry2
= entry2
->next
;
2531 if(!in_sipe_groups
) {
2532 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as not having this group in roaming list", b
->name
, g
->name
);
2533 purple_blist_remove_buddy(b
);
2536 SIPE_DEBUG_INFO("*** REMOVING %s from Purple group: %s as this buddy not in roaming list", b
->name
, g
->name
);
2537 purple_blist_remove_buddy(b
);
2539 entry
= entry
->next
;
2541 g_slist_free(buddies
);
2545 sipe_find_access_level(struct sipe_account_data
*sip
,
2548 gboolean
*is_inherited
);
2551 sipe_refresh_blocked_status_cb(char *buddy_name
,
2552 SIPE_UNUSED_PARAMETER
struct sipe_buddy
*buddy
,
2553 struct sipe_account_data
*sip
)
2555 int container_id
= sipe_find_access_level(sip
, "user", buddy_name
, NULL
);
2556 gboolean blocked
= (container_id
== 32000);
2557 gboolean blocked_in_blist
= !purple_privacy_check(sip
->account
, buddy_name
);
2559 /* SIPE_DEBUG_INFO("sipe_refresh_blocked_status_cb: buddy_name=%s, blocked=%s, blocked_in_blist=%s",
2560 buddy_name, blocked ? "T" : "F", blocked_in_blist ? "T" : "F"); */
2562 if (blocked
!= blocked_in_blist
) {
2564 purple_privacy_permit_remove(sip
->account
, buddy_name
, TRUE
);
2565 purple_privacy_deny_add(sip
->account
, buddy_name
, TRUE
);
2567 purple_privacy_deny_remove(sip
->account
, buddy_name
, TRUE
);
2568 purple_privacy_permit_add(sip
->account
, buddy_name
, TRUE
);
2571 /* stupid workaround to make pidgin re-render screen to reflect our changes */
2573 PurpleBuddy
*pbuddy
= purple_find_buddy(sip
->account
, buddy_name
);
2574 const PurplePresence
*presence
= purple_buddy_get_presence(pbuddy
);
2575 const PurpleStatus
*pstatus
= purple_presence_get_active_status(presence
);
2577 SIPE_DEBUG_INFO_NOFORMAT("sipe_refresh_blocked_status_cb: forcefully refreshing screen.");
2578 sipe_got_user_status(sip
, buddy_name
, purple_status_get_id(pstatus
));
2585 sipe_refresh_blocked_status(struct sipe_account_data
*sip
)
2587 g_hash_table_foreach(sip
->buddies
, (GHFunc
) sipe_refresh_blocked_status_cb
, (gpointer
)sip
);
2590 static gboolean
sipe_process_roaming_contacts(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2592 int len
= msg
->bodylen
;
2594 const gchar
*tmp
= sipmsg_find_header(msg
, "Event");
2595 const sipe_xml
*item
;
2597 const gchar
*contacts_delta
;
2598 const sipe_xml
*group_node
;
2599 if (!g_str_has_prefix(tmp
, "vnd-microsoft-roaming-contacts")) {
2603 /* Convert the contact from XML to Purple Buddies */
2604 isc
= sipe_xml_parse(msg
->body
, len
);
2609 contacts_delta
= sipe_xml_attribute(isc
, "deltaNum");
2610 if (contacts_delta
) {
2611 sip
->contacts_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2614 if (sipe_strequal(sipe_xml_name(isc
), "contactList")) {
2617 for (group_node
= sipe_xml_child(isc
, "group"); group_node
; group_node
= sipe_xml_twin(group_node
)) {
2618 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2619 const char *name
= sipe_xml_attribute(group_node
, "name");
2621 if (g_str_has_prefix(name
, "~")) {
2622 name
= _("Other Contacts");
2624 group
->name
= g_strdup(name
);
2625 group
->id
= (int)g_ascii_strtod(sipe_xml_attribute(group_node
, "id"), NULL
);
2627 sipe_group_add(sip
, group
);
2630 // Make sure we have at least one group
2631 if (g_slist_length(sip
->groups
) == 0) {
2632 struct sipe_group
* group
= g_new0(struct sipe_group
, 1);
2633 PurpleGroup
*purple_group
;
2634 group
->name
= g_strdup(_("Other Contacts"));
2636 purple_group
= purple_group_new(group
->name
);
2637 purple_blist_add_group(purple_group
, NULL
);
2638 sip
->groups
= g_slist_append(sip
->groups
, group
);
2641 /* Parse contacts */
2642 for (item
= sipe_xml_child(isc
, "contact"); item
; item
= sipe_xml_twin(item
)) {
2643 const gchar
*uri
= sipe_xml_attribute(item
, "uri");
2644 const gchar
*name
= sipe_xml_attribute(item
, "name");
2646 struct sipe_buddy
*buddy
= NULL
;
2648 gchar
**item_groups
;
2651 /* Buddy name must be lower case as we use purple_normalize_nocase() to compare */
2652 tmp
= sip_uri_from_name(uri
);
2653 buddy_name
= g_ascii_strdown(tmp
, -1);
2656 /* assign to group Other Contacts if nothing else received */
2657 tmp
= g_strdup(sipe_xml_attribute(item
, "groups"));
2659 struct sipe_group
*group
= sipe_group_find_by_name(sip
, _("Other Contacts"));
2661 tmp
= group
? g_strdup_printf("%d", group
->id
) : g_strdup("1");
2663 item_groups
= g_strsplit(tmp
, " ", 0);
2666 while (item_groups
[i
]) {
2667 struct sipe_group
*group
= sipe_group_find_by_id(sip
, g_ascii_strtod(item_groups
[i
], NULL
));
2669 // If couldn't find the right group for this contact, just put them in the first group we have
2670 if (group
== NULL
&& g_slist_length(sip
->groups
) > 0) {
2671 group
= sip
->groups
->data
;
2674 if (group
!= NULL
) {
2675 PurpleBuddy
*b
= purple_find_buddy_in_group(sip
->account
, buddy_name
, group
->purple_group
);
2677 b
= purple_buddy_new(sip
->account
, buddy_name
, uri
);
2678 purple_blist_add_buddy(b
, NULL
, group
->purple_group
, NULL
);
2680 SIPE_DEBUG_INFO("Created new buddy %s with alias %s", buddy_name
, uri
);
2683 if (sipe_strcase_equal(uri
, purple_buddy_get_alias(b
))) {
2684 if (name
!= NULL
&& strlen(name
) != 0) {
2685 purple_blist_alias_buddy(b
, name
);
2687 SIPE_DEBUG_INFO("Replaced buddy %s alias with %s", buddy_name
, name
);
2692 buddy
= g_new0(struct sipe_buddy
, 1);
2693 buddy
->name
= g_strdup(b
->name
);
2694 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2697 buddy
->groups
= slist_insert_unique_sorted(buddy
->groups
, group
, (GCompareFunc
)sipe_group_compare
);
2699 SIPE_DEBUG_INFO("Added buddy %s to group %s", b
->name
, group
->name
);
2701 SIPE_DEBUG_INFO("No group found for contact %s! Unable to add to buddy list",
2706 } // while, contact groups
2707 g_strfreev(item_groups
);
2712 sipe_cleanup_local_blist(sip
);
2714 /* Add self-contact if not there yet. 2005 systems. */
2715 /* This will resemble subscription to roaming_self in 2007 systems */
2716 if (!sip
->ocs2007
) {
2717 gchar
*self_uri
= sip_uri_self(sip
);
2718 struct sipe_buddy
*buddy
= g_hash_table_lookup(sip
->buddies
, self_uri
);
2721 buddy
= g_new0(struct sipe_buddy
, 1);
2722 buddy
->name
= g_strdup(self_uri
);
2723 g_hash_table_insert(sip
->buddies
, buddy
->name
, buddy
);
2730 /* subscribe to buddies */
2731 if (!sip
->subscribed_buddies
) { //do it once, then count Expire field to schedule resubscribe.
2732 if (sip
->batched_support
) {
2733 sipe_subscribe_presence_batched(sip
, NULL
);
2735 g_hash_table_foreach(sip
->buddies
, (GHFunc
)sipe_buddy_subscribe_cb
, (gpointer
)sip
);
2737 sip
->subscribed_buddies
= TRUE
;
2739 /* for 2005 systems schedule contacts' status update
2740 * based on their calendar information
2742 if (!sip
->ocs2007
) {
2743 sipe_sched_calendar_status_update(sip
, time(NULL
));
2750 * Subscribe roaming contacts
2752 static void sipe_subscribe_roaming_contacts(struct sipe_account_data
*sip
)
2754 gchar
*to
= sip_uri_self(sip
);
2755 gchar
*tmp
= get_contact(sip
);
2756 gchar
*hdr
= g_strdup_printf(
2757 "Event: vnd-microsoft-roaming-contacts\r\n"
2758 "Accept: application/vnd-microsoft-roaming-contacts+xml\r\n"
2759 "Supported: com.microsoft.autoextend\r\n"
2760 "Supported: ms-benotify\r\n"
2761 "Proxy-Require: ms-benotify\r\n"
2762 "Supported: ms-piggyback-first-notify\r\n"
2763 "Contact: %s\r\n", tmp
);
2766 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
2771 static void sipe_subscribe_presence_wpending(struct sipe_account_data
*sip
,
2772 SIPE_UNUSED_PARAMETER
void *unused
)
2775 struct sip_dialog
*dialog
;
2776 gchar
*to
= sip_uri_self(sip
);
2777 gchar
*tmp
= get_contact(sip
);
2778 gchar
*hdr
= g_strdup_printf(
2779 "Event: presence.wpending\r\n"
2780 "Accept: text/xml+msrtc.wpending\r\n"
2781 "Supported: com.microsoft.autoextend\r\n"
2782 "Supported: ms-benotify\r\n"
2783 "Proxy-Require: ms-benotify\r\n"
2784 "Supported: ms-piggyback-first-notify\r\n"
2785 "Contact: %s\r\n", tmp
);
2788 /* Subscription is identified by <event> key */
2789 key
= g_strdup_printf("<%s>", "presence.wpending");
2790 dialog
= (struct sip_dialog
*)g_hash_table_lookup(sip
->subscriptions
, key
);
2791 SIPE_DEBUG_INFO("sipe_subscribe_presence_wpending: subscription dialog for: %s is %s", key
, dialog
? "Not NULL" : "NULL");
2793 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", dialog
, process_subscribe_response
);
2801 * Fires on deregistration event initiated by server.
2802 * [MS-SIPREGE] SIP extension.
2807 // Content-Type: text/registration-event
2808 // subscription-state: terminated;expires=0
2809 // ms-diagnostics-public: 4141;reason="User disabled"
2811 // deregistered;event=rejected
2813 static void sipe_process_registration_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2815 const gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
2816 gchar
*event
= NULL
;
2817 gchar
*reason
= NULL
;
2818 const gchar
*diagnostics
= sipmsg_find_header(msg
, "ms-diagnostics");
2821 diagnostics
= diagnostics
? diagnostics
: sipmsg_find_header(msg
, "ms-diagnostics-public");
2822 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: deregistration received.");
2824 if (!g_ascii_strncasecmp(contenttype
, "text/registration-event", 23)) {
2825 event
= sipmsg_find_part_of_header(msg
->body
, "event=", NULL
, NULL
);
2826 //@TODO have proper parameter extraction _by_name_ func, case insesitive.
2827 event
= event
? event
: sipmsg_find_part_of_header(msg
->body
, "event=", ";", NULL
);
2829 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_registration_notify: unknown content type, exiting.");
2833 if (diagnostics
!= NULL
) {
2834 reason
= sipmsg_find_part_of_header(diagnostics
, "reason=\"", "\"", NULL
);
2835 } else { // for LCS2005
2837 if (event
&& sipe_strcase_equal(event
, "unregistered")) {
2838 error_id
= 4140; // [MS-SIPREGE]
2839 //reason = g_strdup(_("User logged out")); // [MS-OCER]
2840 reason
= g_strdup(_("you are already signed in at another location"));
2841 } else if (event
&& sipe_strcase_equal(event
, "rejected")) {
2843 reason
= g_strdup(_("user disabled")); // [MS-OCER]
2844 } else if (event
&& sipe_strcase_equal(event
, "deactivated")) {
2846 reason
= g_strdup(_("user moved")); // [MS-OCER]
2850 warning
= g_strdup_printf(_("You have been rejected by the server: %s"), reason
? reason
: _("no reason given"));
2853 sip
->gc
->wants_to_die
= TRUE
;
2854 purple_connection_error(sip
->gc
, warning
);
2859 static void sipe_process_provisioning_v2(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2861 sipe_xml
*xn_provision_group_list
;
2862 const sipe_xml
*node
;
2864 xn_provision_group_list
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
2866 /* provisionGroup */
2867 for (node
= sipe_xml_child(xn_provision_group_list
, "provisionGroup"); node
; node
= sipe_xml_twin(node
)) {
2868 if (sipe_strequal("ServerConfiguration", sipe_xml_attribute(node
, "name"))) {
2869 g_free(sip
->focus_factory_uri
);
2870 sip
->focus_factory_uri
= sipe_xml_data(sipe_xml_child(node
, "focusFactoryUri"));
2871 SIPE_DEBUG_INFO("sipe_process_provisioning_v2: sip->focus_factory_uri=%s",
2872 sip
->focus_factory_uri
? sip
->focus_factory_uri
: "");
2876 sipe_xml_free(xn_provision_group_list
);
2879 /** for 2005 system */
2881 sipe_process_provisioning(struct sipe_account_data
*sip
,
2884 sipe_xml
*xn_provision
;
2885 const sipe_xml
*node
;
2887 xn_provision
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
2888 if ((node
= sipe_xml_child(xn_provision
, "user"))) {
2889 SIPE_DEBUG_INFO("sipe_process_provisioning: uri=%s", sipe_xml_attribute(node
, "uri"));
2890 if ((node
= sipe_xml_child(node
, "line"))) {
2891 const gchar
*line_uri
= sipe_xml_attribute(node
, "uri");
2892 const gchar
*server
= sipe_xml_attribute(node
, "server");
2893 SIPE_DEBUG_INFO("sipe_process_provisioning: line_uri=%s server=%s", line_uri
, server
);
2894 sip_csta_open(sip
, line_uri
, server
);
2897 sipe_xml_free(xn_provision
);
2900 static void sipe_process_roaming_acl(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
2902 const gchar
*contacts_delta
;
2905 xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
2911 contacts_delta
= sipe_xml_attribute(xml
, "deltaNum");
2914 sip
->acl_delta
= (int)g_ascii_strtod(contacts_delta
, NULL
);
2921 free_container_member(struct sipe_container_member
*member
)
2923 if (!member
) return;
2925 g_free(member
->type
);
2926 g_free(member
->value
);
2931 free_container(struct sipe_container
*container
)
2935 if (!container
) return;
2937 entry
= container
->members
;
2939 void *data
= entry
->data
;
2940 entry
= g_slist_remove(entry
, data
);
2941 free_container_member((struct sipe_container_member
*)data
);
2947 sipe_send_container_members_prepare(const guint container_id
,
2948 const guint container_version
,
2949 const gchar
*action
,
2952 char **container_xmls
)
2954 gchar
*value_str
= value
? g_strdup_printf(" value=\"%s\"", value
) : g_strdup("");
2957 if (!container_xmls
) return;
2959 body
= g_strdup_printf(
2960 "<container id=\"%d\" version=\"%d\"><member action=\"%s\" type=\"%s\"%s/></container>",
2968 if ((*container_xmls
) == NULL
) {
2969 *container_xmls
= body
;
2971 char *tmp
= *container_xmls
;
2973 *container_xmls
= g_strconcat(*container_xmls
, body
, NULL
);
2980 sipe_send_set_container_members(struct sipe_account_data
*sip
,
2981 char *container_xmls
)
2988 if (!container_xmls
) return;
2990 self
= sip_uri_self(sip
);
2991 body
= g_strdup_printf(
2992 "<setContainerMembers xmlns=\"http://schemas.microsoft.com/2006/09/sip/container-management\">"
2994 "</setContainerMembers>",
2997 contact
= get_contact(sip
);
2998 hdr
= g_strdup_printf("Contact: %s\r\n"
2999 "Content-Type: application/msrtc-setcontainermembers+xml\r\n", contact
);
3002 send_sip_request(sip
->gc
, "SERVICE", self
, self
, hdr
, body
, NULL
, NULL
);
3010 * Finds locally stored MS-PRES container member
3012 static struct sipe_container_member
*
3013 sipe_find_container_member(struct sipe_container
*container
,
3017 struct sipe_container_member
*member
;
3020 if (container
== NULL
|| type
== NULL
) {
3024 entry
= container
->members
;
3026 member
= entry
->data
;
3027 if (sipe_strcase_equal(member
->type
, type
) &&
3028 sipe_strcase_equal(member
->value
, value
))
3032 entry
= entry
->next
;
3038 * Finds locally stored MS-PRES container by id
3040 static struct sipe_container
*
3041 sipe_find_container(struct sipe_account_data
*sip
,
3044 struct sipe_container
*container
;
3051 entry
= sip
->containers
;
3053 container
= entry
->data
;
3054 if (id
== container
->id
) {
3057 entry
= entry
->next
;
3063 sipe_get_access_domains(struct sipe_account_data
*sip
)
3065 struct sipe_container
*container
;
3066 struct sipe_container_member
*member
;
3071 if (!sip
) return NULL
;
3073 entry
= sip
->containers
;
3075 container
= entry
->data
;
3077 entry2
= container
->members
;
3079 member
= entry2
->data
;
3080 if (sipe_strcase_equal(member
->type
, "domain"))
3082 res
= slist_insert_unique_sorted(res
, g_strdup(member
->value
), (GCompareFunc
)g_ascii_strcasecmp
);
3084 entry2
= entry2
->next
;
3086 entry
= entry
->next
;
3092 * Returns pointer to domain part in provided Email URL
3094 * @param email an email URL. Example: first.last@hq.company.com
3095 * @return pointer to domain part of email URL. Coresponding example: hq.company.com
3097 * Doesn't allocate memory
3100 sipe_get_domain(const char *email
)
3104 if (!email
) return NULL
;
3106 tmp
= strstr(email
, "@");
3108 if (tmp
&& ((tmp
+1) < (email
+ strlen(email
)))) {
3116 /* @TODO: replace with binary search for faster access? */
3117 /** source: http://support.microsoft.com/kb/897567 */
3118 static const char * const public_domains
[] = {
3119 "aol.com", "icq.com", "love.com", "mac.com", "br.live.com",
3120 "hotmail.co.il", "hotmail.co.jp", "hotmail.co.th", "hotmail.co.uk",
3121 "hotmail.com", "hotmail.com.ar", "hotmail.com.tr", "hotmail.es",
3122 "hotmail.de", "hotmail.fr", "hotmail.it", "live.at", "live.be",
3123 "live.ca", "live.cl", "live.cn", "live.co.in", "live.co.kr",
3124 "live.co.uk", "live.co.za", "live.com", "live.com.ar", "live.com.au",
3125 "live.com.co", "live.com.mx", "live.com.my", "live.com.pe",
3126 "live.com.ph", "live.com.pk", "live.com.pt", "live.com.sg",
3127 "live.com.ve", "live.de", "live.dk", "live.fr", "live.hk", "live.ie",
3128 "live.in", "live.it", "live.jp", "live.nl", "live.no", "live.ph",
3129 "live.ru", "live.se", "livemail.com.br", "livemail.tw",
3130 "messengeruser.com", "msn.com", "passport.com", "sympatico.ca",
3131 "tw.live.com", "webtv.net", "windowslive.com", "windowslive.es",
3136 sipe_is_public_domain(const char *domain
)
3139 while (public_domains
[i
]) {
3140 if (sipe_strcase_equal(public_domains
[i
], domain
)) {
3157 sipe_get_access_level_name(int container_id
)
3159 switch(container_id
) {
3160 case 32000: return _("Blocked");
3161 case 400: return _("Personal");
3162 case 300: return _("Team");
3163 case 200: return _("Company");
3164 case 100: return _("Public");
3166 return _("Unknown");
3169 static const guint containers
[] = {32000, 400, 300, 200, 100};
3170 #define CONTAINERS_LEN (sizeof(containers) / sizeof(guint))
3174 sipe_find_member_access_level(struct sipe_account_data
*sip
,
3179 const gchar
*value_mod
= value
;
3181 if (!type
) return -1;
3183 if (sipe_strequal("user", type
)) {
3184 value_mod
= sipe_get_no_sip_uri(value
);
3187 for (i
= 0; i
< CONTAINERS_LEN
; i
++) {
3188 struct sipe_container_member
*member
;
3189 struct sipe_container
*container
= sipe_find_container(sip
, containers
[i
]);
3190 if (!container
) continue;
3192 member
= sipe_find_container_member(container
, type
, value_mod
);
3193 if (member
) return containers
[i
];
3199 /** Member type: user, domain, sameEnterprise, federated, publicCloud; everyone */
3201 sipe_find_access_level(struct sipe_account_data
*sip
,
3204 gboolean
*is_inherited
)
3206 int container_id
= -1;
3208 if (sipe_strequal("user", type
)) {
3210 const char *no_sip_uri
= sipe_get_no_sip_uri(value
);
3212 container_id
= sipe_find_member_access_level(sip
, "user", no_sip_uri
);
3213 if (container_id
>= 0) {
3214 if (is_inherited
) *is_inherited
= FALSE
;
3215 return container_id
;
3218 domain
= sipe_get_domain(no_sip_uri
);
3219 container_id
= sipe_find_member_access_level(sip
, "domain", domain
);
3220 if (container_id
>= 0) {
3221 if (is_inherited
) *is_inherited
= TRUE
;
3222 return container_id
;
3225 container_id
= sipe_find_member_access_level(sip
, "sameEnterprise", NULL
);
3226 if ((container_id
>= 0) && sipe_strcase_equal(sip
->sipdomain
, domain
)) {
3227 if (is_inherited
) *is_inherited
= TRUE
;
3228 return container_id
;
3231 container_id
= sipe_find_member_access_level(sip
, "publicCloud", NULL
);
3232 if ((container_id
>= 0) && sipe_is_public_domain(domain
)) {
3233 if (is_inherited
) *is_inherited
= TRUE
;
3234 return container_id
;
3237 container_id
= sipe_find_member_access_level(sip
, "everyone", NULL
);
3238 if ((container_id
>= 0)) {
3239 if (is_inherited
) *is_inherited
= TRUE
;
3240 return container_id
;
3243 container_id
= sipe_find_member_access_level(sip
, type
, value
);
3244 if (is_inherited
) *is_inherited
= FALSE
;
3247 return container_id
;
3251 * @param container_id a new access level. If -1 then current access level
3252 * is just removed (I.e. the member is removed from all containers).
3253 * @param type a type of member. E.g. "user", "sameEnterprise", etc.
3254 * @param value a value for member. E.g. SIP URI for "user" member type.
3257 sipe_change_access_level(struct sipe_account_data
*sip
,
3258 const int container_id
,
3263 int current_container_id
= -1;
3264 char *container_xmls
= NULL
;
3266 /* for each container: find/delete */
3267 for (i
= 0; i
< CONTAINERS_LEN
; i
++) {
3268 struct sipe_container_member
*member
;
3269 struct sipe_container
*container
= sipe_find_container(sip
, containers
[i
]);
3271 if (!container
) continue;
3273 member
= sipe_find_container_member(container
, type
, value
);
3275 current_container_id
= containers
[i
];
3276 /* delete/publish current access level */
3277 if (container_id
< 0 || container_id
!= current_container_id
) {
3278 sipe_send_container_members_prepare(current_container_id
, container
->version
, "remove", type
, value
, &container_xmls
);
3279 /* remove member from our cache, to be able to recalculate AL below */
3280 container
->members
= g_slist_remove(container
->members
, member
);
3281 current_container_id
= -1;
3286 /* recalculate AL below */
3287 current_container_id
= sipe_find_access_level(sip
, type
, value
, NULL
);
3289 /* assign/publish new access level */
3290 if (container_id
!= current_container_id
&& container_id
>= 0) {
3291 struct sipe_container
*container
= sipe_find_container(sip
, container_id
);
3292 guint version
= container
? container
->version
: 0;
3294 sipe_send_container_members_prepare(container_id
, version
, "add", type
, value
, &container_xmls
);
3297 if (container_xmls
) {
3298 sipe_send_set_container_members(sip
, container_xmls
);
3300 g_free(container_xmls
);
3304 free_publication(struct sipe_publication
*publication
)
3306 g_free(publication
->category
);
3307 g_free(publication
->cal_event_hash
);
3308 g_free(publication
->note
);
3310 g_free(publication
->working_hours_xml_str
);
3311 g_free(publication
->fb_start_str
);
3312 g_free(publication
->free_busy_base64
);
3314 g_free(publication
);
3317 /* key is <category><instance><container> */
3319 sipe_is_our_publication(struct sipe_account_data
*sip
,
3324 /* filling keys for our publications if not yet cached */
3325 if (!sip
->our_publication_keys
) {
3326 guint device_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_DEVICE
);
3327 guint machine_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_MACHINE
);
3328 guint user_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_USER
);
3329 guint calendar_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR
);
3330 guint cal_oof_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR_OOF
);
3331 guint cal_data_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_CALENDAR_DATA
);
3332 guint note_oof_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_NOTE_OOF
);
3334 SIPE_DEBUG_INFO_NOFORMAT("* Our Publication Instances *");
3335 SIPE_DEBUG_INFO("\tDevice : %u\t0x%08X", device_instance
, device_instance
);
3336 SIPE_DEBUG_INFO("\tMachine State : %u\t0x%08X", machine_instance
, machine_instance
);
3337 SIPE_DEBUG_INFO("\tUser Stare : %u\t0x%08X", user_instance
, user_instance
);
3338 SIPE_DEBUG_INFO("\tCalendar State : %u\t0x%08X", calendar_instance
, calendar_instance
);
3339 SIPE_DEBUG_INFO("\tCalendar OOF State : %u\t0x%08X", cal_oof_instance
, cal_oof_instance
);
3340 SIPE_DEBUG_INFO("\tCalendar FreeBusy : %u\t0x%08X", cal_data_instance
, cal_data_instance
);
3341 SIPE_DEBUG_INFO("\tOOF Note : %u\t0x%08X", note_oof_instance
, note_oof_instance
);
3342 SIPE_DEBUG_INFO("\tNote : %u", 0);
3343 SIPE_DEBUG_INFO("\tCalendar WorkingHours: %u", 0);
3346 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3347 g_strdup_printf("<%s><%u><%u>", "device", device_instance
, 2));
3349 /* state:machineState */
3350 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3351 g_strdup_printf("<%s><%u><%u>", "state", machine_instance
, 2));
3352 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3353 g_strdup_printf("<%s><%u><%u>", "state", machine_instance
, 3));
3355 /* state:userState */
3356 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3357 g_strdup_printf("<%s><%u><%u>", "state", user_instance
, 2));
3358 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3359 g_strdup_printf("<%s><%u><%u>", "state", user_instance
, 3));
3361 /* state:calendarState */
3362 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3363 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance
, 2));
3364 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3365 g_strdup_printf("<%s><%u><%u>", "state", calendar_instance
, 3));
3367 /* state:calendarState OOF */
3368 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3369 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance
, 2));
3370 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3371 g_strdup_printf("<%s><%u><%u>", "state", cal_oof_instance
, 3));
3374 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3375 g_strdup_printf("<%s><%u><%u>", "note", 0, 200));
3376 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3377 g_strdup_printf("<%s><%u><%u>", "note", 0, 300));
3378 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3379 g_strdup_printf("<%s><%u><%u>", "note", 0, 400));
3382 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3383 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 200));
3384 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3385 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 300));
3386 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3387 g_strdup_printf("<%s><%u><%u>", "note", note_oof_instance
, 400));
3389 /* calendarData:WorkingHours */
3390 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3391 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1));
3392 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3393 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100));
3394 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3395 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200));
3396 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3397 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300));
3398 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3399 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400));
3400 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3401 g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000));
3403 /* calendarData:FreeBusy */
3404 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3405 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 1));
3406 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3407 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 100));
3408 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3409 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 200));
3410 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3411 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 300));
3412 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3413 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 400));
3414 sip
->our_publication_keys
= g_slist_append(sip
->our_publication_keys
,
3415 g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 32000));
3417 //SIPE_DEBUG_INFO("sipe_is_our_publication: sip->our_publication_keys length=%d",
3418 // sip->our_publication_keys ? (int) g_slist_length(sip->our_publication_keys) : -1);
3421 //SIPE_DEBUG_INFO("sipe_is_our_publication: key=%s", key);
3423 entry
= sip
->our_publication_keys
;
3425 //SIPE_DEBUG_INFO(" sipe_is_our_publication: entry->data=%s", entry->data);
3426 if (sipe_strequal(entry
->data
, key
)) {
3429 entry
= entry
->next
;
3434 /** Property names to store in blist.xml */
3435 #define ALIAS_PROP "alias"
3436 #define EMAIL_PROP "email"
3437 #define PHONE_PROP "phone"
3438 #define PHONE_DISPLAY_PROP "phone-display"
3439 #define PHONE_MOBILE_PROP "phone-mobile"
3440 #define PHONE_MOBILE_DISPLAY_PROP "phone-mobile-display"
3441 #define PHONE_HOME_PROP "phone-home"
3442 #define PHONE_HOME_DISPLAY_PROP "phone-home-display"
3443 #define PHONE_OTHER_PROP "phone-other"
3444 #define PHONE_OTHER_DISPLAY_PROP "phone-other-display"
3445 #define PHONE_CUSTOM1_PROP "phone-custom1"
3446 #define PHONE_CUSTOM1_DISPLAY_PROP "phone-custom1-display"
3447 #define SITE_PROP "site"
3448 #define COMPANY_PROP "company"
3449 #define DEPARTMENT_PROP "department"
3450 #define TITLE_PROP "title"
3451 #define OFFICE_PROP "office"
3452 /** implies work address */
3453 #define ADDRESS_STREET_PROP "address-street"
3454 #define ADDRESS_CITY_PROP "address-city"
3455 #define ADDRESS_STATE_PROP "address-state"
3456 #define ADDRESS_ZIPCODE_PROP "address-zipcode"
3457 #define ADDRESS_COUNTRYCODE_PROP "address-country-code"
3460 * Tries to figure out user first and last name
3461 * based on Display Name and email properties.
3463 * Allocates memory - must be g_free()'d
3465 * Examples to parse:
3467 * First Last - Company Name
3470 * Last, First (C)(STP) (Company)
3471 * first.last@company.com (preprocessed as "first last")
3472 * first.last.company.com@reuters.net (preprocessed as "first last company com")
3474 * Unusable examples:
3475 * user@company.com (preprocessed as "user")
3476 * first.m.last@company.com (preprocessed as "first m last")
3477 * user.company.com@reuters.net (preprocessed as "user company com")
3480 sipe_get_first_last_names(struct sipe_account_data
*sip
,
3485 PurpleBuddy
*p_buddy
;
3488 const char *first
, *last
;
3491 gboolean has_comma
= FALSE
;
3493 if (!sip
|| !uri
) return;
3495 p_buddy
= purple_find_buddy(sip
->account
, uri
);
3497 if (!p_buddy
) return;
3499 display_name
= g_strdup(purple_buddy_get_alias(p_buddy
));
3500 email
= purple_blist_node_get_string(&p_buddy
->node
, EMAIL_PROP
);
3502 if (!display_name
&& !email
) return;
3504 /* if no display name, make "first last anything_else" out of email */
3505 if (email
&& !display_name
) {
3506 display_name
= g_strndup(email
, strstr(email
, "@") - email
);
3507 display_name
= sipe_utils_str_replace((tmp
= display_name
), ".", " ");
3512 has_comma
= (strstr(display_name
, ",") != NULL
);
3513 display_name
= sipe_utils_str_replace((tmp
= display_name
), ", ", " ");
3515 display_name
= sipe_utils_str_replace((tmp
= display_name
), ",", " ");
3519 parts
= g_strsplit(display_name
, " ", 0);
3521 if (!parts
[0] || !parts
[1]) {
3522 g_free(display_name
);
3536 *first_name
= g_strstrip(g_strdup(first
));
3540 *last_name
= g_strstrip(g_strdup(last
));
3543 g_free(display_name
);
3548 * Update user information
3550 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3551 * @param property_name
3552 * @param property_value may be modified to strip white space
3555 sipe_update_user_info(struct sipe_account_data
*sip
,
3557 const char *property_name
,
3558 char *property_value
)
3560 GSList
*buddies
, *entry
;
3562 if (!property_name
|| strlen(property_name
) == 0) return;
3565 property_value
= g_strstrip(property_value
);
3567 entry
= buddies
= purple_find_buddies(sip
->account
, uri
); /* all buddies in different groups */
3569 const char *prop_str
;
3570 const char *server_alias
;
3571 PurpleBuddy
*p_buddy
= entry
->data
;
3573 /* for Display Name */
3574 if (sipe_strequal(property_name
, ALIAS_PROP
)) {
3575 if (property_value
&& sipe_is_bad_alias(uri
, purple_buddy_get_alias(p_buddy
))) {
3576 SIPE_DEBUG_INFO("Replacing alias for %s with %s", uri
, property_value
);
3577 purple_blist_alias_buddy(p_buddy
, property_value
);
3580 server_alias
= purple_buddy_get_server_alias(p_buddy
);
3581 if (!is_empty(property_value
) &&
3582 (!sipe_strequal(property_value
, server_alias
) || is_empty(server_alias
)) )
3584 purple_blist_server_alias_buddy(p_buddy
, property_value
);
3587 /* for other properties */
3589 if (!is_empty(property_value
)) {
3590 prop_str
= purple_blist_node_get_string(&p_buddy
->node
, property_name
);
3591 if (!prop_str
|| !sipe_strcase_equal(prop_str
, property_value
)) {
3592 purple_blist_node_set_string(&p_buddy
->node
, property_name
, property_value
);
3597 entry
= entry
->next
;
3599 g_slist_free(buddies
);
3604 * Suitable for both 2005 and 2007 systems.
3606 * @param uri buddy SIP URI with 'sip:' prefix whose info we want to change.
3608 * @param phone may be modified to strip white space
3609 * @param phone_display_string may be modified to strip white space
3612 sipe_update_user_phone(struct sipe_account_data
*sip
,
3614 const gchar
*phone_type
,
3616 gchar
*phone_display_string
)
3618 const char *phone_node
= PHONE_PROP
; /* work phone by default */
3619 const char *phone_display_node
= PHONE_DISPLAY_PROP
; /* work phone by default */
3621 if(!phone
|| strlen(phone
) == 0) return;
3623 if ((sipe_strequal(phone_type
, "mobile") || sipe_strequal(phone_type
, "cell"))) {
3624 phone_node
= PHONE_MOBILE_PROP
;
3625 phone_display_node
= PHONE_MOBILE_DISPLAY_PROP
;
3626 } else if (sipe_strequal(phone_type
, "home")) {
3627 phone_node
= PHONE_HOME_PROP
;
3628 phone_display_node
= PHONE_HOME_DISPLAY_PROP
;
3629 } else if (sipe_strequal(phone_type
, "other")) {
3630 phone_node
= PHONE_OTHER_PROP
;
3631 phone_display_node
= PHONE_OTHER_DISPLAY_PROP
;
3632 } else if (sipe_strequal(phone_type
, "custom1")) {
3633 phone_node
= PHONE_CUSTOM1_PROP
;
3634 phone_display_node
= PHONE_CUSTOM1_DISPLAY_PROP
;
3637 sipe_update_user_info(sip
, uri
, phone_node
, phone
);
3638 if (phone_display_string
) {
3639 sipe_update_user_info(sip
, uri
, phone_display_node
, phone_display_string
);
3644 sipe_update_calendar(struct sipe_account_data
*sip
)
3646 const char* calendar
= purple_account_get_string(sip
->account
, "calendar", "EXCH");
3648 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: started.");
3650 if (sipe_strequal(calendar
, "EXCH")) {
3651 sipe_ews_update_calendar(sip
);
3654 /* schedule repeat */
3655 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_INTERVAL
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
3657 SIPE_DEBUG_INFO_NOFORMAT("sipe_update_calendar: finished.");
3661 * This method motivates Purple's Host (e.g. Pidgin) to update its UI
3662 * by using standard Purple's means of signals and saved statuses.
3664 * Thus all UI elements get updated: Status Button with Note, docklet.
3665 * This is ablolutely important as both our status and note can come
3666 * inbound (roaming) or be updated programmatically (e.g. based on our
3670 sipe_set_purple_account_status_and_note(const PurpleAccount
*account
,
3671 const char *status_id
,
3672 const char *message
,
3673 time_t do_not_publish
[])
3675 PurpleStatus
*status
= purple_account_get_active_status(account
);
3676 gboolean changed
= TRUE
;
3678 if (g_str_equal(status_id
, purple_status_get_id(status
)) &&
3679 sipe_strequal(message
, purple_status_get_attr_string(status
, SIPE_STATUS_ATTR_ID_MESSAGE
)))
3684 if (purple_savedstatus_is_idleaway()) {
3689 PurpleSavedStatus
*saved_status
;
3690 const PurpleStatusType
*acct_status_type
=
3691 purple_status_type_find_with_id(account
->status_types
, status_id
);
3692 PurpleStatusPrimitive primitive
= purple_status_type_get_primitive(acct_status_type
);
3693 sipe_activity activity
= sipe_get_activity_by_token(status_id
);
3695 saved_status
= purple_savedstatus_find_transient_by_type_and_message(primitive
, message
);
3697 purple_savedstatus_set_substatus(saved_status
, account
, acct_status_type
, message
);
3700 /* If this type+message is unique then create a new transient saved status
3701 * Ref: gtkstatusbox.c
3703 if (!saved_status
) {
3705 GList
*active_accts
= purple_accounts_get_all_active();
3707 saved_status
= purple_savedstatus_new(NULL
, primitive
);
3708 purple_savedstatus_set_message(saved_status
, message
);
3710 for (tmp
= active_accts
; tmp
!= NULL
; tmp
= tmp
->next
) {
3711 purple_savedstatus_set_substatus(saved_status
,
3712 (PurpleAccount
*)tmp
->data
, acct_status_type
, message
);
3714 g_list_free(active_accts
);
3717 do_not_publish
[activity
] = time(NULL
);
3718 SIPE_DEBUG_INFO("sipe_set_purple_account_status_and_note: do_not_publish[%s]=%d [now]",
3719 status_id
, (int)do_not_publish
[activity
]);
3721 /* Set the status for each account */
3722 purple_savedstatus_activate(saved_status
);
3726 struct hash_table_delete_payload
{
3727 GHashTable
*hash_table
;
3732 sipe_remove_category_container_publications_cb(const char *name
,
3733 struct sipe_publication
*publication
,
3734 struct hash_table_delete_payload
*payload
)
3736 if (publication
->container
== payload
->container
) {
3737 g_hash_table_remove(payload
->hash_table
, name
);
3741 sipe_remove_category_container_publications(GHashTable
*our_publications
,
3742 const char *category
,
3745 struct hash_table_delete_payload payload
;
3746 payload
.hash_table
= g_hash_table_lookup(our_publications
, category
);
3748 if (!payload
.hash_table
) return;
3750 payload
.container
= container
;
3751 g_hash_table_foreach(payload
.hash_table
, (GHFunc
)sipe_remove_category_container_publications_cb
, &payload
);
3755 send_publish_category_initial(struct sipe_account_data
*sip
);
3758 * When we receive some self (BE) NOTIFY with a new subscriber
3759 * we sends a setSubscribers request to him [SIP-PRES] 4.8
3762 static void sipe_process_roaming_self(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
3767 const sipe_xml
*node
;
3768 const sipe_xml
*node2
;
3769 char *display_name
= NULL
;
3771 GSList
*category_names
= NULL
;
3772 int aggreg_avail
= 0;
3773 static sipe_activity aggreg_activity
= SIPE_ACTIVITY_UNSET
;
3774 gboolean do_update_status
= FALSE
;
3775 gboolean has_note_cleaned
= FALSE
;
3777 SIPE_DEBUG_INFO_NOFORMAT("sipe_process_roaming_self");
3779 xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
3782 contact
= get_contact(sip
);
3783 to
= sip_uri_self(sip
);
3787 /* set list of categories participating in this XML */
3788 for (node
= sipe_xml_child(xml
, "categories/category"); node
; node
= sipe_xml_twin(node
)) {
3789 const gchar
*name
= sipe_xml_attribute(node
, "name");
3790 category_names
= slist_insert_unique_sorted(category_names
, (gchar
*)name
, (GCompareFunc
)strcmp
);
3792 SIPE_DEBUG_INFO("sipe_process_roaming_self: category_names length=%d",
3793 category_names
? (int) g_slist_length(category_names
) : -1);
3794 /* drop category information */
3795 if (category_names
) {
3796 GSList
*entry
= category_names
;
3798 GHashTable
*cat_publications
;
3799 const gchar
*category
= entry
->data
;
3800 entry
= entry
->next
;
3801 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropping category: %s", category
);
3802 cat_publications
= g_hash_table_lookup(sip
->our_publications
, category
);
3803 if (cat_publications
) {
3804 g_hash_table_remove(sip
->our_publications
, category
);
3805 SIPE_DEBUG_INFO("sipe_process_roaming_self: dropped category: %s", category
);
3809 g_slist_free(category_names
);
3810 /* filling our categories reflected in roaming data */
3811 for (node
= sipe_xml_child(xml
, "categories/category"); node
; node
= sipe_xml_twin(node
)) {
3813 const gchar
*name
= sipe_xml_attribute(node
, "name");
3814 guint container
= sipe_xml_int_attribute(node
, "container", -1);
3815 guint instance
= sipe_xml_int_attribute(node
, "instance", -1);
3816 guint version
= sipe_xml_int_attribute(node
, "version", 0);
3817 time_t publish_time
= (tmp
= sipe_xml_attribute(node
, "publishTime")) ?
3818 sipe_utils_str_to_time(tmp
) : 0;
3820 GHashTable
*cat_publications
= g_hash_table_lookup(sip
->our_publications
, name
);
3822 /* Ex. clear note: <category name="note"/> */
3823 if (container
== (guint
)-1) {
3826 do_update_status
= TRUE
;
3830 /* Ex. clear note: <category name="note" container="200"/> */
3831 if (instance
== (guint
)-1) {
3832 if (container
== 200) {
3835 do_update_status
= TRUE
;
3837 SIPE_DEBUG_INFO("sipe_process_roaming_self: removing publications for: %s/%u", name
, container
);
3838 sipe_remove_category_container_publications(
3839 sip
->our_publications
, name
, container
);
3843 /* key is <category><instance><container> */
3844 key
= g_strdup_printf("<%s><%u><%u>", name
, instance
, container
);
3845 SIPE_DEBUG_INFO("sipe_process_roaming_self: key=%s version=%d", key
, version
);
3847 /* capture all userState publication for later clean up if required */
3848 if (sipe_strequal(name
, "state") && (container
== 2 || container
== 3)) {
3849 const sipe_xml
*xn_state
= sipe_xml_child(node
, "state");
3851 if (xn_state
&& sipe_strequal(sipe_xml_attribute(xn_state
, "type"), "userState")) {
3852 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
3853 publication
->category
= g_strdup(name
);
3854 publication
->instance
= instance
;
3855 publication
->container
= container
;
3856 publication
->version
= version
;
3858 if (!sip
->user_state_publications
) {
3859 sip
->user_state_publications
= g_hash_table_new_full(
3860 g_str_hash
, g_str_equal
,
3861 g_free
, (GDestroyNotify
)free_publication
);
3863 g_hash_table_insert(sip
->user_state_publications
, g_strdup(key
), publication
);
3864 SIPE_DEBUG_INFO("sipe_process_roaming_self: added to user_state_publications key=%s version=%d",
3869 if (sipe_is_our_publication(sip
, key
)) {
3870 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
3872 publication
->category
= g_strdup(name
);
3873 publication
->instance
= instance
;
3874 publication
->container
= container
;
3875 publication
->version
= version
;
3877 /* filling publication->availability */
3878 if (sipe_strequal(name
, "state")) {
3879 const sipe_xml
*xn_state
= sipe_xml_child(node
, "state");
3880 const sipe_xml
*xn_avail
= sipe_xml_child(xn_state
, "availability");
3883 gchar
*avail_str
= sipe_xml_data(xn_avail
);
3885 publication
->availability
= atoi(avail_str
);
3889 /* for calendarState */
3890 if (xn_state
&& sipe_strequal(sipe_xml_attribute(xn_state
, "type"), "calendarState")) {
3891 const sipe_xml
*xn_activity
= sipe_xml_child(xn_state
, "activity");
3892 struct sipe_cal_event
*event
= g_new0(struct sipe_cal_event
, 1);
3894 event
->start_time
= sipe_utils_str_to_time(sipe_xml_attribute(xn_state
, "startTime"));
3896 if (sipe_strequal(sipe_xml_attribute(xn_activity
, "token"),
3897 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].token
))
3899 event
->is_meeting
= TRUE
;
3902 event
->subject
= sipe_xml_data(sipe_xml_child(xn_state
, "meetingSubject"));
3903 event
->location
= sipe_xml_data(sipe_xml_child(xn_state
, "meetingLocation"));
3905 publication
->cal_event_hash
= sipe_cal_event_hash(event
);
3906 SIPE_DEBUG_INFO("sipe_process_roaming_self: hash=%s",
3907 publication
->cal_event_hash
);
3908 sipe_cal_event_free(event
);
3911 /* filling publication->note */
3912 if (sipe_strequal(name
, "note")) {
3913 const sipe_xml
*xn_body
= sipe_xml_child(node
, "note/body");
3915 if (!has_note_cleaned
) {
3916 has_note_cleaned
= TRUE
;
3920 sip
->note_since
= publish_time
;
3922 do_update_status
= TRUE
;
3925 g_free(publication
->note
);
3926 publication
->note
= NULL
;
3930 publication
->note
= g_markup_escape_text((tmp
= sipe_xml_data(xn_body
)), -1);
3932 if (publish_time
>= sip
->note_since
) {
3934 sip
->note
= g_strdup(publication
->note
);
3935 sip
->note_since
= publish_time
;
3936 sip
->is_oof_note
= sipe_strequal(sipe_xml_attribute(xn_body
, "type"), "OOF");
3938 do_update_status
= TRUE
;
3943 /* filling publication->fb_start_str, free_busy_base64, working_hours_xml_str */
3944 if (sipe_strequal(name
, "calendarData") && (publication
->container
== 300)) {
3945 const sipe_xml
*xn_free_busy
= sipe_xml_child(node
, "calendarData/freeBusy");
3946 const sipe_xml
*xn_working_hours
= sipe_xml_child(node
, "calendarData/WorkingHours");
3948 publication
->fb_start_str
= g_strdup(sipe_xml_attribute(xn_free_busy
, "startTime"));
3949 publication
->free_busy_base64
= sipe_xml_data(xn_free_busy
);
3951 if (xn_working_hours
) {
3952 publication
->working_hours_xml_str
= sipe_xml_stringify(xn_working_hours
);
3956 if (!cat_publications
) {
3957 cat_publications
= g_hash_table_new_full(
3958 g_str_hash
, g_str_equal
,
3959 g_free
, (GDestroyNotify
)free_publication
);
3960 g_hash_table_insert(sip
->our_publications
, g_strdup(name
), cat_publications
);
3961 SIPE_DEBUG_INFO("sipe_process_roaming_self: added GHashTable cat=%s", name
);
3963 g_hash_table_insert(cat_publications
, g_strdup(key
), publication
);
3964 SIPE_DEBUG_INFO("sipe_process_roaming_self: added key=%s version=%d", key
, version
);
3968 /* aggregateState (not an our publication) from 2-nd container */
3969 if (sipe_strequal(name
, "state") && container
== 2) {
3970 const sipe_xml
*xn_state
= sipe_xml_child(node
, "state");
3972 if (xn_state
&& sipe_strequal(sipe_xml_attribute(xn_state
, "type"), "aggregateState")) {
3973 const sipe_xml
*xn_avail
= sipe_xml_child(xn_state
, "availability");
3974 const sipe_xml
*xn_activity
= sipe_xml_child(xn_state
, "activity");
3977 gchar
*avail_str
= sipe_xml_data(xn_avail
);
3979 aggreg_avail
= atoi(avail_str
);
3985 const char *activity_token
= sipe_xml_attribute(xn_activity
, "token");
3987 aggreg_activity
= sipe_get_activity_by_token(activity_token
);
3990 do_update_status
= TRUE
;
3994 /* userProperties published by server from AD */
3995 if (!sip
->csta
&& sipe_strequal(name
, "userProperties")) {
3996 const sipe_xml
*line
;
3997 /* line, for Remote Call Control (RCC) */
3998 for (line
= sipe_xml_child(node
, "userProperties/lines/line"); line
; line
= sipe_xml_twin(line
)) {
3999 const gchar
*line_server
= sipe_xml_attribute(line
, "lineServer");
4000 const gchar
*line_type
= sipe_xml_attribute(line
, "lineType");
4003 if (!line_server
|| !(sipe_strequal(line_type
, "Rcc") || sipe_strequal(line_type
, "Dual"))) continue;
4005 line_uri
= sipe_xml_data(line
);
4007 SIPE_DEBUG_INFO("sipe_process_roaming_self: line_uri=%s server=%s", line_uri
, line_server
);
4008 sip_csta_open(sip
, line_uri
, line_server
);
4016 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->our_publications size=%d",
4017 sip
->our_publications
? (int) g_hash_table_size(sip
->our_publications
) : -1);
4020 for (node
= sipe_xml_child(xml
, "containers/container"); node
; node
= sipe_xml_twin(node
)) {
4021 guint id
= sipe_xml_int_attribute(node
, "id", 0);
4022 struct sipe_container
*container
= sipe_find_container(sip
, id
);
4025 sip
->containers
= g_slist_remove(sip
->containers
, container
);
4026 SIPE_DEBUG_INFO("sipe_process_roaming_self: removed existing container id=%d v%d", container
->id
, container
->version
);
4027 free_container(container
);
4029 container
= g_new0(struct sipe_container
, 1);
4031 container
->version
= sipe_xml_int_attribute(node
, "version", 0);
4032 sip
->containers
= g_slist_append(sip
->containers
, container
);
4033 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container id=%d v%d", container
->id
, container
->version
);
4035 for (node2
= sipe_xml_child(node
, "member"); node2
; node2
= sipe_xml_twin(node2
)) {
4036 struct sipe_container_member
*member
= g_new0(struct sipe_container_member
, 1);
4037 member
->type
= g_strdup(sipe_xml_attribute(node2
, "type"));
4038 member
->value
= g_strdup(sipe_xml_attribute(node2
, "value"));
4039 container
->members
= g_slist_append(container
->members
, member
);
4040 SIPE_DEBUG_INFO("sipe_process_roaming_self: added container member type=%s value=%s",
4041 member
->type
, member
->value
? member
->value
: "");
4045 SIPE_DEBUG_INFO("sipe_process_roaming_self: sip->access_level_set=%s", sip
->access_level_set
? "TRUE" : "FALSE");
4046 if (!sip
->access_level_set
&& sipe_xml_child(xml
, "containers")) {
4047 char *container_xmls
= NULL
;
4048 int sameEnterpriseAL
= sipe_find_access_level(sip
, "sameEnterprise", NULL
, NULL
);
4049 int federatedAL
= sipe_find_access_level(sip
, "federated", NULL
, NULL
);
4051 SIPE_DEBUG_INFO("sipe_process_roaming_self: sameEnterpriseAL=%d", sameEnterpriseAL
);
4052 SIPE_DEBUG_INFO("sipe_process_roaming_self: federatedAL=%d", federatedAL
);
4053 /* initial set-up to let counterparties see your status */
4054 if (sameEnterpriseAL
< 0) {
4055 struct sipe_container
*container
= sipe_find_container(sip
, 200);
4056 guint version
= container
? container
->version
: 0;
4057 sipe_send_container_members_prepare(200, version
, "add", "sameEnterprise", NULL
, &container_xmls
);
4059 if (federatedAL
< 0) {
4060 struct sipe_container
*container
= sipe_find_container(sip
, 100);
4061 guint version
= container
? container
->version
: 0;
4062 sipe_send_container_members_prepare(100, version
, "add", "federated", NULL
, &container_xmls
);
4064 sip
->access_level_set
= TRUE
;
4066 if (container_xmls
) {
4067 sipe_send_set_container_members(sip
, container_xmls
);
4069 g_free(container_xmls
);
4072 /* Refresh contacts' blocked status */
4073 sipe_refresh_blocked_status(sip
);
4076 for (node
= sipe_xml_child(xml
, "subscribers/subscriber"); node
; node
= sipe_xml_twin(node
)) {
4078 const char *acknowledged
;
4082 user
= sipe_xml_attribute(node
, "user"); /* without 'sip:' prefix */
4083 if (!user
) continue;
4084 SIPE_DEBUG_INFO("sipe_process_roaming_self: user %s", user
);
4085 display_name
= g_strdup(sipe_xml_attribute(node
, "displayName"));
4086 uri
= sip_uri_from_name(user
);
4088 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
4090 acknowledged
= sipe_xml_attribute(node
, "acknowledged");
4091 if(sipe_strcase_equal(acknowledged
,"false")){
4092 SIPE_DEBUG_INFO("sipe_process_roaming_self: user added you %s", user
);
4093 if (!purple_find_buddy(sip
->account
, uri
)) {
4094 purple_account_request_add(sip
->account
, uri
, _("you"), display_name
, NULL
);
4097 hdr
= g_strdup_printf(
4099 "Content-Type: application/msrtc-presence-setsubscriber+xml\r\n", contact
);
4101 body
= g_strdup_printf(
4102 "<setSubscribers xmlns=\"http://schemas.microsoft.com/2006/09/sip/presence-subscribers\">"
4103 "<subscriber user=\"%s\" acknowledged=\"true\"/>"
4104 "</setSubscribers>", user
);
4106 send_sip_request(sip
->gc
, "SERVICE", to
, to
, hdr
, body
, NULL
, NULL
);
4110 g_free(display_name
);
4117 /* Publish initial state if not yet.
4118 * Assuming this happens on initial responce to subscription to roaming-self
4119 * so we've already updated our roaming data in full.
4122 if (!sip
->initial_state_published
) {
4123 send_publish_category_initial(sip
);
4124 sip
->initial_state_published
= TRUE
;
4126 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
4127 do_update_status
= FALSE
;
4128 } else if (aggreg_avail
) {
4130 g_free(sip
->status
);
4131 if (aggreg_avail
&& aggreg_avail
< 18000) { /* not offline */
4132 sip
->status
= g_strdup(sipe_get_status_by_availability(aggreg_avail
, NULL
));
4134 sip
->status
= g_strdup(SIPE_STATUS_ID_INVISIBLE
); /* not not let offline status switch us off */
4138 if (do_update_status
) {
4139 SIPE_DEBUG_INFO("sipe_process_roaming_self: switch to '%s' for the account", sip
->status
);
4140 sipe_set_purple_account_status_and_note(sip
->account
, sip
->status
, sip
->note
, sip
->do_not_publish
);
4146 static void sipe_subscribe_roaming_acl(struct sipe_account_data
*sip
)
4148 gchar
*to
= sip_uri_self(sip
);
4149 gchar
*tmp
= get_contact(sip
);
4150 gchar
*hdr
= g_strdup_printf(
4151 "Event: vnd-microsoft-roaming-ACL\r\n"
4152 "Accept: application/vnd-microsoft-roaming-acls+xml\r\n"
4153 "Supported: com.microsoft.autoextend\r\n"
4154 "Supported: ms-benotify\r\n"
4155 "Proxy-Require: ms-benotify\r\n"
4156 "Supported: ms-piggyback-first-notify\r\n"
4157 "Contact: %s\r\n", tmp
);
4160 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, "", NULL
, process_subscribe_response
);
4166 * To request for presence information about the user, access level settings that have already been configured by the user
4167 * to control who has access to what information, and the list of contacts who currently have outstanding subscriptions.
4168 * We wait (BE)NOTIFY messages with some info change (categories,containers, subscribers)
4171 static void sipe_subscribe_roaming_self(struct sipe_account_data
*sip
)
4173 gchar
*to
= sip_uri_self(sip
);
4174 gchar
*tmp
= get_contact(sip
);
4175 gchar
*hdr
= g_strdup_printf(
4176 "Event: vnd-microsoft-roaming-self\r\n"
4177 "Accept: application/vnd-microsoft-roaming-self+xml\r\n"
4178 "Supported: ms-benotify\r\n"
4179 "Proxy-Require: ms-benotify\r\n"
4180 "Supported: ms-piggyback-first-notify\r\n"
4182 "Content-Type: application/vnd-microsoft-roaming-self+xml\r\n", tmp
);
4184 gchar
*body
=g_strdup(
4185 "<roamingList xmlns=\"http://schemas.microsoft.com/2006/09/sip/roaming-self\">"
4186 "<roaming type=\"categories\"/>"
4187 "<roaming type=\"containers\"/>"
4188 "<roaming type=\"subscribers\"/></roamingList>");
4191 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
4200 static void sipe_subscribe_roaming_provisioning(struct sipe_account_data
*sip
)
4202 gchar
*to
= sip_uri_self(sip
);
4203 gchar
*tmp
= get_contact(sip
);
4204 gchar
*hdr
= g_strdup_printf(
4205 "Event: vnd-microsoft-provisioning\r\n"
4206 "Accept: application/vnd-microsoft-roaming-provisioning+xml\r\n"
4207 "Supported: com.microsoft.autoextend\r\n"
4208 "Supported: ms-benotify\r\n"
4209 "Proxy-Require: ms-benotify\r\n"
4210 "Supported: ms-piggyback-first-notify\r\n"
4212 "Contact: %s\r\n", tmp
);
4215 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, NULL
, NULL
, process_subscribe_response
);
4220 /** Subscription for provisioning information to help with initial
4221 * configuration. This subscription is a one-time query (denoted by the Expires header,
4222 * which asks for 0 seconds for the subscription lifetime). This subscription asks for server
4223 * configuration, meeting policies, and policy settings that Communicator must enforce.
4224 * TODO: for what we need this information.
4227 static void sipe_subscribe_roaming_provisioning_v2(struct sipe_account_data
*sip
)
4229 gchar
*to
= sip_uri_self(sip
);
4230 gchar
*tmp
= get_contact(sip
);
4231 gchar
*hdr
= g_strdup_printf(
4232 "Event: vnd-microsoft-provisioning-v2\r\n"
4233 "Accept: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n"
4234 "Supported: com.microsoft.autoextend\r\n"
4235 "Supported: ms-benotify\r\n"
4236 "Proxy-Require: ms-benotify\r\n"
4237 "Supported: ms-piggyback-first-notify\r\n"
4240 "Content-Type: application/vnd-microsoft-roaming-provisioning-v2+xml\r\n", tmp
);
4241 gchar
*body
= g_strdup(
4242 "<provisioningGroupList xmlns=\"http://schemas.microsoft.com/2006/09/sip/provisioninggrouplist\">"
4243 "<provisioningGroup name=\"ServerConfiguration\"/><provisioningGroup name=\"meetingPolicy\"/>"
4244 "<provisioningGroup name=\"ucPolicy\"/>"
4245 "</provisioningGroupList>");
4248 send_sip_request(sip
->gc
, "SUBSCRIBE", to
, to
, hdr
, body
, NULL
, process_subscribe_response
);
4255 sipe_unsubscribe_cb(SIPE_UNUSED_PARAMETER gpointer key
,
4256 gpointer value
, gpointer user_data
)
4258 struct sip_subscription
*subscription
= value
;
4259 struct sip_dialog
*dialog
= &subscription
->dialog
;
4260 struct sipe_account_data
*sip
= user_data
;
4261 gchar
*tmp
= get_contact(sip
);
4262 gchar
*hdr
= g_strdup_printf(
4265 "Contact: %s\r\n", subscription
->event
, tmp
);
4268 /* Rate limit to max. 25 requests per seconds */
4269 g_usleep(1000000 / 25);
4271 send_sip_request(sip
->gc
, "SUBSCRIBE", dialog
->with
, dialog
->with
, hdr
, NULL
, dialog
, NULL
);
4275 /* IM Session (INVITE and MESSAGE methods) */
4277 /* EndPoints: "alice alisson" <sip:alice@atlanta.local>, <sip:bob@atlanta.local>;epid=ebca82d94d, <sip:carol@atlanta.local> */
4279 get_end_points (struct sipe_account_data
*sip
,
4280 struct sip_session
*session
)
4284 if (session
== NULL
) {
4288 res
= g_strdup_printf("<sip:%s>", sip
->username
);
4290 SIPE_DIALOG_FOREACH
{
4292 res
= g_strdup_printf("%s, <%s>", res
, dialog
->with
);
4295 if (dialog
->theirepid
) {
4297 res
= g_strdup_printf("%s;epid=%s", res
, dialog
->theirepid
);
4300 } SIPE_DIALOG_FOREACH_END
;
4306 process_options_response(SIPE_UNUSED_PARAMETER
struct sipe_account_data
*sip
,
4308 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
4310 gboolean ret
= TRUE
;
4312 if (msg
->response
!= 200) {
4313 SIPE_DEBUG_INFO("process_options_response: OPTIONS response is %d", msg
->response
);
4317 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg
->body
? msg
->body
: "");
4323 * Asks UA/proxy about its capabilities.
4325 static void sipe_options_request(struct sipe_account_data
*sip
, const char *who
)
4327 gchar
*to
= sip_uri(who
);
4328 gchar
*contact
= get_contact(sip
);
4329 gchar
*request
= g_strdup_printf(
4330 "Accept: application/sdp\r\n"
4331 "Contact: %s\r\n", contact
);
4334 send_sip_request(sip
->gc
, "OPTIONS", to
, to
, request
, NULL
, NULL
, process_options_response
);
4341 sipe_notify_user(struct sipe_account_data
*sip
,
4342 struct sip_session
*session
,
4343 PurpleMessageFlags flags
,
4344 const gchar
*message
)
4346 PurpleConversation
*conv
;
4348 if (!session
->conv
) {
4349 conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_ANY
, session
->with
, sip
->account
);
4351 conv
= session
->conv
;
4353 purple_conversation_write(conv
, NULL
, message
, flags
, time(NULL
));
4357 sipe_present_info(struct sipe_account_data
*sip
,
4358 struct sip_session
*session
,
4359 const gchar
*message
)
4361 sipe_notify_user(sip
, session
, PURPLE_MESSAGE_SYSTEM
, message
);
4365 sipe_present_err(struct sipe_account_data
*sip
,
4366 struct sip_session
*session
,
4367 const gchar
*message
)
4369 sipe_notify_user(sip
, session
, PURPLE_MESSAGE_ERROR
, message
);
4373 sipe_present_message_undelivered_err(struct sipe_account_data
*sip
,
4374 struct sip_session
*session
,
4378 const gchar
*message
)
4380 char *msg
, *msg_tmp
, *msg_tmp2
;
4383 msg_tmp
= message
? sipe_backend_markup_strip_html(message
) : NULL
;
4384 msg
= msg_tmp
? g_strdup_printf("<font color=\"#888888\"></b>%s<b></font>", msg_tmp
) : NULL
;
4386 /* Service unavailable; Server Internal Error; Server Time-out */
4387 if (sip_error
== 606 && sip_warning
== 309) { /* Not acceptable all. */ /* Message contents not allowed by policy */
4388 label
= _("Your message or invitation was not delivered, possibly because it contains a hyperlink or other content that the system administrator has blocked.");
4391 } else if (sip_error
== 503 || sip_error
== 500 || sip_error
== 504) {
4392 label
= _("This message was not delivered to %s because the service is not available");
4393 } else if (sip_error
== 486) { /* Busy Here */
4394 label
= _("This message was not delivered to %s because one or more recipients do not want to be disturbed");
4395 } else if (sip_error
== 415) { /* Unsupported media type */
4396 label
= _("This message was not delivered to %s because one or more recipients don't support this type of message");
4398 label
= _("This message was not delivered to %s because one or more recipients are offline");
4401 msg_tmp
= g_strdup_printf( "%s%s\n%s" ,
4402 msg_tmp2
= g_strdup_printf(label
, who
? who
: ""),
4405 sipe_present_err(sip
, session
, msg_tmp
);
4413 process_message_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
4414 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
4416 gboolean ret
= TRUE
;
4417 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
4418 struct sip_session
*session
= sipe_session_find_im(sip
, with
);
4419 struct sip_dialog
*dialog
;
4422 struct queued_message
*message
;
4425 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: unable to find IM session");
4430 dialog
= sipe_dialog_find(session
, with
);
4432 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: session outgoing dialog is NULL");
4437 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
4438 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", sipmsg_find_header(msg
, "Call-ID"), atoi(cseq
), with
);
4440 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
4442 if (msg
->response
>= 400) {
4443 PurpleBuddy
*pbuddy
;
4444 const char *alias
= with
;
4445 const char *warn_hdr
= sipmsg_find_header(msg
, "Warning");
4448 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: MESSAGE response >= 400");
4451 gchar
**parts
= g_strsplit(warn_hdr
, " ", 2);
4453 warning
= atoi(parts
[0]);
4458 /* cancel file transfer as rejected by server */
4459 if (msg
->response
== 606 && /* Not acceptable all. */
4460 warning
== 309 && /* Message contents not allowed by policy */
4461 message
&& g_str_has_prefix(message
->content_type
, "text/x-msmsgsinvite"))
4463 GSList
*parsed_body
= sipe_ft_parse_msg_body(msg
->body
);
4464 sipe_ft_incoming_cancel(sip
->gc
->account
, parsed_body
);
4465 sipe_utils_nameval_free(parsed_body
);
4468 if ((pbuddy
= purple_find_buddy(sip
->account
, with
))) {
4469 alias
= purple_buddy_get_alias(pbuddy
);
4472 sipe_present_message_undelivered_err(sip
, session
, msg
->response
, warning
, alias
, (message
? message
->body
: NULL
));
4474 /* drop dangling IM sessions: assume that BYE from remote never reached us */
4475 if (msg
->response
== 408 || /* Request timeout */
4476 msg
->response
== 480 || /* Temporarily Unavailable */
4477 msg
->response
== 481) { /* Call/Transaction Does Not Exist */
4478 SIPE_DEBUG_INFO_NOFORMAT("process_message_response: assuming dangling IM session, dropping it.");
4479 send_sip_request(sip
->gc
, "BYE", with
, with
, NULL
, NULL
, dialog
, NULL
);
4484 const gchar
*message_id
= sipmsg_find_header(msg
, "Message-Id");
4486 g_hash_table_insert(session
->conf_unconfirmed_messages
, g_strdup(message_id
), g_strdup(message
->body
));
4487 SIPE_DEBUG_INFO("process_message_response: added message with id %s to conf_unconfirmed_messages(count=%d)",
4488 message_id
, g_hash_table_size(session
->conf_unconfirmed_messages
));
4491 g_hash_table_remove(session
->unconfirmed_messages
, key
);
4492 SIPE_DEBUG_INFO("process_message_response: removed message %s from unconfirmed_messages(count=%d)",
4493 key
, g_hash_table_size(session
->unconfirmed_messages
));
4499 if (ret
) sipe_im_process_queue(sip
, session
);
4504 sipe_is_election_finished(struct sip_session
*session
);
4507 sipe_election_result(struct sipe_account_data
*sip
,
4511 process_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
4512 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
4514 const gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
4515 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4516 struct sip_dialog
*dialog
;
4517 struct sip_session
*session
;
4519 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4521 SIPE_DEBUG_INFO("process_info_response: failed find dialog for callid %s, exiting.", callid
);
4525 if (msg
->response
== 200 && g_str_has_prefix(contenttype
, "application/x-ms-mim")) {
4526 sipe_xml
*xn_action
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
4527 const sipe_xml
*xn_request_rm_response
= sipe_xml_child(xn_action
, "RequestRMResponse");
4528 const sipe_xml
*xn_set_rm_response
= sipe_xml_child(xn_action
, "SetRMResponse");
4530 if (xn_request_rm_response
) {
4531 const char *with
= sipe_xml_attribute(xn_request_rm_response
, "uri");
4532 const char *allow
= sipe_xml_attribute(xn_request_rm_response
, "allow");
4534 dialog
= sipe_dialog_find(session
, with
);
4536 SIPE_DEBUG_INFO("process_info_response: failed find dialog for %s, exiting.", with
);
4537 sipe_xml_free(xn_action
);
4541 if (allow
&& !g_strcasecmp(allow
, "true")) {
4542 SIPE_DEBUG_INFO("process_info_response: %s has voted PRO", with
);
4543 dialog
->election_vote
= 1;
4544 } else if (allow
&& !g_strcasecmp(allow
, "false")) {
4545 SIPE_DEBUG_INFO("process_info_response: %s has voted CONTRA", with
);
4546 dialog
->election_vote
= -1;
4549 if (sipe_is_election_finished(session
)) {
4550 sipe_election_result(sip
, session
);
4553 } else if (xn_set_rm_response
) {
4556 sipe_xml_free(xn_action
);
4563 static void sipe_send_message(struct sipe_account_data
*sip
, struct sip_dialog
*dialog
, const char *msg
, const char *content_type
)
4567 char *msgtext
= NULL
;
4568 const gchar
*msgr
= "";
4571 if (!g_str_has_prefix(content_type
, "text/x-msmsgsinvite")) {
4575 sipe_parse_html(msg
, &msgformat
, &msgtext
);
4576 SIPE_DEBUG_INFO("sipe_send_message: msgformat=%s", msgformat
);
4578 msgr_value
= sipmsg_get_msgr_string(msgformat
);
4581 msgr
= tmp2
= g_strdup_printf(";msgr=%s", msgr_value
);
4585 msgtext
= g_strdup(msg
);
4588 tmp
= get_contact(sip
);
4589 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8\r\n");
4590 //hdr = g_strdup("Content-Type: text/rtf\r\n");
4591 //hdr = g_strdup("Content-Type: text/plain; charset=UTF-8;msgr=WAAtAE0ATQBTAC....AoADQA\r\nSupported: timer\r\n");
4592 if (content_type
== NULL
)
4593 content_type
= "text/plain";
4595 hdr
= g_strdup_printf("Contact: %s\r\nContent-Type: %s; charset=UTF-8%s\r\n", tmp
, content_type
, msgr
);
4599 send_sip_request(sip
->gc
, "MESSAGE", dialog
->with
, dialog
->with
, hdr
, msgtext
, dialog
, process_message_response
);
4606 sipe_im_process_queue (struct sipe_account_data
* sip
, struct sip_session
* session
)
4608 GSList
*entry2
= session
->outgoing_message_queue
;
4610 struct queued_message
*msg
= entry2
->data
;
4612 /* for multiparty chat or conference */
4613 if (session
->is_multiparty
|| session
->focus_uri
) {
4614 gchar
*who
= sip_uri_self(sip
);
4615 serv_got_chat_in(sip
->gc
, session
->chat_id
, who
,
4616 PURPLE_MESSAGE_SEND
, msg
->body
, time(NULL
));
4620 SIPE_DIALOG_FOREACH
{
4622 struct queued_message
*message
;
4624 if (dialog
->outgoing_invite
) continue; /* do not send messages as INVITE is not responded. */
4626 message
= g_new0(struct queued_message
,1);
4627 message
->body
= g_strdup(msg
->body
);
4628 if (msg
->content_type
!= NULL
)
4629 message
->content_type
= g_strdup(msg
->content_type
);
4631 key
= g_strdup_printf("<%s><%d><MESSAGE><%s>", dialog
->callid
, (dialog
->cseq
) + 1, dialog
->with
);
4632 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), message
);
4633 SIPE_DEBUG_INFO("sipe_im_process_queue: added message %s to unconfirmed_messages(count=%d)",
4634 key
, g_hash_table_size(session
->unconfirmed_messages
));
4637 sipe_send_message(sip
, dialog
, msg
->body
, msg
->content_type
);
4638 } SIPE_DIALOG_FOREACH_END
;
4640 entry2
= sipe_session_dequeue_message(session
);
4645 sipe_refer_notify(struct sipe_account_data
*sip
,
4646 struct sip_session
*session
,
4653 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
4655 hdr
= g_strdup_printf(
4657 "Subscription-State: %s\r\n"
4658 "Content-Type: message/sipfrag\r\n",
4659 status
>= 200 ? "terminated" : "active");
4661 body
= g_strdup_printf(
4662 "SIP/2.0 %d %s\r\n",
4665 send_sip_request(sip
->gc
, "NOTIFY", who
, who
, hdr
, body
, dialog
, NULL
);
4672 process_invite_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
4674 gchar
*with
= parse_from(sipmsg_find_header(msg
, "To"));
4675 struct sip_session
*session
;
4676 struct sip_dialog
*dialog
;
4679 struct queued_message
*message
;
4680 struct sipmsg
*request_msg
= trans
->msg
;
4682 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
4685 session
= sipe_session_find_chat_by_callid(sip
, callid
);
4687 session
= sipe_session_find_im(sip
, with
);
4690 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: unable to find IM session");
4695 dialog
= sipe_dialog_find(session
, with
);
4697 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: session outgoing dialog is NULL");
4702 sipe_dialog_parse(dialog
, msg
, TRUE
);
4704 cseq
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "CSeq"), NULL
, " ", NULL
);
4705 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, atoi(cseq
));
4707 message
= g_hash_table_lookup(session
->unconfirmed_messages
, key
);
4709 if (msg
->response
!= 200) {
4710 PurpleBuddy
*pbuddy
;
4711 const char *alias
= with
;
4712 const char *warn_hdr
= sipmsg_find_header(msg
, "Warning");
4715 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: INVITE response not 200");
4718 gchar
**parts
= g_strsplit(warn_hdr
, " ", 2);
4720 warning
= atoi(parts
[0]);
4725 /* cancel file transfer as rejected by server */
4726 if (msg
->response
== 606 && /* Not acceptable all. */
4727 warning
== 309 && /* Message contents not allowed by policy */
4728 message
&& g_str_has_prefix(message
->content_type
, "text/x-msmsgsinvite"))
4730 GSList
*parsed_body
= sipe_ft_parse_msg_body(message
->body
);
4731 sipe_ft_incoming_cancel(sip
->gc
->account
, parsed_body
);
4732 sipe_utils_nameval_free(parsed_body
);
4735 if ((pbuddy
= purple_find_buddy(sip
->account
, with
))) {
4736 alias
= purple_buddy_get_alias(pbuddy
);
4740 sipe_present_message_undelivered_err(sip
, session
, msg
->response
, warning
, alias
, message
->body
);
4742 gchar
*tmp_msg
= g_strdup_printf(_("Failed to invite %s"), alias
);
4743 sipe_present_err(sip
, session
, tmp_msg
);
4747 sipe_dialog_remove(session
, with
);
4755 send_sip_request(sip
->gc
, "ACK", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
4756 dialog
->outgoing_invite
= NULL
;
4757 dialog
->is_established
= TRUE
;
4759 referred_by
= parse_from(sipmsg_find_header(request_msg
, "Referred-By"));
4761 sipe_refer_notify(sip
, session
, referred_by
, 200, "OK");
4762 g_free(referred_by
);
4765 /* add user to chat if it is a multiparty session */
4766 if (session
->is_multiparty
) {
4767 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
4769 PURPLE_CBFLAGS_NONE
, TRUE
);
4772 if(g_slist_find_custom(dialog
->supported
, "ms-text-format", (GCompareFunc
)g_ascii_strcasecmp
)) {
4773 SIPE_DEBUG_INFO_NOFORMAT("process_invite_response: remote system accepted message in INVITE");
4774 sipe_session_dequeue_message(session
);
4777 sipe_im_process_queue(sip
, session
);
4779 g_hash_table_remove(session
->unconfirmed_messages
, key
);
4780 SIPE_DEBUG_INFO("process_invite_response: removed message %s from unconfirmed_messages(count=%d)",
4781 key
, g_hash_table_size(session
->unconfirmed_messages
));
4790 sipe_invite(struct sipe_account_data
*sip
,
4791 struct sip_session
*session
,
4793 const gchar
*msg_body
,
4794 const gchar
*msg_content_type
,
4795 const gchar
*referred_by
,
4796 const gboolean is_triggered
)
4803 char *ms_text_format
= NULL
;
4804 gchar
*roster_manager
;
4806 gchar
*referred_by_str
;
4807 struct sip_dialog
*dialog
= sipe_dialog_find(session
, who
);
4809 if (dialog
&& dialog
->is_established
) {
4810 SIPE_DEBUG_INFO("session with %s already has a dialog open", who
);
4815 dialog
= sipe_dialog_add(session
);
4816 dialog
->callid
= session
->callid
? g_strdup(session
->callid
) : gencallid();
4817 dialog
->with
= g_strdup(who
);
4820 if (!(dialog
->ourtag
)) {
4821 dialog
->ourtag
= gentag();
4827 char *msgtext
= NULL
;
4829 const gchar
*msgr
= "";
4831 struct queued_message
*message
;
4834 if (!g_str_has_prefix(msg_content_type
, "text/x-msmsgsinvite")) {
4838 sipe_parse_html(msg_body
, &msgformat
, &msgtext
);
4839 SIPE_DEBUG_INFO("sipe_invite: msgformat=%s", msgformat
);
4841 msgr_value
= sipmsg_get_msgr_string(msgformat
);
4844 msgr
= tmp
= g_strdup_printf(";msgr=%s", msgr_value
);
4848 msgtext
= g_strdup(msg_body
);
4851 base64_msg
= g_base64_encode((guchar
*) msgtext
, strlen(msgtext
));
4852 ms_text_format
= g_strdup_printf(SIPE_INVITE_TEXT
,
4853 msg_content_type
? msg_content_type
: "text/plain",
4860 message
= g_new0(struct queued_message
,1);
4861 message
->body
= g_strdup(msg_body
);
4862 if (msg_content_type
!= NULL
)
4863 message
->content_type
= g_strdup(msg_content_type
);
4865 key
= g_strdup_printf("<%s><%d><INVITE>", dialog
->callid
, (dialog
->cseq
) + 1);
4866 g_hash_table_insert(session
->unconfirmed_messages
, g_strdup(key
), message
);
4867 SIPE_DEBUG_INFO("sipe_invite: added message %s to unconfirmed_messages(count=%d)",
4868 key
, g_hash_table_size(session
->unconfirmed_messages
));
4872 contact
= get_contact(sip
);
4873 end_points
= get_end_points(sip
, session
);
4874 self
= sip_uri_self(sip
);
4875 roster_manager
= g_strdup_printf(
4876 "Roster-Manager: %s\r\n"
4877 "EndPoints: %s\r\n",
4880 referred_by_str
= referred_by
?
4882 "Referred-By: %s\r\n",
4885 hdr
= g_strdup_printf(
4886 "Supported: ms-sender\r\n"
4892 "Content-Type: application/sdp\r\n",
4893 sipe_strcase_equal(session
->roster_manager
, self
) ? roster_manager
: "",
4895 is_triggered
? "TriggeredInvite: TRUE\r\n" : "",
4896 is_triggered
|| session
->is_multiparty
? "Require: com.microsoft.rtc-multiparty\r\n" : "",
4898 ms_text_format
? ms_text_format
: "");
4899 g_free(ms_text_format
);
4902 body
= g_strdup_printf(
4904 "o=- 0 0 IN IP4 %s\r\n"
4908 "m=%s %d sip null\r\n"
4909 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
4910 sipe_backend_network_ip_address(),
4911 sipe_backend_network_ip_address(),
4912 sip
->ocs2007
? "message" : "x-ms-message",
4915 dialog
->outgoing_invite
= send_sip_request(sip
->gc
, "INVITE",
4916 to
, to
, hdr
, body
, dialog
, process_invite_response
);
4919 g_free(roster_manager
);
4921 g_free(referred_by_str
);
4928 sipe_refer(struct sipe_account_data
*sip
,
4929 struct sip_session
*session
,
4934 gchar
*epid
= get_epid(sip
);
4935 struct sip_dialog
*dialog
= sipe_dialog_find(session
,
4936 session
->roster_manager
);
4937 const char *ourtag
= dialog
&& dialog
->ourtag
? dialog
->ourtag
: NULL
;
4939 contact
= get_contact(sip
);
4940 hdr
= g_strdup_printf(
4942 "Refer-to: <%s>\r\n"
4943 "Referred-By: <sip:%s>%s%s;epid=%s\r\n"
4944 "Require: com.microsoft.rtc-multiparty\r\n",
4948 ourtag
? ";tag=" : "",
4949 ourtag
? ourtag
: "",
4953 send_sip_request(sip
->gc
, "REFER",
4954 session
->roster_manager
, session
->roster_manager
, hdr
, NULL
, dialog
, NULL
);
4961 sipe_send_election_request_rm(struct sipe_account_data
*sip
,
4962 struct sip_dialog
*dialog
,
4965 const gchar
*hdr
= "Content-Type: application/x-ms-mim\r\n";
4967 gchar
*body
= g_strdup_printf(
4968 "<?xml version=\"1.0\"?>\r\n"
4969 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4970 "<RequestRM uri=\"sip:%s\" bid=\"%d\"/></action>\r\n",
4971 sip
->username
, bid
);
4973 send_sip_request(sip
->gc
, "INFO",
4974 dialog
->with
, dialog
->with
, hdr
, body
, dialog
, process_info_response
);
4980 sipe_send_election_set_rm(struct sipe_account_data
*sip
,
4981 struct sip_dialog
*dialog
)
4983 const gchar
*hdr
= "Content-Type: application/x-ms-mim\r\n";
4985 gchar
*body
= g_strdup_printf(
4986 "<?xml version=\"1.0\"?>\r\n"
4987 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
4988 "<SetRM uri=\"sip:%s\"/></action>\r\n",
4991 send_sip_request(sip
->gc
, "INFO",
4992 dialog
->with
, dialog
->with
, hdr
, body
, dialog
, process_info_response
);
4998 sipe_session_close(struct sipe_account_data
*sip
,
4999 struct sip_session
* session
)
5001 if (session
&& session
->focus_uri
) {
5002 sipe_conf_immcu_closed(sip
, session
);
5003 conf_session_close(sip
, session
);
5007 SIPE_DIALOG_FOREACH
{
5008 /* @TODO slow down BYE message sending rate */
5009 /* @see single subscription code */
5010 send_sip_request(sip
->gc
, "BYE", dialog
->with
, dialog
->with
, NULL
, NULL
, dialog
, NULL
);
5011 } SIPE_DIALOG_FOREACH_END
;
5013 sipe_session_remove(sip
, session
);
5018 sipe_session_close_all(struct sipe_account_data
*sip
)
5021 while ((entry
= sip
->sessions
) != NULL
) {
5022 sipe_session_close(sip
, entry
->data
);
5027 sipe_convo_closed(PurpleConnection
* gc
, const char *who
)
5029 struct sipe_account_data
*sip
= gc
->proto_data
;
5031 SIPE_DEBUG_INFO("conversation with %s closed", who
);
5032 sipe_session_close(sip
, sipe_session_find_im(sip
, who
));
5036 sipe_chat_invite(PurpleConnection
*gc
, int id
,
5037 SIPE_UNUSED_PARAMETER
const char *message
,
5040 sipe_chat_create(gc
->proto_data
, id
, name
);
5044 sipe_chat_leave (PurpleConnection
*gc
, int id
)
5046 struct sipe_account_data
*sip
= gc
->proto_data
;
5047 struct sip_session
*session
= sipe_session_find_chat_by_id(sip
, id
);
5049 sipe_session_close(sip
, session
);
5052 static int sipe_im_send(PurpleConnection
*gc
, const char *who
, const char *what
,
5053 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
5055 struct sipe_account_data
*sip
= gc
->proto_data
;
5056 struct sip_session
*session
;
5057 struct sip_dialog
*dialog
;
5058 gchar
*uri
= sip_uri(who
);
5060 SIPE_DEBUG_INFO("sipe_im_send what='%s'", what
);
5062 session
= sipe_session_find_or_add_im(sip
, uri
);
5063 dialog
= sipe_dialog_find(session
, uri
);
5065 // Queue the message
5066 sipe_session_enqueue_message(session
, what
, NULL
);
5068 if (dialog
&& !dialog
->outgoing_invite
) {
5069 sipe_im_process_queue(sip
, session
);
5070 } else if (!dialog
|| !dialog
->outgoing_invite
) {
5071 // Need to send the INVITE to get the outgoing dialog setup
5072 sipe_invite(sip
, session
, uri
, what
, NULL
, NULL
, FALSE
);
5079 static int sipe_chat_send(PurpleConnection
*gc
, int id
, const char *what
,
5080 SIPE_UNUSED_PARAMETER PurpleMessageFlags flags
)
5082 struct sipe_account_data
*sip
= gc
->proto_data
;
5083 struct sip_session
*session
;
5085 SIPE_DEBUG_INFO("sipe_chat_send what='%s'", what
);
5087 session
= sipe_session_find_chat_by_id(sip
, id
);
5089 // Queue the message
5090 if (session
&& session
->dialogs
) {
5091 sipe_session_enqueue_message(session
,what
,NULL
);
5092 sipe_im_process_queue(sip
, session
);
5094 gchar
*chat_name
= purple_find_chat(sip
->gc
, id
)->name
;
5095 const gchar
*proto_chat_id
= sipe_chat_find_name(chat_name
);
5097 SIPE_DEBUG_INFO("sipe_chat_send: chat_name='%s'", chat_name
? chat_name
: "NULL");
5098 SIPE_DEBUG_INFO("sipe_chat_send: proto_chat_id='%s'", proto_chat_id
? proto_chat_id
: "NULL");
5101 struct sip_session
*session
= sipe_session_add_chat(sip
);
5103 session
->is_multiparty
= FALSE
;
5104 session
->focus_uri
= g_strdup(proto_chat_id
);
5105 sipe_session_enqueue_message(session
, what
, NULL
);
5106 sipe_invite_conf_focus(sip
, session
);
5113 /* End IM Session (INVITE and MESSAGE methods) */
5115 static void process_incoming_info(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
5117 const gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
5118 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
5120 struct sip_session
*session
;
5122 SIPE_DEBUG_INFO("process_incoming_info: \n%s", msg
->body
? msg
->body
: "");
5124 /* Call Control protocol */
5125 if (g_str_has_prefix(contenttype
, "application/csta+xml"))
5127 process_incoming_info_csta(sip
, msg
);
5131 from
= parse_from(sipmsg_find_header(msg
, "From"));
5132 session
= sipe_session_find_chat_by_callid(sip
, callid
);
5134 session
= sipe_session_find_im(sip
, from
);
5141 if (g_str_has_prefix(contenttype
, "application/x-ms-mim"))
5143 sipe_xml
*xn_action
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
5144 const sipe_xml
*xn_request_rm
= sipe_xml_child(xn_action
, "RequestRM");
5145 const sipe_xml
*xn_set_rm
= sipe_xml_child(xn_action
, "SetRM");
5147 sipmsg_add_header(msg
, "Content-Type", "application/x-ms-mim");
5149 if (xn_request_rm
) {
5150 //const char *rm = sipe_xml_attribute(xn_request_rm, "uri");
5151 int bid
= sipe_xml_int_attribute(xn_request_rm
, "bid", 0);
5152 gchar
*body
= g_strdup_printf(
5153 "<?xml version=\"1.0\"?>\r\n"
5154 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5155 "<RequestRMResponse uri=\"sip:%s\" allow=\"%s\"/></action>\r\n",
5157 session
->bid
< bid
? "true" : "false");
5158 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
5160 } else if (xn_set_rm
) {
5162 const char *rm
= sipe_xml_attribute(xn_set_rm
, "uri");
5163 g_free(session
->roster_manager
);
5164 session
->roster_manager
= g_strdup(rm
);
5166 body
= g_strdup_printf(
5167 "<?xml version=\"1.0\"?>\r\n"
5168 "<action xmlns=\"http://schemas.microsoft.com/sip/multiparty/\">"
5169 "<SetRMResponse uri=\"sip:%s\"/></action>\r\n",
5171 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
5174 sipe_xml_free(xn_action
);
5179 /* looks like purple lacks typing notification for chat */
5180 if (!session
->is_multiparty
&& !session
->focus_uri
) {
5181 sipe_xml
*xn_keyboard_activity
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
5182 const char *status
= sipe_xml_attribute(sipe_xml_child(xn_keyboard_activity
, "status"),
5184 if (sipe_strequal(status
, "type")) {
5185 serv_got_typing(sip
->gc
, from
, SIPE_TYPING_RECV_TIMEOUT
, PURPLE_TYPING
);
5186 } else if (sipe_strequal(status
, "idle")) {
5187 serv_got_typing_stopped(sip
->gc
, from
);
5189 sipe_xml_free(xn_keyboard_activity
);
5192 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
5197 static void process_incoming_bye(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
5199 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
5200 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
5201 struct sip_session
*session
;
5202 struct sip_dialog
*dialog
;
5204 /* collect dialog identification
5205 * we need callid, ourtag and theirtag to unambiguously identify dialog
5207 /* take data before 'msg' will be modified by send_sip_response */
5208 dialog
= g_new0(struct sip_dialog
, 1);
5209 dialog
->callid
= g_strdup(callid
);
5210 dialog
->cseq
= parse_cseq(sipmsg_find_header(msg
, "CSeq"));
5211 dialog
->with
= g_strdup(from
);
5212 sipe_dialog_parse(dialog
, msg
, FALSE
);
5214 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
5216 session
= sipe_session_find_chat_by_callid(sip
, callid
);
5218 session
= sipe_session_find_im(sip
, from
);
5221 sipe_dialog_free(dialog
);
5226 if (session
->roster_manager
&& !g_strcasecmp(from
, session
->roster_manager
)) {
5227 g_free(session
->roster_manager
);
5228 session
->roster_manager
= NULL
;
5231 /* This what BYE is essentially for - terminating dialog */
5232 sipe_dialog_remove_3(session
, dialog
);
5233 sipe_dialog_free(dialog
);
5234 if (session
->focus_uri
&& !g_strcasecmp(from
, session
->im_mcu_uri
)) {
5235 sipe_conf_immcu_closed(sip
, session
);
5236 } else if (session
->is_multiparty
) {
5237 purple_conv_chat_remove_user(PURPLE_CONV_CHAT(session
->conv
), from
, NULL
);
5243 static void process_incoming_refer(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
5245 gchar
*self
= sip_uri_self(sip
);
5246 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
5247 gchar
*from
= parse_from(sipmsg_find_header(msg
, "From"));
5248 gchar
*refer_to
= parse_from(sipmsg_find_header(msg
, "Refer-to"));
5249 gchar
*referred_by
= g_strdup(sipmsg_find_header(msg
, "Referred-By"));
5250 struct sip_session
*session
;
5251 struct sip_dialog
*dialog
;
5253 session
= sipe_session_find_chat_by_callid(sip
, callid
);
5254 dialog
= sipe_dialog_find(session
, from
);
5256 if (!session
|| !dialog
|| !session
->roster_manager
|| !sipe_strcase_equal(session
->roster_manager
, self
)) {
5257 send_sip_response(sip
->gc
, msg
, 500, "Server Internal Error", NULL
);
5259 send_sip_response(sip
->gc
, msg
, 202, "Accepted", NULL
);
5261 sipe_invite(sip
, session
, refer_to
, NULL
, NULL
, referred_by
, FALSE
);
5267 g_free(referred_by
);
5271 sipe_send_typing(PurpleConnection
*gc
, const char *who
, PurpleTypingState state
)
5273 struct sipe_account_data
*sip
= (struct sipe_account_data
*)gc
->proto_data
;
5274 struct sip_session
*session
;
5275 struct sip_dialog
*dialog
;
5277 if (state
== PURPLE_NOT_TYPING
)
5280 session
= sipe_session_find_im(sip
, who
);
5281 dialog
= sipe_dialog_find(session
, who
);
5283 if (session
&& dialog
&& dialog
->is_established
) {
5284 send_sip_request(gc
, "INFO", who
, who
,
5285 "Content-Type: application/xml\r\n",
5286 SIPE_SEND_TYPING
, dialog
, NULL
);
5288 return SIPE_TYPING_SEND_TIMEOUT
;
5291 static gboolean
resend_timeout(struct sipe_account_data
*sip
)
5293 GSList
*tmp
= sip
->transactions
;
5294 time_t currtime
= time(NULL
);
5296 struct transaction
*trans
= tmp
->data
;
5298 SIPE_DEBUG_INFO("have open transaction age: %ld", (long int)currtime
-trans
->time
);
5299 if ((currtime
- trans
->time
> 5) && trans
->retries
>= 1) {
5302 if ((currtime
- trans
->time
> 2) && trans
->retries
== 0) {
5304 sendout_sipmsg(sip
, trans
->msg
);
5311 static void do_reauthenticate_cb(struct sipe_account_data
*sip
,
5312 SIPE_UNUSED_PARAMETER
void *unused
)
5314 /* register again when security token expires */
5315 /* we have to start a new authentication as the security token
5316 * is almost expired by sending a not signed REGISTER message */
5317 SIPE_DEBUG_INFO_NOFORMAT("do a full reauthentication");
5318 sipe_auth_free(&sip
->registrar
);
5319 sipe_auth_free(&sip
->proxy
);
5320 sip
->registerstatus
= 0;
5322 sip
->reauthenticate_set
= FALSE
;
5326 sipe_process_incoming_x_msmsgsinvite(struct sipe_account_data
*sip
,
5328 GSList
*parsed_body
)
5330 gboolean found
= FALSE
;
5333 const gchar
*invitation_command
= sipe_utils_nameval_find(parsed_body
, "Invitation-Command");
5335 if (sipe_strequal(invitation_command
, "INVITE")) {
5336 sipe_ft_incoming_transfer(sip
->gc
->account
, msg
, parsed_body
);
5338 } else if (sipe_strequal(invitation_command
, "CANCEL")) {
5339 sipe_ft_incoming_cancel(sip
->gc
->account
, parsed_body
);
5341 } else if (sipe_strequal(invitation_command
, "ACCEPT")) {
5342 sipe_ft_incoming_accept(sip
->gc
->account
, parsed_body
);
5349 static void process_incoming_message(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
5352 const gchar
*contenttype
;
5353 gboolean found
= FALSE
;
5355 from
= parse_from(sipmsg_find_header(msg
, "From"));
5359 SIPE_DEBUG_INFO("got message from %s: %s", from
, msg
->body
);
5361 contenttype
= sipmsg_find_header(msg
, "Content-Type");
5362 if (g_str_has_prefix(contenttype
, "text/plain")
5363 || g_str_has_prefix(contenttype
, "text/html")
5364 || g_str_has_prefix(contenttype
, "multipart/related")
5365 || g_str_has_prefix(contenttype
, "multipart/alternative"))
5367 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
5368 gchar
*html
= get_html_message(contenttype
, msg
->body
);
5370 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
5372 session
= sipe_session_find_im(sip
, from
);
5375 if (session
&& session
->focus_uri
) { /* a conference */
5376 gchar
*tmp
= parse_from(sipmsg_find_header(msg
, "Ms-Sender"));
5377 gchar
*sender
= parse_from(tmp
);
5379 serv_got_chat_in(sip
->gc
, session
->chat_id
, sender
,
5380 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
5382 } else if (session
&& session
->is_multiparty
) { /* a multiparty chat */
5383 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
5384 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
5386 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
5389 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
5392 } else if (g_str_has_prefix(contenttype
, "application/im-iscomposing+xml")) {
5393 sipe_xml
*isc
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
5394 const sipe_xml
*state
;
5398 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: can not parse iscomposing");
5403 state
= sipe_xml_child(isc
, "state");
5406 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_message: no state found");
5412 statedata
= sipe_xml_data(state
);
5414 if (strstr(statedata
, "active")) serv_got_typing(sip
->gc
, from
, 0, PURPLE_TYPING
);
5415 else serv_got_typing_stopped(sip
->gc
, from
);
5420 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
5422 } else if (g_str_has_prefix(contenttype
, "text/x-msmsgsinvite")) {
5423 GSList
*body
= sipe_ft_parse_msg_body(msg
->body
);
5424 found
= sipe_process_incoming_x_msmsgsinvite(sip
, msg
, body
);
5425 sipe_utils_nameval_free(body
);
5427 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
5431 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
5432 struct sip_session
*session
= sipe_session_find_chat_by_callid(sip
, callid
);
5434 session
= sipe_session_find_im(sip
, from
);
5437 gchar
*errmsg
= g_strdup_printf(_("Received a message with unrecognized contents from %s"),
5439 sipe_present_err(sip
, session
, errmsg
);
5443 SIPE_DEBUG_INFO("got unknown mime-type '%s'", contenttype
);
5444 send_sip_response(sip
->gc
, msg
, 415, "Unsupported media type", NULL
);
5449 static void process_incoming_invite(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
5453 const gchar
*oldHeader
;
5455 gboolean is_multiparty
= FALSE
;
5456 gboolean is_triggered
= FALSE
;
5457 gboolean was_multiparty
= TRUE
;
5458 gboolean just_joined
= FALSE
;
5460 const gchar
*callid
= sipmsg_find_header(msg
, "Call-ID");
5461 const gchar
*roster_manager
= sipmsg_find_header(msg
, "Roster-Manager");
5462 const gchar
*end_points_hdr
= sipmsg_find_header(msg
, "EndPoints");
5463 const gchar
*trig_invite
= sipmsg_find_header(msg
, "TriggeredInvite");
5464 const gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
5465 GSList
*end_points
= NULL
;
5467 struct sip_session
*session
;
5468 const gchar
*ms_text_format
;
5470 SIPE_DEBUG_INFO("process_incoming_invite: body:\n%s!", msg
->body
? tmp
= fix_newlines(msg
->body
) : "");
5473 /* Invitation to join conference */
5474 if (g_str_has_prefix(content_type
, "application/ms-conf-invite+xml")) {
5475 process_incoming_invite_conf(sip
, msg
);
5479 /* Only accept text invitations */
5480 if (msg
->body
&& !(strstr(msg
->body
, "m=message") || strstr(msg
->body
, "m=x-ms-message"))) {
5481 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
5485 // TODO There *must* be a better way to clean up the To header to add a tag...
5486 SIPE_DEBUG_INFO_NOFORMAT("Adding a Tag to the To Header on Invite Request...");
5487 oldHeader
= sipmsg_find_header(msg
, "To");
5489 newHeader
= g_strdup_printf("%s;tag=%s", oldHeader
, newTag
);
5490 sipmsg_remove_header_now(msg
, "To");
5491 sipmsg_add_header_now(msg
, "To", newHeader
);
5494 if (end_points_hdr
) {
5495 end_points
= sipmsg_parse_endpoints_header(end_points_hdr
);
5497 if (g_slist_length(end_points
) > 2) {
5498 is_multiparty
= TRUE
;
5501 if (trig_invite
&& !g_strcasecmp(trig_invite
, "TRUE")) {
5502 is_triggered
= TRUE
;
5503 is_multiparty
= TRUE
;
5506 session
= sipe_session_find_chat_by_callid(sip
, callid
);
5507 /* Convert to multiparty */
5508 if (session
&& is_multiparty
&& !session
->is_multiparty
) {
5509 g_free(session
->with
);
5510 session
->with
= NULL
;
5511 was_multiparty
= FALSE
;
5512 session
->is_multiparty
= TRUE
;
5513 session
->chat_id
= rand();
5516 if (!session
&& is_multiparty
) {
5517 session
= sipe_session_find_or_add_chat_by_callid(sip
, callid
);
5520 from
= parse_from(sipmsg_find_header(msg
, "From"));
5522 session
= sipe_session_find_or_add_im(sip
, from
);
5526 g_free(session
->callid
);
5527 session
->callid
= g_strdup(callid
);
5529 session
->is_multiparty
= is_multiparty
;
5530 if (roster_manager
) {
5531 session
->roster_manager
= g_strdup(roster_manager
);
5535 if (is_multiparty
&& end_points
) {
5536 gchar
*to
= parse_from(sipmsg_find_header(msg
, "To"));
5537 GSList
*entry
= end_points
;
5539 struct sip_dialog
*dialog
;
5540 struct sipendpoint
*end_point
= entry
->data
;
5541 entry
= entry
->next
;
5543 if (!g_strcasecmp(from
, end_point
->contact
) ||
5544 !g_strcasecmp(to
, end_point
->contact
))
5547 dialog
= sipe_dialog_find(session
, end_point
->contact
);
5549 g_free(dialog
->theirepid
);
5550 dialog
->theirepid
= end_point
->epid
;
5551 end_point
->epid
= NULL
;
5553 dialog
= sipe_dialog_add(session
);
5555 dialog
->callid
= g_strdup(session
->callid
);
5556 dialog
->with
= end_point
->contact
;
5557 end_point
->contact
= NULL
;
5558 dialog
->theirepid
= end_point
->epid
;
5559 end_point
->epid
= NULL
;
5563 /* send triggered INVITE */
5564 sipe_invite(sip
, session
, dialog
->with
, NULL
, NULL
, NULL
, TRUE
);
5571 GSList
*entry
= end_points
;
5573 struct sipendpoint
*end_point
= entry
->data
;
5574 entry
= entry
->next
;
5575 g_free(end_point
->contact
);
5576 g_free(end_point
->epid
);
5579 g_slist_free(end_points
);
5583 struct sip_dialog
*dialog
= sipe_dialog_find(session
, from
);
5585 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, session already has dialog!");
5586 sipe_dialog_parse_routes(dialog
, msg
, FALSE
);
5588 dialog
= sipe_dialog_add(session
);
5590 dialog
->callid
= g_strdup(session
->callid
);
5591 dialog
->with
= g_strdup(from
);
5592 sipe_dialog_parse(dialog
, msg
, FALSE
);
5594 if (!dialog
->ourtag
) {
5595 dialog
->ourtag
= newTag
;
5602 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_invite, failed to find or create IM session");
5606 if (is_multiparty
&& !session
->conv
) {
5607 gchar
*chat_title
= sipe_chat_get_name(callid
);
5608 gchar
*self
= sip_uri_self(sip
);
5609 /* create prpl chat */
5610 session
->conv
= serv_got_joined_chat(sip
->gc
, session
->chat_id
, chat_title
);
5611 session
->chat_title
= g_strdup(chat_title
);
5612 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session
->conv
), self
);
5614 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
5616 PURPLE_CBFLAGS_NONE
, FALSE
);
5621 if (is_multiparty
&& !was_multiparty
) {
5622 /* add current IM counterparty to chat */
5623 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
5624 sipe_dialog_first(session
)->with
, NULL
,
5625 PURPLE_CBFLAGS_NONE
, FALSE
);
5628 /* add inviting party to chat */
5629 if (just_joined
&& session
->conv
) {
5630 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
),
5632 PURPLE_CBFLAGS_NONE
, TRUE
);
5635 /* ms-text-format: text/plain; charset=UTF-8;msgr=WAAtAE0...DIADQAKAA0ACgA;ms-body=SGk= */
5637 /* This used only in 2005 official client, not 2007 or Reuters.
5638 Disabled for most cases as interfering with audit of messages which only is applied to regular MESSAGEs.
5639 Only enabled for 2005 multiparty chats as otherwise the first message got lost completely.
5641 /* also enabled for 2005 file transfer. Didn't work otherwise. */
5642 ms_text_format
= sipmsg_find_header(msg
, "ms-text-format");
5643 if (is_multiparty
||
5644 (ms_text_format
&& g_str_has_prefix(ms_text_format
, "text/x-msmsgsinvite")) )
5646 if (ms_text_format
) {
5647 if (g_str_has_prefix(ms_text_format
, "text/x-msmsgsinvite"))
5649 gchar
*tmp
= sipmsg_find_part_of_header(ms_text_format
, "ms-body=", NULL
, NULL
);
5652 gchar
*body
= (gchar
*) g_base64_decode(tmp
, &len
);
5654 GSList
*parsed_body
= sipe_ft_parse_msg_body(body
);
5656 sipe_process_incoming_x_msmsgsinvite(sip
, msg
, parsed_body
);
5657 sipe_utils_nameval_free(parsed_body
);
5658 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
5662 else if (g_str_has_prefix(ms_text_format
, "text/plain") || g_str_has_prefix(ms_text_format
, "text/html"))
5664 /* please do not optimize logic inside as this code may be re-enabled for other cases */
5665 gchar
*html
= get_html_message(ms_text_format
, NULL
);
5667 if (is_multiparty
) {
5668 serv_got_chat_in(sip
->gc
, session
->chat_id
, from
,
5669 PURPLE_MESSAGE_RECV
, html
, time(NULL
));
5671 serv_got_im(sip
->gc
, from
, html
, 0, time(NULL
));
5674 sipmsg_add_header(msg
, "Supported", "ms-text-format"); /* accepts received message */
5682 sipmsg_add_header(msg
, "Supported", "com.microsoft.rtc-multiparty");
5683 sipmsg_add_header(msg
, "User-Agent", sipe_get_useragent(sip
));
5684 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
5686 body
= g_strdup_printf(
5688 "o=- 0 0 IN IP4 %s\r\n"
5692 "m=%s %d sip sip:%s\r\n"
5693 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
5694 sipe_backend_network_ip_address(),
5695 sipe_backend_network_ip_address(),
5696 sip
->ocs2007
? "message" : "x-ms-message",
5699 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
5703 static void process_incoming_options(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
5707 sipmsg_add_header(msg
, "Allow", "INVITE, MESSAGE, INFO, SUBSCRIBE, OPTIONS, BYE, CANCEL, NOTIFY, ACK, REFER, BENOTIFY");
5708 sipmsg_add_header(msg
, "User-Agent", sipe_get_useragent(sip
));
5709 sipmsg_add_header(msg
, "Content-Type", "application/sdp");
5711 body
= g_strdup_printf(
5713 "o=- 0 0 IN IP4 0.0.0.0\r\n"
5715 "c=IN IP4 0.0.0.0\r\n"
5717 "m=%s %d sip sip:%s\r\n"
5718 "a=accept-types:" SDP_ACCEPT_TYPES
"\r\n",
5719 sip
->ocs2007
? "message" : "x-ms-message",
5722 send_sip_response(sip
->gc
, msg
, 200, "OK", body
);
5727 sipe_get_auth_scheme_name(struct sipe_account_data
*sip
)
5729 const char *res
= "NTLM";
5731 if (purple_account_get_bool(sip
->account
, "krb5", FALSE
)) {
5735 (void) sip
; /* make compiler happy */
5740 static void sipe_connection_cleanup(struct sipe_account_data
*);
5741 static void create_connection(struct sipe_account_data
*, gchar
*, int);
5743 gboolean
process_register_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
5744 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
5747 const gchar
*expires_header
;
5749 GSList
*hdr
= msg
->headers
;
5750 struct sipnameval
*elem
;
5752 expires_header
= sipmsg_find_header(msg
, "Expires");
5753 expires
= expires_header
!= NULL
? strtol(expires_header
, NULL
, 10) : 0;
5754 SIPE_DEBUG_INFO("process_register_response: got response to REGISTER; expires = %d", expires
);
5756 switch (msg
->response
) {
5759 sip
->registerstatus
= 0;
5761 const gchar
*contact_hdr
;
5766 const gchar
*server_hdr
= sipmsg_find_header(msg
, "Server");
5767 const char *auth_scheme
;
5769 if (!sip
->reregister_set
) {
5770 gchar
*action_name
= g_strdup_printf("<%s>", "registration");
5771 sipe_schedule_action(action_name
, expires
, do_register_cb
, NULL
, sip
, NULL
);
5772 g_free(action_name
);
5773 sip
->reregister_set
= TRUE
;
5776 sip
->registerstatus
= 3;
5778 if (server_hdr
&& !sip
->server_version
) {
5779 sip
->server_version
= g_strdup(server_hdr
);
5784 auth_scheme
= sipe_get_auth_scheme_name(sip
);
5785 tmp
= sipmsg_find_auth_header(msg
, auth_scheme
);
5788 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp
);
5789 fill_auth(tmp
, &sip
->registrar
);
5792 if (!sip
->reauthenticate_set
) {
5793 gchar
*action_name
= g_strdup_printf("<%s>", "+reauthentication");
5794 guint reauth_timeout
;
5795 if (sip
->registrar
.type
== AUTH_TYPE_KERBEROS
&& sip
->registrar
.expires
> 0) {
5796 /* assuming normal Kerberos ticket expiration of about 8-10 hours */
5797 reauth_timeout
= sip
->registrar
.expires
- 300;
5799 /* NTLM: we have to reauthenticate as our security token expires
5800 after eight hours (be five minutes early) */
5801 reauth_timeout
= (8 * 3600) - 300;
5803 sipe_schedule_action(action_name
, reauth_timeout
, do_reauthenticate_cb
, NULL
, sip
, NULL
);
5804 g_free(action_name
);
5805 sip
->reauthenticate_set
= TRUE
;
5808 purple_connection_set_state(sip
->gc
, PURPLE_CONNECTED
);
5810 epid
= get_epid(sip
);
5811 uuid
= generateUUIDfromEPID(epid
);
5814 // There can be multiple Contact headers (one per location where the user is logged in) so
5815 // make sure to only get the one for this uuid
5816 for (i
= 0; (contact_hdr
= sipmsg_find_header_instance (msg
, "Contact", i
)); i
++) {
5817 gchar
* valid_contact
= sipmsg_find_part_of_header (contact_hdr
, uuid
, NULL
, NULL
);
5818 if (valid_contact
) {
5819 gruu
= sipmsg_find_part_of_header(contact_hdr
, "gruu=\"", "\"", NULL
);
5820 //SIPE_DEBUG_INFO("got gruu %s from contact hdr w/ right uuid: %s", gruu, contact_hdr);
5821 g_free(valid_contact
);
5824 //SIPE_DEBUG_INFO("ignoring contact hdr b/c not right uuid: %s", contact_hdr);
5829 g_free(sip
->contact
);
5831 sip
->contact
= g_strdup_printf("<%s>", gruu
);
5834 //SIPE_DEBUG_INFO_NOFORMAT("didn't find gruu in a Contact hdr");
5835 sip
->contact
= g_strdup_printf("<sip:%s:%d;maddr=%s;transport=%s>;proxy=replace", sip
->username
, sip
->listenport
, sipe_backend_network_ip_address(), TRANSPORT_DESCRIPTOR
);
5837 sip
->ocs2007
= FALSE
;
5838 sip
->batched_support
= FALSE
;
5843 if (sipe_strcase_equal(elem
->name
, "Supported")) {
5844 if (sipe_strcase_equal(elem
->value
, "msrtc-event-categories")) {
5845 /* We interpret this as OCS2007+ indicator */
5846 sip
->ocs2007
= TRUE
;
5847 SIPE_DEBUG_INFO("Supported: %s (indicates OCS2007+)", elem
->value
);
5849 if (sipe_strcase_equal(elem
->value
, "adhoclist")) {
5850 sip
->batched_support
= TRUE
;
5851 SIPE_DEBUG_INFO("Supported: %s", elem
->value
);
5854 if (sipe_strcase_equal(elem
->name
, "Allow-Events")){
5855 gchar
**caps
= g_strsplit(elem
->value
,",",0);
5858 sip
->allow_events
= g_slist_append(sip
->allow_events
, g_strdup(caps
[i
]));
5859 SIPE_DEBUG_INFO("Allow-Events: %s", caps
[i
]);
5864 hdr
= g_slist_next(hdr
);
5867 /* rejoin open chats to be able to use them by continue to send messages */
5868 purple_conversation_foreach(sipe_rejoin_chat
);
5871 if (!sip
->subscribed
) { //do it just once, not every re-register
5873 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-contacts",
5874 (GCompareFunc
)g_ascii_strcasecmp
)) {
5875 sipe_subscribe_roaming_contacts(sip
);
5878 /* For 2007+ it does not make sence to subscribe to:
5879 * vnd-microsoft-roaming-ACL
5880 * vnd-microsoft-provisioning (not v2)
5882 * These are for backward compatibility.
5886 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-self",
5887 (GCompareFunc
)g_ascii_strcasecmp
)) {
5888 sipe_subscribe_roaming_self(sip
);
5890 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-provisioning-v2",
5891 (GCompareFunc
)g_ascii_strcasecmp
)) {
5892 sipe_subscribe_roaming_provisioning_v2(sip
);
5895 /* For 2005- servers */
5898 //sipe_options_request(sip, sip->sipdomain);
5900 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-roaming-ACL",
5901 (GCompareFunc
)g_ascii_strcasecmp
)) {
5902 sipe_subscribe_roaming_acl(sip
);
5904 if (g_slist_find_custom(sip
->allow_events
, "vnd-microsoft-provisioning",
5905 (GCompareFunc
)g_ascii_strcasecmp
)) {
5906 sipe_subscribe_roaming_provisioning(sip
);
5908 if (g_slist_find_custom(sip
->allow_events
, "presence.wpending",
5909 (GCompareFunc
)g_ascii_strcasecmp
)) {
5910 sipe_subscribe_presence_wpending(sip
, msg
);
5913 /* For 2007+ we publish our initial statuses and calendar data only after
5914 * received our existing publications in sipe_process_roaming_self()
5915 * Only in this case we know versions of current publications made
5918 /* For 2005- we publish our initial statuses only after
5919 * received our existing UserInfo data in response to
5920 * self subscription.
5921 * Only in this case we won't override existing UserInfo data
5922 * set earlier or by other client on our behalf.
5926 sip
->subscribed
= TRUE
;
5929 timeout
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "ms-keep-alive"),
5930 "timeout=", ";", NULL
);
5931 if (timeout
!= NULL
) {
5932 sscanf(timeout
, "%u", &sip
->keepalive_timeout
);
5933 SIPE_DEBUG_INFO("server determined keep alive timeout is %u seconds",
5934 sip
->keepalive_timeout
);
5938 SIPE_DEBUG_INFO("process_register_response - got 200, removing CSeq: %d", sip
->cseq
);
5943 gchar
*redirect
= parse_from(sipmsg_find_header(msg
, "Contact"));
5945 if (redirect
&& (g_strncasecmp("sip:", redirect
, 4) == 0)) {
5946 gchar
**parts
= g_strsplit(redirect
+ 4, ";", 0);
5950 sipe_transport_type transport
= SIPE_TRANSPORT_TLS
;
5953 tmp
= g_strsplit(parts
[0], ":", 0);
5954 hostname
= g_strdup(tmp
[0]);
5955 if (tmp
[1]) port
= strtoul(tmp
[1], NULL
, 10);
5959 tmp
= g_strsplit(parts
[i
], "=", 0);
5961 if (g_strcasecmp("transport", tmp
[0]) == 0) {
5962 if (g_strcasecmp("tcp", tmp
[1]) == 0) {
5963 transport
= SIPE_TRANSPORT_TCP
;
5964 } else if (g_strcasecmp("udp", tmp
[1]) == 0) {
5965 transport
= SIPE_TRANSPORT_UDP
;
5974 /* Close old connection */
5975 sipe_connection_cleanup(sip
);
5977 /* Create new connection */
5978 sip
->transport
= transport
;
5979 SIPE_DEBUG_INFO("process_register_response: redirected to host %s port %d transport %s",
5980 hostname
, port
, TRANSPORT_DESCRIPTOR
);
5981 create_connection(sip
, hostname
, port
);
5987 if (sip
->registerstatus
!= 2) {
5988 const char *auth_scheme
;
5989 SIPE_DEBUG_INFO("REGISTER retries %d", sip
->registrar
.retries
);
5990 if (sip
->registrar
.retries
> 3) {
5991 sip
->gc
->wants_to_die
= TRUE
;
5992 purple_connection_error(sip
->gc
, _("Authentication failed"));
5996 auth_scheme
= sipe_get_auth_scheme_name(sip
);
5997 tmp
= sipmsg_find_auth_header(msg
, auth_scheme
);
5999 SIPE_DEBUG_INFO("process_register_response - Auth header: %s", tmp
? tmp
: "");
6001 char *tmp2
= g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme
, NULL
);
6002 sip
->gc
->wants_to_die
= TRUE
;
6003 purple_connection_error(sip
->gc
, tmp2
);
6007 fill_auth(tmp
, &sip
->registrar
);
6008 sip
->registerstatus
= 2;
6009 if (sip
->account
->disconnecting
) {
6010 do_register_exp(sip
, 0);
6018 const gchar
*diagnostics
= sipmsg_find_header(msg
, "Warning");
6019 gchar
**reason
= NULL
;
6021 if (diagnostics
!= NULL
) {
6023 Warning: 310 lcs.microsoft.com "You are currently not using the recommended version of the client"
6025 reason
= g_strsplit(diagnostics
, "\"", 0);
6027 warning
= g_strdup_printf(_("You have been rejected by the server: %s"),
6028 (reason
&& reason
[1]) ? reason
[1] : _("no reason given"));
6031 sip
->gc
->wants_to_die
= TRUE
;
6032 purple_connection_error(sip
->gc
, warning
);
6039 const gchar
*diagnostics
= sipmsg_find_header(msg
, "ms-diagnostics");
6040 gchar
*reason
= NULL
;
6042 if (diagnostics
!= NULL
) {
6043 reason
= sipmsg_find_part_of_header(diagnostics
, "reason=\"", "\"", NULL
);
6045 warning
= g_strdup_printf(_("Not found: %s. Please contact your Administrator"),
6046 diagnostics
? (reason
? reason
: _("no reason given")) :
6047 _("SIP is either not enabled for the destination URI or it does not exist"));
6050 sip
->gc
->wants_to_die
= TRUE
;
6051 purple_connection_error(sip
->gc
, warning
);
6057 case 504: /* Server time-out */
6059 const gchar
*diagnostics
= sipmsg_find_header(msg
, "ms-diagnostics");
6060 gchar
*reason
= NULL
;
6062 if (diagnostics
!= NULL
) {
6063 reason
= sipmsg_find_part_of_header(diagnostics
, "reason=\"", "\"", NULL
);
6065 warning
= g_strdup_printf(_("Service unavailable: %s"), reason
? reason
: _("no reason given"));
6068 sip
->gc
->wants_to_die
= TRUE
;
6069 purple_connection_error(sip
->gc
, warning
);
6079 * Returns 2005-style activity and Availability.
6081 * @param status Sipe statis id.
6084 sipe_get_act_avail_by_status_2005(const char *status
,
6088 int avail
= 300; /* online */
6089 int act
= 400; /* Available */
6091 if (sipe_strequal(status
, SIPE_STATUS_ID_AWAY
)) {
6093 //} else if (sipe_strequal(status, SIPE_STATUS_ID_LUNCH)) {
6095 } else if (sipe_strequal(status
, SIPE_STATUS_ID_BRB
)) {
6097 } else if (sipe_strequal(status
, SIPE_STATUS_ID_AVAILABLE
)) {
6099 //} else if (sipe_strequal(status, SIPE_STATUS_ID_ON_PHONE)) {
6101 } else if (sipe_strequal(status
, SIPE_STATUS_ID_BUSY
) ||
6102 sipe_strequal(status
, SIPE_STATUS_ID_DND
)) {
6104 } else if (sipe_strequal(status
, SIPE_STATUS_ID_INVISIBLE
) ||
6105 sipe_strequal(status
, SIPE_STATUS_ID_OFFLINE
)) {
6106 avail
= 0; /* offline */
6109 act
= 400; /* Available */
6112 if (activity
) *activity
= act
;
6113 if (availability
) *availability
= avail
;
6119 * @param activity 2005 aggregated activity. Ex.: 600
6120 * @param availablity 2005 aggregated availablity. Ex.: 300
6123 sipe_get_status_by_act_avail_2005(const int activity
,
6124 const int availablity
,
6125 char **activity_desc
)
6127 const char *status_id
= NULL
;
6128 const char *act
= NULL
;
6130 if (activity
< 150) {
6131 status_id
= SIPE_STATUS_ID_AWAY
;
6132 } else if (activity
< 200) {
6133 //status_id = SIPE_STATUS_ID_LUNCH;
6134 status_id
= SIPE_STATUS_ID_AWAY
;
6135 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_LUNCH
);
6136 } else if (activity
< 300) {
6137 //status_id = SIPE_STATUS_ID_IDLE;
6138 status_id
= SIPE_STATUS_ID_AWAY
;
6139 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE
);
6140 } else if (activity
< 400) {
6141 status_id
= SIPE_STATUS_ID_BRB
;
6142 } else if (activity
< 500) {
6143 status_id
= SIPE_STATUS_ID_AVAILABLE
;
6144 } else if (activity
< 600) {
6145 //status_id = SIPE_STATUS_ID_ON_PHONE;
6146 status_id
= SIPE_STATUS_ID_BUSY
;
6147 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE
);
6148 } else if (activity
< 700) {
6149 status_id
= SIPE_STATUS_ID_BUSY
;
6150 } else if (activity
< 800) {
6151 status_id
= SIPE_STATUS_ID_AWAY
;
6153 status_id
= SIPE_STATUS_ID_AVAILABLE
;
6156 if (availablity
< 100)
6157 status_id
= SIPE_STATUS_ID_OFFLINE
;
6159 if (activity_desc
&& act
) {
6160 g_free(*activity_desc
);
6161 *activity_desc
= g_strdup(act
);
6168 * [MS-PRES] Table 3: Conversion of legacyInterop elements and attributes to MSRTC elements and attributes.
6171 sipe_get_status_by_availability(int avail
,
6172 char** activity_desc
)
6175 const char *act
= NULL
;
6178 status
= SIPE_STATUS_ID_OFFLINE
;
6179 } else if (avail
< 4500) {
6180 status
= SIPE_STATUS_ID_AVAILABLE
;
6181 } else if (avail
< 6000) {
6182 //status = SIPE_STATUS_ID_IDLE;
6183 status
= SIPE_STATUS_ID_AVAILABLE
;
6184 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_INACTIVE
);
6185 } else if (avail
< 7500) {
6186 status
= SIPE_STATUS_ID_BUSY
;
6187 } else if (avail
< 9000) {
6188 //status = SIPE_STATUS_ID_BUSYIDLE;
6189 status
= SIPE_STATUS_ID_BUSY
;
6190 act
= SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_BUSYIDLE
);
6191 } else if (avail
< 12000) {
6192 status
= SIPE_STATUS_ID_DND
;
6193 } else if (avail
< 15000) {
6194 status
= SIPE_STATUS_ID_BRB
;
6195 } else if (avail
< 18000) {
6196 status
= SIPE_STATUS_ID_AWAY
;
6198 status
= SIPE_STATUS_ID_OFFLINE
;
6201 if (activity_desc
&& act
) {
6202 g_free(*activity_desc
);
6203 *activity_desc
= g_strdup(act
);
6210 * Returns 2007-style availability value
6212 * @param sipe_status_id (in)
6213 * @param activity_token (out) Must be g_free()'d after use if consumed.
6216 sipe_get_availability_by_status(const char* sipe_status_id
, char** activity_token
)
6219 sipe_activity activity
= SIPE_ACTIVITY_UNSET
;
6221 if (sipe_strequal(sipe_status_id
, SIPE_STATUS_ID_AWAY
)) {
6222 availability
= 15500;
6223 if (!activity_token
|| !(*activity_token
)) {
6224 activity
= SIPE_ACTIVITY_AWAY
;
6226 } else if (sipe_strequal(sipe_status_id
, SIPE_STATUS_ID_BRB
)) {
6227 availability
= 12500;
6228 activity
= SIPE_ACTIVITY_BRB
;
6229 } else if (sipe_strequal(sipe_status_id
, SIPE_STATUS_ID_DND
)) {
6230 availability
= 9500;
6231 activity
= SIPE_ACTIVITY_DND
;
6232 } else if (sipe_strequal(sipe_status_id
, SIPE_STATUS_ID_BUSY
)) {
6233 availability
= 6500;
6234 if (!activity_token
|| !(*activity_token
)) {
6235 activity
= SIPE_ACTIVITY_BUSY
;
6237 } else if (sipe_strequal(sipe_status_id
, SIPE_STATUS_ID_AVAILABLE
)) {
6238 availability
= 3500;
6239 activity
= SIPE_ACTIVITY_ONLINE
;
6240 } else if (sipe_strequal(sipe_status_id
, SIPE_STATUS_ID_UNKNOWN
)) {
6243 // Offline or invisible
6244 availability
= 18500;
6245 activity
= SIPE_ACTIVITY_OFFLINE
;
6248 if (activity_token
) {
6249 *activity_token
= g_strdup(sipe_activity_map
[activity
].token
);
6251 return availability
;
6254 static void process_incoming_notify_rlmi(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
6257 sipe_xml
*xn_categories
;
6258 const sipe_xml
*xn_category
;
6259 const char *status
= NULL
;
6260 gboolean do_update_status
= FALSE
;
6261 gboolean has_note_cleaned
= FALSE
;
6262 gboolean has_free_busy_cleaned
= FALSE
;
6264 xn_categories
= sipe_xml_parse(data
, len
);
6265 uri
= sipe_xml_attribute(xn_categories
, "uri"); /* with 'sip:' prefix */
6267 for (xn_category
= sipe_xml_child(xn_categories
, "category");
6269 xn_category
= sipe_xml_twin(xn_category
) )
6271 const sipe_xml
*xn_node
;
6273 const char *attrVar
= sipe_xml_attribute(xn_category
, "name");
6274 time_t publish_time
= (tmp
= sipe_xml_attribute(xn_category
, "publishTime")) ?
6275 sipe_utils_str_to_time(tmp
) : 0;
6278 if (sipe_strequal(attrVar
, "contactCard"))
6280 const sipe_xml
*card
= sipe_xml_child(xn_category
, "contactCard");
6283 const sipe_xml
*node
;
6284 /* identity - Display Name and email */
6285 node
= sipe_xml_child(card
, "identity");
6287 char* display_name
= sipe_xml_data(
6288 sipe_xml_child(node
, "name/displayName"));
6289 char* email
= sipe_xml_data(
6290 sipe_xml_child(node
, "email"));
6292 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
6293 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
6295 g_free(display_name
);
6299 node
= sipe_xml_child(card
, "company");
6301 char* company
= sipe_xml_data(node
);
6302 sipe_update_user_info(sip
, uri
, COMPANY_PROP
, company
);
6306 node
= sipe_xml_child(card
, "department");
6308 char* department
= sipe_xml_data(node
);
6309 sipe_update_user_info(sip
, uri
, DEPARTMENT_PROP
, department
);
6313 node
= sipe_xml_child(card
, "title");
6315 char* title
= sipe_xml_data(node
);
6316 sipe_update_user_info(sip
, uri
, TITLE_PROP
, title
);
6320 node
= sipe_xml_child(card
, "office");
6322 char* office
= sipe_xml_data(node
);
6323 sipe_update_user_info(sip
, uri
, OFFICE_PROP
, office
);
6327 node
= sipe_xml_child(card
, "url");
6329 char* site
= sipe_xml_data(node
);
6330 sipe_update_user_info(sip
, uri
, SITE_PROP
, site
);
6334 for (node
= sipe_xml_child(card
, "phone");
6336 node
= sipe_xml_twin(node
))
6338 const char *phone_type
= sipe_xml_attribute(node
, "type");
6339 char* phone
= sipe_xml_data(sipe_xml_child(node
, "uri"));
6340 char* phone_display_string
= sipe_xml_data(sipe_xml_child(node
, "displayString"));
6342 sipe_update_user_phone(sip
, uri
, phone_type
, phone
, phone_display_string
);
6345 g_free(phone_display_string
);
6348 for (node
= sipe_xml_child(card
, "address");
6350 node
= sipe_xml_twin(node
))
6352 if (sipe_strequal(sipe_xml_attribute(node
, "type"), "work")) {
6353 char* street
= sipe_xml_data(sipe_xml_child(node
, "street"));
6354 char* city
= sipe_xml_data(sipe_xml_child(node
, "city"));
6355 char* state
= sipe_xml_data(sipe_xml_child(node
, "state"));
6356 char* zipcode
= sipe_xml_data(sipe_xml_child(node
, "zipcode"));
6357 char* country_code
= sipe_xml_data(sipe_xml_child(node
, "countryCode"));
6359 sipe_update_user_info(sip
, uri
, ADDRESS_STREET_PROP
, street
);
6360 sipe_update_user_info(sip
, uri
, ADDRESS_CITY_PROP
, city
);
6361 sipe_update_user_info(sip
, uri
, ADDRESS_STATE_PROP
, state
);
6362 sipe_update_user_info(sip
, uri
, ADDRESS_ZIPCODE_PROP
, zipcode
);
6363 sipe_update_user_info(sip
, uri
, ADDRESS_COUNTRYCODE_PROP
, country_code
);
6369 g_free(country_code
);
6377 else if (sipe_strequal(attrVar
, "note"))
6380 struct sipe_buddy
*sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
6382 if (!has_note_cleaned
) {
6383 has_note_cleaned
= TRUE
;
6385 g_free(sbuddy
->note
);
6386 sbuddy
->note
= NULL
;
6387 sbuddy
->is_oof_note
= FALSE
;
6388 sbuddy
->note_since
= publish_time
;
6390 do_update_status
= TRUE
;
6392 if (sbuddy
&& (publish_time
>= sbuddy
->note_since
)) {
6393 /* clean up in case no 'note' element is supplied
6394 * which indicate note removal in client
6396 g_free(sbuddy
->note
);
6397 sbuddy
->note
= NULL
;
6398 sbuddy
->is_oof_note
= FALSE
;
6399 sbuddy
->note_since
= publish_time
;
6401 xn_node
= sipe_xml_child(xn_category
, "note/body");
6404 sbuddy
->note
= g_markup_escape_text((tmp
= sipe_xml_data(xn_node
)), -1);
6406 sbuddy
->is_oof_note
= sipe_strequal(sipe_xml_attribute(xn_node
, "type"), "OOF");
6407 sbuddy
->note_since
= publish_time
;
6409 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: uri(%s), note(%s)",
6410 uri
, sbuddy
->note
? sbuddy
->note
: "");
6412 /* to trigger UI refresh in case no status info is supplied in this update */
6413 do_update_status
= TRUE
;
6418 else if(sipe_strequal(attrVar
, "state"))
6422 const sipe_xml
*xn_availability
;
6423 const sipe_xml
*xn_activity
;
6424 const sipe_xml
*xn_meeting_subject
;
6425 const sipe_xml
*xn_meeting_location
;
6426 struct sipe_buddy
*sbuddy
= uri
? g_hash_table_lookup(sip
->buddies
, uri
) : NULL
;
6428 xn_node
= sipe_xml_child(xn_category
, "state");
6429 if (!xn_node
) continue;
6430 xn_availability
= sipe_xml_child(xn_node
, "availability");
6431 if (!xn_availability
) continue;
6432 xn_activity
= sipe_xml_child(xn_node
, "activity");
6433 xn_meeting_subject
= sipe_xml_child(xn_node
, "meetingSubject");
6434 xn_meeting_location
= sipe_xml_child(xn_node
, "meetingLocation");
6436 tmp
= sipe_xml_data(xn_availability
);
6437 availability
= atoi(tmp
);
6440 /* activity, meeting_subject, meeting_location */
6445 g_free(sbuddy
->activity
);
6446 sbuddy
->activity
= NULL
;
6448 const char *token
= sipe_xml_attribute(xn_activity
, "token");
6449 const sipe_xml
*xn_custom
= sipe_xml_child(xn_activity
, "custom");
6452 if (!is_empty(token
)) {
6453 sbuddy
->activity
= g_strdup(sipe_get_activity_desc_by_token(token
));
6455 /* from custom element */
6457 char *custom
= sipe_xml_data(xn_custom
);
6459 if (!is_empty(custom
)) {
6460 sbuddy
->activity
= custom
;
6466 /* meeting_subject */
6467 g_free(sbuddy
->meeting_subject
);
6468 sbuddy
->meeting_subject
= NULL
;
6469 if (xn_meeting_subject
) {
6470 char *meeting_subject
= sipe_xml_data(xn_meeting_subject
);
6472 if (!is_empty(meeting_subject
)) {
6473 sbuddy
->meeting_subject
= meeting_subject
;
6474 meeting_subject
= NULL
;
6476 g_free(meeting_subject
);
6478 /* meeting_location */
6479 g_free(sbuddy
->meeting_location
);
6480 sbuddy
->meeting_location
= NULL
;
6481 if (xn_meeting_location
) {
6482 char *meeting_location
= sipe_xml_data(xn_meeting_location
);
6484 if (!is_empty(meeting_location
)) {
6485 sbuddy
->meeting_location
= meeting_location
;
6486 meeting_location
= NULL
;
6488 g_free(meeting_location
);
6491 status
= sipe_get_status_by_availability(availability
, &tmp
);
6492 if (sbuddy
->activity
&& tmp
) {
6493 char *tmp2
= sbuddy
->activity
;
6495 sbuddy
->activity
= g_strdup_printf("%s, %s", sbuddy
->activity
, tmp
);
6499 sbuddy
->activity
= tmp
;
6503 do_update_status
= TRUE
;
6506 else if(sipe_strequal(attrVar
, "calendarData"))
6508 struct sipe_buddy
*sbuddy
= uri
? g_hash_table_lookup(sip
->buddies
, uri
) : NULL
;
6509 const sipe_xml
*xn_free_busy
= sipe_xml_child(xn_category
, "calendarData/freeBusy");
6510 const sipe_xml
*xn_working_hours
= sipe_xml_child(xn_category
, "calendarData/WorkingHours");
6512 if (sbuddy
&& xn_free_busy
) {
6513 if (!has_free_busy_cleaned
) {
6514 has_free_busy_cleaned
= TRUE
;
6516 g_free(sbuddy
->cal_start_time
);
6517 sbuddy
->cal_start_time
= NULL
;
6519 g_free(sbuddy
->cal_free_busy_base64
);
6520 sbuddy
->cal_free_busy_base64
= NULL
;
6522 g_free(sbuddy
->cal_free_busy
);
6523 sbuddy
->cal_free_busy
= NULL
;
6525 sbuddy
->cal_free_busy_published
= publish_time
;
6528 if (publish_time
>= sbuddy
->cal_free_busy_published
) {
6529 g_free(sbuddy
->cal_start_time
);
6530 sbuddy
->cal_start_time
= g_strdup(sipe_xml_attribute(xn_free_busy
, "startTime"));
6532 sbuddy
->cal_granularity
= sipe_strcase_equal(sipe_xml_attribute(xn_free_busy
, "granularity"), "PT15M") ?
6535 g_free(sbuddy
->cal_free_busy_base64
);
6536 sbuddy
->cal_free_busy_base64
= sipe_xml_data(xn_free_busy
);
6538 g_free(sbuddy
->cal_free_busy
);
6539 sbuddy
->cal_free_busy
= NULL
;
6541 sbuddy
->cal_free_busy_published
= publish_time
;
6543 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: startTime=%s granularity=%d cal_free_busy_base64=\n%s", sbuddy
->cal_start_time
, sbuddy
->cal_granularity
, sbuddy
->cal_free_busy_base64
);
6547 if (sbuddy
&& xn_working_hours
) {
6548 sipe_cal_parse_working_hours(xn_working_hours
, sbuddy
);
6553 if (do_update_status
) {
6554 if (!status
) { /* no status category in this update, using contact's current status */
6555 PurpleBuddy
*pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri
);
6556 const PurplePresence
*presence
= purple_buddy_get_presence(pbuddy
);
6557 const PurpleStatus
*pstatus
= purple_presence_get_active_status(presence
);
6558 status
= purple_status_get_id(pstatus
);
6561 SIPE_DEBUG_INFO("process_incoming_notify_rlmi: %s", status
);
6562 sipe_got_user_status(sip
, uri
, status
);
6565 sipe_xml_free(xn_categories
);
6568 static void sipe_subscribe_poolfqdn_resource_uri(const char *host
, GSList
*server
, struct sipe_account_data
*sip
)
6570 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
6571 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: pool(%s)", host
);
6572 payload
->host
= g_strdup(host
);
6573 payload
->buddies
= server
;
6574 sipe_subscribe_presence_batched_routed(sip
, payload
);
6575 sipe_subscribe_presence_batched_routed_free(payload
);
6578 static void process_incoming_notify_rlmi_resub(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
6581 const sipe_xml
*xn_resource
;
6582 GHashTable
*servers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
6587 xn_list
= sipe_xml_parse(data
, len
);
6589 for (xn_resource
= sipe_xml_child(xn_list
, "resource");
6591 xn_resource
= sipe_xml_twin(xn_resource
) )
6593 const char *uri
, *state
;
6594 const sipe_xml
*xn_instance
;
6596 xn_instance
= sipe_xml_child(xn_resource
, "instance");
6597 if (!xn_instance
) continue;
6599 uri
= sipe_xml_attribute(xn_resource
, "uri");
6600 state
= sipe_xml_attribute(xn_instance
, "state");
6601 SIPE_DEBUG_INFO("process_incoming_notify_rlmi_resub: uri(%s),state(%s)", uri
, state
);
6603 if (strstr(state
, "resubscribe")) {
6604 const char *poolFqdn
= sipe_xml_attribute(xn_instance
, "poolFqdn");
6606 if (poolFqdn
) { //[MS-PRES] Section 3.4.5.1.3 Processing Details
6607 gchar
*user
= g_strdup(uri
);
6608 host
= g_strdup(poolFqdn
);
6609 server
= g_hash_table_lookup(servers
, host
);
6610 server
= g_slist_append(server
, user
);
6611 g_hash_table_insert(servers
, host
, server
);
6613 sipe_subscribe_presence_single(sip
, (void *) uri
);
6618 /* Send out any deferred poolFqdn subscriptions */
6619 g_hash_table_foreach(servers
, (GHFunc
) sipe_subscribe_poolfqdn_resource_uri
, sip
);
6620 g_hash_table_destroy(servers
);
6622 sipe_xml_free(xn_list
);
6625 static void process_incoming_notify_pidf(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
6629 gchar
*activity
= NULL
;
6631 const sipe_xml
*basicstatus
= NULL
, *tuple
, *status
;
6632 gboolean isonline
= FALSE
;
6633 const sipe_xml
*display_name_node
;
6635 pidf
= sipe_xml_parse(data
, len
);
6637 SIPE_DEBUG_INFO("process_incoming_notify_pidf: no parseable pidf:%s", data
);
6641 if ((tuple
= sipe_xml_child(pidf
, "tuple")))
6643 if ((status
= sipe_xml_child(tuple
, "status"))) {
6644 basicstatus
= sipe_xml_child(status
, "basic");
6649 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic found");
6650 sipe_xml_free(pidf
);
6654 getbasic
= sipe_xml_data(basicstatus
);
6656 SIPE_DEBUG_INFO_NOFORMAT("process_incoming_notify_pidf: no basic data found");
6657 sipe_xml_free(pidf
);
6661 SIPE_DEBUG_INFO("process_incoming_notify_pidf: basic-status(%s)", getbasic
);
6662 if (strstr(getbasic
, "open")) {
6667 uri
= sip_uri(sipe_xml_attribute(pidf
, "entity")); /* with 'sip:' prefix */ /* AOL comes without the prefix */
6669 display_name_node
= sipe_xml_child(pidf
, "display-name");
6670 if (display_name_node
) {
6671 char * display_name
= sipe_xml_data(display_name_node
);
6673 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
6674 g_free(display_name
);
6677 if ((tuple
= sipe_xml_child(pidf
, "tuple"))) {
6678 if ((status
= sipe_xml_child(tuple
, "status"))) {
6679 if ((basicstatus
= sipe_xml_child(status
, "activities"))) {
6680 if ((basicstatus
= sipe_xml_child(basicstatus
, "activity"))) {
6681 activity
= sipe_xml_data(basicstatus
);
6682 SIPE_DEBUG_INFO("process_incoming_notify_pidf: activity(%s)", activity
);
6689 const gchar
* status_id
= NULL
;
6691 if (sipe_strequal(activity
, sipe_activity_map
[SIPE_ACTIVITY_BUSY
].token
)) {
6692 status_id
= SIPE_STATUS_ID_BUSY
;
6693 } else if (sipe_strequal(activity
, sipe_activity_map
[SIPE_ACTIVITY_AWAY
].token
)) {
6694 status_id
= SIPE_STATUS_ID_AWAY
;
6699 status_id
= SIPE_STATUS_ID_AVAILABLE
;
6702 SIPE_DEBUG_INFO("process_incoming_notify_pidf: status_id(%s)", status_id
);
6703 sipe_got_user_status(sip
, uri
, status_id
);
6705 sipe_got_user_status(sip
, uri
, SIPE_STATUS_ID_OFFLINE
);
6710 sipe_xml_free(pidf
);
6715 sipe_user_info_has_updated(struct sipe_account_data
*sip
,
6716 const sipe_xml
*xn_userinfo
)
6718 const sipe_xml
*xn_states
;
6720 g_free(sip
->user_states
);
6721 sip
->user_states
= NULL
;
6722 if ((xn_states
= sipe_xml_child(xn_userinfo
, "states")) != NULL
) {
6723 gchar
*orig
= sip
->user_states
= sipe_xml_stringify(xn_states
);
6725 /* this is a hack-around to remove added newline after inner element,
6726 * state in this case, where it shouldn't be.
6727 * After several use of sipe_xml_stringify, amount of added newlines
6728 * grows significantly.
6731 gchar c
, *stripped
= orig
;
6732 while ((c
= *orig
++)) {
6733 if ((c
!= '\n') /* && (c != '\r') */) {
6741 /* Publish initial state if not yet.
6742 * Assuming this happens on initial responce to self subscription
6743 * so we've already updated our UserInfo.
6745 if (!sip
->initial_state_published
) {
6746 send_presence_soap(sip
, FALSE
);
6748 sipe_schedule_action("<+update-calendar>", UPDATE_CALENDAR_DELAY
, (Action
)sipe_update_calendar
, NULL
, sip
, NULL
);
6752 static void process_incoming_notify_msrtc(struct sipe_account_data
*sip
, const gchar
*data
, unsigned len
)
6754 char *activity
= NULL
;
6756 const char *status_id
= NULL
;
6759 char *self_uri
= sip_uri_self(sip
);
6762 const char *device_name
= NULL
;
6763 const char *cal_start_time
= NULL
;
6764 const char *cal_granularity
= NULL
;
6765 char *cal_free_busy_base64
= NULL
;
6766 struct sipe_buddy
*sbuddy
;
6767 const sipe_xml
*node
;
6768 sipe_xml
*xn_presentity
;
6769 const sipe_xml
*xn_availability
;
6770 const sipe_xml
*xn_activity
;
6771 const sipe_xml
*xn_display_name
;
6772 const sipe_xml
*xn_email
;
6773 const sipe_xml
*xn_phone_number
;
6774 const sipe_xml
*xn_userinfo
;
6775 const sipe_xml
*xn_note
;
6776 const sipe_xml
*xn_oof
;
6777 const sipe_xml
*xn_state
;
6778 const sipe_xml
*xn_contact
;
6780 char *free_activity
;
6782 const char *user_avail_nil
;
6784 time_t user_avail_since
= 0;
6785 time_t activity_since
= 0;
6787 /* fix for Reuters environment on Linux */
6788 if (data
&& strstr(data
, "encoding=\"utf-16\"")) {
6790 tmp_data
= replace(data
, "encoding=\"utf-16\"", "encoding=\"utf-8\"");
6791 xn_presentity
= sipe_xml_parse(tmp_data
, strlen(tmp_data
));
6794 xn_presentity
= sipe_xml_parse(data
, len
);
6797 xn_availability
= sipe_xml_child(xn_presentity
, "availability");
6798 xn_activity
= sipe_xml_child(xn_presentity
, "activity");
6799 xn_display_name
= sipe_xml_child(xn_presentity
, "displayName");
6800 xn_email
= sipe_xml_child(xn_presentity
, "email");
6801 xn_phone_number
= sipe_xml_child(xn_presentity
, "phoneNumber");
6802 xn_userinfo
= sipe_xml_child(xn_presentity
, "userInfo");
6803 xn_oof
= xn_userinfo
? sipe_xml_child(xn_userinfo
, "oof") : NULL
;
6804 xn_state
= xn_userinfo
? sipe_xml_child(xn_userinfo
, "states/state"): NULL
;
6805 user_avail
= xn_state
? sipe_xml_int_attribute(xn_state
, "avail", 0) : 0;
6806 user_avail_since
= xn_state
? sipe_utils_str_to_time(sipe_xml_attribute(xn_state
, "since")) : 0;
6807 user_avail_nil
= xn_state
? sipe_xml_attribute(xn_state
, "nil") : NULL
;
6808 xn_contact
= xn_userinfo
? sipe_xml_child(xn_userinfo
, "contact") : NULL
;
6809 xn_note
= xn_userinfo
? sipe_xml_child(xn_userinfo
, "note") : NULL
;
6810 note
= xn_note
? sipe_xml_data(xn_note
) : NULL
;
6812 if (sipe_strequal(user_avail_nil
, "true")) { /* null-ed */
6814 user_avail_since
= 0;
6817 free_activity
= NULL
;
6819 name
= sipe_xml_attribute(xn_presentity
, "uri"); /* without 'sip:' prefix */
6820 uri
= sip_uri_from_name(name
);
6821 avl
= sipe_xml_int_attribute(xn_availability
, "aggregate", 0);
6822 epid
= sipe_xml_attribute(xn_availability
, "epid");
6823 act
= sipe_xml_int_attribute(xn_activity
, "aggregate", 0);
6825 status_id
= sipe_get_status_by_act_avail_2005(act
, avl
, &activity
);
6826 res_avail
= sipe_get_availability_by_status(status_id
, NULL
);
6827 if (user_avail
> res_avail
) {
6828 res_avail
= user_avail
;
6829 status_id
= sipe_get_status_by_availability(user_avail
, NULL
);
6832 if (xn_display_name
) {
6833 char *display_name
= g_strdup(sipe_xml_attribute(xn_display_name
, "displayName"));
6834 char *email
= xn_email
? g_strdup(sipe_xml_attribute(xn_email
, "email")) : NULL
;
6835 char *phone_label
= xn_phone_number
? g_strdup(sipe_xml_attribute(xn_phone_number
, "label")) : NULL
;
6836 char *phone_number
= xn_phone_number
? g_strdup(sipe_xml_attribute(xn_phone_number
, "number")) : NULL
;
6837 char *tel_uri
= sip_to_tel_uri(phone_number
);
6839 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, display_name
);
6840 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
6841 sipe_update_user_info(sip
, uri
, PHONE_PROP
, tel_uri
);
6842 sipe_update_user_info(sip
, uri
, PHONE_DISPLAY_PROP
, !is_empty(phone_label
) ? phone_label
: phone_number
);
6845 g_free(phone_label
);
6846 g_free(phone_number
);
6848 g_free(display_name
);
6853 for (node
= sipe_xml_child(xn_contact
, "tel"); node
; node
= sipe_xml_twin(node
))
6855 /* Ex.: <tel type="work">tel:+3222220000</tel> */
6856 const char *phone_type
= sipe_xml_attribute(node
, "type");
6857 char* phone
= sipe_xml_data(node
);
6859 sipe_update_user_phone(sip
, uri
, phone_type
, phone
, NULL
);
6865 /* devicePresence */
6866 for (node
= sipe_xml_child(xn_presentity
, "devices/devicePresence"); node
; node
= sipe_xml_twin(node
)) {
6867 const sipe_xml
*xn_device_name
;
6868 const sipe_xml
*xn_calendar_info
;
6869 const sipe_xml
*xn_state
;
6873 if (sipe_strequal(sipe_xml_attribute(node
, "epid"), epid
)) {
6874 xn_device_name
= sipe_xml_child(node
, "deviceName");
6875 device_name
= xn_device_name
? sipe_xml_attribute(xn_device_name
, "name") : NULL
;
6879 xn_calendar_info
= sipe_xml_child(node
, "calendarInfo");
6880 if (xn_calendar_info
) {
6881 const char *cal_start_time_tmp
= sipe_xml_attribute(xn_calendar_info
, "startTime");
6883 if (cal_start_time
) {
6884 time_t cal_start_time_t
= sipe_utils_str_to_time(cal_start_time
);
6885 time_t cal_start_time_t_tmp
= sipe_utils_str_to_time(cal_start_time_tmp
);
6887 if (cal_start_time_t_tmp
> cal_start_time_t
) {
6888 cal_start_time
= cal_start_time_tmp
;
6889 cal_granularity
= sipe_xml_attribute(xn_calendar_info
, "granularity");
6890 g_free(cal_free_busy_base64
);
6891 cal_free_busy_base64
= sipe_xml_data(xn_calendar_info
);
6893 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time
, cal_granularity
, cal_free_busy_base64
);
6896 cal_start_time
= cal_start_time_tmp
;
6897 cal_granularity
= sipe_xml_attribute(xn_calendar_info
, "granularity");
6898 g_free(cal_free_busy_base64
);
6899 cal_free_busy_base64
= sipe_xml_data(xn_calendar_info
);
6901 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: startTime=%s granularity=%s cal_free_busy_base64=\n%s", cal_start_time
, cal_granularity
, cal_free_busy_base64
);
6906 xn_state
= sipe_xml_child(node
, "states/state");
6908 int dev_avail
= sipe_xml_int_attribute(xn_state
, "avail", 0);
6909 time_t dev_avail_since
= sipe_utils_str_to_time(sipe_xml_attribute(xn_state
, "since"));
6911 state
= sipe_xml_data(xn_state
);
6912 if (dev_avail_since
> user_avail_since
&&
6913 dev_avail
>= res_avail
)
6915 res_avail
= dev_avail
;
6916 if (!is_empty(state
))
6918 if (sipe_strequal(state
, sipe_activity_map
[SIPE_ACTIVITY_ON_PHONE
].token
)) {
6920 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_ON_PHONE
));
6921 } else if (sipe_strequal(state
, "presenting")) {
6923 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_IN_CONF
));
6928 activity_since
= dev_avail_since
;
6930 status_id
= sipe_get_status_by_availability(res_avail
, &activity
);
6937 if (xn_oof
&& res_avail
>= 15000) { /* 12000 in 2007 */
6939 activity
= g_strdup(SIPE_ACTIVITY_I18N(SIPE_ACTIVITY_OOF
));
6943 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
6946 g_free(sbuddy
->activity
);
6947 sbuddy
->activity
= activity
;
6950 sbuddy
->activity_since
= activity_since
;
6952 sbuddy
->user_avail
= user_avail
;
6953 sbuddy
->user_avail_since
= user_avail_since
;
6955 g_free(sbuddy
->note
);
6956 sbuddy
->note
= NULL
;
6957 if (!is_empty(note
)) { sbuddy
->note
= g_markup_escape_text(note
, -1); }
6959 sbuddy
->is_oof_note
= (xn_oof
!= NULL
);
6961 g_free(sbuddy
->device_name
);
6962 sbuddy
->device_name
= NULL
;
6963 if (!is_empty(device_name
)) { sbuddy
->device_name
= g_strdup(device_name
); }
6965 if (!is_empty(cal_free_busy_base64
)) {
6966 g_free(sbuddy
->cal_start_time
);
6967 sbuddy
->cal_start_time
= g_strdup(cal_start_time
);
6969 sbuddy
->cal_granularity
= sipe_strcase_equal(cal_granularity
, "PT15M") ? 15 : 0;
6971 g_free(sbuddy
->cal_free_busy_base64
);
6972 sbuddy
->cal_free_busy_base64
= cal_free_busy_base64
;
6973 cal_free_busy_base64
= NULL
;
6975 g_free(sbuddy
->cal_free_busy
);
6976 sbuddy
->cal_free_busy
= NULL
;
6979 sbuddy
->last_non_cal_status_id
= status_id
;
6980 g_free(sbuddy
->last_non_cal_activity
);
6981 sbuddy
->last_non_cal_activity
= g_strdup(sbuddy
->activity
);
6983 if (sipe_strcase_equal(sbuddy
->name
, self_uri
)) {
6984 if (!sipe_strequal(sbuddy
->note
, sip
->note
)) /* not same */
6986 sip
->is_oof_note
= sbuddy
->is_oof_note
;
6989 sip
->note
= g_strdup(sbuddy
->note
);
6991 sip
->note_since
= time(NULL
);
6994 g_free(sip
->status
);
6995 sip
->status
= g_strdup(sbuddy
->last_non_cal_status_id
);
6998 g_free(cal_free_busy_base64
);
7001 SIPE_DEBUG_INFO("process_incoming_notify_msrtc: status(%s)", status_id
);
7002 sipe_got_user_status(sip
, uri
, status_id
);
7004 if (!sip
->ocs2007
&& sipe_strcase_equal(self_uri
, uri
)) {
7005 sipe_user_info_has_updated(sip
, xn_userinfo
);
7009 sipe_xml_free(xn_presentity
);
7014 static void sipe_presence_mime_cb(gpointer user_data
,
7019 if (strstr(type
,"application/rlmi+xml")) {
7020 process_incoming_notify_rlmi_resub(user_data
, body
, length
);
7021 } else if (strstr(type
, "text/xml+msrtc.pidf")) {
7022 process_incoming_notify_msrtc(user_data
, body
, length
);
7024 process_incoming_notify_rlmi(user_data
, body
, length
);
7028 static void sipe_process_presence(struct sipe_account_data
*sip
, struct sipmsg
*msg
)
7030 const char *ctype
= sipmsg_find_header(msg
, "Content-Type");
7032 SIPE_DEBUG_INFO("sipe_process_presence: Content-Type: %s", ctype
? ctype
: "");
7035 (strstr(ctype
, "application/rlmi+xml") ||
7036 strstr(ctype
, "application/msrtc-event-categories+xml")))
7038 if (strstr(ctype
, "multipart"))
7040 sipe_mime_parts_foreach(ctype
, msg
->body
, sipe_presence_mime_cb
, sip
);
7042 else if(strstr(ctype
, "application/msrtc-event-categories+xml") )
7044 process_incoming_notify_rlmi(sip
, msg
->body
, msg
->bodylen
);
7046 else if(strstr(ctype
, "application/rlmi+xml"))
7048 process_incoming_notify_rlmi_resub(sip
, msg
->body
, msg
->bodylen
);
7051 else if(ctype
&& strstr(ctype
, "text/xml+msrtc.pidf"))
7053 process_incoming_notify_msrtc(sip
, msg
->body
, msg
->bodylen
);
7057 process_incoming_notify_pidf(sip
, msg
->body
, msg
->bodylen
);
7061 static void sipe_presence_timeout_mime_cb(gpointer user_data
,
7062 SIPE_UNUSED_PARAMETER
const gchar
*type
,
7066 GSList
**buddies
= user_data
;
7067 sipe_xml
*xml
= sipe_xml_parse(body
, length
);
7069 if (xml
&& !sipe_strequal(sipe_xml_name(xml
), "list")) {
7070 const gchar
*uri
= sipe_xml_attribute(xml
, "uri");
7071 const sipe_xml
*xn_category
;
7074 * automaton: presence is never expected to change
7076 * see: http://msdn.microsoft.com/en-us/library/ee354295(office.13).aspx
7078 for (xn_category
= sipe_xml_child(xml
, "category");
7080 xn_category
= sipe_xml_twin(xn_category
)) {
7081 if (sipe_strequal(sipe_xml_attribute(xn_category
, "name"),
7083 const sipe_xml
*node
= sipe_xml_child(xn_category
, "contactCard/automaton");
7085 char *boolean
= sipe_xml_data(node
);
7086 if (sipe_strequal(boolean
, "true")) {
7087 SIPE_DEBUG_INFO("sipe_process_presence_timeout: %s is an automaton: - not subscribing to presence updates",
7098 *buddies
= g_slist_append(*buddies
, sip_uri(uri
));
7105 static void sipe_process_presence_timeout(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gchar
*who
, int timeout
)
7107 const char *ctype
= sipmsg_find_header(msg
, "Content-Type");
7108 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
7110 SIPE_DEBUG_INFO("sipe_process_presence_timeout: Content-Type: %s", ctype
? ctype
: "");
7113 strstr(ctype
, "multipart") &&
7114 (strstr(ctype
, "application/rlmi+xml") ||
7115 strstr(ctype
, "application/msrtc-event-categories+xml"))) {
7116 GSList
*buddies
= NULL
;
7118 sipe_mime_parts_foreach(ctype
, msg
->body
, sipe_presence_timeout_mime_cb
, &buddies
);
7121 struct presence_batched_routed
*payload
= g_malloc(sizeof(struct presence_batched_routed
));
7122 payload
->host
= g_strdup(who
);
7123 payload
->buddies
= buddies
;
7124 sipe_schedule_action(action_name
, timeout
,
7125 sipe_subscribe_presence_batched_routed
,
7126 sipe_subscribe_presence_batched_routed_free
,
7128 SIPE_DEBUG_INFO("Resubscription multiple contacts with batched support & route(%s) in %d", who
, timeout
);
7132 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(who
));
7133 SIPE_DEBUG_INFO("Resubscription single contact with batched support(%s) in %d", who
, timeout
);
7135 g_free(action_name
);
7139 * Dispatcher for all incoming subscription information
7140 * whether it comes from NOTIFY, BENOTIFY requests or
7141 * piggy-backed to subscription's OK responce.
7143 * @param request whether initiated from BE/NOTIFY request or OK-response message.
7144 * @param benotify whether initiated from NOTIFY or BENOTIFY request.
7146 static void process_incoming_notify(struct sipe_account_data
*sip
, struct sipmsg
*msg
, gboolean request
, gboolean benotify
)
7148 const gchar
*content_type
= sipmsg_find_header(msg
, "Content-Type");
7149 const gchar
*event
= sipmsg_find_header(msg
, "Event");
7150 const gchar
*subscription_state
= sipmsg_find_header(msg
, "subscription-state");
7153 SIPE_DEBUG_INFO("process_incoming_notify: Event: %s\n\n%s",
7155 tmp
= fix_newlines(msg
->body
));
7157 SIPE_DEBUG_INFO("process_incoming_notify: subscription_state: %s", subscription_state
? subscription_state
: "");
7159 /* implicit subscriptions */
7160 if (content_type
&& g_str_has_prefix(content_type
, "application/ms-imdn+xml")) {
7161 sipe_process_imdn(sip
, msg
);
7165 /* for one off subscriptions (send with Expire: 0) */
7166 if (sipe_strcase_equal(event
, "vnd-microsoft-provisioning-v2"))
7168 sipe_process_provisioning_v2(sip
, msg
);
7170 else if (sipe_strcase_equal(event
, "vnd-microsoft-provisioning"))
7172 sipe_process_provisioning(sip
, msg
);
7174 else if (sipe_strcase_equal(event
, "presence"))
7176 sipe_process_presence(sip
, msg
);
7178 else if (sipe_strcase_equal(event
, "registration-notify"))
7180 sipe_process_registration_notify(sip
, msg
);
7183 if (!subscription_state
|| strstr(subscription_state
, "active"))
7185 if (sipe_strcase_equal(event
, "vnd-microsoft-roaming-contacts"))
7187 sipe_process_roaming_contacts(sip
, msg
);
7189 else if (sipe_strcase_equal(event
, "vnd-microsoft-roaming-self"))
7191 sipe_process_roaming_self(sip
, msg
);
7193 else if (sipe_strcase_equal(event
, "vnd-microsoft-roaming-ACL"))
7195 sipe_process_roaming_acl(sip
, msg
);
7197 else if (sipe_strcase_equal(event
, "presence.wpending"))
7199 sipe_process_presence_wpending(sip
, msg
);
7201 else if (sipe_strcase_equal(event
, "conference"))
7203 sipe_process_conference(sip
, msg
);
7208 /* The server sends status 'terminated' */
7209 if (subscription_state
&& strstr(subscription_state
, "terminated") ) {
7210 gchar
*who
= parse_from(sipmsg_find_header(msg
, request
? "From" : "To"));
7211 gchar
*key
= sipe_get_subscription_key(event
, who
);
7213 SIPE_DEBUG_INFO("process_incoming_notify: server says that subscription to %s was terminated.", who
);
7216 if (g_hash_table_lookup(sip
->subscriptions
, key
)) {
7217 g_hash_table_remove(sip
->subscriptions
, key
);
7218 SIPE_DEBUG_INFO("process_subscribe_response: subscription dialog removed for: %s", key
);
7224 if (!request
&& event
) {
7225 const gchar
*expires_header
= sipmsg_find_header(msg
, "Expires");
7226 int timeout
= expires_header
? strtol(expires_header
, NULL
, 10) : 0;
7227 SIPE_DEBUG_INFO("process_incoming_notify: subscription expires:%d", timeout
);
7230 /* 2 min ahead of expiration */
7231 timeout
= (timeout
- 120) > 120 ? (timeout
- 120) : timeout
;
7233 if (sipe_strcase_equal(event
, "presence.wpending") &&
7234 g_slist_find_custom(sip
->allow_events
, "presence.wpending", (GCompareFunc
)g_ascii_strcasecmp
))
7236 gchar
*action_name
= g_strdup_printf("<%s>", "presence.wpending");
7237 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_wpending
, NULL
, sip
, NULL
);
7238 g_free(action_name
);
7240 else if (sipe_strcase_equal(event
, "presence") &&
7241 g_slist_find_custom(sip
->allow_events
, "presence", (GCompareFunc
)g_ascii_strcasecmp
))
7243 gchar
*who
= parse_from(sipmsg_find_header(msg
, "To"));
7244 gchar
*action_name
= g_strdup_printf(ACTION_NAME_PRESENCE
, who
);
7246 if (sip
->batched_support
) {
7247 sipe_process_presence_timeout(sip
, msg
, who
, timeout
);
7250 sipe_schedule_action(action_name
, timeout
, sipe_subscribe_presence_single
, g_free
, sip
, g_strdup(who
));
7251 SIPE_DEBUG_INFO("Resubscription single contact (%s) in %d", who
, timeout
);
7253 g_free(action_name
);
7259 /* The client responses on received a NOTIFY message */
7260 if (request
&& !benotify
)
7262 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
7267 * Whether user manually changed status or
7268 * it was changed automatically due to user
7269 * became inactive/active again
7272 sipe_is_user_state(struct sipe_account_data
*sip
)
7275 time_t now
= time(NULL
);
7277 SIPE_DEBUG_INFO("sipe_is_user_state: sip->idle_switch : %s", asctime(localtime(&(sip
->idle_switch
))));
7278 SIPE_DEBUG_INFO("sipe_is_user_state: now : %s", asctime(localtime(&now
)));
7280 res
= ((now
- SIPE_IDLE_SET_DELAY
* 2) >= sip
->idle_switch
);
7282 SIPE_DEBUG_INFO("sipe_is_user_state: res = %s", res
? "USER" : "MACHINE");
7287 send_presence_soap0(struct sipe_account_data
*sip
,
7288 gboolean do_publish_calendar
,
7289 gboolean do_reset_status
)
7291 struct sipe_ews
* ews
= sip
->ews
;
7292 int availability
= 0;
7297 gchar
*res_note
= NULL
;
7298 gchar
*res_oof
= NULL
;
7299 const gchar
*note_pub
= NULL
;
7300 gchar
*states
= NULL
;
7301 gchar
*calendar_data
= NULL
;
7302 gchar
*epid
= get_epid(sip
);
7303 time_t now
= time(NULL
);
7304 gchar
*since_time_str
= sipe_utils_time_to_str(now
);
7305 const gchar
*oof_note
= ews
? sipe_ews_get_oof_note(ews
) : NULL
;
7306 const char *user_input
;
7307 gboolean pub_oof
= ews
&& oof_note
&& (!sip
->note
|| ews
->updated
> sip
->note_since
);
7309 if (oof_note
&& sip
->note
) {
7310 SIPE_DEBUG_INFO("ews->oof_start : %s", asctime(localtime(&(ews
->oof_start
))));
7311 SIPE_DEBUG_INFO("sip->note_since : %s", asctime(localtime(&(sip
->note_since
))));
7314 SIPE_DEBUG_INFO("sip->note : %s", sip
->note
? sip
->note
: "");
7316 if (!sip
->initial_state_published
||
7319 g_free(sip
->status
);
7320 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
);
7323 sipe_get_act_avail_by_status_2005(sip
->status
, &activity
, &availability
);
7327 note_pub
= oof_note
;
7328 res_oof
= SIPE_SOAP_SET_PRESENCE_OOF_XML
;
7329 ews
->published
= TRUE
;
7330 } else if (sip
->note
) {
7331 if (sip
->is_oof_note
&& !oof_note
) { /* stale OOF note, as it's not present in ews already */
7334 sip
->is_oof_note
= FALSE
;
7335 sip
->note_since
= 0;
7337 note_pub
= sip
->note
;
7338 res_oof
= sip
->is_oof_note
? SIPE_SOAP_SET_PRESENCE_OOF_XML
: "";
7344 /* to protocol internal plain text format */
7345 tmp
= sipe_backend_markup_strip_html(note_pub
);
7346 res_note
= g_markup_printf_escaped(SIPE_SOAP_SET_PRESENCE_NOTE_XML
, tmp
);
7351 if (!do_reset_status
) {
7352 if (sipe_is_user_state(sip
) && !do_publish_calendar
&& sip
->initial_state_published
)
7354 gchar
*activity_token
= NULL
;
7355 int avail_2007
= sipe_get_availability_by_status(sip
->status
, &activity_token
);
7357 states
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_STATES
,
7362 g_free(activity_token
);
7364 else /* preserve existing publication */
7366 if (sip
->user_states
) {
7367 states
= g_strdup(sip
->user_states
);
7371 /* do nothing - then User state will be erased */
7373 sip
->initial_state_published
= TRUE
;
7376 if (ews
&& (!is_empty(ews
->legacy_dn
) || !is_empty(ews
->email
)) && ews
->fb_start
&& !is_empty(ews
->free_busy
))
7378 char *fb_start_str
= sipe_utils_time_to_str(ews
->fb_start
);
7379 char *free_busy_base64
= sipe_cal_get_freebusy_base64(ews
->free_busy
);
7380 calendar_data
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE_CALENDAR
,
7381 !is_empty(ews
->legacy_dn
) ? ews
->legacy_dn
: ews
->email
,
7384 g_free(fb_start_str
);
7385 g_free(free_busy_base64
);
7388 user_input
= !sipe_is_user_state(sip
) && sip
->status
!= SIPE_STATUS_ID_AVAILABLE
? "idle" : "active";
7390 /* forming resulting XML */
7391 body
= g_strdup_printf(SIPE_SOAP_SET_PRESENCE
,
7395 (tmp
= g_ascii_strup(g_get_host_name(), -1)),
7396 res_note
? res_note
: "",
7397 res_oof
? res_oof
: "",
7398 states
? states
: "",
7399 calendar_data
? calendar_data
: "",
7408 g_free(calendar_data
);
7410 send_soap_request(sip
, body
);
7413 g_free(since_time_str
);
7418 send_presence_soap(struct sipe_account_data
*sip
,
7419 gboolean do_publish_calendar
)
7421 return send_presence_soap0(sip
, do_publish_calendar
, FALSE
);
7426 process_send_presence_category_publish_response(struct sipe_account_data
*sip
,
7428 struct transaction
*trans
)
7430 const gchar
*contenttype
= sipmsg_find_header(msg
, "Content-Type");
7432 if (msg
->response
== 409 && g_str_has_prefix(contenttype
, "application/msrtc-fault+xml")) {
7434 const sipe_xml
*node
;
7438 gboolean has_device_publication
= FALSE
;
7440 xml
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
7442 /* test if version mismatch fault */
7443 fault_code
= sipe_xml_data(sipe_xml_child(xml
, "Faultcode"));
7444 if (!sipe_strequal(fault_code
, "Client.BadCall.WrongDelta")) {
7445 SIPE_DEBUG_INFO("process_send_presence_category_publish_response: unsupported fault code:%s returning.", fault_code
);
7452 /* accumulating information about faulty versions */
7453 faults
= g_hash_table_new_full(g_str_hash
, g_str_equal
, g_free
, g_free
);
7454 for (node
= sipe_xml_child(xml
, "details/operation");
7456 node
= sipe_xml_twin(node
))
7458 const gchar
*index
= sipe_xml_attribute(node
, "index");
7459 const gchar
*curVersion
= sipe_xml_attribute(node
, "curVersion");
7461 g_hash_table_insert(faults
, g_strdup(index
), g_strdup(curVersion
));
7462 SIPE_DEBUG_INFO("fault added: index:%s curVersion:%s", index
, curVersion
);
7466 /* here we are parsing own request to figure out what publication
7467 * referensed here only by index went wrong
7469 xml
= sipe_xml_parse(trans
->msg
->body
, trans
->msg
->bodylen
);
7472 for (node
= sipe_xml_child(xml
, "publications/publication"),
7473 index_our
= 1; /* starts with 1 - our first publication */
7475 node
= sipe_xml_twin(node
), index_our
++)
7477 gchar
*idx
= g_strdup_printf("%d", index_our
);
7478 const gchar
*curVersion
= g_hash_table_lookup(faults
, idx
);
7479 const gchar
*categoryName
= sipe_xml_attribute(node
, "categoryName");
7482 if (sipe_strequal("device", categoryName
)) {
7483 has_device_publication
= TRUE
;
7486 if (curVersion
) { /* fault exist on this index */
7487 const gchar
*container
= sipe_xml_attribute(node
, "container");
7488 const gchar
*instance
= sipe_xml_attribute(node
, "instance");
7489 /* key is <category><instance><container> */
7490 gchar
*key
= g_strdup_printf("<%s><%s><%s>", categoryName
, instance
, container
);
7491 GHashTable
*category
= g_hash_table_lookup(sip
->our_publications
, categoryName
);
7494 struct sipe_publication
*publication
=
7495 g_hash_table_lookup(category
, key
);
7497 SIPE_DEBUG_INFO("key is %s", key
);
7500 SIPE_DEBUG_INFO("Updating %s with version %s. Was %d before.",
7501 key
, curVersion
, publication
->version
);
7502 /* updating publication's version to the correct one */
7503 publication
->version
= atoi(curVersion
);
7506 /* We somehow lost this category from our publications... */
7507 struct sipe_publication
*publication
= g_new0(struct sipe_publication
, 1);
7508 publication
->category
= g_strdup(categoryName
);
7509 publication
->instance
= atoi(instance
);
7510 publication
->container
= atoi(container
);
7511 publication
->version
= atoi(curVersion
);
7512 category
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
7513 g_free
, (GDestroyNotify
)free_publication
);
7514 g_hash_table_insert(category
, g_strdup(key
), publication
);
7515 g_hash_table_insert(sip
->our_publications
, g_strdup(categoryName
), category
);
7516 SIPE_DEBUG_INFO("added lost category '%s' key '%s'", categoryName
, key
);
7522 g_hash_table_destroy(faults
);
7524 /* rebublishing with right versions */
7525 if (has_device_publication
) {
7526 send_publish_category_initial(sip
);
7528 send_presence_status(sip
);
7535 * Returns 'device' XML part for publication.
7536 * Must be g_free'd after use.
7539 sipe_publish_get_category_device(struct sipe_account_data
*sip
)
7543 gchar
*epid
= get_epid(sip
);
7544 gchar
*uuid
= generateUUIDfromEPID(epid
);
7545 guint device_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_DEVICE
);
7546 /* key is <category><instance><container> */
7547 gchar
*key
= g_strdup_printf("<%s><%u><%u>", "device", device_instance
, 2);
7548 struct sipe_publication
*publication
=
7549 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "device"), key
);
7554 uri
= sip_uri_self(sip
);
7555 doc
= g_strdup_printf(SIPE_PUB_XML_DEVICE
,
7557 publication
? publication
->version
: 0,
7560 "00:00:00+01:00", /* @TODO make timezone real*/
7571 * A service method - use
7572 * - send_publish_get_category_state_machine and
7573 * - send_publish_get_category_state_user instead.
7574 * Must be g_free'd after use.
7577 sipe_publish_get_category_state(struct sipe_account_data
*sip
,
7578 gboolean is_user_state
)
7580 int availability
= sipe_get_availability_by_status(sip
->status
, NULL
);
7581 guint instance
= is_user_state
? sipe_get_pub_instance(sip
, SIPE_PUB_STATE_USER
) :
7582 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_MACHINE
);
7583 /* key is <category><instance><container> */
7584 gchar
*key_2
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 2);
7585 gchar
*key_3
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 3);
7586 struct sipe_publication
*publication_2
=
7587 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_2
);
7588 struct sipe_publication
*publication_3
=
7589 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_3
);
7594 if (publication_2
&& (publication_2
->availability
== availability
))
7596 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_state: state has NOT changed. Exiting.");
7597 return NULL
; /* nothing to update */
7600 return g_strdup_printf( is_user_state
? SIPE_PUB_XML_STATE_USER
: SIPE_PUB_XML_STATE_MACHINE
,
7602 publication_2
? publication_2
->version
: 0,
7605 publication_3
? publication_3
->version
: 0,
7610 * Only Busy and OOF calendar event are published.
7611 * Different instances are used for that.
7613 * Must be g_free'd after use.
7616 sipe_publish_get_category_state_calendar(struct sipe_account_data
*sip
,
7617 struct sipe_cal_event
*event
,
7621 gchar
*start_time_str
;
7622 int availability
= 0;
7625 guint instance
= (cal_satus
== SIPE_CAL_OOF
) ?
7626 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR_OOF
) :
7627 sipe_get_pub_instance(sip
, SIPE_PUB_STATE_CALENDAR
);
7629 /* key is <category><instance><container> */
7630 gchar
*key_2
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 2);
7631 gchar
*key_3
= g_strdup_printf("<%s><%u><%u>", "state", instance
, 3);
7632 struct sipe_publication
*publication_2
=
7633 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_2
);
7634 struct sipe_publication
*publication_3
=
7635 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "state"), key_3
);
7640 if (!publication_3
&& !event
) { /* was nothing, have nothing, exiting */
7641 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7642 "Exiting as no publication and no event for cal_satus:%d", cal_satus
);
7648 (publication_3
->availability
== availability
) &&
7649 sipe_strequal(publication_3
->cal_event_hash
, (tmp
= sipe_cal_event_hash(event
))))
7652 SIPE_DEBUG_INFO("sipe_publish_get_category_state_calendar: "
7653 "cal state has NOT changed for cal_satus:%d. Exiting.", cal_satus
);
7654 return NULL
; /* nothing to update */
7659 (event
->cal_status
== SIPE_CAL_BUSY
||
7660 event
->cal_status
== SIPE_CAL_OOF
))
7662 gchar
*availability_xml_str
= NULL
;
7663 gchar
*activity_xml_str
= NULL
;
7665 if (event
->cal_status
== SIPE_CAL_BUSY
) {
7666 availability_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_AVAIL
, 6500);
7669 if (event
->cal_status
== SIPE_CAL_BUSY
&& event
->is_meeting
) {
7670 activity_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
,
7671 sipe_activity_map
[SIPE_ACTIVITY_IN_MEETING
].token
,
7672 "minAvailability=\"6500\"",
7673 "maxAvailability=\"8999\"");
7674 } else if (event
->cal_status
== SIPE_CAL_OOF
) {
7675 activity_xml_str
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_ACTIVITY
,
7676 sipe_activity_map
[SIPE_ACTIVITY_OOF
].token
,
7677 "minAvailability=\"12000\"",
7680 start_time_str
= sipe_utils_time_to_str(event
->start_time
);
7682 res
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR
,
7684 publication_2
? publication_2
->version
: 0,
7687 availability_xml_str
? availability_xml_str
: "",
7688 activity_xml_str
? activity_xml_str
: "",
7689 event
->subject
? event
->subject
: "",
7690 event
->location
? event
->location
: "",
7693 publication_3
? publication_3
->version
: 0,
7696 availability_xml_str
? availability_xml_str
: "",
7697 activity_xml_str
? activity_xml_str
: "",
7698 event
->subject
? event
->subject
: "",
7699 event
->location
? event
->location
: ""
7701 g_free(start_time_str
);
7702 g_free(availability_xml_str
);
7703 g_free(activity_xml_str
);
7706 else /* including !event, SIPE_CAL_FREE, SIPE_CAL_TENTATIVE */
7708 res
= g_strdup_printf(SIPE_PUB_XML_STATE_CALENDAR_CLEAR
,
7710 publication_2
? publication_2
->version
: 0,
7713 publication_3
? publication_3
->version
: 0
7721 * Returns 'machineState' XML part for publication.
7722 * Must be g_free'd after use.
7725 sipe_publish_get_category_state_machine(struct sipe_account_data
*sip
)
7727 return sipe_publish_get_category_state(sip
, FALSE
);
7731 * Returns 'userState' XML part for publication.
7732 * Must be g_free'd after use.
7735 sipe_publish_get_category_state_user(struct sipe_account_data
*sip
)
7737 return sipe_publish_get_category_state(sip
, TRUE
);
7741 * Returns 'note' XML part for publication.
7742 * Must be g_free'd after use.
7744 * Protocol format for Note is plain text.
7746 * @param note a note in Sipe internal HTML format
7747 * @param note_type either personal or OOF
7750 sipe_publish_get_category_note(struct sipe_account_data
*sip
,
7751 const char *note
, /* html */
7752 const char *note_type
,
7756 guint instance
= sipe_strequal("OOF", note_type
) ? sipe_get_pub_instance(sip
, SIPE_PUB_NOTE_OOF
) : 0;
7757 /* key is <category><instance><container> */
7758 gchar
*key_note_200
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 200);
7759 gchar
*key_note_300
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 300);
7760 gchar
*key_note_400
= g_strdup_printf("<%s><%u><%u>", "note", instance
, 400);
7762 struct sipe_publication
*publication_note_200
=
7763 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_200
);
7764 struct sipe_publication
*publication_note_300
=
7765 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_300
);
7766 struct sipe_publication
*publication_note_400
=
7767 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "note"), key_note_400
);
7769 char *tmp
= note
? sipe_backend_markup_strip_html(note
) : NULL
;
7770 char *n1
= tmp
? g_markup_escape_text(tmp
, -1) : NULL
;
7771 const char *n2
= publication_note_200
? publication_note_200
->note
: NULL
;
7772 char *res
, *tmp1
, *tmp2
, *tmp3
;
7773 char *start_time_attr
;
7774 char *end_time_attr
;
7778 g_free(key_note_200
);
7779 g_free(key_note_300
);
7780 g_free(key_note_400
);
7782 /* we even need to republish empty note */
7783 if (sipe_strequal(n1
, n2
))
7785 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_note: note has NOT changed. Exiting.");
7787 return NULL
; /* nothing to update */
7790 start_time_attr
= note_start
? g_strdup_printf(" startTime=\"%s\"", (tmp
= sipe_utils_time_to_str(note_start
))) : NULL
;
7793 end_time_attr
= note_end
? g_strdup_printf(" endTime=\"%s\"", (tmp
= sipe_utils_time_to_str(note_end
))) : NULL
;
7797 tmp1
= g_strdup_printf(SIPE_PUB_XML_NOTE
,
7800 publication_note_200
? publication_note_200
->version
: 0,
7802 start_time_attr
? start_time_attr
: "",
7803 end_time_attr
? end_time_attr
: "",
7806 tmp2
= g_strdup_printf(SIPE_PUB_XML_NOTE
,
7809 publication_note_300
? publication_note_300
->version
: 0,
7811 start_time_attr
? start_time_attr
: "",
7812 end_time_attr
? end_time_attr
: "",
7815 tmp3
= g_strdup_printf(SIPE_PUB_XML_NOTE
,
7818 publication_note_400
? publication_note_400
->version
: 0,
7820 start_time_attr
? start_time_attr
: "",
7821 end_time_attr
? end_time_attr
: "",
7824 tmp1
= g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR
,
7828 publication_note_200
? publication_note_200
->version
: 0,
7830 tmp2
= g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR
,
7834 publication_note_200
? publication_note_200
->version
: 0,
7836 tmp3
= g_strdup_printf( SIPE_PUB_XML_PUBLICATION_CLEAR
,
7840 publication_note_200
? publication_note_200
->version
: 0,
7843 res
= g_strconcat(tmp1
, tmp2
, tmp3
, NULL
);
7845 g_free(start_time_attr
);
7846 g_free(end_time_attr
);
7856 * Returns 'calendarData' XML part with WorkingHours for publication.
7857 * Must be g_free'd after use.
7860 sipe_publish_get_category_cal_working_hours(struct sipe_account_data
*sip
)
7862 struct sipe_ews
* ews
= sip
->ews
;
7864 /* key is <category><instance><container> */
7865 gchar
*key_cal_1
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 1);
7866 gchar
*key_cal_100
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 100);
7867 gchar
*key_cal_200
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 200);
7868 gchar
*key_cal_300
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 300);
7869 gchar
*key_cal_400
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 400);
7870 gchar
*key_cal_32000
= g_strdup_printf("<%s><%u><%u>", "calendarData", 0, 32000);
7872 struct sipe_publication
*publication_cal_1
=
7873 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_1
);
7874 struct sipe_publication
*publication_cal_100
=
7875 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_100
);
7876 struct sipe_publication
*publication_cal_200
=
7877 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_200
);
7878 struct sipe_publication
*publication_cal_300
=
7879 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_300
);
7880 struct sipe_publication
*publication_cal_400
=
7881 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_400
);
7882 struct sipe_publication
*publication_cal_32000
=
7883 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_32000
);
7885 const char *n1
= ews
? ews
->working_hours_xml_str
: NULL
;
7886 const char *n2
= publication_cal_300
? publication_cal_300
->working_hours_xml_str
: NULL
;
7889 g_free(key_cal_100
);
7890 g_free(key_cal_200
);
7891 g_free(key_cal_300
);
7892 g_free(key_cal_400
);
7893 g_free(key_cal_32000
);
7895 if (!ews
|| is_empty(ews
->email
) || is_empty(ews
->working_hours_xml_str
)) {
7896 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: no data to publish, exiting");
7900 if (sipe_strequal(n1
, n2
))
7902 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_working_hours: WorkingHours has NOT changed. Exiting.");
7903 return NULL
; /* nothing to update */
7906 return g_strdup_printf(SIPE_PUB_XML_WORKING_HOURS
,
7908 publication_cal_1
? publication_cal_1
->version
: 0,
7910 ews
->working_hours_xml_str
,
7912 publication_cal_100
? publication_cal_100
->version
: 0,
7914 publication_cal_200
? publication_cal_200
->version
: 0,
7916 ews
->working_hours_xml_str
,
7918 publication_cal_300
? publication_cal_300
->version
: 0,
7920 ews
->working_hours_xml_str
,
7921 /* 400 - Personal */
7922 publication_cal_400
? publication_cal_400
->version
: 0,
7924 ews
->working_hours_xml_str
,
7925 /* 32000 - Blocked */
7926 publication_cal_32000
? publication_cal_32000
->version
: 0
7931 * Returns 'calendarData' XML part with FreeBusy for publication.
7932 * Must be g_free'd after use.
7935 sipe_publish_get_category_cal_free_busy(struct sipe_account_data
*sip
)
7937 struct sipe_ews
* ews
= sip
->ews
;
7938 guint cal_data_instance
= sipe_get_pub_instance(sip
, SIPE_PUB_CALENDAR_DATA
);
7940 char *free_busy_base64
;
7945 /* key is <category><instance><container> */
7946 gchar
*key_cal_1
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 1);
7947 gchar
*key_cal_100
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 100);
7948 gchar
*key_cal_200
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 200);
7949 gchar
*key_cal_300
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 300);
7950 gchar
*key_cal_400
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 400);
7951 gchar
*key_cal_32000
= g_strdup_printf("<%s><%u><%u>", "calendarData", cal_data_instance
, 32000);
7953 struct sipe_publication
*publication_cal_1
=
7954 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_1
);
7955 struct sipe_publication
*publication_cal_100
=
7956 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_100
);
7957 struct sipe_publication
*publication_cal_200
=
7958 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_200
);
7959 struct sipe_publication
*publication_cal_300
=
7960 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_300
);
7961 struct sipe_publication
*publication_cal_400
=
7962 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_400
);
7963 struct sipe_publication
*publication_cal_32000
=
7964 g_hash_table_lookup(g_hash_table_lookup(sip
->our_publications
, "calendarData"), key_cal_32000
);
7967 g_free(key_cal_100
);
7968 g_free(key_cal_200
);
7969 g_free(key_cal_300
);
7970 g_free(key_cal_400
);
7971 g_free(key_cal_32000
);
7973 if (!ews
|| is_empty(ews
->email
) || !ews
->fb_start
|| is_empty(ews
->free_busy
)) {
7974 SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: no data to publish, exiting");
7978 fb_start_str
= sipe_utils_time_to_str(ews
->fb_start
);
7979 free_busy_base64
= sipe_cal_get_freebusy_base64(ews
->free_busy
);
7981 st
= publication_cal_300
? publication_cal_300
->fb_start_str
: NULL
;
7982 fb
= publication_cal_300
? publication_cal_300
->free_busy_base64
: NULL
;
7984 /* we will rebuplish the same data to refresh publication time,
7985 * so if data from multiple sources, most recent will be choosen
7987 //if (sipe_strequal(st, fb_start_str) && sipe_strequal(fb, free_busy_base64))
7989 // SIPE_DEBUG_INFO_NOFORMAT("sipe_publish_get_category_cal_free_busy: FreeBusy has NOT changed. Exiting.");
7990 // g_free(fb_start_str);
7991 // g_free(free_busy_base64);
7992 // return NULL; /* nothing to update */
7995 res
= g_strdup_printf(SIPE_PUB_XML_FREE_BUSY
,
7998 publication_cal_1
? publication_cal_1
->version
: 0,
8001 publication_cal_100
? publication_cal_100
->version
: 0,
8004 publication_cal_200
? publication_cal_200
->version
: 0,
8010 publication_cal_300
? publication_cal_300
->version
: 0,
8014 /* 400 - Personal */
8016 publication_cal_400
? publication_cal_400
->version
: 0,
8020 /* 32000 - Blocked */
8022 publication_cal_32000
? publication_cal_32000
->version
: 0
8025 g_free(fb_start_str
);
8026 g_free(free_busy_base64
);
8030 static void send_presence_publish(struct sipe_account_data
*sip
, const char *publications
)
8037 uri
= sip_uri_self(sip
);
8038 doc
= g_strdup_printf(SIPE_SEND_PRESENCE
,
8042 tmp
= get_contact(sip
);
8043 hdr
= g_strdup_printf("Contact: %s\r\n"
8044 "Content-Type: application/msrtc-category-publish+xml\r\n", tmp
);
8046 send_sip_request(sip
->gc
, "SERVICE", uri
, uri
, hdr
, doc
, NULL
, process_send_presence_category_publish_response
);
8055 send_publish_category_initial(struct sipe_account_data
*sip
)
8057 gchar
*pub_device
= sipe_publish_get_category_device(sip
);
8059 gchar
*publications
;
8061 g_free(sip
->status
);
8062 sip
->status
= g_strdup(SIPE_STATUS_ID_AVAILABLE
); /* our initial state */
8064 pub_machine
= sipe_publish_get_category_state_machine(sip
);
8065 publications
= g_strdup_printf("%s%s",
8067 pub_machine
? pub_machine
: "");
8069 g_free(pub_machine
);
8071 send_presence_publish(sip
, publications
);
8072 g_free(publications
);
8076 send_presence_category_publish(struct sipe_account_data
*sip
)
8078 gchar
*pub_state
= sipe_is_user_state(sip
) ?
8079 sipe_publish_get_category_state_user(sip
) :
8080 sipe_publish_get_category_state_machine(sip
);
8081 gchar
*pub_note
= sipe_publish_get_category_note(sip
,
8083 sip
->is_oof_note
? "OOF" : "personal",
8086 gchar
*publications
;
8088 if (!pub_state
&& !pub_note
) {
8089 SIPE_DEBUG_INFO_NOFORMAT("send_presence_category_publish: nothing has changed. Exiting.");
8093 publications
= g_strdup_printf("%s%s",
8094 pub_state
? pub_state
: "",
8095 pub_note
? pub_note
: "");
8100 send_presence_publish(sip
, publications
);
8101 g_free(publications
);
8105 * Publishes self status
8106 * based on own calendar information.
8111 publish_calendar_status_self(struct sipe_account_data
*sip
)
8113 struct sipe_cal_event
* event
= NULL
;
8114 gchar
*pub_cal_working_hours
= NULL
;
8115 gchar
*pub_cal_free_busy
= NULL
;
8116 gchar
*pub_calendar
= NULL
;
8117 gchar
*pub_calendar2
= NULL
;
8118 gchar
*pub_oof_note
= NULL
;
8119 const gchar
*oof_note
;
8120 time_t oof_start
= 0;
8124 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() no calendar data.");
8128 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self() started.");
8129 if (sip
->ews
->cal_events
) {
8130 event
= sipe_cal_get_event(sip
->ews
->cal_events
, time(NULL
));
8134 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: current event is NULL");
8136 char *desc
= sipe_cal_event_describe(event
);
8137 SIPE_DEBUG_INFO("publish_calendar_status_self: current event is:\n%s", desc
? desc
: "");
8143 OOF publish, Busy clean
8145 OOF clean, Busy publish
8147 OOF clean, Busy clean
8149 if (event
&& event
->cal_status
== SIPE_CAL_OOF
) {
8150 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, event
, sip
->ews
->email
, SIPE_CAL_OOF
);
8151 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_BUSY
);
8152 } else if (event
&& event
->cal_status
== SIPE_CAL_BUSY
) {
8153 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_OOF
);
8154 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, event
, sip
->ews
->email
, SIPE_CAL_BUSY
);
8156 pub_calendar
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_OOF
);
8157 pub_calendar2
= sipe_publish_get_category_state_calendar(sip
, NULL
, sip
->ews
->email
, SIPE_CAL_BUSY
);
8160 oof_note
= sipe_ews_get_oof_note(sip
->ews
);
8161 if (sipe_strequal("Scheduled", sip
->ews
->oof_state
)) {
8162 oof_start
= sip
->ews
->oof_start
;
8163 oof_end
= sip
->ews
->oof_end
;
8165 pub_oof_note
= sipe_publish_get_category_note(sip
, oof_note
, "OOF", oof_start
, oof_end
);
8167 pub_cal_working_hours
= sipe_publish_get_category_cal_working_hours(sip
);
8168 pub_cal_free_busy
= sipe_publish_get_category_cal_free_busy(sip
);
8170 if (!pub_cal_working_hours
&& !pub_cal_free_busy
&& !pub_calendar
&& !pub_calendar2
&& !pub_oof_note
) {
8171 SIPE_DEBUG_INFO_NOFORMAT("publish_calendar_status_self: nothing has changed.");
8173 gchar
*publications
= g_strdup_printf("%s%s%s%s%s",
8174 pub_cal_working_hours
? pub_cal_working_hours
: "",
8175 pub_cal_free_busy
? pub_cal_free_busy
: "",
8176 pub_calendar
? pub_calendar
: "",
8177 pub_calendar2
? pub_calendar2
: "",
8178 pub_oof_note
? pub_oof_note
: "");
8180 send_presence_publish(sip
, publications
);
8181 g_free(publications
);
8184 g_free(pub_cal_working_hours
);
8185 g_free(pub_cal_free_busy
);
8186 g_free(pub_calendar
);
8187 g_free(pub_calendar2
);
8188 g_free(pub_oof_note
);
8190 /* repeat scheduling */
8191 sipe_sched_calendar_status_self_publish(sip
, time(NULL
));
8194 static void send_presence_status(struct sipe_account_data
*sip
)
8196 PurpleStatus
* status
= purple_account_get_active_status(sip
->account
);
8198 if (!status
) return;
8200 SIPE_DEBUG_INFO("send_presence_status: status: %s (%s)",
8201 purple_status_get_id(status
) ? purple_status_get_id(status
) : "",
8202 sipe_is_user_state(sip
) ? "USER" : "MACHINE");
8205 send_presence_category_publish(sip
);
8207 send_presence_soap(sip
, FALSE
);
8211 static void process_input_message(struct sipe_account_data
*sip
,struct sipmsg
*msg
)
8213 gboolean found
= FALSE
;
8214 const char *method
= msg
->method
? msg
->method
: "NOT FOUND";
8215 SIPE_DEBUG_INFO("msg->response(%d),msg->method(%s)", msg
->response
,method
);
8216 if (msg
->response
== 0) { /* request */
8217 if (sipe_strequal(method
, "MESSAGE")) {
8218 process_incoming_message(sip
, msg
);
8220 } else if (sipe_strequal(method
, "NOTIFY")) {
8221 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_notify");
8222 process_incoming_notify(sip
, msg
, TRUE
, FALSE
);
8224 } else if (sipe_strequal(method
, "BENOTIFY")) {
8225 SIPE_DEBUG_INFO_NOFORMAT("send->process_incoming_benotify");
8226 process_incoming_notify(sip
, msg
, TRUE
, TRUE
);
8228 } else if (sipe_strequal(method
, "INVITE")) {
8229 process_incoming_invite(sip
, msg
);
8231 } else if (sipe_strequal(method
, "REFER")) {
8232 process_incoming_refer(sip
, msg
);
8234 } else if (sipe_strequal(method
, "OPTIONS")) {
8235 process_incoming_options(sip
, msg
);
8237 } else if (sipe_strequal(method
, "INFO")) {
8238 process_incoming_info(sip
, msg
);
8240 } else if (sipe_strequal(method
, "ACK")) {
8241 // ACK's don't need any response
8243 } else if (sipe_strequal(method
, "SUBSCRIBE")) {
8244 // LCS 2005 sends us these - just respond 200 OK
8246 send_sip_response(sip
->gc
, msg
, 200, "OK", NULL
);
8247 } else if (sipe_strequal(method
, "BYE")) {
8248 process_incoming_bye(sip
, msg
);
8251 send_sip_response(sip
->gc
, msg
, 501, "Not implemented", NULL
);
8253 } else { /* response */
8254 struct transaction
*trans
= transactions_find(sip
, msg
);
8256 if (msg
->response
== 407) {
8257 gchar
*resend
, *auth
;
8260 if (sip
->proxy
.retries
> 30) return;
8261 sip
->proxy
.retries
++;
8262 /* do proxy authentication */
8264 ptmp
= sipmsg_find_header(msg
, "Proxy-Authenticate");
8266 fill_auth(ptmp
, &sip
->proxy
);
8267 auth
= auth_header(sip
, &sip
->proxy
, trans
->msg
);
8268 sipmsg_remove_header_now(trans
->msg
, "Proxy-Authorization");
8269 sipmsg_add_header_now_pos(trans
->msg
, "Proxy-Authorization", auth
, 5);
8271 resend
= sipmsg_to_string(trans
->msg
);
8272 /* resend request */
8273 sendout_pkt(sip
->gc
, resend
);
8276 if (msg
->response
< 200) {
8277 /* ignore provisional response */
8278 SIPE_DEBUG_INFO("got provisional (%d) response, ignoring", msg
->response
);
8280 sip
->proxy
.retries
= 0;
8281 if (sipe_strequal(trans
->msg
->method
, "REGISTER")) {
8282 if (msg
->response
== 401)
8284 sip
->registrar
.retries
++;
8288 sip
->registrar
.retries
= 0;
8290 SIPE_DEBUG_INFO("RE-REGISTER CSeq: %d", sip
->cseq
);
8292 if (msg
->response
== 401) {
8293 gchar
*resend
, *auth
, *ptmp
;
8294 const char* auth_scheme
;
8296 if (sip
->registrar
.retries
> 4) return;
8297 sip
->registrar
.retries
++;
8299 auth_scheme
= sipe_get_auth_scheme_name(sip
);
8300 ptmp
= sipmsg_find_auth_header(msg
, auth_scheme
);
8302 SIPE_DEBUG_INFO("process_input_message - Auth header: %s", ptmp
? ptmp
: "");
8304 char *tmp2
= g_strconcat(_("Incompatible authentication scheme chosen"), ": ", auth_scheme
, NULL
);
8305 sip
->gc
->wants_to_die
= TRUE
;
8306 purple_connection_error(sip
->gc
, tmp2
);
8311 fill_auth(ptmp
, &sip
->registrar
);
8312 auth
= auth_header(sip
, &sip
->registrar
, trans
->msg
);
8313 sipmsg_remove_header_now(trans
->msg
, "Authorization");
8314 sipmsg_add_header_now_pos(trans
->msg
, "Authorization", auth
, 5);
8316 resend
= sipmsg_to_string(trans
->msg
);
8317 /* resend request */
8318 sendout_pkt(sip
->gc
, resend
);
8323 if (trans
->callback
) {
8324 SIPE_DEBUG_INFO_NOFORMAT("process_input_message - we have a transaction callback");
8325 /* call the callback to process response*/
8326 (trans
->callback
)(sip
, msg
, trans
);
8329 SIPE_DEBUG_INFO("process_input_message - removing CSeq %d", sip
->cseq
);
8330 transactions_remove(sip
, trans
);
8336 SIPE_DEBUG_INFO_NOFORMAT("received response to unknown transaction");
8340 SIPE_DEBUG_INFO("received a unknown sip message with method %s and response %d", method
, msg
->response
);
8344 static void process_input(struct sipe_account_data
*sip
, struct sip_connection
*conn
)
8353 /* according to the RFC remove CRLF at the beginning */
8354 while (*cur
== '\r' || *cur
== '\n') {
8357 if (cur
!= conn
->inbuf
) {
8358 memmove(conn
->inbuf
, cur
, conn
->inbufused
- (cur
- conn
->inbuf
));
8359 conn
->inbufused
= strlen(conn
->inbuf
);
8362 /* Received a full Header? */
8363 sip
->processing_input
= TRUE
;
8364 while (sip
->processing_input
&&
8365 ((cur
= strstr(conn
->inbuf
, "\r\n\r\n")) != NULL
)) {
8366 time_t currtime
= time(NULL
);
8369 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime
), tmp
= fix_newlines(conn
->inbuf
));
8371 msg
= sipmsg_parse_header(conn
->inbuf
);
8374 restlen
= conn
->inbufused
- (cur
- conn
->inbuf
);
8375 if (msg
&& restlen
>= msg
->bodylen
) {
8376 dummy
= g_malloc(msg
->bodylen
+ 1);
8377 memcpy(dummy
, cur
, msg
->bodylen
);
8378 dummy
[msg
->bodylen
] = '\0';
8380 cur
+= msg
->bodylen
;
8381 memmove(conn
->inbuf
, cur
, conn
->inbuflen
- (cur
- conn
->inbuf
));
8382 conn
->inbufused
= strlen(conn
->inbuf
);
8385 SIPE_DEBUG_INFO("process_input: body too short (%d < %d, strlen %d) - ignoring message", restlen
, msg
->bodylen
, (int)strlen(conn
->inbuf
));
8392 SIPE_DEBUG_INFO("body:\n%s", msg->body);
8395 // Verify the signature before processing it
8396 if (sip
->registrar
.gssapi_context
) {
8397 struct sipmsg_breakdown msgbd
;
8398 gchar
*signature_input_str
;
8401 sipmsg_breakdown_parse(&msgbd
, sip
->registrar
.realm
, sip
->registrar
.target
);
8402 signature_input_str
= sipmsg_breakdown_get_string(sip
->registrar
.version
, &msgbd
);
8404 rspauth
= sipmsg_find_part_of_header(sipmsg_find_header(msg
, "Authentication-Info"), "rspauth=\"", "\"", NULL
);
8406 if (rspauth
!= NULL
) {
8407 if (!sip_sec_verify_signature(sip
->registrar
.gssapi_context
, signature_input_str
, rspauth
)) {
8408 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature validated");
8409 process_input_message(sip
, msg
);
8411 SIPE_DEBUG_INFO_NOFORMAT("incoming message's signature is invalid.");
8412 purple_connection_error(sip
->gc
, _("Invalid message signature received"));
8413 sip
->gc
->wants_to_die
= TRUE
;
8415 } else if (msg
->response
== 401) {
8416 purple_connection_error(sip
->gc
, _("Authentication failed"));
8417 sip
->gc
->wants_to_die
= TRUE
;
8419 g_free(signature_input_str
);
8422 sipmsg_breakdown_free(&msgbd
);
8424 process_input_message(sip
, msg
);
8431 static void sipe_udp_process(gpointer data
, gint source
,
8432 SIPE_UNUSED_PARAMETER PurpleInputCondition con
)
8434 PurpleConnection
*gc
= data
;
8435 struct sipe_account_data
*sip
= gc
->proto_data
;
8438 static char buffer
[65536];
8439 if ((len
= recv(source
, buffer
, sizeof(buffer
) - 1, 0)) > 0) {
8440 time_t currtime
= time(NULL
);
8443 SIPE_DEBUG_INFO("received - %s######\n%s\n#######", ctime(&currtime
), buffer
);
8444 msg
= sipmsg_parse_msg(buffer
);
8445 if (msg
) process_input_message(sip
, msg
);
8449 static void sipe_invalidate_ssl_connection(PurpleConnection
*gc
, const char *msg
, const char *debug
)
8451 struct sipe_account_data
*sip
= gc
->proto_data
;
8452 PurpleSslConnection
*gsc
= sip
->gsc
;
8454 SIPE_DEBUG_ERROR("%s", debug
);
8455 purple_connection_error(gc
, msg
);
8457 /* Invalidate this connection. Next send will open a new one */
8459 connection_remove(sip
, gsc
->fd
);
8460 purple_ssl_close(gsc
);
8466 static void sipe_input_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
8467 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
8469 PurpleConnection
*gc
= data
;
8470 struct sipe_account_data
*sip
;
8471 struct sip_connection
*conn
;
8473 gboolean firstread
= TRUE
;
8475 /* NOTE: This check *IS* necessary */
8476 if (!PURPLE_CONNECTION_IS_VALID(gc
)) {
8477 purple_ssl_close(gsc
);
8481 sip
= gc
->proto_data
;
8482 conn
= connection_find(sip
, gsc
->fd
);
8484 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found; Please try to connect again.");
8485 gc
->wants_to_die
= TRUE
;
8486 purple_connection_error(gc
, _("Connection not found. Please try to connect again"));
8490 /* Read all available data from the SSL connection */
8492 /* Increase input buffer size as needed */
8493 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
8494 conn
->inbuflen
+= SIMPLE_BUF_INC
;
8495 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
8496 SIPE_DEBUG_INFO("sipe_input_cb_ssl: new input buffer length %d", conn
->inbuflen
);
8499 /* Try to read as much as there is space left in the buffer */
8500 readlen
= conn
->inbuflen
- conn
->inbufused
- 1;
8501 len
= purple_ssl_read(gsc
, conn
->inbuf
+ conn
->inbufused
, readlen
);
8503 if (len
< 0 && errno
== EAGAIN
) {
8504 /* Try again later */
8506 } else if (len
< 0) {
8507 sipe_invalidate_ssl_connection(gc
, _("SSL read error"), "SSL read error\n");
8509 } else if (firstread
&& (len
== 0)) {
8510 sipe_invalidate_ssl_connection(gc
, _("Server has disconnected"), "Server has disconnected\n");
8514 conn
->inbufused
+= len
;
8517 /* Equivalence indicates that there is possibly more data to read */
8518 } while (len
== readlen
);
8520 conn
->inbuf
[conn
->inbufused
] = '\0';
8521 process_input(sip
, conn
);
8525 static void sipe_input_cb(gpointer data
, gint source
,
8526 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
8528 PurpleConnection
*gc
= data
;
8529 struct sipe_account_data
*sip
= gc
->proto_data
;
8531 struct sip_connection
*conn
= connection_find(sip
, source
);
8533 SIPE_DEBUG_ERROR_NOFORMAT("Connection not found!");
8537 if (conn
->inbuflen
< conn
->inbufused
+ SIMPLE_BUF_INC
) {
8538 conn
->inbuflen
+= SIMPLE_BUF_INC
;
8539 conn
->inbuf
= g_realloc(conn
->inbuf
, conn
->inbuflen
);
8542 len
= read(source
, conn
->inbuf
+ conn
->inbufused
, SIMPLE_BUF_INC
- 1);
8544 if (len
< 0 && errno
== EAGAIN
)
8546 else if (len
<= 0) {
8547 SIPE_DEBUG_INFO_NOFORMAT("sipe_input_cb: read error");
8548 connection_remove(sip
, source
);
8549 if (sip
->fd
== source
) sip
->fd
= -1;
8553 conn
->inbufused
+= len
;
8554 conn
->inbuf
[conn
->inbufused
] = '\0';
8556 process_input(sip
, conn
);
8559 /* Callback for new connections on incoming TCP port */
8560 static void sipe_newconn_cb(gpointer data
, gint source
,
8561 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
8563 PurpleConnection
*gc
= data
;
8564 struct sipe_account_data
*sip
= gc
->proto_data
;
8565 struct sip_connection
*conn
;
8567 int newfd
= accept(source
, NULL
, NULL
);
8569 conn
= connection_create(sip
, newfd
);
8571 conn
->inputhandler
= purple_input_add(newfd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
8574 static void login_cb(gpointer data
, gint source
,
8575 SIPE_UNUSED_PARAMETER
const gchar
*error_message
)
8577 PurpleConnection
*gc
= data
;
8578 struct sipe_account_data
*sip
;
8579 struct sip_connection
*conn
;
8581 if (!PURPLE_CONNECTION_IS_VALID(gc
))
8589 purple_connection_error(gc
, _("Could not connect"));
8593 sip
= gc
->proto_data
;
8595 sip
->last_keepalive
= time(NULL
);
8597 conn
= connection_create(sip
, source
);
8601 conn
->inputhandler
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_input_cb
, gc
);
8604 static void login_cb_ssl(gpointer data
, PurpleSslConnection
*gsc
,
8605 SIPE_UNUSED_PARAMETER PurpleInputCondition cond
)
8607 struct sipe_account_data
*sip
= sipe_setup_ssl(data
, gsc
);
8608 if (sip
== NULL
) return;
8613 static guint
sipe_ht_hash_nick(const char *nick
)
8615 char *lc
= g_utf8_strdown(nick
, -1);
8616 guint bucket
= g_str_hash(lc
);
8622 static gboolean
sipe_ht_equals_nick(const char *nick1
, const char *nick2
)
8624 char *nick1_norm
= NULL
;
8625 char *nick2_norm
= NULL
;
8628 if (nick1
== NULL
&& nick2
== NULL
) return TRUE
;
8629 if (nick1
== NULL
|| nick2
== NULL
||
8630 !g_utf8_validate(nick1
, -1, NULL
) ||
8631 !g_utf8_validate(nick2
, -1, NULL
)) return FALSE
;
8633 nick1_norm
= g_utf8_casefold(nick1
, -1);
8634 nick2_norm
= g_utf8_casefold(nick2
, -1);
8635 equal
= g_utf8_collate(nick2_norm
, nick2_norm
) == 0;
8642 static void sipe_udp_host_resolved_listen_cb(int listenfd
, gpointer data
)
8644 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
8646 sip
->listen_data
= NULL
;
8648 if (listenfd
== -1) {
8649 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
8655 sip
->listenport
= purple_network_get_port_from_fd(sip
->fd
);
8656 sip
->listenfd
= sip
->fd
;
8658 sip
->listenpa
= purple_input_add(sip
->fd
, PURPLE_INPUT_READ
, sipe_udp_process
, sip
->gc
);
8660 sip
->resendtimeout
= purple_timeout_add(2500, (GSourceFunc
) resend_timeout
, sip
);
8664 static void sipe_udp_host_resolved(GSList
*hosts
, gpointer data
,
8665 SIPE_UNUSED_PARAMETER
const char *error_message
)
8667 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
8669 sip
->query_data
= NULL
;
8671 if (!hosts
|| !hosts
->data
) {
8672 purple_connection_error(sip
->gc
, _("Could not resolve hostname"));
8676 hosts
= g_slist_remove(hosts
, hosts
->data
);
8677 g_free(sip
->serveraddr
);
8678 sip
->serveraddr
= hosts
->data
;
8679 hosts
= g_slist_remove(hosts
, hosts
->data
);
8681 void *tmp
= hosts
->data
;
8682 hosts
= g_slist_remove(hosts
, tmp
);
8683 hosts
= g_slist_remove(hosts
, tmp
);
8687 /* create socket for incoming connections */
8688 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_DGRAM
,
8689 sipe_udp_host_resolved_listen_cb
, sip
);
8690 if (sip
->listen_data
== NULL
) {
8691 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
8696 static const struct sipe_service_data
*current_service
= NULL
;
8698 static void sipe_ssl_connect_failure(SIPE_UNUSED_PARAMETER PurpleSslConnection
*gsc
,
8699 PurpleSslErrorType error
,
8702 PurpleConnection
*gc
= data
;
8703 struct sipe_account_data
*sip
;
8705 /* If the connection is already disconnected, we don't need to do anything else */
8706 if (!PURPLE_CONNECTION_IS_VALID(gc
))
8709 sip
= gc
->proto_data
;
8710 current_service
= sip
->service_data
;
8711 if (current_service
) {
8712 SIPE_DEBUG_INFO("current_service: transport '%s' service '%s'",
8713 current_service
->transport
? current_service
->transport
: "NULL",
8714 current_service
->service
? current_service
->service
: "NULL");
8721 case PURPLE_SSL_CONNECT_FAILED
:
8722 purple_connection_error(gc
, _("Connection failed"));
8724 case PURPLE_SSL_HANDSHAKE_FAILED
:
8725 purple_connection_error(gc
, _("SSL handshake failed"));
8727 case PURPLE_SSL_CERTIFICATE_INVALID
:
8728 purple_connection_error(gc
, _("SSL certificate invalid"));
8734 sipe_tcp_connect_listen_cb(int listenfd
, gpointer data
)
8736 struct sipe_account_data
*sip
= (struct sipe_account_data
*) data
;
8737 PurpleProxyConnectData
*connect_data
;
8739 sip
->listen_data
= NULL
;
8741 sip
->listenfd
= listenfd
;
8742 if (sip
->listenfd
== -1) {
8743 purple_connection_error(sip
->gc
, _("Could not create listen socket"));
8747 SIPE_DEBUG_INFO("listenfd: %d", sip
->listenfd
);
8748 //sip->listenport = purple_network_get_port_from_fd(sip->listenfd);
8749 sip
->listenport
= purple_network_get_port_from_fd(sip
->listenfd
);
8750 sip
->listenpa
= purple_input_add(sip
->listenfd
, PURPLE_INPUT_READ
,
8751 sipe_newconn_cb
, sip
->gc
);
8752 SIPE_DEBUG_INFO("connecting to %s port %d",
8753 sip
->realhostname
, sip
->realport
);
8754 /* open tcp connection to the server */
8755 connect_data
= purple_proxy_connect(sip
->gc
, sip
->account
, sip
->realhostname
,
8756 sip
->realport
, login_cb
, sip
->gc
);
8758 if (connect_data
== NULL
) {
8759 purple_connection_error(sip
->gc
, _("Could not create socket"));
8763 static void create_connection(struct sipe_account_data
*sip
, gchar
*hostname
, int port
)
8765 PurpleAccount
*account
= sip
->account
;
8766 PurpleConnection
*gc
= sip
->gc
;
8769 port
= (sip
->transport
== SIPE_TRANSPORT_TLS
) ? 5061 : 5060;
8772 sip
->realhostname
= hostname
;
8773 sip
->realport
= port
;
8775 SIPE_DEBUG_INFO("create_connection - hostname: %s port: %d",
8778 /* TODO: is there a good default grow size? */
8779 if (sip
->transport
!= SIPE_TRANSPORT_UDP
)
8780 sip
->txbuf
= purple_circ_buffer_new(0);
8782 if (sip
->transport
== SIPE_TRANSPORT_TLS
) {
8784 if (!purple_ssl_is_supported()) {
8785 gc
->wants_to_die
= TRUE
;
8786 purple_connection_error(gc
, _("SSL support is not installed. Either install SSL support or configure a different connection type in the account editor"));
8790 SIPE_DEBUG_INFO_NOFORMAT("using SSL");
8792 sip
->gsc
= purple_ssl_connect(account
, hostname
, port
,
8793 login_cb_ssl
, sipe_ssl_connect_failure
, gc
);
8794 if (sip
->gsc
== NULL
) {
8795 purple_connection_error(gc
, _("Could not create SSL context"));
8798 } else if (sip
->transport
== SIPE_TRANSPORT_UDP
) {
8800 SIPE_DEBUG_INFO_NOFORMAT("using UDP");
8802 sip
->query_data
= purple_dnsquery_a(hostname
, port
, sipe_udp_host_resolved
, sip
);
8803 if (sip
->query_data
== NULL
) {
8804 purple_connection_error(gc
, _("Could not resolve hostname"));
8808 SIPE_DEBUG_INFO_NOFORMAT("using TCP");
8809 /* create socket for incoming connections */
8810 sip
->listen_data
= purple_network_listen_range(5060, 5160, SOCK_STREAM
,
8811 sipe_tcp_connect_listen_cb
, sip
);
8812 if (sip
->listen_data
== NULL
) {
8813 purple_connection_error(gc
, _("Could not create listen socket"));
8819 /* Service list for autodection */
8820 static const struct sipe_service_data service_autodetect
[] = {
8821 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
8822 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
8823 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
8824 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
8828 /* Service list for SSL/TLS */
8829 static const struct sipe_service_data service_tls
[] = {
8830 { "sipinternaltls", "tcp", SIPE_TRANSPORT_TLS
}, /* for internal TLS connections */
8831 { "sip", "tls", SIPE_TRANSPORT_TLS
}, /* for external TLS connections */
8835 /* Service list for TCP */
8836 static const struct sipe_service_data service_tcp
[] = {
8837 { "sipinternal", "tcp", SIPE_TRANSPORT_TCP
}, /* for internal TCP connections */
8838 { "sip", "tcp", SIPE_TRANSPORT_TCP
}, /*.for external TCP connections */
8842 /* Service list for UDP */
8843 static const struct sipe_service_data service_udp
[] = {
8844 { "sip", "udp", SIPE_TRANSPORT_UDP
},
8848 static void srvresolved(PurpleSrvResponse
*, int, gpointer
);
8849 static void resolve_next_service(struct sipe_account_data
*sip
,
8850 const struct sipe_service_data
*start
)
8853 sip
->service_data
= start
;
8855 sip
->service_data
++;
8856 if (sip
->service_data
->service
== NULL
) {
8858 /* Try connecting to the SIP hostname directly */
8859 SIPE_DEBUG_INFO_NOFORMAT("no SRV records found; using SIP domain as fallback");
8860 if (sip
->auto_transport
) {
8861 // If SSL is supported, default to using it; OCS servers aren't configured
8862 // by default to accept TCP
8863 // TODO: LCS 2007 is the opposite, only configured by default to accept TCP
8864 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
8865 SIPE_DEBUG_INFO_NOFORMAT("set transport type..");
8868 hostname
= g_strdup(sip
->sipdomain
);
8869 create_connection(sip
, hostname
, 0);
8874 /* Try to resolve next service */
8875 sip
->srv_query_data
= purple_srv_resolve(sip
->service_data
->service
,
8876 sip
->service_data
->transport
,
8881 static void srvresolved(PurpleSrvResponse
*resp
, int results
, gpointer data
)
8883 struct sipe_account_data
*sip
= data
;
8885 sip
->srv_query_data
= NULL
;
8887 /* find the host to connect to */
8889 gchar
*hostname
= g_strdup(resp
->hostname
);
8890 int port
= resp
->port
;
8891 SIPE_DEBUG_INFO("srvresolved - SRV hostname: %s port: %d",
8895 sip
->transport
= sip
->service_data
->type
;
8897 create_connection(sip
, hostname
, port
);
8899 resolve_next_service(sip
, NULL
);
8903 static void sipe_login(PurpleAccount
*account
)
8905 PurpleConnection
*gc
;
8906 struct sipe_account_data
*sip
;
8907 gchar
**signinname_login
, **userserver
;
8908 const char *transport
;
8911 const char *username
= purple_account_get_username(account
);
8912 gc
= purple_account_get_connection(account
);
8914 SIPE_DEBUG_INFO("sipe_login: username '%s'", username
);
8916 if (strpbrk(username
, "\t\v\r\n") != NULL
) {
8917 gc
->wants_to_die
= TRUE
;
8918 purple_connection_error(gc
, _("SIP Exchange user name contains invalid characters"));
8922 gc
->proto_data
= sip
= g_new0(struct sipe_account_data
, 1);
8923 gc
->flags
|= PURPLE_CONNECTION_HTML
| PURPLE_CONNECTION_FORMATTING_WBFO
| PURPLE_CONNECTION_NO_BGCOLOR
|
8924 PURPLE_CONNECTION_NO_FONTSIZE
| PURPLE_CONNECTION_NO_URLDESC
| PURPLE_CONNECTION_ALLOW_CUSTOM_SMILEY
;
8926 sip
->account
= account
;
8927 sip
->reregister_set
= FALSE
;
8928 sip
->reauthenticate_set
= FALSE
;
8929 sip
->subscribed
= FALSE
;
8930 sip
->subscribed_buddies
= FALSE
;
8931 sip
->initial_state_published
= FALSE
;
8933 /* username format: <username>,[<optional login>] */
8934 signinname_login
= g_strsplit(username
, ",", 2);
8935 SIPE_DEBUG_INFO("sipe_login: signinname[0] '%s'", signinname_login
[0]);
8937 /* ensure that username format is name@domain */
8938 if (!strchr(signinname_login
[0], '@') || g_str_has_prefix(signinname_login
[0], "@") || g_str_has_suffix(signinname_login
[0], "@")) {
8939 g_strfreev(signinname_login
);
8940 gc
->wants_to_die
= TRUE
;
8941 purple_connection_error(gc
, _("User name should be a valid SIP URI\nExample: user@company.com"));
8944 sip
->username
= g_strdup(signinname_login
[0]);
8946 /* ensure that email format is name@domain if provided */
8947 email
= purple_account_get_string(sip
->account
, "email", NULL
);
8948 if (!is_empty(email
) &&
8949 (!strchr(email
, '@') || g_str_has_prefix(email
, "@") || g_str_has_suffix(email
, "@")))
8951 gc
->wants_to_die
= TRUE
;
8952 purple_connection_error(gc
, _("Email address should be valid if provided\nExample: user@company.com"));
8955 sip
->email
= !is_empty(email
) ? g_strdup(email
) : g_strdup(sip
->username
);
8957 /* login name specified? */
8958 if (signinname_login
[1] && strlen(signinname_login
[1])) {
8959 gchar
**domain_user
= g_strsplit(signinname_login
[1], "\\", 2);
8960 gboolean has_domain
= domain_user
[1] != NULL
;
8961 SIPE_DEBUG_INFO("sipe_login: signinname[1] '%s'", signinname_login
[1]);
8962 sip
->authdomain
= has_domain
? g_strdup(domain_user
[0]) : NULL
;
8963 sip
->authuser
= g_strdup(domain_user
[has_domain
? 1 : 0]);
8964 SIPE_DEBUG_INFO("sipe_login: auth domain '%s' user '%s'",
8965 sip
->authdomain
? sip
->authdomain
: "", sip
->authuser
);
8966 g_strfreev(domain_user
);
8969 userserver
= g_strsplit(signinname_login
[0], "@", 2);
8970 SIPE_DEBUG_INFO("sipe_login: user '%s' server '%s'", userserver
[0], userserver
[1]);
8971 purple_connection_set_display_name(gc
, userserver
[0]);
8972 sip
->sipdomain
= g_strdup(userserver
[1]);
8973 g_strfreev(userserver
);
8974 g_strfreev(signinname_login
);
8976 if (strchr(sip
->username
, ' ') != NULL
) {
8977 gc
->wants_to_die
= TRUE
;
8978 purple_connection_error(gc
, _("SIP Exchange user name contains whitespace"));
8982 sip
->password
= g_strdup(purple_connection_get_password(gc
));
8984 sip
->buddies
= g_hash_table_new((GHashFunc
)sipe_ht_hash_nick
, (GEqualFunc
)sipe_ht_equals_nick
);
8985 sip
->our_publications
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8986 g_free
, (GDestroyNotify
)g_hash_table_destroy
);
8987 sip
->subscriptions
= g_hash_table_new_full(g_str_hash
, g_str_equal
,
8988 g_free
, (GDestroyNotify
)sipe_subscription_free
);
8990 sip
->filetransfers
= g_hash_table_new_full(g_str_hash
, g_str_equal
,g_free
,NULL
);
8992 purple_connection_update_progress(gc
, _("Connecting"), 1, 2);
8994 g_free(sip
->status
);
8995 sip
->status
= g_strdup(SIPE_STATUS_ID_UNKNOWN
);
8997 sip
->auto_transport
= FALSE
;
8998 transport
= purple_account_get_string(account
, "transport", "auto");
8999 userserver
= g_strsplit(purple_account_get_string(account
, "server", ""), ":", 2);
9000 if (userserver
[0]) {
9001 /* Use user specified server[:port] */
9005 port
= atoi(userserver
[1]);
9007 SIPE_DEBUG_INFO("sipe_login: user specified SIP server %s:%d",
9008 userserver
[0], port
);
9010 if (sipe_strequal(transport
, "auto")) {
9011 sip
->transport
= purple_ssl_is_supported() ? SIPE_TRANSPORT_TLS
: SIPE_TRANSPORT_TCP
;
9012 } else if (sipe_strequal(transport
, "tls")) {
9013 sip
->transport
= SIPE_TRANSPORT_TLS
;
9014 } else if (sipe_strequal(transport
, "tcp")) {
9015 sip
->transport
= SIPE_TRANSPORT_TCP
;
9017 sip
->transport
= SIPE_TRANSPORT_UDP
;
9020 create_connection(sip
, g_strdup(userserver
[0]), port
);
9022 /* Server auto-discovery */
9023 if (sipe_strequal(transport
, "auto")) {
9024 sip
->auto_transport
= TRUE
;
9025 if (current_service
&& current_service
->transport
!= NULL
&& current_service
->service
!= NULL
){
9027 resolve_next_service(sip
, current_service
);
9029 resolve_next_service(sip
, purple_ssl_is_supported() ? service_autodetect
: service_tcp
);
9031 } else if (sipe_strequal(transport
, "tls")) {
9032 resolve_next_service(sip
, service_tls
);
9033 } else if (sipe_strequal(transport
, "tcp")) {
9034 resolve_next_service(sip
, service_tcp
);
9036 resolve_next_service(sip
, service_udp
);
9039 g_strfreev(userserver
);
9042 static void sipe_connection_cleanup(struct sipe_account_data
*sip
)
9044 connection_free_all(sip
);
9049 if (sip
->query_data
!= NULL
)
9050 purple_dnsquery_destroy(sip
->query_data
);
9051 sip
->query_data
= NULL
;
9053 if (sip
->srv_query_data
!= NULL
)
9054 purple_srv_cancel(sip
->srv_query_data
);
9055 sip
->srv_query_data
= NULL
;
9057 if (sip
->listen_data
!= NULL
)
9058 purple_network_listen_cancel(sip
->listen_data
);
9059 sip
->listen_data
= NULL
;
9061 if (sip
->gsc
!= NULL
)
9062 purple_ssl_close(sip
->gsc
);
9065 sipe_auth_free(&sip
->registrar
);
9066 sipe_auth_free(&sip
->proxy
);
9069 purple_circ_buffer_destroy(sip
->txbuf
);
9072 g_free(sip
->realhostname
);
9073 sip
->realhostname
= NULL
;
9075 g_free(sip
->server_version
);
9076 sip
->server_version
= NULL
;
9079 purple_input_remove(sip
->listenpa
);
9081 if (sip
->tx_handler
)
9082 purple_input_remove(sip
->tx_handler
);
9083 sip
->tx_handler
= 0;
9084 if (sip
->resendtimeout
)
9085 purple_timeout_remove(sip
->resendtimeout
);
9086 sip
->resendtimeout
= 0;
9087 if (sip
->timeouts
) {
9088 GSList
*entry
= sip
->timeouts
;
9090 struct scheduled_action
*sched_action
= entry
->data
;
9091 SIPE_DEBUG_INFO("purple_timeout_remove: action name=%s", sched_action
->name
);
9092 purple_timeout_remove(sched_action
->timeout_handler
);
9093 if (sched_action
->destroy
) {
9094 (*sched_action
->destroy
)(sched_action
->payload
);
9096 g_free(sched_action
->name
);
9097 g_free(sched_action
);
9098 entry
= entry
->next
;
9101 g_slist_free(sip
->timeouts
);
9103 if (sip
->allow_events
) {
9104 GSList
*entry
= sip
->allow_events
;
9106 g_free(entry
->data
);
9107 entry
= entry
->next
;
9110 g_slist_free(sip
->allow_events
);
9112 if (sip
->containers
) {
9113 GSList
*entry
= sip
->containers
;
9115 free_container((struct sipe_container
*)entry
->data
);
9116 entry
= entry
->next
;
9119 g_slist_free(sip
->containers
);
9122 g_free(sip
->contact
);
9123 sip
->contact
= NULL
;
9125 g_free(sip
->regcallid
);
9126 sip
->regcallid
= NULL
;
9128 if (sip
->serveraddr
)
9129 g_free(sip
->serveraddr
);
9130 sip
->serveraddr
= NULL
;
9132 if (sip
->focus_factory_uri
)
9133 g_free(sip
->focus_factory_uri
);
9134 sip
->focus_factory_uri
= NULL
;
9137 sip
->processing_input
= FALSE
;
9140 sipe_ews_free(sip
->ews
);
9146 * A callback for g_hash_table_foreach_remove
9148 static gboolean
sipe_buddy_remove(SIPE_UNUSED_PARAMETER gpointer key
, gpointer buddy
,
9149 SIPE_UNUSED_PARAMETER gpointer user_data
)
9151 sipe_free_buddy((struct sipe_buddy
*) buddy
);
9153 /* We must return TRUE as the key/value have already been deleted */
9157 static void sipe_close(PurpleConnection
*gc
)
9159 struct sipe_account_data
*sip
= gc
->proto_data
;
9162 /* leave all conversations */
9163 sipe_session_close_all(sip
);
9164 sipe_session_remove_all(sip
);
9167 sip_csta_close(sip
);
9170 if (PURPLE_CONNECTION_IS_CONNECTED(sip
->gc
)) {
9171 /* unsubscribe all */
9172 g_hash_table_foreach(sip
->subscriptions
, sipe_unsubscribe_cb
, sip
);
9175 do_register_exp(sip
, 0);
9178 sipe_connection_cleanup(sip
);
9179 g_free(sip
->sipdomain
);
9180 g_free(sip
->username
);
9182 g_free(sip
->password
);
9183 g_free(sip
->authdomain
);
9184 g_free(sip
->authuser
);
9185 g_free(sip
->status
);
9187 g_free(sip
->user_states
);
9189 g_hash_table_foreach_steal(sip
->buddies
, sipe_buddy_remove
, NULL
);
9190 g_hash_table_destroy(sip
->buddies
);
9191 g_hash_table_destroy(sip
->our_publications
);
9192 g_hash_table_destroy(sip
->user_state_publications
);
9193 g_hash_table_destroy(sip
->subscriptions
);
9194 g_hash_table_destroy(sip
->filetransfers
);
9197 GSList
*entry
= sip
->groups
;
9199 struct sipe_group
*group
= entry
->data
;
9200 g_free(group
->name
);
9202 entry
= entry
->next
;
9205 g_slist_free(sip
->groups
);
9207 if (sip
->our_publication_keys
) {
9208 GSList
*entry
= sip
->our_publication_keys
;
9210 g_free(entry
->data
);
9211 entry
= entry
->next
;
9214 g_slist_free(sip
->our_publication_keys
);
9216 while (sip
->transactions
)
9217 transactions_remove(sip
, sip
->transactions
->data
);
9219 g_free(gc
->proto_data
);
9220 gc
->proto_data
= NULL
;
9223 static void sipe_searchresults_im_buddy(PurpleConnection
*gc
, GList
*row
,
9224 SIPE_UNUSED_PARAMETER
void *user_data
)
9226 PurpleAccount
*acct
= purple_connection_get_account(gc
);
9227 char *id
= sip_uri_from_name((gchar
*)g_list_nth_data(row
, 0));
9228 PurpleConversation
*conv
= purple_find_conversation_with_account(PURPLE_CONV_TYPE_IM
, id
, acct
);
9230 conv
= purple_conversation_new(PURPLE_CONV_TYPE_IM
, acct
, id
);
9231 purple_conversation_present(conv
);
9235 static void sipe_searchresults_add_buddy(PurpleConnection
*gc
, GList
*row
,
9236 SIPE_UNUSED_PARAMETER
void *user_data
)
9239 purple_blist_request_add_buddy(purple_connection_get_account(gc
),
9240 g_list_nth_data(row
, 0), _("Other Contacts"), g_list_nth_data(row
, 1));
9243 static gboolean
process_search_contact_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
,
9244 SIPE_UNUSED_PARAMETER
struct transaction
*trans
)
9246 PurpleNotifySearchResults
*results
;
9247 PurpleNotifySearchColumn
*column
;
9248 sipe_xml
*searchResults
;
9249 const sipe_xml
*mrow
;
9250 int match_count
= 0;
9251 gboolean more
= FALSE
;
9254 SIPE_DEBUG_INFO("process_search_contact_response: body:\n%s", msg
->body
? msg
->body
: "");
9256 searchResults
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
9257 if (!searchResults
) {
9258 SIPE_DEBUG_INFO_NOFORMAT("process_search_contact_response: no parseable searchResults");
9262 results
= purple_notify_searchresults_new();
9264 if (results
== NULL
) {
9265 SIPE_DEBUG_ERROR_NOFORMAT("purple_parse_searchreply: Unable to display the search results.");
9266 purple_notify_error(sip
->gc
, NULL
, _("Unable to display the search results"), NULL
);
9268 sipe_xml_free(searchResults
);
9272 column
= purple_notify_searchresults_column_new(_("User name"));
9273 purple_notify_searchresults_column_add(results
, column
);
9275 column
= purple_notify_searchresults_column_new(_("Name"));
9276 purple_notify_searchresults_column_add(results
, column
);
9278 column
= purple_notify_searchresults_column_new(_("Company"));
9279 purple_notify_searchresults_column_add(results
, column
);
9281 column
= purple_notify_searchresults_column_new(_("Country"));
9282 purple_notify_searchresults_column_add(results
, column
);
9284 column
= purple_notify_searchresults_column_new(_("Email"));
9285 purple_notify_searchresults_column_add(results
, column
);
9287 for (mrow
= sipe_xml_child(searchResults
, "Body/Array/row"); mrow
; mrow
= sipe_xml_twin(mrow
)) {
9290 gchar
**uri_parts
= g_strsplit(sipe_xml_attribute(mrow
, "uri"), ":", 2);
9291 row
= g_list_append(row
, g_strdup(uri_parts
[1]));
9292 g_strfreev(uri_parts
);
9294 row
= g_list_append(row
, g_strdup(sipe_xml_attribute(mrow
, "displayName")));
9295 row
= g_list_append(row
, g_strdup(sipe_xml_attribute(mrow
, "company")));
9296 row
= g_list_append(row
, g_strdup(sipe_xml_attribute(mrow
, "country")));
9297 row
= g_list_append(row
, g_strdup(sipe_xml_attribute(mrow
, "email")));
9299 purple_notify_searchresults_row_add(results
, row
);
9303 if ((mrow
= sipe_xml_child(searchResults
, "Body/directorySearch/moreAvailable")) != NULL
) {
9304 char *data
= sipe_xml_data(mrow
);
9305 more
= (g_strcasecmp(data
, "true") == 0);
9309 secondary
= g_strdup_printf(
9310 dngettext(PACKAGE_NAME
,
9311 "Found %d contact%s:",
9312 "Found %d contacts%s:", match_count
),
9313 match_count
, more
? _(" (more matched your query)") : "");
9315 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_IM
, sipe_searchresults_im_buddy
);
9316 purple_notify_searchresults_button_add(results
, PURPLE_NOTIFY_BUTTON_ADD
, sipe_searchresults_add_buddy
);
9317 purple_notify_searchresults(sip
->gc
, NULL
, NULL
, secondary
, results
, NULL
, NULL
);
9320 sipe_xml_free(searchResults
);
9324 static void sipe_search_contact_with_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
9326 GList
*entries
= purple_request_field_group_get_fields(purple_request_fields_get_groups(fields
)->data
);
9327 gchar
**attrs
= g_new(gchar
*, g_list_length(entries
) + 1);
9333 PurpleRequestField
*field
= entries
->data
;
9334 const char *id
= purple_request_field_get_id(field
);
9335 const char *value
= purple_request_field_string_get_value(field
);
9337 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: %s = '%s'", id
, value
? value
: "");
9339 if (value
!= NULL
) attrs
[i
++] = g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, id
, value
);
9340 } while ((entries
= g_list_next(entries
)) != NULL
);
9344 struct sipe_account_data
*sip
= gc
->proto_data
;
9345 gchar
*domain_uri
= sip_uri_from_name(sip
->sipdomain
);
9346 gchar
*query
= g_strjoinv(NULL
, attrs
);
9347 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 100, query
);
9348 SIPE_DEBUG_INFO("sipe_search_contact_with_cb: body:\n%s", body
? body
: "");
9349 send_soap_request_with_cb(sip
, domain_uri
, body
,
9350 (TransCallback
) process_search_contact_response
, NULL
);
9359 static void sipe_show_find_contact(PurplePluginAction
*action
)
9361 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
9362 PurpleRequestFields
*fields
;
9363 PurpleRequestFieldGroup
*group
;
9364 PurpleRequestField
*field
;
9366 fields
= purple_request_fields_new();
9367 group
= purple_request_field_group_new(NULL
);
9368 purple_request_fields_add_group(fields
, group
);
9370 field
= purple_request_field_string_new("givenName", _("First name"), NULL
, FALSE
);
9371 purple_request_field_group_add_field(group
, field
);
9372 field
= purple_request_field_string_new("sn", _("Last name"), NULL
, FALSE
);
9373 purple_request_field_group_add_field(group
, field
);
9374 field
= purple_request_field_string_new("company", _("Company"), NULL
, FALSE
);
9375 purple_request_field_group_add_field(group
, field
);
9376 field
= purple_request_field_string_new("c", _("Country"), NULL
, FALSE
);
9377 purple_request_field_group_add_field(group
, field
);
9379 purple_request_fields(gc
,
9381 _("Search for a contact"),
9382 _("Enter the information for the person you wish to find. Empty fields will be ignored."),
9384 _("_Search"), G_CALLBACK(sipe_search_contact_with_cb
),
9386 purple_connection_get_account(gc
), NULL
, NULL
, gc
);
9389 static void sipe_show_about_plugin(PurplePluginAction
*action
)
9391 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
9392 char *tmp
= g_strdup_printf(
9394 * Non-translatable parts, like markup, are hard-coded
9395 * into the format string. This requires more translatable
9396 * texts but it makes the translations less error prone.
9398 "<b><font size=\"+1\">SIPE " PACKAGE_VERSION
" </font></b><br/>"
9401 "<li> - MS Office Communications Server 2007 R2</li><br/>"
9402 "<li> - MS Office Communications Server 2007</li><br/>"
9403 "<li> - MS Live Communications Server 2005</li><br/>"
9404 "<li> - MS Live Communications Server 2003</li><br/>"
9405 "<li> - Reuters Messaging</li><br/>"
9407 /* 2 */ "%s: <a href=\"" PACKAGE_URL
"\">" PACKAGE_URL
"</a><br/>"
9408 /* 3,4 */ "%s: <a href=\"http://sourceforge.net/projects/sipe/forums/forum/688534\">%s</a><br/>"
9409 /* 5,6 */ "%s: <a href=\"" PACKAGE_BUGREPORT
"\">%s</a><br/>"
9410 /* 7 */ "%s: <a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a><br/>"
9411 /* 8 */ "%s: GPLv2+<br/>"
9415 " - Reuters Messaging network<br/>"
9416 " - Deutsche Bank<br/>"
9417 " - Merrill Lynch<br/>"
9426 " - Alcatel-Lucent<br/>"
9429 /* 10,11 */ "%s<a href=\"https://transifex.net/projects/p/pidgin-sipe/c/mob-branch/\">Transifex.net</a>%s.<br/>"
9431 /* 12 */ "<b>%s:</b><br/>"
9432 " - Anibal Avelar<br/>"
9433 " - Gabriel Burt<br/>"
9434 " - Stefan Becker<br/>"
9436 " - Jakub Adam<br/>"
9437 " - Tomáš Hrabčík<br/>"
9441 /* The next 13 texts make up the SIPE about note text */
9442 /* About note, part 1/13: introduction */
9443 _("A third-party plugin implementing extended version of SIP/SIMPLE used by various products"),
9444 /* About note, part 2/13: home page URL (label) */
9446 /* About note, part 3/13: support forum URL (label) */
9448 /* About note, part 4/13: support forum name (hyperlink text) */
9450 /* About note, part 5/13: bug tracker URL (label) */
9451 _("Report Problems"),
9452 /* About note, part 6/13: bug tracker URL (hyperlink text) */
9454 /* About note, part 7/13: translation service URL (label) */
9456 /* About note, part 8/13: license type (label) */
9458 /* About note, part 9/13: known users */
9459 _("We support users in such organizations as"),
9460 /* About note, part 10/13: translation request, text before Transifex.net URL */
9461 /* append a space if text is not empty */
9462 _("Please help us to translate SIPE to your native language here at "),
9463 /* About note, part 11/13: translation request, text after Transifex.net URL */
9464 /* start with a space if text is not empty */
9465 _(" using convenient web interface"),
9466 /* About note, part 12/13: author list (header) */
9468 /* About note, part 13/13: Localization credit */
9469 /* PLEASE NOTE: do *NOT* simply translate the english original */
9470 /* but write something similar to the following sentence: */
9471 /* "Localization for <language name> (<language code>): <name>" */
9472 _("Original texts in English (en): SIPE developers")
9474 purple_notify_formatted(gc
, NULL
, " ", NULL
, tmp
, NULL
, NULL
);
9478 static void sipe_republish_calendar(PurplePluginAction
*action
)
9480 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
9481 struct sipe_account_data
*sip
= gc
->proto_data
;
9483 sipe_update_calendar(sip
);
9486 static void sipe_publish_get_cat_state_user_to_clear(SIPE_UNUSED_PARAMETER
const char *name
,
9490 struct sipe_publication
*publication
= value
;
9492 g_string_append_printf( str
,
9493 SIPE_PUB_XML_PUBLICATION_CLEAR
,
9494 publication
->category
,
9495 publication
->instance
,
9496 publication
->container
,
9497 publication
->version
,
9501 static void sipe_reset_status(PurplePluginAction
*action
)
9503 PurpleConnection
*gc
= (PurpleConnection
*) action
->context
;
9504 struct sipe_account_data
*sip
= gc
->proto_data
;
9506 if (sip
->ocs2007
) /* 2007+ */
9508 GString
* str
= g_string_new(NULL
);
9509 gchar
*publications
;
9511 if (!sip
->user_state_publications
|| g_hash_table_size(sip
->user_state_publications
) == 0) {
9512 SIPE_DEBUG_INFO_NOFORMAT("sipe_reset_status: no userState publications, exiting.");
9516 g_hash_table_foreach(sip
->user_state_publications
, (GHFunc
)sipe_publish_get_cat_state_user_to_clear
, str
);
9517 publications
= g_string_free(str
, FALSE
);
9519 send_presence_publish(sip
, publications
);
9520 g_free(publications
);
9524 send_presence_soap0(sip
, FALSE
, TRUE
);
9528 GList
*sipe_actions(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
,
9531 PurpleConnection
*gc
= (PurpleConnection
*)context
;
9532 struct sipe_account_data
*sip
= gc
->proto_data
;
9534 PurplePluginAction
*act
;
9535 const char* calendar
= purple_account_get_string(sip
->account
, "calendar", "EXCH");
9537 act
= purple_plugin_action_new(_("About SIPE plugin..."), sipe_show_about_plugin
);
9538 menu
= g_list_prepend(menu
, act
);
9540 act
= purple_plugin_action_new(_("Contact search..."), sipe_show_find_contact
);
9541 menu
= g_list_prepend(menu
, act
);
9543 if (sipe_strequal(calendar
, "EXCH")) {
9544 act
= purple_plugin_action_new(_("Republish Calendar"), sipe_republish_calendar
);
9545 menu
= g_list_prepend(menu
, act
);
9548 act
= purple_plugin_action_new(_("Reset status"), sipe_reset_status
);
9549 menu
= g_list_prepend(menu
, act
);
9551 menu
= g_list_reverse(menu
);
9556 static void dummy_permit_deny(SIPE_UNUSED_PARAMETER PurpleConnection
*gc
)
9560 static gboolean
sipe_plugin_load(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
9566 static gboolean
sipe_plugin_unload(SIPE_UNUSED_PARAMETER PurplePlugin
*plugin
)
9572 static char *sipe_status_text(PurpleBuddy
*buddy
)
9574 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
9575 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
9576 const char *status_id
= purple_status_get_id(status
);
9577 struct sipe_account_data
*sip
= (struct sipe_account_data
*)buddy
->account
->gc
->proto_data
;
9578 struct sipe_buddy
*sbuddy
;
9581 if (!sip
) return NULL
; /* happens on pidgin exit */
9583 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
9585 const char *activity_str
= sbuddy
->activity
?
9587 sipe_strequal(status_id
, SIPE_STATUS_ID_BUSY
) || sipe_strequal(status_id
, SIPE_STATUS_ID_BRB
) ?
9588 purple_status_get_name(status
) : NULL
;
9590 if (activity_str
&& sbuddy
->note
)
9592 text
= g_strdup_printf("%s - <i>%s</i>", activity_str
, sbuddy
->note
);
9594 else if (activity_str
)
9596 text
= g_strdup(activity_str
);
9598 else if (sbuddy
->note
)
9600 text
= g_strdup_printf("<i>%s</i>", sbuddy
->note
);
9607 static void sipe_tooltip_text(PurpleBuddy
*buddy
, PurpleNotifyUserInfo
*user_info
, SIPE_UNUSED_PARAMETER gboolean full
)
9609 const PurplePresence
*presence
= purple_buddy_get_presence(buddy
);
9610 const PurpleStatus
*status
= purple_presence_get_active_status(presence
);
9611 struct sipe_account_data
*sip
;
9612 struct sipe_buddy
*sbuddy
;
9614 gboolean is_oof_note
= FALSE
;
9615 char *activity
= NULL
;
9616 char *calendar
= NULL
;
9617 char *meeting_subject
= NULL
;
9618 char *meeting_location
= NULL
;
9620 sip
= (struct sipe_account_data
*) buddy
->account
->gc
->proto_data
;
9621 if (sip
) //happens on pidgin exit
9623 sbuddy
= g_hash_table_lookup(sip
->buddies
, buddy
->name
);
9626 note
= sbuddy
->note
;
9627 is_oof_note
= sbuddy
->is_oof_note
;
9628 activity
= sbuddy
->activity
;
9629 calendar
= sipe_cal_get_description(sbuddy
);
9630 meeting_subject
= sbuddy
->meeting_subject
;
9631 meeting_location
= sbuddy
->meeting_location
;
9636 if (purple_presence_is_online(presence
))
9638 const char *status_str
= activity
? activity
: purple_status_get_name(status
);
9640 purple_notify_user_info_add_pair(user_info
, _("Status"), status_str
);
9642 if (purple_presence_is_online(presence
) &&
9643 !is_empty(calendar
))
9645 purple_notify_user_info_add_pair(user_info
, _("Calendar"), calendar
);
9648 if (!is_empty(meeting_location
))
9650 purple_notify_user_info_add_pair(user_info
, _("Meeting in"), meeting_location
);
9652 if (!is_empty(meeting_subject
))
9654 purple_notify_user_info_add_pair(user_info
, _("Meeting about"), meeting_subject
);
9659 char *tmp
= g_strdup_printf("<i>%s</i>", note
);
9660 SIPE_DEBUG_INFO("sipe_tooltip_text: %s note: '%s'", buddy
->name
, note
);
9662 purple_notify_user_info_add_pair(user_info
, is_oof_note
? _("Out of office note") : _("Note"), tmp
);
9666 if (sip
&& sip
->ocs2007
) {
9667 const int container_id
= sipe_find_access_level(sip
, "user", sipe_get_no_sip_uri(buddy
->name
), NULL
);
9668 const char *access_level
= sipe_get_access_level_name(container_id
);
9670 purple_notify_user_info_add_pair(user_info
, _("Access level"), access_level
);
9674 #if PURPLE_VERSION_CHECK(2,5,0)
9676 sipe_get_account_text_table(SIPE_UNUSED_PARAMETER PurpleAccount
*account
)
9679 table
= g_hash_table_new(g_str_hash
, g_str_equal
);
9680 g_hash_table_insert(table
, "login_label", (gpointer
)_("user@company.com"));
9685 static PurpleBuddy
*
9686 purple_blist_add_buddy_clone(PurpleGroup
* group
, PurpleBuddy
* buddy
)
9689 const gchar
*server_alias
, *email
;
9690 const PurpleStatus
*status
= purple_presence_get_active_status(purple_buddy_get_presence(buddy
));
9692 clone
= purple_buddy_new(buddy
->account
, buddy
->name
, buddy
->alias
);
9694 purple_blist_add_buddy(clone
, NULL
, group
, NULL
);
9696 server_alias
= purple_buddy_get_server_alias(buddy
);
9698 purple_blist_server_alias_buddy(clone
, server_alias
);
9701 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
9703 purple_blist_node_set_string(&clone
->node
, EMAIL_PROP
, email
);
9706 purple_presence_set_status_active(purple_buddy_get_presence(clone
), purple_status_get_id(status
), TRUE
);
9708 purple_prpl_got_user_status(clone
->account
, clone
->name
, purple_status_get_id(status
), NULL
);
9713 sipe_buddy_menu_copy_to_cb(PurpleBlistNode
*node
, const char *group_name
)
9715 PurpleBuddy
*buddy
, *b
;
9716 PurpleConnection
*gc
;
9717 PurpleGroup
* group
= purple_find_group(group_name
);
9719 g_return_if_fail(PURPLE_BLIST_NODE_IS_BUDDY(node
));
9721 buddy
= (PurpleBuddy
*)node
;
9723 SIPE_DEBUG_INFO("sipe_buddy_menu_copy_to_cb: copying %s to %s", buddy
->name
, group_name
);
9724 gc
= purple_account_get_connection(buddy
->account
);
9726 b
= purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
);
9728 purple_blist_add_buddy_clone(group
, buddy
);
9731 sipe_group_buddy(gc
, buddy
->name
, NULL
, group_name
);
9735 sipe_buddy_menu_chat_new_cb(PurpleBuddy
*buddy
)
9737 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9739 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_new_cb: buddy->name=%s", buddy
->name
);
9741 /* 2007+ conference */
9744 sipe_conf_add(sip
, buddy
->name
);
9746 else /* 2005- multiparty chat */
9748 gchar
*self
= sip_uri_self(sip
);
9749 struct sip_session
*session
;
9751 session
= sipe_session_add_chat(sip
);
9752 session
->chat_title
= sipe_chat_get_name(session
->callid
);
9753 session
->roster_manager
= g_strdup(self
);
9755 session
->conv
= serv_got_joined_chat(buddy
->account
->gc
, session
->chat_id
, session
->chat_title
);
9756 purple_conv_chat_set_nick(PURPLE_CONV_CHAT(session
->conv
), self
);
9757 purple_conv_chat_add_user(PURPLE_CONV_CHAT(session
->conv
), self
, NULL
, PURPLE_CBFLAGS_NONE
, FALSE
);
9758 sipe_invite(sip
, session
, buddy
->name
, NULL
, NULL
, NULL
, FALSE
);
9765 sipe_is_election_finished(struct sip_session
*session
)
9767 gboolean res
= TRUE
;
9769 SIPE_DIALOG_FOREACH
{
9770 if (dialog
->election_vote
== 0) {
9774 } SIPE_DIALOG_FOREACH_END
;
9777 session
->is_voting_in_progress
= FALSE
;
9783 sipe_election_start(struct sipe_account_data
*sip
,
9784 struct sip_session
*session
)
9786 int election_timeout
;
9788 if (session
->is_voting_in_progress
) {
9789 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_start: other election is in progress, exiting.");
9792 session
->is_voting_in_progress
= TRUE
;
9794 session
->bid
= rand();
9796 SIPE_DEBUG_INFO("sipe_election_start: RM election has initiated. Our bid=%d", session
->bid
);
9798 SIPE_DIALOG_FOREACH
{
9799 /* reset election_vote for each chat participant */
9800 dialog
->election_vote
= 0;
9802 /* send RequestRM to each chat participant*/
9803 sipe_send_election_request_rm(sip
, dialog
, session
->bid
);
9804 } SIPE_DIALOG_FOREACH_END
;
9806 election_timeout
= 15; /* sec */
9807 sipe_schedule_action("<+election-result>", election_timeout
, sipe_election_result
, NULL
, sip
, session
);
9811 * @param who a URI to whom to invite to chat
9814 sipe_invite_to_chat(struct sipe_account_data
*sip
,
9815 struct sip_session
*session
,
9819 if (session
->focus_uri
)
9821 sipe_invite_conf(sip
, session
, who
);
9823 else /* a multi-party chat */
9825 gchar
*self
= sip_uri_self(sip
);
9826 if (session
->roster_manager
) {
9827 if (sipe_strcase_equal(session
->roster_manager
, self
)) {
9828 sipe_invite(sip
, session
, who
, NULL
, NULL
, NULL
, FALSE
);
9830 sipe_refer(sip
, session
, who
);
9833 SIPE_DEBUG_INFO_NOFORMAT("sipe_buddy_menu_chat_invite: no RM available");
9835 session
->pending_invite_queue
= slist_insert_unique_sorted(
9836 session
->pending_invite_queue
, g_strdup(who
), (GCompareFunc
)strcmp
);
9838 sipe_election_start(sip
, session
);
9845 sipe_process_pending_invite_queue(struct sipe_account_data
*sip
,
9846 struct sip_session
*session
)
9849 GSList
*entry
= session
->pending_invite_queue
;
9852 invitee
= entry
->data
;
9853 sipe_invite_to_chat(sip
, session
, invitee
);
9854 entry
= session
->pending_invite_queue
= g_slist_remove(session
->pending_invite_queue
, invitee
);
9860 sipe_election_result(struct sipe_account_data
*sip
,
9863 struct sip_session
*session
= (struct sip_session
*)sess
;
9865 gboolean has_won
= TRUE
;
9867 if (session
->roster_manager
) {
9869 "sipe_election_result: RM has already been elected in the meantime. It is %s",
9870 session
->roster_manager
);
9874 session
->is_voting_in_progress
= FALSE
;
9876 SIPE_DIALOG_FOREACH
{
9877 if (dialog
->election_vote
< 0) {
9879 rival
= dialog
->with
;
9882 } SIPE_DIALOG_FOREACH_END
;
9885 SIPE_DEBUG_INFO_NOFORMAT("sipe_election_result: we have won RM election!");
9887 session
->roster_manager
= sip_uri_self(sip
);
9889 SIPE_DIALOG_FOREACH
{
9890 /* send SetRM to each chat participant*/
9891 sipe_send_election_set_rm(sip
, dialog
);
9892 } SIPE_DIALOG_FOREACH_END
;
9894 SIPE_DEBUG_INFO("sipe_election_result: we loose RM election to %s", rival
);
9898 sipe_process_pending_invite_queue(sip
, session
);
9902 * For 2007+ conference only.
9905 sipe_buddy_menu_chat_make_leader_cb(PurpleBuddy
*buddy
, const char *chat_title
)
9907 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9908 struct sip_session
*session
;
9910 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: buddy->name=%s", buddy
->name
);
9911 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_make_leader_cb: chat_title=%s", chat_title
);
9913 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
9915 sipe_conf_modify_user_role(sip
, session
, buddy
->name
);
9919 * For 2007+ conference only.
9922 sipe_buddy_menu_chat_remove_cb(PurpleBuddy
*buddy
, const char *chat_title
)
9924 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9925 struct sip_session
*session
;
9927 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: buddy->name=%s", buddy
->name
);
9928 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_remove_cb: chat_title=%s", chat_title
);
9930 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
9932 sipe_conf_delete_user(sip
, session
, buddy
->name
);
9936 sipe_buddy_menu_chat_invite_cb(PurpleBuddy
*buddy
, char *chat_title
)
9938 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9939 struct sip_session
*session
;
9941 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: buddy->name=%s", buddy
->name
);
9942 SIPE_DEBUG_INFO("sipe_buddy_menu_chat_invite_cb: chat_title=%s", chat_title
);
9944 session
= sipe_session_find_chat_by_title(sip
, chat_title
);
9946 sipe_invite_to_chat(sip
, session
, buddy
->name
);
9950 sipe_buddy_menu_make_call_cb(PurpleBuddy
*buddy
, const char *phone
)
9952 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
9954 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: buddy->name=%s", buddy
->name
);
9956 char *tel_uri
= sip_to_tel_uri(phone
);
9958 SIPE_DEBUG_INFO("sipe_buddy_menu_make_call_cb: going to call number: %s", tel_uri
? tel_uri
: "");
9959 sip_csta_make_call(sip
, tel_uri
);
9966 sipe_open_url(const char *url
)
9974 util
= "cmd /c start";
9976 util
= g_str_has_prefix(url
, "mailto:") ? "xdg-email" : "xdg-open";
9979 command_line
= g_strdup_printf("%s %s", util
, url
);
9980 g_spawn_command_line_async(command_line
, NULL
);
9981 g_free(command_line
);
9985 sipe_buddy_menu_access_level_help_cb(SIPE_UNUSED_PARAMETER PurpleBuddy
*buddy
)
9987 /** Translators: replace with URL to localized page
9988 * If it doesn't exist copy the original URL */
9989 sipe_open_url(_("https://sourceforge.net/apps/mediawiki/sipe/index.php?title=Access_Levels"));
9993 sipe_buddy_menu_send_email_cb(PurpleBuddy
*buddy
)
9996 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: buddy->name=%s", buddy
->name
);
9998 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
10001 char *mailto
= g_strdup_printf("mailto:%s", email
);
10002 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: going to call default mail client with email: %s", email
);
10003 sipe_open_url(mailto
);
10008 SIPE_DEBUG_INFO("sipe_buddy_menu_send_email_cb: no email address stored for buddy=%s", buddy
->name
);
10013 sipe_buddy_menu_access_level_cb(SIPE_UNUSED_PARAMETER PurpleBuddy
*buddy
,
10014 struct sipe_container
*container
)
10016 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
10017 struct sipe_container_member
*member
;
10019 if (!container
|| !container
->members
) return;
10021 member
= ((struct sipe_container_member
*)container
->members
->data
);
10023 if (!member
->type
) return;
10025 SIPE_DEBUG_INFO("sipe_buddy_menu_access_level_cb: container->id=%d, member->type=%s, member->value=%s",
10026 container
->id
, member
->type
, member
->value
? member
->value
: "");
10028 sipe_change_access_level(sip
, container
->id
, member
->type
, member
->value
);
10032 sipe_get_access_control_menu(struct sipe_account_data
*sip
,
10036 * A menu which appear when right-clicking on buddy in contact list.
10039 sipe_buddy_menu(PurpleBuddy
*buddy
)
10041 PurpleBlistNode
*g_node
;
10042 PurpleGroup
*group
, *gr_parent
;
10043 PurpleMenuAction
*act
;
10044 GList
*menu
= NULL
;
10045 GList
*menu_groups
= NULL
;
10046 struct sipe_account_data
*sip
= buddy
->account
->gc
->proto_data
;
10049 const char *phone_disp_str
;
10050 gchar
*self
= sip_uri_self(sip
);
10052 SIPE_SESSION_FOREACH
{
10053 if (!sipe_strcase_equal(self
, buddy
->name
) && session
->chat_title
&& session
->conv
)
10055 if (purple_conv_chat_find_user(PURPLE_CONV_CHAT(session
->conv
), buddy
->name
))
10057 PurpleConvChatBuddyFlags flags
;
10058 PurpleConvChatBuddyFlags flags_us
;
10060 flags
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), buddy
->name
);
10061 flags_us
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), self
);
10062 if (session
->focus_uri
10063 && PURPLE_CBFLAGS_OP
!= (flags
& PURPLE_CBFLAGS_OP
) /* Not conf OP */
10064 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
10066 gchar
*label
= g_strdup_printf(_("Make leader of '%s'"), session
->chat_title
);
10067 act
= purple_menu_action_new(label
,
10068 PURPLE_CALLBACK(sipe_buddy_menu_chat_make_leader_cb
),
10069 session
->chat_title
, NULL
);
10071 menu
= g_list_prepend(menu
, act
);
10074 if (session
->focus_uri
10075 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
10077 gchar
*label
= g_strdup_printf(_("Remove from '%s'"), session
->chat_title
);
10078 act
= purple_menu_action_new(label
,
10079 PURPLE_CALLBACK(sipe_buddy_menu_chat_remove_cb
),
10080 session
->chat_title
, NULL
);
10082 menu
= g_list_prepend(menu
, act
);
10087 if (!session
->focus_uri
10088 || (session
->focus_uri
&& !session
->locked
))
10090 gchar
*label
= g_strdup_printf(_("Invite to '%s'"), session
->chat_title
);
10091 act
= purple_menu_action_new(label
,
10092 PURPLE_CALLBACK(sipe_buddy_menu_chat_invite_cb
),
10093 session
->chat_title
, NULL
);
10095 menu
= g_list_prepend(menu
, act
);
10099 } SIPE_SESSION_FOREACH_END
;
10101 act
= purple_menu_action_new(_("New chat"),
10102 PURPLE_CALLBACK(sipe_buddy_menu_chat_new_cb
),
10104 menu
= g_list_prepend(menu
, act
);
10106 if (sip
->csta
&& !sip
->csta
->line_status
) {
10109 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_PROP
);
10110 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_DISPLAY_PROP
);
10112 gchar
*label
= g_strdup_printf(_("Work %s"),
10113 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
10114 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
10118 menu
= g_list_prepend(menu
, act
);
10122 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_MOBILE_PROP
);
10123 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_MOBILE_DISPLAY_PROP
);
10125 gchar
*label
= g_strdup_printf(_("Mobile %s"),
10126 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
10127 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
10131 menu
= g_list_prepend(menu
, act
);
10135 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_HOME_PROP
);
10136 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_HOME_DISPLAY_PROP
);
10138 gchar
*label
= g_strdup_printf(_("Home %s"),
10139 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
10140 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
10144 menu
= g_list_prepend(menu
, act
);
10148 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_OTHER_PROP
);
10149 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_OTHER_DISPLAY_PROP
);
10151 gchar
*label
= g_strdup_printf(_("Other %s"),
10152 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
10153 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
10157 menu
= g_list_prepend(menu
, act
);
10160 /* custom1 phone */
10161 phone
= purple_blist_node_get_string(&buddy
->node
, PHONE_CUSTOM1_PROP
);
10162 phone_disp_str
= purple_blist_node_get_string(&buddy
->node
, PHONE_CUSTOM1_DISPLAY_PROP
);
10164 gchar
*label
= g_strdup_printf(_("Custom1 %s"),
10165 phone_disp_str
? phone_disp_str
: (tmp
= sip_tel_uri_denormalize(phone
)));
10166 act
= purple_menu_action_new(label
, PURPLE_CALLBACK(sipe_buddy_menu_make_call_cb
), (gpointer
) phone
, NULL
);
10170 menu
= g_list_prepend(menu
, act
);
10174 email
= purple_blist_node_get_string(&buddy
->node
, EMAIL_PROP
);
10176 act
= purple_menu_action_new(_("Send email..."),
10177 PURPLE_CALLBACK(sipe_buddy_menu_send_email_cb
),
10179 menu
= g_list_prepend(menu
, act
);
10183 if (sip
->ocs2007
) {
10184 GList
*menu_access_levels
= sipe_get_access_control_menu(sip
, buddy
->name
);
10186 act
= purple_menu_action_new(_("Access level"),
10188 NULL
, menu_access_levels
);
10189 menu
= g_list_prepend(menu
, act
);
10193 gr_parent
= purple_buddy_get_group(buddy
);
10194 for (g_node
= purple_blist_get_root(); g_node
; g_node
= g_node
->next
) {
10195 if (g_node
->type
!= PURPLE_BLIST_GROUP_NODE
)
10198 group
= (PurpleGroup
*)g_node
;
10199 if (group
== gr_parent
)
10202 if (purple_find_buddy_in_group(buddy
->account
, buddy
->name
, group
))
10205 act
= purple_menu_action_new(purple_group_get_name(group
),
10206 PURPLE_CALLBACK(sipe_buddy_menu_copy_to_cb
),
10207 group
->name
, NULL
);
10208 menu_groups
= g_list_prepend(menu_groups
, act
);
10210 menu_groups
= g_list_reverse(menu_groups
);
10212 act
= purple_menu_action_new(_("Copy to"),
10214 NULL
, menu_groups
);
10215 menu
= g_list_prepend(menu
, act
);
10217 menu
= g_list_reverse(menu
);
10224 sipe_ask_access_domain_cb(PurpleConnection
*gc
, PurpleRequestFields
*fields
)
10226 struct sipe_account_data
*sip
= gc
->proto_data
;
10227 const char *domain
= purple_request_fields_get_string(fields
, "access_domain");
10228 int index
= purple_request_fields_get_choice(fields
, "container_id");
10229 /* move Blocked first */
10230 int i
= (index
== 4) ? 0 : index
+ 1;
10231 int container_id
= containers
[i
];
10233 SIPE_DEBUG_INFO("sipe_ask_access_domain_cb: domain=%s, container_id=(%d)%d", domain
? domain
: "", index
, container_id
);
10235 sipe_change_access_level(sip
, container_id
, "domain", domain
);
10239 sipe_ask_access_domain(struct sipe_account_data
*sip
)
10241 PurpleAccount
*account
= sip
->account
;
10242 PurpleConnection
*gc
= sip
->gc
;
10243 PurpleRequestFields
*fields
;
10244 PurpleRequestFieldGroup
*g
;
10245 PurpleRequestField
*f
;
10247 fields
= purple_request_fields_new();
10249 g
= purple_request_field_group_new(NULL
);
10250 f
= purple_request_field_string_new("access_domain", _("Domain"), "partner-company.com", FALSE
);
10251 purple_request_field_set_required(f
, TRUE
);
10252 purple_request_field_group_add_field(g
, f
);
10254 f
= purple_request_field_choice_new("container_id", _("Access level"), 0);
10255 purple_request_field_choice_add(f
, _("Personal")); /* index 0 */
10256 purple_request_field_choice_add(f
, _("Team"));
10257 purple_request_field_choice_add(f
, _("Company"));
10258 purple_request_field_choice_add(f
, _("Public"));
10259 purple_request_field_choice_add(f
, _("Blocked")); /* index 4 */
10260 purple_request_field_choice_set_default_value(f
, 3); /* index */
10261 purple_request_field_set_required(f
, TRUE
);
10262 purple_request_field_group_add_field(g
, f
);
10264 purple_request_fields_add_group(fields
, g
);
10266 purple_request_fields(gc
, _("Add new domain"),
10267 _("Add new domain"), NULL
, fields
,
10268 _("Add"), G_CALLBACK(sipe_ask_access_domain_cb
),
10270 account
, NULL
, NULL
, gc
);
10274 sipe_buddy_menu_access_level_add_domain_cb(PurpleBuddy
*buddy
)
10276 sipe_ask_access_domain((struct sipe_account_data
*)buddy
->account
->gc
->proto_data
);
10279 #define INDENT_FMT " %s"
10281 /** Member is directly placed to access level container.
10282 * For example SIP URI of user is in the container.
10284 #define INDENT_MARKED_FMT "* %s"
10286 /** Member is indirectly belong to access level container.
10287 * For example 'sameEnterprise' is in the container and user
10288 * belongs to that same enterprise.
10290 #define INDENT_MARKED_INHERITED_FMT "= %s"
10293 sipe_get_access_levels_menu(struct sipe_account_data
*sip
,
10294 const char* member_type
,
10295 const char* member_value
,
10296 const gboolean extra_menu
)
10298 GList
*menu_access_levels
= NULL
;
10301 PurpleMenuAction
*act
;
10302 struct sipe_container
*container
;
10303 struct sipe_container_member
*member
;
10304 gboolean is_inherited
= FALSE
;
10305 int container_id
= sipe_find_access_level(sip
, member_type
, member_value
, &is_inherited
);
10307 for (i
= 1; i
<= CONTAINERS_LEN
; i
++) {
10308 /* to put Blocked level last in menu list.
10309 * Blocked should remaim in the first place in the containers[] array.
10311 unsigned int j
= (i
== CONTAINERS_LEN
) ? 0 : i
;
10312 const char *acc_level_name
= sipe_get_access_level_name(containers
[j
]);
10314 container
= g_new0(struct sipe_container
, 1);
10315 member
= g_new0(struct sipe_container_member
, 1);
10316 container
->id
= containers
[j
];
10317 container
->members
= g_slist_append(container
->members
, member
);
10318 member
->type
= g_strdup(member_type
);
10319 member
->value
= g_strdup(member_value
);
10321 /* current container/access level */
10322 if (((int)containers
[j
]) == container_id
) {
10323 menu_name
= is_inherited
?
10324 g_strdup_printf(INDENT_MARKED_INHERITED_FMT
, acc_level_name
) :
10325 g_strdup_printf(INDENT_MARKED_FMT
, acc_level_name
);
10327 menu_name
= g_strdup_printf(INDENT_FMT
, acc_level_name
);
10330 act
= purple_menu_action_new(menu_name
,
10331 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb
),
10334 menu_access_levels
= g_list_prepend(menu_access_levels
, act
);
10337 if (extra_menu
&& (container_id
>= 0)) {
10339 act
= purple_menu_action_new(" --------------", NULL
, NULL
, NULL
);
10340 menu_access_levels
= g_list_prepend(menu_access_levels
, act
);
10342 if (!is_inherited
) {
10343 container
= g_new0(struct sipe_container
, 1);
10344 member
= g_new0(struct sipe_container_member
, 1);
10345 container
->id
= -1;
10346 container
->members
= g_slist_append(container
->members
, member
);
10347 member
->type
= g_strdup(member_type
);
10348 member
->value
= g_strdup(member_value
);
10350 /* Translators: remove (clear) previously assigned access level */
10351 menu_name
= g_strdup_printf(INDENT_FMT
, _("Unspecify"));
10352 act
= purple_menu_action_new(menu_name
,
10353 PURPLE_CALLBACK(sipe_buddy_menu_access_level_cb
),
10356 menu_access_levels
= g_list_prepend(menu_access_levels
, act
);
10360 menu_access_levels
= g_list_reverse(menu_access_levels
);
10361 return menu_access_levels
;
10365 sipe_get_access_control_menu(struct sipe_account_data
*sip
,
10368 GList
*menu_access_levels
= NULL
;
10369 GList
*menu_access_groups
= NULL
;
10370 GSList
*access_domains
= NULL
;
10373 PurpleMenuAction
*act
;
10376 menu_access_levels
= sipe_get_access_levels_menu(sip
, "user", sipe_get_no_sip_uri(uri
), TRUE
);
10378 /* Access Groups submenu */
10379 act
= purple_menu_action_new(_("People in my company"),
10381 NULL
, sipe_get_access_levels_menu(sip
, "sameEnterprise", NULL
, FALSE
));
10382 menu_access_groups
= g_list_prepend(menu_access_groups
, act
);
10384 /* this is original name, don't edit */
10385 act
= purple_menu_action_new(_("People in domains connected with my company"),
10387 NULL
, sipe_get_access_levels_menu(sip
, "federated", NULL
, FALSE
));
10388 menu_access_groups
= g_list_prepend(menu_access_groups
, act
);
10390 act
= purple_menu_action_new(_("People in public domains"),
10392 NULL
, sipe_get_access_levels_menu(sip
, "publicCloud", NULL
, TRUE
));
10393 menu_access_groups
= g_list_prepend(menu_access_groups
, act
);
10395 /* @TODO add domains here */
10396 access_domains
= sipe_get_access_domains(sip
);
10397 entry
= access_domains
;
10399 domain
= entry
->data
;
10401 menu_name
= g_strdup_printf(_("People at %s"), domain
);
10402 act
= purple_menu_action_new(menu_name
,
10404 NULL
, sipe_get_access_levels_menu(sip
, "domain", g_strdup(domain
), TRUE
));
10405 menu_access_groups
= g_list_prepend(menu_access_groups
, act
);
10408 entry
= entry
->next
;
10412 /* People in domains connected with my company */
10413 act
= purple_menu_action_new("-------------------------------------------", NULL
, NULL
, NULL
);
10414 menu_access_groups
= g_list_prepend(menu_access_groups
, act
);
10416 act
= purple_menu_action_new(_("Add new domain"),
10417 PURPLE_CALLBACK(sipe_buddy_menu_access_level_add_domain_cb
),
10419 menu_access_groups
= g_list_prepend(menu_access_groups
, act
);
10421 menu_access_groups
= g_list_reverse(menu_access_groups
);
10422 /* End of Access Groups submenu */
10424 menu_name
= g_strdup_printf(INDENT_FMT
, _("Access groups"));
10425 act
= purple_menu_action_new(menu_name
,
10427 NULL
, menu_access_groups
);
10429 menu_access_levels
= g_list_append(menu_access_levels
, act
);
10431 menu_name
= g_strdup_printf(INDENT_FMT
, _("Online help..."));
10432 act
= purple_menu_action_new(menu_name
,
10433 PURPLE_CALLBACK(sipe_buddy_menu_access_level_help_cb
),
10436 menu_access_levels
= g_list_append(menu_access_levels
, act
);
10438 return menu_access_levels
;
10442 sipe_conf_modify_lock(PurpleChat
*chat
, gboolean locked
)
10444 struct sipe_account_data
*sip
= chat
->account
->gc
->proto_data
;
10445 struct sip_session
*session
;
10447 session
= sipe_session_find_chat_by_title(sip
, (gchar
*)g_hash_table_lookup(chat
->components
, "channel"));
10448 sipe_conf_modify_conference_lock(sip
, session
, locked
);
10452 sipe_chat_menu_unlock_cb(PurpleChat
*chat
)
10454 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_unlock_cb() called");
10455 sipe_conf_modify_lock(chat
, FALSE
);
10459 sipe_chat_menu_lock_cb(PurpleChat
*chat
)
10461 SIPE_DEBUG_INFO_NOFORMAT("sipe_chat_menu_lock_cb() called");
10462 sipe_conf_modify_lock(chat
, TRUE
);
10466 sipe_chat_menu(PurpleChat
*chat
)
10468 PurpleMenuAction
*act
;
10469 PurpleConvChatBuddyFlags flags_us
;
10470 GList
*menu
= NULL
;
10471 struct sipe_account_data
*sip
= chat
->account
->gc
->proto_data
;
10472 struct sip_session
*session
;
10475 session
= sipe_session_find_chat_by_title(sip
, (gchar
*)g_hash_table_lookup(chat
->components
, "channel"));
10476 if (!session
) return NULL
;
10478 self
= sip_uri_self(sip
);
10479 flags_us
= purple_conv_chat_user_get_flags(PURPLE_CONV_CHAT(session
->conv
), self
);
10481 if (session
->focus_uri
10482 && PURPLE_CBFLAGS_OP
== (flags_us
& PURPLE_CBFLAGS_OP
)) /* We are a conf OP */
10484 if (session
->locked
) {
10485 act
= purple_menu_action_new(_("Unlock"),
10486 PURPLE_CALLBACK(sipe_chat_menu_unlock_cb
),
10488 menu
= g_list_prepend(menu
, act
);
10490 act
= purple_menu_action_new(_("Lock"),
10491 PURPLE_CALLBACK(sipe_chat_menu_lock_cb
),
10493 menu
= g_list_prepend(menu
, act
);
10497 menu
= g_list_reverse(menu
);
10504 sipe_blist_node_menu(PurpleBlistNode
*node
)
10506 if(PURPLE_BLIST_NODE_IS_BUDDY(node
)) {
10507 return sipe_buddy_menu((PurpleBuddy
*) node
);
10508 } else if(PURPLE_BLIST_NODE_IS_CHAT(node
)) {
10509 return sipe_chat_menu((PurpleChat
*)node
);
10516 process_get_info_response(struct sipe_account_data
*sip
, struct sipmsg
*msg
, struct transaction
*trans
)
10518 char *uri
= trans
->payload
->data
;
10520 PurpleNotifyUserInfo
*info
;
10521 PurpleBuddy
*pbuddy
= NULL
;
10522 struct sipe_buddy
*sbuddy
;
10523 const char *alias
= NULL
;
10524 char *device_name
= NULL
;
10525 char *server_alias
= NULL
;
10526 char *phone_number
= NULL
;
10527 char *email
= NULL
;
10529 char *first_name
= NULL
;
10530 char *last_name
= NULL
;
10532 if (!sip
) return FALSE
;
10534 SIPE_DEBUG_INFO("Fetching %s's user info for %s", uri
, sip
->username
);
10536 pbuddy
= purple_find_buddy((PurpleAccount
*)sip
->account
, uri
);
10537 alias
= purple_buddy_get_local_alias(pbuddy
);
10539 //will query buddy UA's capabilities and send answer to log
10540 sipe_options_request(sip
, uri
);
10542 sbuddy
= g_hash_table_lookup(sip
->buddies
, uri
);
10544 device_name
= sbuddy
->device_name
? g_strdup(sbuddy
->device_name
) : NULL
;
10547 info
= purple_notify_user_info_new();
10549 if (msg
->response
!= 200) {
10550 SIPE_DEBUG_INFO("process_options_response: SERVICE response is %d", msg
->response
);
10552 sipe_xml
*searchResults
;
10553 const sipe_xml
*mrow
;
10555 SIPE_DEBUG_INFO("process_options_response: body:\n%s", msg
->body
? msg
->body
: "");
10556 searchResults
= sipe_xml_parse(msg
->body
, msg
->bodylen
);
10557 if (!searchResults
) {
10558 SIPE_DEBUG_INFO_NOFORMAT("process_get_info_response: no parseable searchResults");
10559 } else if ((mrow
= sipe_xml_child(searchResults
, "Body/Array/row"))) {
10561 server_alias
= g_strdup(sipe_xml_attribute(mrow
, "displayName"));
10562 email
= g_strdup(sipe_xml_attribute(mrow
, "email"));
10563 phone_number
= g_strdup(sipe_xml_attribute(mrow
, "phone"));
10565 /* For 2007 system we will take this from ContactCard -
10566 * it has cleaner tel: URIs at least
10568 if (!sip
->ocs2007
) {
10569 char *tel_uri
= sip_to_tel_uri(phone_number
);
10570 /* trims its parameters, so call first */
10571 sipe_update_user_info(sip
, uri
, ALIAS_PROP
, server_alias
);
10572 sipe_update_user_info(sip
, uri
, EMAIL_PROP
, email
);
10573 sipe_update_user_info(sip
, uri
, PHONE_PROP
, tel_uri
);
10574 sipe_update_user_info(sip
, uri
, PHONE_DISPLAY_PROP
, phone_number
);
10578 if (server_alias
&& strlen(server_alias
) > 0) {
10579 purple_notify_user_info_add_pair(info
, _("Display name"), server_alias
);
10581 if ((value
= sipe_xml_attribute(mrow
, "title")) && strlen(value
) > 0) {
10582 purple_notify_user_info_add_pair(info
, _("Job title"), value
);
10584 if ((value
= sipe_xml_attribute(mrow
, "office")) && strlen(value
) > 0) {
10585 purple_notify_user_info_add_pair(info
, _("Office"), value
);
10587 if (phone_number
&& strlen(phone_number
) > 0) {
10588 purple_notify_user_info_add_pair(info
, _("Business phone"), phone_number
);
10590 if ((value
= sipe_xml_attribute(mrow
, "company")) && strlen(value
) > 0) {
10591 purple_notify_user_info_add_pair(info
, _("Company"), value
);
10593 if ((value
= sipe_xml_attribute(mrow
, "city")) && strlen(value
) > 0) {
10594 purple_notify_user_info_add_pair(info
, _("City"), value
);
10596 if ((value
= sipe_xml_attribute(mrow
, "state")) && strlen(value
) > 0) {
10597 purple_notify_user_info_add_pair(info
, _("State"), value
);
10599 if ((value
= sipe_xml_attribute(mrow
, "country")) && strlen(value
) > 0) {
10600 purple_notify_user_info_add_pair(info
, _("Country"), value
);
10602 if (email
&& strlen(email
) > 0) {
10603 purple_notify_user_info_add_pair(info
, _("Email address"), email
);
10607 sipe_xml_free(searchResults
);
10610 purple_notify_user_info_add_section_break(info
);
10612 if (is_empty(server_alias
)) {
10613 g_free(server_alias
);
10614 server_alias
= g_strdup(purple_buddy_get_server_alias(pbuddy
));
10615 if (server_alias
) {
10616 purple_notify_user_info_add_pair(info
, _("Display name"), server_alias
);
10620 /* present alias if it differs from server alias */
10621 if (alias
&& !sipe_strequal(alias
, server_alias
))
10623 purple_notify_user_info_add_pair(info
, _("Alias"), alias
);
10626 if (is_empty(email
)) {
10628 email
= g_strdup(purple_blist_node_get_string(&pbuddy
->node
, EMAIL_PROP
));
10630 purple_notify_user_info_add_pair(info
, _("Email address"), email
);
10634 site
= purple_blist_node_get_string(&pbuddy
->node
, SITE_PROP
);
10636 purple_notify_user_info_add_pair(info
, _("Site"), site
);
10639 sipe_get_first_last_names(sip
, uri
, &first_name
, &last_name
);
10640 if (first_name
&& last_name
) {
10641 char *link
= g_strconcat("http://www.linkedin.com/pub/dir/", first_name
, "/", last_name
, NULL
);
10643 purple_notify_user_info_add_pair(info
, _("Find on LinkedIn"), link
);
10646 g_free(first_name
);
10650 purple_notify_user_info_add_pair(info
, _("Device"), device_name
);
10653 /* show a buddy's user info in a nice dialog box */
10654 purple_notify_userinfo(sip
->gc
, /* connection the buddy info came through */
10655 uri
, /* buddy's URI */
10657 NULL
, /* callback called when dialog closed */
10658 NULL
); /* userdata for callback */
10660 g_free(phone_number
);
10661 g_free(server_alias
);
10663 g_free(device_name
);
10669 * AD search first, LDAP based
10671 static void sipe_get_info(PurpleConnection
*gc
, const char *username
)
10673 struct sipe_account_data
*sip
= gc
->proto_data
;
10674 gchar
*domain_uri
= sip_uri_from_name(sip
->sipdomain
);
10675 char *row
= g_markup_printf_escaped(SIPE_SOAP_SEARCH_ROW
, "msRTCSIP-PrimaryUserAddress", username
);
10676 gchar
*body
= g_strdup_printf(SIPE_SOAP_SEARCH_CONTACT
, 1, row
);
10677 struct transaction_payload
*payload
= g_new0(struct transaction_payload
, 1);
10679 payload
->destroy
= g_free
;
10680 payload
->data
= g_strdup(username
);
10682 SIPE_DEBUG_INFO("sipe_get_contact_data: body:\n%s", body
? body
: "");
10683 send_soap_request_with_cb(sip
, domain_uri
, body
,
10684 (TransCallback
) process_get_info_response
, payload
);
10685 g_free(domain_uri
);
10690 PurplePluginProtocolInfo prpl_info
=
10692 OPT_PROTO_CHAT_TOPIC
,
10693 NULL
, /* user_splits */
10694 NULL
, /* protocol_options */
10695 NO_BUDDY_ICONS
, /* icon_spec */
10696 sipe_list_icon
, /* list_icon */
10697 NULL
, /* list_emblems */
10698 sipe_status_text
, /* status_text */
10699 sipe_tooltip_text
, /* tooltip_text */ // add custom info to contact tooltip
10700 sipe_status_types
, /* away_states */
10701 sipe_blist_node_menu
, /* blist_node_menu */
10702 NULL
, /* chat_info */
10703 NULL
, /* chat_info_defaults */
10704 sipe_login
, /* login */
10705 sipe_close
, /* close */
10706 sipe_im_send
, /* send_im */
10707 NULL
, /* set_info */ // TODO maybe
10708 sipe_send_typing
, /* send_typing */
10709 sipe_get_info
, /* get_info */
10710 sipe_set_status
, /* set_status */
10711 sipe_set_idle
, /* set_idle */
10712 NULL
, /* change_passwd */
10713 sipe_add_buddy
, /* add_buddy */
10714 NULL
, /* add_buddies */
10715 sipe_remove_buddy
, /* remove_buddy */
10716 NULL
, /* remove_buddies */
10717 sipe_add_permit
, /* add_permit */
10718 sipe_add_deny
, /* add_deny */
10719 sipe_add_deny
, /* rem_permit */
10720 sipe_add_permit
, /* rem_deny */
10721 dummy_permit_deny
, /* set_permit_deny */
10722 NULL
, /* join_chat */
10723 NULL
, /* reject_chat */
10724 NULL
, /* get_chat_name */
10725 sipe_chat_invite
, /* chat_invite */
10726 sipe_chat_leave
, /* chat_leave */
10727 NULL
, /* chat_whisper */
10728 sipe_chat_send
, /* chat_send */
10729 sipe_keep_alive
, /* keepalive */
10730 NULL
, /* register_user */
10731 NULL
, /* get_cb_info */ // deprecated
10732 NULL
, /* get_cb_away */ // deprecated
10733 sipe_alias_buddy
, /* alias_buddy */
10734 sipe_group_buddy
, /* group_buddy */
10735 sipe_rename_group
, /* rename_group */
10736 NULL
, /* buddy_free */
10737 sipe_convo_closed
, /* convo_closed */
10738 purple_normalize_nocase
, /* normalize */
10739 NULL
, /* set_buddy_icon */
10740 sipe_remove_group
, /* remove_group */
10741 NULL
, /* get_cb_real_name */ // TODO?
10742 NULL
, /* set_chat_topic */
10743 NULL
, /* find_blist_chat */
10744 NULL
, /* roomlist_get_list */
10745 NULL
, /* roomlist_cancel */
10746 NULL
, /* roomlist_expand_category */
10747 NULL
, /* can_receive_file */
10748 sipe_ft_send_file
, /* send_file */
10749 sipe_ft_new_xfer
, /* new_xfer */
10750 NULL
, /* offline_message */
10751 NULL
, /* whiteboard_prpl_ops */
10752 sipe_send_raw
, /* send_raw */
10753 NULL
, /* roomlist_room_serialize */
10754 NULL
, /* unregister_user */
10755 NULL
, /* send_attention */
10756 NULL
, /* get_attention_types */
10757 #if !PURPLE_VERSION_CHECK(2,5,0)
10758 /* Backward compatibility when compiling against 2.4.x API */
10759 (void (*)(void)) /* _purple_reserved4 */
10761 sizeof(PurplePluginProtocolInfo
), /* struct_size */
10762 #if PURPLE_VERSION_CHECK(2,5,0)
10763 sipe_get_account_text_table
, /* get_account_text_table */
10764 #if PURPLE_VERSION_CHECK(2,6,0)
10765 NULL
, /* initiate_media */
10766 NULL
, /* get_media_caps */
10767 #if PURPLE_VERSION_CHECK(2,7,0)
10768 NULL
, /* get_moods */
10775 PurplePluginInfo info
= {
10776 PURPLE_PLUGIN_MAGIC
,
10777 PURPLE_MAJOR_VERSION
,
10778 PURPLE_MINOR_VERSION
,
10779 PURPLE_PLUGIN_PROTOCOL
, /**< type */
10780 NULL
, /**< ui_requirement */
10782 NULL
, /**< dependencies */
10783 PURPLE_PRIORITY_DEFAULT
, /**< priority */
10784 "prpl-sipe", /**< id */
10785 "Office Communicator", /**< name */
10786 PACKAGE_VERSION
, /**< version */
10787 "Microsoft Office Communicator Protocol Plugin", /**< summary */
10788 "A plugin for the extended SIP/SIMPLE protocol used by " /**< description */
10789 "Microsoft Live/Office Communications Server (LCS2005/OCS2007+)", /**< description */
10790 "Anibal Avelar <avelar@gmail.com>, " /**< author */
10791 "Gabriel Burt <gburt@novell.com>, " /**< author */
10792 "Stefan Becker <stefan.becker@nokia.com>, " /**< author */
10793 "pier11 <pier11@operamail.com>", /**< author */
10794 PACKAGE_URL
, /**< homepage */
10795 sipe_plugin_load
, /**< load */
10796 sipe_plugin_unload
, /**< unload */
10797 sipe_plugin_destroy
, /**< destroy */
10798 NULL
, /**< ui_info */
10799 &prpl_info
, /**< extra_info */
10808 void sipe_core_init(void)
10814 SIPE_DEBUG_INFO("bindtextdomain = %s",
10815 bindtextdomain(PACKAGE_NAME
, LOCALEDIR
));
10816 SIPE_DEBUG_INFO("bind_textdomain_codeset = %s",
10817 bind_textdomain_codeset(PACKAGE_NAME
, "UTF-8"));
10818 textdomain(PACKAGE_NAME
);
10825 void sipe_core_destroy(void)
10836 c-file-style: "bsd"
10837 indent-tabs-mode: t