diff options
Diffstat (limited to '')
-rw-r--r-- | libcli/http/http_conn.c | 347 |
1 files changed, 347 insertions, 0 deletions
diff --git a/libcli/http/http_conn.c b/libcli/http/http_conn.c new file mode 100644 index 0000000..ac39a0a --- /dev/null +++ b/libcli/http/http_conn.c @@ -0,0 +1,347 @@ +/* + Unix SMB/CIFS implementation. + + HTTP library + + Copyright (C) 2019 Ralph Boehme <slow@samba.org> + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "includes.h" +#include "lib/util/tevent_ntstatus.h" +#include "libcli/dns/dns_lookup.h" +#include "lib/tsocket/tsocket.h" +#include "lib/util/util_net.h" +#include "lib/tls/tls.h" +#include "lib/util/tevent_unix.h" +#include "http.h" +#include "http_internal.h" + +struct http_connect_state { + struct tevent_context *ev; + const char *http_server; + const char *http_server_ip; + uint16_t http_port; + struct tsocket_address *local_address; + struct tsocket_address *remote_address; + struct cli_credentials *credentials; + struct tstream_tls_params *tls_params; + + struct http_conn *http_conn; +}; + +static void http_connect_dns_done(struct tevent_req *subreq); +static void http_connect_tcp_connect(struct tevent_req *req); +static void http_connect_tcp_done(struct tevent_req *subreq); +static void http_connect_tls_done(struct tevent_req *subreq); + +struct tevent_req *http_connect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + const char *http_server, + uint16_t http_port, + struct cli_credentials *credentials, + struct tstream_tls_params *tls_params) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct http_connect_state *state = NULL; + int ret; + + DBG_DEBUG("Connecting to [%s] over HTTP%s\n", + http_server, tls_params != NULL ? "S" : ""); + + req = tevent_req_create(mem_ctx, &state, struct http_connect_state); + if (req == NULL) { + return NULL; + } + + *state = (struct http_connect_state) { + .ev = ev, + .http_port = http_port, + .credentials = credentials, + .tls_params = tls_params, + }; + + state->http_server = talloc_strdup(state, http_server); + if (tevent_req_nomem(state->http_server, req)) { + return tevent_req_post(req, ev); + } + + state->http_conn = talloc_zero(state, struct http_conn); + if (tevent_req_nomem(state->http_conn, req)) { + return tevent_req_post(req, ev); + } + + state->http_conn->send_queue = tevent_queue_create(state->http_conn, + "HTTP send queue"); + if (tevent_req_nomem(state->http_conn->send_queue, req)) { + return tevent_req_post(req, ev); + } + + ret = tsocket_address_inet_from_strings(state, + "ip", + NULL, + 0, + &state->local_address); + if (ret != 0) { + tevent_req_error(req, errno); + return tevent_req_post(req, ev); + } + + if (!is_ipaddress(http_server)) { + subreq = dns_lookup_send(state, + ev, + NULL, + http_server, + DNS_QCLASS_IN, + DNS_QTYPE_A); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, http_connect_dns_done, req); + return req; + } + state->http_server_ip = state->http_server; + + http_connect_tcp_connect(req); + if (!tevent_req_is_in_progress(req)) { + return tevent_req_post(req, ev); + } + + return req; +} + +static void http_connect_dns_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + struct dns_name_packet *dns_reply = NULL; + struct dns_res_rec *an = NULL; + uint16_t i; + int ret; + + ret = dns_lookup_recv(subreq, state, &dns_reply); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_error(req, ret); + return; + } + + for (i = 0; i < dns_reply->ancount; i++) { + an = &dns_reply->answers[i]; + if (an->rr_type == DNS_QTYPE_A) { + break; + } + } + if (i >= dns_reply->ancount) { + tevent_req_error(req, ENOENT); + return; + } + + state->http_server_ip = talloc_strdup(state, an->rdata.ipv4_record); + if (tevent_req_nomem(state->http_server_ip, req)) { + return; + } + http_connect_tcp_connect(req); +} + +static void http_connect_tcp_connect(struct tevent_req *req) +{ + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + struct tevent_req *subreq = NULL; + int ret; + + ret = tsocket_address_inet_from_strings(state, + "ip", + state->http_server_ip, + state->http_port, + &state->remote_address); + if (ret != 0) { + int saved_errno = errno; + + DBG_ERR("Cannot create remote socket address, error: %s (%d)\n", + strerror(errno), errno); + tevent_req_error(req, saved_errno); + return; + } + + subreq = tstream_inet_tcp_connect_send(state, + state->ev, + state->local_address, + state->remote_address); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, http_connect_tcp_done, req); +} + +static void http_connect_tcp_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + int error; + int ret; + + ret = tstream_inet_tcp_connect_recv(subreq, + &error, + state->http_conn, + &state->http_conn->tstreams.raw, + NULL); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_error(req, error); + return; + } + + state->http_conn->tstreams.active = state->http_conn->tstreams.raw; + DBG_DEBUG("Socket connected\n"); + + if (state->tls_params == NULL) { + tevent_req_done(req); + return; + } + + DBG_DEBUG("Starting TLS\n"); + + subreq = tstream_tls_connect_send(state, + state->ev, + state->http_conn->tstreams.active, + state->tls_params); + if (tevent_req_nomem(subreq, req)) { + return; + } + tevent_req_set_callback(subreq, http_connect_tls_done, req); +} + +static void http_connect_tls_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + int error; + int ret; + + ret = tstream_tls_connect_recv(subreq, + &error, + state->http_conn, + &state->http_conn->tstreams.tls); + TALLOC_FREE(subreq); + if (ret != 0) { + tevent_req_error(req, error); + return; + } + + state->http_conn->tstreams.active = state->http_conn->tstreams.tls; + + DBG_DEBUG("TLS handshake completed\n"); + tevent_req_done(req); +} + +int http_connect_recv(struct tevent_req *req, + TALLOC_CTX *mem_ctx, + struct http_conn **http_conn) +{ + struct http_connect_state *state = tevent_req_data( + req, struct http_connect_state); + int error; + + if (tevent_req_is_unix_error(req, &error)) { + tevent_req_received(req); + return error; + } + + *http_conn = talloc_move(mem_ctx, &state->http_conn); + tevent_req_received(req); + + return 0; +} + +struct tevent_queue *http_conn_send_queue(struct http_conn *http_conn) +{ + return http_conn->send_queue; +} + +struct tstream_context *http_conn_tstream(struct http_conn *http_conn) +{ + return http_conn->tstreams.active; +} + +struct http_conn_disconnect_state { + struct tevent_context *ev; + struct http_conn *http_conn; +}; + +static void http_conn_disconnect_done(struct tevent_req *subreq); + +struct tevent_req *http_disconnect_send(TALLOC_CTX *mem_ctx, + struct tevent_context *ev, + struct http_conn *http_conn) +{ + struct tevent_req *req = NULL; + struct tevent_req *subreq = NULL; + struct http_conn_disconnect_state *state = NULL; + + req = tevent_req_create(mem_ctx, &state, + struct http_conn_disconnect_state); + if (req == NULL) { + return NULL; + } + + *state = (struct http_conn_disconnect_state) { + .ev = ev, + .http_conn = http_conn, + }; + + if (http_conn->tstreams.active == NULL) { + tevent_req_error(req, ENOTCONN); + return tevent_req_post(req, ev); + } + + subreq = tstream_disconnect_send(state, ev, http_conn->tstreams.active); + if (tevent_req_nomem(subreq, req)) { + return tevent_req_post(req, ev); + } + tevent_req_set_callback(subreq, http_conn_disconnect_done, req); + + return req; +} + +static void http_conn_disconnect_done(struct tevent_req *subreq) +{ + struct tevent_req *req = tevent_req_callback_data( + subreq, struct tevent_req); + int ret; + int error; + + ret = tstream_disconnect_recv(subreq, &error); + TALLOC_FREE(subreq); + if (ret == -1) { + tevent_req_error(req, error); + return; + } + + tevent_req_done(req); +} + +int http_disconnect_recv(struct tevent_req *req) +{ + return tevent_req_simple_recv_unix(req); +} |