1 #define _XOPEN_SOURCE 500 /* strdup from string.h */
8 /* Private structure for write_body() call back */
14 /* Private structure for write_header() call back */
16 _Bool is_complete
; /* Response has finished, next iteration is new
17 response, values become obsolete. */
18 char *last_header
; /* Temporary storage for previous unfinished header */
19 char *method
; /* WWW-Authenticate value */
20 char *code
; /* X-Response-message-code value */
21 isds_otp_resolution resolution
; /* Decoded .code member */
22 char *message
; /* X-Response-message-text value */
23 char *redirect
; /* Redirect URL */
27 /* Deallocate content of struct auth_headers */
28 static void auth_headers_free(struct auth_headers
*headers
) {
29 zfree(headers
->last_header
);
30 zfree(headers
->method
);
32 zfree(headers
->message
);
33 zfree(headers
->redirect
);
37 /* If given @line is HTTP header of @name,
38 * return pointer to the header value. Otherwise return NULL.
39 * @name is header name without name---value separator, terminated with 0. */
40 static const char *header_value(const char *line
, const char *name
) {
42 if (line
== NULL
|| name
== NULL
) return NULL
;
44 for (value
= line
; ; value
++, name
++) {
45 if (*value
== '\0') return NULL
; /* Line too short */
46 if (*name
== '\0') break; /* Name matches */
47 if (*name
!= *value
) return NULL
; /* Name does not match */
50 /* Check separator. RFC2616, section 4.2 requires collon only. */
51 if (*value
++ != ':') return NULL
;
57 /* Decode HTTP header value per RFC ???
58 * @encoded_value is encoded HTTP header value terminated with NUL. It can
59 * contain HTTP LWS separators that will be replaced with a space.
60 * @return newly allocated decoded value without EOL, or return NULL */
61 static char *decode_header_value(const char *encoded_value
) {
62 char *decoded
= NULL
, *decoded_cursor
;
63 size_t content_length
;
64 _Bool text_started
= 0, lws_seen
= 0;
66 if (encoded_value
== NULL
) return NULL
;
67 content_length
= strlen(encoded_value
);
69 decoded
= malloc(content_length
+ 1);
70 if (decoded
== NULL
) {
76 /* RFC 2616, section 4.2: Remove surrounding LWS, replace inner ones with
78 /* FIXME: Implement the RFC (?=UTF-8?B?...?) */
79 for(decoded_cursor
= decoded
; *encoded_value
; encoded_value
++) {
80 if (*encoded_value
== '\r' || *encoded_value
== '\n' ||
81 *encoded_value
== '\t' || *encoded_value
== ' ') {
87 if (text_started
) *(decoded_cursor
++) = ' ';
89 *(decoded_cursor
++) = *encoded_value
;
92 *decoded_cursor
= '\0';
98 /* Return true, if server requests OTP authorization method that client
99 * requested. Otherwise return false.
100 * @client_method is method client requested
101 * @server_method is value of WWW-Authenticate header */
102 /*static _Bool otp_method_matches(const isds_otp_method client_method,
103 const char *server_method) {
104 char *method_name = NULL;
106 switch (client_method) {
107 case OTP_HASH: method_name = "hotp"; break;
108 case OTP_TIME: method_name = "totp"; break;
112 if (!strncmp(server_method, method_name, 4) && (
113 server_method[4] == '\0' || server_method[4] == ' ' ||
114 server_method[4] == '\t'))
120 /* Convert UTF-8 @string to HTTP OTP resolution enum type.
121 * @Return corresponding value or OTP_RESOLUTION_UNKNOWN if @string is not
122 * defined or unknown value. */
123 static isds_otp_resolution
string2isds_otp_resolution(const char *string
) {
125 return OTP_RESOLUTION_UNKNOWN
;
126 else if (!strcmp(string
, "authentication.info.totpSended"))
127 return OTP_RESOLUTION_TOTP_SENT
;
128 else if (!strcmp(string
, "authentication.error.userIsNotAuthenticated"))
129 return OTP_RESOLUTION_BAD_AUTHENTICATION
;
130 else if (!strcmp(string
, "authentication.error.intruderDetected"))
131 return OTP_RESOLUTION_ACCESS_BLOCKED
;
132 else if (!strcmp(string
, "authentication.error.paswordExpired"))
133 return OTP_RESOLUTION_PASSWORD_EXPIRED
;
134 else if (!strcmp(string
, "authentication.info.cannotSendQuickly"))
135 return OTP_RESOLUTION_TO_FAST
;
136 else if (!strcmp(string
, "authentication.error.badRole"))
137 return OTP_RESOLUTION_UNAUTHORIZED
;
138 else if (!strcmp(string
, "authentication.info.totpNotSended"))
139 return OTP_RESOLUTION_TOTP_NOT_SENT
;
141 return OTP_RESOLUTION_UNKNOWN
;
145 /* Close connection to server and destroy CURL handle associated
147 _hidden isds_error
_isds_close_connection(struct isds_ctx
*context
) {
148 if (!context
) return IE_INVALID_CONTEXT
;
151 curl_easy_cleanup(context
->curl
);
152 context
->curl
= NULL
;
153 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Connection to server %s closed\n"),
157 return IE_CONNECTION_CLOSED
;
162 /* CURL call back function called when chunk of HTTP response body is available.
163 * @buffer points to new data
164 * @size * @nmemb is length of the chunk in bytes. Zero means empty body.
165 * @userp is private structure.
166 * Must return the length of the chunk, otherwise CURL will signal
167 * CURL_WRITE_ERROR. */
168 static size_t write_body(void *buffer
, size_t size
, size_t nmemb
, void *userp
) {
169 struct soap_body
*body
= (struct soap_body
*) userp
;
172 /* FIXME: Check for (size * nmemb + body->lengt) !> SIZE_T_MAX.
173 * Precompute the product then. */
175 if (!body
) return 0; /* This should never happen */
176 if (0 == (size
* nmemb
)) return 0; /* Empty body */
178 new_data
= realloc(body
->data
, body
->length
+ size
* nmemb
);
179 if (!new_data
) return 0;
181 memcpy(new_data
+ body
->length
, buffer
, size
* nmemb
);
183 body
->data
= new_data
;
184 body
->length
+= size
* nmemb
;
186 return (size
* nmemb
);
190 /* CURL call back function called when a HTTP response header is available.
191 * This is called for each header even if reply consists of more responses.
192 * @buffer points to new header (no zero terminator, but HTTP EOL is included)
193 * @size * @nmemb is length of the header in bytes
194 * @userp is private structure.
195 * Must return the length of the header, otherwise CURL will signal
196 * CURL_WRITE_ERROR. */
197 static size_t write_header(void *buffer
, size_t size
, size_t nmemb
, void *userp
) {
198 struct auth_headers
*headers
= (struct auth_headers
*) userp
;
202 /* FIXME: Check for (size * nmemb) !> SIZE_T_MAX.
203 * Precompute the product then. */
204 length
= size
* nmemb
;
206 if (NULL
== headers
) return 0; /* This should never happen */
208 /* ??? Is this the empty line delimiter? */
209 return 0; /* Empty headers */
212 /* New response, invalide authentication headers. */
213 /* XXX: Chunked encoding trailer is not supported */
214 if (headers
->is_complete
) auth_headers_free(headers
);
216 /* Append continuation to multi-line header */
217 if (*(char *)buffer
== ' ' || *(char *)buffer
== '\t') {
218 if (headers
->last_header
!= NULL
) {
219 size_t old_length
= strlen(headers
->last_header
);
220 char *longer_header
= realloc(headers
->last_header
, old_length
+ length
);
221 if (longer_header
== NULL
) {
225 strncpy(longer_header
+ old_length
, (char*)buffer
+ 1, length
- 1);
226 longer_header
[old_length
+ length
- 1] = '\0';
227 headers
->last_header
= longer_header
;
229 /* Invalid continuation without starting header will be skipped. */
230 isds_log(ILF_HTTP
, ILL_WARNING
,
231 _("HTTP header continuation without starting header has "
232 "been encountered. Skipping invalid HTTP response "
238 /* Decode last header */
239 value
= header_value(headers
->last_header
, "WWW-Authenticate");
241 free(headers
->method
);
242 if (NULL
== (headers
->method
= decode_header_value(value
))) {
243 /* TODO: Set IE_NOMEM to context */
249 value
= header_value(headers
->last_header
, "X-Response-message-code");
252 if (NULL
== (headers
->code
= decode_header_value(value
))) {
253 /* TODO: Set IE_NOMEM to context */
259 value
= header_value(headers
->last_header
, "X-Response-message-text");
261 free(headers
->message
);
262 if (NULL
== (headers
->message
= decode_header_value(value
))) {
263 /* TODO: Set IE_NOMEM to context */
270 /* Last header decoded, free it */
271 zfree(headers
->last_header
);
273 if (!strncmp(buffer
, "\r\n", length
)) {
274 /* Current line is header---body separator */
275 headers
->is_complete
= 1;
278 /* Current line is new header, store it */
279 headers
->last_header
= malloc(length
+ 1);
280 if (headers
->last_header
== NULL
) {
281 /* TODO: Set IE_NOMEM to context */
284 memcpy(headers
->last_header
, buffer
, length
);
285 headers
->last_header
[length
] = '\0';
293 /* CURL progress callback proxy to rearrange arguments.
294 * @curl_data is session context */
295 static int progress_proxy(void *curl_data
, double download_total
,
296 double download_current
, double upload_total
, double upload_current
) {
297 struct isds_ctx
*context
= (struct isds_ctx
*) curl_data
;
300 if (context
&& context
->progress_callback
) {
301 abort
= context
->progress_callback(
302 upload_total
, upload_current
,
303 download_total
, download_current
,
304 context
->progress_callback_data
);
306 isds_log(ILF_HTTP
, ILL_INFO
,
307 _("Application aborted HTTP transfer"));
315 /* CURL call back function called when curl has something to log.
316 * @curl is cURL context
317 * @type is cURL log facility
318 * @buffer points to log data, XXX: not zero-terminated
319 * @size is length of log data
320 * @userp is private structure.
322 static int log_curl(CURL
*curl
, curl_infotype type
, char *buffer
, size_t size
,
324 if (!buffer
|| 0 == size
) return 0;
325 if (type
== CURLINFO_TEXT
|| type
== CURLINFO_HEADER_IN
||
326 type
== CURLINFO_HEADER_OUT
)
327 isds_log(ILF_HTTP
, ILL_DEBUG
, "%*s", size
, buffer
);
333 * @context holds the base URL,
334 * @url is a (CGI) file of SOAP URL,
335 * @request is body for POST request
336 * @request_length is length of @request in bytes
337 * @reponse is automatically reallocated() buffer to fit HTTP response with
338 * @response_length (does not need to match allocated memory exactly). You must
339 * free() the @response.
340 * @mime_type is automatically allocated MIME type send by server (*NULL if not
341 * sent). Set NULL if you don't care.
342 * @charset is charset of the body signaled by server. The same constrains
343 * like on @mime_type apply.
344 * @http_code is final HTTP code returned by server. This can be 200, 401, 500
345 * or any other one. Pass NULL if you don't interest.
346 * In case of error, the response memory, MIME type, charset and length will be
347 * deallocated and zeroed automatically. Thus be sure they are preallocated or
348 * they points to NULL.
349 * @response_otp_headers is pre-allocated structure for OTP authentication
350 * headers sent by server. Members must be valid pointers or NULLs.
351 * Pass NULL if you don't interest.
352 * Be ware that successful return value does not mean the HTTP request has
353 * been accepted by the server. You must consult @http_code. OTOH, failure
354 * return value means the request could not been sent (e.g. SSL error).
355 * Side effect: message buffer */
356 static isds_error
http(struct isds_ctx
*context
, const char *url
,
357 const void *request
, const size_t request_length
,
358 void **response
, size_t *response_length
,
359 char **mime_type
, char **charset
, long *http_code
,
360 struct auth_headers
*response_otp_headers
) {
363 isds_error err
= IE_SUCCESS
;
364 struct soap_body body
;
366 struct curl_slist
*headers
= NULL
;
369 if (!context
) return IE_INVALID_CONTEXT
;
370 if (!url
) return IE_INVAL
;
371 if (request_length
> 0 && !request
) return IE_INVAL
;
372 if (!response
|| !response_length
) return IE_INVAL
;
374 /* Clean authentication headers */
376 /* Set the body here to allow deallocation in leave block */
377 body
.data
= *response
;
380 /* Set Request-URI */
381 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_URL
, url
);
383 /* Set TLS options */
384 if (!curl_err
&& context
->tls_verify_server
) {
385 if (!*context
->tls_verify_server
)
386 isds_log(ILF_SEC
, ILL_WARNING
,
387 _("Disabling server identity verification. "
388 "That was your decision.\n"));
389 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSL_VERIFYPEER
,
390 (*context
->tls_verify_server
)? 1L : 0L);
392 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSL_VERIFYHOST
,
393 (*context
->tls_verify_server
)? 2L : 0L);
396 if (!curl_err
&& context
->tls_ca_file
) {
397 isds_log(ILF_SEC
, ILL_INFO
,
398 _("CA certificates will be searched in `%s' file since now\n"),
399 context
->tls_ca_file
);
400 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_CAINFO
,
401 context
->tls_ca_file
);
403 if (!curl_err
&& context
->tls_ca_dir
) {
404 isds_log(ILF_SEC
, ILL_INFO
,
405 _("CA certificates will be searched in `%s' directory "
406 "since now\n"), context
->tls_ca_dir
);
407 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_CAPATH
,
408 context
->tls_ca_dir
);
410 if (!curl_err
&& context
->tls_crl_file
) {
411 #if HAVE_DECL_CURLOPT_CRLFILE /* Since curl-7.19.0 */
412 isds_log(ILF_SEC
, ILL_INFO
,
413 _("CRLs will be searched in `%s' file since now\n"),
414 context
->tls_crl_file
);
415 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_CRLFILE
,
416 context
->tls_crl_file
);
418 isds_log(ILF_SEC
, ILL_WARNING
,
419 _("Your curl library cannot pass certificate revocation "
420 "list to cryptographic library.\n"
421 "Make sure cryptographic library default setting "
422 "delivers proper CRLs,\n"
423 "or upgrade curl.\n"));
424 #endif /* not HAVE_DECL_CURLOPT_CRLFILE */
428 /* Set credentials */
429 #if HAVE_DECL_CURLOPT_USERNAME /* Since curl-7.19.1 */
430 if (!curl_err
&& context
->username
) {
431 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_USERNAME
,
434 if (!curl_err
&& context
->password
) {
435 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_PASSWORD
,
439 if (!curl_err
&& (context
->username
|| context
->password
)) {
441 _isds_astrcat3(context
->username
, ":", context
->password
);
443 isds_log_message(context
, _("Could not pass credentials to CURL"));
447 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_USERPWD
, userpwd
);
450 #endif /* not HAVE_DECL_CURLOPT_USERNAME */
452 /* Set PKI credentials */
453 if (!curl_err
&& (context
->pki_credentials
)) {
454 if (context
->pki_credentials
->engine
) {
455 /* Select SSL engine */
456 isds_log(ILF_SEC
, ILL_INFO
,
457 _("Cryptographic engine `%s' will be used for "
458 "key or certificate\n"),
459 context
->pki_credentials
->engine
);
460 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLENGINE
,
461 context
->pki_credentials
->engine
);
465 /* Select certificate format */
466 #if HAVE_DECL_CURLOPT_SSLCERTTYPE /* since curl-7.9.3 */
467 if (context
->pki_credentials
->certificate_format
==
469 /* XXX: It's valid to have certificate in engine without name.
470 * Engines can select certificate according private key and
472 if (context
->pki_credentials
->certificate
)
473 isds_log(ILF_SEC
, ILL_INFO
, _("Client `%s' certificate "
474 "will be read from `%s' engine\n"),
475 context
->pki_credentials
->certificate
,
476 context
->pki_credentials
->engine
);
478 isds_log(ILF_SEC
, ILL_INFO
, _("Client certificate "
479 "will be read from `%s' engine\n"),
480 context
->pki_credentials
->engine
);
481 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLCERTTYPE
,
483 } else if (context
->pki_credentials
->certificate
) {
484 isds_log(ILF_SEC
, ILL_INFO
, _("Client %s certificate "
485 "will be read from `%s' file\n"),
486 (context
->pki_credentials
->certificate_format
==
487 PKI_FORMAT_DER
) ? _("DER") : _("PEM"),
488 context
->pki_credentials
->certificate
);
489 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLCERTTYPE
,
490 (context
->pki_credentials
->certificate_format
==
491 PKI_FORMAT_DER
) ? "DER" : "PEM");
494 if ((context
->pki_credentials
->certificate_format
==
496 context
->pki_credentials
->certificate
))
497 isds_log(ILF_SEC
, ILL_WARNING
,
498 _("Your curl library cannot distinguish certificate "
499 "formats. Make sure your cryptographic library\n"
500 "understands your certificate file by default, "
501 "or upgrade curl.\n"));
502 #endif /* not HAVE_DECL_CURLOPT_SSLCERTTYPE */
505 if (!curl_err
&& context
->pki_credentials
->certificate
) {
506 /* Select certificate */
508 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLCERT
,
509 context
->pki_credentials
->certificate
);
513 /* Select key format */
514 if (context
->pki_credentials
->key_format
== PKI_FORMAT_ENG
) {
515 if (context
->pki_credentials
->key
)
516 isds_log(ILF_SEC
, ILL_INFO
, _("Client private key `%s' "
517 "from `%s' engine will be used\n"),
518 context
->pki_credentials
->key
,
519 context
->pki_credentials
->engine
);
521 isds_log(ILF_SEC
, ILL_INFO
, _("Client private key "
522 "from `%s' engine will be used\n"),
523 context
->pki_credentials
->engine
);
524 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLKEYTYPE
,
526 } else if (context
->pki_credentials
->key
) {
527 isds_log(ILF_SEC
, ILL_INFO
, _("Client %s private key will be "
528 "read from `%s' file\n"),
529 (context
->pki_credentials
->key_format
==
530 PKI_FORMAT_DER
) ? _("DER") : _("PEM"),
531 context
->pki_credentials
->key
);
532 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLKEYTYPE
,
533 (context
->pki_credentials
->key_format
==
534 PKI_FORMAT_DER
) ? "DER" : "PEM");
539 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLKEY
,
540 context
->pki_credentials
->key
);
543 /* Pass key pass-phrase */
544 #if HAVE_DECL_CURLOPT_KEYPASSWD /* since curl-7.16.5 */
545 curl_err
= curl_easy_setopt(context
->curl
,
547 context
->pki_credentials
->passphrase
);
548 #elif HAVE_DECL_CURLOPT_SSLKEYPASSWD /* up to curl-7.16.4 */
549 curl_err
= curl_easy_setopt(context
->curl
,
550 CURLOPT_SSLKEYPASSWD
,
551 context
->pki_credentials
->passphrase
);
552 #else /* up to curl-7.9.2 */
553 curl_err
= curl_easy_setopt(context
->curl
,
554 CURLOPT_SSLCERTPASSWD
,
555 context
->pki_credentials
->passphrase
);
561 /* Set authorization cookie for OTP session */
562 if (!curl_err
&& context
->otp
!= NULL
) {
563 isds_log(ILF_SEC
, ILL_INFO
,
564 _("Cookies will be stored and send "
565 "because context has been authorized by OTP.\n"));
566 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_COOKIEFILE
, "");
571 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_NOSIGNAL
, 1);
573 if (!curl_err
&& context
->timeout
) {
574 #if HAVE_DECL_CURLOPT_TIMEOUT_MS /* Since curl-7.16.2 */
575 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_TIMEOUT_MS
,
578 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_TIMEOUT
,
579 context
->timeout
/ 1000);
580 #endif /* not HAVE_DECL_CURLOPT_TIMEOUT_MS */
583 /* Register callback */
584 if (context
->progress_callback
) {
586 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_NOPROGRESS
, 0);
589 curl_err
= curl_easy_setopt(context
->curl
,
590 CURLOPT_PROGRESSFUNCTION
, progress_proxy
);
593 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_PROGRESSDATA
,
598 /* Set other CURL features */
600 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_FAILONERROR
, 0);
603 /* Set get-response function */
605 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_WRITEFUNCTION
,
609 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_WRITEDATA
, &body
);
612 if (response_otp_headers
!= NULL
) {
613 /* Set get-response-headers function */
615 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_HEADERFUNCTION
,
619 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_WRITEHEADER
,
620 response_otp_headers
);
624 /* Set MIME types and headers requires by SOAP 1.1.
625 * SOAP 1.1 requires text/xml, SOAP 1.2 requires application/soap+xml */
627 headers
= curl_slist_append(headers
,
628 "Accept: application/soap+xml,application/xml,text/xml");
633 headers
= curl_slist_append(headers
, "Content-Type: text/xml");
638 headers
= curl_slist_append(headers
, "SOAPAction: ");
643 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_HTTPHEADER
, headers
);
646 /* Set user agent identification */
647 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_USERAGENT
,
648 "libisds/" PACKAGE_VERSION
);
651 /* Set POST request body */
653 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POST
, 1);
656 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POSTFIELDS
, request
);
659 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POSTFIELDSIZE
,
663 /* Check for errors so far */
665 isds_log_message(context
, curl_easy_strerror(curl_err
));
670 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Sending POST request to %s\n"), url
);
671 isds_log(ILF_HTTP
, ILL_DEBUG
,
672 _("POST body length: %zu, content follows:\n"), request_length
);
673 isds_log(ILF_HTTP
, ILL_DEBUG
, "%.*s\n", request_length
, request
);
674 isds_log(ILF_HTTP
, ILL_DEBUG
, _("End of POST body\n"));
675 if ((log_facilities
& ILF_HTTP
) && (log_level
>= ILL_DEBUG
) ) {
676 curl_easy_setopt(context
->curl
, CURLOPT_VERBOSE
, 1);
677 curl_easy_setopt(context
->curl
, CURLOPT_DEBUGFUNCTION
, log_curl
);
679 curl_easy_setopt(context
->curl
, CURLOPT_VERBOSE
, 0);
680 curl_easy_setopt(context
->curl
, CURLOPT_DEBUGFUNCTION
, NULL
);
685 curl_err
= curl_easy_perform(context
->curl
);
688 curl_err
= curl_easy_getinfo(context
->curl
, CURLINFO_CONTENT_TYPE
,
692 /* TODO: CURL is not internationalized yet. Collect CURL messages for
694 isds_printf_message(context
,
695 _("%s: %s"), url
, _(curl_easy_strerror(curl_err
)));
696 if (curl_err
== CURLE_ABORTED_BY_CALLBACK
)
703 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Final response to %s received\n"), url
);
704 isds_log(ILF_HTTP
, ILL_DEBUG
,
705 _("Response body length: %zu, content follows:\n"),
707 isds_log(ILF_HTTP
, ILL_DEBUG
, "%.*s\n", body
.length
, body
.data
);
708 isds_log(ILF_HTTP
, ILL_DEBUG
, _("End of response body\n"));
711 /* Extract MIME type and charset */
716 sep
= strchr(content_type
, ';');
717 if (sep
) offset
= (size_t) (sep
- content_type
);
718 else offset
= strlen(content_type
);
721 *mime_type
= malloc(offset
+ 1);
726 memcpy(*mime_type
, content_type
, offset
);
727 (*mime_type
)[offset
] = '\0';
734 sep
= strstr(sep
, "charset=");
738 *charset
= strdup(sep
+ 8);
748 /* Get HTTP response code */
750 curl_err
= curl_easy_getinfo(context
->curl
,
751 CURLINFO_RESPONSE_CODE
, http_code
);
758 /* Store OTP authentication results */
759 if (response_otp_headers
&& response_otp_headers
->is_complete
) {
760 isds_log(ILF_SEC
, ILL_DEBUG
,
761 _("OTP authentication headers received: "
762 "method=%s, code=%s, message=%s\n"),
763 response_otp_headers
->method
, response_otp_headers
->code
,
764 response_otp_headers
->message
);
766 /* XXX: Don't make unknown code fatal. Missing code can be succcess if
767 * HTTP code is 302. This is checked in _isds_soap(). */
768 response_otp_headers
->resolution
=
769 string2isds_otp_resolution(response_otp_headers
->code
);
771 if (response_otp_headers
->message
!= NULL
) {
772 char *message_locale
= _isds_utf82locale(response_otp_headers
->message
);
773 if (message_locale
== NULL
) {
777 isds_printf_message(context
,
778 _("Server returned OTP authentication message: %s"),
780 free(message_locale
);
783 char *next_url
= NULL
; /* Weak pointer managed by cURL */
784 curl_err
= curl_easy_getinfo(context
->curl
, CURLINFO_REDIRECT_URL
,
790 if (next_url
!= NULL
) {
791 isds_log(ILF_SEC
, ILL_DEBUG
,
792 _("OTP authentication headers redirect to: <%s>\n"),
794 free(response_otp_headers
->redirect
);
795 response_otp_headers
->redirect
= strdup(next_url
);
796 if (response_otp_headers
->redirect
== NULL
) {
803 curl_slist_free_all(headers
);
819 if (err
!= IE_ABORTED
) _isds_close_connection(context
);
822 *response
= body
.data
;
823 *response_length
= body
.length
;
830 * @context holds the base URL,
831 * @file is a (CGI) file of SOAP URL,
832 * @request is XML node set with SOAP request body.
833 * @file must be NULL, @request should be NULL rather than empty, if they should
834 * not be signaled in the SOAP request.
835 * @reponse is automatically allocated() node set with SOAP response body.
836 * You must xmlFreeNodeList() it. This is literal body, empty (NULL), one node
837 * or more nodes can be returned.
838 * @raw_response is automatically allocated bit stream with response body. Use
839 * NULL if you don't care
840 * @raw_response_length is size of @raw_response in bytes
841 * In case of error the response will be deallocated automatically.
842 * Side effect: message buffer */
843 _hidden isds_error
_isds_soap(struct isds_ctx
*context
, const char *file
,
844 const xmlNodePtr request
, xmlNodePtr
*response
,
845 void **raw_response
, size_t *raw_response_length
) {
847 isds_error err
= IE_SUCCESS
;
849 char *mime_type
= NULL
;
851 struct auth_headers response_otp_headers
;
852 xmlBufferPtr http_request
= NULL
;
853 xmlSaveCtxtPtr save_ctx
= NULL
;
854 xmlDocPtr request_soap_doc
= NULL
;
855 xmlNodePtr request_soap_envelope
= NULL
, request_soap_body
= NULL
;
856 xmlNsPtr soap_ns
= NULL
;
857 void *http_response
= NULL
;
858 size_t response_length
= 0;
859 xmlDocPtr response_soap_doc
= NULL
;
860 xmlNodePtr response_root
= NULL
;
861 xmlXPathContextPtr xpath_ctx
= NULL
;
862 xmlXPathObjectPtr response_soap_headers
= NULL
, response_soap_body
= NULL
,
863 response_soap_fault
= NULL
;
866 if (!context
) return IE_INVALID_CONTEXT
;
867 if (!response
) return IE_INVAL
;
868 if (!raw_response_length
&& raw_response
) return IE_INVAL
;
870 xmlFreeNodeList(*response
);
872 if (raw_response
) *raw_response
= NULL
;
874 url
= _isds_astrcat(context
->url
, file
);
875 if (!url
) return IE_NOMEM
;
877 /* Build SOAP request envelope */
878 request_soap_doc
= xmlNewDoc(BAD_CAST
"1.0");
879 if (!request_soap_doc
) {
880 isds_log_message(context
, _("could not build soap request document"));
884 request_soap_envelope
= xmlNewNode(NULL
, BAD_CAST
"Envelope");
885 if (!request_soap_envelope
) {
886 isds_log_message(context
, _("Could not build SOAP request envelope"));
890 xmlDocSetRootElement(request_soap_doc
, request_soap_envelope
);
891 /* Only this way we get namespace definition as @xmlns:soap,
892 * otherwise we get namespace prefix without definition */
893 soap_ns
= xmlNewNs(request_soap_envelope
, BAD_CAST SOAP_NS
, NULL
);
895 isds_log_message(context
, _("Could not create SOAP name space"));
899 xmlSetNs(request_soap_envelope
, soap_ns
);
900 request_soap_body
= xmlNewChild(request_soap_envelope
, NULL
,
901 BAD_CAST
"Body", NULL
);
902 if (!request_soap_body
) {
903 isds_log_message(context
,
904 _("Could not add Body to SOAP request envelope"));
909 /* Append request XML node set to SOAP body if request is not empty */
910 /* XXX: Copy of request must be used, otherwise xmlFreeDoc(request_soap_doc)
911 * would destroy this outer structure. */
913 xmlNodePtr request_copy
= xmlCopyNodeList(request
);
915 isds_log_message(context
,
916 _("Could not copy request content"));
920 if (!xmlAddChildList(request_soap_body
, request_copy
)) {
921 xmlFreeNodeList(request_copy
);
922 isds_log_message(context
,
923 _("Could not add request content to SOAP "
924 "request envelope"));
931 /* Serialize the SOAP request into HTTP request body */
932 http_request
= xmlBufferCreate();
934 isds_log_message(context
,
935 _("Could not create xmlBuffer for HTTP request body"));
939 /* Last argument 1 means format the XML tree. This is pretty but it breaks
940 * XML document transport as it adds text nodes (indentiation) between
942 save_ctx
= xmlSaveToBuffer(http_request
, "UTF-8", 0);
944 isds_log_message(context
,
945 _("Could not create XML serializer"));
949 /* XXX: According LibXML documentation, this function does not return
950 * meaningful value yet */
951 xmlSaveDoc(save_ctx
, request_soap_doc
);
952 if (-1 == xmlSaveFlush(save_ctx
)) {
953 isds_log_message(context
,
954 _("Could not serialize SOAP request to HTTP request body"));
959 if (context
->otp
!= NULL
)
960 memset(&response_otp_headers
, 0, sizeof(response_otp_headers
));
962 if (context
->otp
!= NULL
) auth_headers_free(&response_otp_headers
);
963 isds_log(ILF_SOAP
, ILL_DEBUG
,
964 _("SOAP request to sent to %s:\n%.*s\nEnd of SOAP request\n"),
965 url
, http_request
->use
, http_request
->content
);
967 err
= http(context
, url
, http_request
->content
, http_request
->use
,
968 &http_response
, &response_length
,
969 &mime_type
, NULL
, &http_code
,
970 (context
->otp
== NULL
) ? NULL
: &response_otp_headers
);
972 /* TODO: HTTP binding for SOAP prescribes non-200 HTTP return codes
973 * to be processed too. */
979 /* Check for HTTP return code */
980 isds_log(ILF_SOAP
, ILL_DEBUG
, _("Server returned %ld HTTP code\n"),
983 /* XXX: We must see which code is used for not permitted ISDS
984 * operation like downloading message without proper user
985 * permissions. In that case we should keep connection opened. */
988 if (response_otp_headers
.resolution
== OTP_RESOLUTION_UNKNOWN
)
989 context
->otp
->resolution
= OTP_RESOLUTION_SUCCESS
;
991 context
->otp
->resolution
= response_otp_headers
.resolution
;
992 err
= IE_PARTIAL_SUCCESS
;
993 isds_printf_message(context
,
994 _("Server redirects on <%s> because OTP authentication "
997 if (context
->otp
->otp_code
!= NULL
&&
998 response_otp_headers
.redirect
!= NULL
) {
999 /* XXX: If OTP code is known, this must be second OTP phase, so
1000 * sent final POST request. */
1002 url
= response_otp_headers
.redirect
;
1003 response_otp_headers
.redirect
= NULL
;
1006 /* XXX: Otherwise bail out to ask application for OTP code. */
1011 isds_printf_message(context
,
1012 _("Code 302: Server redirects on <%s> request. "
1013 "Redirection is forbidden in stateless mode."),
1018 case 401: /* ISDS server returns 401 even if Authorization
1020 case 403: /* HTTP/1.0 prescribes 403 if Authorization presents. */
1022 context
->otp
->resolution
= response_otp_headers
.resolution
;
1023 err
= IE_NOT_LOGGED_IN
;
1024 isds_log_message(context
, _("Authentication failed"));
1029 isds_printf_message(context
,
1030 _("Code 404: Document (%s) not found on server"), url
);
1033 /* 500 should return standard SOAP message */
1036 /* Check for Content-Type: text/xml.
1037 * Do it after HTTP code check because 401 Unauthorized returns HTML web
1038 * page for browsers. */
1039 if (mime_type
&& strcmp(mime_type
, "text/xml")
1040 && strcmp(mime_type
, "application/soap+xml")
1041 && strcmp(mime_type
, "application/xml")) {
1042 char *mime_type_locale
= _isds_utf82locale(mime_type
);
1043 isds_printf_message(context
,
1044 _("%s: bad MIME type sent by server: %s"), url
,
1046 free(mime_type_locale
);
1051 /* TODO: Convert returned body into XML default encoding */
1053 /* Parse the HTTP body as XML */
1054 response_soap_doc
= xmlParseMemory(http_response
, response_length
);
1055 if (!response_soap_doc
) {
1060 xpath_ctx
= xmlXPathNewContext(response_soap_doc
);
1066 if (_isds_register_namespaces(xpath_ctx
, MESSAGE_NS_UNSIGNED
)) {
1071 isds_log(ILF_SOAP
, ILL_DEBUG
,
1072 _("SOAP response received:\n%.*s\nEnd of SOAP response\n"),
1073 response_length
, http_response
);
1076 /* Check for SOAP version */
1077 response_root
= xmlDocGetRootElement(response_soap_doc
);
1078 if (!response_root
) {
1079 isds_log_message(context
, "SOAP response has no root element");
1083 if (xmlStrcmp(response_root
->name
, BAD_CAST
"Envelope") ||
1084 xmlStrcmp(response_root
->ns
->href
, BAD_CAST SOAP_NS
)) {
1085 isds_log_message(context
, "SOAP response is not SOAP 1.1 document");
1090 /* Check for SOAP Headers */
1091 response_soap_headers
= xmlXPathEvalExpression(
1092 BAD_CAST
"/soap:Envelope/soap:Header/"
1093 "*[@soap:mustUnderstand/text() = true()]", xpath_ctx
);
1094 if (!response_soap_headers
) {
1098 if (!xmlXPathNodeSetIsEmpty(response_soap_headers
->nodesetval
)) {
1099 isds_log_message(context
,
1100 _("SOAP response requires unsupported feature"));
1101 /* TODO: log the headers
1102 * xmlChar *fragment = NULL;
1103 * fragment = xmlXPathCastNodeSetToSting(response_soap_headers->nodesetval);*/
1109 response_soap_body
= xmlXPathEvalExpression(
1110 BAD_CAST
"/soap:Envelope/soap:Body", xpath_ctx
);
1111 if (!response_soap_body
) {
1115 if (xmlXPathNodeSetIsEmpty(response_soap_body
->nodesetval
)) {
1116 isds_log_message(context
,
1117 _("SOAP response does not contain SOAP Body element"));
1121 if (response_soap_body
->nodesetval
->nodeNr
> 1) {
1122 isds_log_message(context
,
1123 _("SOAP response has more than one Body element"));
1128 /* Check for SOAP Fault */
1129 response_soap_fault
= xmlXPathEvalExpression(
1130 BAD_CAST
"/soap:Envelope/soap:Body/soap:Fault", xpath_ctx
);
1131 if (!response_soap_fault
) {
1135 if (!xmlXPathNodeSetIsEmpty(response_soap_fault
->nodesetval
)) {
1136 /* Server signals Fault. Gather error message and croak. */
1137 /* XXX: Only first message is passed */
1138 char *message
= NULL
, *message_locale
= NULL
;
1139 xpath_ctx
->node
= response_soap_fault
->nodesetval
->nodeTab
[0];
1140 xmlXPathFreeObject(response_soap_fault
);
1141 /* XXX: faultstring and faultcode are in no name space according
1142 * ISDS specification */
1143 /* First more verbose faultstring */
1144 response_soap_fault
= xmlXPathEvalExpression(
1145 BAD_CAST
"faultstring[1]/text()", xpath_ctx
);
1146 if (response_soap_fault
&&
1147 !xmlXPathNodeSetIsEmpty(response_soap_fault
->nodesetval
)) {
1149 xmlXPathCastNodeSetToString(response_soap_fault
->nodesetval
);
1150 message_locale
= _isds_utf82locale(message
);
1152 /* If not available, try shorter faultcode */
1153 if (!message_locale
) {
1155 xmlXPathFreeObject(response_soap_fault
);
1156 response_soap_fault
= xmlXPathEvalExpression(
1157 BAD_CAST
"faultcode[1]/text()", xpath_ctx
);
1158 if (response_soap_fault
&&
1159 !xmlXPathNodeSetIsEmpty(response_soap_fault
->nodesetval
)) {
1161 xmlXPathCastNodeSetToString(
1162 response_soap_fault
->nodesetval
);
1163 message_locale
= _isds_utf82locale(message
);
1169 isds_printf_message(context
, _("SOAP response signals Fault: %s"),
1172 isds_log_message(context
, _("SOAP response signals Fault"));
1174 free(message_locale
);
1182 /* Extract XML Tree with ISDS response from SOAP envelope and return it.
1183 * XXX: response_soap_body is Body, we need children which may not exist
1184 * (i.e. empty Body). */
1185 /* TODO: Destroy SOAP response but Body children. This is more memory
1186 * friendly than copying (potentially) fat body */
1187 if (response_soap_body
->nodesetval
->nodeTab
[0]->children
) {
1188 *response
= xmlDocCopyNodeList(response_soap_doc
,
1189 response_soap_body
->nodesetval
->nodeTab
[0]->children
);
1194 } else *response
= NULL
;
1196 /* Save raw response */
1198 *raw_response
= http_response
;
1199 *raw_response_length
= response_length
;
1200 http_response
= NULL
;
1206 xmlFreeNodeList(*response
);
1210 xmlXPathFreeObject(response_soap_fault
);
1211 xmlXPathFreeObject(response_soap_body
);
1212 xmlXPathFreeObject(response_soap_headers
);
1213 xmlXPathFreeContext(xpath_ctx
);
1214 xmlFreeDoc(response_soap_doc
);
1215 auth_headers_free(&response_otp_headers
);
1217 free(http_response
);
1218 xmlSaveClose(save_ctx
);
1219 xmlBufferFree(http_request
);
1220 xmlFreeDoc(request_soap_doc
); /* recursive, frees request_body, soap_ns*/
1227 /* LibXML functions:
1229 * void xmlInitParser(void)
1230 * Initialization function for the XML parser. This is not reentrant. Call
1231 * once before processing in case of use in multithreaded programs.
1233 * int xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
1234 * Initialize a parser context
1236 * xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar * cur,
1237 * const * char URL, const char * encoding, int options);
1238 * Parse in-memory NULL-terminated document @cur.
1240 * xmlDocPtr xmlParseMemory(const char * buffer, int size)
1241 * Parse an XML in-memory block and build a tree.
1243 * xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char * buffer, int
1245 * Create a parser context for an XML in-memory document.
1247 * xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar * cur)
1248 * Creates a parser context for an XML in-memory document.
1250 * xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt,
1251 * const char * buffer, int size, const char * URL, const char * encoding,
1253 * Parse an XML in-memory document and build a tree. This reuses the existing
1254 * @ctxt parser context.
1256 * void xmlCleanupParser(void)
1257 * Cleanup function for the XML library. It tries to reclaim all parsing
1258 * related glob document related memory. Calling this function should not
1259 * prevent reusing the libr finished using the library or XML document built
1262 * void xmlClearParserCtxt(xmlParserCtxtPtr ctxt)
1263 * Clear (release owned resources) and reinitialize a parser context.
1265 * void xmlCtxtReset(xmlParserCtxtPtr ctxt)
1266 * Reset a parser context
1268 * void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)
1269 * Free all the memory used by a parser context. However the parsed document
1270 * in ctxt->myDoc is not freed.
1272 * void xmlFreeDoc(xmlDocPtr cur)
1273 * Free up all the structures used by a document, tree included.