/**
* @file sipe-ucs.c
*
* pidgin-sipe
*
* Copyright (C) 2013-2016 SIPE Project <http://sipe.sourceforge.net/>
*
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*
* Implementation for Unified Contact Store [MS-OXWSCOS]
* <http://msdn.microsoft.com/en-us/library/jj194130.aspx>
* EWS Reference
* <http://msdn.microsoft.com/en-us/library/office/bb204119.aspx>
* Photo Web Service Protocol [MS-OXWSPHOTO]
* <http://msdn.microsoft.com/en-us/library/jj194353.aspx>
* FindPeople operation
* <http://msdn.microsoft.com/en-us/library/office/jj191039.aspx>
*/
#include <string.h>
#include <glib.h>
#include <time.h>
#include "sipe-backend.h"
#include "sipe-buddy.h"
#include "sipe-common.h"
#include "sipe-core.h"
#include "sipe-core-private.h"
#include "sipe-ews-autodiscover.h"
#include "sipe-group.h"
#include "sipe-http.h"
#include "sipe-nls.h"
#include "sipe-subscriptions.h"
#include "sipe-ucs.h"
#include "sipe-utils.h"
#include "sipe-xml.h"
struct sipe_ucs_transaction {
GSList *pending_requests;
};
typedef void (ucs_callback)(struct sipe_core_private *sipe_private,
struct sipe_ucs_transaction *trans,
const sipe_xml *body,
gpointer callback_data);
struct ucs_request {
gchar *body;
ucs_callback *cb;
gpointer cb_data;
struct sipe_ucs_transaction *transaction;
struct sipe_http_request *request;
};
struct sipe_ucs {
struct ucs_request *active_request;
GSList *transactions;
GSList *default_transaction;
gchar *ews_url;
time_t last_response;
guint group_id;
gboolean migrated;
gboolean shutting_down;
};
static void sipe_ucs_request_free(struct sipe_core_private *sipe_private,
struct ucs_request *data)
{
struct sipe_ucs *ucs = sipe_private->ucs;
struct sipe_ucs_transaction *trans = data->transaction;
/* remove request from transaction */
trans->pending_requests = g_slist_remove(trans->pending_requests,
data);
sipe_private->ucs->active_request = NULL;
/* remove completed transactions (except default transaction) */
if (!trans->pending_requests &&
(trans != ucs->default_transaction->data)) {
ucs->transactions = g_slist_remove(ucs->transactions,
trans);
g_free(trans);
}
if (data->request)
sipe_http_request_cancel(data->request);
if (data->cb)
/* Callback: aborted */
(*data->cb)(sipe_private, NULL, NULL, data->cb_data);
g_free(data->body);
g_free(data);
}
static void sipe_ucs_next_request(struct sipe_core_private *sipe_private);
static void sipe_ucs_http_response(struct sipe_core_private *sipe_private,
guint status,
SIPE_UNUSED_PARAMETER GSList *headers,
const gchar *body,
gpointer callback_data)
{
struct ucs_request *data = callback_data;
SIPE_DEBUG_INFO("sipe_ucs_http_response: code %d", status);
data->request = NULL;
if ((status == SIPE_HTTP_STATUS_OK) && body) {
sipe_xml *xml = sipe_xml_parse(body, strlen(body));
const sipe_xml *soap_body = sipe_xml_child(xml, "Body");
/* Callback: success */
(*data->cb)(sipe_private,
data->transaction,
soap_body,
data->cb_data);
sipe_xml_free(xml);
} else {
/* Callback: failed */
(*data->cb)(sipe_private, NULL, NULL, data->cb_data);
}
/* already been called */
data->cb = NULL;
sipe_ucs_request_free(sipe_private, data);
sipe_ucs_next_request(sipe_private);
}
static void sipe_ucs_next_request(struct sipe_core_private *sipe_private)
{
struct sipe_ucs *ucs = sipe_private->ucs;
struct sipe_ucs_transaction *trans;
if (ucs->active_request || ucs->shutting_down || !ucs->ews_url)
return;
trans = ucs->transactions->data;
while (trans->pending_requests) {
struct ucs_request *data = trans->pending_requests->data;
gchar *soap = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
"<soap:Envelope"
" xmlns:m=\"http://schemas.microsoft.com/exchange/services/2006/messages\""
" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\""
" xmlns:t=\"http://schemas.microsoft.com/exchange/services/2006/types\""
" >"
" <soap:Header>"
" <t:RequestServerVersion Version=\"Exchange2013\" />"
" </soap:Header>"
" <soap:Body>"
" %s"
" </soap:Body>"
"</soap:Envelope>",
data->body);
struct sipe_http_request *request = sipe_http_request_post(sipe_private,
ucs->ews_url,
NULL,
soap,
"text/xml; charset=UTF-8",
sipe_ucs_http_response,
data);
g_free(soap);
if (request) {
g_free(data->body);
data->body = NULL;
data->request = request;
ucs->active_request = data;
sipe_core_email_authentication(sipe_private,
request);
sipe_http_request_allow_redirect(request);
sipe_http_request_ready(request);
break;
} else {
SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_next_request: failed to create HTTP connection");
sipe_ucs_request_free(sipe_private, data);
}
}
}
static gboolean sipe_ucs_http_request(struct sipe_core_private *sipe_private,
struct sipe_ucs_transaction *trans,
gchar *body, /* takes ownership */
ucs_callback *callback,
gpointer callback_data)
{
struct sipe_ucs *ucs = sipe_private->ucs;
if (!ucs || ucs->shutting_down) {
SIPE_DEBUG_ERROR("sipe_ucs_http_request: new UCS request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
"Body: %s\n",
body ? body : "<EMPTY>");
g_free(body);
return(FALSE);
} else {
struct ucs_request *data = g_new0(struct ucs_request, 1);
data->cb = callback;
data->cb_data = callback_data;
data->body = body;
if (!trans)
trans = ucs->default_transaction->data;
data->transaction = trans;
trans->pending_requests = g_slist_append(trans->pending_requests,
data);
sipe_ucs_next_request(sipe_private);
return(TRUE);
}
}
struct sipe_ucs_transaction *sipe_ucs_transaction(struct sipe_core_private *sipe_private)
{
struct sipe_ucs *ucs = sipe_private->ucs;
struct sipe_ucs_transaction *trans;
if (!ucs)
return(NULL);
/* always insert new transactions before default transaction */
trans = g_new0(struct sipe_ucs_transaction, 1);
ucs->transactions = g_slist_insert_before(ucs->transactions,
ucs->default_transaction,
trans);
return(trans);
}
static void sipe_ucs_search_response(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
const sipe_xml *body,
gpointer callback_data)
{
const sipe_xml *persona_node;
struct sipe_backend_search_results *results = NULL;
guint match_count = 0;
for (persona_node = sipe_xml_child(body,
"FindPeopleResponse/People/Persona");
persona_node;
persona_node = sipe_xml_twin(persona_node)) {
const sipe_xml *address = sipe_xml_child(persona_node,
"ImAddress");
/* only display Persona nodes which have an "ImAddress" node */
if (address) {
gchar *uri;
gchar *displayname;
gchar *company;
gchar *email;
/* OK, we found something - show the results to the user */
match_count++;
if (!results) {
results = sipe_backend_search_results_start(SIPE_CORE_PUBLIC,
callback_data);
if (!results) {
SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_search_response: Unable to display the search results.");
sipe_backend_search_failed(SIPE_CORE_PUBLIC,
callback_data,
_("Unable to display the search results"));
return;
}
}
uri = sipe_xml_data(address);
displayname = sipe_xml_data(sipe_xml_child(persona_node,
"DisplayName"));
company = sipe_xml_data(sipe_xml_child(persona_node,
"CompanyName"));
email = sipe_xml_data(sipe_xml_child(persona_node,
"EmailAddress/EmailAddress"));
sipe_backend_search_results_add(SIPE_CORE_PUBLIC,
results,
sipe_get_no_sip_uri(uri),
displayname,
company,
NULL,
email);
g_free(email);
g_free(company);
g_free(displayname);
g_free(uri);
}
}
if (match_count > 0)
sipe_buddy_search_contacts_finalize(sipe_private,
results,
match_count,
FALSE);
else
sipe_backend_search_failed(SIPE_CORE_PUBLIC,
callback_data,
_("No contacts found"));
}
void sipe_ucs_search(struct sipe_core_private *sipe_private,
struct sipe_backend_search_token *token,
const gchar *given_name,
const gchar *surname,
const gchar *email,
const gchar *sipid,
const gchar *company,
const gchar *country)
{
guint count = 0;
GString *query = g_string_new(NULL);
/*
* Search GAL for matching entries
*
* QueryString should support field properties and quoting ("")
* according to the specification. But in my trials I couldn't get
* them to work. Concatenate all query words to a single string.
* Only items that match ALL words will be returned by this query.
*/
#define ADD_QUERY_VALUE(val) \
if (val) { \
if (count++) \
g_string_append_c(query, ' '); \
g_string_append(query, val); \
}
ADD_QUERY_VALUE(given_name);
ADD_QUERY_VALUE(surname);
ADD_QUERY_VALUE(email);
ADD_QUERY_VALUE(sipid);
ADD_QUERY_VALUE(company);
ADD_QUERY_VALUE(country);
if (count > 0) {
gchar *body = g_markup_printf_escaped("<m:FindPeople>"
" <m:PersonaShape>"
" <t:BaseShape>IdOnly</t:BaseShape>"
" <t:AdditionalProperties>"
" <t:FieldURI FieldURI=\"persona:CompanyName\"/>"
" <t:FieldURI FieldURI=\"persona:DisplayName\"/>"
" <t:FieldURI FieldURI=\"persona:EmailAddress\"/>"
" <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
/* Locations doesn't seem to work
" <t:FieldURI FieldURI=\"persona:Locations\"/>"
*/
" </t:AdditionalProperties>"
" </m:PersonaShape>"
" <m:IndexedPageItemView BasePoint=\"Beginning\" MaxEntriesReturned=\"100\" Offset=\"0\"/>"
/*
* I have no idea why Exchnage doesn't accept this
* FieldURI for restrictions. Without it the search
* will return users that don't have an ImAddress
* and we need to filter them out ourselves :-(
" <m:Restriction>"
" <t:Exists>"
" <t:FieldURI FieldURI=\"persona:ImAddress\"/>"
" </t:Exists>"
" </m:Restriction>"
*/
" <m:ParentFolderId>"
" <t:DistinguishedFolderId Id=\"directory\"/>"
" </m:ParentFolderId>"
" <m:QueryString>%s</m:QueryString>"
"</m:FindPeople>",
query->str);
if (!sipe_ucs_http_request(sipe_private,
NULL,
body,
sipe_ucs_search_response,
token))
sipe_backend_search_failed(SIPE_CORE_PUBLIC,
token,
_("Contact search failed"));
} else
sipe_backend_search_failed(SIPE_CORE_PUBLIC,
token,
_("Invalid contact search query"));
g_string_free(query, TRUE);
}
static void sipe_ucs_ignore_response(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
SIPE_UNUSED_PARAMETER const sipe_xml *body,
SIPE_UNUSED_PARAMETER gpointer callback_data)
{
SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_ignore_response: done");
sipe_private->ucs->last_response = time(NULL);
}
static void ucs_extract_keys(const sipe_xml *persona_node,
const gchar **key,
const gchar **change)
{
const sipe_xml *attr_node;
/*
* extract Exchange key - play the guessing game :-(
*
* We can't use the "DisplayName" node, because the text is localized.
*
* Assume that IsQuickContact == "true" and IsHidden == "false" means
* this Attribution node contains the information for the Lync contact.
*/
for (attr_node = sipe_xml_child(persona_node,
"Attributions/Attribution");
attr_node;
attr_node = sipe_xml_twin(attr_node)) {
const sipe_xml *id_node = sipe_xml_child(attr_node,
"SourceId");
gchar *hidden = sipe_xml_data(sipe_xml_child(attr_node,
"IsHidden"));
gchar *quick = sipe_xml_data(sipe_xml_child(attr_node,
"IsQuickContact"));
if (id_node &&
sipe_strcase_equal(hidden, "false") &&
sipe_strcase_equal(quick, "true")) {
*key = sipe_xml_attribute(id_node, "Id");
*change = sipe_xml_attribute(id_node, "ChangeKey");
g_free(quick);
g_free(hidden);
break;
}
g_free(quick);
g_free(hidden);
}
}
static void sipe_ucs_add_new_im_contact_to_group_response(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
const sipe_xml *body,
gpointer callback_data)
{
gchar *who = callback_data;
struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private, who);
const sipe_xml *persona_node = sipe_xml_child(body,
"AddNewImContactToGroupResponse/Persona");
sipe_private->ucs->last_response = time(NULL);
if (persona_node &&
buddy &&
is_empty(buddy->exchange_key) &&
is_empty(buddy->change_key)) {
const gchar *key = NULL;
const gchar *change = NULL;
ucs_extract_keys(persona_node, &key, &change);
if (!is_empty(key) && !is_empty(change)) {
sipe_buddy_add_keys(sipe_private,
buddy,
key,
change);
SIPE_DEBUG_INFO("sipe_ucs_add_new_im_contact_to_group_response: persona URI '%s' key '%s' change '%s'",
buddy->name, key, change);
}
}
g_free(who);
}
void sipe_ucs_group_add_buddy(struct sipe_core_private *sipe_private,
struct sipe_ucs_transaction *trans,
struct sipe_group *group,
struct sipe_buddy *buddy,
const gchar *who)
{
/* existing or new buddy? */
if (buddy && buddy->exchange_key) {
gchar *body = g_strdup_printf("<m:AddImContactToGroup>"
" <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
" <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
"</m:AddImContactToGroup>",
buddy->exchange_key,
buddy->change_key,
group->exchange_key,
group->change_key);
sipe_ucs_http_request(sipe_private,
trans,
body,
sipe_ucs_ignore_response,
NULL);
} else {
gchar *payload = g_strdup(who);
gchar *body = g_strdup_printf("<m:AddNewImContactToGroup>"
" <m:ImAddress>%s</m:ImAddress>"
" <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
"</m:AddNewImContactToGroup>",
sipe_get_no_sip_uri(who),
group->exchange_key,
group->change_key);
if (!sipe_ucs_http_request(sipe_private,
trans,
body,
sipe_ucs_add_new_im_contact_to_group_response,
payload))
g_free(payload);
}
}
void sipe_ucs_group_remove_buddy(struct sipe_core_private *sipe_private,
struct sipe_ucs_transaction *trans,
struct sipe_group *group,
struct sipe_buddy *buddy)
{
if (group) {
/*
* If a contact is removed from last group, it will also be
* removed from contact list completely. The documentation has
* a RemoveContactFromImList operation, but that doesn't seem
* to work at all, i.e. it is always rejected by the server.
*/
gchar *body = g_strdup_printf("<m:RemoveImContactFromGroup>"
" <m:ContactId Id=\"%s\" ChangeKey=\"%s\"/>"
" <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
"</m:RemoveImContactFromGroup>",
buddy->exchange_key,
buddy->change_key,
group->exchange_key,
group->change_key);
sipe_ucs_http_request(sipe_private,
trans,
body,
sipe_ucs_ignore_response,
NULL);
}
}
static struct sipe_group *ucs_create_group(struct sipe_core_private *sipe_private,
const sipe_xml *group_node)
{
const sipe_xml *id_node = sipe_xml_child(group_node,
"ExchangeStoreId");
const gchar *key = sipe_xml_attribute(id_node, "Id");
const gchar *change = sipe_xml_attribute(id_node, "ChangeKey");
struct sipe_group *group = NULL;
if (!(is_empty(key) || is_empty(change))) {
gchar *name = sipe_xml_data(sipe_xml_child(group_node,
"DisplayName"));
group = sipe_group_add(sipe_private,
name,
key,
change,
/* sipe_group must have unique ID */
++sipe_private->ucs->group_id);
g_free(name);
}
return(group);
}
static void sipe_ucs_add_im_group_response(struct sipe_core_private *sipe_private,
struct sipe_ucs_transaction *trans,
const sipe_xml *body,
gpointer callback_data)
{
gchar *who = callback_data;
const sipe_xml *group_node = sipe_xml_child(body,
"AddImGroupResponse/ImGroup");
struct sipe_group *group = ucs_create_group(sipe_private, group_node);
sipe_private->ucs->last_response = time(NULL);
if (group) {
struct sipe_buddy *buddy = sipe_buddy_find_by_uri(sipe_private,
who);
if (buddy)
sipe_buddy_insert_group(buddy, group);
sipe_ucs_group_add_buddy(sipe_private,
trans,
group,
buddy,
who);
}
g_free(who);
}
void sipe_ucs_group_create(struct sipe_core_private *sipe_private,
struct sipe_ucs_transaction *trans,
const gchar *name,
const gchar *who)
{
gchar *payload = g_strdup(who);
/* new_name can contain restricted characters */
gchar *body = g_markup_printf_escaped("<m:AddImGroup>"
" <m:DisplayName>%s</m:DisplayName>"
"</m:AddImGroup>",
name);
if (!sipe_ucs_http_request(sipe_private,
trans,
body,
sipe_ucs_add_im_group_response,
payload))
g_free(payload);
}
void sipe_ucs_group_rename(struct sipe_core_private *sipe_private,
struct sipe_group *group,
const gchar *new_name)
{
/* new_name can contain restricted characters */
gchar *body = g_markup_printf_escaped("<m:SetImGroup>"
" <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
" <m:NewDisplayName>%s</m:NewDisplayName>"
"</m:SetImGroup>",
group->exchange_key,
group->change_key,
new_name);
sipe_ucs_http_request(sipe_private,
NULL,
body,
sipe_ucs_ignore_response,
NULL);
}
void sipe_ucs_group_remove(struct sipe_core_private *sipe_private,
struct sipe_group *group)
{
gchar *body = g_strdup_printf("<m:RemoveImGroup>"
" <m:GroupId Id=\"%s\" ChangeKey=\"%s\"/>"
"</m:RemoveImGroup>",
group->exchange_key,
group->change_key);
sipe_ucs_http_request(sipe_private,
NULL,
body,
sipe_ucs_ignore_response,
NULL);
}
static void ucs_init_failure(struct sipe_core_private *sipe_private)
{
/* Did the user specify any email settings? */
gboolean default_settings =
is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
SIPE_SETTING_EMAIL_URL)) &&
is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
SIPE_SETTING_EMAIL_LOGIN)) &&
is_empty(sipe_backend_setting(SIPE_CORE_PUBLIC,
SIPE_SETTING_EMAIL_PASSWORD));
sipe_backend_notify_error(SIPE_CORE_PUBLIC,
_("UCS initialization failed!"),
default_settings ?
_("Couldn't find an Exchange server with the default Email settings. Therefore the contacts list will not work.\n\nYou'll need to provide Email settings in the account setup.") :
_("Couldn't find an Exchange server with the Email settings provided in the account setup. Therefore the contacts list will not work.\n\nPlease correct your Email settings."));
}
static void sipe_ucs_get_im_item_list_response(struct sipe_core_private *sipe_private,
SIPE_UNUSED_PARAMETER struct sipe_ucs_transaction *trans,
const sipe_xml *body,
SIPE_UNUSED_PARAMETER gpointer callback_data)
{
const sipe_xml *node = sipe_xml_child(body,
"GetImItemListResponse/ImItemList");
if (node) {
const sipe_xml *persona_node;
const sipe_xml *group_node;
GHashTable *uri_to_alias = g_hash_table_new_full(g_str_hash,
g_str_equal,
NULL,
g_free);
/* Start processing contact list */
if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
sipe_group_update_start(sipe_private);
sipe_buddy_update_start(sipe_private);
} else
sipe_backend_buddy_list_processing_start(SIPE_CORE_PUBLIC);
for (persona_node = sipe_xml_child(node, "Personas/Persona");
persona_node;
persona_node = sipe_xml_twin(persona_node)) {
gchar *address = sipe_xml_data(sipe_xml_child(persona_node,
"ImAddress"));
const gchar *key = NULL;
const gchar *change = NULL;
ucs_extract_keys(persona_node, &key, &change);
if (!(is_empty(address) || is_empty(key) || is_empty(change))) {
gchar *alias = sipe_xml_data(sipe_xml_child(persona_node,
"DisplayName"));
/*
* it seems to be undefined if ImAddress node
* contains "sip:" prefix or not...
*/
gchar *uri = sip_uri(address);
struct sipe_buddy *buddy = sipe_buddy_add(sipe_private,
uri,
key,
change);
g_free(uri);
/* hash table takes ownership of alias */
g_hash_table_insert(uri_to_alias,
buddy->name,
alias);
SIPE_DEBUG_INFO("sipe_ucs_get_im_item_list_response: persona URI '%s' key '%s' change '%s'",
buddy->name, key, change);
}
g_free(address);
}
for (group_node = sipe_xml_child(node, "Groups/ImGroup");
group_node;
group_node = sipe_xml_twin(group_node)) {
struct sipe_group *group = ucs_create_group(sipe_private,
group_node);
if (group) {
const sipe_xml *member_node;
for (member_node = sipe_xml_child(group_node,
"MemberCorrelationKey/ItemId");
member_node;
member_node = sipe_xml_twin(member_node)) {
struct sipe_buddy *buddy = sipe_buddy_find_by_exchange_key(sipe_private,
sipe_xml_attribute(member_node,
"Id"));
if (buddy)
sipe_buddy_add_to_group(sipe_private,
buddy,
group,
g_hash_table_lookup(uri_to_alias,
buddy->name));
}
}
}
g_hash_table_destroy(uri_to_alias);
/* Finished processing contact list */
if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
sipe_buddy_update_finish(sipe_private);
sipe_group_update_finish(sipe_private);
} else {
sipe_buddy_cleanup_local_list(sipe_private);
sipe_backend_buddy_list_processing_finish(SIPE_CORE_PUBLIC);
sipe_subscribe_presence_initial(sipe_private);
}
} else if (sipe_private->ucs) {
SIPE_DEBUG_ERROR_NOFORMAT("sipe_ucs_get_im_item_list_response: query failed, contact list operations will not work!");
ucs_init_failure(sipe_private);
}
}
static void ucs_get_im_item_list(struct sipe_core_private *sipe_private)
{
if (sipe_private->ucs->migrated)
sipe_ucs_http_request(sipe_private,
/* prioritize over pending default requests */
sipe_ucs_transaction(sipe_private),
g_strdup("<m:GetImItemList/>"),
sipe_ucs_get_im_item_list_response,
NULL);
}
static void ucs_set_ews_url(struct sipe_core_private *sipe_private,
const gchar *ews_url)
{
struct sipe_ucs *ucs = sipe_private->ucs;
SIPE_DEBUG_INFO("ucs_set_ews_url: '%s'", ews_url);
ucs->ews_url = g_strdup(ews_url);
/* this will trigger sending of the first deferred request */
ucs_get_im_item_list(sipe_private);
}
static void ucs_ews_autodiscover_cb(struct sipe_core_private *sipe_private,
const struct sipe_ews_autodiscover_data *ews_data,
SIPE_UNUSED_PARAMETER gpointer callback_data)
{
struct sipe_ucs *ucs = sipe_private->ucs;
const gchar *ews_url = NULL;
if (!ucs)
return;
if (ews_data)
ews_url = ews_data->ews_url;
if (is_empty(ews_url)) {
SIPE_DEBUG_ERROR_NOFORMAT("ucs_ews_autodiscover_cb: can't detect EWS URL, contact list operations will not work!");
ucs_init_failure(sipe_private);
} else {
ucs_set_ews_url(sipe_private, ews_url);
}
}
gboolean sipe_ucs_is_migrated(struct sipe_core_private *sipe_private)
{
return(sipe_private->ucs ? sipe_private->ucs->migrated : FALSE);
}
const gchar *sipe_ucs_ews_url(struct sipe_core_private *sipe_private)
{
return(sipe_private->ucs ? sipe_private->ucs->ews_url : NULL);
}
void sipe_ucs_init(struct sipe_core_private *sipe_private,
gboolean migrated)
{
struct sipe_ucs *ucs;
if (sipe_private->ucs) {
struct sipe_ucs *ucs = sipe_private->ucs;
/*
* contact list update trigger -> request list again
*
* If the trigger arrives less than 10 seconds after our
* last UCS response, then ignore it, because it is caused
* by our own changes to the contact list.
*/
if (SIPE_CORE_PRIVATE_FLAG_IS(SUBSCRIBED_BUDDIES)) {
if ((time(NULL) - ucs->last_response) >= 10)
ucs_get_im_item_list(sipe_private);
else
SIPE_DEBUG_INFO_NOFORMAT("sipe_ucs_init: ignoring this contact list update - triggered by our last change");
}
ucs->last_response = 0;
return;
}
sipe_private->ucs = ucs = g_new0(struct sipe_ucs, 1);
ucs->migrated = migrated;
/* create default transaction */
sipe_ucs_transaction(sipe_private);
ucs->default_transaction = ucs->transactions;
if (migrated) {
/* user specified a service URL? */
const gchar *ews_url = sipe_backend_setting(SIPE_CORE_PUBLIC, SIPE_SETTING_EMAIL_URL);
if (is_empty(ews_url))
sipe_ews_autodiscover_start(sipe_private,
ucs_ews_autodiscover_cb,
NULL);
else
ucs_set_ews_url(sipe_private, ews_url);
}
}
void sipe_ucs_free(struct sipe_core_private *sipe_private)
{
struct sipe_ucs *ucs = sipe_private->ucs;
GSList *entry;
if (!ucs)
return;
/* UCS stack is shutting down: reject all new requests */
ucs->shutting_down = TRUE;
entry = ucs->transactions;
while (entry) {
struct sipe_ucs_transaction *trans = entry->data;
GSList *entry2 = trans->pending_requests;
/* transactions get deleted by sipe_ucs_request_free() */
entry = entry->next;
while (entry2) {
struct ucs_request *request = entry2->data;
/* transactions get deleted by sipe_ucs_request_free() */
entry2 = entry2->next;
sipe_ucs_request_free(sipe_private, request);
}
}
/* only default transaction is left... */
sipe_utils_slist_free_full(ucs->transactions, g_free);
g_free(ucs->ews_url);
g_free(ucs);
sipe_private->ucs = NULL;
}
/*
Local Variables:
mode: c
c-file-style: "bsd"
indent-tabs-mode: t
tab-width: 8
End:
*/