HTTPScode https_recv(struct https_request *req, int *code, const char **body, int *len, int msecs) { int n, err; if (BIO_reset(req->body) != 1) { ctx->errstr = _SSL_strerror(); return (HTTPS_ERR_LIB); } /* Read loop sentinel set by parser in __on_message_done() */ while (!req->done) { while ((n = BIO_read(req->cbio, ctx->parse_buf, sizeof(ctx->parse_buf))) <= 0) { if ((n = _BIO_wait(req->cbio, msecs)) != 1) { ctx->errstr = n ? _SSL_strerror() : "Connection closed"; return (HTTPS_ERR_SERVER); } } if ((err = http_parser_execute(req->parser, &ctx->parse_settings, ctx->parse_buf, n)) != n) { ctx->errstr = http_errno_description(err); return (HTTPS_ERR_SERVER); } } *len = BIO_get_mem_data(req->body, (char **)body); *code = req->parser->status_code; return (HTTPS_OK); }
HTTPScode https_send(struct https_request *req, const char *method, const char *uri, const char *qs, const char *hdrs) { char *p; int n, qsbody; req->done = 0; /* Format HTTP/1.1 request line */ qsbody = (strcmp(method, "GET") != 0 && strcmp(method, "HEAD") != 0 && strcmp(method, "DELETE") != 0); BIO_printf(req->cbio, "%s %s", method, uri); if (qs != NULL && !qsbody) BIO_printf(req->cbio, "?%s", qs); BIO_puts(req->cbio, " HTTP/1.1\r\n"); /* Add host header */ BIO_printf(req->cbio, "Host: %s", req->host); if (strcmp(req->port, "443") != 0) BIO_printf(req->cbio, ":%s", req->port); BIO_puts(req->cbio, "\r\n"); /* Add User-agent header */ BIO_printf(req->cbio, "User-Agent: %s\r\n", ctx->useragent); /* Add headers */ if (hdrs != NULL && (p = strdup(hdrs)) != NULL) { BIO_printf(req->cbio, "%s\r\n", strtok(p, "\r\n")); while ((p = strtok(NULL, "\r\n")) != NULL && *p) BIO_printf(req->cbio, "%s\r\n", p); } /* Finish request */ if (qs != NULL && qsbody) { BIO_printf(req->cbio, "Content-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %d\r\n", (int)strlen(qs)); BIO_printf(req->cbio, "\r\n%s", qs); } else { BIO_puts(req->cbio, "\r\n\r\n"); } /* Send request */ while (BIO_flush(req->cbio) != 1) { if ((n = _BIO_wait(req->cbio, -1)) != 1) { ctx->errstr = n ? _SSL_strerror() : "Write timed out"; return (HTTPS_ERR_SERVER); } } return (HTTPS_OK); }
HTTPScode https_recv(struct https_request *req, int *code, const char **body, int *len, int msecs_timeout) { BUF_MEM *bm; int n, err; if (BIO_reset(req->body) != 1) { ctx->errstr = _SSL_strerror(); return (HTTPS_ERR_LIB); } /* Read loop sentinel set by parser in __on_message_done() */ while (!req->done) { while ((n = BIO_read(req->cbio, ctx->parse_buf, sizeof(ctx->parse_buf))) <= 0) { if ((n = _BIO_wait(req->cbio, msecs_timeout)) != 1) { ctx->errstr = n ? _SSL_strerror() : "Connection closed"; return (HTTPS_ERR_SERVER); } } if ((err = http_parser_execute(req->parser, &ctx->parse_settings, ctx->parse_buf, n)) != n) { ctx->errstr = http_errno_description(err); return (HTTPS_ERR_SERVER); } } /* XXX - NUL-terminate body for string parsing */ BIO_write(req->body, "\0", 1); BIO_get_mem_ptr(req->body, &bm); *code = req->parser->status_code; *body = bm->data; *len = bm->length - 1; return (HTTPS_OK); }
HTTPScode https_open(struct https_request **reqp, const char *host) { struct https_request *req; BIO *b64, *sbio; char *p; int n; int connection_error = 0; const char *api_host; const char *api_port; /* Set up our handle */ n = 1; if ((req = calloc(1, sizeof(*req))) == NULL || (req->host = strdup(host)) == NULL || (req->parser = malloc(sizeof(http_parser))) == NULL) { ctx->errstr = strerror(errno); https_close(&req); return (HTTPS_ERR_SYSTEM); } if ((p = strchr(req->host, ':')) != NULL) { *p = '\0'; req->port = p + 1; } else { req->port = "443"; } if ((req->body = BIO_new(BIO_s_mem())) == NULL) { ctx->errstr = _SSL_strerror(); https_close(&req); return (HTTPS_ERR_LIB); } http_parser_init(req->parser, HTTP_RESPONSE); req->parser->data = req; /* Connect to server */ if (ctx->proxy) { api_host = ctx->proxy; api_port = ctx->proxy_port; } else { api_host = req->host; api_port = req->port; } connection_error = _establish_connection(req, api_host, api_port); if (connection_error != HTTPS_OK) { https_close(&req); return connection_error; } /* Tunnel through proxy, if specified */ if (ctx->proxy != NULL) { BIO_printf(req->cbio, "CONNECT %s:%s HTTP/1.0\r\n" "User-Agent: %s\r\n", req->host, req->port, ctx->useragent); if (ctx->proxy_auth != NULL) { b64 = BIO_push(BIO_new(BIO_f_base64()), BIO_new(BIO_s_mem())); BIO_set_flags(b64,BIO_FLAGS_BASE64_NO_NL); BIO_write(b64, ctx->proxy_auth, strlen(ctx->proxy_auth)); (void)BIO_flush(b64); n = BIO_get_mem_data(b64, &p); BIO_puts(req->cbio, "Proxy-Authorization: Basic "); BIO_write(req->cbio, p, n); BIO_puts(req->cbio, "\r\n"); BIO_free_all(b64); } BIO_puts(req->cbio, "\r\n"); (void)BIO_flush(req->cbio); while ((n = BIO_read(req->cbio, ctx->parse_buf, sizeof(ctx->parse_buf))) <= 0) { _BIO_wait(req->cbio, 5000); } if (strncmp("HTTP/1.0 200", ctx->parse_buf, 12) != 0) { snprintf(ctx->errbuf, sizeof(ctx->errbuf), "Proxy error: %s", ctx->parse_buf); ctx->errstr = strtok(ctx->errbuf, "\r\n"); https_close(&req); if (n < 12 || atoi(ctx->parse_buf + 9) < 500) return (HTTPS_ERR_CLIENT); return (HTTPS_ERR_SERVER); } } /* Establish SSL connection */ if ((sbio = BIO_new_ssl(ctx->ssl_ctx, 1)) == NULL) { https_close(&req); return (HTTPS_ERR_LIB); } req->cbio = BIO_push(sbio, req->cbio); BIO_get_ssl(req->cbio, &req->ssl); while (BIO_do_handshake(req->cbio) <= 0) { if ((n = _BIO_wait(req->cbio, 5000)) != 1) { ctx->errstr = n ? _SSL_strerror() : "SSL handshake timed out"; https_close(&req); return (n ? HTTPS_ERR_SYSTEM : HTTPS_ERR_SERVER); } } /* Validate server certificate name */ if (_SSL_check_server_cert(req->ssl, req->host) != 1) { ctx->errstr = "Certificate name validation failed"; https_close(&req); return (HTTPS_ERR_LIB); } *reqp = req; return (HTTPS_OK); }
/* * Establishes the connection to the Duo server. On successful return, * req->cbio is connected and ready to use. * Return HTTPS_OK on success, error code on failure. */ static HTTPScode _establish_connection(struct https_request * const req, const char * const api_host, const char * const api_port) { #ifndef HAVE_GETADDRINFO /* Systems that don't have getaddrinfo can use the BIO wrappers, but only get IPv4 support. */ int n; if ((req->cbio = BIO_new(BIO_s_connect())) == NULL) { ctx->errstr = _SSL_strerror(); return HTTPS_ERR_LIB; } BIO_set_conn_hostname(req->cbio, api_host); BIO_set_conn_port(req->cbio, api_port); BIO_set_nbio(req->cbio, 1); while (BIO_do_connect(req->cbio) <= 0) { if ((n = _BIO_wait(req->cbio, 10000)) != 1) { ctx->errstr = n ? _SSL_strerror() : "Connection timed out"; return (n ? HTTPS_ERR_SYSTEM : HTTPS_ERR_SERVER); } } return HTTPS_OK; #else /* HAVE_GETADDRINFO */ /* IPv6 Support * BIO wrapped io does not support IPv6 addressing. To work around, * resolve the address and connect the socket manually. Then pass * the connected socket to the BIO wrapper with BIO_new_socket. */ int connected_socket = -1; int socket_error = 0; /* Address Lookup */ struct addrinfo *res = NULL; struct addrinfo *cur_res = NULL; struct addrinfo hints; int error; memset(&hints, 0, sizeof(hints)); hints.ai_family = PF_UNSPEC; hints.ai_socktype = SOCK_STREAM; error = getaddrinfo(api_host, api_port, &hints, &res); if (error) { ctx->errstr = gai_strerror(error); return HTTPS_ERR_SYSTEM; } /* Connect */ for (cur_res = res; cur_res; cur_res = cur_res->ai_next) { int connretries = 3; while (connected_socket == -1 && connretries--) { int sock_flags; connected_socket = socket(cur_res->ai_family, cur_res->ai_socktype, cur_res->ai_protocol); if (connected_socket == -1) { continue; } sock_flags = fcntl(connected_socket, F_GETFL, 0); fcntl(connected_socket, F_SETFL, sock_flags|O_NONBLOCK); if (connect(connected_socket, cur_res->ai_addr, cur_res->ai_addrlen) != 0 && errno != EINPROGRESS) { close(connected_socket); connected_socket = -1; break; } socket_error = _fd_wait(connected_socket, 10000); if (socket_error != 1) { close(connected_socket); connected_socket = -1; continue; } /* Connected! */ break; } } cur_res = NULL; freeaddrinfo(res); res = NULL; if (connected_socket == -1) { ctx->errstr = "Failed to connect"; return socket_error ? HTTPS_ERR_SYSTEM : HTTPS_ERR_SERVER; } if ((req->cbio = BIO_new_socket(connected_socket, BIO_CLOSE)) == NULL) { ctx->errstr = _SSL_strerror(); return (HTTPS_ERR_LIB); } BIO_set_conn_hostname(req->cbio, api_host); BIO_set_conn_port(req->cbio, api_port); BIO_set_nbio(req->cbio, 1); return HTTPS_OK; #endif /* HAVE_GETADDRINFO */ }
HTTPScode https_send(struct https_request *req, const char *method, const char *uri, int argc, char *argv[]) { BIO *b64; HMAC_CTX hmac; unsigned char MD[SHA_DIGEST_LENGTH]; char *qs, *p; int i, n, is_get; req->done = 0; /* Generate query string and canonical request to sign */ if ((qs = _argv_to_qs(argc, argv)) == NULL || (asprintf(&p, "%s\n%s\n%s\n%s", method, req->host, uri, qs)) < 0) { free(qs); ctx->errstr = strerror(errno); return (HTTPS_ERR_LIB); } /* Format request */ if ((is_get = (strcmp(method, "GET") == 0))) { BIO_printf(req->cbio, "GET %s?%s HTTP/1.1\r\n", uri, qs); } else { BIO_printf(req->cbio, "%s %s HTTP/1.1\r\n", method, uri); } if (strcmp(req->port, "443") == 0) { BIO_printf(req->cbio, "Host: %s\r\n", req->host); } else { BIO_printf(req->cbio, "Host: %s:%s\r\n", req->host, req->port); } /* Add User-Agent header */ BIO_printf(req->cbio, "User-Agent: %s\r\n", ctx->useragent); /* Add signature */ BIO_puts(req->cbio, "Authorization: Basic "); HMAC_CTX_init(&hmac); HMAC_Init(&hmac, ctx->skey, strlen(ctx->skey), EVP_sha1()); HMAC_Update(&hmac, (unsigned char *)p, strlen(p)); HMAC_Final(&hmac, MD, NULL); HMAC_CTX_cleanup(&hmac); free(p); b64 = _BIO_new_base64(); BIO_printf(b64, "%s:", ctx->ikey); for (i = 0; i < sizeof(MD); i++) { BIO_printf(b64, "%02x", MD[i]); } (void)BIO_flush(b64); n = BIO_get_mem_data(b64, &p); BIO_write(req->cbio, p, n); BIO_free_all(b64); /* Finish request */ if (!is_get) { BIO_printf(req->cbio, "\r\nContent-Type: application/x-www-form-urlencoded\r\n" "Content-Length: %d\r\n\r\n%s", (int)strlen(qs), qs); } else { BIO_puts(req->cbio, "\r\n\r\n"); } /* Send request */ while (BIO_flush(req->cbio) != 1) { if ((n = _BIO_wait(req->cbio, -1)) != 1) { ctx->errstr = n ? _SSL_strerror() : "Write timed out"; return (HTTPS_ERR_SERVER); } } return (HTTPS_OK); }