TODO: OTP-authenticatd password change tested
[libisds.git] / src / soap.c
blobfd513c2b64ba32e823ab4cc6985cd10901366aee
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) */
8 /* Private structure for write_body() call back */
9 struct soap_body {
10 void *data;
11 size_t length;
14 /* Private structure for write_header() call back */
15 struct auth_headers {
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);
31 zfree(headers->code);
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) {
41 const char *value;
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;
53 return value;
57 /* Try to decode header value per RFC 2047.
58 * @prepend_space is true if a space should be inserted before decoded word
59 * into @output in case the word has been decoded successfully.
60 * @input is zero terminated input, it's updated to point all consumed
61 * input - 1.
62 * @output is buffer to store decoded value, it's updated to point after last
63 * written character. The buffer must be preallocated.
64 * @return 0 if input has been successfully decoded, then @input and @output
65 * poineres will be updated. Otherwise return non-zero value and keeps
66 * argument pointers and memory unchanged. */
67 static int try_rfc2047_decode(_Bool prepend_space, const char **input,
68 char **output) {
69 const char *encoded;
70 const char *charset_start, *encoding, *end;
71 size_t charset_length;
72 char *charset = NULL;
73 /* ISDS prescribes B encoding only, but RFC 2047 requires to support Q
74 * encoding too. ISDS prescribes UTF-8 charset only, RFC requiers to
75 * support any MIME charset. */
76 if (input == NULL || *input == NULL || output == NULL || *output == NULL)
77 return -1;
79 /* Start is "=?" */
80 encoded = *input;
81 if (encoded[0] != '=' || encoded[1] != '?')
82 return -1;
84 /* Then is "CHARSET?" */
85 charset_start = (encoded += 2);
86 while (*encoded != '?') {
87 if (*encoded == '\0')
88 return -1;
89 if (*encoded == ' ' || *encoded == '\t' || *encoded == '\r' || *encoded == '\n')
90 return -1;
91 encoded++;
93 encoded++;
95 /* Then is "ENCODING?", where ENCODING is /[BbQq]/ */
96 if (*encoded == '\0') return -1;
97 encoding = encoded++;
98 if (*encoded != '?')
99 return -1;
100 encoded++;
102 /* Then is "ENCODED_TEXT?=" */
103 while (*encoded != '?') {
104 if (*encoded == '\0')
105 return -1;
106 if (*encoded == ' ' || *encoded == '\t' || *encoded == '\r' || *encoded == '\n')
107 return -1;
108 encoded++;
110 end = encoded;
111 if (*(++encoded) != '=') return -1;
113 /* Now pointers are:
114 * "=?CHARSET?E?ENCODED_TEXT?="
115 * | | | ||
116 * | | | |\- encoded
117 * | | | \- end
118 * | | \- encoding
119 * | \- charset_start
120 * \- *input
123 charset_length = encoding - charset_start - 1;
124 if (charset_length < 1)
125 return -1;
126 charset = strndup(charset_start, charset_length);
127 if (charset == NULL)
128 return -1;
130 /* Decode encoding */
131 char *bit_stream = NULL;
132 size_t bit_length = 0;
133 size_t encoding_length = end - encoding - 2;
135 if (*encoding == 'B') {
136 /* Decode Base-64 */
137 char *b64_stream = NULL;
138 if (NULL == (b64_stream =
139 malloc((encoding_length + 1) * sizeof(*encoding)))) {
140 free(charset);
141 return -1;
143 memcpy(b64_stream, encoding + 2, encoding_length);
144 b64_stream[encoding_length] = '\0';
145 bit_length = _isds_b64decode(b64_stream, (void **)&bit_stream);
146 free(b64_stream);
147 if (bit_length == (size_t) -1) {
148 free(charset);
149 return -1;
151 } else if (*encoding == 'Q') {
152 /* Decode Quoted-printable-like */
153 if (NULL == (bit_stream =
154 malloc((encoding_length) * sizeof(*encoding)))) {
155 free(charset);
156 return -1;
158 for (size_t q = 2; q < encoding_length + 2; q++) {
159 if (encoding[q] == '_') {
160 bit_stream[bit_length] = '\x20';
161 } else if (encoding[q] == '=') {
162 int ordinar;
163 /* Validate "=HH", where H is hexadecimal digit */
164 if (q + 2 >= encoding_length + 2 ) {
165 free(bit_stream);
166 free(charset);
167 return -1;
169 /* Convert =HH */
170 if ((ordinar = _isds_hex2i(encoding[++q])) < 0) {
171 free(bit_stream);
172 free(charset);
173 return -1;
175 bit_stream[bit_length] = (ordinar << 4);
176 if ((ordinar = _isds_hex2i(encoding[++q])) < 0) {
177 free(bit_stream);
178 free(charset);
179 return -1;
181 bit_stream[bit_length] += ordinar;
182 } else {
183 bit_stream[bit_length] = encoding[q];
185 bit_length++;
187 } else {
188 /* Unknown encoding */
189 free(charset);
190 return -1;
193 /* Convert to UTF-8 */
194 char *utf_stream = NULL;
195 size_t utf_length;
196 utf_length = _isds_any2any(charset, "UTF-8", bit_stream, bit_length,
197 (void **)&utf_stream);
198 free(bit_stream);
199 free(charset);
200 if (utf_length == (size_t) -1) {
201 return -1;
204 /* Copy UTF-8 stream to output buffer */
205 if (prepend_space) {
206 **output = ' ';
207 (*output)++;
209 memcpy(*output, utf_stream, utf_length);
210 free(utf_stream);
211 *output += utf_length;
213 *input = encoded;
214 return 0;
218 /* Decode HTTP header value per RFC 2047.
219 * @encoded_value is encoded HTTP header value terminated with NUL. It can
220 * contain HTTP LWS separators that will be replaced with a space.
221 * @return newly allocated decoded value without EOL, or return NULL. */
222 static char *decode_header_value(const char *encoded_value) {
223 char *decoded = NULL, *decoded_cursor;
224 size_t content_length;
225 _Bool text_started = 0, lws_seen = 0, encoded_word_seen = 0;
227 if (encoded_value == NULL) return NULL;
228 content_length = strlen(encoded_value);
230 /* A character can occupy up to 6 bytes in UTF-8 */
231 decoded = malloc(content_length * 6 + 1);
232 if (decoded == NULL) {
233 /* ENOMEM */
234 return NULL;
237 /* Decode */
238 /* RFC 2616, section 4.2: Remove surrounding LWS, replace inner ones with
239 * a space. */
240 /* RFC 2047, section 6.2: LWS between adjacent encoded words is ignored.
241 * */
242 for (decoded_cursor = decoded; *encoded_value; encoded_value++) {
243 if (*encoded_value == '\r' || *encoded_value == '\n' ||
244 *encoded_value == '\t' || *encoded_value == ' ') {
245 lws_seen = 1;
246 continue;
248 if (*encoded_value == '=' &&
249 !try_rfc2047_decode(
250 lws_seen && text_started && !encoded_word_seen,
251 &encoded_value, &decoded_cursor)) {
252 encoded_word_seen = 1;
253 } else {
254 if (lws_seen && text_started)
255 *(decoded_cursor++) = ' ';
256 *(decoded_cursor++) = *encoded_value;
257 encoded_word_seen = 0;
259 lws_seen = 0;
260 text_started = 1;
262 *decoded_cursor = '\0';
264 return decoded;
268 /* Return true, if server requests OTP authorization method that client
269 * requested. Otherwise return false.
270 * @client_method is method client requested
271 * @server_method is value of WWW-Authenticate header */
272 /*static _Bool otp_method_matches(const isds_otp_method client_method,
273 const char *server_method) {
274 char *method_name = NULL;
276 switch (client_method) {
277 case OTP_HMAC: method_name = "hotp"; break;
278 case OTP_TIME: method_name = "totp"; break;
279 default: return 0;
282 if (!strncmp(server_method, method_name, 4) && (
283 server_method[4] == '\0' || server_method[4] == ' ' ||
284 server_method[4] == '\t'))
285 return 1;
286 return 0;
290 /* Convert UTF-8 @string to HTTP OTP resolution enum type.
291 * @Return corresponding value or OTP_RESOLUTION_UNKNOWN if @string is not
292 * defined or unknown value. */
293 static isds_otp_resolution string2isds_otp_resolution(const char *string) {
294 if (string == NULL)
295 return OTP_RESOLUTION_UNKNOWN;
296 else if (!strcmp(string, "authentication.info.totpSended"))
297 return OTP_RESOLUTION_TOTP_SENT;
298 else if (!strcmp(string, "authentication.error.userIsNotAuthenticated"))
299 return OTP_RESOLUTION_BAD_AUTHENTICATION;
300 else if (!strcmp(string, "authentication.error.intruderDetected"))
301 return OTP_RESOLUTION_ACCESS_BLOCKED;
302 else if (!strcmp(string, "authentication.error.paswordExpired"))
303 return OTP_RESOLUTION_PASSWORD_EXPIRED;
304 else if (!strcmp(string, "authentication.info.cannotSendQuickly"))
305 return OTP_RESOLUTION_TO_FAST;
306 else if (!strcmp(string, "authentication.error.badRole"))
307 return OTP_RESOLUTION_UNAUTHORIZED;
308 else if (!strcmp(string, "authentication.info.totpNotSended"))
309 return OTP_RESOLUTION_TOTP_NOT_SENT;
310 else
311 return OTP_RESOLUTION_UNKNOWN;
315 /* Close connection to server and destroy CURL handle associated
316 * with @context */
317 _hidden isds_error _isds_close_connection(struct isds_ctx *context) {
318 if (!context) return IE_INVALID_CONTEXT;
320 if (context->curl) {
321 curl_easy_cleanup(context->curl);
322 context->curl = NULL;
323 isds_log(ILF_HTTP, ILL_DEBUG, _("Connection to server %s closed\n"),
324 context->url);
325 return IE_SUCCESS;
326 } else {
327 return IE_CONNECTION_CLOSED;
332 /* Remove username and password from context CURL handle. */
333 static isds_error unset_http_authorization(struct isds_ctx *context) {
334 isds_error error = IE_SUCCESS;
336 if (context == NULL) return IE_INVALID_CONTEXT;
337 if (context->curl == NULL) return IE_CONNECTION_CLOSED;
339 #if HAVE_DECL_CURLOPT_USERNAME /* Since curl-7.19.1 */
340 if (curl_easy_setopt(context->curl, CURLOPT_USERNAME, NULL))
341 error = IE_ERROR;
342 if (curl_easy_setopt(context->curl, CURLOPT_PASSWORD, NULL))
343 error = IE_ERROR;
344 #else
345 if (curl_easy_setopt(context->curl, CURLOPT_USERPWD, NULL))
346 error = IE_ERROR;
347 #endif /* not HAVE_DECL_CURLOPT_USERNAME */
349 if (error)
350 isds_log(ILF_HTTP, ILL_ERR, _("Error while unsetting user name and"
351 "password from CURL handle for connection to server %s.\n"),
352 context->url);
353 else
354 isds_log(ILF_HTTP, ILL_DEBUG, _("User name and password for server %s"
355 "have been unset from CURL handle.\n"), context->url);
356 return error;
360 /* CURL call back function called when chunk of HTTP response body is available.
361 * @buffer points to new data
362 * @size * @nmemb is length of the chunk in bytes. Zero means empty body.
363 * @userp is private structure.
364 * Must return the length of the chunk, otherwise CURL will signal
365 * CURL_WRITE_ERROR. */
366 static size_t write_body(void *buffer, size_t size, size_t nmemb, void *userp) {
367 struct soap_body *body = (struct soap_body *) userp;
368 void *new_data;
370 /* FIXME: Check for (size * nmemb + body->lengt) !> SIZE_T_MAX.
371 * Precompute the product then. */
373 if (!body) return 0; /* This should never happen */
374 if (0 == (size * nmemb)) return 0; /* Empty body */
376 new_data = realloc(body->data, body->length + size * nmemb);
377 if (!new_data) return 0;
379 memcpy(new_data + body->length, buffer, size * nmemb);
381 body->data = new_data;
382 body->length += size * nmemb;
384 return (size * nmemb);
388 /* CURL call back function called when a HTTP response header is available.
389 * This is called for each header even if reply consists of more responses.
390 * @buffer points to new header (no zero terminator, but HTTP EOL is included)
391 * @size * @nmemb is length of the header in bytes
392 * @userp is private structure.
393 * Must return the length of the header, otherwise CURL will signal
394 * CURL_WRITE_ERROR. */
395 static size_t write_header(void *buffer, size_t size, size_t nmemb, void *userp) {
396 struct auth_headers *headers = (struct auth_headers *) userp;
397 size_t length;
398 const char *value;
400 /* FIXME: Check for (size * nmemb) !> SIZE_T_MAX.
401 * Precompute the product then. */
402 length = size * nmemb;
404 if (NULL == headers) return 0; /* This should never happen */
405 if (0 == length) {
406 /* ??? Is this the empty line delimiter? */
407 return 0; /* Empty headers */
410 /* New response, invalide authentication headers. */
411 /* XXX: Chunked encoding trailer is not supported */
412 if (headers->is_complete) auth_headers_free(headers);
414 /* Append continuation to multi-line header */
415 if (*(char *)buffer == ' ' || *(char *)buffer == '\t') {
416 if (headers->last_header != NULL) {
417 size_t old_length = strlen(headers->last_header);
418 char *longer_header = realloc(headers->last_header, old_length + length);
419 if (longer_header == NULL) {
420 /* ENOMEM */
421 return 0;
423 strncpy(longer_header + old_length, (char*)buffer + 1, length - 1);
424 longer_header[old_length + length - 1] = '\0';
425 headers->last_header = longer_header;
426 } else {
427 /* Invalid continuation without starting header will be skipped. */
428 isds_log(ILF_HTTP, ILL_WARNING,
429 _("HTTP header continuation without starting header has "
430 "been encountered. Skipping invalid HTTP response "
431 "line.\n"));
433 goto leave;
436 /* Decode last header */
437 value = header_value(headers->last_header, "WWW-Authenticate");
438 if (value != NULL) {
439 free(headers->method);
440 if (NULL == (headers->method = decode_header_value(value))) {
441 /* TODO: Set IE_NOMEM to context */
442 return 0;
444 goto store;
447 value = header_value(headers->last_header, "X-Response-message-code");
448 if (value != NULL) {
449 free(headers->code);
450 if (NULL == (headers->code = decode_header_value(value))) {
451 /* TODO: Set IE_NOMEM to context */
452 return 0;
454 goto store;
457 value = header_value(headers->last_header, "X-Response-message-text");
458 if (value != NULL) {
459 free(headers->message);
460 if (NULL == (headers->message = decode_header_value(value))) {
461 /* TODO: Set IE_NOMEM to context */
462 return 0;
464 goto store;
467 store:
468 /* Last header decoded, free it */
469 zfree(headers->last_header);
471 if (!strncmp(buffer, "\r\n", length)) {
472 /* Current line is header---body separator */
473 headers->is_complete = 1;
474 goto leave;
475 } else {
476 /* Current line is new header, store it */
477 headers->last_header = malloc(length + 1);
478 if (headers->last_header == NULL) {
479 /* TODO: Set IE_NOMEM to context */
480 return 0;
482 memcpy(headers->last_header, buffer, length);
483 headers->last_header[length] = '\0';
486 leave:
487 return (length);
491 /* CURL progress callback proxy to rearrange arguments.
492 * @curl_data is session context */
493 static int progress_proxy(void *curl_data, double download_total,
494 double download_current, double upload_total, double upload_current) {
495 struct isds_ctx *context = (struct isds_ctx *) curl_data;
496 int abort = 0;
498 if (context && context->progress_callback) {
499 abort = context->progress_callback(
500 upload_total, upload_current,
501 download_total, download_current,
502 context->progress_callback_data);
503 if (abort) {
504 isds_log(ILF_HTTP, ILL_INFO,
505 _("Application aborted HTTP transfer"));
509 return abort;
513 /* CURL call back function called when curl has something to log.
514 * @curl is cURL context
515 * @type is cURL log facility
516 * @buffer points to log data, XXX: not zero-terminated
517 * @size is length of log data
518 * @userp is private structure.
519 * Must return 0. */
520 static int log_curl(CURL *curl, curl_infotype type, char *buffer, size_t size,
521 void *userp) {
522 if (!buffer || 0 == size) return 0;
523 if (type == CURLINFO_TEXT || type == CURLINFO_HEADER_IN ||
524 type == CURLINFO_HEADER_OUT)
525 isds_log(ILF_HTTP, ILL_DEBUG, "%*s", size, buffer);
526 return 0;
530 /* Do HTTP request.
531 * @context holds the base URL,
532 * @url is a (CGI) file of SOAP URL,
533 * @use_get is a false to do a POST request, true to do a GET request.
534 * @request is body for POST request
535 * @request_length is length of @request in bytes
536 * @reponse is automatically reallocated() buffer to fit HTTP response with
537 * @response_length (does not need to match allocated memory exactly). You must
538 * free() the @response.
539 * @mime_type is automatically allocated MIME type send by server (*NULL if not
540 * sent). Set NULL if you don't care.
541 * @charset is charset of the body signaled by server. The same constrains
542 * like on @mime_type apply.
543 * @http_code is final HTTP code returned by server. This can be 200, 401, 500
544 * or any other one. Pass NULL if you don't interest.
545 * In case of error, the response memory, MIME type, charset and length will be
546 * deallocated and zeroed automatically. Thus be sure they are preallocated or
547 * they points to NULL.
548 * @response_otp_headers is pre-allocated structure for OTP authentication
549 * headers sent by server. Members must be valid pointers or NULLs.
550 * Pass NULL if you don't interest.
551 * Be ware that successful return value does not mean the HTTP request has
552 * been accepted by the server. You must consult @http_code. OTOH, failure
553 * return value means the request could not been sent (e.g. SSL error).
554 * Side effect: message buffer */
555 static isds_error http(struct isds_ctx *context,
556 const char *url, _Bool use_get,
557 const void *request, const size_t request_length,
558 void **response, size_t *response_length,
559 char **mime_type, char **charset, long *http_code,
560 struct auth_headers *response_otp_headers) {
562 CURLcode curl_err;
563 isds_error err = IE_SUCCESS;
564 struct soap_body body;
565 char *content_type;
566 struct curl_slist *headers = NULL;
569 if (!context) return IE_INVALID_CONTEXT;
570 if (!url) return IE_INVAL;
571 if (request_length > 0 && !request) return IE_INVAL;
572 if (!response || !response_length) return IE_INVAL;
574 /* Clean authentication headers */
576 /* Set the body here to allow deallocation in leave block */
577 body.data = *response;
578 body.length = 0;
580 /* Set Request-URI */
581 curl_err = curl_easy_setopt(context->curl, CURLOPT_URL, url);
583 /* Set TLS options */
584 if (!curl_err && context->tls_verify_server) {
585 if (!*context->tls_verify_server)
586 isds_log(ILF_SEC, ILL_WARNING,
587 _("Disabling server identity verification. "
588 "That was your decision.\n"));
589 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSL_VERIFYPEER,
590 (*context->tls_verify_server)? 1L : 0L);
591 if (!curl_err) {
592 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSL_VERIFYHOST,
593 (*context->tls_verify_server)? 2L : 0L);
596 if (!curl_err && context->tls_ca_file) {
597 isds_log(ILF_SEC, ILL_INFO,
598 _("CA certificates will be searched in `%s' file since now\n"),
599 context->tls_ca_file);
600 curl_err = curl_easy_setopt(context->curl, CURLOPT_CAINFO,
601 context->tls_ca_file);
603 if (!curl_err && context->tls_ca_dir) {
604 isds_log(ILF_SEC, ILL_INFO,
605 _("CA certificates will be searched in `%s' directory "
606 "since now\n"), context->tls_ca_dir);
607 curl_err = curl_easy_setopt(context->curl, CURLOPT_CAPATH,
608 context->tls_ca_dir);
610 if (!curl_err && context->tls_crl_file) {
611 #if HAVE_DECL_CURLOPT_CRLFILE /* Since curl-7.19.0 */
612 isds_log(ILF_SEC, ILL_INFO,
613 _("CRLs will be searched in `%s' file since now\n"),
614 context->tls_crl_file);
615 curl_err = curl_easy_setopt(context->curl, CURLOPT_CRLFILE,
616 context->tls_crl_file);
617 #else
618 isds_log(ILF_SEC, ILL_WARNING,
619 _("Your curl library cannot pass certificate revocation "
620 "list to cryptographic library.\n"
621 "Make sure cryptographic library default setting "
622 "delivers proper CRLs,\n"
623 "or upgrade curl.\n"));
624 #endif /* not HAVE_DECL_CURLOPT_CRLFILE */
628 /* Set credentials */
629 #if HAVE_DECL_CURLOPT_USERNAME /* Since curl-7.19.1 */
630 if (!curl_err && context->username) {
631 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERNAME,
632 context->username);
634 if (!curl_err && context->password) {
635 curl_err = curl_easy_setopt(context->curl, CURLOPT_PASSWORD,
636 context->password);
638 #else
639 if (!curl_err && (context->username || context->password)) {
640 char *userpwd =
641 _isds_astrcat3(context->username, ":", context->password);
642 if (!userpwd) {
643 isds_log_message(context, _("Could not pass credentials to CURL"));
644 err = IE_NOMEM;
645 goto leave;
647 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERPWD, userpwd);
648 free(userpwd);
650 #endif /* not HAVE_DECL_CURLOPT_USERNAME */
652 /* Set PKI credentials */
653 if (!curl_err && (context->pki_credentials)) {
654 if (context->pki_credentials->engine) {
655 /* Select SSL engine */
656 isds_log(ILF_SEC, ILL_INFO,
657 _("Cryptographic engine `%s' will be used for "
658 "key or certificate\n"),
659 context->pki_credentials->engine);
660 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLENGINE,
661 context->pki_credentials->engine);
664 if (!curl_err) {
665 /* Select certificate format */
666 #if HAVE_DECL_CURLOPT_SSLCERTTYPE /* since curl-7.9.3 */
667 if (context->pki_credentials->certificate_format ==
668 PKI_FORMAT_ENG) {
669 /* XXX: It's valid to have certificate in engine without name.
670 * Engines can select certificate according private key and
671 * vice versa. */
672 if (context->pki_credentials->certificate)
673 isds_log(ILF_SEC, ILL_INFO, _("Client `%s' certificate "
674 "will be read from `%s' engine\n"),
675 context->pki_credentials->certificate,
676 context->pki_credentials->engine);
677 else
678 isds_log(ILF_SEC, ILL_INFO, _("Client certificate "
679 "will be read from `%s' engine\n"),
680 context->pki_credentials->engine);
681 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERTTYPE,
682 "ENG");
683 } else if (context->pki_credentials->certificate) {
684 isds_log(ILF_SEC, ILL_INFO, _("Client %s certificate "
685 "will be read from `%s' file\n"),
686 (context->pki_credentials->certificate_format ==
687 PKI_FORMAT_DER) ? _("DER") : _("PEM"),
688 context->pki_credentials->certificate);
689 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERTTYPE,
690 (context->pki_credentials->certificate_format ==
691 PKI_FORMAT_DER) ? "DER" : "PEM");
693 #else
694 if ((context->pki_credentials->certificate_format ==
695 PKI_FORMAT_ENG ||
696 context->pki_credentials->certificate))
697 isds_log(ILF_SEC, ILL_WARNING,
698 _("Your curl library cannot distinguish certificate "
699 "formats. Make sure your cryptographic library\n"
700 "understands your certificate file by default, "
701 "or upgrade curl.\n"));
702 #endif /* not HAVE_DECL_CURLOPT_SSLCERTTYPE */
705 if (!curl_err && context->pki_credentials->certificate) {
706 /* Select certificate */
707 if (!curl_err)
708 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLCERT,
709 context->pki_credentials->certificate);
712 if (!curl_err) {
713 /* Select key format */
714 if (context->pki_credentials->key_format == PKI_FORMAT_ENG) {
715 if (context->pki_credentials->key)
716 isds_log(ILF_SEC, ILL_INFO, _("Client private key `%s' "
717 "from `%s' engine will be used\n"),
718 context->pki_credentials->key,
719 context->pki_credentials->engine);
720 else
721 isds_log(ILF_SEC, ILL_INFO, _("Client private key "
722 "from `%s' engine will be used\n"),
723 context->pki_credentials->engine);
724 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEYTYPE,
725 "ENG");
726 } else if (context->pki_credentials->key) {
727 isds_log(ILF_SEC, ILL_INFO, _("Client %s private key will be "
728 "read from `%s' file\n"),
729 (context->pki_credentials->key_format ==
730 PKI_FORMAT_DER) ? _("DER") : _("PEM"),
731 context->pki_credentials->key);
732 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEYTYPE,
733 (context->pki_credentials->key_format ==
734 PKI_FORMAT_DER) ? "DER" : "PEM");
737 if (!curl_err)
738 /* Select key */
739 curl_err = curl_easy_setopt(context->curl, CURLOPT_SSLKEY,
740 context->pki_credentials->key);
742 if (!curl_err) {
743 /* Pass key pass-phrase */
744 #if HAVE_DECL_CURLOPT_KEYPASSWD /* since curl-7.16.5 */
745 curl_err = curl_easy_setopt(context->curl,
746 CURLOPT_KEYPASSWD,
747 context->pki_credentials->passphrase);
748 #elif HAVE_DECL_CURLOPT_SSLKEYPASSWD /* up to curl-7.16.4 */
749 curl_err = curl_easy_setopt(context->curl,
750 CURLOPT_SSLKEYPASSWD,
751 context->pki_credentials->passphrase);
752 #else /* up to curl-7.9.2 */
753 curl_err = curl_easy_setopt(context->curl,
754 CURLOPT_SSLCERTPASSWD,
755 context->pki_credentials->passphrase);
756 #endif
761 /* Set authorization cookie for OTP session */
762 if (!curl_err && context->otp) {
763 isds_log(ILF_SEC, ILL_INFO,
764 _("Cookies will be stored and sent "
765 "because context has been authorized by OTP.\n"));
766 curl_err = curl_easy_setopt(context->curl, CURLOPT_COOKIEFILE, "");
769 /* Set timeout */
770 if (!curl_err) {
771 curl_err = curl_easy_setopt(context->curl, CURLOPT_NOSIGNAL, 1);
773 if (!curl_err && context->timeout) {
774 #if HAVE_DECL_CURLOPT_TIMEOUT_MS /* Since curl-7.16.2 */
775 curl_err = curl_easy_setopt(context->curl, CURLOPT_TIMEOUT_MS,
776 context->timeout);
777 #else
778 curl_err = curl_easy_setopt(context->curl, CURLOPT_TIMEOUT,
779 context->timeout / 1000);
780 #endif /* not HAVE_DECL_CURLOPT_TIMEOUT_MS */
783 /* Register callback */
784 if (context->progress_callback) {
785 if (!curl_err) {
786 curl_err = curl_easy_setopt(context->curl, CURLOPT_NOPROGRESS, 0);
788 if (!curl_err) {
789 curl_err = curl_easy_setopt(context->curl,
790 CURLOPT_PROGRESSFUNCTION, progress_proxy);
792 if (!curl_err) {
793 curl_err = curl_easy_setopt(context->curl, CURLOPT_PROGRESSDATA,
794 context);
798 /* Set other CURL features */
799 if (!curl_err) {
800 curl_err = curl_easy_setopt(context->curl, CURLOPT_FAILONERROR, 0);
803 /* Set get-response function */
804 if (!curl_err) {
805 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEFUNCTION,
806 write_body);
808 if (!curl_err) {
809 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEDATA, &body);
812 /* Set get-response-headers function if needed.
813 * XXX: Both CURLOPT_HEADERFUNCTION and CURLOPT_WRITEHEADER must be set or
814 * unset at the same time (see curl_easy_setopt(3)) ASAP, otherwise old
815 * invalid CURLOPT_WRITEHEADER value could be derefenced. */
816 if (!curl_err) {
817 curl_err = curl_easy_setopt(context->curl, CURLOPT_HEADERFUNCTION,
818 (response_otp_headers == NULL) ? NULL: write_header);
820 if (!curl_err) {
821 curl_err = curl_easy_setopt(context->curl, CURLOPT_WRITEHEADER,
822 response_otp_headers);
825 /* Set MIME types and headers requires by SOAP 1.1.
826 * SOAP 1.1 requires text/xml, SOAP 1.2 requires application/soap+xml */
827 if (!curl_err) {
828 headers = curl_slist_append(headers,
829 "Accept: application/soap+xml,application/xml,text/xml");
830 if (!headers) {
831 err = IE_NOMEM;
832 goto leave;
834 headers = curl_slist_append(headers, "Content-Type: text/xml");
835 if (!headers) {
836 err = IE_NOMEM;
837 goto leave;
839 headers = curl_slist_append(headers, "SOAPAction: ");
840 if (!headers) {
841 err = IE_NOMEM;
842 goto leave;
844 curl_err = curl_easy_setopt(context->curl, CURLOPT_HTTPHEADER, headers);
846 if (!curl_err) {
847 /* Set user agent identification */
848 curl_err = curl_easy_setopt(context->curl, CURLOPT_USERAGENT,
849 "libisds/" PACKAGE_VERSION);
852 if (use_get) {
853 /* Set GET request */
854 if (!curl_err) {
855 curl_err = curl_easy_setopt(context->curl, CURLOPT_HTTPGET, 1);
857 } else {
858 /* Set POST request body */
859 if (!curl_err) {
860 curl_err = curl_easy_setopt(context->curl, CURLOPT_POST, 1);
862 if (!curl_err) {
863 curl_err = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDS, request);
865 if (!curl_err) {
866 curl_err = curl_easy_setopt(context->curl, CURLOPT_POSTFIELDSIZE,
867 request_length);
871 /* Check for errors so far */
872 if (curl_err) {
873 isds_log_message(context, curl_easy_strerror(curl_err));
874 err = IE_NETWORK;
875 goto leave;
878 isds_log(ILF_HTTP, ILL_DEBUG, _("Sending %s request to <%s>\n"),
879 use_get ? "GET" : "POST", url);
880 if (!use_get) {
881 isds_log(ILF_HTTP, ILL_DEBUG,
882 _("POST body length: %zu, content follows:\n"), request_length);
883 isds_log(ILF_HTTP, ILL_DEBUG, "%.*s\n", request_length, request);
884 isds_log(ILF_HTTP, ILL_DEBUG, _("End of POST body\n"));
886 if ((log_facilities & ILF_HTTP) && (log_level >= ILL_DEBUG) ) {
887 curl_easy_setopt(context->curl, CURLOPT_VERBOSE, 1);
888 curl_easy_setopt(context->curl, CURLOPT_DEBUGFUNCTION, log_curl);
889 } else {
890 curl_easy_setopt(context->curl, CURLOPT_VERBOSE, 0);
891 curl_easy_setopt(context->curl, CURLOPT_DEBUGFUNCTION, NULL);
895 /* Do the request */
896 curl_err = curl_easy_perform(context->curl);
898 if (!curl_err)
899 curl_err = curl_easy_getinfo(context->curl, CURLINFO_CONTENT_TYPE,
900 &content_type);
902 if (curl_err) {
903 /* TODO: CURL is not internationalized yet. Collect CURL messages for
904 * I18N. */
905 isds_printf_message(context,
906 _("%s: %s"), url, _(curl_easy_strerror(curl_err)));
907 if (curl_err == CURLE_ABORTED_BY_CALLBACK)
908 err = IE_ABORTED;
909 else
910 err = IE_NETWORK;
911 goto leave;
914 isds_log(ILF_HTTP, ILL_DEBUG, _("Final response to %s received\n"), url);
915 isds_log(ILF_HTTP, ILL_DEBUG,
916 _("Response body length: %zu, content follows:\n"),
917 body.length);
918 isds_log(ILF_HTTP, ILL_DEBUG, "%.*s\n", body.length, body.data);
919 isds_log(ILF_HTTP, ILL_DEBUG, _("End of response body\n"));
922 /* Extract MIME type and charset */
923 if (content_type) {
924 char *sep;
925 size_t offset;
927 sep = strchr(content_type, ';');
928 if (sep) offset = (size_t) (sep - content_type);
929 else offset = strlen(content_type);
931 if (mime_type) {
932 *mime_type = malloc(offset + 1);
933 if (!*mime_type) {
934 err = IE_NOMEM;
935 goto leave;
937 memcpy(*mime_type, content_type, offset);
938 (*mime_type)[offset] = '\0';
941 if (charset) {
942 if (!sep) {
943 *charset = NULL;
944 } else {
945 sep = strstr(sep, "charset=");
946 if (!sep) {
947 *charset = NULL;
948 } else {
949 *charset = strdup(sep + 8);
950 if (!*charset) {
951 err = IE_NOMEM;
952 goto leave;
959 /* Get HTTP response code */
960 if (http_code) {
961 curl_err = curl_easy_getinfo(context->curl,
962 CURLINFO_RESPONSE_CODE, http_code);
963 if (curl_err) {
964 err = IE_ERROR;
965 goto leave;
969 /* Store OTP authentication results */
970 if (response_otp_headers && response_otp_headers->is_complete) {
971 isds_log(ILF_SEC, ILL_DEBUG,
972 _("OTP authentication headers received: "
973 "method=%s, code=%s, message=%s\n"),
974 response_otp_headers->method, response_otp_headers->code,
975 response_otp_headers->message);
977 /* XXX: Don't make unknown code fatal. Missing code can be succcess if
978 * HTTP code is 302. This is checked in _isds_soap(). */
979 response_otp_headers->resolution =
980 string2isds_otp_resolution(response_otp_headers->code);
982 if (response_otp_headers->message != NULL) {
983 char *message_locale = _isds_utf82locale(response_otp_headers->message);
984 /* _isds_utf82locale() return NULL on inconverable string. Do not
985 * panic on it.
986 * TODO: Escape such characters.
987 * if (message_locale == NULL) {
988 err = IE_NOMEM;
989 goto leave;
991 isds_printf_message(context,
992 _("Server returned OTP authentication message: %s"),
993 message_locale);
994 free(message_locale);
997 char *next_url = NULL; /* Weak pointer managed by cURL */
998 curl_err = curl_easy_getinfo(context->curl, CURLINFO_REDIRECT_URL,
999 &next_url);
1000 if (curl_err) {
1001 err = IE_ERROR;
1002 goto leave;
1004 if (next_url != NULL) {
1005 isds_log(ILF_SEC, ILL_DEBUG,
1006 _("OTP authentication headers redirect to: <%s>\n"),
1007 next_url);
1008 free(response_otp_headers->redirect);
1009 response_otp_headers->redirect = strdup(next_url);
1010 if (response_otp_headers->redirect == NULL) {
1011 err = IE_NOMEM;
1012 goto leave;
1016 leave:
1017 curl_slist_free_all(headers);
1019 if (err) {
1020 free(body.data);
1021 body.data = NULL;
1022 body.length = 0;
1024 if (mime_type) {
1025 free(*mime_type);
1026 *mime_type = NULL;
1028 if (charset) {
1029 free(*charset);
1030 *charset = NULL;
1033 if (err != IE_ABORTED) _isds_close_connection(context);
1036 *response = body.data;
1037 *response_length = body.length;
1039 return err;
1043 /* Do SOAP request.
1044 * @context holds the base URL,
1045 * @file is a (CGI) file of SOAP URL,
1046 * @request is XML node set with SOAP request body.
1047 * @file must be NULL, @request should be NULL rather than empty, if they should
1048 * not be signaled in the SOAP request.
1049 * @reponse is automatically allocated() node set with SOAP response body.
1050 * You must xmlFreeNodeList() it. This is literal body, empty (NULL), one node
1051 * or more nodes can be returned.
1052 * @raw_response is automatically allocated bit stream with response body. Use
1053 * NULL if you don't care
1054 * @raw_response_length is size of @raw_response in bytes
1055 * In case of error the response will be deallocated automatically.
1056 * Side effect: message buffer */
1057 _hidden isds_error _isds_soap(struct isds_ctx *context, const char *file,
1058 const xmlNodePtr request, xmlNodePtr *response,
1059 void **raw_response, size_t *raw_response_length) {
1061 isds_error err = IE_SUCCESS;
1062 char *url = NULL;
1063 char *mime_type = NULL;
1064 long http_code = 0;
1065 struct auth_headers response_otp_headers;
1066 xmlBufferPtr http_request = NULL;
1067 xmlSaveCtxtPtr save_ctx = NULL;
1068 xmlDocPtr request_soap_doc = NULL;
1069 xmlNodePtr request_soap_envelope = NULL, request_soap_body = NULL;
1070 xmlNsPtr soap_ns = NULL;
1071 void *http_response = NULL;
1072 size_t response_length = 0;
1073 xmlDocPtr response_soap_doc = NULL;
1074 xmlNodePtr response_root = NULL;
1075 xmlXPathContextPtr xpath_ctx = NULL;
1076 xmlXPathObjectPtr response_soap_headers = NULL, response_soap_body = NULL,
1077 response_soap_fault = NULL;
1080 if (!context) return IE_INVALID_CONTEXT;
1081 if (!response) return IE_INVAL;
1082 if (!raw_response_length && raw_response) return IE_INVAL;
1084 xmlFreeNodeList(*response);
1085 *response = NULL;
1086 if (raw_response) *raw_response = NULL;
1088 url = _isds_astrcat(context->url, file);
1089 if (!url) return IE_NOMEM;
1091 /* Build SOAP request envelope */
1092 request_soap_doc = xmlNewDoc(BAD_CAST "1.0");
1093 if (!request_soap_doc) {
1094 isds_log_message(context, _("could not build soap request document"));
1095 err = IE_ERROR;
1096 goto leave;
1098 request_soap_envelope = xmlNewNode(NULL, BAD_CAST "Envelope");
1099 if (!request_soap_envelope) {
1100 isds_log_message(context, _("Could not build SOAP request envelope"));
1101 err = IE_ERROR;
1102 goto leave;
1104 xmlDocSetRootElement(request_soap_doc, request_soap_envelope);
1105 /* Only this way we get namespace definition as @xmlns:soap,
1106 * otherwise we get namespace prefix without definition */
1107 soap_ns = xmlNewNs(request_soap_envelope, BAD_CAST SOAP_NS, NULL);
1108 if(!soap_ns) {
1109 isds_log_message(context, _("Could not create SOAP name space"));
1110 err = IE_ERROR;
1111 goto leave;
1113 xmlSetNs(request_soap_envelope, soap_ns);
1114 request_soap_body = xmlNewChild(request_soap_envelope, NULL,
1115 BAD_CAST "Body", NULL);
1116 if (!request_soap_body) {
1117 isds_log_message(context,
1118 _("Could not add Body to SOAP request envelope"));
1119 err = IE_ERROR;
1120 goto leave;
1123 /* Append request XML node set to SOAP body if request is not empty */
1124 /* XXX: Copy of request must be used, otherwise xmlFreeDoc(request_soap_doc)
1125 * would destroy this outer structure. */
1126 if (request) {
1127 xmlNodePtr request_copy = xmlCopyNodeList(request);
1128 if (!request_copy) {
1129 isds_log_message(context,
1130 _("Could not copy request content"));
1131 err = IE_ERROR;
1132 goto leave;
1134 if (!xmlAddChildList(request_soap_body, request_copy)) {
1135 xmlFreeNodeList(request_copy);
1136 isds_log_message(context,
1137 _("Could not add request content to SOAP "
1138 "request envelope"));
1139 err = IE_ERROR;
1140 goto leave;
1145 /* Serialize the SOAP request into HTTP request body */
1146 http_request = xmlBufferCreate();
1147 if (!http_request) {
1148 isds_log_message(context,
1149 _("Could not create xmlBuffer for HTTP request body"));
1150 err = IE_ERROR;
1151 goto leave;
1153 /* Last argument 1 means format the XML tree. This is pretty but it breaks
1154 * XML document transport as it adds text nodes (indentiation) between
1155 * elements. */
1156 save_ctx = xmlSaveToBuffer(http_request, "UTF-8", 0);
1157 if (!save_ctx) {
1158 isds_log_message(context,
1159 _("Could not create XML serializer"));
1160 err = IE_ERROR;
1161 goto leave;
1163 /* XXX: According LibXML documentation, this function does not return
1164 * meaningful value yet */
1165 xmlSaveDoc(save_ctx, request_soap_doc);
1166 if (-1 == xmlSaveFlush(save_ctx)) {
1167 isds_log_message(context,
1168 _("Could not serialize SOAP request to HTTP request body"));
1169 err = IE_ERROR;
1170 goto leave;
1173 if (context->otp_credentials != NULL)
1174 memset(&response_otp_headers, 0, sizeof(response_otp_headers));
1175 redirect:
1176 if (context->otp_credentials != NULL)
1177 auth_headers_free(&response_otp_headers);
1178 isds_log(ILF_SOAP, ILL_DEBUG,
1179 _("SOAP request to sent to %s:\n%.*s\nEnd of SOAP request\n"),
1180 url, http_request->use, http_request->content);
1182 err = http(context, url, 0, http_request->content, http_request->use,
1183 &http_response, &response_length,
1184 &mime_type, NULL, &http_code,
1185 (context->otp_credentials == NULL) ? NULL: &response_otp_headers);
1187 /* TODO: HTTP binding for SOAP prescribes non-200 HTTP return codes
1188 * to be processed too. */
1190 if (err) {
1191 goto leave;
1194 if (NULL != context->otp_credentials)
1195 context->otp_credentials->resolution = response_otp_headers.resolution;
1197 /* Check for HTTP return code */
1198 isds_log(ILF_SOAP, ILL_DEBUG, _("Server returned %ld HTTP code\n"),
1199 http_code);
1200 switch (http_code) {
1201 /* XXX: We must see which code is used for not permitted ISDS
1202 * operation like downloading message without proper user
1203 * permissions. In that case we should keep connection opened. */
1204 case 200:
1205 if (NULL != context->otp_credentials) {
1206 if (context->otp_credentials->resolution ==
1207 OTP_RESOLUTION_UNKNOWN)
1208 context->otp_credentials->resolution =
1209 OTP_RESOLUTION_SUCCESS;
1211 break;
1212 case 302:
1213 if (NULL != context->otp_credentials) {
1214 if (context->otp_credentials->resolution ==
1215 OTP_RESOLUTION_UNKNOWN)
1216 context->otp_credentials->resolution =
1217 OTP_RESOLUTION_SUCCESS;
1218 err = IE_PARTIAL_SUCCESS;
1219 isds_printf_message(context,
1220 _("Server redirects on <%s> because OTP authentication "
1221 "succeeded."),
1222 url);
1223 if (context->otp_credentials->otp_code != NULL &&
1224 response_otp_headers.redirect != NULL) {
1225 /* XXX: If OTP code is known, this must be second OTP phase, so
1226 * send final POST request and unset Basic authentication
1227 * from cURL context as cookie is used instead. */
1228 free(url);
1229 url = response_otp_headers.redirect;
1230 response_otp_headers.redirect = NULL;
1231 _isds_discard_credentials(context, 0);
1232 err = unset_http_authorization(context);
1233 if (err) {
1234 isds_log_message(context, _("Could not remove "
1235 "credentials from CURL handle."));
1236 goto leave;
1238 goto redirect;
1239 } else {
1240 /* XXX: Otherwise bail out to ask application for OTP code. */
1241 goto leave;
1243 } else {
1244 err = IE_HTTP;
1245 isds_printf_message(context,
1246 _("Code 302: Server redirects on <%s> request. "
1247 "Redirection is forbidden in stateless mode."),
1248 url);
1249 goto leave;
1251 break;
1252 case 401: /* ISDS server returns 401 even if Authorization
1253 presents. */
1254 case 403: /* HTTP/1.0 prescribes 403 if Authorization presents. */
1255 err = IE_NOT_LOGGED_IN;
1256 isds_log_message(context, _("Authentication failed"));
1257 goto leave;
1258 break;
1259 case 404:
1260 err = IE_HTTP;
1261 isds_printf_message(context,
1262 _("Code 404: Document (%s) not found on server"), url);
1263 goto leave;
1264 break;
1265 /* 500 should return standard SOAP message */
1268 /* Check for Content-Type: text/xml.
1269 * Do it after HTTP code check because 401 Unauthorized returns HTML web
1270 * page for browsers. */
1271 if (mime_type && strcmp(mime_type, "text/xml")
1272 && strcmp(mime_type, "application/soap+xml")
1273 && strcmp(mime_type, "application/xml")) {
1274 char *mime_type_locale = _isds_utf82locale(mime_type);
1275 isds_printf_message(context,
1276 _("%s: bad MIME type sent by server: %s"), url,
1277 mime_type_locale);
1278 free(mime_type_locale);
1279 err = IE_SOAP;
1280 goto leave;
1283 /* TODO: Convert returned body into XML default encoding */
1285 /* Parse the HTTP body as XML */
1286 response_soap_doc = xmlParseMemory(http_response, response_length);
1287 if (!response_soap_doc) {
1288 err = IE_XML;
1289 goto leave;
1292 xpath_ctx = xmlXPathNewContext(response_soap_doc);
1293 if (!xpath_ctx) {
1294 err = IE_ERROR;
1295 goto leave;
1298 if (_isds_register_namespaces(xpath_ctx, MESSAGE_NS_UNSIGNED)) {
1299 err = IE_ERROR;
1300 goto leave;
1303 isds_log(ILF_SOAP, ILL_DEBUG,
1304 _("SOAP response received:\n%.*s\nEnd of SOAP response\n"),
1305 response_length, http_response);
1308 /* Check for SOAP version */
1309 response_root = xmlDocGetRootElement(response_soap_doc);
1310 if (!response_root) {
1311 isds_log_message(context, "SOAP response has no root element");
1312 err = IE_SOAP;
1313 goto leave;
1315 if (xmlStrcmp(response_root->name, BAD_CAST "Envelope") ||
1316 xmlStrcmp(response_root->ns->href, BAD_CAST SOAP_NS)) {
1317 isds_log_message(context, "SOAP response is not SOAP 1.1 document");
1318 err = IE_SOAP;
1319 goto leave;
1322 /* Check for SOAP Headers */
1323 response_soap_headers = xmlXPathEvalExpression(
1324 BAD_CAST "/soap:Envelope/soap:Header/"
1325 "*[@soap:mustUnderstand/text() = true()]", xpath_ctx);
1326 if (!response_soap_headers) {
1327 err = IE_ERROR;
1328 goto leave;
1330 if (!xmlXPathNodeSetIsEmpty(response_soap_headers->nodesetval)) {
1331 isds_log_message(context,
1332 _("SOAP response requires unsupported feature"));
1333 /* TODO: log the headers
1334 * xmlChar *fragment = NULL;
1335 * fragment = xmlXPathCastNodeSetToSting(response_soap_headers->nodesetval);*/
1336 err = IE_NOTSUP;
1337 goto leave;
1340 /* Get SOAP Body */
1341 response_soap_body = xmlXPathEvalExpression(
1342 BAD_CAST "/soap:Envelope/soap:Body", xpath_ctx);
1343 if (!response_soap_body) {
1344 err = IE_ERROR;
1345 goto leave;
1347 if (xmlXPathNodeSetIsEmpty(response_soap_body->nodesetval)) {
1348 isds_log_message(context,
1349 _("SOAP response does not contain SOAP Body element"));
1350 err = IE_SOAP;
1351 goto leave;
1353 if (response_soap_body->nodesetval->nodeNr > 1) {
1354 isds_log_message(context,
1355 _("SOAP response has more than one Body element"));
1356 err = IE_SOAP;
1357 goto leave;
1360 /* Check for SOAP Fault */
1361 response_soap_fault = xmlXPathEvalExpression(
1362 BAD_CAST "/soap:Envelope/soap:Body/soap:Fault", xpath_ctx);
1363 if (!response_soap_fault) {
1364 err = IE_ERROR;
1365 goto leave;
1367 if (!xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
1368 /* Server signals Fault. Gather error message and croak. */
1369 /* XXX: Only first message is passed */
1370 char *message = NULL, *message_locale = NULL;
1371 xpath_ctx->node = response_soap_fault->nodesetval->nodeTab[0];
1372 xmlXPathFreeObject(response_soap_fault);
1373 /* XXX: faultstring and faultcode are in no name space according
1374 * ISDS specification */
1375 /* First more verbose faultstring */
1376 response_soap_fault = xmlXPathEvalExpression(
1377 BAD_CAST "faultstring[1]/text()", xpath_ctx);
1378 if (response_soap_fault &&
1379 !xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
1380 message = (char *)
1381 xmlXPathCastNodeSetToString(response_soap_fault->nodesetval);
1382 message_locale = _isds_utf82locale(message);
1384 /* If not available, try shorter faultcode */
1385 if (!message_locale) {
1386 free(message);
1387 xmlXPathFreeObject(response_soap_fault);
1388 response_soap_fault = xmlXPathEvalExpression(
1389 BAD_CAST "faultcode[1]/text()", xpath_ctx);
1390 if (response_soap_fault &&
1391 !xmlXPathNodeSetIsEmpty(response_soap_fault->nodesetval)) {
1392 message = (char *)
1393 xmlXPathCastNodeSetToString(
1394 response_soap_fault->nodesetval);
1395 message_locale = _isds_utf82locale(message);
1399 /* Croak */
1400 if (message_locale)
1401 isds_printf_message(context, _("SOAP response signals Fault: %s"),
1402 message_locale);
1403 else
1404 isds_log_message(context, _("SOAP response signals Fault"));
1406 free(message_locale);
1407 free(message);
1409 err = IE_SOAP;
1410 goto leave;
1414 /* Extract XML Tree with ISDS response from SOAP envelope and return it.
1415 * XXX: response_soap_body is Body, we need children which may not exist
1416 * (i.e. empty Body). */
1417 /* TODO: Destroy SOAP response but Body children. This is more memory
1418 * friendly than copying (potentially) fat body */
1419 if (response_soap_body->nodesetval->nodeTab[0]->children) {
1420 *response = xmlDocCopyNodeList(response_soap_doc,
1421 response_soap_body->nodesetval->nodeTab[0]->children);
1422 if (!*response) {
1423 err = IE_NOMEM;
1424 goto leave;
1426 } else *response = NULL;
1428 /* Save raw response */
1429 if (raw_response) {
1430 *raw_response = http_response;
1431 *raw_response_length = response_length;
1432 http_response = NULL;
1436 leave:
1437 if (err) {
1438 xmlFreeNodeList(*response);
1439 *response = NULL;
1442 xmlXPathFreeObject(response_soap_fault);
1443 xmlXPathFreeObject(response_soap_body);
1444 xmlXPathFreeObject(response_soap_headers);
1445 xmlXPathFreeContext(xpath_ctx);
1446 xmlFreeDoc(response_soap_doc);
1447 if (context->otp_credentials != NULL)
1448 auth_headers_free(&response_otp_headers);
1449 free(mime_type);
1450 free(http_response);
1451 xmlSaveClose(save_ctx);
1452 xmlBufferFree(http_request);
1453 xmlFreeDoc(request_soap_doc); /* recursive, frees request_body, soap_ns*/
1454 free(url);
1456 return err;
1460 /* Build new URL from current @context and template.
1461 * @context is context carrying an URL
1462 * @template is printf(3) format string. First argument is string of base URL
1463 * found in @context, second argument is length of the base URL.
1464 * @new_url is newly allocated URL built from @template. Caller must free it.
1465 * Return IE_SUCCESS, or corresponding error code and @new_url will not be
1466 * allocated.
1467 * */
1468 _hidden isds_error _isds_build_url_from_context(struct isds_ctx *context,
1469 const char *template, char **new_url) {
1470 int length, slashes;
1472 if (NULL != new_url) *new_url = NULL;
1473 if (NULL == context) return IE_INVALID_CONTEXT;
1474 if (NULL == template) return IE_INVAL;
1475 if (NULL == new_url) return IE_INVAL;
1477 /* Find length of base URL from context URL */
1478 if (NULL == context->url) {
1479 isds_log_message(context, _("Base URL could not have been determined "
1480 "from context URL because there was no URL set in the "
1481 "context"));
1482 return IE_ERROR;
1484 for (length = 0, slashes = 0; context->url[length] != '\0'; length++) {
1485 if (context->url[length] == '/') slashes++;
1486 if (slashes == 3) break;
1488 if (slashes != 3) {
1489 isds_log_message(context, _("Base URL could not have been determined "
1490 "from context URL"));
1491 return IE_ERROR;
1493 length++;
1495 /* Build new URL */
1496 if (-1 == isds_asprintf(new_url, template, context->url, length))
1497 return IE_NOMEM;
1499 return IE_SUCCESS;
1503 /* Invalidate session cookie for otp authenticated @context */
1504 _hidden isds_error _isds_invalidate_otp_cookie(struct isds_ctx *context) {
1505 isds_error err;
1506 char *url = NULL;
1507 long http_code;
1508 void *response = NULL;
1509 size_t response_length;
1511 if (context == NULL || !context->otp) return IE_INVALID_CONTEXT;
1512 if (context->curl == NULL) return IE_CONNECTION_CLOSED;
1514 /* Build logout URL */
1515 /*"https://DOMAINNAME/as/processLogout?uri=https://DOMAINNAME/apps/DS/WEB_SERVICE_ENDPOINT"*/
1516 err = _isds_build_url_from_context(context,
1517 "%1$.*2$sas/processLogout?uri=%1$sDS/dz", &url);
1518 if (err) return err;
1520 /* Invalidate the cookie by GET request */
1521 err = http(context,
1522 url, 1,
1523 NULL, 0,
1524 &response, &response_length,
1525 NULL, NULL, &http_code,
1526 NULL);
1527 free(response);
1528 free(url);
1529 if (err) {
1530 /* long message set by http() */
1531 } else if (http_code != 200) {
1532 /* TODO: Specification does not define response for this request.
1533 * Especially it does not state whether direct 200 or 302 redirect is
1534 * sent. We need to check real implementation. */
1535 err = IE_ISDS;
1536 isds_printf_message(context, _("Cookie for OTP authenticated "
1537 "connection to <%s> could not been invalidated"),
1538 context->url);
1539 } else {
1540 isds_log(ILF_SEC, ILL_DEBUG, _("Cookie for OTP authenticated "
1541 "connection to <%s> has been invalidated.\n"),
1542 context->url);
1544 return err;
1548 /* LibXML functions:
1550 * void xmlInitParser(void)
1551 * Initialization function for the XML parser. This is not reentrant. Call
1552 * once before processing in case of use in multithreaded programs.
1554 * int xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
1555 * Initialize a parser context
1557 * xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar * cur,
1558 * const * char URL, const char * encoding, int options);
1559 * Parse in-memory NULL-terminated document @cur.
1561 * xmlDocPtr xmlParseMemory(const char * buffer, int size)
1562 * Parse an XML in-memory block and build a tree.
1564 * xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char * buffer, int
1565 * size);
1566 * Create a parser context for an XML in-memory document.
1568 * xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar * cur)
1569 * Creates a parser context for an XML in-memory document.
1571 * xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt,
1572 * const char * buffer, int size, const char * URL, const char * encoding,
1573 * int options)
1574 * Parse an XML in-memory document and build a tree. This reuses the existing
1575 * @ctxt parser context.
1577 * void xmlCleanupParser(void)
1578 * Cleanup function for the XML library. It tries to reclaim all parsing
1579 * related glob document related memory. Calling this function should not
1580 * prevent reusing the libr finished using the library or XML document built
1581 * with it.
1583 * void xmlClearParserCtxt(xmlParserCtxtPtr ctxt)
1584 * Clear (release owned resources) and reinitialize a parser context.
1586 * void xmlCtxtReset(xmlParserCtxtPtr ctxt)
1587 * Reset a parser context
1589 * void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)
1590 * Free all the memory used by a parser context. However the parsed document
1591 * in ctxt->myDoc is not freed.
1593 * void xmlFreeDoc(xmlDocPtr cur)
1594 * Free up all the structures used by a document, tree included.