http: add API function to flag request as ready
[siplcs.git] / src / core / sipe-svc.c
blobccb5f1aba46f31f38dbbbc081bef90382efc17b8
1 /**
2 * @file sipe-svc.c
4 * pidgin-sipe
6 * Copyright (C) 2011-2013 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;
67 struct sipe_svc_session {
68 struct sipe_http_session *session;
71 static void sipe_svc_request_free(struct sipe_core_private *sipe_private,
72 struct svc_request *data)
74 if (data->request)
75 sipe_http_request_cancel(data->request);
76 if (data->cb)
77 /* Callback: aborted */
78 (*data->cb)(sipe_private, NULL, NULL, NULL, data->cb_data);
79 g_free(data->uri);
80 g_free(data);
83 void sipe_svc_free(struct sipe_core_private *sipe_private)
85 struct sipe_svc *svc = sipe_private->svc;
86 if (!svc)
87 return;
89 if (svc->pending_requests) {
90 GSList *entry = svc->pending_requests;
91 while (entry) {
92 sipe_svc_request_free(sipe_private, entry->data);
93 entry = entry->next;
95 g_slist_free(svc->pending_requests);
98 g_free(svc);
99 sipe_private->svc = NULL;
102 static void sipe_svc_init(struct sipe_core_private *sipe_private)
104 if (sipe_private->svc)
105 return;
107 sipe_private->svc = g_new0(struct sipe_svc, 1);
110 struct sipe_svc_session *sipe_svc_session_start(void)
112 struct sipe_svc_session *session = g_new0(struct sipe_svc_session, 1);
113 session->session = sipe_http_session_start();
114 return(session);
117 void sipe_svc_session_close(struct sipe_svc_session *session)
119 if (session) {
120 sipe_http_session_close(session->session);
121 g_free(session);
125 static void sipe_svc_https_response(struct sipe_core_private *sipe_private,
126 guint status,
127 SIPE_UNUSED_PARAMETER GSList *headers,
128 const gchar *body,
129 gpointer callback_data)
131 struct svc_request *data = callback_data;
132 struct sipe_svc *svc = sipe_private->svc;
134 SIPE_DEBUG_INFO("sipe_svc_https_response: code %d", status);
135 data->request = NULL;
137 if ((status == SIPE_HTTP_STATUS_OK) && body) {
138 sipe_xml *xml = sipe_xml_parse(body, strlen(body));
139 /* Internal callback: success */
140 (*data->internal_cb)(sipe_private, data, body, xml);
141 sipe_xml_free(xml);
142 } else {
143 /* Internal callback: failed */
144 (*data->internal_cb)(sipe_private, data, NULL, NULL);
147 /* Internal callback has already called this */
148 data->cb = NULL;
150 svc->pending_requests = g_slist_remove(svc->pending_requests,
151 data);
152 sipe_svc_request_free(sipe_private, data);
156 * Send GET request when @c body is NULL, otherwise send POST request
158 * @param content_type MIME type for body content (ignored when body is @c NULL)
159 * @param soap_action SOAP action header value (ignored when body is @c NULL)
160 * @param body body contents (may be @c NULL)
162 static gboolean sipe_svc_https_request(struct sipe_core_private *sipe_private,
163 struct sipe_svc_session *session,
164 const gchar *uri,
165 const gchar *content_type,
166 const gchar *soap_action,
167 const gchar *body,
168 svc_callback *internal_callback,
169 sipe_svc_callback *callback,
170 gpointer callback_data)
172 struct svc_request *data = g_new0(struct svc_request, 1);
173 struct sipe_http_request *request;
175 if (body) {
176 gchar *headers = g_strdup_printf("SOAPAction: \"%s\"\r\n",
177 soap_action);
179 request = sipe_http_request_post(sipe_private,
180 uri,
181 headers,
182 body,
183 content_type,
184 sipe_svc_https_response,
185 data);
186 g_free(headers);
188 } else {
189 request = sipe_http_request_get(sipe_private,
190 uri,
191 NULL,
192 sipe_svc_https_response,
193 data);
196 if (request) {
197 data->internal_cb = internal_callback;
198 data->cb = callback;
199 data->cb_data = callback_data;
200 data->request = request;
201 data->uri = g_strdup(uri);
203 sipe_svc_init(sipe_private);
204 sipe_private->svc->pending_requests = g_slist_prepend(sipe_private->svc->pending_requests,
205 data);
207 sipe_http_request_session(request, session->session);
208 sipe_http_request_ready(request);
210 } else {
211 SIPE_DEBUG_ERROR("failed to create HTTP connection to %s", uri);
212 g_free(data);
215 return(request != NULL);
218 static gboolean sipe_svc_wsdl_request(struct sipe_core_private *sipe_private,
219 struct sipe_svc_session *session,
220 const gchar *uri,
221 const gchar *additional_ns,
222 const gchar *soap_action,
223 const gchar *wsse_security,
224 const gchar *soap_body,
225 const gchar *content_type,
226 svc_callback *internal_callback,
227 sipe_svc_callback *callback,
228 gpointer callback_data)
230 /* Only generate SOAP header if we have a security token */
231 gchar *soap_header = wsse_security ?
232 g_strdup_printf("<soap:Header>"
233 " <wsa:To>%s</wsa:To>"
234 " <wsa:ReplyTo>"
235 " <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>"
236 " </wsa:ReplyTo>"
237 " <wsa:Action>%s</wsa:Action>"
238 " <wsse:Security>%s</wsse:Security>"
239 "</soap:Header>",
240 uri,
241 soap_action,
242 wsse_security) :
243 g_strdup("");
244 gchar *body = g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
245 "<soap:Envelope %s"
246 " xmlns:auth=\"http://schemas.xmlsoap.org/ws/2006/12/authorization\""
247 " xmlns:wsa=\"http://www.w3.org/2005/08/addressing\""
248 " xmlns:wsp=\"http://schemas.xmlsoap.org/ws/2004/09/policy\""
249 " xmlns:wsse=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd\""
250 " xmlns:wsu=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd\""
251 " >"
252 "%s"
253 " <soap:Body>%s</soap:Body>"
254 "</soap:Envelope>",
255 additional_ns,
256 soap_header,
257 soap_body);
259 gboolean ret = sipe_svc_https_request(sipe_private,
260 session,
261 uri,
262 content_type ? content_type : "text/xml",
263 soap_action,
264 body,
265 internal_callback,
266 callback,
267 callback_data);
268 g_free(soap_header);
269 g_free(body);
271 return(ret);
274 static gboolean new_soap_req(struct sipe_core_private *sipe_private,
275 struct sipe_svc_session *session,
276 const gchar *uri,
277 const gchar *soap_action,
278 const gchar *wsse_security,
279 const gchar *soap_body,
280 svc_callback *internal_callback,
281 sipe_svc_callback *callback,
282 gpointer callback_data)
284 return(sipe_svc_wsdl_request(sipe_private,
285 session,
286 uri,
287 "xmlns:saml=\"urn:oasis:names:tc:SAML:1.0:assertion\" "
288 "xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\" "
289 "xmlns:wst=\"http://docs.oasis-open.org/ws-sx/ws-trust/200512\"",
290 soap_action,
291 wsse_security,
292 soap_body,
293 NULL,
294 internal_callback,
295 callback,
296 callback_data));
299 static void sipe_svc_wsdl_response(struct sipe_core_private *sipe_private,
300 struct svc_request *data,
301 const gchar *raw,
302 sipe_xml *xml)
304 if (xml) {
305 /* Callback: success */
306 (*data->cb)(sipe_private, data->uri, raw, xml, data->cb_data);
307 } else {
308 /* Callback: failed */
309 (*data->cb)(sipe_private, data->uri, NULL, NULL, data->cb_data);
313 gboolean sipe_svc_get_and_publish_cert(struct sipe_core_private *sipe_private,
314 struct sipe_svc_session *session,
315 const gchar *uri,
316 const gchar *wsse_security,
317 const gchar *certreq,
318 sipe_svc_callback *callback,
319 gpointer callback_data)
321 struct sipe_tls_random id;
322 gchar *id_base64;
323 gchar *id_uuid;
324 gchar *uuid = get_uuid(sipe_private);
325 gchar *soap_body;
326 gboolean ret;
328 /* random request ID */
329 sipe_tls_fill_random(&id, 256);
330 id_base64 = g_base64_encode(id.buffer, id.length);
331 sipe_tls_free_random(&id);
332 id_uuid = generateUUIDfromEPID(id_base64);
333 g_free(id_base64);
335 soap_body = g_strdup_printf("<GetAndPublishCert"
336 " xmlns=\"http://schemas.microsoft.com/OCS/AuthWebServices/\""
337 " xmlns:wst=\"http://docs.oasis-open.org/ws-sx/ws-trust/200512/\""
338 " xmlns:wstep=\"http://schemas.microsoft.com/windows/pki/2009/01/enrollment\""
339 " DeviceId=\"{%s}\""
340 " Entity=\"%s\""
342 " <wst:RequestSecurityToken>"
343 " <wst:TokenType>http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509v3</wst:TokenType>"
344 " <wst:RequestType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/Issue</wst:RequestType>"
345 " <wsse:BinarySecurityToken"
346 " ValueType=\"http://schemas.microsoft.com/OCS/AuthWebServices.xsd#PKCS10\""
347 " EncodingType=\"http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd#Base64Binary\""
348 " >\r\n%s</wsse:BinarySecurityToken>"
349 " <wstep:RequestID>%s</wstep:RequestID>"
350 " </wst:RequestSecurityToken>"
351 "</GetAndPublishCert>",
352 uuid,
353 sipe_private->username,
354 certreq,
355 id_uuid);
356 g_free(id_uuid);
357 g_free(uuid);
359 ret = new_soap_req(sipe_private,
360 session,
361 uri,
362 "http://schemas.microsoft.com/OCS/AuthWebServices/GetAndPublishCert",
363 wsse_security,
364 soap_body,
365 sipe_svc_wsdl_response,
366 callback,
367 callback_data);
368 g_free(soap_body);
370 return(ret);
373 gboolean sipe_svc_ab_entry_request(struct sipe_core_private *sipe_private,
374 struct sipe_svc_session *session,
375 const gchar *uri,
376 const gchar *wsse_security,
377 const gchar *search,
378 guint entries,
379 guint max_returns,
380 sipe_svc_callback *callback,
381 gpointer callback_data)
383 gboolean ret;
384 gchar *soap_body = g_strdup_printf("<SearchAbEntry"
385 " xmlns=\"DistributionListExpander\""
386 " xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\""
387 " xmlns:soapenc=\"http://schemas.xmlsoap.org/soap/encoding/\""
389 " <AbEntryRequest>"
390 " <ChangeSearch xmlns:q1=\"DistributionListExpander\" soapenc:arrayType=\"q1:AbEntryRequest.ChangeSearchQuery[%d]\">"
391 " %s"
392 " </ChangeSearch>"
393 " <Metadata>"
394 " <FromDialPad>false</FromDialPad>"
395 " <MaxResultNum>%d</MaxResultNum>"
396 " <ReturnList>displayName,msRTCSIP-PrimaryUserAddress,title,telephoneNumber,homePhone,mobile,otherTelephone,mail,company,country,photoRelPath,photoSize,photoHash</ReturnList>"
397 " </Metadata>"
398 " </AbEntryRequest>"
399 "</SearchAbEntry>",
400 entries,
401 search,
402 max_returns);
404 ret = new_soap_req(sipe_private,
405 session,
406 uri,
407 "DistributionListExpander/IAddressBook/SearchAbEntry",
408 wsse_security,
409 soap_body,
410 sipe_svc_wsdl_response,
411 callback,
412 callback_data);
413 g_free(soap_body);
415 return(ret);
418 /* Requests to login.microsoftonline.com & ADFS */
419 static gboolean request_passport(struct sipe_core_private *sipe_private,
420 struct sipe_svc_session *session,
421 const gchar *service_uri,
422 const gchar *auth_uri,
423 const gchar *wsse_security,
424 const gchar *content_type,
425 const gchar *request_extension,
426 sipe_svc_callback *callback,
427 gpointer callback_data)
429 gchar *soap_body = g_strdup_printf("<wst:RequestSecurityToken>"
430 " <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
431 " <wsp:AppliesTo>"
432 " <wsa:EndpointReference>"
433 " <wsa:Address>%s</wsa:Address>"
434 " </wsa:EndpointReference>"
435 " </wsp:AppliesTo>"
436 " %s"
437 "</wst:RequestSecurityToken>",
438 service_uri,
439 request_extension ? request_extension : "");
441 gboolean ret = sipe_svc_wsdl_request(sipe_private,
442 session,
443 auth_uri,
444 "xmlns:soap=\"http://www.w3.org/2003/05/soap-envelope\" "
445 "xmlns:wst=\"http://schemas.xmlsoap.org/ws/2005/02/trust\"",
446 "http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue",
447 wsse_security,
448 soap_body,
449 content_type,
450 sipe_svc_wsdl_response,
451 callback,
452 callback_data);
453 g_free(soap_body);
455 return(ret);
458 static gboolean request_user_password(struct sipe_core_private *sipe_private,
459 struct sipe_svc_session *session,
460 const gchar *service_uri,
461 const gchar *auth_uri,
462 const gchar *authuser,
463 const gchar *content_type,
464 const gchar *request_extension,
465 sipe_svc_callback *callback,
466 gpointer callback_data)
468 /* Only cleartext passwords seem to be accepted... */
469 gchar *wsse_security = g_strdup_printf("<wsse:UsernameToken>"
470 " <wsse:Username>%s</wsse:Username>"
471 " <wsse:Password>%s</wsse:Password>"
472 "</wsse:UsernameToken>",
473 authuser,
474 sipe_private->password ? sipe_private->password : "");
476 gboolean ret = request_passport(sipe_private,
477 session,
478 service_uri,
479 auth_uri,
480 wsse_security,
481 content_type,
482 request_extension,
483 callback,
484 callback_data);
485 g_free(wsse_security);
487 return(ret);
490 gboolean sipe_svc_webticket_adfs(struct sipe_core_private *sipe_private,
491 struct sipe_svc_session *session,
492 const gchar *adfs_uri,
493 sipe_svc_callback *callback,
494 gpointer callback_data)
496 return(request_user_password(sipe_private,
497 session,
498 "urn:federation:MicrosoftOnline",
499 adfs_uri,
500 sipe_private->username,
501 /* ADFS is special, *sigh* */
502 "application/soap+xml; charset=utf-8",
503 "<wst:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</wst:KeyType>",
504 callback,
505 callback_data));
508 #define LMC_URI "https://login.microsoftonline.com:443/RST2.srf"
510 gboolean sipe_svc_webticket_lmc(struct sipe_core_private *sipe_private,
511 struct sipe_svc_session *session,
512 const gchar *service_uri,
513 sipe_svc_callback *callback,
514 gpointer callback_data)
516 return(request_user_password(sipe_private,
517 session,
518 service_uri,
519 LMC_URI,
520 sipe_private->authuser ? sipe_private->authuser : sipe_private->username,
521 NULL,
522 NULL,
523 callback,
524 callback_data));
527 gboolean sipe_svc_webticket_lmc_federated(struct sipe_core_private *sipe_private,
528 struct sipe_svc_session *session,
529 const gchar *wsse_security,
530 const gchar *service_uri,
531 sipe_svc_callback *callback,
532 gpointer callback_data)
534 return(request_passport(sipe_private,
535 session,
536 service_uri,
537 LMC_URI,
538 wsse_security,
539 NULL,
540 NULL,
541 callback,
542 callback_data));
545 gboolean sipe_svc_webticket(struct sipe_core_private *sipe_private,
546 struct sipe_svc_session *session,
547 const gchar *uri,
548 const gchar *wsse_security,
549 const gchar *service_uri,
550 const struct sipe_tls_random *entropy,
551 sipe_svc_callback *callback,
552 gpointer callback_data)
554 gchar *uuid = get_uuid(sipe_private);
555 gchar *secret = g_base64_encode(entropy->buffer, entropy->length);
556 gchar *soap_body = g_strdup_printf("<wst:RequestSecurityToken Context=\"%s\">"
557 " <wst:TokenType>http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1</wst:TokenType>"
558 " <wst:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</wst:RequestType>"
559 " <wsp:AppliesTo>"
560 " <wsa:EndpointReference>"
561 " <wsa:Address>%s</wsa:Address>"
562 " </wsa:EndpointReference>"
563 " </wsp:AppliesTo>"
564 " <wst:Claims Dialect=\"urn:component:Microsoft.Rtc.WebAuthentication.2010:authclaims\">"
565 " <auth:ClaimType Uri=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/uri\" Optional=\"false\">"
566 " <auth:Value>sip:%s</auth:Value>"
567 " </auth:ClaimType>"
568 " </wst:Claims>"
569 " <wst:Entropy>"
570 " <wst:BinarySecret>%s</wst:BinarySecret>"
571 " </wst:Entropy>"
572 " <wst:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</wst:KeyType>"
573 "</wst:RequestSecurityToken>",
574 uuid,
575 service_uri,
576 sipe_private->username,
577 secret);
579 gboolean ret = new_soap_req(sipe_private,
580 session,
581 uri,
582 "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue",
583 wsse_security,
584 soap_body,
585 sipe_svc_wsdl_response,
586 callback,
587 callback_data);
588 g_free(soap_body);
589 g_free(secret);
590 g_free(uuid);
592 return(ret);
595 static void sipe_svc_metadata_response(struct sipe_core_private *sipe_private,
596 struct svc_request *data,
597 const gchar *raw,
598 sipe_xml *xml)
600 if (xml) {
601 /* Callback: success */
602 (*data->cb)(sipe_private, data->uri, raw, xml, data->cb_data);
603 } else {
604 /* Callback: failed */
605 (*data->cb)(sipe_private, data->uri, NULL, NULL, data->cb_data);
609 gboolean sipe_svc_realminfo(struct sipe_core_private *sipe_private,
610 struct sipe_svc_session *session,
611 sipe_svc_callback *callback,
612 gpointer callback_data)
614 gchar *realminfo_uri = g_strdup_printf("https://login.microsoftonline.com/getuserrealm.srf?login=%s&xml=1",
615 sipe_private->username);
616 gboolean ret = sipe_svc_https_request(sipe_private,
617 session,
618 realminfo_uri,
619 NULL,
620 NULL,
621 NULL,
622 sipe_svc_metadata_response,
623 callback,
624 callback_data);
625 g_free(realminfo_uri);
626 return(ret);
629 gboolean sipe_svc_metadata(struct sipe_core_private *sipe_private,
630 struct sipe_svc_session *session,
631 const gchar *uri,
632 sipe_svc_callback *callback,
633 gpointer callback_data)
635 gchar *mex_uri = g_strdup_printf("%s/mex", uri);
636 gboolean ret = sipe_svc_https_request(sipe_private,
637 session,
638 mex_uri,
639 NULL,
640 NULL,
641 NULL,
642 sipe_svc_metadata_response,
643 callback,
644 callback_data);
645 g_free(mex_uri);
646 return(ret);
650 Local Variables:
651 mode: c
652 c-file-style: "bsd"
653 indent-tabs-mode: t
654 tab-width: 8
655 End: