Fix building with libxml2-2.12.0
[libisds.git] / src / soap.c
blob7378fcd97e87c121cd7d76a0bd8d5ac9bde55a76
1 #include "isds_priv.h"
2 #include "soap.h"
3 #include "utils.h"
4 #include <stdlib.h>
5 #include <string.h>
6 #include <strings.h> /* strncasecmp(3) */
7 #include "system.h"
9 /* Private structure for write_body() call back */
10 struct soap_body {
11 void *data;
12 size_t length;
15 /* Private structure for write_header() call back */
16 struct auth_headers {
17 _Bool is_complete; /* Response has finished, next iteration is new
18 response, values become obsolete. */
19 char *last_header; /* Temporary storage for previous unfinished header */
20 char *method; /* WWW-Authenticate value */
21 char *code; /* X-Response-message-code value */
22 isds_otp_resolution resolution; /* Decoded .code member */
23 char *message; /* X-Response-message-text value */
24 char *redirect; /* Redirect URL */
28 /* Deallocate content of struct auth_headers */
29 static void auth_headers_free(struct auth_headers *headers) {
30 zfree(headers->last_header);
31 zfree(headers->method);
32 zfree(headers->code);
33 zfree(headers->message);
34 zfree(headers->redirect);
38 /* If given @line is HTTP header of @name,
39 * return pointer to the header value. Otherwise return NULL.
40 * @name is header name without name---value separator, terminated with 0. */
41 static const char *header_value(const char *line, const char *name) {
42 const char *value;
43 if (line == NULL || name == NULL) return NULL;
45 for (value = line; ; value++, name++) {
46 if (*value == '\0') return NULL; /* Line too short */
47 if (*name == '\0') break; /* Name matches */
48 if (*name != *value) return NULL; /* Name does not match */
51 /* Check separator. RFC2616, section 4.2 requires collon only. */
52 if (*value++ != ':') return NULL;
54 return value;
58 /* Try to decode header value per RFC 2047.
59 * @prepend_space is true if a space should be inserted before decoded word
60 * into @output in case the word has been decoded successfully.
61 * @input is zero terminated input, it's updated to point all consumed
62 * input - 1.
63 * @output is buffer to store decoded value, it's updated to point after last
64 * written character. The buffer must be preallocated.
65 * @return 0 if input has been successfully decoded, then @input and @output
66 * poineres will be updated. Otherwise return non-zero value and keeps
67 * argument pointers and memory unchanged. */
68 static int try_rfc2047_decode(_Bool prepend_space, const char **input,
69 char **output) {
70 const char *encoded;
71 const char *charset_start, *encoding, *end;
72 size_t charset_length;
73 char *charset = NULL;
74 /* ISDS prescribes B encoding only, but RFC 2047 requires to support Q
75 * encoding too. ISDS prescribes UTF-8 charset only, RFC requiers to
76 * support any MIME charset. */
77 if (input == NULL || *input == NULL || output == NULL || *output == NULL)
78 return -1;
80 /* Start is "=?" */
81 encoded = *input;
82 if (encoded[0] != '=' || encoded[1] != '?')
83 return -1;
85 /* Then is "CHARSET?" */
86 charset_start = (encoded += 2);
87 while (*encoded != '?') {
88 if (*encoded == '\0')
89 return -1;
90 if (*encoded == ' ' || *encoded == '\t' || *encoded == '\r' || *encoded == '\n')
91 return -1;
92 encoded++;
94 encoded++;
96 /* Then is "ENCODING?", where ENCODING is /[BbQq]/ */
97 if (*encoded == '\0') return -1;
98 encoding = encoded++;
99 if (*encoded != '?')
100 return -1;
101 encoded++;
103 /* Then is "ENCODED_TEXT?=" */
104 while (*encoded != '?') {
105 if (*encoded == '\0')
106 return -1;
107 if (*encoded == ' ' || *encoded == '\t' || *encoded == '\r' || *encoded == '\n')
108 return -1;
109 encoded++;
111 end = encoded;
112 if (*(++encoded) != '=') return -1;
114 /* Now pointers are:
115 * "=?CHARSET?E?ENCODED_TEXT?="
116 * | | | ||
117 * | | | |\- encoded
118 * | | | \- end
119 * | | \- encoding
120 * | \- charset_start
121 * \- *input
124 charset_length = encoding - charset_start - 1;
125 if (charset_length < 1)
126 return -1;
127 charset = strndup(charset_start, charset_length);
128 if (charset == NULL)
129 return -1;
131 /* Decode encoding */
132 char *bit_stream = NULL;
133 size_t bit_length = 0;
134 size_t encoding_length = end - encoding - 2;
136 if (*encoding == 'B') {
137 /* Decode Base-64 */
138 char *b64_stream = NULL;
139 if (NULL == (b64_stream =
140 malloc((encoding_length + 1) * sizeof(*encoding)))) {
141 free(charset);
142 return -1;
144 memcpy(b64_stream, encoding + 2, encoding_length);
145 b64_stream[encoding_length] = '\0';
146 bit_length = _isds_b64decode(b64_stream, (void **)&bit_stream);
147 free(b64_stream);
148 if (bit_length == (size_t) -1) {
149 free(charset);
150 return -1;
152 } else if (*encoding == 'Q') {
153 /* Decode Quoted-printable-like */
154 if (NULL == (bit_stream =
155 malloc((encoding_length) * sizeof(*encoding)))) {
156 free(charset);
157 return -1;
159 for (size_t q = 2; q < encoding_length + 2; q++) {
160 if (encoding[q] == '_') {
161 bit_stream[bit_length] = '\x20';
162 } else if (encoding[q] == '=') {
163 int ordinar;
164 /* Validate "=HH", where H is hexadecimal digit */
165 if (q + 2 >= encoding_length + 2 ) {
166 free(bit_stream);
167 free(charset);
168 return -1;
170 /* Convert =HH */
171 if ((ordinar = _isds_hex2i(encoding[++q])) < 0) {
172 free(bit_stream);
173 free(charset);
174 return -1;
176 bit_stream[bit_length] = (ordinar << 4);
177 if ((ordinar = _isds_hex2i(encoding[++q])) < 0) {
178 free(bit_stream);
179 free(charset);
180 return -1;
182 bit_stream[bit_length] += ordinar;
183 } else {
184 bit_stream[bit_length] = encoding[q];
186 bit_length++;
188 } else {
189 /* Unknown encoding */
190 free(charset);
191 return -1;
194 /* Convert to UTF-8 */
195 char *utf_stream = NULL;
196 size_t utf_length;
197 utf_length = _isds_any2any(charset, "UTF-8", bit_stream, bit_length,
198 (void **)&utf_stream);
199 free(bit_stream);
200 free(charset);
201 if (utf_length == (size_t) -1) {
202 return -1;
205 /* Copy UTF-8 stream to output buffer */
206 if (prepend_space) {
207 **output = ' ';
208 (*output)++;
210 memcpy(*output, utf_stream, utf_length);
211 free(utf_stream);
212 *output += utf_length;
214 *input = encoded;
215 return 0;
219 /* Decode HTTP header value per RFC 2047.
220 * @encoded_value is encoded HTTP header value terminated with NUL. It can
221 * contain HTTP LWS separators that will be replaced with a space.
222 * @return newly allocated decoded value without EOL, or return NULL. */
223 static char *decode_header_value(const char *encoded_value) {
224 char *decoded = NULL, *decoded_cursor;
225 size_t content_length;
226 _Bool text_started = 0, lws_seen = 0, encoded_word_seen = 0;
228 if (encoded_value == NULL) return NULL;
229 content_length = strlen(encoded_value);
231 /* A character can occupy up to 6 bytes in UTF-8 */
232 decoded = malloc(content_length * 6 + 1);
233 if (decoded == NULL) {
234 /* ENOMEM */
235 return NULL;
238 /* Decode */
239 /* RFC 2616, section 4.2: Remove surrounding LWS, replace inner ones with
240 * a space. */
241 /* RFC 2047, section 6.2: LWS between adjacent encoded words is ignored.
242 * */
243 for (decoded_cursor = decoded; *encoded_value; encoded_value++) {
244 if (*encoded_value == '\r' || *encoded_value == '\n' ||
245 *encoded_value == '\t' || *encoded_value == ' ') {
246 lws_seen = 1;
247 continue;
249 if (*encoded_value == '=' &&
250 !try_rfc2047_decode(
251 lws_seen && text_started && !encoded_word_seen,
252 &encoded_value, &decoded_cursor)) {
253 encoded_word_seen = 1;
254 } else {
255 if (lws_seen && text_started)
256 *(decoded_cursor++) = ' ';
257 *(decoded_cursor++) = *encoded_value;
258 encoded_word_seen = 0;
260 lws_seen = 0;
261 text_started = 1;
263 *decoded_cursor = '\0';
265 return decoded;
269 /* Return true, if server requests OTP authorization method that client
270 * requested. Otherwise return false.
271 * @client_method is method client requested
272 * @server_method is value of WWW-Authenticate header */
273 /*static _Bool otp_method_matches(const isds_otp_method client_method,
274 const char *server_method) {
275 char *method_name = NULL;
277 switch (client_method) {
278 case OTP_HMAC: method_name = "hotp"; break;
279 case OTP_TIME: method_name = "totp"; break;
280 default: return 0;
283 if (!strncmp(server_method, method_name, 4) && (
284 server_method[4] == '\0' || server_method[4] == ' ' ||
285 server_method[4] == '\t'))
286 return 1;
287 return 0;
291 /* Convert UTF-8 @string to HTTP OTP resolution enum type.
292 * @Return corresponding value or OTP_RESOLUTION_UNKNOWN if @string is not
293 * defined or unknown value. */
294 static isds_otp_resolution string2isds_otp_resolution(const char *string) {
295 if (string == NULL)
296 return OTP_RESOLUTION_UNKNOWN;
297 else if (!strcmp(string, "authentication.info.totpSended"))
298 return OTP_RESOLUTION_TOTP_SENT;
299 else if (!strcmp(string, "authentication.error.userIsNotAuthenticated"))
300 return OTP_RESOLUTION_BAD_AUTHENTICATION;
301 else if (!strcmp(string, "authentication.error.intruderDetected"))
302 return OTP_RESOLUTION_ACCESS_BLOCKED;
303 else if (!strcmp(string, "authentication.error.paswordExpired"))
304 return OTP_RESOLUTION_PASSWORD_EXPIRED;
305 else if (!strcmp(string, "authentication.info.cannotSendQuickly"))
306 return OTP_RESOLUTION_TO_FAST;
307 else if (!strcmp(string, "authentication.error.badRole"))
308 return OTP_RESOLUTION_UNAUTHORIZED;
309 else if (!strcmp(string, "authentication.info.totpNotSended"))
310 return OTP_RESOLUTION_TOTP_NOT_SENT;
311 else
312 return OTP_RESOLUTION_UNKNOWN;
316 /* Close connection to server and destroy CURL handle associated
317 * with @context */
318 _hidden isds_error _isds_close_connection(struct isds_ctx *context) {
319 if (!context) return IE_INVALID_CONTEXT;
321 if (context->curl) {
322 curl_easy_cleanup(context->curl);
323 context->curl = NULL;
324 isds_log(ILF_HTTP, ILL_DEBUG, _("Connection to server %s closed\n"),
325 context->url);
326 return IE_SUCCESS;
327 } else {
328 return IE_CONNECTION_CLOSED;
333 /* Remove username and password from context CURL handle. */
334 static isds_error unset_http_authorization(struct isds_ctx *context) {
335 isds_error error = IE_SUCCESS;
337 if (context == NULL) return IE_INVALID_CONTEXT;
338 if (context->curl == NULL) return IE_CONNECTION_CLOSED;
340 #if HAVE_DECL_CURLOPT_USERNAME /* Since curl-7.19.1 */
341 if (curl_easy_setopt(context->curl, CURLOPT_USERNAME, NULL))
342 error = IE_ERROR;
343 if (curl_easy_setopt(context->curl, CURLOPT_PASSWORD, NULL))
344 error = IE_ERROR;
345 #else
346 if (curl_easy_setopt(context->curl, CURLOPT_USERPWD, NULL))
347 error = IE_ERROR;
348 #endif /* not HAVE_DECL_CURLOPT_USERNAME */
350 if (error)
351 isds_log(ILF_HTTP, ILL_ERR, _("Error while unsetting user name and "
352 "password from CURL handle for connection to server %s.\n"),
353 context->url);
354 else
355 isds_log(ILF_HTTP, ILL_DEBUG, _("User name and password for server %s "
356 "have been unset from CURL handle.\n"), context->url);
357 return error;
361 /* CURL call back function called when chunk of HTTP response body is available.
362 * @buffer points to new data
363 * @size * @nmemb is length of the chunk in bytes. Zero means empty body.
364 * @userp is private structure.
365 * Must return the length of the chunk, otherwise CURL will signal
366 * CURL_WRITE_ERROR. */
367 static size_t write_body(void *buffer, size_t size, size_t nmemb, void *userp) {
368 struct soap_body *body = (struct soap_body *) userp;
369 void *new_data;
371 /* FIXME: Check for (size * nmemb + body->lengt) !> SIZE_T_MAX.
372 * Precompute the product then. */
374 if (!body) return 0; /* This should never happen */
375 if (0 == (size * nmemb)) return 0; /* Empty body */
377 new_data = realloc(body->data, body->length + size * nmemb);
378 if (!new_data) return 0;
380 memcpy(new_data + body->length, buffer, size * nmemb);
382 body->data = new_data;
383 body->length += size * nmemb;
385 return (size * nmemb);
389 /* CURL call back function called when a HTTP response header is available.
390 * This is called for each header even if reply consists of more responses.
391 * @buffer points to new header (no zero terminator, but HTTP EOL is included)
392 * @size * @nmemb is length of the header in bytes
393 * @userp is private structure.
394 * Must return the length of the header, otherwise CURL will signal
395 * CURL_WRITE_ERROR. */
396 static size_t write_header(void *buffer, size_t size, size_t nmemb, void *userp) {
397 struct auth_headers *headers = (struct auth_headers *) userp;
398 size_t length;
399 const char *value;
401 /* FIXME: Check for (size * nmemb) !> SIZE_T_MAX.
402 * Precompute the product then. */
403 length = size * nmemb;
405 if (NULL == headers) return 0; /* This should never happen */
406 if (0 == length) {
407 /* ??? Is this the empty line delimiter? */
408 return 0; /* Empty headers */
411 /* New response, invalide authentication headers. */
412 /* XXX: Chunked encoding trailer is not supported */
413 if (headers->is_complete) auth_headers_free(headers);
415 /* Append continuation to multi-line header */
416 if (*(char *)buffer == ' ' || *(char *)buffer == '\t') {
417 if (headers->last_header != NULL) {
418 size_t old_length = strlen(headers->last_header);
419 char *longer_header = realloc(headers->last_header, old_length + length);
420 if (longer_header == NULL) {
421 /* ENOMEM */
422 return 0;
424 strncpy(longer_header + old_length, (char*)buffer + 1, length - 1);
425 longer_header[old_length + length - 1] = '\0';
426 headers->last_header = longer_header;
427 } else {
428 /* Invalid continuation without starting header will be skipped. */
429 isds_log(ILF_HTTP, ILL_WARNING,
430 _("HTTP header continuation without starting header has "
431 "been encountered. Skipping invalid HTTP response "
432 "line.\n"));
434 goto leave;
437 /* Decode last header */
438 value = header_value(headers->last_header, "WWW-Authenticate");
439 if (value != NULL) {
440 free(headers->method);
441 if (NULL == (headers->method = decode_header_value(value))) {
442 /* TODO: Set IE_NOMEM to context */
443 return 0;
445 goto store;
448 value = header_value(headers->last_header, "X-Response-message-code");
449 if (value != NULL) {
450 free(headers->code);
451 if (NULL == (headers->code = decode_header_value(value))) {
452 /* TODO: Set IE_NOMEM to context */
453 return 0;
455 goto store;
458 value = header_value(headers->last_header, "X-Response-message-text");
459 if (value != NULL) {
460 free(headers->message);
461 if (NULL == (headers->message = decode_header_value(value))) {
462 /* TODO: Set IE_NOMEM to context */
463 return 0;
465 goto store;
468 store:
469 /* Last header decoded, free it */
470 zfree(headers->last_header);
472 if (!strncmp(buffer, "\r\n", length)) {
473 /* Current line is header---body separator */
474 headers->is_complete = 1;
475 goto leave;
476 } else {
477 /* Current line is new header, store it */
478 headers->last_header = malloc(length + 1);
479 if (headers->last_header == NULL) {
480 /* TODO: Set IE_NOMEM to context */
481 return 0;
483 memcpy(headers->last_header, buffer, length);
484 headers->last_header[length] = '\0';
487 leave:
488 return (length);
492 /* CURL progress callback proxy to rearrange arguments.
493 * @curl_data is session context */
494 static int progress_proxy(void *curl_data, double download_total,
495 double download_current, double upload_total, double upload_current) {
496 struct isds_ctx *context = (struct isds_ctx *) curl_data;
497 int abort = 0;
499 if (context && context->progress_callback) {
500 abort = context->progress_callback(
501 upload_total, upload_current,
502 download_total, download_current,
503 context->progress_callback_data);
504 if (abort) {
505 isds_log(ILF_HTTP, ILL_INFO,
506 _("Application aborted HTTP transfer"));
510 return abort;
514 /* CURL call back function called when curl has something to log.
515 * @curl is cURL context
516 * @type is cURL log facility
517 * @buffer points to log data, XXX: not zero-terminated
518 * @size is length of log data
519 * @userp is private structure.
520 * Must return 0. */
521 static int log_curl(CURL *curl, curl_infotype type, char *buffer, size_t size,
522 void *userp) {
523 /* Silent warning about usused arguments.
524 * This prototype is cURL's debug_callback type. */
525 (void)curl;
526 (void)userp;
528 if (!buffer || 0 == size) return 0;
529 if (type == CURLINFO_TEXT || type == CURLINFO_HEADER_IN ||
530 type == CURLINFO_HEADER_OUT)
531 isds_log(ILF_HTTP, ILL_DEBUG, "%*s", size, buffer);
532 return 0;
536 /* Do HTTP request.
537 * @context holds the base URL,
538 * @url is a (CGI) file of SOAP URL,
539 * @use_get is a false to do a POST request, true to do a GET request.
540 * @request is body for POST request
541 * @request_length is length of @request in bytes
542 * @reponse is automatically reallocated() buffer to fit HTTP response with
543 * @response_length (does not need to match allocated memory exactly). You must
544 * free() the @response.
545 * @mime_type is automatically allocated MIME type send by server (*NULL if not
546 * sent). Set NULL if you don't care.
547 * @charset is charset of the body signaled by server. The same constrains
548 * like on @mime_type apply.
549 * @http_code is final HTTP code returned by server. This can be 200, 401, 500
550 * or any other one. Pass NULL if you don't interest.
551 * In case of error, the response memory, MIME type, charset and length will be
552 * deallocated and zeroed automatically. Thus be sure they are preallocated or
553 * they points to NULL.
554 * @response_otp_headers is pre-allocated structure for OTP authentication
555 * headers sent by server. Members must be valid pointers or NULLs.
556 * Pass NULL if you don't interest.
557 * Be ware that successful return value does not mean the HTTP request has
558 * been accepted by the server. You must consult @http_code. OTOH, failure
559 * return value means the request could not been sent (e.g. SSL error).
560 * Side effect: message buffer */
561 static isds_error http(struct isds_ctx *context,
562 const char *url, _Bool use_get,
563 const void *request, const size_t request_length,
564 void **response, size_t *response_length,
565 char **mime_type, char **charset, long *http_code,
566 struct auth_headers *response_otp_headers) {
568 CURLcode curl_err;
569 isds_error err = IE_SUCCESS;
570 struct soap_body body;
571 char *content_type;
572 struct curl_slist *headers = NULL;
575 if (!context) return IE_INVALID_CONTEXT;
576 if (!url) return IE_INVAL;
577 if (request_length > 0 && !request) return IE_INVAL;
578 if (!response || !response_length) return IE_INVAL;
580 /* Clean authentication headers */
582 /* Set the body here to allow deallocation in leave block */
583 body.data = *response;
584 body.length = 0;
586 /* Set Request-URI */
587 curl_err = curl_easy_setopt(context->curl, CURLOPT_URL, url);
589 /* Set TLS options */
590 if (!curl_err && context->tls_verify_server) {
591 if (!*context->tls_verify_server)
592 isds_log(ILF_SEC, ILL_WARNING,
593 _("Disabling server identity verification. "
594 "That was your decision.\n"));
595 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSL_VERIFYPEER,
596 (*context->tls_verify_server)? 1L : 0L);
597 if (!curl_err) {
598 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSL_VERIFYHOST,
599 (*context->tls_verify_server)? 2L : 0L);
602 if (!curl_err && context->tls_ca_file) {
603 isds_log(ILF_SEC, ILL_INFO,
604 _("CA certificates will be searched in `%s' file since now\n"),
605 context->tls_ca_file);
606 curl_err = curl_easy_setopt(context->curl, CURLOPT_CAINFO,
607 context->tls_ca_file);
609 if (!curl_err && context->tls_ca_dir) {
610 isds_log(ILF_SEC, ILL_INFO,
611 _("CA certificates will be searched in `%s' directory "
612 "since now\n"), context->tls_ca_dir);
613 curl_err = curl_easy_setopt(context->curl, CURLOPT_CAPATH,
614 context->tls_ca_dir);
616 if (!curl_err && context->tls_crl_file) {
617 #if HAVE_DECL_CURLOPT_CRLFILE /* Since curl-7.19.0 */
618 isds_log(ILF_SEC, ILL_INFO,
619 _("CRLs will be searched in `%s' file since now\n"),
620 context->tls_crl_file);
621 curl_err = curl_easy_setopt(context->curl, CURLOPT_CRLFILE,
622 context->tls_crl_file);
623 #else
624 isds_log(ILF_SEC, ILL_WARNING,
625 _("Your curl library cannot pass certificate revocation "
626 "list to cryptographic library.\n"
627 "Make sure cryptographic library default setting "
628 "delivers proper CRLs,\n"
629 "or upgrade curl.\n"));
630 #endif /* not HAVE_DECL_CURLOPT_CRLFILE */
634 if ((NULL == context->mep_credentials) || (NULL == context->mep_credentials->intermediate_uri)) {
635 /* Don't set credentials in intermediate mobile key login state. */
636 /* Set credentials */
637 #if HAVE_DECL_CURLOPT_USERNAME /* Since curl-7.19.1 */
638 if (!curl_err && context->username) {
639 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERNAME,
640 context->username);
642 if (!curl_err && context->password) {
643 curl_err = curl_easy_setopt(context->curl, CURLOPT_PASSWORD,
644 context->password);
646 #else
647 if (!curl_err && (context->username || context->password)) {
648 char *userpwd =
649 _isds_astrcat3(context->username, ":", context->password);
650 if (!userpwd) {
651 isds_log_message(context, _("Could not pass credentials to CURL"));
652 err = IE_NOMEM;
653 goto leave;
655 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERPWD, userpwd);
656 free(userpwd);
658 #endif /* not HAVE_DECL_CURLOPT_USERNAME */
661 /* Set PKI credentials */
662 if (!curl_err && (context->pki_credentials)) {
663 if (context->pki_credentials->engine) {
664 /* Select SSL engine */
665 isds_log(ILF_SEC, ILL_INFO,
666 _("Cryptographic engine `%s' will be used for "
667 "key or certificate\n"),
668 context->pki_credentials->engine);
669 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLENGINE,
670 context->pki_credentials->engine);
673 if (!curl_err) {
674 /* Select certificate format */
675 #if HAVE_DECL_CURLOPT_SSLCERTTYPE /* since curl-7.9.3 */
676 if (context->pki_credentials->certificate_format ==
677 PKI_FORMAT_ENG) {
678 /* XXX: It's valid to have certificate in engine without name.
679 * Engines can select certificate according private key and
680 * vice versa. */
681 if (context->pki_credentials->certificate)
682 isds_log(ILF_SEC, ILL_INFO, _("Client `%s' certificate "
683 "will be read from `%s' engine\n"),
684 context->pki_credentials->certificate,
685 context->pki_credentials->engine);
686 else
687 isds_log(ILF_SEC, ILL_INFO, _("Client certificate "
688 "will be read from `%s' engine\n"),
689 context->pki_credentials->engine);
690 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERTTYPE,
691 "ENG");
692 } else if (context->pki_credentials->certificate) {
693 isds_log(ILF_SEC, ILL_INFO, _("Client %s certificate "
694 "will be read from `%s' file\n"),
695 (context->pki_credentials->certificate_format ==
696 PKI_FORMAT_DER) ? _("DER") : _("PEM"),
697 context->pki_credentials->certificate);
698 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERTTYPE,
699 (context->pki_credentials->certificate_format ==
700 PKI_FORMAT_DER) ? "DER" : "PEM");
702 #else
703 if ((context->pki_credentials->certificate_format ==
704 PKI_FORMAT_ENG ||
705 context->pki_credentials->certificate))
706 isds_log(ILF_SEC, ILL_WARNING,
707 _("Your curl library cannot distinguish certificate "
708 "formats. Make sure your cryptographic library\n"
709 "understands your certificate file by default, "
710 "or upgrade curl.\n"));
711 #endif /* not HAVE_DECL_CURLOPT_SSLCERTTYPE */
714 if (!curl_err && context->pki_credentials->certificate) {
715 /* Select certificate */
716 if (!curl_err)
717 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERT,
718 context->pki_credentials->certificate);
721 if (!curl_err) {
722 /* Select key format */
723 if (context->pki_credentials->key_format == PKI_FORMAT_ENG) {
724 if (context->pki_credentials->key)
725 isds_log(ILF_SEC, ILL_INFO, _("Client private key `%s' "
726 "from `%s' engine will be used\n"),
727 context->pki_credentials->key,
728 context->pki_credentials->engine);
729 else
730 isds_log(ILF_SEC, ILL_INFO, _("Client private key "
731 "from `%s' engine will be used\n"),
732 context->pki_credentials->engine);
733 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEYTYPE,
734 "ENG");
735 } else if (context->pki_credentials->key) {
736 isds_log(ILF_SEC, ILL_INFO, _("Client %s private key will be "
737 "read from `%s' file\n"),
738 (context->pki_credentials->key_format ==
739 PKI_FORMAT_DER) ? _("DER") : _("PEM"),
740 context->pki_credentials->key);
741 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEYTYPE,
742 (context->pki_credentials->key_format ==
743 PKI_FORMAT_DER) ? "DER" : "PEM");
746 if (!curl_err)
747 /* Select key */
748 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEY,
749 context->pki_credentials->key);
751 if (!curl_err) {
752 /* Pass key pass-phrase */
753 #if HAVE_DECL_CURLOPT_KEYPASSWD /* since curl-7.16.5 */
754 curl_err = curl_easy_setopt(context->curl,
755 CURLOPT_KEYPASSWD,
756 context->pki_credentials->passphrase);
757 #elif HAVE_DECL_CURLOPT_SSLKEYPASSWD /* up to curl-7.16.4 */
758 curl_err = curl_easy_setopt(context->curl,
759 CURLOPT_SSLKEYPASSWD,
760 context->pki_credentials->passphrase);
761 #else /* up to curl-7.9.2 */
762 curl_err = curl_easy_setopt(context->curl,
763 CURLOPT_SSLCERTPASSWD,
764 context->pki_credentials->passphrase);
765 #endif
770 /* Set authorization cookie for OTP session */
771 if (!curl_err && (context->otp || context->mep)) {
772 isds_log(ILF_SEC, ILL_INFO,
773 _("Cookies will be stored and sent "
774 "because context has been authorized by OTP or mobile key.\n"));
775 curl_err = curl_easy_setopt(context->curl, CURLOPT_COOKIEFILE, "");
778 /* Set timeout */
779 if (!curl_err) {
780 curl_err = curl_easy_setopt(context->curl, CURLOPT_NOSIGNAL, 1);
782 if (!curl_err && context->timeout) {
783 #if HAVE_DECL_CURLOPT_TIMEOUT_MS /* Since curl-7.16.2 */
784 curl_err = curl_easy_setopt(context->curl, CURLOPT_TIMEOUT_MS,
785 context->timeout);
786 #else
787 curl_err = curl_easy_setopt(context->curl, CURLOPT_TIMEOUT,
788 context->timeout / 1000);
789 #endif /* not HAVE_DECL_CURLOPT_TIMEOUT_MS */
792 /* Register callback */
793 if (context->progress_callback) {
794 if (!curl_err) {
795 curl_err = curl_easy_setopt(context->curl, CURLOPT_NOPROGRESS, 0);
797 if (!curl_err) {
798 #if HAVE_DECL_CURLOPT_XFERINFOFUNCTION /* Since curl-7.32.0 */
799 curl_err = curl_easy_setopt(context->curl,
800 CURLOPT_XFERINFOFUNCTION, progress_proxy);
801 #else
802 curl_err = curl_easy_setopt(context->curl,
803 CURLOPT_PROGRESSFUNCTION, progress_proxy);
804 #endif /* not HAVE_DECL_CURLOPT_XFERINFOFUNCTION */
806 if (!curl_err) {
807 curl_err = curl_easy_setopt(context->curl, CURLOPT_PROGRESSDATA,
808 context);
812 /* Set other CURL features */
813 if (!curl_err) {
814 curl_err = curl_easy_setopt(context->curl, CURLOPT_FAILONERROR, 0);
817 /* Set get-response function */
818 if (!curl_err) {
819 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEFUNCTION,
820 write_body);
822 if (!curl_err) {
823 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEDATA, &body);
826 /* Set get-response-headers function if needed.
827 * XXX: Both CURLOPT_HEADERFUNCTION and CURLOPT_WRITEHEADER must be set or
828 * unset at the same time (see curl_easy_setopt(3)) ASAP, otherwise old
829 * invalid CURLOPT_WRITEHEADER value could be derefenced. */
830 if (!curl_err) {
831 curl_err = curl_easy_setopt(context->curl, CURLOPT_HEADERFUNCTION,
832 (response_otp_headers == NULL) ? NULL: write_header);
834 if (!curl_err) {
835 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEHEADER,
836 response_otp_headers);
839 /* Set MIME types and headers requires by SOAP 1.1.
840 * SOAP 1.1 requires text/xml, SOAP 1.2 requires application/soap+xml.
841 * But suppress sending the headers to proxies first if supported. */
842 #if HAVE_DECL_CURLOPT_HEADEROPT /* since curl-7.37.0 */
843 if (!curl_err) {
844 curl_err = curl_easy_setopt(context->curl, CURLOPT_HEADEROPT,
845 CURLHEADER_SEPARATE);
847 #endif /* HAVE_DECL_CURLOPT_HEADEROPT */
848 if (!curl_err) {
849 headers = curl_slist_append(headers,
850 "Accept: application/soap+xml,application/xml,text/xml");
851 if (!headers) {
852 err = IE_NOMEM;
853 goto leave;
855 headers = curl_slist_append(headers, "Content-Type: text/xml");
856 if (!headers) {
857 err = IE_NOMEM;
858 goto leave;
860 headers = curl_slist_append(headers, "SOAPAction: ");
861 if (!headers) {
862 err = IE_NOMEM;
863 goto leave;
865 curl_err = curl_easy_setopt(context->curl, CURLOPT_HTTPHEADER, headers);
867 if (!curl_err) {
868 /* Set user agent identification */
869 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERAGENT,
870 "libisds/" PACKAGE_VERSION);
873 if (use_get) {
874 /* Set GET request */
875 if (!curl_err) {
876 curl_err = curl_easy_setopt(context->curl, CURLOPT_HTTPGET, 1);
878 } else {
879 /* Set POST request body */
880 if (!curl_err) {
881 curl_err = curl_easy_setopt(context->curl, CURLOPT_POST, 1);
883 if (!curl_err) {
884 curl_err = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDS, request);
886 if (!curl_err) {
887 curl_err = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDSIZE,
888 request_length);
893 /* Debug cURL if requested */
894 _Bool debug_curl =
895 ((log_facilities & ILF_HTTP) && (log_level >= ILL_DEBUG));
896 if (!curl_err) {
897 curl_err = curl_easy_setopt(context->curl, CURLOPT_VERBOSE,
898 (debug_curl) ? 1 : 0);
900 if (!curl_err) {
901 curl_err = curl_easy_setopt(context->curl, CURLOPT_DEBUGFUNCTION,
902 (debug_curl) ? log_curl : NULL);
906 /* Check for errors so far */
907 if (curl_err) {
908 isds_log_message(context, curl_easy_strerror(curl_err));
909 err = IE_NETWORK;
910 goto leave;
913 isds_log(ILF_HTTP, ILL_DEBUG, _("Sending %s request to <%s>\n"),
914 use_get ? "GET" : "POST", url);
915 if (!use_get) {
916 isds_log(ILF_HTTP, ILL_DEBUG,
917 _("POST body length: %zu, content follows:\n"), request_length);
918 if (_isds_sizet2int(request_length) >= 0 ) {
919 isds_log(ILF_HTTP, ILL_DEBUG, "%.*s\n",
920 _isds_sizet2int(request_length), request);
922 isds_log(ILF_HTTP, ILL_DEBUG, _("End of POST body\n"));
926 /* Do the request */
927 curl_err = curl_easy_perform(context->curl);
929 if (!curl_err)
930 curl_err = curl_easy_getinfo(context->curl, CURLINFO_CONTENT_TYPE,
931 &content_type);
933 if (curl_err) {
934 /* TODO: Use curl_easy_setopt(CURLOPT_ERRORBUFFER) to obtain detailed
935 * error message. */
936 /* TODO: CURL is not internationalized yet. Collect CURL messages for
937 * I18N. */
938 isds_printf_message(context,
939 _("%s: %s"), url, _(curl_easy_strerror(curl_err)));
940 if (curl_err == CURLE_ABORTED_BY_CALLBACK)
941 err = IE_ABORTED;
942 else if (
943 curl_err == CURLE_SSL_CONNECT_ERROR ||
944 curl_err == CURLE_SSL_ENGINE_NOTFOUND ||
945 curl_err == CURLE_SSL_ENGINE_SETFAILED ||
946 curl_err == CURLE_SSL_CERTPROBLEM ||
947 curl_err == CURLE_SSL_CIPHER ||
948 curl_err == CURLE_SSL_CACERT ||
949 curl_err == CURLE_USE_SSL_FAILED ||
950 curl_err == CURLE_SSL_ENGINE_INITFAILED ||
951 curl_err == CURLE_SSL_CACERT_BADFILE ||
952 curl_err == CURLE_SSL_SHUTDOWN_FAILED ||
953 curl_err == CURLE_SSL_CRL_BADFILE ||
954 curl_err == CURLE_SSL_ISSUER_ERROR
956 err = IE_SECURITY;
957 else
958 err = IE_NETWORK;
959 goto leave;
962 isds_log(ILF_HTTP, ILL_DEBUG, _("Final response to %s received\n"), url);
963 isds_log(ILF_HTTP, ILL_DEBUG,
964 _("Response body length: %zu, content follows:\n"),
965 body.length);
966 if (_isds_sizet2int(body.length) >= 0) {
967 isds_log(ILF_HTTP, ILL_DEBUG, "%.*s\n",
968 _isds_sizet2int(body.length), body.data);
970 isds_log(ILF_HTTP, ILL_DEBUG, _("End of response body\n"));
973 /* Extract MIME type and charset */
974 if (content_type) {
975 char *sep;
976 size_t offset;
978 sep = strchr(content_type, ';');
979 if (sep) offset = (size_t) (sep - content_type);
980 else offset = strlen(content_type);
982 if (mime_type) {
983 *mime_type = malloc(offset + 1);
984 if (!*mime_type) {
985 err = IE_NOMEM;
986 goto leave;
988 memcpy(*mime_type, content_type, offset);
989 (*mime_type)[offset] = '\0';
992 if (charset) {
993 if (!sep) {
994 *charset = NULL;
995 } else {
996 sep = strstr(sep, "charset=");
997 if (!sep) {
998 *charset = NULL;
999 } else {
1000 *charset = strdup(sep + 8);
1001 if (!*charset) {
1002 err = IE_NOMEM;
1003 goto leave;
1010 /* Get HTTP response code */
1011 if (http_code) {
1012 curl_err = curl_easy_getinfo(context->curl,
1013 CURLINFO_RESPONSE_CODE, http_code);
1014 if (curl_err) {
1015 err = IE_ERROR;
1016 goto leave;
1020 /* Store OTP authentication results */
1021 if (response_otp_headers && response_otp_headers->is_complete) {
1022 isds_log(ILF_SEC, ILL_DEBUG,
1023 _("OTP authentication headers received: "
1024 "method=%s, code=%s, message=%s\n"),
1025 response_otp_headers->method, response_otp_headers->code,
1026 response_otp_headers->message);
1028 /* XXX: Don't make unknown code fatal. Missing code can be success if
1029 * HTTP code is 302. This is checked in _isds_soap(). */
1030 response_otp_headers->resolution =
1031 string2isds_otp_resolution(response_otp_headers->code);
1033 if (response_otp_headers->message != NULL) {
1034 char *message_locale = _isds_utf82locale(response_otp_headers->message);
1035 /* _isds_utf82locale() return NULL on inconverable string. Do not
1036 * panic on it.
1037 * TODO: Escape such characters.
1038 * if (message_locale == NULL) {
1039 err = IE_NOMEM;
1040 goto leave;
1042 isds_printf_message(context,
1043 _("Server returned OTP authentication message: %s"),
1044 message_locale);
1045 free(message_locale);
1048 char *next_url = NULL; /* Weak pointer managed by cURL */
1049 curl_err = curl_easy_getinfo(context->curl, CURLINFO_REDIRECT_URL,
1050 &next_url);
1051 if (curl_err) {
1052 err = IE_ERROR;
1053 goto leave;
1055 if (next_url != NULL) {
1056 isds_log(ILF_SEC, ILL_DEBUG,
1057 _("OTP authentication headers redirect to: <%s>\n"),
1058 next_url);
1059 free(response_otp_headers->redirect);
1060 response_otp_headers->redirect = strdup(next_url);
1061 if (response_otp_headers->redirect == NULL) {
1062 err = IE_NOMEM;
1063 goto leave;
1067 leave:
1068 curl_slist_free_all(headers);
1070 if (err) {
1071 free(body.data);
1072 body.data = NULL;
1073 body.length = 0;
1075 if (mime_type) {
1076 free(*mime_type);
1077 *mime_type = NULL;
1079 if (charset) {
1080 free(*charset);
1081 *charset = NULL;
1084 if (err != IE_ABORTED) _isds_close_connection(context);
1087 *response = body.data;
1088 *response_length = body.length;
1090 return err;
1093 /* Converts numeric server response when periodically checking for MEP
1094 * authentication status.
1095 * @str String containing the numeric server response value
1096 * @len String length
1097 * @return server response code or MEP_RESOLUTION_UNKNOWN if the code was not
1098 * recognised. */
1099 static isds_mep_resolution mep_ws_state_response(const char *str, size_t len) {
1100 isds_mep_resolution res = MEP_RESOLUTION_UNKNOWN; /* Default error. */
1102 if ((str == NULL) || (len == 0)) {
1103 return res;
1105 /* Ensure trailing '\0' character. */
1106 char *tmp_str = malloc(len + 1);
1107 if (tmp_str == NULL) {
1108 return res;
1110 memcpy(tmp_str, str, len);
1111 tmp_str[len] = '\0';
1113 char *endptr;
1114 long num = strtol(tmp_str, &endptr, 10);
1115 if (*endptr != '\0' || LONG_MIN == num || LONG_MAX == num) {
1116 return res;
1119 switch (num) {
1120 case -1:
1121 res = MEP_RESOLUTION_UNRECOGNISED;
1122 break;
1123 case 1:
1124 res = MEP_RESOLUTION_ACK_REQUESTED;
1125 break;
1126 case 2:
1127 res = MEP_RESOLUTION_ACK;
1128 break;
1129 case 3:
1130 res = MEP_RESOLUTION_ACK_EXPIRED;
1131 break;
1132 default:
1133 break;
1136 free(tmp_str);
1138 return res;
1142 /* Build SOAP request.
1143 * @context needed for error logging,
1144 * @request is XML node set with SOAP request body,
1145 * @http_request_ptr the address of a pointer to an automatically allocated
1146 * buffer to which the request data are written.
1148 static isds_error build_http_request(struct isds_ctx *context,
1149 const xmlNodePtr request, xmlBufferPtr *http_request_ptr) {
1151 isds_error err = IE_SUCCESS;
1152 xmlBufferPtr http_request = NULL;
1153 xmlSaveCtxtPtr save_ctx = NULL;
1154 xmlDocPtr request_soap_doc = NULL;
1155 xmlNodePtr request_soap_envelope = NULL, request_soap_body = NULL;
1156 xmlNsPtr soap_ns = NULL;
1158 if (NULL == context) {
1159 return IE_INVALID_CONTEXT;
1161 if (NULL == http_request_ptr) {
1162 return IE_ERROR;
1166 /* Build SOAP request envelope */
1167 request_soap_doc = xmlNewDoc(BAD_CAST "1.0");
1168 if (NULL == request_soap_doc) {
1169 isds_log_message(context, _("Could not build SOAP request document"));
1170 err = IE_ERROR;
1171 goto leave;
1173 request_soap_envelope = xmlNewNode(NULL, BAD_CAST "Envelope");
1174 if (NULL == request_soap_envelope) {
1175 isds_log_message(context, _("Could not build SOAP request envelope"));
1176 err = IE_ERROR;
1177 goto leave;
1179 xmlDocSetRootElement(request_soap_doc, request_soap_envelope);
1180 /* Only this way we get namespace definition as @xmlns:soap,
1181 * otherwise we get namespace prefix without definition */
1182 soap_ns = xmlNewNs(request_soap_envelope, BAD_CAST SOAP_NS, NULL);
1183 if(NULL == soap_ns) {
1184 isds_log_message(context, _("Could not create SOAP name space"));
1185 err = IE_ERROR;
1186 goto leave;
1188 xmlSetNs(request_soap_envelope, soap_ns);
1189 request_soap_body = xmlNewChild(request_soap_envelope, NULL,
1190 BAD_CAST "Body", NULL);
1191 if (NULL == request_soap_body) {
1192 isds_log_message(context,
1193 _("Could not add Body to SOAP request envelope"));
1194 err = IE_ERROR;
1195 goto leave;
1199 /* Append request XML node set to SOAP body if request is not empty */
1200 /* XXX: Copy of request must be used, otherwise xmlFreeDoc(request_soap_doc)
1201 * would destroy this outer structure. */
1202 if (NULL != request) {
1203 xmlNodePtr request_copy = xmlCopyNodeList(request);
1204 if (NULL == request_copy) {
1205 isds_log_message(context,
1206 _("Could not copy request content"));
1207 err = IE_ERROR;
1208 goto leave;
1210 if (NULL == xmlAddChildList(request_soap_body, request_copy)) {
1211 xmlFreeNodeList(request_copy);
1212 isds_log_message(context,
1213 _("Could not add request content to SOAP "
1214 "request envelope"));
1215 err = IE_ERROR;
1216 goto leave;
1221 /* Serialize the SOAP request into HTTP request body */
1222 http_request = xmlBufferCreate();
1223 if (NULL == http_request) {
1224 isds_log_message(context,
1225 _("Could not create xmlBuffer for HTTP request body"));
1226 err = IE_ERROR;
1227 goto leave;
1229 /* Last argument 1 means format the XML tree. This is pretty but it breaks
1230 * XML document transport as it adds text nodes (indentation) between
1231 * elements. */
1232 save_ctx = xmlSaveToBuffer(http_request, "UTF-8", 0);
1233 if (NULL == save_ctx) {
1234 isds_log_message(context,
1235 _("Could not create XML serializer"));
1236 err = IE_ERROR;
1237 goto leave;
1239 /* XXX: According LibXML documentation, this function does not return
1240 * meaningful value yet */
1241 xmlSaveDoc(save_ctx, request_soap_doc);
1242 if (-1 == xmlSaveFlush(save_ctx)) {
1243 isds_log_message(context,
1244 _("Could not serialize SOAP request to HTTP request body"));
1245 err = IE_ERROR;
1246 goto leave;
1249 leave:
1250 xmlSaveClose(save_ctx);
1251 if (err == IE_SUCCESS) {
1252 /* Pass buffer to caller when successfully written. */
1253 *http_request_ptr = http_request;
1254 } else {
1255 xmlBufferFree(http_request);
1257 xmlFreeDoc(request_soap_doc); /* recursive, frees request_body, soap_ns*/
1258 return err;
1261 /* Process SOAP response.
1262 * @context needed for error logging,
1263 * @response is a pointer to a buffer where response data are held,
1264 * @response_length is the size of the response,
1265 * @response_document is an automatically allocated XML document whose sub-tree
1266 * identified by @response_node_list holds the SOAP response body content. You
1267 * must xmlFreeDoc() it. If you don't care pass NULL and also
1268 * NULL @response_node_list.
1269 * @response_node_list is a pointer to node set with SOAP response body
1270 * content. The returned pointer points into @response_document to the first
1271 * child of SOAP Body element. Pass NULL and NULL @response_document, if you
1272 * don't care.
1274 static
1275 isds_error process_http_response(struct isds_ctx *context,
1276 const void *response, size_t response_length,
1277 xmlDocPtr *response_document, xmlNodePtr *response_node_list) {
1279 isds_error err = IE_SUCCESS;
1280 xmlDocPtr response_soap_doc = NULL;
1281 xmlNodePtr response_root = NULL;
1282 xmlXPathContextPtr xpath_ctx = NULL;
1283 xmlXPathObjectPtr response_soap_headers = NULL, response_soap_body = NULL,
1284 response_soap_fault = NULL;
1286 if (NULL == context) {
1287 return IE_INVALID_CONTEXT;
1289 if ((NULL == response_document && NULL != response_node_list) ||
1290 (NULL != response_document && NULL == response_node_list)) {
1291 return IE_INVAL;
1294 /* TODO: Convert returned body into XML default encoding */
1296 /* Parse the HTTP body as XML */
1297 response_soap_doc = xmlParseMemory(response, response_length);
1298 if (NULL == response_soap_doc) {
1299 err = IE_XML;
1300 goto leave;
1303 xpath_ctx = xmlXPathNewContext(response_soap_doc);
1304 if (NULL == xpath_ctx) {
1305 err = IE_ERROR;
1306 goto leave;
1309 if (IE_SUCCESS !=
1310 _isds_register_namespaces(xpath_ctx, MESSAGE_NS_UNSIGNED)) {
1311 err = IE_ERROR;
1312 goto leave;
1315 if (_isds_sizet2int(response_length) >= 0) {
1316 isds_log(ILF_SOAP, ILL_DEBUG,
1317 _("SOAP response received:\n%.*s\nEnd of SOAP response\n"),
1318 _isds_sizet2int(response_length), response);
1321 /* Check for SOAP version */
1322 response_root = xmlDocGetRootElement(response_soap_doc);
1323 if (NULL == response_root) {
1324 isds_log_message(context, "SOAP response has no root element");
1325 err = IE_SOAP;
1326 goto leave;
1328 if (xmlStrcmp(response_root->name, BAD_CAST "Envelope") ||
1329 xmlStrcmp(response_root->ns->href, BAD_CAST SOAP_NS)) {
1330 isds_log_message(context, "SOAP response is not SOAP 1.1 document");
1331 err = IE_SOAP;
1332 goto leave;
1335 /* Check for SOAP Headers */
1336 response_soap_headers = xmlXPathEvalExpression(
1337 BAD_CAST "/soap:Envelope/soap:Header/"
1338 "*[@soap:mustUnderstand/text() = true()]", xpath_ctx);
1339 if (NULL == response_soap_headers) {
1340 err = IE_ERROR;
1341 goto leave;
1343 if (!xmlXPathNodeSetIsEmpty(response_soap_headers->nodesetval)) {
1344 isds_log_message(context,
1345 _("SOAP response requires unsupported feature"));
1346 /* TODO: log the headers
1347 * xmlChar *fragment = NULL;
1348 * fragment = xmlXPathCastNodeSetToSting(response_soap_headers->nodesetval);*/
1349 err = IE_NOTSUP;
1350 goto leave;
1353 /* Get SOAP Body */
1354 response_soap_body = xmlXPathEvalExpression(
1355 BAD_CAST "/soap:Envelope/soap:Body", xpath_ctx);
1356 if (NULL == response_soap_body) {
1357 err = IE_ERROR;
1358 goto leave;
1360 if (xmlXPathNodeSetIsEmpty(response_soap_body->nodesetval)) {
1361 isds_log_message(context,
1362 _("SOAP response does not contain SOAP Body element"));
1363 err = IE_SOAP;
1364 goto leave;
1366 if (response_soap_body->nodesetval->nodeNr > 1) {
1367 isds_log_message(context,
1368 _("SOAP response has more than one Body element"));
1369 err = IE_SOAP;
1370 goto leave;
1373 /* Check for SOAP Fault */
1374 response_soap_fault = xmlXPathEvalExpression(
1375 BAD_CAST "/soap:Envelope/soap:Body/soap:Fault", xpath_ctx);
1376 if (NULL == response_soap_fault) {
1377 err = IE_ERROR;
1378 goto leave;
1380 if (!xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
1381 /* Server signals Fault. Gather error message and croak. */
1382 /* XXX: Only first message is passed */
1383 char *message = NULL, *message_locale = NULL;
1384 xpath_ctx->node = response_soap_fault->nodesetval->nodeTab[0];
1385 xmlXPathFreeObject(response_soap_fault);
1386 /* XXX: faultstring and faultcode are in no name space according
1387 * ISDS specification */
1388 /* First more verbose faultstring */
1389 response_soap_fault = xmlXPathEvalExpression(
1390 BAD_CAST "faultstring[1]/text()", xpath_ctx);
1391 if ((NULL != response_soap_fault) &&
1392 !xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
1393 message = (char *)
1394 xmlXPathCastNodeSetToString(response_soap_fault->nodesetval);
1395 message_locale = _isds_utf82locale(message);
1397 /* If not available, try shorter faultcode */
1398 if (NULL == message_locale) {
1399 free(message);
1400 xmlXPathFreeObject(response_soap_fault);
1401 response_soap_fault = xmlXPathEvalExpression(
1402 BAD_CAST "faultcode[1]/text()", xpath_ctx);
1403 if ((NULL != response_soap_fault) &&
1404 !xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
1405 message = (char *)
1406 xmlXPathCastNodeSetToString(
1407 response_soap_fault->nodesetval);
1408 message_locale = _isds_utf82locale(message);
1412 /* Croak */
1413 if (NULL != message_locale) {
1414 isds_printf_message(context, _("SOAP response signals Fault: %s"),
1415 message_locale);
1416 } else {
1417 isds_log_message(context, _("SOAP response signals Fault"));
1420 free(message_locale);
1421 free(message);
1423 err = IE_SOAP;
1424 goto leave;
1428 /* Extract XML tree with ISDS response from SOAP envelope and return it.
1429 * XXX: response_soap_body lists only one Body element here. We need
1430 * children which may not exist (i.e. empty Body) or being more than one
1431 * (this is not the case of ISDS payload, but let's support generic SOAP).
1432 * XXX: We will return the XML document and children as a node list for
1433 * two reasons:
1434 * (1) We won't to do expensive xmlDocCopyNodeList(),
1435 * (2) Any node is unusable after calling xmlFreeDoc() on it's document
1436 * because the document holds a dictionary with identifiers. Caller always
1437 * can do xmlDocCopyNodeList() on a fresh document later. */
1438 if (NULL != response_document && NULL != response_node_list) {
1439 *response_document = response_soap_doc;
1440 *response_node_list =
1441 response_soap_body->nodesetval->nodeTab[0]->children;
1442 response_soap_doc = NULL; /* The document has been passed to the caller. */
1445 leave:
1446 xmlXPathFreeObject(response_soap_fault);
1447 xmlXPathFreeObject(response_soap_body);
1448 xmlXPathFreeObject(response_soap_headers);
1449 xmlXPathFreeContext(xpath_ctx);
1450 xmlFreeDoc(response_soap_doc);
1451 return err;
1454 /* Do SOAP request.
1455 * @context holds the base URL,
1456 * @file is a (CGI) file of SOAP URL,
1457 * @request is XML node set with SOAP request body.
1458 * @file must be NULL, @request should be NULL rather than empty, if they should
1459 * not be signaled in the SOAP request.
1460 * @response_document is an automatically allocated XML document whose subtree
1461 * identified by @response_node_list holds the SOAP response body content. You
1462 * must xmlFreeDoc() it. If you don't care pass NULL and also
1463 * NULL @response_node_list.
1464 * @response_node_list is a pointer to node set with SOAP response body
1465 * content. The returned pointer points into @response_document to the first
1466 * child of SOAP Body element. Pass NULL and NULL @response_document, if you
1467 * don't care.
1468 * @raw_response is automatically allocated bit stream with response body. Use
1469 * NULL if you don't care
1470 * @raw_response_length is size of @raw_response in bytes
1471 * In case of error the response will be deallocated automatically.
1472 * Side effect: message buffer */
1473 _hidden isds_error _isds_soap(struct isds_ctx *context, const char *file,
1474 const xmlNodePtr request,
1475 xmlDocPtr *response_document, xmlNodePtr *response_node_list,
1476 void **raw_response, size_t *raw_response_length) {
1478 isds_error err = IE_SUCCESS;
1479 char *url = NULL;
1480 char *mime_type = NULL;
1481 long http_code = 0;
1482 struct auth_headers response_otp_headers;
1483 xmlBufferPtr http_request = NULL;
1484 void *http_response = NULL;
1485 size_t response_length = 0;
1487 if (!context) return IE_INVALID_CONTEXT;
1488 if ( (NULL == response_document && NULL != response_node_list) ||
1489 (NULL != response_document && NULL == response_node_list))
1490 return IE_INVAL;
1491 if (!raw_response_length && raw_response) return IE_INVAL;
1493 if (response_document) *response_document = NULL;
1494 if (response_node_list) *response_node_list = NULL;
1495 if (raw_response) *raw_response = NULL;
1497 url = _isds_astrcat(context->url, file);
1498 if (!url) return IE_NOMEM;
1501 err = build_http_request(context, request, &http_request);
1502 if (IE_SUCCESS != err) {
1503 goto leave;
1507 if ((context->otp_credentials != NULL) || (context->mep_credentials != NULL)) {
1508 memset(&response_otp_headers, 0, sizeof(response_otp_headers));
1510 redirect:
1511 if ((context->otp_credentials != NULL) || (context->mep_credentials != NULL)) {
1512 auth_headers_free(&response_otp_headers);
1514 isds_log(ILF_SOAP, ILL_DEBUG,
1515 _("SOAP request to sent to %s:\n%.*s\nEnd of SOAP request\n"),
1516 url, http_request->use, http_request->content);
1518 if ((NULL != context->mep_credentials) && (NULL != context->mep_credentials->intermediate_uri)) {
1519 /* POST does not work for the intermediate URI, using GET here. */
1520 err = http(context, context->mep_credentials->intermediate_uri, 1, NULL, 0,
1521 &http_response, &response_length,
1522 &mime_type, NULL, &http_code,
1523 ((context->otp_credentials == NULL) && (context->mep_credentials == NULL)) ? NULL: &response_otp_headers);
1524 } else {
1525 err = http(context, url, 0, http_request->content, http_request->use,
1526 &http_response, &response_length,
1527 &mime_type, NULL, &http_code,
1528 ((context->otp_credentials == NULL) && (context->mep_credentials == NULL)) ? NULL: &response_otp_headers);
1531 /* TODO: HTTP binding for SOAP prescribes non-200 HTTP return codes
1532 * to be processed too. */
1534 if (err) {
1535 goto leave;
1538 if (NULL != context->otp_credentials)
1539 context->otp_credentials->resolution = response_otp_headers.resolution;
1541 /* Check for HTTP return code */
1542 isds_log(ILF_SOAP, ILL_DEBUG, _("Server returned %ld HTTP code\n"),
1543 http_code);
1544 switch (http_code) {
1545 /* XXX: We must see which code is used for not permitted ISDS
1546 * operation like downloading message without proper user
1547 * permissions. In that case we should keep connection opened. */
1548 case 200:
1549 if (NULL != context->otp_credentials) {
1550 if (context->otp_credentials->resolution ==
1551 OTP_RESOLUTION_UNKNOWN)
1552 context->otp_credentials->resolution =
1553 OTP_RESOLUTION_SUCCESS;
1554 } else if (NULL != context->mep_credentials) {
1555 /* The server returns just a numerical value in the body, nothing else. */
1556 context->mep_credentials->resolution =
1557 mep_ws_state_response(http_response, response_length);
1558 switch (context->mep_credentials->resolution) {
1559 case MEP_RESOLUTION_ACK_REQUESTED:
1560 /* Waiting for the user to acknowledge the login request
1561 * in the mobile application. This may take a while.
1562 * Return with partial success. Don't close communication
1563 * context. */
1564 err = IE_PARTIAL_SUCCESS;
1565 goto leave;
1566 break;
1567 case MEP_RESOLUTION_ACK:
1568 /* Immediately redirect to login finalisation. */
1569 zfree(context->mep_credentials->intermediate_uri);
1570 err = IE_PARTIAL_SUCCESS;
1571 goto redirect;
1572 break;
1573 default:
1574 zfree(context->mep_credentials->intermediate_uri);
1575 err = IE_NOT_LOGGED_IN;
1576 /* No SOAP data are returned here just plain response code. */
1577 goto leave;
1578 break;
1581 break;
1582 case 302:
1583 if (NULL != context->otp_credentials) {
1584 if (context->otp_credentials->resolution ==
1585 OTP_RESOLUTION_UNKNOWN)
1586 context->otp_credentials->resolution =
1587 OTP_RESOLUTION_SUCCESS;
1588 err = IE_PARTIAL_SUCCESS;
1589 isds_printf_message(context,
1590 _("Server redirects on <%s> because OTP authentication "
1591 "succeeded."),
1592 url);
1593 if (context->otp_credentials->otp_code != NULL &&
1594 response_otp_headers.redirect != NULL) {
1595 /* XXX: If OTP code is known, this must be second OTP phase, so
1596 * send final POST request and unset Basic authentication
1597 * from cURL context as cookie is used instead. */
1598 free(url);
1599 url = response_otp_headers.redirect;
1600 response_otp_headers.redirect = NULL;
1601 _isds_discard_credentials(context, 0);
1602 err = unset_http_authorization(context);
1603 if (err) {
1604 isds_log_message(context, _("Could not remove "
1605 "credentials from CURL handle."));
1606 goto leave;
1608 goto redirect;
1609 } else {
1610 /* XXX: Otherwise bail out to ask application for OTP code. */
1611 goto leave;
1613 } else if (NULL != context->mep_credentials) {
1614 if (context->mep_credentials->resolution == MEP_RESOLUTION_UNKNOWN) {
1615 context->mep_credentials->resolution = MEP_RESOLUTION_ACK_REQUESTED;
1616 if ((context->mep_credentials->intermediate_uri == NULL) &&
1617 (response_otp_headers.redirect != NULL)) {
1618 /* This is the first attempt. */
1619 isds_printf_message(context,
1620 _("Server redirects on <%s> because mobile key authentication "
1621 "succeeded."),
1622 url);
1623 context->mep_credentials->intermediate_uri =
1624 _isds_astrcat(response_otp_headers.redirect, NULL);
1625 err = IE_PARTIAL_SUCCESS;
1626 goto redirect;
1628 } else if (context->mep_credentials->resolution == MEP_RESOLUTION_ACK) {
1629 /* MEP login succeeded. No SOAP data received even though
1630 * they were requested. */
1631 context->mep_credentials->resolution = MEP_RESOLUTION_SUCCESS;
1632 err = IE_SUCCESS;
1633 goto leave;
1635 break;
1636 } else {
1637 err = IE_HTTP;
1638 isds_printf_message(context,
1639 _("Code 302: Server redirects on <%s> request. "
1640 "Redirection is forbidden in stateless mode."),
1641 url);
1642 goto leave;
1644 break;
1645 case 400:
1646 if (NULL != context->mep_credentials) {
1647 free(context->mep_credentials->intermediate_uri);
1648 context->mep_credentials->intermediate_uri = NULL;
1649 context->mep_credentials->resolution = MEP_RESOLUTION_UNRECOGNISED;
1650 err = IE_NOT_LOGGED_IN;
1651 isds_printf_message(context,
1652 _("Code 400: Server redirects on <%s> request. "
1653 "MEP communication code was not recognised."), url);
1654 goto leave;
1656 break;
1657 case 401: /* ISDS server returns 401 even if Authorization
1658 presents. */
1659 case 403: /* HTTP/1.0 prescribes 403 if Authorization presents. */
1660 err = IE_NOT_LOGGED_IN;
1661 isds_log_message(context, _("Authentication failed"));
1662 goto leave;
1663 break;
1664 case 404:
1665 err = IE_HTTP;
1666 isds_printf_message(context,
1667 _("Code 404: Document (%s) not found on server"), url);
1668 goto leave;
1669 break;
1670 /* 500 should return standard SOAP message */
1673 /* Check for Content-Type: text/xml.
1674 * Do it after HTTP code check because 401 Unauthorized returns HTML web
1675 * page for browsers. */
1676 if (mime_type && strcmp(mime_type, "text/xml")
1677 && strcmp(mime_type, "application/soap+xml")
1678 && strcmp(mime_type, "application/xml")) {
1679 char *mime_type_locale = _isds_utf82locale(mime_type);
1680 isds_printf_message(context,
1681 _("%s: bad MIME type sent by server: %s"), url,
1682 mime_type_locale);
1683 free(mime_type_locale);
1684 err = IE_SOAP;
1685 goto leave;
1689 err = process_http_response(context, http_response, response_length,
1690 response_document, response_node_list);
1691 if (IE_SUCCESS != err) {
1692 goto leave;
1696 /* Save raw response */
1697 if (NULL != raw_response) {
1698 *raw_response = http_response;
1699 http_response = NULL;
1701 if (NULL != raw_response_length) {
1702 *raw_response_length = response_length;
1705 leave:
1706 if ((context->otp_credentials != NULL) || (context->mep_credentials != NULL))
1707 auth_headers_free(&response_otp_headers);
1708 free(mime_type);
1709 free(http_response);
1710 xmlBufferFree(http_request);
1711 free(url);
1713 return err;
1717 /* Build new URL from current @context and template.
1718 * @context is context carrying an URL
1719 * @template is printf(3) format string. First argument is length of the base
1720 * URL found in @context, second argument is the base URL, third argument is
1721 * again the base URL.
1722 * XXX: We cannot use "$" formatting character because it's not in the ISO C99.
1723 * @new_url is newly allocated URL built from @template. Caller must free it.
1724 * Return IE_SUCCESS, or corresponding error code and @new_url will not be
1725 * allocated.
1726 * */
1727 _hidden isds_error _isds_build_url_from_context(struct isds_ctx *context,
1728 const char *template, char **new_url) {
1729 int length, slashes;
1731 if (NULL != new_url) *new_url = NULL;
1732 if (NULL == context) return IE_INVALID_CONTEXT;
1733 if (NULL == template) return IE_INVAL;
1734 if (NULL == new_url) return IE_INVAL;
1736 /* Find length of base URL from context URL */
1737 if (NULL == context->url) {
1738 isds_log_message(context, _("Base URL could not have been determined "
1739 "from context URL because there was no URL set in the "
1740 "context"));
1741 return IE_ERROR;
1743 for (length = 0, slashes = 0; context->url[length] != '\0'; length++) {
1744 if (context->url[length] == '/') slashes++;
1745 if (slashes == 3) break;
1747 if (slashes != 3) {
1748 isds_log_message(context, _("Base URL could not have been determined "
1749 "from context URL"));
1750 return IE_ERROR;
1752 length++;
1754 /* Build new URL */
1755 if (-1 == isds_asprintf(new_url, template, length, context->url,
1756 context->url))
1757 return IE_NOMEM;
1759 return IE_SUCCESS;
1763 /* Invalidate session cookie for otp authenticated @context */
1764 _hidden isds_error _isds_invalidate_otp_cookie(struct isds_ctx *context) {
1765 isds_error err;
1766 char *url = NULL;
1767 long http_code;
1768 void *response = NULL;
1769 size_t response_length;
1771 if (context == NULL || (!context->otp && !context->mep)) return IE_INVALID_CONTEXT;
1772 if (context->curl == NULL) return IE_CONNECTION_CLOSED;
1774 /* Build logout URL */
1775 /*"https://DOMAINNAME/as/processLogout?uri=https://DOMAINNAME/apps/DS/WEB_SERVICE_ENDPOINT"*/
1776 err = _isds_build_url_from_context(context,
1777 "%.*sas/processLogout?uri=%sDS/dz", &url);
1778 if (err) return err;
1780 /* Invalidate the cookie by GET request */
1781 err = http(context,
1782 url, 1,
1783 NULL, 0,
1784 &response, &response_length,
1785 NULL, NULL, &http_code,
1786 NULL);
1787 free(response);
1788 free(url);
1789 if (err) {
1790 /* long message set by http() */
1791 } else if (http_code != 200) {
1792 /* TODO: Specification does not define response for this request.
1793 * Especially it does not state whether direct 200 or 302 redirect is
1794 * sent. We need to check real implementation. */
1795 err = IE_ISDS;
1796 isds_printf_message(context, _("Cookie for OTP authenticated "
1797 "connection to <%s> could not been invalidated"),
1798 context->url);
1799 } else {
1800 isds_log(ILF_SEC, ILL_DEBUG, _("Cookie for OTP authenticated "
1801 "connection to <%s> has been invalidated.\n"),
1802 context->url);
1804 return err;
1808 /* LibXML functions:
1810 * void xmlInitParser(void)
1811 * Initialization function for the XML parser. This is not reentrant. Call
1812 * once before processing in case of use in multithreaded programs.
1814 * int xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
1815 * Initialize a parser context
1817 * xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar * cur,
1818 * const * char URL, const char * encoding, int options);
1819 * Parse in-memory NULL-terminated document @cur.
1821 * xmlDocPtr xmlParseMemory(const char * buffer, int size)
1822 * Parse an XML in-memory block and build a tree.
1824 * xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char * buffer, int
1825 * size);
1826 * Create a parser context for an XML in-memory document.
1828 * xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar * cur)
1829 * Creates a parser context for an XML in-memory document.
1831 * xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt,
1832 * const char * buffer, int size, const char * URL, const char * encoding,
1833 * int options)
1834 * Parse an XML in-memory document and build a tree. This reuses the existing
1835 * @ctxt parser context.
1837 * void xmlCleanupParser(void)
1838 * Cleanup function for the XML library. It tries to reclaim all parsing
1839 * related glob document related memory. Calling this function should not
1840 * prevent reusing the libr finished using the library or XML document built
1841 * with it.
1843 * void xmlClearParserCtxt(xmlParserCtxtPtr ctxt)
1844 * Clear (release owned resources) and reinitialize a parser context.
1846 * void xmlCtxtReset(xmlParserCtxtPtr ctxt)
1847 * Reset a parser context
1849 * void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)
1850 * Free all the memory used by a parser context. However the parsed document
1851 * in ctxt->myDoc is not freed.
1853 * void xmlFreeDoc(xmlDocPtr cur)
1854 * Free up all the structures used by a document, tree included.