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
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"
44 #include "sipe-utils.h"
48 /* forward declaration */
50 typedef void (svc_callback
)(struct sipe_core_private
*sipe_private
,
51 struct svc_request
*data
,
56 svc_callback
*internal_cb
;
57 sipe_svc_callback
*cb
;
59 struct sipe_http_request
*request
;
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
)
76 sipe_http_request_cancel(data
->request
);
78 /* Callback: aborted */
79 (*data
->cb
)(sipe_private
, NULL
, NULL
, NULL
, data
->cb_data
);
84 void sipe_svc_free(struct sipe_core_private
*sipe_private
)
86 struct sipe_svc
*svc
= sipe_private
->svc
;
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
;
96 sipe_svc_request_free(sipe_private
, entry
->data
);
99 g_slist_free(svc
->pending_requests
);
103 sipe_private
->svc
= NULL
;
106 static void sipe_svc_init(struct sipe_core_private
*sipe_private
)
108 if (sipe_private
->svc
)
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();
121 void sipe_svc_session_close(struct sipe_svc_session
*session
)
124 sipe_http_session_close(session
->session
);
129 static void sipe_svc_https_response(struct sipe_core_private
*sipe_private
,
131 SIPE_UNUSED_PARAMETER GSList
*headers
,
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
);
147 /* Internal callback: failed */
148 (*data
->internal_cb
)(sipe_private
, data
, NULL
, NULL
);
151 /* Internal callback has already called this */
154 svc
->pending_requests
= g_slist_remove(svc
->pending_requests
,
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
,
169 const gchar
*content_type
,
170 const gchar
*soap_action
,
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"
189 soap_action
? soap_action
: "<NONE>",
190 body
? body
: "<EMPTY>");
193 gchar
*headers
= g_strdup_printf("SOAPAction: \"%s\"\r\n",
196 request
= sipe_http_request_post(sipe_private
,
201 sipe_svc_https_response
,
206 request
= sipe_http_request_get(sipe_private
,
209 sipe_svc_https_response
,
215 data
->internal_cb
= internal_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
,
224 sipe_http_request_session(request
, session
->session
);
225 sipe_http_request_ready(request
);
228 SIPE_DEBUG_ERROR("failed to create HTTP connection to %s", uri
);
232 return(request
!= NULL
);
235 static gboolean
sipe_svc_wsdl_request(struct sipe_core_private
*sipe_private
,
236 struct sipe_svc_session
*session
,
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
) :
251 gchar
*soap_header
= wsse_security
?
252 g_strdup_printf("<soap:Header>"
253 " <wsa:To>%s</wsa:To>"
255 " <wsa:Address>http://www.w3.org/2005/08/addressing/anonymous</wsa:Address>"
257 " <wsa:MessageID>uuid:%s</wsa:MessageID>"
258 " <wsa:Action>%s</wsa:Action>"
259 " <wsse:Security>%s</wsse:Security>"
266 gchar
*body
= g_strdup_printf("<?xml version=\"1.0\"?>\r\n"
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\""
275 " <soap:Body>%s</soap:Body>"
281 gboolean ret
= sipe_svc_https_request(sipe_private
,
284 content_type
? content_type
: "text/xml",
297 static gboolean
new_soap_req(struct sipe_core_private
*sipe_private
,
298 struct sipe_svc_session
*session
,
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
,
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\"",
322 static void sipe_svc_wsdl_response(struct sipe_core_private
*sipe_private
,
323 struct svc_request
*data
,
328 /* Callback: success */
329 (*data
->cb
)(sipe_private
, data
->uri
, raw
, xml
, data
->cb_data
);
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
,
339 const gchar
*wsse_security
,
340 const gchar
*certreq
,
341 sipe_svc_callback
*callback
,
342 gpointer callback_data
)
344 struct sipe_tls_random id
;
347 gchar
*uuid
= get_uuid(sipe_private
);
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
);
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\""
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>",
376 sipe_private
->username
,
382 ret
= new_soap_req(sipe_private
,
385 "http://schemas.microsoft.com/OCS/AuthWebServices/GetAndPublishCert",
388 sipe_svc_wsdl_response
,
396 gboolean
sipe_svc_ab_entry_request(struct sipe_core_private
*sipe_private
,
397 struct sipe_svc_session
*session
,
399 const gchar
*wsse_security
,
402 sipe_svc_callback
*callback
,
403 gpointer callback_data
)
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/\""
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>"
423 ret
= new_soap_req(sipe_private
,
426 "DistributionListExpander/IAddressBook/SearchAbEntry",
429 sipe_svc_wsdl_response
,
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>"
451 " <wsa:EndpointReference>"
452 " <wsa:Address>%s</wsa:Address>"
453 " </wsa:EndpointReference>"
456 "</wst:RequestSecurityToken>",
458 request_extension
? request_extension
: "");
460 gboolean ret
= sipe_svc_wsdl_request(sipe_private
,
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",
469 sipe_svc_wsdl_response
,
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
,
503 g_free(wsse_security
);
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
,
516 "urn:federation:MicrosoftOnline",
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>",
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
,
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
,
561 gboolean
sipe_svc_webticket(struct sipe_core_private
*sipe_private
,
562 struct sipe_svc_session
*session
,
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>"
576 " <wsa:EndpointReference>"
577 " <wsa:Address>%s</wsa:Address>"
578 " </wsa:EndpointReference>"
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>"
586 " <wst:BinarySecret>%s</wst:BinarySecret>"
588 " <wst:KeyType>http://docs.oasis-open.org/ws-sx/ws-trust/200512/SymmetricKey</wst:KeyType>"
589 "</wst:RequestSecurityToken>",
592 sipe_private
->username
,
595 gboolean ret
= new_soap_req(sipe_private
,
598 "http://docs.oasis-open.org/ws-sx/ws-trust/200512/RST/Issue",
601 sipe_svc_wsdl_response
,
611 static void sipe_svc_metadata_response(struct sipe_core_private
*sipe_private
,
612 struct svc_request
*data
,
617 /* Callback: success */
618 (*data
->cb
)(sipe_private
, data
->uri
, raw
, xml
, data
->cb_data
);
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
,
643 sipe_svc_metadata_response
,
646 g_free(realminfo_uri
);
650 gboolean
sipe_svc_metadata(struct sipe_core_private
*sipe_private
,
651 struct sipe_svc_session
*session
,
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
,
663 sipe_svc_metadata_response
,