lync: add missing _ready() call for HTTP requests
[siplcs.git] / src / core / sipe-svc.c
blob2eb028df402992c09deb97a2300c032ca49cc844
1 /**
2 * @file sipe-svc.c
4 * pidgin-sipe
6 * Copyright (C) 2011-2015 SIPE Project <http://sipe.sourceforge.net/>
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
24 * Specification references:
26 * - [MS-SIPAE]: http://msdn.microsoft.com/en-us/library/cc431510.aspx
27 * - [MS-OCAUTHWS]: http://msdn.microsoft.com/en-us/library/ff595592.aspx
28 * - MS Tech-Ed Europe 2010 "UNC310: Microsoft Lync 2010 Technology Explained"
29 * http://ecn.channel9.msdn.com/o9/te/Europe/2010/pptx/unc310.pptx
32 #include <stdlib.h>
33 #include <string.h>
35 #include <glib.h>
37 #include "sipe-common.h"
38 #include "sipe-backend.h"
39 #include "sipe-core.h"
40 #include "sipe-core-private.h"
41 #include "sipe-http.h"
42 #include "sipe-svc.h"
43 #include "sipe-tls.h"
44 #include "sipe-utils.h"
45 #include "sipe-xml.h"
46 #include "uuid.h"
48 /* forward declaration */
49 struct svc_request;
50 typedef void (svc_callback)(struct sipe_core_private *sipe_private,
51 struct svc_request *data,
52 const gchar *raw,
53 sipe_xml *xml);
55 struct svc_request {
56 svc_callback *internal_cb;
57 sipe_svc_callback *cb;
58 gpointer *cb_data;
59 struct sipe_http_request *request;
60 gchar *uri;
63 struct sipe_svc {
64 GSList *pending_requests;
65 gboolean shutting_down;
68 struct sipe_svc_session {
69 struct sipe_http_session *session;
72 static void sipe_svc_request_free(struct sipe_core_private *sipe_private,
73 struct svc_request *data)
75 if (data->request)
76 sipe_http_request_cancel(data->request);
77 if (data->cb)
78 /* Callback: aborted */
79 (*data->cb)(sipe_private, NULL, NULL, NULL, data->cb_data);
80 g_free(data->uri);
81 g_free(data);
84 void sipe_svc_free(struct sipe_core_private *sipe_private)
86 struct sipe_svc *svc = sipe_private->svc;
87 if (!svc)
88 return;
90 /* Web Service stack is shutting down: reject all new requests */
91 svc->shutting_down = TRUE;
93 if (svc->pending_requests) {
94 GSList *entry = svc->pending_requests;
95 while (entry) {
96 sipe_svc_request_free(sipe_private, entry->data);
97 entry = entry->next;
99 g_slist_free(svc->pending_requests);
102 g_free(svc);
103 sipe_private->svc = NULL;
106 static void sipe_svc_init(struct sipe_core_private *sipe_private)
108 if (sipe_private->svc)
109 return;
111 sipe_private->svc = g_new0(struct sipe_svc, 1);
114 struct sipe_svc_session *sipe_svc_session_start(void)
116 struct sipe_svc_session *session = g_new0(struct sipe_svc_session, 1);
117 session->session = sipe_http_session_start();
118 return(session);
121 void sipe_svc_session_close(struct sipe_svc_session *session)
123 if (session) {
124 sipe_http_session_close(session->session);
125 g_free(session);
129 static void sipe_svc_https_response(struct sipe_core_private *sipe_private,
130 guint status,
131 SIPE_UNUSED_PARAMETER GSList *headers,
132 const gchar *body,
133 gpointer callback_data)
135 struct svc_request *data = callback_data;
136 struct sipe_svc *svc = sipe_private->svc;
138 SIPE_DEBUG_INFO("sipe_svc_https_response: code %d", status);
139 data->request = NULL;
141 if ((status == SIPE_HTTP_STATUS_OK) && body) {
142 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
143 /* Internal callback: success */
144 (*data->internal_cb)(sipe_private, data, body, xml);
145 sipe_xml_free(xml);
146 } else {
147 /* Internal callback: failed */
148 (*data->internal_cb)(sipe_private, data, NULL, NULL);
151 /* Internal callback has already called this */
152 data->cb = NULL;
154 svc->pending_requests = g_slist_remove(svc->pending_requests,
155 data);
156 sipe_svc_request_free(sipe_private, data);
160 * Send GET request when @c body is NULL, otherwise send POST request
162 * @param content_type MIME type for body content (ignored when body is @c NULL)
163 * @param soap_action SOAP action header value (ignored when body is @c NULL)
164 * @param body body contents (may be @c NULL)
166 static gboolean sipe_svc_https_request(struct sipe_core_private *sipe_private,
167 struct sipe_svc_session *session,
168 const gchar *uri,
169 const gchar *content_type,
170 const gchar *soap_action,
171 const gchar *body,
172 svc_callback *internal_callback,
173 sipe_svc_callback *callback,
174 gpointer callback_data)
176 struct svc_request *data = g_new0(struct svc_request, 1);
177 struct sipe_http_request *request = NULL;
178 struct sipe_svc *svc;
180 sipe_svc_init(sipe_private);
181 svc = sipe_private->svc;
183 if (svc->shutting_down) {
184 SIPE_DEBUG_ERROR("sipe_svc_https_request: new Web Service request during shutdown: THIS SHOULD NOT HAPPEN! Debugging information:\n"
185 "URI: %s\n"
186 "Action: %s\n"
187 "Body: %s\n",
188 uri,
189 soap_action ? soap_action : "<NONE>",
190 body ? body : "<EMPTY>");
191 } else {
192 if (body) {
193 gchar *headers = g_strdup_printf("SOAPAction: \"%s\"\r\n",
194 soap_action);
196 request = sipe_http_request_post(sipe_private,
197 uri,
198 headers,
199 body,
200 content_type,
201 sipe_svc_https_response,
202 data);
203 g_free(headers);
205 } else {
206 request = sipe_http_request_get(sipe_private,
207 uri,
208 NULL,
209 sipe_svc_https_response,
210 data);
214 if (request) {
215 data->internal_cb = internal_callback;
216 data->cb = callback;
217 data->cb_data = callback_data;
218 data->request = request;
219 data->uri = g_strdup(uri);
221 svc->pending_requests = g_slist_prepend(svc->pending_requests,
222 data);
224 sipe_http_request_session(request, session->session);
225 sipe_http_request_ready(request);
227 } else {
228 SIPE_DEBUG_ERROR("failed to create HTTP connection to %s", uri);
229 g_free(data);
232 return(request != NULL);
235 static gboolean sipe_svc_wsdl_request(struct sipe_core_private *sipe_private,
236 struct sipe_svc_session *session,
237 const gchar *uri,
238 const gchar *additional_ns,
239 const gchar *soap_action,
240 const gchar *wsse_security,
241 const gchar *soap_body,
242 const gchar *content_type,
243 svc_callback *internal_callback,
244 sipe_svc_callback *callback,
245 gpointer callback_data)
247 /* Only generate UUID & SOAP header if we have a security token */
248 gchar *uuid = wsse_security ?
249 generateUUIDfromEPID(wsse_security) :
250 NULL;
251 gchar *soap_header = wsse_security ?
252 g_strdup_printf("<soap:Header>"
253 " <wsa:To>%s</wsa:To>"
254 " <wsa:ReplyTo>"
255 " <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>"
256 " </wsa:ReplyTo>"
257 " <wsa:MessageID>uuid:%s</wsa:MessageID>"
258 " <wsa:Action>%s</wsa:Action>"
259 " <wsse:Security>%s</wsse:Security>"
260 "</soap:Header>",
261 uri,
262 uuid,
263 soap_action,
264 wsse_security) :
265 g_strdup("");
266 gchar *body = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
267 "<soap:Envelope %s"
268 " xmlns:auth=\"http://schemas.xmlsoap.org/ws/2006/12/authorization\""
269 " xmlns:wsa=\"http://www.w3.org/2005/08/addressing\""
270 " xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\""
271 " xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\""
272 " xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\""
273 " >"
274 "%s"
275 " <soap:Body>%s</soap:Body>"
276 "</soap:Envelope>",
277 additional_ns,
278 soap_header,
279 soap_body);
281 gboolean ret = sipe_svc_https_request(sipe_private,
282 session,
283 uri,
284 content_type ? content_type : "text/xml",
285 soap_action,
286 body,
287 internal_callback,
288 callback,
289 callback_data);
290 g_free(uuid);
291 g_free(soap_header);
292 g_free(body);
294 return(ret);
297 static gboolean new_soap_req(struct sipe_core_private *sipe_private,
298 struct sipe_svc_session *session,
299 const gchar *uri,
300 const gchar *soap_action,
301 const gchar *wsse_security,
302 const gchar *soap_body,
303 svc_callback *internal_callback,
304 sipe_svc_callback *callback,
305 gpointer callback_data)
307 return(sipe_svc_wsdl_request(sipe_private,
308 session,
309 uri,
310 "xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" "
311 "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
312 "xmlns:wst=\"http://docs.oasis-open.org/ws-sx/ws-trust/200512\"",
313 soap_action,
314 wsse_security,
315 soap_body,
316 NULL,
317 internal_callback,
318 callback,
319 callback_data));
322 static void sipe_svc_wsdl_response(struct sipe_core_private *sipe_private,
323 struct svc_request *data,
324 const gchar *raw,
325 sipe_xml *xml)
327 if (xml) {
328 /* Callback: success */
329 (*data->cb)(sipe_private, data->uri, raw, xml, data->cb_data);
330 } else {
331 /* Callback: failed */
332 (*data->cb)(sipe_private, data->uri, NULL, NULL, data->cb_data);
336 gboolean sipe_svc_get_and_publish_cert(struct sipe_core_private *sipe_private,
337 struct sipe_svc_session *session,
338 const gchar *uri,
339 const gchar *wsse_security,
340 const gchar *certreq,
341 sipe_svc_callback *callback,
342 gpointer callback_data)
344 struct sipe_tls_random id;
345 gchar *id_base64;
346 gchar *id_uuid;
347 gchar *uuid = get_uuid(sipe_private);
348 gchar *soap_body;
349 gboolean ret;
351 /* random request ID */
352 sipe_tls_fill_random(&id, 256);
353 id_base64 = g_base64_encode(id.buffer, id.length);
354 sipe_tls_free_random(&id);
355 id_uuid = generateUUIDfromEPID(id_base64);
356 g_free(id_base64);
358 soap_body = g_strdup_printf("<GetAndPublishCert"
359 " xmlns=\"http://schemas.microsoft.com/OCS/AuthWebServices/\""
360 " xmlns:wst=\"http://docs.oasis-open.org/ws-sx/ws-trust/200512/\""
361 " xmlns:wstep=\"http://schemas.microsoft.com/windows/pki/2009/01/enrollment\""
362 " DeviceId=\"{%s}\""
363 " Entity=\"%s\""
365 " <wst:RequestSecurityToken>"
366 " <wst:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3</wst:TokenType>"
367 " <wst:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</wst:RequestType>"
368 " <wsse:BinarySecurityToken"
369 " ValueType=\"http://schemas.microsoft.com/OCS/AuthWebServices.xsd#PKCS10\""
370 " EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#Base64Binary\""
371 " >\r\n%s</wsse:BinarySecurityToken>"
372 " <wstep:RequestID>%s</wstep:RequestID>"
373 " </wst:RequestSecurityToken>"
374 "</GetAndPublishCert>",
375 uuid,
376 sipe_private->username,
377 certreq,
378 id_uuid);
379 g_free(id_uuid);
380 g_free(uuid);
382 ret = new_soap_req(sipe_private,
383 session,
384 uri,
385 "http://schemas.microsoft.com/OCS/AuthWebServices/GetAndPublishCert",
386 wsse_security,
387 soap_body,
388 sipe_svc_wsdl_response,
389 callback,
390 callback_data);
391 g_free(soap_body);
393 return(ret);
396 gboolean sipe_svc_ab_entry_request(struct sipe_core_private *sipe_private,
397 struct sipe_svc_session *session,
398 const gchar *uri,
399 const gchar *wsse_security,
400 const gchar *search,
401 guint max_returns,
402 sipe_svc_callback *callback,
403 gpointer callback_data)
405 gboolean ret;
406 gchar *soap_body = g_strdup_printf("<SearchAbEntry"
407 " xmlns=\"DistributionListExpander\""
408 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
409 " xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\""
411 " <AbEntryRequest>"
412 " %s"
413 " <Metadata>"
414 " <FromDialPad>false</FromDialPad>"
415 " <MaxResultNum>%d</MaxResultNum>"
416 " <ReturnList>displayName,msRTCSIP-PrimaryUserAddress,title,telephoneNumber,homePhone,mobile,otherTelephone,mail,company,country,photoRelPath,photoSize,photoHash</ReturnList>"
417 " </Metadata>"
418 " </AbEntryRequest>"
419 "</SearchAbEntry>",
420 search,
421 max_returns);
423 ret = new_soap_req(sipe_private,
424 session,
425 uri,
426 "DistributionListExpander/IAddressBook/SearchAbEntry",
427 wsse_security,
428 soap_body,
429 sipe_svc_wsdl_response,
430 callback,
431 callback_data);
432 g_free(soap_body);
434 return(ret);
437 /* Requests to login.microsoftonline.com & ADFS */
438 static gboolean request_passport(struct sipe_core_private *sipe_private,
439 struct sipe_svc_session *session,
440 const gchar *service_uri,
441 const gchar *auth_uri,
442 const gchar *wsse_security,
443 const gchar *content_type,
444 const gchar *request_extension,
445 sipe_svc_callback *callback,
446 gpointer callback_data)
448 gchar *soap_body = g_strdup_printf("<wst:RequestSecurityToken>"
449 " <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
450 " <wsp:AppliesTo>"
451 " <wsa:EndpointReference>"
452 " <wsa:Address>%s</wsa:Address>"
453 " </wsa:EndpointReference>"
454 " </wsp:AppliesTo>"
455 " %s"
456 "</wst:RequestSecurityToken>",
457 service_uri,
458 request_extension ? request_extension : "");
460 gboolean ret = sipe_svc_wsdl_request(sipe_private,
461 session,
462 auth_uri,
463 "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" "
464 "xmlns:wst=\"http://schemas.xmlsoap.org/ws/2005/02/trust\"",
465 "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue",
466 wsse_security,
467 soap_body,
468 content_type,
469 sipe_svc_wsdl_response,
470 callback,
471 callback_data);
472 g_free(soap_body);
474 return(ret);
477 static gboolean request_user_password(struct sipe_core_private *sipe_private,
478 struct sipe_svc_session *session,
479 const gchar *service_uri,
480 const gchar *auth_uri,
481 const gchar *content_type,
482 const gchar *request_extension,
483 sipe_svc_callback *callback,
484 gpointer callback_data)
486 /* Only cleartext passwords seem to be accepted... */
487 gchar *wsse_security = g_markup_printf_escaped("<wsse:UsernameToken>"
488 " <wsse:Username>%s</wsse:Username>"
489 " <wsse:Password>%s</wsse:Password>"
490 "</wsse:UsernameToken>",
491 sipe_private->authuser ? sipe_private->authuser : sipe_private->username,
492 sipe_private->password ? sipe_private->password : "");
494 gboolean ret = request_passport(sipe_private,
495 session,
496 service_uri,
497 auth_uri,
498 wsse_security,
499 content_type,
500 request_extension,
501 callback,
502 callback_data);
503 g_free(wsse_security);
505 return(ret);
508 gboolean sipe_svc_webticket_adfs(struct sipe_core_private *sipe_private,
509 struct sipe_svc_session *session,
510 const gchar *adfs_uri,
511 sipe_svc_callback *callback,
512 gpointer callback_data)
514 return(request_user_password(sipe_private,
515 session,
516 "urn:federation:MicrosoftOnline",
517 adfs_uri,
518 /* ADFS is special, *sigh* */
519 "application/soap+xml; charset=utf-8",
520 "<wst:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</wst:KeyType>",
521 callback,
522 callback_data));
525 #define LMC_URI "https://login.microsoftonline.com:443/RST2.srf"
527 gboolean sipe_svc_webticket_lmc(struct sipe_core_private *sipe_private,
528 struct sipe_svc_session *session,
529 const gchar *service_uri,
530 sipe_svc_callback *callback,
531 gpointer callback_data)
533 return(request_user_password(sipe_private,
534 session,
535 service_uri,
536 LMC_URI,
537 NULL,
538 NULL,
539 callback,
540 callback_data));
543 gboolean sipe_svc_webticket_lmc_federated(struct sipe_core_private *sipe_private,
544 struct sipe_svc_session *session,
545 const gchar *wsse_security,
546 const gchar *service_uri,
547 sipe_svc_callback *callback,
548 gpointer callback_data)
550 return(request_passport(sipe_private,
551 session,
552 service_uri,
553 LMC_URI,
554 wsse_security,
555 NULL,
556 NULL,
557 callback,
558 callback_data));
561 gboolean sipe_svc_webticket(struct sipe_core_private *sipe_private,
562 struct sipe_svc_session *session,
563 const gchar *uri,
564 const gchar *wsse_security,
565 const gchar *service_uri,
566 const struct sipe_tls_random *entropy,
567 sipe_svc_callback *callback,
568 gpointer callback_data)
570 gchar *uuid = get_uuid(sipe_private);
571 gchar *secret = g_base64_encode(entropy->buffer, entropy->length);
572 gchar *soap_body = g_strdup_printf("<wst:RequestSecurityToken Context=\"%s\">"
573 " <wst:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1</wst:TokenType>"
574 " <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
575 " <wsp:AppliesTo>"
576 " <wsa:EndpointReference>"
577 " <wsa:Address>%s</wsa:Address>"
578 " </wsa:EndpointReference>"
579 " </wsp:AppliesTo>"
580 " <wst:Claims Dialect=\"urn:component:Microsoft.Rtc.WebAuthentication.2010:authclaims\">"
581 " <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/uri\" Optional=\"false\">"
582 " <auth:Value>sip:%s</auth:Value>"
583 " </auth:ClaimType>"
584 " </wst:Claims>"
585 " <wst:Entropy>"
586 " <wst:BinarySecret>%s</wst:BinarySecret>"
587 " </wst:Entropy>"
588 " <wst:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</wst:KeyType>"
589 "</wst:RequestSecurityToken>",
590 uuid,
591 service_uri,
592 sipe_private->username,
593 secret);
595 gboolean ret = new_soap_req(sipe_private,
596 session,
597 uri,
598 "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue",
599 wsse_security,
600 soap_body,
601 sipe_svc_wsdl_response,
602 callback,
603 callback_data);
604 g_free(soap_body);
605 g_free(secret);
606 g_free(uuid);
608 return(ret);
611 static void sipe_svc_metadata_response(struct sipe_core_private *sipe_private,
612 struct svc_request *data,
613 const gchar *raw,
614 sipe_xml *xml)
616 if (xml) {
617 /* Callback: success */
618 (*data->cb)(sipe_private, data->uri, raw, xml, data->cb_data);
619 } else {
620 /* Callback: failed */
621 (*data->cb)(sipe_private, data->uri, NULL, NULL, data->cb_data);
625 gboolean sipe_svc_realminfo(struct sipe_core_private *sipe_private,
626 struct sipe_svc_session *session,
627 sipe_svc_callback *callback,
628 gpointer callback_data)
631 * For some users RealmInfo response is different for authuser and
632 * username. Use authuser, but only if it looks like "user@domain".
634 gchar *realminfo_uri = g_strdup_printf("https://login.microsoftonline.com/getuserrealm.srf?login=%s&xml=1",
635 sipe_private->authuser && strchr(sipe_private->authuser, '@') ?
636 sipe_private->authuser : sipe_private->username);
637 gboolean ret = sipe_svc_https_request(sipe_private,
638 session,
639 realminfo_uri,
640 NULL,
641 NULL,
642 NULL,
643 sipe_svc_metadata_response,
644 callback,
645 callback_data);
646 g_free(realminfo_uri);
647 return(ret);
650 gboolean sipe_svc_metadata(struct sipe_core_private *sipe_private,
651 struct sipe_svc_session *session,
652 const gchar *uri,
653 sipe_svc_callback *callback,
654 gpointer callback_data)
656 gchar *mex_uri = g_strdup_printf("%s/mex", uri);
657 gboolean ret = sipe_svc_https_request(sipe_private,
658 session,
659 mex_uri,
660 NULL,
661 NULL,
662 NULL,
663 sipe_svc_metadata_response,
664 callback,
665 callback_data);
666 g_free(mex_uri);
667 return(ret);
671 Local Variables:
672 mode: c
673 c-file-style: "bsd"
674 indent-tabs-mode: t
675 tab-width: 8
676 End: