]>
Commit | Line | Data |
---|---|---|
29f178bd | 1 | /* |
4333b89f | 2 | * Copyright 2001-2021 The OpenSSL Project Authors. All Rights Reserved. |
29f178bd DDO |
3 | * Copyright Siemens AG 2018-2020 |
4 | * | |
5 | * Licensed under the Apache License 2.0 (the "License"). You may not use | |
6 | * this file except in compliance with the License. You can obtain a copy | |
7 | * in the file LICENSE in the source distribution or at | |
8 | * https://www.openssl.org/source/license.html | |
9 | */ | |
10 | ||
11 | #include "e_os.h" | |
12 | #include <stdio.h> | |
13 | #include <stdlib.h> | |
14 | #include "crypto/ctype.h" | |
15 | #include <string.h> | |
16 | #include <openssl/asn1.h> | |
17 | #include <openssl/evp.h> | |
18 | #include <openssl/err.h> | |
19 | #include <openssl/httperr.h> | |
20 | #include <openssl/cmperr.h> | |
21 | #include <openssl/buffer.h> | |
22 | #include <openssl/http.h> | |
23 | #include "internal/sockets.h" | |
4b1fe471 | 24 | #include "internal/cryptlib.h" /* for ossl_assert() */ |
29f178bd DDO |
25 | |
26 | #include "http_local.h" | |
27 | ||
28 | #define HTTP_PREFIX "HTTP/" | |
29 | #define HTTP_VERSION_PATT "1." /* allow 1.x */ | |
30 | #define HTTP_VERSION_STR_LEN 3 | |
31 | #define HTTP_LINE1_MINLEN ((int)strlen(HTTP_PREFIX HTTP_VERSION_PATT "x 200\n")) | |
32 | #define HTTP_VERSION_MAX_REDIRECTIONS 50 | |
33 | ||
34 | #define HTTP_STATUS_CODE_OK 200 | |
35 | #define HTTP_STATUS_CODE_MOVED_PERMANENTLY 301 | |
36 | #define HTTP_STATUS_CODE_FOUND 302 | |
37 | ||
38 | ||
39 | /* Stateful HTTP request code, supporting blocking and non-blocking I/O */ | |
40 | ||
41 | /* Opaque HTTP request status structure */ | |
42 | ||
43 | struct ossl_http_req_ctx_st { | |
44 | int state; /* Current I/O state */ | |
d337af18 DDO |
45 | unsigned char *readbuf; /* Buffer for reading response by line */ |
46 | int readbuflen; /* Buffer length, equals maxline */ | |
29f178bd DDO |
47 | BIO *wbio; /* BIO to send request to */ |
48 | BIO *rbio; /* BIO to read response from */ | |
49 | BIO *mem; /* Memory BIO response is built into */ | |
046fba44 | 50 | int method_POST; /* HTTP method is "POST" (else "GET") */ |
29f178bd DDO |
51 | const char *expected_ct; /* expected Content-Type, or NULL */ |
52 | int expect_asn1; /* response must be ASN.1-encoded */ | |
673474b1 | 53 | long len_to_send; /* number of bytes in request still to send */ |
29f178bd DDO |
54 | unsigned long resp_len; /* length of response */ |
55 | unsigned long max_resp_len; /* Maximum length of response */ | |
56 | time_t max_time; /* Maximum end time of the transfer, or 0 */ | |
57 | char *redirection_url; /* Location given with HTTP status 301/302 */ | |
58 | }; | |
59 | ||
29f178bd DDO |
60 | /* HTTP states */ |
61 | ||
62 | #define OHS_NOREAD 0x1000 /* If set no reading should be performed */ | |
63 | #define OHS_ERROR (0 | OHS_NOREAD) /* Error condition */ | |
64 | #define OHS_FIRSTLINE 1 /* First line being read */ | |
65 | #define OHS_REDIRECT 0xa /* Looking for redirection location */ | |
66 | #define OHS_HEADERS 2 /* MIME headers being read */ | |
67 | #define OHS_ASN1_HEADER 3 /* HTTP initial header (tag+length) being read */ | |
68 | #define OHS_CONTENT 4 /* HTTP content octets being read */ | |
673474b1 | 69 | #define OHS_WRITE_INIT (5 | OHS_NOREAD) /* 1st call: ready to start send */ |
29f178bd DDO |
70 | #define OHS_WRITE (6 | OHS_NOREAD) /* Request being sent */ |
71 | #define OHS_FLUSH (7 | OHS_NOREAD) /* Request being flushed */ | |
72 | #define OHS_DONE (8 | OHS_NOREAD) /* Completed */ | |
73 | #define OHS_HTTP_HEADER (9 | OHS_NOREAD) /* Headers set, w/o final \r\n */ | |
74 | ||
75 | OSSL_HTTP_REQ_CTX *OSSL_HTTP_REQ_CTX_new(BIO *wbio, BIO *rbio, | |
046fba44 | 76 | int method_POST, int maxline, |
29f178bd DDO |
77 | unsigned long max_resp_len, |
78 | int timeout, | |
79 | const char *expected_content_type, | |
80 | int expect_asn1) | |
81 | { | |
82 | OSSL_HTTP_REQ_CTX *rctx; | |
83 | ||
84 | if (wbio == NULL || rbio == NULL) { | |
9311d0c4 | 85 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
86 | return NULL; |
87 | } | |
88 | ||
89 | if ((rctx = OPENSSL_zalloc(sizeof(*rctx))) == NULL) | |
90 | return NULL; | |
91 | rctx->state = OHS_ERROR; | |
d337af18 DDO |
92 | rctx->readbuflen = maxline > 0 ? maxline : HTTP_DEFAULT_MAX_LINE_LENGTH; |
93 | rctx->readbuf = OPENSSL_malloc(rctx->readbuflen); | |
29f178bd DDO |
94 | rctx->wbio = wbio; |
95 | rctx->rbio = rbio; | |
a6d40689 DDO |
96 | if (rctx->readbuf == NULL) { |
97 | OPENSSL_free(rctx); | |
29f178bd DDO |
98 | return NULL; |
99 | } | |
046fba44 | 100 | rctx->method_POST = method_POST; |
29f178bd DDO |
101 | rctx->expected_ct = expected_content_type; |
102 | rctx->expect_asn1 = expect_asn1; | |
103 | rctx->resp_len = 0; | |
104 | OSSL_HTTP_REQ_CTX_set_max_response_length(rctx, max_resp_len); | |
105 | rctx->max_time = timeout > 0 ? time(NULL) + timeout : 0; | |
a6d40689 | 106 | /* everything else is 0, e.g. rctx->len_to_send, or NULL, e.g. rctx->mem */ |
29f178bd DDO |
107 | return rctx; |
108 | } | |
109 | ||
110 | void OSSL_HTTP_REQ_CTX_free(OSSL_HTTP_REQ_CTX *rctx) | |
111 | { | |
112 | if (rctx == NULL) | |
113 | return; | |
114 | BIO_free(rctx->mem); /* this may indirectly call ERR_clear_error() */ | |
d337af18 | 115 | OPENSSL_free(rctx->readbuf); |
29f178bd DDO |
116 | OPENSSL_free(rctx); |
117 | } | |
118 | ||
119 | BIO *OSSL_HTTP_REQ_CTX_get0_mem_bio(OSSL_HTTP_REQ_CTX *rctx) | |
120 | { | |
121 | if (rctx == NULL) { | |
9311d0c4 | 122 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
123 | return NULL; |
124 | } | |
125 | return rctx->mem; | |
126 | } | |
127 | ||
128 | void OSSL_HTTP_REQ_CTX_set_max_response_length(OSSL_HTTP_REQ_CTX *rctx, | |
129 | unsigned long len) | |
130 | { | |
131 | if (rctx == NULL) { | |
9311d0c4 | 132 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
133 | return; |
134 | } | |
135 | rctx->max_resp_len = len != 0 ? len : HTTP_DEFAULT_MAX_RESP_LEN; | |
136 | } | |
137 | ||
138 | /* | |
cddbcf02 | 139 | * Create request line using |ctx| and |path| (or "/" in case |path| is NULL). |
29f178bd DDO |
140 | * Server name (and port) must be given if and only if plain HTTP proxy is used. |
141 | */ | |
cddbcf02 DDO |
142 | int OSSL_HTTP_REQ_CTX_set_request_line(OSSL_HTTP_REQ_CTX *rctx, |
143 | const char *server, const char *port, | |
144 | const char *path) | |
29f178bd DDO |
145 | { |
146 | if (rctx == NULL) { | |
9311d0c4 | 147 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
148 | return 0; |
149 | } | |
a6d40689 DDO |
150 | BIO_free(rctx->mem); |
151 | if ((rctx->mem = BIO_new(BIO_s_mem())) == NULL) | |
152 | return 0; | |
29f178bd | 153 | |
046fba44 | 154 | if (BIO_printf(rctx->mem, "%s ", rctx->method_POST ? "POST" : "GET") <= 0) |
29f178bd DDO |
155 | return 0; |
156 | ||
157 | if (server != NULL) { /* HTTP (but not HTTPS) proxy is used */ | |
158 | /* | |
159 | * Section 5.1.2 of RFC 1945 states that the absoluteURI form is only | |
160 | * allowed when using a proxy | |
161 | */ | |
4b1fe471 | 162 | if (BIO_printf(rctx->mem, OSSL_HTTP_PREFIX"%s", server) <= 0) |
29f178bd DDO |
163 | return 0; |
164 | if (port != NULL && BIO_printf(rctx->mem, ":%s", port) <= 0) | |
165 | return 0; | |
166 | } | |
167 | ||
168 | /* Make sure path includes a forward slash */ | |
169 | if (path == NULL) | |
170 | path = "/"; | |
171 | if (path[0] != '/' && BIO_printf(rctx->mem, "/") <= 0) | |
172 | return 0; | |
173 | ||
174 | if (BIO_printf(rctx->mem, "%s "HTTP_PREFIX"1.0\r\n", path) <= 0) | |
175 | return 0; | |
176 | rctx->state = OHS_HTTP_HEADER; | |
177 | return 1; | |
178 | } | |
179 | ||
180 | int OSSL_HTTP_REQ_CTX_add1_header(OSSL_HTTP_REQ_CTX *rctx, | |
181 | const char *name, const char *value) | |
182 | { | |
183 | if (rctx == NULL || name == NULL) { | |
9311d0c4 | 184 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
185 | return 0; |
186 | } | |
a6d40689 DDO |
187 | if (rctx->mem == NULL) { |
188 | ERR_raise(ERR_LIB_HTTP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | |
189 | return 0; | |
190 | } | |
29f178bd DDO |
191 | |
192 | if (BIO_puts(rctx->mem, name) <= 0) | |
193 | return 0; | |
194 | if (value != NULL) { | |
195 | if (BIO_write(rctx->mem, ": ", 2) != 2) | |
196 | return 0; | |
197 | if (BIO_puts(rctx->mem, value) <= 0) | |
198 | return 0; | |
199 | } | |
200 | if (BIO_write(rctx->mem, "\r\n", 2) != 2) | |
201 | return 0; | |
202 | rctx->state = OHS_HTTP_HEADER; | |
203 | return 1; | |
204 | } | |
205 | ||
a6d40689 DDO |
206 | static int OSSL_HTTP_REQ_CTX_set_content(OSSL_HTTP_REQ_CTX *rctx, |
207 | const char *content_type, BIO *req_mem) | |
29f178bd DDO |
208 | { |
209 | const unsigned char *req; | |
210 | long req_len; | |
211 | ||
212 | if (rctx == NULL || req_mem == NULL) { | |
9311d0c4 | 213 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
214 | return 0; |
215 | } | |
a6d40689 | 216 | if (rctx->mem == NULL || !rctx->method_POST) { |
0a20cc4b DDO |
217 | ERR_raise(ERR_LIB_HTTP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); |
218 | return 0; | |
219 | } | |
29f178bd DDO |
220 | |
221 | if (content_type != NULL | |
222 | && BIO_printf(rctx->mem, "Content-Type: %s\r\n", content_type) <= 0) | |
223 | return 0; | |
224 | ||
225 | if ((req_len = BIO_get_mem_data(req_mem, &req)) <= 0) | |
226 | return 0; | |
227 | rctx->state = OHS_WRITE_INIT; | |
228 | ||
229 | return BIO_printf(rctx->mem, "Content-Length: %ld\r\n\r\n", req_len) > 0 | |
230 | && BIO_write(rctx->mem, req, req_len) == (int)req_len; | |
231 | } | |
232 | ||
9253f834 | 233 | BIO *HTTP_asn1_item2bio(const ASN1_ITEM *it, const ASN1_VALUE *val) |
29f178bd DDO |
234 | { |
235 | BIO *res; | |
236 | ||
237 | if (it == NULL || val == NULL) { | |
9311d0c4 | 238 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
239 | return NULL; |
240 | } | |
241 | ||
242 | if ((res = BIO_new(BIO_s_mem())) == NULL) | |
243 | return NULL; | |
244 | if (ASN1_item_i2d_bio(it, res, val) <= 0) { | |
245 | BIO_free(res); | |
246 | res = NULL; | |
247 | } | |
248 | return res; | |
249 | } | |
250 | ||
251 | int OSSL_HTTP_REQ_CTX_i2d(OSSL_HTTP_REQ_CTX *rctx, const char *content_type, | |
252 | const ASN1_ITEM *it, ASN1_VALUE *req) | |
253 | { | |
254 | BIO *mem; | |
255 | int res; | |
256 | ||
257 | if (rctx == NULL || it == NULL || req == NULL) { | |
9311d0c4 | 258 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
259 | return 0; |
260 | } | |
261 | ||
262 | res = (mem = HTTP_asn1_item2bio(it, req)) != NULL | |
a6d40689 | 263 | && OSSL_HTTP_REQ_CTX_set_content(rctx, content_type, mem); |
29f178bd DDO |
264 | BIO_free(mem); |
265 | return res; | |
266 | } | |
267 | ||
268 | static int OSSL_HTTP_REQ_CTX_add1_headers(OSSL_HTTP_REQ_CTX *rctx, | |
269 | const STACK_OF(CONF_VALUE) *headers, | |
270 | const char *host) | |
271 | { | |
272 | int i; | |
273 | int add_host = 1; | |
274 | CONF_VALUE *hdr; | |
275 | ||
276 | for (i = 0; i < sk_CONF_VALUE_num(headers); i++) { | |
277 | hdr = sk_CONF_VALUE_value(headers, i); | |
278 | if (add_host && strcasecmp("host", hdr->name) == 0) | |
279 | add_host = 0; | |
280 | if (!OSSL_HTTP_REQ_CTX_add1_header(rctx, hdr->name, hdr->value)) | |
281 | return 0; | |
282 | } | |
283 | ||
284 | if (add_host && !OSSL_HTTP_REQ_CTX_add1_header(rctx, "Host", host)) | |
285 | return 0; | |
286 | return 1; | |
287 | } | |
288 | ||
289 | /*- | |
290 | * Create OSSL_HTTP_REQ_CTX structure using the values provided. | |
291 | * If !use_http_proxy then the 'server' and 'port' parameters are ignored. | |
292 | * If req_mem == NULL then use GET and ignore content_type, else POST. | |
293 | */ | |
294 | OSSL_HTTP_REQ_CTX *HTTP_REQ_CTX_new(BIO *wbio, BIO *rbio, int use_http_proxy, | |
295 | const char *server, const char *port, | |
296 | const char *path, | |
297 | const STACK_OF(CONF_VALUE) *headers, | |
298 | const char *content_type, BIO *req_mem, | |
299 | int maxline, unsigned long max_resp_len, | |
300 | int timeout, | |
301 | const char *expected_content_type, | |
302 | int expect_asn1) | |
303 | { | |
304 | OSSL_HTTP_REQ_CTX *rctx; | |
305 | ||
306 | if (use_http_proxy && (server == NULL || port == NULL)) { | |
9311d0c4 | 307 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
308 | return NULL; |
309 | } | |
310 | /* remaining parameters are checked indirectly by the functions called */ | |
311 | ||
046fba44 | 312 | if ((rctx = OSSL_HTTP_REQ_CTX_new(wbio, rbio, req_mem != NULL, maxline, |
29f178bd DDO |
313 | max_resp_len, timeout, |
314 | expected_content_type, expect_asn1)) | |
315 | == NULL) | |
316 | return NULL; | |
317 | ||
cddbcf02 DDO |
318 | if (OSSL_HTTP_REQ_CTX_set_request_line(rctx, |
319 | use_http_proxy ? server : NULL, port, | |
320 | path) | |
29f178bd DDO |
321 | && OSSL_HTTP_REQ_CTX_add1_headers(rctx, headers, server) |
322 | && (req_mem == NULL | |
a6d40689 | 323 | || OSSL_HTTP_REQ_CTX_set_content(rctx, content_type, req_mem))) |
29f178bd DDO |
324 | return rctx; |
325 | ||
326 | OSSL_HTTP_REQ_CTX_free(rctx); | |
327 | return NULL; | |
328 | } | |
329 | ||
330 | /* | |
331 | * Parse first HTTP response line. This should be like this: "HTTP/1.0 200 OK". | |
332 | * We need to obtain the numeric code and (optional) informational message. | |
333 | */ | |
334 | ||
335 | static int parse_http_line1(char *line) | |
336 | { | |
337 | int retcode; | |
338 | char *code, *reason, *end; | |
339 | ||
340 | /* Skip to first whitespace (past protocol info) */ | |
341 | for (code = line; *code != '\0' && !ossl_isspace(*code); code++) | |
342 | continue; | |
343 | if (*code == '\0') { | |
9311d0c4 | 344 | ERR_raise(ERR_LIB_HTTP, HTTP_R_RESPONSE_PARSE_ERROR); |
29f178bd DDO |
345 | return 0; |
346 | } | |
347 | ||
348 | /* Skip past whitespace to start of response code */ | |
349 | while (*code != '\0' && ossl_isspace(*code)) | |
350 | code++; | |
351 | ||
352 | if (*code == '\0') { | |
9311d0c4 | 353 | ERR_raise(ERR_LIB_HTTP, HTTP_R_RESPONSE_PARSE_ERROR); |
29f178bd DDO |
354 | return 0; |
355 | } | |
356 | ||
357 | /* Find end of response code: first whitespace after start of code */ | |
358 | for (reason = code; *reason != '\0' && !ossl_isspace(*reason); reason++) | |
359 | continue; | |
360 | ||
361 | if (*reason == '\0') { | |
9311d0c4 | 362 | ERR_raise(ERR_LIB_HTTP, HTTP_R_RESPONSE_PARSE_ERROR); |
29f178bd DDO |
363 | return 0; |
364 | } | |
365 | ||
366 | /* Set end of response code and start of message */ | |
367 | *reason++ = '\0'; | |
368 | ||
369 | /* Attempt to parse numeric code */ | |
370 | retcode = strtoul(code, &end, 10); | |
371 | ||
372 | if (*end != '\0') | |
373 | return 0; | |
374 | ||
375 | /* Skip over any leading whitespace in message */ | |
376 | while (*reason != '\0' && ossl_isspace(*reason)) | |
377 | reason++; | |
378 | ||
379 | if (*reason != '\0') { | |
380 | /* | |
381 | * Finally zap any trailing whitespace in message (include CRLF) | |
382 | */ | |
383 | ||
384 | /* chop any trailing whitespace from reason */ | |
385 | /* We know reason has a non-whitespace character so this is OK */ | |
386 | for (end = reason + strlen(reason) - 1; ossl_isspace(*end); end--) | |
387 | *end = '\0'; | |
388 | } | |
389 | ||
390 | switch (retcode) { | |
391 | case HTTP_STATUS_CODE_OK: | |
392 | case HTTP_STATUS_CODE_MOVED_PERMANENTLY: | |
393 | case HTTP_STATUS_CODE_FOUND: | |
394 | return retcode; | |
395 | default: | |
396 | if (retcode < 400) | |
a150f8e1 | 397 | retcode = HTTP_R_STATUS_CODE_UNSUPPORTED; |
29f178bd | 398 | else |
a150f8e1 | 399 | retcode = HTTP_R_RECEIVED_ERROR; |
29f178bd | 400 | if (*reason == '\0') |
a150f8e1 | 401 | ERR_raise_data(ERR_LIB_HTTP, retcode, "Code=%s", code); |
29f178bd | 402 | else |
a150f8e1 RL |
403 | ERR_raise_data(ERR_LIB_HTTP, retcode, |
404 | "Code=%s, Reason=%s", code, reason); | |
29f178bd DDO |
405 | return 0; |
406 | } | |
407 | } | |
408 | ||
409 | static int check_set_resp_len(OSSL_HTTP_REQ_CTX *rctx, unsigned long len) | |
410 | { | |
a150f8e1 RL |
411 | if (len > rctx->max_resp_len) |
412 | ERR_raise_data(ERR_LIB_HTTP, HTTP_R_MAX_RESP_LEN_EXCEEDED, | |
413 | "length=%lu, max=%lu", len, rctx->max_resp_len); | |
414 | if (rctx->resp_len != 0 && rctx->resp_len != len) | |
415 | ERR_raise_data(ERR_LIB_HTTP, HTTP_R_INCONSISTENT_CONTENT_LENGTH, | |
d337af18 DDO |
416 | "ASN.1 length=%lu, Content-Length=%lu", |
417 | len, rctx->resp_len); | |
29f178bd DDO |
418 | rctx->resp_len = len; |
419 | return 1; | |
420 | } | |
421 | ||
422 | /* | |
423 | * Try exchanging request and response via HTTP on (non-)blocking BIO in rctx. | |
424 | * Returns 1 on success, 0 on error or redirection, -1 on BIO_should_retry. | |
425 | */ | |
426 | int OSSL_HTTP_REQ_CTX_nbio(OSSL_HTTP_REQ_CTX *rctx) | |
427 | { | |
428 | int i; | |
673474b1 | 429 | long n; |
29f178bd DDO |
430 | unsigned long resp_len; |
431 | const unsigned char *p; | |
432 | char *key, *value, *line_end = NULL; | |
433 | ||
434 | if (rctx == NULL) { | |
9311d0c4 | 435 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
436 | return 0; |
437 | } | |
a6d40689 DDO |
438 | if (rctx->mem == NULL || rctx->wbio == NULL || rctx->rbio == NULL) { |
439 | ERR_raise(ERR_LIB_HTTP, ERR_R_SHOULD_NOT_HAVE_BEEN_CALLED); | |
440 | return 0; | |
441 | } | |
29f178bd DDO |
442 | |
443 | rctx->redirection_url = NULL; | |
444 | next_io: | |
445 | if ((rctx->state & OHS_NOREAD) == 0) { | |
d337af18 | 446 | n = BIO_read(rctx->rbio, rctx->readbuf, rctx->readbuflen); |
29f178bd DDO |
447 | if (n <= 0) { |
448 | if (BIO_should_retry(rctx->rbio)) | |
449 | return -1; | |
a6d40689 | 450 | ERR_raise(ERR_LIB_HTTP, HTTP_R_FAILED_READING_DATA); |
29f178bd DDO |
451 | return 0; |
452 | } | |
453 | ||
454 | /* Write data to memory BIO */ | |
d337af18 | 455 | if (BIO_write(rctx->mem, rctx->readbuf, n) != n) |
29f178bd DDO |
456 | return 0; |
457 | } | |
458 | ||
459 | switch (rctx->state) { | |
460 | case OHS_HTTP_HEADER: | |
461 | /* Last operation was adding headers: need a final \r\n */ | |
462 | if (BIO_write(rctx->mem, "\r\n", 2) != 2) { | |
463 | rctx->state = OHS_ERROR; | |
464 | return 0; | |
465 | } | |
466 | rctx->state = OHS_WRITE_INIT; | |
467 | ||
468 | /* fall thru */ | |
469 | case OHS_WRITE_INIT: | |
673474b1 | 470 | rctx->len_to_send = BIO_get_mem_data(rctx->mem, NULL); |
29f178bd DDO |
471 | rctx->state = OHS_WRITE; |
472 | ||
473 | /* fall thru */ | |
474 | case OHS_WRITE: | |
673474b1 DDO |
475 | n = BIO_get_mem_data(rctx->mem, &p) - rctx->len_to_send; |
476 | i = BIO_write(rctx->wbio, p + n, rctx->len_to_send); | |
29f178bd DDO |
477 | |
478 | if (i <= 0) { | |
479 | if (BIO_should_retry(rctx->wbio)) | |
480 | return -1; | |
481 | rctx->state = OHS_ERROR; | |
482 | return 0; | |
483 | } | |
484 | ||
673474b1 | 485 | rctx->len_to_send -= i; |
29f178bd | 486 | |
673474b1 | 487 | if (rctx->len_to_send > 0) |
29f178bd DDO |
488 | goto next_io; |
489 | ||
490 | rctx->state = OHS_FLUSH; | |
491 | ||
492 | (void)BIO_reset(rctx->mem); | |
493 | ||
494 | /* fall thru */ | |
495 | case OHS_FLUSH: | |
496 | ||
497 | i = BIO_flush(rctx->wbio); | |
498 | ||
499 | if (i > 0) { | |
500 | rctx->state = OHS_FIRSTLINE; | |
501 | goto next_io; | |
502 | } | |
503 | ||
504 | if (BIO_should_retry(rctx->wbio)) | |
505 | return -1; | |
506 | ||
507 | rctx->state = OHS_ERROR; | |
508 | return 0; | |
509 | ||
510 | case OHS_ERROR: | |
511 | return 0; | |
512 | ||
513 | case OHS_FIRSTLINE: | |
514 | case OHS_HEADERS: | |
515 | case OHS_REDIRECT: | |
516 | ||
517 | /* Attempt to read a line in */ | |
518 | next_line: | |
519 | /* | |
520 | * Due to strange memory BIO behavior with BIO_gets we have to check | |
521 | * there's a complete line in there before calling BIO_gets or we'll | |
522 | * just get a partial read. | |
523 | */ | |
524 | n = BIO_get_mem_data(rctx->mem, &p); | |
525 | if (n <= 0 || memchr(p, '\n', n) == 0) { | |
d337af18 | 526 | if (n >= rctx->readbuflen) { |
29f178bd DDO |
527 | rctx->state = OHS_ERROR; |
528 | return 0; | |
529 | } | |
530 | goto next_io; | |
531 | } | |
d337af18 | 532 | n = BIO_gets(rctx->mem, (char *)rctx->readbuf, rctx->readbuflen); |
29f178bd DDO |
533 | |
534 | if (n <= 0) { | |
535 | if (BIO_should_retry(rctx->mem)) | |
536 | goto next_io; | |
537 | rctx->state = OHS_ERROR; | |
538 | return 0; | |
539 | } | |
540 | ||
541 | /* Don't allow excessive lines */ | |
d337af18 | 542 | if (n == rctx->readbuflen) { |
9311d0c4 | 543 | ERR_raise(ERR_LIB_HTTP, HTTP_R_RESPONSE_LINE_TOO_LONG); |
29f178bd DDO |
544 | rctx->state = OHS_ERROR; |
545 | return 0; | |
546 | } | |
547 | ||
548 | /* First line */ | |
549 | if (rctx->state == OHS_FIRSTLINE) { | |
d337af18 | 550 | switch (parse_http_line1((char *)rctx->readbuf)) { |
29f178bd DDO |
551 | case HTTP_STATUS_CODE_OK: |
552 | rctx->state = OHS_HEADERS; | |
553 | goto next_line; | |
554 | case HTTP_STATUS_CODE_MOVED_PERMANENTLY: | |
555 | case HTTP_STATUS_CODE_FOUND: /* i.e., moved temporarily */ | |
046fba44 | 556 | if (!rctx->method_POST) { /* method is GET */ |
29f178bd DDO |
557 | rctx->state = OHS_REDIRECT; |
558 | goto next_line; | |
559 | } | |
9311d0c4 | 560 | ERR_raise(ERR_LIB_HTTP, HTTP_R_REDIRECTION_NOT_ENABLED); |
29f178bd DDO |
561 | /* redirection is not supported/recommended for POST */ |
562 | /* fall through */ | |
563 | default: | |
564 | rctx->state = OHS_ERROR; | |
565 | return 0; | |
566 | } | |
567 | } | |
d337af18 | 568 | key = (char *)rctx->readbuf; |
29f178bd DDO |
569 | value = strchr(key, ':'); |
570 | if (value != NULL) { | |
571 | *(value++) = '\0'; | |
572 | while (ossl_isspace(*value)) | |
573 | value++; | |
574 | line_end = strchr(value, '\r'); | |
575 | if (line_end == NULL) | |
576 | line_end = strchr(value, '\n'); | |
577 | if (line_end != NULL) | |
578 | *line_end = '\0'; | |
579 | } | |
580 | if (value != NULL && line_end != NULL) { | |
afe554c2 DDO |
581 | if (rctx->state == OHS_REDIRECT |
582 | && strcasecmp(key, "Location") == 0) { | |
29f178bd DDO |
583 | rctx->redirection_url = value; |
584 | return 0; | |
585 | } | |
afe554c2 DDO |
586 | if (rctx->expected_ct != NULL |
587 | && strcasecmp(key, "Content-Type") == 0) { | |
588 | if (strcasecmp(rctx->expected_ct, value) != 0) { | |
a150f8e1 RL |
589 | ERR_raise_data(ERR_LIB_HTTP, HTTP_R_UNEXPECTED_CONTENT_TYPE, |
590 | "expected=%s, actual=%s", | |
591 | rctx->expected_ct, value); | |
29f178bd DDO |
592 | return 0; |
593 | } | |
594 | rctx->expected_ct = NULL; /* content-type has been found */ | |
595 | } | |
afe554c2 | 596 | if (strcasecmp(key, "Content-Length") == 0) { |
29f178bd DDO |
597 | resp_len = strtoul(value, &line_end, 10); |
598 | if (line_end == value || *line_end != '\0') { | |
a150f8e1 RL |
599 | ERR_raise_data(ERR_LIB_HTTP, |
600 | HTTP_R_ERROR_PARSING_CONTENT_LENGTH, | |
601 | "input=%s", value); | |
29f178bd DDO |
602 | return 0; |
603 | } | |
604 | if (!check_set_resp_len(rctx, resp_len)) | |
605 | return 0; | |
606 | } | |
607 | } | |
608 | ||
d337af18 DDO |
609 | /* Look for blank line indicating end of headers */ |
610 | for (p = rctx->readbuf; *p != '\0'; p++) { | |
29f178bd DDO |
611 | if (*p != '\r' && *p != '\n') |
612 | break; | |
613 | } | |
614 | if (*p != '\0') /* not end of headers */ | |
615 | goto next_line; | |
616 | ||
617 | if (rctx->expected_ct != NULL) { | |
a150f8e1 RL |
618 | ERR_raise_data(ERR_LIB_HTTP, HTTP_R_MISSING_CONTENT_TYPE, |
619 | "expected=%s", rctx->expected_ct); | |
29f178bd DDO |
620 | return 0; |
621 | } | |
622 | if (rctx->state == OHS_REDIRECT) { | |
623 | /* http status code indicated redirect but there was no Location */ | |
9311d0c4 | 624 | ERR_raise(ERR_LIB_HTTP, HTTP_R_MISSING_REDIRECT_LOCATION); |
29f178bd DDO |
625 | return 0; |
626 | } | |
627 | ||
628 | if (!rctx->expect_asn1) { | |
629 | rctx->state = OHS_CONTENT; | |
630 | goto content; | |
631 | } | |
632 | ||
633 | rctx->state = OHS_ASN1_HEADER; | |
634 | ||
635 | /* Fall thru */ | |
636 | case OHS_ASN1_HEADER: | |
637 | /* | |
638 | * Now reading ASN1 header: can read at least 2 bytes which is enough | |
639 | * for ASN1 SEQUENCE header and either length field or at least the | |
640 | * length of the length field. | |
641 | */ | |
642 | n = BIO_get_mem_data(rctx->mem, &p); | |
643 | if (n < 2) | |
644 | goto next_io; | |
645 | ||
646 | /* Check it is an ASN1 SEQUENCE */ | |
647 | if (*p++ != (V_ASN1_SEQUENCE | V_ASN1_CONSTRUCTED)) { | |
9311d0c4 | 648 | ERR_raise(ERR_LIB_HTTP, HTTP_R_MISSING_ASN1_ENCODING); |
29f178bd DDO |
649 | return 0; |
650 | } | |
651 | ||
652 | /* Check out length field */ | |
653 | if ((*p & 0x80) != 0) { | |
654 | /* | |
655 | * If MSB set on initial length octet we can now always read 6 | |
656 | * octets: make sure we have them. | |
657 | */ | |
658 | if (n < 6) | |
659 | goto next_io; | |
660 | n = *p & 0x7F; | |
661 | /* Not NDEF or excessive length */ | |
662 | if (n == 0 || (n > 4)) { | |
9311d0c4 | 663 | ERR_raise(ERR_LIB_HTTP, HTTP_R_ERROR_PARSING_ASN1_LENGTH); |
29f178bd DDO |
664 | return 0; |
665 | } | |
666 | p++; | |
667 | resp_len = 0; | |
668 | for (i = 0; i < n; i++) { | |
669 | resp_len <<= 8; | |
670 | resp_len |= *p++; | |
671 | } | |
672 | resp_len += n + 2; | |
673 | } else { | |
674 | resp_len = *p + 2; | |
675 | } | |
676 | if (!check_set_resp_len(rctx, resp_len)) | |
677 | return 0; | |
678 | ||
679 | content: | |
680 | rctx->state = OHS_CONTENT; | |
681 | ||
682 | /* Fall thru */ | |
683 | case OHS_CONTENT: | |
684 | default: | |
685 | n = BIO_get_mem_data(rctx->mem, NULL); | |
686 | if (n < (long)rctx->resp_len /* may be 0 if no Content-Type or ASN.1 */) | |
687 | goto next_io; | |
688 | ||
689 | rctx->state = OHS_DONE; | |
690 | return 1; | |
691 | } | |
692 | } | |
693 | ||
694 | #ifndef OPENSSL_NO_SOCK | |
695 | ||
696 | /* set up a new connection BIO, to HTTP server or to HTTP(S) proxy if given */ | |
afe554c2 DDO |
697 | static BIO *HTTP_new_bio(const char *server /* optionally includes ":port" */, |
698 | const char *server_port /* explicit server port */, | |
699 | const char *proxy /* optionally includes ":port" */) | |
29f178bd | 700 | { |
afe554c2 DDO |
701 | const char *host = server, *host_end; |
702 | char host_name[100]; | |
29f178bd DDO |
703 | const char *port = server_port; |
704 | BIO *cbio; | |
705 | ||
4b1fe471 | 706 | if (!ossl_assert(server != NULL)) |
29f178bd | 707 | return NULL; |
29f178bd DDO |
708 | |
709 | if (proxy != NULL) { | |
710 | host = proxy; | |
afe554c2 | 711 | port = NULL; |
29f178bd | 712 | } |
afe554c2 DDO |
713 | |
714 | host_end = strchr(host, '/'); | |
5a640713 MC |
715 | if (host_end != NULL) { |
716 | size_t host_len = host_end - host; | |
717 | ||
718 | if (host_len < sizeof(host_name)) { | |
719 | /* chop trailing string starting with '/' */ | |
720 | strncpy(host_name, host, host_len); | |
721 | host_name[host_len] = '\0'; | |
722 | host = host_name; | |
723 | } | |
afe554c2 DDO |
724 | } |
725 | ||
726 | cbio = BIO_new_connect(host /* optionally includes ":port" */); | |
29f178bd DDO |
727 | if (cbio == NULL) |
728 | goto end; | |
729 | if (port != NULL) | |
730 | (void)BIO_set_conn_port(cbio, port); | |
731 | ||
732 | end: | |
733 | return cbio; | |
734 | } | |
e8d0819d | 735 | #endif /* OPENSSL_NO_SOCK */ |
29f178bd DDO |
736 | |
737 | static ASN1_VALUE *BIO_mem_d2i(BIO *mem, const ASN1_ITEM *it) | |
738 | { | |
739 | const unsigned char *p; | |
740 | long len = BIO_get_mem_data(mem, &p); | |
741 | ASN1_VALUE *resp = ASN1_item_d2i(NULL, &p, len, it); | |
742 | ||
743 | if (resp == NULL) | |
9311d0c4 | 744 | ERR_raise(ERR_LIB_HTTP, HTTP_R_RESPONSE_PARSE_ERROR); |
29f178bd DDO |
745 | return resp; |
746 | } | |
747 | ||
748 | static BIO *OSSL_HTTP_REQ_CTX_transfer(OSSL_HTTP_REQ_CTX *rctx) | |
749 | { | |
750 | int sending = 1; | |
751 | int rv; | |
752 | ||
753 | if (rctx == NULL) { | |
9311d0c4 | 754 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
755 | return NULL; |
756 | } | |
757 | ||
758 | for (;;) { | |
759 | rv = OSSL_HTTP_REQ_CTX_nbio(rctx); | |
760 | if (rv != -1) | |
761 | break; | |
762 | /* BIO_should_retry was true */ | |
763 | sending = 0; | |
764 | /* will not actually wait if rctx->max_time == 0 */ | |
e8d0819d | 765 | if (BIO_wait(rctx->rbio, rctx->max_time, 100 /* milliseconds */) <= 0) |
29f178bd DDO |
766 | return NULL; |
767 | } | |
768 | ||
769 | if (rv == 0) { | |
770 | if (rctx->redirection_url == NULL) { /* an error occurred */ | |
771 | if (sending && (rctx->state & OHS_NOREAD) != 0) | |
9311d0c4 | 772 | ERR_raise(ERR_LIB_HTTP, HTTP_R_ERROR_SENDING); |
29f178bd | 773 | else |
9311d0c4 | 774 | ERR_raise(ERR_LIB_HTTP, HTTP_R_ERROR_RECEIVING); |
29f178bd DDO |
775 | } |
776 | return NULL; | |
777 | } | |
778 | if (!BIO_up_ref(rctx->mem)) | |
779 | return NULL; | |
780 | return rctx->mem; | |
781 | } | |
782 | ||
783 | /* Exchange ASN.1-encoded request and response via HTTP on (non-)blocking BIO */ | |
784 | ASN1_VALUE *OSSL_HTTP_REQ_CTX_sendreq_d2i(OSSL_HTTP_REQ_CTX *rctx, | |
785 | const ASN1_ITEM *it) | |
786 | { | |
787 | if (rctx == NULL || it == NULL) { | |
9311d0c4 | 788 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
789 | return NULL; |
790 | } | |
791 | return BIO_mem_d2i(OSSL_HTTP_REQ_CTX_transfer(rctx), it); | |
792 | } | |
793 | ||
794 | static int update_timeout(int timeout, time_t start_time) | |
795 | { | |
796 | long elapsed_time; | |
797 | ||
798 | if (timeout == 0) | |
799 | return 0; | |
800 | elapsed_time = (long)(time(NULL) - start_time); /* this might overflow */ | |
801 | return timeout <= elapsed_time ? -1 : timeout - elapsed_time; | |
802 | } | |
803 | ||
804 | /*- | |
805 | * Exchange HTTP request and response with the given server. | |
806 | * If req_mem == NULL then use GET and ignore content_type, else POST. | |
807 | * The redirection_url output (freed by caller) parameter is used only for GET. | |
808 | * | |
809 | * Typically the bio and rbio parameters are NULL and a network BIO is created | |
810 | * internally for connecting to the given server and port, optionally via a | |
811 | * proxy and its port, and is then used for exchanging the request and response. | |
812 | * If bio is given and rbio is NULL then this BIO is used instead. | |
813 | * If both bio and rbio are given (which may be memory BIOs for instance) | |
814 | * then no explicit connection is attempted, | |
815 | * bio is used for writing the request, and rbio for reading the response. | |
816 | * | |
817 | * bio_update_fn is an optional BIO connect/disconnect callback function, | |
818 | * which has the prototype | |
819 | * BIO *(*OSSL_HTTP_bio_cb_t) (BIO *bio, void *arg, int conn, int detail); | |
820 | * The callback may modify the HTTP BIO provided in the bio argument, | |
821 | * whereby it may make use of any custom defined argument 'arg'. | |
e98c7350 | 822 | * During connection establishment, just after BIO_do_connect_retry(), |
29f178bd DDO |
823 | * the callback function is invoked with the 'conn' argument being 1 |
824 | * 'detail' indicating whether a HTTPS (i.e., TLS) connection is requested. | |
825 | * On disconnect 'conn' is 0 and 'detail' indicates that no error occurred. | |
826 | * For instance, on connect the funct may prepend a TLS BIO to implement HTTPS; | |
827 | * after disconnect it may do some error diagnostics and/or specific cleanup. | |
828 | * The function should return NULL to indicate failure. | |
829 | * After disconnect the modified BIO will be deallocated using BIO_free_all(). | |
830 | */ | |
831 | BIO *OSSL_HTTP_transfer(const char *server, const char *port, const char *path, | |
afe554c2 | 832 | int use_ssl, const char *proxy, const char *no_proxy, |
29f178bd DDO |
833 | BIO *bio, BIO *rbio, |
834 | OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, | |
835 | const STACK_OF(CONF_VALUE) *headers, | |
836 | const char *content_type, BIO *req_mem, | |
837 | int maxline, unsigned long max_resp_len, int timeout, | |
838 | const char *expected_ct, int expect_asn1, | |
839 | char **redirection_url) | |
840 | { | |
841 | time_t start_time = timeout > 0 ? time(NULL) : 0; | |
842 | BIO *cbio; /* = bio if present, used as connection BIO if rbio is NULL */ | |
843 | OSSL_HTTP_REQ_CTX *rctx; | |
844 | BIO *resp = NULL; | |
845 | ||
846 | if (redirection_url != NULL) | |
847 | *redirection_url = NULL; /* do this beforehand to prevent dbl free */ | |
848 | ||
849 | if (use_ssl && bio_update_fn == NULL) { | |
9311d0c4 | 850 | ERR_raise(ERR_LIB_HTTP, HTTP_R_TLS_NOT_ENABLED); |
29f178bd DDO |
851 | return NULL; |
852 | } | |
853 | if (rbio != NULL && (bio == NULL || bio_update_fn != NULL)) { | |
9311d0c4 | 854 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_INVALID_ARGUMENT); |
29f178bd DDO |
855 | return NULL; |
856 | } | |
29f178bd | 857 | |
4b1fe471 | 858 | if (bio != NULL) { |
29f178bd | 859 | cbio = bio; |
4b1fe471 | 860 | } else { |
e8d0819d | 861 | #ifndef OPENSSL_NO_SOCK |
4b1fe471 | 862 | if (server == NULL) { |
9311d0c4 | 863 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
4b1fe471 DDO |
864 | return NULL; |
865 | } | |
866 | if (*port == '\0') | |
867 | port = NULL; | |
868 | if (port == NULL && strchr(server, ':') == NULL) | |
869 | port = use_ssl ? OSSL_HTTPS_PORT : OSSL_HTTP_PORT; | |
870 | proxy = http_adapt_proxy(proxy, no_proxy, server, use_ssl); | |
afe554c2 | 871 | if ((cbio = HTTP_new_bio(server, port, proxy)) == NULL) |
e8d0819d DDO |
872 | return NULL; |
873 | #else | |
9311d0c4 | 874 | ERR_raise(ERR_LIB_HTTP, HTTP_R_SOCK_NOT_SUPPORTED); |
29f178bd | 875 | return NULL; |
e8d0819d | 876 | #endif |
4b1fe471 DDO |
877 | } |
878 | /* remaining parameters are checked indirectly by the functions called */ | |
29f178bd DDO |
879 | |
880 | (void)ERR_set_mark(); /* prepare removing any spurious libssl errors */ | |
e98c7350 | 881 | if (rbio == NULL && BIO_do_connect_retry(cbio, timeout, -1) <= 0) |
29f178bd DDO |
882 | goto end; |
883 | /* now timeout is guaranteed to be >= 0 */ | |
884 | ||
885 | /* callback can be used to wrap or prepend TLS session */ | |
886 | if (bio_update_fn != NULL) { | |
887 | BIO *orig_bio = cbio; | |
888 | cbio = (*bio_update_fn)(cbio, arg, 1 /* connect */, use_ssl); | |
889 | if (cbio == NULL) { | |
890 | cbio = orig_bio; | |
891 | goto end; | |
892 | } | |
893 | } | |
894 | ||
895 | rctx = HTTP_REQ_CTX_new(cbio, rbio != NULL ? rbio : cbio, | |
896 | !use_ssl && proxy != NULL, server, port, path, | |
897 | headers, content_type, req_mem, maxline, | |
898 | max_resp_len, update_timeout(timeout, start_time), | |
899 | expected_ct, expect_asn1); | |
900 | if (rctx == NULL) | |
901 | goto end; | |
902 | ||
903 | resp = OSSL_HTTP_REQ_CTX_transfer(rctx); | |
904 | if (resp == NULL) { | |
905 | if (rctx->redirection_url != NULL) { | |
906 | if (redirection_url == NULL) | |
9311d0c4 | 907 | ERR_raise(ERR_LIB_HTTP, HTTP_R_REDIRECTION_NOT_ENABLED); |
29f178bd DDO |
908 | else |
909 | /* may be NULL if out of memory: */ | |
910 | *redirection_url = OPENSSL_strdup(rctx->redirection_url); | |
911 | } else { | |
912 | char buf[200]; | |
913 | unsigned long err = ERR_peek_error(); | |
914 | int lib = ERR_GET_LIB(err); | |
915 | int reason = ERR_GET_REASON(err); | |
916 | ||
917 | if (lib == ERR_LIB_SSL || lib == ERR_LIB_HTTP | |
918 | || (lib == ERR_LIB_BIO && reason == BIO_R_CONNECT_TIMEOUT) | |
919 | || (lib == ERR_LIB_BIO && reason == BIO_R_CONNECT_ERROR) | |
4b1fe471 | 920 | #ifndef OPENSSL_NO_CMP |
29f178bd | 921 | || (lib == ERR_LIB_CMP |
100cc8b0 | 922 | && reason == CMP_R_POTENTIALLY_INVALID_CERTIFICATE) |
4b1fe471 | 923 | #endif |
100cc8b0 | 924 | ) { |
29f178bd DDO |
925 | BIO_snprintf(buf, 200, "server=%s:%s", server, port); |
926 | ERR_add_error_data(1, buf); | |
afe554c2 DDO |
927 | if (proxy != NULL) |
928 | ERR_add_error_data(2, " proxy=", proxy); | |
29f178bd | 929 | if (err == 0) { |
afe554c2 | 930 | BIO_snprintf(buf, 200, " peer has disconnected%s", |
29f178bd DDO |
931 | use_ssl ? " violating the protocol" : |
932 | ", likely because it requires the use of TLS"); | |
933 | ERR_add_error_data(1, buf); | |
934 | } | |
935 | } | |
936 | } | |
937 | } | |
938 | OSSL_HTTP_REQ_CTX_free(rctx); | |
939 | ||
940 | /* callback can be used to clean up TLS session */ | |
941 | if (bio_update_fn != NULL | |
942 | && (*bio_update_fn)(cbio, arg, 0, resp != NULL) == NULL) { | |
943 | BIO_free(resp); | |
944 | resp = NULL; | |
945 | } | |
946 | ||
947 | end: | |
948 | /* | |
949 | * Use BIO_free_all() because bio_update_fn may prepend or append to cbio. | |
950 | * This also frees any (e.g., SSL/TLS) BIOs linked with bio and, | |
951 | * like BIO_reset(bio), calls SSL_shutdown() to notify/alert the peer. | |
952 | */ | |
953 | if (bio == NULL) /* cbio was not provided by caller */ | |
954 | BIO_free_all(cbio); | |
955 | ||
956 | if (resp != NULL) | |
957 | /* remove any spurious error queue entries by ssl_add_cert_chain() */ | |
958 | (void)ERR_pop_to_mark(); | |
959 | else | |
960 | (void)ERR_clear_last_mark(); | |
961 | ||
962 | return resp; | |
963 | } | |
964 | ||
965 | static int redirection_ok(int n_redir, const char *old_url, const char *new_url) | |
966 | { | |
4b1fe471 | 967 | size_t https_len = strlen(OSSL_HTTPS_NAME":"); |
29f178bd DDO |
968 | |
969 | if (n_redir >= HTTP_VERSION_MAX_REDIRECTIONS) { | |
9311d0c4 | 970 | ERR_raise(ERR_LIB_HTTP, HTTP_R_TOO_MANY_REDIRECTIONS); |
29f178bd DDO |
971 | return 0; |
972 | } | |
973 | if (*new_url == '/') /* redirection to same server => same protocol */ | |
974 | return 1; | |
4b1fe471 DDO |
975 | if (strncmp(old_url, OSSL_HTTPS_NAME":", https_len) == 0 && |
976 | strncmp(new_url, OSSL_HTTPS_NAME":", https_len) != 0) { | |
9311d0c4 | 977 | ERR_raise(ERR_LIB_HTTP, HTTP_R_REDIRECTION_FROM_HTTPS_TO_HTTP); |
29f178bd DDO |
978 | return 0; |
979 | } | |
980 | return 1; | |
981 | } | |
982 | ||
983 | /* Get data via HTTP from server at given URL, potentially with redirection */ | |
afe554c2 | 984 | BIO *OSSL_HTTP_get(const char *url, const char *proxy, const char *no_proxy, |
29f178bd DDO |
985 | BIO *bio, BIO *rbio, |
986 | OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, | |
987 | const STACK_OF(CONF_VALUE) *headers, | |
988 | int maxline, unsigned long max_resp_len, int timeout, | |
989 | const char *expected_content_type, int expect_asn1) | |
990 | { | |
991 | time_t start_time = timeout > 0 ? time(NULL) : 0; | |
992 | char *current_url, *redirection_url; | |
993 | int n_redirs = 0; | |
994 | char *host; | |
995 | char *port; | |
996 | char *path; | |
997 | int use_ssl; | |
998 | BIO *resp = NULL; | |
999 | ||
1000 | if (url == NULL) { | |
9311d0c4 | 1001 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
1002 | return NULL; |
1003 | } | |
1004 | if ((current_url = OPENSSL_strdup(url)) == NULL) | |
1005 | return NULL; | |
1006 | ||
1007 | for (;;) { | |
d7fcee3b DDO |
1008 | if (!OSSL_HTTP_parse_url(current_url, &host, &port, NULL /* port_num */, |
1009 | &path, &use_ssl)) | |
29f178bd DDO |
1010 | break; |
1011 | ||
1012 | new_rpath: | |
afe554c2 | 1013 | resp = OSSL_HTTP_transfer(host, port, path, use_ssl, proxy, no_proxy, |
29f178bd DDO |
1014 | bio, rbio, |
1015 | bio_update_fn, arg, headers, NULL, NULL, | |
1016 | maxline, max_resp_len, | |
1017 | update_timeout(timeout, start_time), | |
1018 | expected_content_type, expect_asn1, | |
1019 | &redirection_url); | |
1020 | OPENSSL_free(path); | |
1021 | if (resp == NULL && redirection_url != NULL) { | |
1022 | if (redirection_ok(++n_redirs, current_url, redirection_url)) { | |
1023 | (void)BIO_reset(bio); | |
1024 | OPENSSL_free(current_url); | |
1025 | current_url = redirection_url; | |
1026 | if (*redirection_url == '/') { /* redirection to same server */ | |
1027 | path = OPENSSL_strdup(redirection_url); | |
1028 | goto new_rpath; | |
1029 | } | |
1030 | OPENSSL_free(host); | |
1031 | OPENSSL_free(port); | |
1032 | continue; | |
1033 | } | |
1034 | OPENSSL_free(redirection_url); | |
1035 | } | |
1036 | OPENSSL_free(host); | |
1037 | OPENSSL_free(port); | |
1038 | break; | |
1039 | } | |
1040 | OPENSSL_free(current_url); | |
1041 | return resp; | |
1042 | } | |
1043 | ||
1044 | /* Get ASN.1-encoded data via HTTP from server at given URL */ | |
1045 | ASN1_VALUE *OSSL_HTTP_get_asn1(const char *url, | |
afe554c2 | 1046 | const char *proxy, const char *no_proxy, |
29f178bd DDO |
1047 | BIO *bio, BIO *rbio, |
1048 | OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, | |
1049 | const STACK_OF(CONF_VALUE) *headers, | |
1050 | int maxline, unsigned long max_resp_len, | |
1051 | int timeout, const char *expected_content_type, | |
1052 | const ASN1_ITEM *it) | |
1053 | { | |
1054 | BIO *mem; | |
1055 | ASN1_VALUE *resp = NULL; | |
1056 | ||
1057 | if (url == NULL || it == NULL) { | |
9311d0c4 | 1058 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
1059 | return NULL; |
1060 | } | |
afe554c2 | 1061 | if ((mem = OSSL_HTTP_get(url, proxy, no_proxy, bio, rbio, bio_update_fn, |
29f178bd DDO |
1062 | arg, headers, maxline, max_resp_len, timeout, |
1063 | expected_content_type, 1 /* expect_asn1 */)) | |
1064 | != NULL) | |
1065 | resp = BIO_mem_d2i(mem, it); | |
1066 | BIO_free(mem); | |
1067 | return resp; | |
1068 | } | |
1069 | ||
1070 | /* Post ASN.1-encoded request via HTTP to server return ASN.1 response */ | |
1071 | ASN1_VALUE *OSSL_HTTP_post_asn1(const char *server, const char *port, | |
1072 | const char *path, int use_ssl, | |
afe554c2 | 1073 | const char *proxy, const char *no_proxy, |
29f178bd DDO |
1074 | BIO *bio, BIO *rbio, |
1075 | OSSL_HTTP_bio_cb_t bio_update_fn, void *arg, | |
1076 | const STACK_OF(CONF_VALUE) *headers, | |
1077 | const char *content_type, | |
9253f834 | 1078 | const ASN1_VALUE *req, const ASN1_ITEM *req_it, |
29f178bd DDO |
1079 | int maxline, unsigned long max_resp_len, |
1080 | int timeout, const char *expected_ct, | |
1081 | const ASN1_ITEM *rsp_it) | |
1082 | { | |
1083 | BIO *req_mem; | |
1084 | BIO *res_mem; | |
1085 | ASN1_VALUE *resp = NULL; | |
1086 | ||
1087 | if (req == NULL) { | |
9311d0c4 | 1088 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
1089 | return NULL; |
1090 | } | |
1091 | /* remaining parameters are checked indirectly */ | |
1092 | ||
1093 | req_mem = HTTP_asn1_item2bio(req_it, req); | |
afe554c2 | 1094 | res_mem = OSSL_HTTP_transfer(server, port, path, use_ssl, proxy, no_proxy, |
29f178bd DDO |
1095 | bio, rbio, |
1096 | bio_update_fn, arg, headers, content_type, | |
1097 | req_mem /* may be NULL */, maxline, | |
1098 | max_resp_len, timeout, | |
1099 | expected_ct, 1 /* expect_asn1 */, NULL); | |
1100 | BIO_free(req_mem); | |
1101 | if (res_mem != NULL) | |
1102 | resp = BIO_mem_d2i(res_mem, rsp_it); | |
1103 | BIO_free(res_mem); | |
1104 | return resp; | |
1105 | } | |
1106 | ||
1107 | /* BASE64 encoder used for encoding basic proxy authentication credentials */ | |
1108 | static char *base64encode(const void *buf, size_t len) | |
1109 | { | |
1110 | int i; | |
1111 | size_t outl; | |
1112 | char *out; | |
1113 | ||
1114 | /* Calculate size of encoded data */ | |
1115 | outl = (len / 3); | |
1116 | if (len % 3 > 0) | |
1117 | outl++; | |
1118 | outl <<= 2; | |
1119 | out = OPENSSL_malloc(outl + 1); | |
1120 | if (out == NULL) | |
1121 | return 0; | |
1122 | ||
1123 | i = EVP_EncodeBlock((unsigned char *)out, buf, len); | |
1124 | if (!ossl_assert(0 <= i && (size_t)i <= outl)) { | |
1125 | OPENSSL_free(out); | |
1126 | return NULL; | |
1127 | } | |
1128 | return out; | |
1129 | } | |
1130 | ||
1131 | /* | |
1132 | * Promote the given connection BIO using the CONNECT method for a TLS proxy. | |
1133 | * This is typically called by an app, so bio_err and prog are used unless NULL | |
1134 | * to print additional diagnostic information in a user-oriented way. | |
1135 | */ | |
1136 | int OSSL_HTTP_proxy_connect(BIO *bio, const char *server, const char *port, | |
1137 | const char *proxyuser, const char *proxypass, | |
1138 | int timeout, BIO *bio_err, const char *prog) | |
1139 | { | |
4b1fe471 DDO |
1140 | #undef BUF_SIZE |
1141 | #define BUF_SIZE (8 * 1024) | |
29f178bd DDO |
1142 | char *mbuf = OPENSSL_malloc(BUF_SIZE); |
1143 | char *mbufp; | |
1144 | int read_len = 0; | |
29f178bd DDO |
1145 | int ret = 0; |
1146 | BIO *fbio = BIO_new(BIO_f_buffer()); | |
e8d0819d | 1147 | int rv; |
29f178bd DDO |
1148 | time_t max_time = timeout > 0 ? time(NULL) + timeout : 0; |
1149 | ||
4b1fe471 | 1150 | if (bio == NULL || server == NULL |
29f178bd | 1151 | || (bio_err != NULL && prog == NULL)) { |
9311d0c4 | 1152 | ERR_raise(ERR_LIB_HTTP, ERR_R_PASSED_NULL_PARAMETER); |
29f178bd DDO |
1153 | goto end; |
1154 | } | |
4b1fe471 DDO |
1155 | if (port == NULL || *port == '\0') |
1156 | port = OSSL_HTTPS_PORT; | |
29f178bd DDO |
1157 | |
1158 | if (mbuf == NULL || fbio == NULL) { | |
1159 | BIO_printf(bio_err /* may be NULL */, "%s: out of memory", prog); | |
1160 | goto end; | |
1161 | } | |
1162 | BIO_push(fbio, bio); | |
1163 | ||
1164 | BIO_printf(fbio, "CONNECT %s:%s "HTTP_PREFIX"1.0\r\n", server, port); | |
1165 | ||
1166 | /* | |
1167 | * Workaround for broken proxies which would otherwise close | |
1168 | * the connection when entering tunnel mode (e.g., Squid 2.6) | |
1169 | */ | |
1170 | BIO_printf(fbio, "Proxy-Connection: Keep-Alive\r\n"); | |
1171 | ||
1172 | /* Support for basic (base64) proxy authentication */ | |
1173 | if (proxyuser != NULL) { | |
1174 | size_t len = strlen(proxyuser) + 1; | |
1175 | char *proxyauth, *proxyauthenc = NULL; | |
1176 | ||
1177 | if (proxypass != NULL) | |
1178 | len += strlen(proxypass); | |
1179 | proxyauth = OPENSSL_malloc(len + 1); | |
1180 | if (proxyauth == NULL) | |
1181 | goto end; | |
1182 | if (BIO_snprintf(proxyauth, len + 1, "%s:%s", proxyuser, | |
1183 | proxypass != NULL ? proxypass : "") != (int)len) | |
1184 | goto proxy_end; | |
1185 | proxyauthenc = base64encode(proxyauth, len); | |
1186 | if (proxyauthenc != NULL) { | |
1187 | BIO_printf(fbio, "Proxy-Authorization: Basic %s\r\n", proxyauthenc); | |
1188 | OPENSSL_clear_free(proxyauthenc, strlen(proxyauthenc)); | |
1189 | } | |
a6d40689 | 1190 | proxy_end: |
29f178bd DDO |
1191 | OPENSSL_clear_free(proxyauth, len); |
1192 | if (proxyauthenc == NULL) | |
1193 | goto end; | |
1194 | } | |
1195 | ||
1196 | /* Terminate the HTTP CONNECT request */ | |
1197 | BIO_printf(fbio, "\r\n"); | |
1198 | ||
1199 | for (;;) { | |
1200 | if (BIO_flush(fbio) != 0) | |
1201 | break; | |
1202 | /* potentially needs to be retried if BIO is non-blocking */ | |
1203 | if (!BIO_should_retry(fbio)) | |
1204 | break; | |
1205 | } | |
1206 | ||
1207 | for (;;) { | |
1208 | /* will not actually wait if timeout == 0 */ | |
e8d0819d | 1209 | rv = BIO_wait(fbio, max_time, 100 /* milliseconds */); |
29f178bd DDO |
1210 | if (rv <= 0) { |
1211 | BIO_printf(bio_err, "%s: HTTP CONNECT %s\n", prog, | |
1212 | rv == 0 ? "timed out" : "failed waiting for data"); | |
1213 | goto end; | |
1214 | } | |
1215 | ||
1216 | /*- | |
1217 | * The first line is the HTTP response. | |
1218 | * According to RFC 7230, it is formatted exactly like this: | |
1219 | * HTTP/d.d ddd Reason text\r\n | |
1220 | */ | |
1221 | read_len = BIO_gets(fbio, mbuf, BUF_SIZE); | |
1222 | /* the BIO may not block, so we must wait for the 1st line to come in */ | |
1223 | if (read_len < HTTP_LINE1_MINLEN) | |
1224 | continue; | |
1225 | ||
1226 | /* RFC 7231 4.3.6: any 2xx status code is valid */ | |
1227 | if (strncmp(mbuf, HTTP_PREFIX, strlen(HTTP_PREFIX)) != 0) { | |
9311d0c4 | 1228 | ERR_raise(ERR_LIB_HTTP, HTTP_R_RESPONSE_PARSE_ERROR); |
29f178bd DDO |
1229 | BIO_printf(bio_err, "%s: HTTP CONNECT failed, non-HTTP response\n", |
1230 | prog); | |
1231 | /* Wrong protocol, not even HTTP, so stop reading headers */ | |
1232 | goto end; | |
1233 | } | |
1234 | mbufp = mbuf + strlen(HTTP_PREFIX); | |
1235 | if (strncmp(mbufp, HTTP_VERSION_PATT, strlen(HTTP_VERSION_PATT)) != 0) { | |
9311d0c4 | 1236 | ERR_raise(ERR_LIB_HTTP, HTTP_R_RECEIVED_WRONG_HTTP_VERSION); |
29f178bd DDO |
1237 | BIO_printf(bio_err, |
1238 | "%s: HTTP CONNECT failed, bad HTTP version %.*s\n", | |
1239 | prog, HTTP_VERSION_STR_LEN, mbufp); | |
1240 | goto end; | |
1241 | } | |
1242 | mbufp += HTTP_VERSION_STR_LEN; | |
1243 | if (strncmp(mbufp, " 2", strlen(" 2")) != 0) { | |
1244 | mbufp += 1; | |
1245 | /* chop any trailing whitespace */ | |
1246 | while (read_len > 0 && ossl_isspace(mbuf[read_len - 1])) | |
1247 | read_len--; | |
1248 | mbuf[read_len] = '\0'; | |
a150f8e1 RL |
1249 | ERR_raise_data(ERR_LIB_HTTP, HTTP_R_CONNECT_FAILURE, |
1250 | "Reason=%s", mbufp); | |
29f178bd DDO |
1251 | BIO_printf(bio_err, "%s: HTTP CONNECT failed, Reason=%s\n", |
1252 | prog, mbufp); | |
1253 | goto end; | |
1254 | } | |
1255 | ret = 1; | |
1256 | break; | |
1257 | } | |
1258 | ||
1259 | /* Read past all following headers */ | |
1260 | do { | |
1261 | /* | |
1262 | * TODO: This does not necessarily catch the case when the full | |
1263 | * HTTP response came in in more than a single TCP message. | |
1264 | */ | |
1265 | read_len = BIO_gets(fbio, mbuf, BUF_SIZE); | |
1266 | } while (read_len > 2); | |
1267 | ||
1268 | end: | |
1269 | if (fbio != NULL) { | |
1270 | (void)BIO_flush(fbio); | |
1271 | BIO_pop(fbio); | |
1272 | BIO_free(fbio); | |
1273 | } | |
1274 | OPENSSL_free(mbuf); | |
1275 | return ret; | |
4b1fe471 | 1276 | #undef BUF_SIZE |
29f178bd | 1277 | } |