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