6 #include <strings.h> /* strncasecmp(3) */
9 /* Private structure for write_body() call back */
15 /* Private structure for write_header() call back */
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
);
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
) {
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
;
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
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
,
71 const char *charset_start
, *encoding
, *end
;
72 size_t charset_length
;
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
)
82 if (encoded
[0] != '=' || encoded
[1] != '?')
85 /* Then is "CHARSET?" */
86 charset_start
= (encoded
+= 2);
87 while (*encoded
!= '?') {
90 if (*encoded
== ' ' || *encoded
== '\t' || *encoded
== '\r' || *encoded
== '\n')
96 /* Then is "ENCODING?", where ENCODING is /[BbQq]/ */
97 if (*encoded
== '\0') return -1;
103 /* Then is "ENCODED_TEXT?=" */
104 while (*encoded
!= '?') {
105 if (*encoded
== '\0')
107 if (*encoded
== ' ' || *encoded
== '\t' || *encoded
== '\r' || *encoded
== '\n')
112 if (*(++encoded
) != '=') return -1;
115 * "=?CHARSET?E?ENCODED_TEXT?="
124 charset_length
= encoding
- charset_start
- 1;
125 if (charset_length
< 1)
127 charset
= strndup(charset_start
, charset_length
);
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') {
138 char *b64_stream
= NULL
;
139 if (NULL
== (b64_stream
=
140 malloc((encoding_length
+ 1) * sizeof(*encoding
)))) {
144 memcpy(b64_stream
, encoding
+ 2, encoding_length
);
145 b64_stream
[encoding_length
] = '\0';
146 bit_length
= _isds_b64decode(b64_stream
, (void **)&bit_stream
);
148 if (bit_length
== (size_t) -1) {
152 } else if (*encoding
== 'Q') {
153 /* Decode Quoted-printable-like */
154 if (NULL
== (bit_stream
=
155 malloc((encoding_length
) * sizeof(*encoding
)))) {
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
] == '=') {
164 /* Validate "=HH", where H is hexadecimal digit */
165 if (q
+ 2 >= encoding_length
+ 2 ) {
171 if ((ordinar
= _isds_hex2i(encoding
[++q
])) < 0) {
176 bit_stream
[bit_length
] = (ordinar
<< 4);
177 if ((ordinar
= _isds_hex2i(encoding
[++q
])) < 0) {
182 bit_stream
[bit_length
] += ordinar
;
184 bit_stream
[bit_length
] = encoding
[q
];
189 /* Unknown encoding */
194 /* Convert to UTF-8 */
195 char *utf_stream
= NULL
;
197 utf_length
= _isds_any2any(charset
, "UTF-8", bit_stream
, bit_length
,
198 (void **)&utf_stream
);
201 if (utf_length
== (size_t) -1) {
205 /* Copy UTF-8 stream to output buffer */
210 memcpy(*output
, utf_stream
, utf_length
);
212 *output
+= utf_length
;
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
) {
239 /* RFC 2616, section 4.2: Remove surrounding LWS, replace inner ones with
241 /* RFC 2047, section 6.2: LWS between adjacent encoded words is ignored.
243 for (decoded_cursor
= decoded
; *encoded_value
; encoded_value
++) {
244 if (*encoded_value
== '\r' || *encoded_value
== '\n' ||
245 *encoded_value
== '\t' || *encoded_value
== ' ') {
249 if (*encoded_value
== '=' &&
251 lws_seen
&& text_started
&& !encoded_word_seen
,
252 &encoded_value
, &decoded_cursor
)) {
253 encoded_word_seen
= 1;
255 if (lws_seen
&& text_started
)
256 *(decoded_cursor
++) = ' ';
257 *(decoded_cursor
++) = *encoded_value
;
258 encoded_word_seen
= 0;
263 *decoded_cursor
= '\0';
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;
283 if (!strncmp(server_method, method_name, 4) && (
284 server_method[4] == '\0' || server_method[4] == ' ' ||
285 server_method[4] == '\t'))
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
) {
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
;
312 return OTP_RESOLUTION_UNKNOWN
;
316 /* Close connection to server and destroy CURL handle associated
318 _hidden isds_error
_isds_close_connection(struct isds_ctx
*context
) {
319 if (!context
) return IE_INVALID_CONTEXT
;
322 curl_easy_cleanup(context
->curl
);
323 context
->curl
= NULL
;
324 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Connection to server %s closed\n"),
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
))
343 if (curl_easy_setopt(context
->curl
, CURLOPT_PASSWORD
, NULL
))
346 if (curl_easy_setopt(context
->curl
, CURLOPT_USERPWD
, NULL
))
348 #endif /* not HAVE_DECL_CURLOPT_USERNAME */
351 isds_log(ILF_HTTP
, ILL_ERR
, _("Error while unsetting user name and "
352 "password from CURL handle for connection to server %s.\n"),
355 isds_log(ILF_HTTP
, ILL_DEBUG
, _("User name and password for server %s "
356 "have been unset from CURL handle.\n"), context
->url
);
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
;
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
;
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 */
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
) {
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
;
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 "
437 /* Decode last header */
438 value
= header_value(headers
->last_header
, "WWW-Authenticate");
440 free(headers
->method
);
441 if (NULL
== (headers
->method
= decode_header_value(value
))) {
442 /* TODO: Set IE_NOMEM to context */
448 value
= header_value(headers
->last_header
, "X-Response-message-code");
451 if (NULL
== (headers
->code
= decode_header_value(value
))) {
452 /* TODO: Set IE_NOMEM to context */
458 value
= header_value(headers
->last_header
, "X-Response-message-text");
460 free(headers
->message
);
461 if (NULL
== (headers
->message
= decode_header_value(value
))) {
462 /* TODO: Set IE_NOMEM to context */
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;
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 */
483 memcpy(headers
->last_header
, buffer
, length
);
484 headers
->last_header
[length
] = '\0';
492 /* CURL progress callback proxy to rearrange arguments.
493 * @curl_data is session context */
494 static int progress_proxy(void *curl_data
,
495 #if HAVE_DECL_CURLOPT_XFERINFOFUNCTION /* Since curl-7.32.0 */
496 curl_off_t download_total
, curl_off_t download_current
,
497 curl_off_t upload_total
, curl_off_t upload_current
499 double download_total
, double download_current
,
500 double upload_total
, double upload_current
501 #endif /* not HAVE_DECL_CURLOPT_XFERINFOFUNCTION */
503 struct isds_ctx
*context
= (struct isds_ctx
*) curl_data
;
506 if (context
&& context
->progress_callback
) {
507 abort
= context
->progress_callback(
508 upload_total
, upload_current
,
509 download_total
, download_current
,
510 context
->progress_callback_data
);
512 isds_log(ILF_HTTP
, ILL_INFO
,
513 _("Application aborted HTTP transfer"));
521 /* CURL call back function called when curl has something to log.
522 * @curl is cURL context
523 * @type is cURL log facility
524 * @buffer points to log data, XXX: not zero-terminated
525 * @size is length of log data
526 * @userp is private structure.
528 static int log_curl(CURL
*curl
, curl_infotype type
, char *buffer
, size_t size
,
530 /* Silent warning about usused arguments.
531 * This prototype is cURL's debug_callback type. */
535 if (!buffer
|| 0 == size
) return 0;
536 if (type
== CURLINFO_TEXT
|| type
== CURLINFO_HEADER_IN
||
537 type
== CURLINFO_HEADER_OUT
)
538 isds_log(ILF_HTTP
, ILL_DEBUG
, "%*s", size
, buffer
);
544 * @context holds the base URL,
545 * @url is a (CGI) file of SOAP URL,
546 * @use_get is a false to do a POST request, true to do a GET request.
547 * @request is body for POST request
548 * @request_length is length of @request in bytes
549 * @reponse is automatically reallocated() buffer to fit HTTP response with
550 * @response_length (does not need to match allocated memory exactly). You must
551 * free() the @response.
552 * @mime_type is automatically allocated MIME type send by server (*NULL if not
553 * sent). Set NULL if you don't care.
554 * @charset is charset of the body signaled by server. The same constrains
555 * like on @mime_type apply.
556 * @http_code is final HTTP code returned by server. This can be 200, 401, 500
557 * or any other one. Pass NULL if you don't interest.
558 * In case of error, the response memory, MIME type, charset and length will be
559 * deallocated and zeroed automatically. Thus be sure they are preallocated or
560 * they points to NULL.
561 * @response_otp_headers is pre-allocated structure for OTP authentication
562 * headers sent by server. Members must be valid pointers or NULLs.
563 * Pass NULL if you don't interest.
564 * Be ware that successful return value does not mean the HTTP request has
565 * been accepted by the server. You must consult @http_code. OTOH, failure
566 * return value means the request could not been sent (e.g. SSL error).
567 * Side effect: message buffer */
568 static isds_error
http(struct isds_ctx
*context
,
569 const char *url
, _Bool use_get
,
570 const void *request
, const size_t request_length
,
571 void **response
, size_t *response_length
,
572 char **mime_type
, char **charset
, long *http_code
,
573 struct auth_headers
*response_otp_headers
) {
576 isds_error err
= IE_SUCCESS
;
577 struct soap_body body
;
579 struct curl_slist
*headers
= NULL
;
582 if (!context
) return IE_INVALID_CONTEXT
;
583 if (!url
) return IE_INVAL
;
584 if (request_length
> 0 && !request
) return IE_INVAL
;
585 if (!response
|| !response_length
) return IE_INVAL
;
587 /* Clean authentication headers */
589 /* Set the body here to allow deallocation in leave block */
590 body
.data
= *response
;
593 /* Set Request-URI */
594 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_URL
, url
);
596 /* Set TLS options */
597 if (!curl_err
&& context
->tls_verify_server
) {
598 if (!*context
->tls_verify_server
)
599 isds_log(ILF_SEC
, ILL_WARNING
,
600 _("Disabling server identity verification. "
601 "That was your decision.\n"));
602 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSL_VERIFYPEER
,
603 (*context
->tls_verify_server
)? 1L : 0L);
605 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSL_VERIFYHOST
,
606 (*context
->tls_verify_server
)? 2L : 0L);
609 if (!curl_err
&& context
->tls_ca_file
) {
610 isds_log(ILF_SEC
, ILL_INFO
,
611 _("CA certificates will be searched in `%s' file since now\n"),
612 context
->tls_ca_file
);
613 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_CAINFO
,
614 context
->tls_ca_file
);
616 if (!curl_err
&& context
->tls_ca_dir
) {
617 isds_log(ILF_SEC
, ILL_INFO
,
618 _("CA certificates will be searched in `%s' directory "
619 "since now\n"), context
->tls_ca_dir
);
620 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_CAPATH
,
621 context
->tls_ca_dir
);
623 if (!curl_err
&& context
->tls_crl_file
) {
624 #if HAVE_DECL_CURLOPT_CRLFILE /* Since curl-7.19.0 */
625 isds_log(ILF_SEC
, ILL_INFO
,
626 _("CRLs will be searched in `%s' file since now\n"),
627 context
->tls_crl_file
);
628 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_CRLFILE
,
629 context
->tls_crl_file
);
631 isds_log(ILF_SEC
, ILL_WARNING
,
632 _("Your curl library cannot pass certificate revocation "
633 "list to cryptographic library.\n"
634 "Make sure cryptographic library default setting "
635 "delivers proper CRLs,\n"
636 "or upgrade curl.\n"));
637 #endif /* not HAVE_DECL_CURLOPT_CRLFILE */
641 if ((NULL
== context
->mep_credentials
) || (NULL
== context
->mep_credentials
->intermediate_uri
)) {
642 /* Don't set credentials in intermediate mobile key login state. */
643 /* Set credentials */
644 #if HAVE_DECL_CURLOPT_USERNAME /* Since curl-7.19.1 */
645 if (!curl_err
&& context
->username
) {
646 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_USERNAME
,
649 if (!curl_err
&& context
->password
) {
650 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_PASSWORD
,
654 if (!curl_err
&& (context
->username
|| context
->password
)) {
656 _isds_astrcat3(context
->username
, ":", context
->password
);
658 isds_log_message(context
, _("Could not pass credentials to CURL"));
662 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_USERPWD
, userpwd
);
665 #endif /* not HAVE_DECL_CURLOPT_USERNAME */
668 /* Set PKI credentials */
669 if (!curl_err
&& (context
->pki_credentials
)) {
670 if (context
->pki_credentials
->engine
) {
671 /* Select SSL engine */
672 isds_log(ILF_SEC
, ILL_INFO
,
673 _("Cryptographic engine `%s' will be used for "
674 "key or certificate\n"),
675 context
->pki_credentials
->engine
);
676 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLENGINE
,
677 context
->pki_credentials
->engine
);
681 /* Select certificate format */
682 #if HAVE_DECL_CURLOPT_SSLCERTTYPE /* since curl-7.9.3 */
683 if (context
->pki_credentials
->certificate_format
==
685 /* XXX: It's valid to have certificate in engine without name.
686 * Engines can select certificate according private key and
688 if (context
->pki_credentials
->certificate
)
689 isds_log(ILF_SEC
, ILL_INFO
, _("Client `%s' certificate "
690 "will be read from `%s' engine\n"),
691 context
->pki_credentials
->certificate
,
692 context
->pki_credentials
->engine
);
694 isds_log(ILF_SEC
, ILL_INFO
, _("Client certificate "
695 "will be read from `%s' engine\n"),
696 context
->pki_credentials
->engine
);
697 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLCERTTYPE
,
699 } else if (context
->pki_credentials
->certificate
) {
700 isds_log(ILF_SEC
, ILL_INFO
, _("Client %s certificate "
701 "will be read from `%s' file\n"),
702 (context
->pki_credentials
->certificate_format
==
703 PKI_FORMAT_DER
) ? _("DER") : _("PEM"),
704 context
->pki_credentials
->certificate
);
705 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLCERTTYPE
,
706 (context
->pki_credentials
->certificate_format
==
707 PKI_FORMAT_DER
) ? "DER" : "PEM");
710 if ((context
->pki_credentials
->certificate_format
==
712 context
->pki_credentials
->certificate
))
713 isds_log(ILF_SEC
, ILL_WARNING
,
714 _("Your curl library cannot distinguish certificate "
715 "formats. Make sure your cryptographic library\n"
716 "understands your certificate file by default, "
717 "or upgrade curl.\n"));
718 #endif /* not HAVE_DECL_CURLOPT_SSLCERTTYPE */
721 if (!curl_err
&& context
->pki_credentials
->certificate
) {
722 /* Select certificate */
724 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLCERT
,
725 context
->pki_credentials
->certificate
);
729 /* Select key format */
730 if (context
->pki_credentials
->key_format
== PKI_FORMAT_ENG
) {
731 if (context
->pki_credentials
->key
)
732 isds_log(ILF_SEC
, ILL_INFO
, _("Client private key `%s' "
733 "from `%s' engine will be used\n"),
734 context
->pki_credentials
->key
,
735 context
->pki_credentials
->engine
);
737 isds_log(ILF_SEC
, ILL_INFO
, _("Client private key "
738 "from `%s' engine will be used\n"),
739 context
->pki_credentials
->engine
);
740 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLKEYTYPE
,
742 } else if (context
->pki_credentials
->key
) {
743 isds_log(ILF_SEC
, ILL_INFO
, _("Client %s private key will be "
744 "read from `%s' file\n"),
745 (context
->pki_credentials
->key_format
==
746 PKI_FORMAT_DER
) ? _("DER") : _("PEM"),
747 context
->pki_credentials
->key
);
748 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLKEYTYPE
,
749 (context
->pki_credentials
->key_format
==
750 PKI_FORMAT_DER
) ? "DER" : "PEM");
755 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_SSLKEY
,
756 context
->pki_credentials
->key
);
759 /* Pass key pass-phrase */
760 #if HAVE_DECL_CURLOPT_KEYPASSWD /* since curl-7.16.5 */
761 curl_err
= curl_easy_setopt(context
->curl
,
763 context
->pki_credentials
->passphrase
);
764 #elif HAVE_DECL_CURLOPT_SSLKEYPASSWD /* up to curl-7.16.4 */
765 curl_err
= curl_easy_setopt(context
->curl
,
766 CURLOPT_SSLKEYPASSWD
,
767 context
->pki_credentials
->passphrase
);
768 #else /* up to curl-7.9.2 */
769 curl_err
= curl_easy_setopt(context
->curl
,
770 CURLOPT_SSLCERTPASSWD
,
771 context
->pki_credentials
->passphrase
);
777 /* Set authorization cookie for OTP session */
778 if (!curl_err
&& (context
->otp
|| context
->mep
)) {
779 isds_log(ILF_SEC
, ILL_INFO
,
780 _("Cookies will be stored and sent "
781 "because context has been authorized by OTP or mobile key.\n"));
782 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_COOKIEFILE
, "");
787 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_NOSIGNAL
, 1);
789 if (!curl_err
&& context
->timeout
) {
790 #if HAVE_DECL_CURLOPT_TIMEOUT_MS /* Since curl-7.16.2 */
791 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_TIMEOUT_MS
,
794 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_TIMEOUT
,
795 context
->timeout
/ 1000);
796 #endif /* not HAVE_DECL_CURLOPT_TIMEOUT_MS */
799 /* Register callback */
800 if (context
->progress_callback
) {
802 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_NOPROGRESS
, 0);
805 #if HAVE_DECL_CURLOPT_XFERINFOFUNCTION /* Since curl-7.32.0 */
806 curl_err
= curl_easy_setopt(context
->curl
,
807 CURLOPT_XFERINFOFUNCTION
, progress_proxy
);
809 curl_err
= curl_easy_setopt(context
->curl
,
810 CURLOPT_PROGRESSFUNCTION
, progress_proxy
);
811 #endif /* not HAVE_DECL_CURLOPT_XFERINFOFUNCTION */
814 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_PROGRESSDATA
,
819 /* Set other CURL features */
821 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_FAILONERROR
, 0);
824 /* Set get-response function */
826 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_WRITEFUNCTION
,
830 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_WRITEDATA
, &body
);
833 /* Set get-response-headers function if needed.
834 * XXX: Both CURLOPT_HEADERFUNCTION and CURLOPT_WRITEHEADER must be set or
835 * unset at the same time (see curl_easy_setopt(3)) ASAP, otherwise old
836 * invalid CURLOPT_WRITEHEADER value could be derefenced. */
838 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_HEADERFUNCTION
,
839 (response_otp_headers
== NULL
) ? NULL
: write_header
);
842 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_WRITEHEADER
,
843 response_otp_headers
);
846 /* Set MIME types and headers requires by SOAP 1.1.
847 * SOAP 1.1 requires text/xml, SOAP 1.2 requires application/soap+xml.
848 * But suppress sending the headers to proxies first if supported. */
849 #if HAVE_DECL_CURLOPT_HEADEROPT /* since curl-7.37.0 */
851 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_HEADEROPT
,
852 CURLHEADER_SEPARATE
);
854 #endif /* HAVE_DECL_CURLOPT_HEADEROPT */
856 headers
= curl_slist_append(headers
,
857 "Accept: application/soap+xml,application/xml,text/xml");
862 headers
= curl_slist_append(headers
, "Content-Type: text/xml");
867 headers
= curl_slist_append(headers
, "SOAPAction: ");
872 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_HTTPHEADER
, headers
);
875 /* Set user agent identification */
876 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_USERAGENT
,
877 "libisds/" PACKAGE_VERSION
);
881 /* Set GET request */
883 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_HTTPGET
, 1);
886 /* Set POST request body */
888 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POST
, 1);
891 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POSTFIELDS
, request
);
894 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_POSTFIELDSIZE
,
900 /* Debug cURL if requested */
902 ((log_facilities
& ILF_HTTP
) && (log_level
>= ILL_DEBUG
));
904 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_VERBOSE
,
905 (debug_curl
) ? 1 : 0);
908 curl_err
= curl_easy_setopt(context
->curl
, CURLOPT_DEBUGFUNCTION
,
909 (debug_curl
) ? log_curl
: NULL
);
913 /* Check for errors so far */
915 isds_log_message(context
, curl_easy_strerror(curl_err
));
920 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Sending %s request to <%s>\n"),
921 use_get
? "GET" : "POST", url
);
923 isds_log(ILF_HTTP
, ILL_DEBUG
,
924 _("POST body length: %zu, content follows:\n"), request_length
);
925 if (_isds_sizet2int(request_length
) >= 0 ) {
926 isds_log(ILF_HTTP
, ILL_DEBUG
, "%.*s\n",
927 _isds_sizet2int(request_length
), request
);
929 isds_log(ILF_HTTP
, ILL_DEBUG
, _("End of POST body\n"));
934 curl_err
= curl_easy_perform(context
->curl
);
937 curl_err
= curl_easy_getinfo(context
->curl
, CURLINFO_CONTENT_TYPE
,
941 /* TODO: Use curl_easy_setopt(CURLOPT_ERRORBUFFER) to obtain detailed
943 /* TODO: CURL is not internationalized yet. Collect CURL messages for
945 isds_printf_message(context
,
946 _("%s: %s"), url
, _(curl_easy_strerror(curl_err
)));
947 if (curl_err
== CURLE_ABORTED_BY_CALLBACK
)
950 curl_err
== CURLE_SSL_CONNECT_ERROR
||
951 curl_err
== CURLE_SSL_ENGINE_NOTFOUND
||
952 curl_err
== CURLE_SSL_ENGINE_SETFAILED
||
953 curl_err
== CURLE_SSL_CERTPROBLEM
||
954 curl_err
== CURLE_SSL_CIPHER
||
955 curl_err
== CURLE_SSL_CACERT
||
956 curl_err
== CURLE_USE_SSL_FAILED
||
957 curl_err
== CURLE_SSL_ENGINE_INITFAILED
||
958 curl_err
== CURLE_SSL_CACERT_BADFILE
||
959 curl_err
== CURLE_SSL_SHUTDOWN_FAILED
||
960 curl_err
== CURLE_SSL_CRL_BADFILE
||
961 curl_err
== CURLE_SSL_ISSUER_ERROR
969 isds_log(ILF_HTTP
, ILL_DEBUG
, _("Final response to %s received\n"), url
);
970 isds_log(ILF_HTTP
, ILL_DEBUG
,
971 _("Response body length: %zu, content follows:\n"),
973 if (_isds_sizet2int(body
.length
) >= 0) {
974 isds_log(ILF_HTTP
, ILL_DEBUG
, "%.*s\n",
975 _isds_sizet2int(body
.length
), body
.data
);
977 isds_log(ILF_HTTP
, ILL_DEBUG
, _("End of response body\n"));
980 /* Extract MIME type and charset */
985 sep
= strchr(content_type
, ';');
986 if (sep
) offset
= (size_t) (sep
- content_type
);
987 else offset
= strlen(content_type
);
990 *mime_type
= malloc(offset
+ 1);
995 memcpy(*mime_type
, content_type
, offset
);
996 (*mime_type
)[offset
] = '\0';
1003 sep
= strstr(sep
, "charset=");
1007 *charset
= strdup(sep
+ 8);
1017 /* Get HTTP response code */
1019 curl_err
= curl_easy_getinfo(context
->curl
,
1020 CURLINFO_RESPONSE_CODE
, http_code
);
1027 /* Store OTP authentication results */
1028 if (response_otp_headers
&& response_otp_headers
->is_complete
) {
1029 isds_log(ILF_SEC
, ILL_DEBUG
,
1030 _("OTP authentication headers received: "
1031 "method=%s, code=%s, message=%s\n"),
1032 response_otp_headers
->method
, response_otp_headers
->code
,
1033 response_otp_headers
->message
);
1035 /* XXX: Don't make unknown code fatal. Missing code can be success if
1036 * HTTP code is 302. This is checked in _isds_soap(). */
1037 response_otp_headers
->resolution
=
1038 string2isds_otp_resolution(response_otp_headers
->code
);
1040 if (response_otp_headers
->message
!= NULL
) {
1041 char *message_locale
= _isds_utf82locale(response_otp_headers
->message
);
1042 /* _isds_utf82locale() return NULL on inconverable string. Do not
1044 * TODO: Escape such characters.
1045 * if (message_locale == NULL) {
1049 isds_printf_message(context
,
1050 _("Server returned OTP authentication message: %s"),
1052 free(message_locale
);
1055 char *next_url
= NULL
; /* Weak pointer managed by cURL */
1056 curl_err
= curl_easy_getinfo(context
->curl
, CURLINFO_REDIRECT_URL
,
1062 if (next_url
!= NULL
) {
1063 isds_log(ILF_SEC
, ILL_DEBUG
,
1064 _("OTP authentication headers redirect to: <%s>\n"),
1066 free(response_otp_headers
->redirect
);
1067 response_otp_headers
->redirect
= strdup(next_url
);
1068 if (response_otp_headers
->redirect
== NULL
) {
1075 curl_slist_free_all(headers
);
1091 if (err
!= IE_ABORTED
) _isds_close_connection(context
);
1094 *response
= body
.data
;
1095 *response_length
= body
.length
;
1100 /* Converts numeric server response when periodically checking for MEP
1101 * authentication status.
1102 * @str String containing the numeric server response value
1103 * @len String length
1104 * @return server response code or MEP_RESOLUTION_UNKNOWN if the code was not
1106 static isds_mep_resolution
mep_ws_state_response(const char *str
, size_t len
) {
1107 isds_mep_resolution res
= MEP_RESOLUTION_UNKNOWN
; /* Default error. */
1109 if ((str
== NULL
) || (len
== 0)) {
1112 /* Ensure trailing '\0' character. */
1113 char *tmp_str
= malloc(len
+ 1);
1114 if (tmp_str
== NULL
) {
1117 memcpy(tmp_str
, str
, len
);
1118 tmp_str
[len
] = '\0';
1121 long num
= strtol(tmp_str
, &endptr
, 10);
1122 if (*endptr
!= '\0' || LONG_MIN
== num
|| LONG_MAX
== num
) {
1128 res
= MEP_RESOLUTION_UNRECOGNISED
;
1131 res
= MEP_RESOLUTION_ACK_REQUESTED
;
1134 res
= MEP_RESOLUTION_ACK
;
1137 res
= MEP_RESOLUTION_ACK_EXPIRED
;
1149 /* Build SOAP request.
1150 * @context needed for error logging,
1151 * @request is XML node set with SOAP request body,
1152 * @http_request_ptr the address of a pointer to an automatically allocated
1153 * buffer to which the request data are written.
1155 static isds_error
build_http_request(struct isds_ctx
*context
,
1156 const xmlNodePtr request
, xmlBufferPtr
*http_request_ptr
) {
1158 isds_error err
= IE_SUCCESS
;
1159 xmlBufferPtr http_request
= NULL
;
1160 xmlSaveCtxtPtr save_ctx
= NULL
;
1161 xmlDocPtr request_soap_doc
= NULL
;
1162 xmlNodePtr request_soap_envelope
= NULL
, request_soap_body
= NULL
;
1163 xmlNsPtr soap_ns
= NULL
;
1165 if (NULL
== context
) {
1166 return IE_INVALID_CONTEXT
;
1168 if (NULL
== http_request_ptr
) {
1173 /* Build SOAP request envelope */
1174 request_soap_doc
= xmlNewDoc(BAD_CAST
"1.0");
1175 if (NULL
== request_soap_doc
) {
1176 isds_log_message(context
, _("Could not build SOAP request document"));
1180 request_soap_envelope
= xmlNewNode(NULL
, BAD_CAST
"Envelope");
1181 if (NULL
== request_soap_envelope
) {
1182 isds_log_message(context
, _("Could not build SOAP request envelope"));
1186 xmlDocSetRootElement(request_soap_doc
, request_soap_envelope
);
1187 /* Only this way we get namespace definition as @xmlns:soap,
1188 * otherwise we get namespace prefix without definition */
1189 soap_ns
= xmlNewNs(request_soap_envelope
, BAD_CAST SOAP_NS
, NULL
);
1190 if(NULL
== soap_ns
) {
1191 isds_log_message(context
, _("Could not create SOAP name space"));
1195 xmlSetNs(request_soap_envelope
, soap_ns
);
1196 request_soap_body
= xmlNewChild(request_soap_envelope
, NULL
,
1197 BAD_CAST
"Body", NULL
);
1198 if (NULL
== request_soap_body
) {
1199 isds_log_message(context
,
1200 _("Could not add Body to SOAP request envelope"));
1206 /* Append request XML node set to SOAP body if request is not empty */
1207 /* XXX: Copy of request must be used, otherwise xmlFreeDoc(request_soap_doc)
1208 * would destroy this outer structure. */
1209 if (NULL
!= request
) {
1210 xmlNodePtr request_copy
= xmlCopyNodeList(request
);
1211 if (NULL
== request_copy
) {
1212 isds_log_message(context
,
1213 _("Could not copy request content"));
1217 if (NULL
== xmlAddChildList(request_soap_body
, request_copy
)) {
1218 xmlFreeNodeList(request_copy
);
1219 isds_log_message(context
,
1220 _("Could not add request content to SOAP "
1221 "request envelope"));
1228 /* Serialize the SOAP request into HTTP request body */
1229 http_request
= xmlBufferCreate();
1230 if (NULL
== http_request
) {
1231 isds_log_message(context
,
1232 _("Could not create xmlBuffer for HTTP request body"));
1236 /* Last argument 1 means format the XML tree. This is pretty but it breaks
1237 * XML document transport as it adds text nodes (indentation) between
1239 save_ctx
= xmlSaveToBuffer(http_request
, "UTF-8", 0);
1240 if (NULL
== save_ctx
) {
1241 isds_log_message(context
,
1242 _("Could not create XML serializer"));
1246 /* XXX: According LibXML documentation, this function does not return
1247 * meaningful value yet */
1248 xmlSaveDoc(save_ctx
, request_soap_doc
);
1249 if (-1 == xmlSaveFlush(save_ctx
)) {
1250 isds_log_message(context
,
1251 _("Could not serialize SOAP request to HTTP request body"));
1257 xmlSaveClose(save_ctx
);
1258 if (err
== IE_SUCCESS
) {
1259 /* Pass buffer to caller when successfully written. */
1260 *http_request_ptr
= http_request
;
1262 xmlBufferFree(http_request
);
1264 xmlFreeDoc(request_soap_doc
); /* recursive, frees request_body, soap_ns*/
1268 /* Process SOAP response.
1269 * @context needed for error logging,
1270 * @response is a pointer to a buffer where response data are held,
1271 * @response_length is the size of the response,
1272 * @response_document is an automatically allocated XML document whose sub-tree
1273 * identified by @response_node_list holds the SOAP response body content. You
1274 * must xmlFreeDoc() it. If you don't care pass NULL and also
1275 * NULL @response_node_list.
1276 * @response_node_list is a pointer to node set with SOAP response body
1277 * content. The returned pointer points into @response_document to the first
1278 * child of SOAP Body element. Pass NULL and NULL @response_document, if you
1282 isds_error
process_http_response(struct isds_ctx
*context
,
1283 const void *response
, size_t response_length
,
1284 xmlDocPtr
*response_document
, xmlNodePtr
*response_node_list
) {
1286 isds_error err
= IE_SUCCESS
;
1287 xmlDocPtr response_soap_doc
= NULL
;
1288 xmlNodePtr response_root
= NULL
;
1289 xmlXPathContextPtr xpath_ctx
= NULL
;
1290 xmlXPathObjectPtr response_soap_headers
= NULL
, response_soap_body
= NULL
,
1291 response_soap_fault
= NULL
;
1293 if (NULL
== context
) {
1294 return IE_INVALID_CONTEXT
;
1296 if ((NULL
== response_document
&& NULL
!= response_node_list
) ||
1297 (NULL
!= response_document
&& NULL
== response_node_list
)) {
1301 /* TODO: Convert returned body into XML default encoding */
1303 /* Parse the HTTP body as XML */
1304 response_soap_doc
= xmlParseMemory(response
, response_length
);
1305 if (NULL
== response_soap_doc
) {
1310 xpath_ctx
= xmlXPathNewContext(response_soap_doc
);
1311 if (NULL
== xpath_ctx
) {
1317 _isds_register_namespaces(xpath_ctx
, MESSAGE_NS_UNSIGNED
)) {
1322 if (_isds_sizet2int(response_length
) >= 0) {
1323 isds_log(ILF_SOAP
, ILL_DEBUG
,
1324 _("SOAP response received:\n%.*s\nEnd of SOAP response\n"),
1325 _isds_sizet2int(response_length
), response
);
1328 /* Check for SOAP version */
1329 response_root
= xmlDocGetRootElement(response_soap_doc
);
1330 if (NULL
== response_root
) {
1331 isds_log_message(context
, "SOAP response has no root element");
1335 if (xmlStrcmp(response_root
->name
, BAD_CAST
"Envelope") ||
1336 xmlStrcmp(response_root
->ns
->href
, BAD_CAST SOAP_NS
)) {
1337 isds_log_message(context
, "SOAP response is not SOAP 1.1 document");
1342 /* Check for SOAP Headers */
1343 response_soap_headers
= xmlXPathEvalExpression(
1344 BAD_CAST
"/soap:Envelope/soap:Header/"
1345 "*[@soap:mustUnderstand/text() = true()]", xpath_ctx
);
1346 if (NULL
== response_soap_headers
) {
1350 if (!xmlXPathNodeSetIsEmpty(response_soap_headers
->nodesetval
)) {
1351 isds_log_message(context
,
1352 _("SOAP response requires unsupported feature"));
1353 /* TODO: log the headers
1354 * xmlChar *fragment = NULL;
1355 * fragment = xmlXPathCastNodeSetToSting(response_soap_headers->nodesetval);*/
1361 response_soap_body
= xmlXPathEvalExpression(
1362 BAD_CAST
"/soap:Envelope/soap:Body", xpath_ctx
);
1363 if (NULL
== response_soap_body
) {
1367 if (xmlXPathNodeSetIsEmpty(response_soap_body
->nodesetval
)) {
1368 isds_log_message(context
,
1369 _("SOAP response does not contain SOAP Body element"));
1373 if (response_soap_body
->nodesetval
->nodeNr
> 1) {
1374 isds_log_message(context
,
1375 _("SOAP response has more than one Body element"));
1380 /* Check for SOAP Fault */
1381 response_soap_fault
= xmlXPathEvalExpression(
1382 BAD_CAST
"/soap:Envelope/soap:Body/soap:Fault", xpath_ctx
);
1383 if (NULL
== response_soap_fault
) {
1387 if (!xmlXPathNodeSetIsEmpty(response_soap_fault
->nodesetval
)) {
1388 /* Server signals Fault. Gather error message and croak. */
1389 /* XXX: Only first message is passed */
1390 char *message
= NULL
, *message_locale
= NULL
;
1391 xpath_ctx
->node
= response_soap_fault
->nodesetval
->nodeTab
[0];
1392 xmlXPathFreeObject(response_soap_fault
);
1393 /* XXX: faultstring and faultcode are in no name space according
1394 * ISDS specification */
1395 /* First more verbose faultstring */
1396 response_soap_fault
= xmlXPathEvalExpression(
1397 BAD_CAST
"faultstring[1]/text()", xpath_ctx
);
1398 if ((NULL
!= response_soap_fault
) &&
1399 !xmlXPathNodeSetIsEmpty(response_soap_fault
->nodesetval
)) {
1401 xmlXPathCastNodeSetToString(response_soap_fault
->nodesetval
);
1402 message_locale
= _isds_utf82locale(message
);
1404 /* If not available, try shorter faultcode */
1405 if (NULL
== message_locale
) {
1407 xmlXPathFreeObject(response_soap_fault
);
1408 response_soap_fault
= xmlXPathEvalExpression(
1409 BAD_CAST
"faultcode[1]/text()", xpath_ctx
);
1410 if ((NULL
!= response_soap_fault
) &&
1411 !xmlXPathNodeSetIsEmpty(response_soap_fault
->nodesetval
)) {
1413 xmlXPathCastNodeSetToString(
1414 response_soap_fault
->nodesetval
);
1415 message_locale
= _isds_utf82locale(message
);
1420 if (NULL
!= message_locale
) {
1421 isds_printf_message(context
, _("SOAP response signals Fault: %s"),
1424 isds_log_message(context
, _("SOAP response signals Fault"));
1427 free(message_locale
);
1435 /* Extract XML tree with ISDS response from SOAP envelope and return it.
1436 * XXX: response_soap_body lists only one Body element here. We need
1437 * children which may not exist (i.e. empty Body) or being more than one
1438 * (this is not the case of ISDS payload, but let's support generic SOAP).
1439 * XXX: We will return the XML document and children as a node list for
1441 * (1) We won't to do expensive xmlDocCopyNodeList(),
1442 * (2) Any node is unusable after calling xmlFreeDoc() on it's document
1443 * because the document holds a dictionary with identifiers. Caller always
1444 * can do xmlDocCopyNodeList() on a fresh document later. */
1445 if (NULL
!= response_document
&& NULL
!= response_node_list
) {
1446 *response_document
= response_soap_doc
;
1447 *response_node_list
=
1448 response_soap_body
->nodesetval
->nodeTab
[0]->children
;
1449 response_soap_doc
= NULL
; /* The document has been passed to the caller. */
1453 xmlXPathFreeObject(response_soap_fault
);
1454 xmlXPathFreeObject(response_soap_body
);
1455 xmlXPathFreeObject(response_soap_headers
);
1456 xmlXPathFreeContext(xpath_ctx
);
1457 xmlFreeDoc(response_soap_doc
);
1462 * @context holds the base URL,
1463 * @file is a (CGI) file of SOAP URL,
1464 * @request is XML node set with SOAP request body.
1465 * @file must be NULL, @request should be NULL rather than empty, if they should
1466 * not be signaled in the SOAP request.
1467 * @response_document is an automatically allocated XML document whose subtree
1468 * identified by @response_node_list holds the SOAP response body content. You
1469 * must xmlFreeDoc() it. If you don't care pass NULL and also
1470 * NULL @response_node_list.
1471 * @response_node_list is a pointer to node set with SOAP response body
1472 * content. The returned pointer points into @response_document to the first
1473 * child of SOAP Body element. Pass NULL and NULL @response_document, if you
1475 * @raw_response is automatically allocated bit stream with response body. Use
1476 * NULL if you don't care
1477 * @raw_response_length is size of @raw_response in bytes
1478 * In case of error the response will be deallocated automatically.
1479 * Side effect: message buffer */
1480 _hidden isds_error
_isds_soap(struct isds_ctx
*context
, const char *file
,
1481 const xmlNodePtr request
,
1482 xmlDocPtr
*response_document
, xmlNodePtr
*response_node_list
,
1483 void **raw_response
, size_t *raw_response_length
) {
1485 isds_error err
= IE_SUCCESS
;
1487 char *mime_type
= NULL
;
1489 struct auth_headers response_otp_headers
;
1490 xmlBufferPtr http_request
= NULL
;
1491 void *http_response
= NULL
;
1492 size_t response_length
= 0;
1494 if (!context
) return IE_INVALID_CONTEXT
;
1495 if ( (NULL
== response_document
&& NULL
!= response_node_list
) ||
1496 (NULL
!= response_document
&& NULL
== response_node_list
))
1498 if (!raw_response_length
&& raw_response
) return IE_INVAL
;
1500 if (response_document
) *response_document
= NULL
;
1501 if (response_node_list
) *response_node_list
= NULL
;
1502 if (raw_response
) *raw_response
= NULL
;
1504 url
= _isds_astrcat(context
->url
, file
);
1505 if (!url
) return IE_NOMEM
;
1508 err
= build_http_request(context
, request
, &http_request
);
1509 if (IE_SUCCESS
!= err
) {
1514 if ((context
->otp_credentials
!= NULL
) || (context
->mep_credentials
!= NULL
)) {
1515 memset(&response_otp_headers
, 0, sizeof(response_otp_headers
));
1518 if ((context
->otp_credentials
!= NULL
) || (context
->mep_credentials
!= NULL
)) {
1519 auth_headers_free(&response_otp_headers
);
1521 isds_log(ILF_SOAP
, ILL_DEBUG
,
1522 _("SOAP request to sent to %s:\n%.*s\nEnd of SOAP request\n"),
1523 url
, http_request
->use
, http_request
->content
);
1525 if ((NULL
!= context
->mep_credentials
) && (NULL
!= context
->mep_credentials
->intermediate_uri
)) {
1526 /* POST does not work for the intermediate URI, using GET here. */
1527 err
= http(context
, context
->mep_credentials
->intermediate_uri
, 1, NULL
, 0,
1528 &http_response
, &response_length
,
1529 &mime_type
, NULL
, &http_code
,
1530 ((context
->otp_credentials
== NULL
) && (context
->mep_credentials
== NULL
)) ? NULL
: &response_otp_headers
);
1532 err
= http(context
, url
, 0, http_request
->content
, http_request
->use
,
1533 &http_response
, &response_length
,
1534 &mime_type
, NULL
, &http_code
,
1535 ((context
->otp_credentials
== NULL
) && (context
->mep_credentials
== NULL
)) ? NULL
: &response_otp_headers
);
1538 /* TODO: HTTP binding for SOAP prescribes non-200 HTTP return codes
1539 * to be processed too. */
1545 if (NULL
!= context
->otp_credentials
)
1546 context
->otp_credentials
->resolution
= response_otp_headers
.resolution
;
1548 /* Check for HTTP return code */
1549 isds_log(ILF_SOAP
, ILL_DEBUG
, _("Server returned %ld HTTP code\n"),
1551 switch (http_code
) {
1552 /* XXX: We must see which code is used for not permitted ISDS
1553 * operation like downloading message without proper user
1554 * permissions. In that case we should keep connection opened. */
1556 if (NULL
!= context
->otp_credentials
) {
1557 if (context
->otp_credentials
->resolution
==
1558 OTP_RESOLUTION_UNKNOWN
)
1559 context
->otp_credentials
->resolution
=
1560 OTP_RESOLUTION_SUCCESS
;
1561 } else if (NULL
!= context
->mep_credentials
) {
1562 /* The server returns just a numerical value in the body, nothing else. */
1563 context
->mep_credentials
->resolution
=
1564 mep_ws_state_response(http_response
, response_length
);
1565 switch (context
->mep_credentials
->resolution
) {
1566 case MEP_RESOLUTION_ACK_REQUESTED
:
1567 /* Waiting for the user to acknowledge the login request
1568 * in the mobile application. This may take a while.
1569 * Return with partial success. Don't close communication
1571 err
= IE_PARTIAL_SUCCESS
;
1574 case MEP_RESOLUTION_ACK
:
1575 /* Immediately redirect to login finalisation. */
1576 zfree(context
->mep_credentials
->intermediate_uri
);
1577 err
= IE_PARTIAL_SUCCESS
;
1581 zfree(context
->mep_credentials
->intermediate_uri
);
1582 err
= IE_NOT_LOGGED_IN
;
1583 /* No SOAP data are returned here just plain response code. */
1590 if (NULL
!= context
->otp_credentials
) {
1591 if (context
->otp_credentials
->resolution
==
1592 OTP_RESOLUTION_UNKNOWN
)
1593 context
->otp_credentials
->resolution
=
1594 OTP_RESOLUTION_SUCCESS
;
1595 err
= IE_PARTIAL_SUCCESS
;
1596 isds_printf_message(context
,
1597 _("Server redirects on <%s> because OTP authentication "
1600 if (context
->otp_credentials
->otp_code
!= NULL
&&
1601 response_otp_headers
.redirect
!= NULL
) {
1602 /* XXX: If OTP code is known, this must be second OTP phase, so
1603 * send final POST request and unset Basic authentication
1604 * from cURL context as cookie is used instead. */
1606 url
= response_otp_headers
.redirect
;
1607 response_otp_headers
.redirect
= NULL
;
1608 _isds_discard_credentials(context
, 0);
1609 err
= unset_http_authorization(context
);
1611 isds_log_message(context
, _("Could not remove "
1612 "credentials from CURL handle."));
1617 /* XXX: Otherwise bail out to ask application for OTP code. */
1620 } else if (NULL
!= context
->mep_credentials
) {
1621 if (context
->mep_credentials
->resolution
== MEP_RESOLUTION_UNKNOWN
) {
1622 context
->mep_credentials
->resolution
= MEP_RESOLUTION_ACK_REQUESTED
;
1623 if ((context
->mep_credentials
->intermediate_uri
== NULL
) &&
1624 (response_otp_headers
.redirect
!= NULL
)) {
1625 /* This is the first attempt. */
1626 isds_printf_message(context
,
1627 _("Server redirects on <%s> because mobile key authentication "
1630 context
->mep_credentials
->intermediate_uri
=
1631 _isds_astrcat(response_otp_headers
.redirect
, NULL
);
1632 err
= IE_PARTIAL_SUCCESS
;
1635 } else if (context
->mep_credentials
->resolution
== MEP_RESOLUTION_ACK
) {
1636 /* MEP login succeeded. No SOAP data received even though
1637 * they were requested. */
1638 context
->mep_credentials
->resolution
= MEP_RESOLUTION_SUCCESS
;
1645 isds_printf_message(context
,
1646 _("Code 302: Server redirects on <%s> request. "
1647 "Redirection is forbidden in stateless mode."),
1653 if (NULL
!= context
->mep_credentials
) {
1654 free(context
->mep_credentials
->intermediate_uri
);
1655 context
->mep_credentials
->intermediate_uri
= NULL
;
1656 context
->mep_credentials
->resolution
= MEP_RESOLUTION_UNRECOGNISED
;
1657 err
= IE_NOT_LOGGED_IN
;
1658 isds_printf_message(context
,
1659 _("Code 400: Server redirects on <%s> request. "
1660 "MEP communication code was not recognised."), url
);
1664 case 401: /* ISDS server returns 401 even if Authorization
1666 case 403: /* HTTP/1.0 prescribes 403 if Authorization presents. */
1667 err
= IE_NOT_LOGGED_IN
;
1668 isds_log_message(context
, _("Authentication failed"));
1673 isds_printf_message(context
,
1674 _("Code 404: Document (%s) not found on server"), url
);
1677 /* 500 should return standard SOAP message */
1680 /* Check for Content-Type: text/xml.
1681 * Do it after HTTP code check because 401 Unauthorized returns HTML web
1682 * page for browsers. */
1683 if (mime_type
&& strcmp(mime_type
, "text/xml")
1684 && strcmp(mime_type
, "application/soap+xml")
1685 && strcmp(mime_type
, "application/xml")) {
1686 char *mime_type_locale
= _isds_utf82locale(mime_type
);
1687 isds_printf_message(context
,
1688 _("%s: bad MIME type sent by server: %s"), url
,
1690 free(mime_type_locale
);
1696 err
= process_http_response(context
, http_response
, response_length
,
1697 response_document
, response_node_list
);
1698 if (IE_SUCCESS
!= err
) {
1703 /* Save raw response */
1704 if (NULL
!= raw_response
) {
1705 *raw_response
= http_response
;
1706 http_response
= NULL
;
1708 if (NULL
!= raw_response_length
) {
1709 *raw_response_length
= response_length
;
1713 if ((context
->otp_credentials
!= NULL
) || (context
->mep_credentials
!= NULL
))
1714 auth_headers_free(&response_otp_headers
);
1716 free(http_response
);
1717 xmlBufferFree(http_request
);
1724 /* Build new URL from current @context and template.
1725 * @context is context carrying an URL
1726 * @template is printf(3) format string. First argument is length of the base
1727 * URL found in @context, second argument is the base URL, third argument is
1728 * again the base URL.
1729 * XXX: We cannot use "$" formatting character because it's not in the ISO C99.
1730 * @new_url is newly allocated URL built from @template. Caller must free it.
1731 * Return IE_SUCCESS, or corresponding error code and @new_url will not be
1734 _hidden isds_error
_isds_build_url_from_context(struct isds_ctx
*context
,
1735 const char *template, char **new_url
) {
1736 int length
, slashes
;
1738 if (NULL
!= new_url
) *new_url
= NULL
;
1739 if (NULL
== context
) return IE_INVALID_CONTEXT
;
1740 if (NULL
== template) return IE_INVAL
;
1741 if (NULL
== new_url
) return IE_INVAL
;
1743 /* Find length of base URL from context URL */
1744 if (NULL
== context
->url
) {
1745 isds_log_message(context
, _("Base URL could not have been determined "
1746 "from context URL because there was no URL set in the "
1750 for (length
= 0, slashes
= 0; context
->url
[length
] != '\0'; length
++) {
1751 if (context
->url
[length
] == '/') slashes
++;
1752 if (slashes
== 3) break;
1755 isds_log_message(context
, _("Base URL could not have been determined "
1756 "from context URL"));
1762 if (-1 == isds_asprintf(new_url
, template, length
, context
->url
,
1770 /* Invalidate session cookie for otp authenticated @context */
1771 _hidden isds_error
_isds_invalidate_otp_cookie(struct isds_ctx
*context
) {
1775 void *response
= NULL
;
1776 size_t response_length
;
1778 if (context
== NULL
|| (!context
->otp
&& !context
->mep
)) return IE_INVALID_CONTEXT
;
1779 if (context
->curl
== NULL
) return IE_CONNECTION_CLOSED
;
1781 /* Build logout URL */
1782 /*"https://DOMAINNAME/as/processLogout?uri=https://DOMAINNAME/apps/DS/WEB_SERVICE_ENDPOINT"*/
1783 err
= _isds_build_url_from_context(context
,
1784 "%.*sas/processLogout?uri=%sDS/dz", &url
);
1785 if (err
) return err
;
1787 /* Invalidate the cookie by GET request */
1791 &response
, &response_length
,
1792 NULL
, NULL
, &http_code
,
1797 /* long message set by http() */
1798 } else if (http_code
!= 200) {
1799 /* TODO: Specification does not define response for this request.
1800 * Especially it does not state whether direct 200 or 302 redirect is
1801 * sent. We need to check real implementation. */
1803 isds_printf_message(context
, _("Cookie for OTP authenticated "
1804 "connection to <%s> could not been invalidated"),
1807 isds_log(ILF_SEC
, ILL_DEBUG
, _("Cookie for OTP authenticated "
1808 "connection to <%s> has been invalidated.\n"),
1815 /* LibXML functions:
1817 * void xmlInitParser(void)
1818 * Initialization function for the XML parser. This is not reentrant. Call
1819 * once before processing in case of use in multithreaded programs.
1821 * int xmlInitParserCtxt(xmlParserCtxtPtr ctxt)
1822 * Initialize a parser context
1824 * xmlDocPtr xmlCtxtReadDoc(xmlParserCtxtPtr ctxt, const xmlChar * cur,
1825 * const * char URL, const char * encoding, int options);
1826 * Parse in-memory NULL-terminated document @cur.
1828 * xmlDocPtr xmlParseMemory(const char * buffer, int size)
1829 * Parse an XML in-memory block and build a tree.
1831 * xmlParserCtxtPtr xmlCreateMemoryParserCtxt(const char * buffer, int
1833 * Create a parser context for an XML in-memory document.
1835 * xmlParserCtxtPtr xmlCreateDocParserCtxt(const xmlChar * cur)
1836 * Creates a parser context for an XML in-memory document.
1838 * xmlDocPtr xmlCtxtReadMemory(xmlParserCtxtPtr ctxt,
1839 * const char * buffer, int size, const char * URL, const char * encoding,
1841 * Parse an XML in-memory document and build a tree. This reuses the existing
1842 * @ctxt parser context.
1844 * void xmlCleanupParser(void)
1845 * Cleanup function for the XML library. It tries to reclaim all parsing
1846 * related glob document related memory. Calling this function should not
1847 * prevent reusing the libr finished using the library or XML document built
1850 * void xmlClearParserCtxt(xmlParserCtxtPtr ctxt)
1851 * Clear (release owned resources) and reinitialize a parser context.
1853 * void xmlCtxtReset(xmlParserCtxtPtr ctxt)
1854 * Reset a parser context
1856 * void xmlFreeParserCtxt(xmlParserCtxtPtr ctxt)
1857 * Free all the memory used by a parser context. However the parsed document
1858 * in ctxt->myDoc is not freed.
1860 * void xmlFreeDoc(xmlDocPtr cur)
1861 * Free up all the structures used by a document, tree included.