diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 21:30:40 +0000 |
commit | 133a45c109da5310add55824db21af5239951f93 (patch) | |
tree | ba6ac4c0a950a0dda56451944315d66409923918 /src/libserver/http/http_connection.c | |
parent | Initial commit. (diff) | |
download | rspamd-133a45c109da5310add55824db21af5239951f93.tar.xz rspamd-133a45c109da5310add55824db21af5239951f93.zip |
Adding upstream version 3.8.1.upstream/3.8.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/libserver/http/http_connection.c')
-rw-r--r-- | src/libserver/http/http_connection.c | 2649 |
1 files changed, 2649 insertions, 0 deletions
diff --git a/src/libserver/http/http_connection.c b/src/libserver/http/http_connection.c new file mode 100644 index 0000000..5557fbf --- /dev/null +++ b/src/libserver/http/http_connection.c @@ -0,0 +1,2649 @@ +/*- + * Copyright 2016 Vsevolod Stakhov + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" +#include "http_connection.h" +#include "http_private.h" +#include "http_message.h" +#include "utlist.h" +#include "util.h" +#include "printf.h" +#include "logger.h" +#include "ref.h" +#include "ottery.h" +#include "keypair_private.h" +#include "cryptobox.h" +#include "libutil/libev_helper.h" +#include "libserver/ssl_util.h" +#include "libserver/url.h" + +#include "contrib/mumhash/mum.h" +#include "contrib/http-parser/http_parser.h" +#include "unix-std.h" + +#include <openssl/err.h> + +#define ENCRYPTED_VERSION " HTTP/1.0" + +struct _rspamd_http_privbuf { + rspamd_fstring_t *data; + const gchar *zc_buf; + gsize zc_remain; + ref_entry_t ref; +}; + +enum rspamd_http_priv_flags { + RSPAMD_HTTP_CONN_FLAG_ENCRYPTED = 1u << 0u, + RSPAMD_HTTP_CONN_FLAG_NEW_HEADER = 1u << 1u, + RSPAMD_HTTP_CONN_FLAG_RESETED = 1u << 2u, + RSPAMD_HTTP_CONN_FLAG_TOO_LARGE = 1u << 3u, + RSPAMD_HTTP_CONN_FLAG_ENCRYPTION_NEEDED = 1u << 4u, + RSPAMD_HTTP_CONN_FLAG_PROXY = 1u << 5u, + RSPAMD_HTTP_CONN_FLAG_PROXY_REQUEST = 1u << 6u, + RSPAMD_HTTP_CONN_OWN_SOCKET = 1u << 7u, +}; + +#define IS_CONN_ENCRYPTED(c) ((c)->flags & RSPAMD_HTTP_CONN_FLAG_ENCRYPTED) +#define IS_CONN_RESETED(c) ((c)->flags & RSPAMD_HTTP_CONN_FLAG_RESETED) + +struct rspamd_http_connection_private { + struct rspamd_http_context *ctx; + struct rspamd_ssl_connection *ssl; + struct _rspamd_http_privbuf *buf; + struct rspamd_keypair_cache *cache; + struct rspamd_cryptobox_pubkey *peer_key; + struct rspamd_cryptobox_keypair *local_key; + struct rspamd_http_header *header; + struct http_parser parser; + struct http_parser_settings parser_cb; + struct rspamd_io_ev ev; + ev_tstamp timeout; + struct rspamd_http_message *msg; + struct iovec *out; + guint outlen; + enum rspamd_http_priv_flags flags; + gsize wr_pos; + gsize wr_total; +}; + +static const rspamd_ftok_t key_header = { + .begin = "Key", + .len = 3}; +static const rspamd_ftok_t date_header = { + .begin = "Date", + .len = 4}; +static const rspamd_ftok_t last_modified_header = { + .begin = "Last-Modified", + .len = 13}; + +static void rspamd_http_event_handler(int fd, short what, gpointer ud); +static void rspamd_http_ssl_err_handler(gpointer ud, GError *err); + + +#define HTTP_ERROR http_error_quark() +GQuark +http_error_quark(void) +{ + return g_quark_from_static_string("http-error-quark"); +} + +static void +rspamd_http_privbuf_dtor(gpointer ud) +{ + struct _rspamd_http_privbuf *p = (struct _rspamd_http_privbuf *) ud; + + if (p->data) { + rspamd_fstring_free(p->data); + } + + g_free(p); +} + +static const gchar * +rspamd_http_code_to_str(gint code) +{ + if (code == 200) { + return "OK"; + } + else if (code == 404) { + return "Not found"; + } + else if (code == 403 || code == 401) { + return "Not authorized"; + } + else if (code >= 400 && code < 500) { + return "Bad request"; + } + else if (code >= 300 && code < 400) { + return "See Other"; + } + else if (code >= 500 && code < 600) { + return "Internal server error"; + } + + return "Unknown error"; +} + +static void +rspamd_http_parse_key(rspamd_ftok_t *data, struct rspamd_http_connection *conn, + struct rspamd_http_connection_private *priv) +{ + guchar *decoded_id; + const gchar *eq_pos; + gsize id_len; + struct rspamd_cryptobox_pubkey *pk; + + if (priv->local_key == NULL) { + /* In this case we cannot do anything, e.g. we cannot decrypt payload */ + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_ENCRYPTED; + } + else { + /* Check sanity of what we have */ + eq_pos = memchr(data->begin, '=', data->len); + if (eq_pos != NULL) { + decoded_id = rspamd_decode_base32(data->begin, eq_pos - data->begin, + &id_len, RSPAMD_BASE32_DEFAULT); + + if (decoded_id != NULL && id_len >= RSPAMD_KEYPAIR_SHORT_ID_LEN) { + pk = rspamd_pubkey_from_base32(eq_pos + 1, + data->begin + data->len - eq_pos - 1, + RSPAMD_KEYPAIR_KEX, + RSPAMD_CRYPTOBOX_MODE_25519); + if (pk != NULL) { + if (memcmp(rspamd_keypair_get_id(priv->local_key), + decoded_id, + RSPAMD_KEYPAIR_SHORT_ID_LEN) == 0) { + priv->msg->peer_key = pk; + + if (priv->cache && priv->msg->peer_key) { + rspamd_keypair_cache_process(priv->cache, + priv->local_key, + priv->msg->peer_key); + } + } + else { + rspamd_pubkey_unref(pk); + } + } + } + + priv->flags |= RSPAMD_HTTP_CONN_FLAG_ENCRYPTED; + g_free(decoded_id); + } + } +} + +static inline void +rspamd_http_check_special_header(struct rspamd_http_connection *conn, + struct rspamd_http_connection_private *priv) +{ + if (rspamd_ftok_casecmp(&priv->header->name, &date_header) == 0) { + priv->msg->date = rspamd_http_parse_date(priv->header->value.begin, + priv->header->value.len); + } + else if (rspamd_ftok_casecmp(&priv->header->name, &key_header) == 0) { + rspamd_http_parse_key(&priv->header->value, conn, priv); + } + else if (rspamd_ftok_casecmp(&priv->header->name, &last_modified_header) == 0) { + priv->msg->last_modified = rspamd_http_parse_date( + priv->header->value.begin, + priv->header->value.len); + } +} + +static gint +rspamd_http_on_url(http_parser *parser, const gchar *at, size_t length) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + + priv = conn->priv; + + priv->msg->url = rspamd_fstring_append(priv->msg->url, at, length); + + return 0; +} + +static gint +rspamd_http_on_status(http_parser *parser, const gchar *at, size_t length) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + + priv = conn->priv; + + if (parser->status_code != 200) { + if (priv->msg->status == NULL) { + priv->msg->status = rspamd_fstring_new(); + } + + priv->msg->status = rspamd_fstring_append(priv->msg->status, at, length); + } + + return 0; +} + +static void +rspamd_http_finish_header(struct rspamd_http_connection *conn, + struct rspamd_http_connection_private *priv) +{ + struct rspamd_http_header *hdr; + khiter_t k; + gint r; + + priv->header->combined = rspamd_fstring_append(priv->header->combined, + "\r\n", 2); + priv->header->value.len = priv->header->combined->len - + priv->header->name.len - 4; + priv->header->value.begin = priv->header->combined->str + + priv->header->name.len + 2; + priv->header->name.begin = priv->header->combined->str; + + k = kh_put(rspamd_http_headers_hash, priv->msg->headers, &priv->header->name, + &r); + + if (r != 0) { + kh_value(priv->msg->headers, k) = priv->header; + hdr = NULL; + } + else { + hdr = kh_value(priv->msg->headers, k); + } + + DL_APPEND(hdr, priv->header); + + rspamd_http_check_special_header(conn, priv); +} + +static void +rspamd_http_init_header(struct rspamd_http_connection_private *priv) +{ + priv->header = g_malloc0(sizeof(struct rspamd_http_header)); + priv->header->combined = rspamd_fstring_new(); +} + +static gint +rspamd_http_on_header_field(http_parser *parser, + const gchar *at, + size_t length) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + + priv = conn->priv; + + if (priv->header == NULL) { + rspamd_http_init_header(priv); + } + else if (priv->flags & RSPAMD_HTTP_CONN_FLAG_NEW_HEADER) { + rspamd_http_finish_header(conn, priv); + rspamd_http_init_header(priv); + } + + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_NEW_HEADER; + priv->header->combined = rspamd_fstring_append(priv->header->combined, + at, length); + + return 0; +} + +static gint +rspamd_http_on_header_value(http_parser *parser, + const gchar *at, + size_t length) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + + priv = conn->priv; + + if (priv->header == NULL) { + /* Should not happen */ + return -1; + } + + if (!(priv->flags & RSPAMD_HTTP_CONN_FLAG_NEW_HEADER)) { + priv->flags |= RSPAMD_HTTP_CONN_FLAG_NEW_HEADER; + priv->header->combined = rspamd_fstring_append(priv->header->combined, + ": ", 2); + priv->header->name.len = priv->header->combined->len - 2; + } + + priv->header->combined = rspamd_fstring_append(priv->header->combined, + at, length); + + return 0; +} + +static int +rspamd_http_on_headers_complete(http_parser *parser) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + struct rspamd_http_message *msg; + int ret; + + priv = conn->priv; + msg = priv->msg; + + if (priv->header != NULL) { + rspamd_http_finish_header(conn, priv); + + priv->header = NULL; + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_NEW_HEADER; + } + + if (msg->method == HTTP_HEAD) { + /* We don't care about the rest */ + rspamd_ev_watcher_stop(priv->ctx->event_loop, &priv->ev); + + msg->code = parser->status_code; + rspamd_http_connection_ref(conn); + ret = conn->finish_handler(conn, msg); + + if (conn->opts & RSPAMD_HTTP_CLIENT_KEEP_ALIVE) { + rspamd_http_context_push_keepalive(conn->priv->ctx, conn, + msg, conn->priv->ctx->event_loop); + rspamd_http_connection_reset(conn); + } + else { + conn->finished = TRUE; + } + + rspamd_http_connection_unref(conn); + + return ret; + } + + /* + * HTTP parser sets content length to (-1) when it doesn't know the real + * length, for example, in case of chunked encoding. + * + * Hence, we skip body setup here + */ + if (parser->content_length != ULLONG_MAX && parser->content_length != 0 && + msg->method != HTTP_HEAD) { + if (conn->max_size > 0 && + parser->content_length > conn->max_size) { + /* Too large message */ + priv->flags |= RSPAMD_HTTP_CONN_FLAG_TOO_LARGE; + return -1; + } + + if (!rspamd_http_message_set_body(msg, NULL, parser->content_length)) { + return -1; + } + } + + if (parser->flags & F_SPAMC) { + msg->flags |= RSPAMD_HTTP_FLAG_SPAMC; + } + + + msg->method = parser->method; + msg->code = parser->status_code; + + return 0; +} + +static void +rspamd_http_switch_zc(struct _rspamd_http_privbuf *pbuf, + struct rspamd_http_message *msg) +{ + pbuf->zc_buf = msg->body_buf.begin + msg->body_buf.len; + pbuf->zc_remain = msg->body_buf.allocated_len - msg->body_buf.len; +} + +static int +rspamd_http_on_body(http_parser *parser, const gchar *at, size_t length) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + struct rspamd_http_message *msg; + struct _rspamd_http_privbuf *pbuf; + const gchar *p; + + priv = conn->priv; + msg = priv->msg; + pbuf = priv->buf; + p = at; + + if (!(msg->flags & RSPAMD_HTTP_FLAG_HAS_BODY)) { + if (!rspamd_http_message_set_body(msg, NULL, parser->content_length)) { + return -1; + } + } + + if (conn->finished) { + return 0; + } + + if (conn->max_size > 0 && + msg->body_buf.len + length > conn->max_size) { + /* Body length overflow */ + priv->flags |= RSPAMD_HTTP_CONN_FLAG_TOO_LARGE; + return -1; + } + + if (!pbuf->zc_buf) { + if (!rspamd_http_message_append_body(msg, at, length)) { + return -1; + } + + /* We might have some leftover in our private buffer */ + if (pbuf->data->len == length) { + /* Switch to zero-copy mode */ + rspamd_http_switch_zc(pbuf, msg); + } + } + else { + if (msg->body_buf.begin + msg->body_buf.len != at) { + /* Likely chunked encoding */ + memmove((gchar *) msg->body_buf.begin + msg->body_buf.len, at, length); + p = msg->body_buf.begin + msg->body_buf.len; + } + + /* Adjust zero-copy buf */ + msg->body_buf.len += length; + + if (!(msg->flags & RSPAMD_HTTP_FLAG_SHMEM)) { + msg->body_buf.c.normal->len += length; + } + + pbuf->zc_buf = msg->body_buf.begin + msg->body_buf.len; + pbuf->zc_remain = msg->body_buf.allocated_len - msg->body_buf.len; + } + + if ((conn->opts & RSPAMD_HTTP_BODY_PARTIAL) && !IS_CONN_ENCRYPTED(priv)) { + /* Incremental update is impossible for encrypted requests so far */ + return (conn->body_handler(conn, msg, p, length)); + } + + return 0; +} + +static int +rspamd_http_on_body_decrypted(http_parser *parser, const gchar *at, size_t length) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + + priv = conn->priv; + + if (priv->header != NULL) { + rspamd_http_finish_header(conn, priv); + priv->header = NULL; + } + + if (conn->finished) { + return 0; + } + + if (priv->msg->body_buf.len == 0) { + + priv->msg->body_buf.begin = at; + priv->msg->method = parser->method; + priv->msg->code = parser->status_code; + } + + priv->msg->body_buf.len += length; + + return 0; +} + +static int +rspamd_http_on_headers_complete_decrypted(http_parser *parser) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + struct rspamd_http_message *msg; + int ret; + + priv = conn->priv; + msg = priv->msg; + + if (priv->header != NULL) { + rspamd_http_finish_header(conn, priv); + + priv->header = NULL; + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_NEW_HEADER; + } + + if (parser->flags & F_SPAMC) { + priv->msg->flags |= RSPAMD_HTTP_FLAG_SPAMC; + } + + if (msg->method == HTTP_HEAD) { + /* We don't care about the rest */ + rspamd_ev_watcher_stop(priv->ctx->event_loop, &priv->ev); + msg->code = parser->status_code; + rspamd_http_connection_ref(conn); + ret = conn->finish_handler(conn, msg); + + if (conn->opts & RSPAMD_HTTP_CLIENT_KEEP_ALIVE) { + rspamd_http_context_push_keepalive(conn->priv->ctx, conn, + msg, conn->priv->ctx->event_loop); + rspamd_http_connection_reset(conn); + } + else { + conn->finished = TRUE; + } + + rspamd_http_connection_unref(conn); + + return ret; + } + + priv->msg->method = parser->method; + priv->msg->code = parser->status_code; + + return 0; +} + +static int +rspamd_http_decrypt_message(struct rspamd_http_connection *conn, + struct rspamd_http_connection_private *priv, + struct rspamd_cryptobox_pubkey *peer_key) +{ + guchar *nonce, *m; + const guchar *nm; + gsize dec_len; + struct rspamd_http_message *msg = priv->msg; + struct rspamd_http_header *hdr, *hcur, *hcurtmp; + struct http_parser decrypted_parser; + struct http_parser_settings decrypted_cb; + enum rspamd_cryptobox_mode mode; + + mode = rspamd_keypair_alg(priv->local_key); + nonce = msg->body_buf.str; + m = msg->body_buf.str + rspamd_cryptobox_nonce_bytes(mode) + + rspamd_cryptobox_mac_bytes(mode); + dec_len = msg->body_buf.len - rspamd_cryptobox_nonce_bytes(mode) - + rspamd_cryptobox_mac_bytes(mode); + + if ((nm = rspamd_pubkey_get_nm(peer_key, priv->local_key)) == NULL) { + nm = rspamd_pubkey_calculate_nm(peer_key, priv->local_key); + } + + if (!rspamd_cryptobox_decrypt_nm_inplace(m, dec_len, nonce, + nm, m - rspamd_cryptobox_mac_bytes(mode), mode)) { + msg_err("cannot verify encrypted message, first bytes of the input: %*xs", + (gint) MIN(msg->body_buf.len, 64), msg->body_buf.begin); + return -1; + } + + /* Cleanup message */ + kh_foreach_value (msg->headers, hdr, { + DL_FOREACH_SAFE (hdr, hcur, hcurtmp) { + rspamd_fstring_free (hcur->combined); + g_free (hcur); +} +}); + +kh_destroy(rspamd_http_headers_hash, msg->headers); +msg->headers = kh_init(rspamd_http_headers_hash); + +if (msg->url != NULL) { + msg->url = rspamd_fstring_assign(msg->url, "", 0); +} + +msg->body_buf.len = 0; + +memset(&decrypted_parser, 0, sizeof(decrypted_parser)); +http_parser_init(&decrypted_parser, + conn->type == RSPAMD_HTTP_SERVER ? HTTP_REQUEST : HTTP_RESPONSE); + +memset(&decrypted_cb, 0, sizeof(decrypted_cb)); +decrypted_cb.on_url = rspamd_http_on_url; +decrypted_cb.on_status = rspamd_http_on_status; +decrypted_cb.on_header_field = rspamd_http_on_header_field; +decrypted_cb.on_header_value = rspamd_http_on_header_value; +decrypted_cb.on_headers_complete = rspamd_http_on_headers_complete_decrypted; +decrypted_cb.on_body = rspamd_http_on_body_decrypted; +decrypted_parser.data = conn; +decrypted_parser.content_length = dec_len; + +if (http_parser_execute(&decrypted_parser, &decrypted_cb, m, + dec_len) != (size_t) dec_len) { + msg_err("HTTP parser error: %s when parsing encrypted request", + http_errno_description(decrypted_parser.http_errno)); + return -1; +} + +return 0; +} + +static int +rspamd_http_on_message_complete(http_parser *parser) +{ + struct rspamd_http_connection *conn = + (struct rspamd_http_connection *) parser->data; + struct rspamd_http_connection_private *priv; + int ret = 0; + enum rspamd_cryptobox_mode mode; + + if (conn->finished) { + return 0; + } + + priv = conn->priv; + + if ((conn->opts & RSPAMD_HTTP_REQUIRE_ENCRYPTION) && !IS_CONN_ENCRYPTED(priv)) { + priv->flags |= RSPAMD_HTTP_CONN_FLAG_ENCRYPTION_NEEDED; + msg_err("unencrypted connection when encryption has been requested"); + return -1; + } + + if ((conn->opts & RSPAMD_HTTP_BODY_PARTIAL) == 0 && IS_CONN_ENCRYPTED(priv)) { + mode = rspamd_keypair_alg(priv->local_key); + + if (priv->local_key == NULL || priv->msg->peer_key == NULL || + priv->msg->body_buf.len < rspamd_cryptobox_nonce_bytes(mode) + + rspamd_cryptobox_mac_bytes(mode)) { + msg_err("cannot decrypt message"); + return -1; + } + + /* We have keys, so we can decrypt message */ + ret = rspamd_http_decrypt_message(conn, priv, priv->msg->peer_key); + + if (ret != 0) { + return ret; + } + + if (conn->body_handler != NULL) { + rspamd_http_connection_ref(conn); + ret = conn->body_handler(conn, + priv->msg, + priv->msg->body_buf.begin, + priv->msg->body_buf.len); + rspamd_http_connection_unref(conn); + } + } + else if ((conn->opts & RSPAMD_HTTP_BODY_PARTIAL) == 0 && conn->body_handler) { + g_assert(conn->body_handler != NULL); + rspamd_http_connection_ref(conn); + ret = conn->body_handler(conn, + priv->msg, + priv->msg->body_buf.begin, + priv->msg->body_buf.len); + rspamd_http_connection_unref(conn); + } + + if (ret == 0) { + rspamd_ev_watcher_stop(priv->ctx->event_loop, &priv->ev); + rspamd_http_connection_ref(conn); + ret = conn->finish_handler(conn, priv->msg); + + if (conn->opts & RSPAMD_HTTP_CLIENT_KEEP_ALIVE) { + rspamd_http_context_push_keepalive(conn->priv->ctx, conn, + priv->msg, conn->priv->ctx->event_loop); + rspamd_http_connection_reset(conn); + } + else { + conn->finished = TRUE; + } + + rspamd_http_connection_unref(conn); + } + + return ret; +} + +static void +rspamd_http_simple_client_helper(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv; + gpointer ssl; + gint request_method; + GString *prev_host = NULL; + + priv = conn->priv; + ssl = priv->ssl; + priv->ssl = NULL; + + /* Preserve data */ + if (priv->msg) { + request_method = priv->msg->method; + /* Preserve host for keepalive */ + prev_host = priv->msg->host; + priv->msg->host = NULL; + } + + rspamd_http_connection_reset(conn); + priv->ssl = ssl; + + /* Plan read message */ + + if (conn->opts & RSPAMD_HTTP_CLIENT_SHARED) { + rspamd_http_connection_read_message_shared(conn, conn->ud, + conn->priv->timeout); + } + else { + rspamd_http_connection_read_message(conn, conn->ud, + conn->priv->timeout); + } + + if (priv->msg) { + priv->msg->method = request_method; + priv->msg->host = prev_host; + } + else { + if (prev_host) { + g_string_free(prev_host, TRUE); + } + } +} + +static void +rspamd_http_write_helper(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv; + struct iovec *start; + guint niov, i; + gint flags = 0; + gsize remain; + gssize r; + GError *err; + struct iovec *cur_iov; + struct msghdr msg; + + priv = conn->priv; + + if (priv->wr_pos == priv->wr_total) { + goto call_finish_handler; + } + + start = &priv->out[0]; + niov = priv->outlen; + remain = priv->wr_pos; + /* We know that niov is small enough for that */ + if (priv->ssl) { + /* Might be recursive! */ + cur_iov = g_malloc(niov * sizeof(struct iovec)); + } + else { + cur_iov = alloca(niov * sizeof(struct iovec)); + } + memcpy(cur_iov, priv->out, niov * sizeof(struct iovec)); + for (i = 0; i < priv->outlen && remain > 0; i++) { + /* Find out the first iov required */ + start = &cur_iov[i]; + if (start->iov_len <= remain) { + remain -= start->iov_len; + start = &cur_iov[i + 1]; + niov--; + } + else { + start->iov_base = (void *) ((char *) start->iov_base + remain); + start->iov_len -= remain; + remain = 0; + } + } + + memset(&msg, 0, sizeof(msg)); + msg.msg_iov = start; + msg.msg_iovlen = MIN(IOV_MAX, niov); + g_assert(niov > 0); +#ifdef MSG_NOSIGNAL + flags = MSG_NOSIGNAL; +#endif + + if (priv->ssl) { + r = rspamd_ssl_writev(priv->ssl, msg.msg_iov, msg.msg_iovlen); + g_free(cur_iov); + } + else { + r = sendmsg(conn->fd, &msg, flags); + } + + if (r == -1) { + if (!priv->ssl) { + err = g_error_new(HTTP_ERROR, 500, "IO write error: %s", strerror(errno)); + rspamd_http_connection_ref(conn); + conn->error_handler(conn, err); + rspamd_http_connection_unref(conn); + g_error_free(err); + } + + return; + } + else { + priv->wr_pos += r; + } + + if (priv->wr_pos >= priv->wr_total) { + goto call_finish_handler; + } + else { + /* Want to write more */ + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_RESETED; + + if (priv->ssl && r > 0) { + /* We can write more data... */ + rspamd_http_write_helper(conn); + return; + } + } + + return; + +call_finish_handler: + rspamd_ev_watcher_stop(priv->ctx->event_loop, &priv->ev); + + if ((conn->opts & RSPAMD_HTTP_CLIENT_SIMPLE) == 0) { + rspamd_http_connection_ref(conn); + conn->finished = TRUE; + conn->finish_handler(conn, priv->msg); + rspamd_http_connection_unref(conn); + } + else { + /* Plan read message */ + rspamd_http_simple_client_helper(conn); + } +} + +static gssize +rspamd_http_try_read(gint fd, + struct rspamd_http_connection *conn, + struct rspamd_http_connection_private *priv, + struct _rspamd_http_privbuf *pbuf, + const gchar **buf_ptr) +{ + gssize r; + gchar *data; + gsize len; + struct rspamd_http_message *msg; + + msg = priv->msg; + + if (pbuf->zc_buf == NULL) { + data = priv->buf->data->str; + len = priv->buf->data->allocated; + } + else { + data = (gchar *) pbuf->zc_buf; + len = pbuf->zc_remain; + + if (len == 0) { + rspamd_http_message_grow_body(priv->msg, priv->buf->data->allocated); + rspamd_http_switch_zc(pbuf, msg); + data = (gchar *) pbuf->zc_buf; + len = pbuf->zc_remain; + } + } + + if (priv->ssl) { + r = rspamd_ssl_read(priv->ssl, data, len); + } + else { + r = read(fd, data, len); + } + + if (r <= 0) { + return r; + } + else { + if (pbuf->zc_buf == NULL) { + priv->buf->data->len = r; + } + else { + pbuf->zc_remain -= r; + pbuf->zc_buf += r; + } + } + + if (buf_ptr) { + *buf_ptr = data; + } + + return r; +} + +static void +rspamd_http_ssl_err_handler(gpointer ud, GError *err) +{ + struct rspamd_http_connection *conn = (struct rspamd_http_connection *) ud; + + rspamd_http_connection_ref(conn); + conn->error_handler(conn, err); + rspamd_http_connection_unref(conn); +} + +static void +rspamd_http_event_handler(int fd, short what, gpointer ud) +{ + struct rspamd_http_connection *conn = (struct rspamd_http_connection *) ud; + struct rspamd_http_connection_private *priv; + struct _rspamd_http_privbuf *pbuf; + const gchar *d; + gssize r; + GError *err; + + priv = conn->priv; + pbuf = priv->buf; + REF_RETAIN(pbuf); + rspamd_http_connection_ref(conn); + + if (what == EV_READ) { + r = rspamd_http_try_read(fd, conn, priv, pbuf, &d); + + if (r > 0) { + if (http_parser_execute(&priv->parser, &priv->parser_cb, + d, r) != (size_t) r || + priv->parser.http_errno != 0) { + if (priv->flags & RSPAMD_HTTP_CONN_FLAG_TOO_LARGE) { + err = g_error_new(HTTP_ERROR, 413, + "Request entity too large: %zu", + (size_t) priv->parser.content_length); + } + else if (priv->flags & RSPAMD_HTTP_CONN_FLAG_ENCRYPTION_NEEDED) { + err = g_error_new(HTTP_ERROR, 400, + "Encryption required"); + } + else if (priv->parser.http_errno == HPE_CLOSED_CONNECTION) { + msg_err("got garbage after end of the message, ignore it"); + + REF_RELEASE(pbuf); + rspamd_http_connection_unref(conn); + + return; + } + else { + if (priv->parser.http_errno > HPE_CB_status) { + err = g_error_new(HTTP_ERROR, 400, + "HTTP parser error: %s", + http_errno_description(priv->parser.http_errno)); + } + else { + err = g_error_new(HTTP_ERROR, 500, + "HTTP parser internal error: %s", + http_errno_description(priv->parser.http_errno)); + } + } + + if (!conn->finished) { + conn->error_handler(conn, err); + } + else { + msg_err("got error after HTTP request is finished: %e", err); + } + + g_error_free(err); + + REF_RELEASE(pbuf); + rspamd_http_connection_unref(conn); + + return; + } + } + else if (r == 0) { + /* We can still call http parser */ + http_parser_execute(&priv->parser, &priv->parser_cb, d, r); + + if (!conn->finished) { + err = g_error_new(HTTP_ERROR, + 400, + "IO read error: unexpected EOF"); + conn->error_handler(conn, err); + g_error_free(err); + } + REF_RELEASE(pbuf); + rspamd_http_connection_unref(conn); + + return; + } + else { + if (!priv->ssl) { + err = g_error_new(HTTP_ERROR, + 500, + "HTTP IO read error: %s", + strerror(errno)); + conn->error_handler(conn, err); + g_error_free(err); + } + + REF_RELEASE(pbuf); + rspamd_http_connection_unref(conn); + + return; + } + } + else if (what == EV_TIMEOUT) { + if (!priv->ssl) { + /* Let's try to read from the socket first */ + r = rspamd_http_try_read(fd, conn, priv, pbuf, &d); + + if (r > 0) { + if (http_parser_execute(&priv->parser, &priv->parser_cb, + d, r) != (size_t) r || + priv->parser.http_errno != 0) { + err = g_error_new(HTTP_ERROR, 400, + "HTTP parser error: %s", + http_errno_description(priv->parser.http_errno)); + + if (!conn->finished) { + conn->error_handler(conn, err); + } + else { + msg_err("got error after HTTP request is finished: %e", err); + } + + g_error_free(err); + + REF_RELEASE(pbuf); + rspamd_http_connection_unref(conn); + + return; + } + } + else { + err = g_error_new(HTTP_ERROR, 408, + "IO timeout"); + conn->error_handler(conn, err); + g_error_free(err); + + REF_RELEASE(pbuf); + rspamd_http_connection_unref(conn); + + return; + } + } + else { + /* In case of SSL we disable this logic as we already came from SSL handler */ + REF_RELEASE(pbuf); + rspamd_http_connection_unref(conn); + + return; + } + } + else if (what == EV_WRITE) { + rspamd_http_write_helper(conn); + } + + REF_RELEASE(pbuf); + rspamd_http_connection_unref(conn); +} + +static void +rspamd_http_parser_reset(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv = conn->priv; + + http_parser_init(&priv->parser, + conn->type == RSPAMD_HTTP_SERVER ? HTTP_REQUEST : HTTP_RESPONSE); + + priv->parser_cb.on_url = rspamd_http_on_url; + priv->parser_cb.on_status = rspamd_http_on_status; + priv->parser_cb.on_header_field = rspamd_http_on_header_field; + priv->parser_cb.on_header_value = rspamd_http_on_header_value; + priv->parser_cb.on_headers_complete = rspamd_http_on_headers_complete; + priv->parser_cb.on_body = rspamd_http_on_body; + priv->parser_cb.on_message_complete = rspamd_http_on_message_complete; +} + +static struct rspamd_http_connection * +rspamd_http_connection_new_common(struct rspamd_http_context *ctx, + gint fd, + rspamd_http_body_handler_t body_handler, + rspamd_http_error_handler_t error_handler, + rspamd_http_finish_handler_t finish_handler, + unsigned opts, + enum rspamd_http_connection_type type, + enum rspamd_http_priv_flags priv_flags, + struct upstream *proxy_upstream) +{ + struct rspamd_http_connection *conn; + struct rspamd_http_connection_private *priv; + + g_assert(error_handler != NULL && finish_handler != NULL); + + if (ctx == NULL) { + ctx = rspamd_http_context_default(); + } + + conn = g_malloc0(sizeof(struct rspamd_http_connection)); + conn->opts = opts; + conn->type = type; + conn->body_handler = body_handler; + conn->error_handler = error_handler; + conn->finish_handler = finish_handler; + conn->fd = fd; + conn->ref = 1; + conn->finished = FALSE; + + /* Init priv */ + priv = g_malloc0(sizeof(struct rspamd_http_connection_private)); + conn->priv = priv; + priv->ctx = ctx; + priv->flags = priv_flags; + + if (type == RSPAMD_HTTP_SERVER) { + priv->cache = ctx->server_kp_cache; + } + else { + priv->cache = ctx->client_kp_cache; + if (ctx->client_kp) { + priv->local_key = rspamd_keypair_ref(ctx->client_kp); + } + } + + rspamd_http_parser_reset(conn); + priv->parser.data = conn; + + return conn; +} + +struct rspamd_http_connection * +rspamd_http_connection_new_server(struct rspamd_http_context *ctx, + gint fd, + rspamd_http_body_handler_t body_handler, + rspamd_http_error_handler_t error_handler, + rspamd_http_finish_handler_t finish_handler, + unsigned opts) +{ + return rspamd_http_connection_new_common(ctx, fd, body_handler, + error_handler, finish_handler, opts, RSPAMD_HTTP_SERVER, 0, NULL); +} + +struct rspamd_http_connection * +rspamd_http_connection_new_client_socket(struct rspamd_http_context *ctx, + rspamd_http_body_handler_t body_handler, + rspamd_http_error_handler_t error_handler, + rspamd_http_finish_handler_t finish_handler, + unsigned opts, + gint fd) +{ + return rspamd_http_connection_new_common(ctx, fd, body_handler, + error_handler, finish_handler, opts, RSPAMD_HTTP_CLIENT, 0, NULL); +} + +struct rspamd_http_connection * +rspamd_http_connection_new_client(struct rspamd_http_context *ctx, + rspamd_http_body_handler_t body_handler, + rspamd_http_error_handler_t error_handler, + rspamd_http_finish_handler_t finish_handler, + unsigned opts, + rspamd_inet_addr_t *addr) +{ + gint fd; + + if (ctx == NULL) { + ctx = rspamd_http_context_default(); + } + + if (ctx->http_proxies) { + struct upstream *up = rspamd_upstream_get(ctx->http_proxies, + RSPAMD_UPSTREAM_ROUND_ROBIN, NULL, 0); + + if (up) { + rspamd_inet_addr_t *proxy_addr = rspamd_upstream_addr_next(up); + + fd = rspamd_inet_address_connect(proxy_addr, SOCK_STREAM, TRUE); + + if (fd == -1) { + msg_info("cannot connect to http proxy %s: %s", + rspamd_inet_address_to_string_pretty(proxy_addr), + strerror(errno)); + rspamd_upstream_fail(up, TRUE, strerror(errno)); + + return NULL; + } + + return rspamd_http_connection_new_common(ctx, fd, body_handler, + error_handler, finish_handler, opts, + RSPAMD_HTTP_CLIENT, + RSPAMD_HTTP_CONN_OWN_SOCKET | RSPAMD_HTTP_CONN_FLAG_PROXY, + up); + } + } + + /* Unproxied version */ + fd = rspamd_inet_address_connect(addr, SOCK_STREAM, TRUE); + + if (fd == -1) { + msg_info("cannot connect make http connection to %s: %s", + rspamd_inet_address_to_string_pretty(addr), + strerror(errno)); + + return NULL; + } + + return rspamd_http_connection_new_common(ctx, fd, body_handler, + error_handler, finish_handler, opts, + RSPAMD_HTTP_CLIENT, + RSPAMD_HTTP_CONN_OWN_SOCKET, + NULL); +} + +struct rspamd_http_connection * +rspamd_http_connection_new_client_keepalive(struct rspamd_http_context *ctx, + rspamd_http_body_handler_t body_handler, + rspamd_http_error_handler_t error_handler, + rspamd_http_finish_handler_t finish_handler, + unsigned opts, + rspamd_inet_addr_t *addr, + const gchar *host) +{ + struct rspamd_http_connection *conn; + + if (ctx == NULL) { + ctx = rspamd_http_context_default(); + } + + conn = rspamd_http_context_check_keepalive(ctx, addr, host, + opts & RSPAMD_HTTP_CLIENT_SSL); + + if (conn) { + return conn; + } + + conn = rspamd_http_connection_new_client(ctx, + body_handler, error_handler, finish_handler, + opts | RSPAMD_HTTP_CLIENT_SIMPLE | RSPAMD_HTTP_CLIENT_KEEP_ALIVE, + addr); + + if (conn) { + rspamd_http_context_prepare_keepalive(ctx, conn, addr, host, + opts & RSPAMD_HTTP_CLIENT_SSL); + } + + return conn; +} + +void rspamd_http_connection_reset(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv; + struct rspamd_http_message *msg; + + priv = conn->priv; + msg = priv->msg; + + /* Clear request */ + if (msg != NULL) { + if (msg->peer_key) { + priv->peer_key = msg->peer_key; + msg->peer_key = NULL; + } + rspamd_http_message_unref(msg); + priv->msg = NULL; + } + + conn->finished = FALSE; + /* Clear priv */ + rspamd_ev_watcher_stop(priv->ctx->event_loop, &priv->ev); + + if (!(priv->flags & RSPAMD_HTTP_CONN_FLAG_RESETED)) { + rspamd_http_parser_reset(conn); + } + + if (priv->buf != NULL) { + REF_RELEASE(priv->buf); + priv->buf = NULL; + } + + if (priv->out != NULL) { + g_free(priv->out); + priv->out = NULL; + } + + priv->flags |= RSPAMD_HTTP_CONN_FLAG_RESETED; +} + +struct rspamd_http_message * +rspamd_http_connection_steal_msg(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv; + struct rspamd_http_message *msg; + + priv = conn->priv; + msg = priv->msg; + + /* Clear request */ + if (msg != NULL) { + if (msg->peer_key) { + priv->peer_key = msg->peer_key; + msg->peer_key = NULL; + } + priv->msg = NULL; + } + + return msg; +} + +struct rspamd_http_message * +rspamd_http_connection_copy_msg(struct rspamd_http_message *msg, GError **err) +{ + struct rspamd_http_message *new_msg; + struct rspamd_http_header *hdr, *nhdr, *nhdrs, *hcur; + const gchar *old_body; + gsize old_len; + struct stat st; + union _rspamd_storage_u *storage; + + new_msg = rspamd_http_new_message(msg->type); + new_msg->flags = msg->flags; + + if (msg->body_buf.len > 0) { + + if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { + /* Avoid copying by just mapping a shared segment */ + new_msg->flags |= RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE; + + storage = &new_msg->body_buf.c; + storage->shared.shm_fd = dup(msg->body_buf.c.shared.shm_fd); + + if (storage->shared.shm_fd == -1) { + rspamd_http_message_unref(new_msg); + g_set_error(err, http_error_quark(), errno, + "cannot dup shmem fd: %d: %s", + msg->body_buf.c.shared.shm_fd, strerror(errno)); + + return NULL; + } + + if (fstat(storage->shared.shm_fd, &st) == -1) { + g_set_error(err, http_error_quark(), errno, + "cannot stat shmem fd: %d: %s", + storage->shared.shm_fd, strerror(errno)); + rspamd_http_message_unref(new_msg); + + return NULL; + } + + /* We don't own segment, so do not try to touch it */ + + if (msg->body_buf.c.shared.name) { + storage->shared.name = msg->body_buf.c.shared.name; + REF_RETAIN(storage->shared.name); + } + + new_msg->body_buf.str = mmap(NULL, st.st_size, + PROT_READ, MAP_SHARED, + storage->shared.shm_fd, 0); + + if (new_msg->body_buf.str == MAP_FAILED) { + g_set_error(err, http_error_quark(), errno, + "cannot mmap shmem fd: %d: %s", + storage->shared.shm_fd, strerror(errno)); + rspamd_http_message_unref(new_msg); + + return NULL; + } + + new_msg->body_buf.begin = new_msg->body_buf.str; + new_msg->body_buf.len = msg->body_buf.len; + new_msg->body_buf.begin = new_msg->body_buf.str + + (msg->body_buf.begin - msg->body_buf.str); + } + else { + old_body = rspamd_http_message_get_body(msg, &old_len); + + if (!rspamd_http_message_set_body(new_msg, old_body, old_len)) { + g_set_error(err, http_error_quark(), errno, + "cannot set body for message, length: %zd", + old_len); + rspamd_http_message_unref(new_msg); + + return NULL; + } + } + } + + if (msg->url) { + if (new_msg->url) { + new_msg->url = rspamd_fstring_append(new_msg->url, msg->url->str, + msg->url->len); + } + else { + new_msg->url = rspamd_fstring_new_init(msg->url->str, + msg->url->len); + } + } + + if (msg->host) { + new_msg->host = g_string_new_len(msg->host->str, msg->host->len); + } + + new_msg->method = msg->method; + new_msg->port = msg->port; + new_msg->date = msg->date; + new_msg->last_modified = msg->last_modified; + + kh_foreach_value(msg->headers, hdr, { + nhdrs = NULL; + + DL_FOREACH(hdr, hcur) + { + nhdr = g_malloc(sizeof(struct rspamd_http_header)); + + nhdr->combined = rspamd_fstring_new_init(hcur->combined->str, + hcur->combined->len); + nhdr->name.begin = nhdr->combined->str + + (hcur->name.begin - hcur->combined->str); + nhdr->name.len = hcur->name.len; + nhdr->value.begin = nhdr->combined->str + + (hcur->value.begin - hcur->combined->str); + nhdr->value.len = hcur->value.len; + DL_APPEND(nhdrs, nhdr); + } + + gint r; + khiter_t k = kh_put(rspamd_http_headers_hash, new_msg->headers, + &nhdrs->name, &r); + + if (r != 0) { + kh_value(new_msg->headers, k) = nhdrs; + } + else { + DL_CONCAT(kh_value(new_msg->headers, k), nhdrs); + } + }); + + return new_msg; +} + +void rspamd_http_connection_free(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv; + + priv = conn->priv; + + if (priv != NULL) { + rspamd_http_connection_reset(conn); + + if (priv->ssl) { + rspamd_ssl_connection_free(priv->ssl); + priv->ssl = NULL; + } + + if (priv->local_key) { + rspamd_keypair_unref(priv->local_key); + } + if (priv->peer_key) { + rspamd_pubkey_unref(priv->peer_key); + } + + if (priv->flags & RSPAMD_HTTP_CONN_OWN_SOCKET) { + /* Fd is owned by a connection */ + close(conn->fd); + } + + g_free(priv); + } + + g_free(conn); +} + +static void +rspamd_http_connection_read_message_common(struct rspamd_http_connection *conn, + gpointer ud, ev_tstamp timeout, + gint flags) +{ + struct rspamd_http_connection_private *priv = conn->priv; + struct rspamd_http_message *req; + + conn->ud = ud; + req = rspamd_http_new_message( + conn->type == RSPAMD_HTTP_SERVER ? HTTP_REQUEST : HTTP_RESPONSE); + priv->msg = req; + req->flags = flags; + + if (flags & RSPAMD_HTTP_FLAG_SHMEM) { + req->body_buf.c.shared.shm_fd = -1; + } + + if (priv->peer_key) { + priv->msg->peer_key = priv->peer_key; + priv->peer_key = NULL; + priv->flags |= RSPAMD_HTTP_CONN_FLAG_ENCRYPTED; + } + + priv->timeout = timeout; + priv->header = NULL; + priv->buf = g_malloc0(sizeof(*priv->buf)); + REF_INIT_RETAIN(priv->buf, rspamd_http_privbuf_dtor); + priv->buf->data = rspamd_fstring_sized_new(8192); + priv->flags |= RSPAMD_HTTP_CONN_FLAG_NEW_HEADER; + + if (!priv->ssl) { + rspamd_ev_watcher_init(&priv->ev, conn->fd, EV_READ, + rspamd_http_event_handler, conn); + rspamd_ev_watcher_start(priv->ctx->event_loop, &priv->ev, priv->timeout); + } + else { + rspamd_ssl_connection_restore_handlers(priv->ssl, + rspamd_http_event_handler, + rspamd_http_ssl_err_handler, + conn, + EV_READ); + } + + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_RESETED; +} + +void rspamd_http_connection_read_message(struct rspamd_http_connection *conn, + gpointer ud, ev_tstamp timeout) +{ + rspamd_http_connection_read_message_common(conn, ud, timeout, 0); +} + +void rspamd_http_connection_read_message_shared(struct rspamd_http_connection *conn, + gpointer ud, ev_tstamp timeout) +{ + rspamd_http_connection_read_message_common(conn, ud, timeout, + RSPAMD_HTTP_FLAG_SHMEM); +} + +static void +rspamd_http_connection_encrypt_message( + struct rspamd_http_connection *conn, + struct rspamd_http_message *msg, + struct rspamd_http_connection_private *priv, + guchar *pbody, + guint bodylen, + guchar *pmethod, + guint methodlen, + guint preludelen, + gint hdrcount, + guchar *np, + guchar *mp, + struct rspamd_cryptobox_pubkey *peer_key) +{ + struct rspamd_cryptobox_segment *segments; + guchar *crlfp; + const guchar *nm; + gint i, cnt; + guint outlen; + struct rspamd_http_header *hdr, *hcur; + enum rspamd_cryptobox_mode mode; + + mode = rspamd_keypair_alg(priv->local_key); + crlfp = mp + rspamd_cryptobox_mac_bytes(mode); + + outlen = priv->out[0].iov_len + priv->out[1].iov_len; + /* + * Create segments from the following: + * Method, [URL], CRLF, nheaders, CRLF, body + */ + segments = g_new(struct rspamd_cryptobox_segment, hdrcount + 5); + + segments[0].data = pmethod; + segments[0].len = methodlen; + + if (conn->type != RSPAMD_HTTP_SERVER) { + segments[1].data = msg->url->str; + segments[1].len = msg->url->len; + /* space + HTTP version + crlf */ + segments[2].data = crlfp; + segments[2].len = preludelen - 2; + crlfp += segments[2].len; + i = 3; + } + else { + /* Here we send just CRLF */ + segments[1].data = crlfp; + segments[1].len = 2; + crlfp += segments[1].len; + + i = 2; + } + + + kh_foreach_value (msg->headers, hdr, { + DL_FOREACH (hdr, hcur) { + segments[i].data = hcur->combined->str; + segments[i++].len = hcur->combined->len; +} +}); + +/* crlfp should point now at the second crlf */ +segments[i].data = crlfp; +segments[i++].len = 2; + +if (pbody) { + segments[i].data = pbody; + segments[i++].len = bodylen; +} + +cnt = i; + +if ((nm = rspamd_pubkey_get_nm(peer_key, priv->local_key)) == NULL) { + nm = rspamd_pubkey_calculate_nm(peer_key, priv->local_key); +} + +rspamd_cryptobox_encryptv_nm_inplace(segments, cnt, np, nm, mp, mode); + +/* + * iov[0] = base HTTP request + * iov[1] = CRLF + * iov[2] = nonce + * iov[3] = mac + * iov[4..i] = encrypted HTTP request/reply + */ +priv->out[2].iov_base = np; +priv->out[2].iov_len = rspamd_cryptobox_nonce_bytes(mode); +priv->out[3].iov_base = mp; +priv->out[3].iov_len = rspamd_cryptobox_mac_bytes(mode); + +outlen += rspamd_cryptobox_nonce_bytes(mode) + + rspamd_cryptobox_mac_bytes(mode); + +for (i = 0; i < cnt; i++) { + priv->out[i + 4].iov_base = segments[i].data; + priv->out[i + 4].iov_len = segments[i].len; + outlen += segments[i].len; +} + +priv->wr_total = outlen; + +g_free(segments); +} + +static void +rspamd_http_detach_shared(struct rspamd_http_message *msg) +{ + rspamd_fstring_t *cpy_str; + + cpy_str = rspamd_fstring_new_init(msg->body_buf.begin, msg->body_buf.len); + rspamd_http_message_set_body_from_fstring_steal(msg, cpy_str); +} + +gint rspamd_http_message_write_header(const gchar *mime_type, gboolean encrypted, + gchar *repbuf, gsize replen, gsize bodylen, gsize enclen, const gchar *host, + struct rspamd_http_connection *conn, struct rspamd_http_message *msg, + rspamd_fstring_t **buf, + struct rspamd_http_connection_private *priv, + struct rspamd_cryptobox_pubkey *peer_key) +{ + gchar datebuf[64]; + gint meth_len = 0; + const gchar *conn_type = "close"; + + if (conn->type == RSPAMD_HTTP_SERVER) { + /* Format reply */ + if (msg->method < HTTP_SYMBOLS) { + rspamd_ftok_t status; + + rspamd_http_date_format(datebuf, sizeof(datebuf), msg->date); + + if (mime_type == NULL) { + mime_type = + encrypted ? "application/octet-stream" : "text/plain"; + } + + if (msg->status == NULL || msg->status->len == 0) { + if (msg->code == 200) { + RSPAMD_FTOK_ASSIGN(&status, "OK"); + } + else if (msg->code == 404) { + RSPAMD_FTOK_ASSIGN(&status, "Not Found"); + } + else if (msg->code == 403) { + RSPAMD_FTOK_ASSIGN(&status, "Forbidden"); + } + else if (msg->code >= 500 && msg->code < 600) { + RSPAMD_FTOK_ASSIGN(&status, "Internal Server Error"); + } + else { + RSPAMD_FTOK_ASSIGN(&status, "Undefined Error"); + } + } + else { + status.begin = msg->status->str; + status.len = msg->status->len; + } + + if (encrypted) { + /* Internal reply (encrypted) */ + if (mime_type) { + meth_len = + rspamd_snprintf(repbuf, replen, + "HTTP/1.1 %d %T\r\n" + "Connection: close\r\n" + "Server: %s\r\n" + "Date: %s\r\n" + "Content-Length: %z\r\n" + "Content-Type: %s", /* NO \r\n at the end ! */ + msg->code, &status, priv->ctx->config.server_hdr, + datebuf, + bodylen, mime_type); + } + else { + meth_len = + rspamd_snprintf(repbuf, replen, + "HTTP/1.1 %d %T\r\n" + "Connection: close\r\n" + "Server: %s\r\n" + "Date: %s\r\n" + "Content-Length: %z", /* NO \r\n at the end ! */ + msg->code, &status, priv->ctx->config.server_hdr, + datebuf, + bodylen); + } + enclen += meth_len; + /* External reply */ + rspamd_printf_fstring(buf, + "HTTP/1.1 200 OK\r\n" + "Connection: close\r\n" + "Server: %s\r\n" + "Date: %s\r\n" + "Content-Length: %z\r\n" + "Content-Type: application/octet-stream\r\n", + priv->ctx->config.server_hdr, + datebuf, enclen); + } + else { + if (mime_type) { + meth_len = + rspamd_printf_fstring(buf, + "HTTP/1.1 %d %T\r\n" + "Connection: close\r\n" + "Server: %s\r\n" + "Date: %s\r\n" + "Content-Length: %z\r\n" + "Content-Type: %s\r\n", + msg->code, &status, priv->ctx->config.server_hdr, + datebuf, + bodylen, mime_type); + } + else { + meth_len = + rspamd_printf_fstring(buf, + "HTTP/1.1 %d %T\r\n" + "Connection: close\r\n" + "Server: %s\r\n" + "Date: %s\r\n" + "Content-Length: %z\r\n", + msg->code, &status, priv->ctx->config.server_hdr, + datebuf, + bodylen); + } + } + } + else { + /* Legacy spamd reply */ + if (msg->flags & RSPAMD_HTTP_FLAG_SPAMC) { + gsize real_bodylen; + goffset eoh_pos; + GString tmp; + + /* Unfortunately, spamc protocol is deadly brain damaged */ + tmp.str = (gchar *) msg->body_buf.begin; + tmp.len = msg->body_buf.len; + + if (rspamd_string_find_eoh(&tmp, &eoh_pos) != -1 && + bodylen > eoh_pos) { + real_bodylen = bodylen - eoh_pos; + } + else { + real_bodylen = bodylen; + } + + rspamd_printf_fstring(buf, "SPAMD/1.1 0 EX_OK\r\n" + "Content-length: %z\r\n", + real_bodylen); + } + else { + rspamd_printf_fstring(buf, "RSPAMD/1.3 0 EX_OK\r\n"); + } + } + } + else { + + /* Client request */ + if (conn->opts & RSPAMD_HTTP_CLIENT_KEEP_ALIVE) { + conn_type = "keep-alive"; + } + + /* Format request */ + enclen += RSPAMD_FSTRING_LEN(msg->url) + + strlen(http_method_str(msg->method)) + 1; + + if (host == NULL && msg->host == NULL) { + /* Fallback to HTTP/1.0 */ + if (encrypted) { + rspamd_printf_fstring(buf, + "%s %s HTTP/1.0\r\n" + "Content-Length: %z\r\n" + "Content-Type: application/octet-stream\r\n" + "Connection: %s\r\n", + "POST", + "/post", + enclen, + conn_type); + } + else { + rspamd_printf_fstring(buf, + "%s %V HTTP/1.0\r\n" + "Content-Length: %z\r\n" + "Connection: %s\r\n", + http_method_str(msg->method), + msg->url, + bodylen, + conn_type); + + if (bodylen > 0) { + if (mime_type == NULL) { + mime_type = "text/plain"; + } + + rspamd_printf_fstring(buf, + "Content-Type: %s\r\n", + mime_type); + } + } + } + else { + /* Normal HTTP/1.1 with Host */ + if (host == NULL) { + host = msg->host->str; + } + + if (encrypted) { + /* TODO: Add proxy support to HTTPCrypt */ + if (rspamd_http_message_is_standard_port(msg)) { + rspamd_printf_fstring(buf, + "%s %s HTTP/1.1\r\n" + "Connection: %s\r\n" + "Host: %s\r\n" + "Content-Length: %z\r\n" + "Content-Type: application/octet-stream\r\n", + "POST", + "/post", + conn_type, + host, + enclen); + } + else { + rspamd_printf_fstring(buf, + "%s %s HTTP/1.1\r\n" + "Connection: %s\r\n" + "Host: %s:%d\r\n" + "Content-Length: %z\r\n" + "Content-Type: application/octet-stream\r\n", + "POST", + "/post", + conn_type, + host, + msg->port, + enclen); + } + } + else { + if (conn->priv->flags & RSPAMD_HTTP_CONN_FLAG_PROXY) { + /* Write proxied request */ + if ((msg->flags & RSPAMD_HTTP_FLAG_HAS_HOST_HEADER)) { + rspamd_printf_fstring(buf, + "%s %s://%s:%d/%V HTTP/1.1\r\n" + "Connection: %s\r\n" + "Content-Length: %z\r\n", + http_method_str(msg->method), + (conn->opts & RSPAMD_HTTP_CLIENT_SSL) ? "https" : "http", + host, + msg->port, + msg->url, + conn_type, + bodylen); + } + else { + if (rspamd_http_message_is_standard_port(msg)) { + rspamd_printf_fstring(buf, + "%s %s://%s:%d/%V HTTP/1.1\r\n" + "Connection: %s\r\n" + "Host: %s\r\n" + "Content-Length: %z\r\n", + http_method_str(msg->method), + (conn->opts & RSPAMD_HTTP_CLIENT_SSL) ? "https" : "http", + host, + msg->port, + msg->url, + conn_type, + host, + bodylen); + } + else { + rspamd_printf_fstring(buf, + "%s %s://%s:%d/%V HTTP/1.1\r\n" + "Connection: %s\r\n" + "Host: %s:%d\r\n" + "Content-Length: %z\r\n", + http_method_str(msg->method), + (conn->opts & RSPAMD_HTTP_CLIENT_SSL) ? "https" : "http", + host, + msg->port, + msg->url, + conn_type, + host, + msg->port, + bodylen); + } + } + } + else { + /* Unproxied version */ + if ((msg->flags & RSPAMD_HTTP_FLAG_HAS_HOST_HEADER)) { + rspamd_printf_fstring(buf, + "%s %V HTTP/1.1\r\n" + "Connection: %s\r\n" + "Content-Length: %z\r\n", + http_method_str(msg->method), + msg->url, + conn_type, + bodylen); + } + else { + if (rspamd_http_message_is_standard_port(msg)) { + rspamd_printf_fstring(buf, + "%s %V HTTP/1.1\r\n" + "Connection: %s\r\n" + "Host: %s\r\n" + "Content-Length: %z\r\n", + http_method_str(msg->method), + msg->url, + conn_type, + host, + bodylen); + } + else { + rspamd_printf_fstring(buf, + "%s %V HTTP/1.1\r\n" + "Connection: %s\r\n" + "Host: %s:%d\r\n" + "Content-Length: %z\r\n", + http_method_str(msg->method), + msg->url, + conn_type, + host, + msg->port, + bodylen); + } + } + } + + if (bodylen > 0) { + if (mime_type != NULL) { + rspamd_printf_fstring(buf, + "Content-Type: %s\r\n", + mime_type); + } + } + } + } + + if (encrypted) { + GString *b32_key, *b32_id; + + b32_key = rspamd_keypair_print(priv->local_key, + RSPAMD_KEYPAIR_PUBKEY | RSPAMD_KEYPAIR_BASE32); + b32_id = rspamd_pubkey_print(peer_key, + RSPAMD_KEYPAIR_ID_SHORT | RSPAMD_KEYPAIR_BASE32); + /* XXX: add some fuzz here */ + rspamd_printf_fstring(&*buf, "Key: %v=%v\r\n", b32_id, b32_key); + g_string_free(b32_key, TRUE); + g_string_free(b32_id, TRUE); + } + } + + return meth_len; +} + +static gboolean +rspamd_http_connection_write_message_common(struct rspamd_http_connection *conn, + struct rspamd_http_message *msg, + const gchar *host, + const gchar *mime_type, + gpointer ud, + ev_tstamp timeout, + gboolean allow_shared) +{ + struct rspamd_http_connection_private *priv = conn->priv; + struct rspamd_http_header *hdr, *hcur; + gchar repbuf[512], *pbody; + gint i, hdrcount, meth_len = 0, preludelen = 0; + gsize bodylen, enclen = 0; + rspamd_fstring_t *buf; + gboolean encrypted = FALSE; + guchar nonce[rspamd_cryptobox_MAX_NONCEBYTES], mac[rspamd_cryptobox_MAX_MACBYTES]; + guchar *np = NULL, *mp = NULL, *meth_pos = NULL; + struct rspamd_cryptobox_pubkey *peer_key = NULL; + enum rspamd_cryptobox_mode mode; + GError *err; + + conn->ud = ud; + priv->msg = msg; + priv->timeout = timeout; + + priv->header = NULL; + priv->buf = g_malloc0(sizeof(*priv->buf)); + REF_INIT_RETAIN(priv->buf, rspamd_http_privbuf_dtor); + priv->buf->data = rspamd_fstring_sized_new(512); + buf = priv->buf->data; + + if ((msg->flags & RSPAMD_HTTP_FLAG_WANT_SSL) && !(conn->opts & RSPAMD_HTTP_CLIENT_SSL)) { + err = g_error_new(HTTP_ERROR, 400, + "SSL connection requested but not created properly, internal error"); + rspamd_http_connection_ref(conn); + conn->error_handler(conn, err); + rspamd_http_connection_unref(conn); + g_error_free(err); + return FALSE; + } + + if (priv->peer_key && priv->local_key) { + priv->msg->peer_key = priv->peer_key; + priv->peer_key = NULL; + priv->flags |= RSPAMD_HTTP_CONN_FLAG_ENCRYPTED; + } + + if (msg->peer_key != NULL) { + if (priv->local_key == NULL) { + /* Automatically generate a temporary keypair */ + priv->local_key = rspamd_keypair_new(RSPAMD_KEYPAIR_KEX, + RSPAMD_CRYPTOBOX_MODE_25519); + } + + encrypted = TRUE; + + if (priv->cache) { + rspamd_keypair_cache_process(priv->cache, + priv->local_key, priv->msg->peer_key); + } + } + + if (encrypted && (msg->flags & + (RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE | RSPAMD_HTTP_FLAG_SHMEM))) { + /* We cannot use immutable body to encrypt message in place */ + allow_shared = FALSE; + rspamd_http_detach_shared(msg); + } + + if (allow_shared) { + gchar tmpbuf[64]; + + if (!(msg->flags & RSPAMD_HTTP_FLAG_SHMEM) || + msg->body_buf.c.shared.name == NULL) { + allow_shared = FALSE; + } + else { + /* Insert new headers */ + rspamd_http_message_add_header(msg, "Shm", + msg->body_buf.c.shared.name->shm_name); + rspamd_snprintf(tmpbuf, sizeof(tmpbuf), "%d", + (int) (msg->body_buf.begin - msg->body_buf.str)); + rspamd_http_message_add_header(msg, "Shm-Offset", + tmpbuf); + rspamd_snprintf(tmpbuf, sizeof(tmpbuf), "%z", + msg->body_buf.len); + rspamd_http_message_add_header(msg, "Shm-Length", + tmpbuf); + } + } + + if (priv->ctx->config.user_agent && conn->type == RSPAMD_HTTP_CLIENT) { + rspamd_ftok_t srch; + khiter_t k; + gint r; + + RSPAMD_FTOK_ASSIGN(&srch, "User-Agent"); + + k = kh_put(rspamd_http_headers_hash, msg->headers, &srch, &r); + + if (r != 0) { + hdr = g_malloc0(sizeof(struct rspamd_http_header)); + guint vlen = strlen(priv->ctx->config.user_agent); + hdr->combined = rspamd_fstring_sized_new(srch.len + vlen + 4); + rspamd_printf_fstring(&hdr->combined, "%T: %*s\r\n", &srch, vlen, + priv->ctx->config.user_agent); + hdr->name.begin = hdr->combined->str; + hdr->name.len = srch.len; + hdr->value.begin = hdr->combined->str + srch.len + 2; + hdr->value.len = vlen; + hdr->prev = hdr; /* for utlists */ + + kh_value(msg->headers, k) = hdr; + /* as we searched using static buffer */ + kh_key(msg->headers, k) = &hdr->name; + } + } + + if (encrypted) { + mode = rspamd_keypair_alg(priv->local_key); + + if (msg->body_buf.len == 0) { + pbody = NULL; + bodylen = 0; + msg->method = HTTP_GET; + } + else { + pbody = (gchar *) msg->body_buf.begin; + bodylen = msg->body_buf.len; + msg->method = HTTP_POST; + } + + if (conn->type == RSPAMD_HTTP_SERVER) { + /* + * iov[0] = base reply + * iov[1] = CRLF + * iov[2] = nonce + * iov[3] = mac + * iov[4] = encrypted reply + * iov[6] = encrypted crlf + * iov[7..n] = encrypted headers + * iov[n + 1] = encrypted crlf + * [iov[n + 2] = encrypted body] + */ + priv->outlen = 7; + enclen = rspamd_cryptobox_nonce_bytes(mode) + + rspamd_cryptobox_mac_bytes(mode) + + 4 + /* 2 * CRLF */ + bodylen; + } + else { + /* + * iov[0] = base request + * iov[1] = CRLF + * iov[2] = nonce + * iov[3] = mac + * iov[4] = encrypted method + space + * iov[5] = encrypted url + * iov[7] = encrypted prelude + * iov[8..n] = encrypted headers + * iov[n + 1] = encrypted crlf + * [iov[n + 2] = encrypted body] + */ + priv->outlen = 8; + + if (bodylen > 0) { + if (mime_type != NULL) { + preludelen = rspamd_snprintf(repbuf, sizeof(repbuf), "%s\r\n" + "Content-Length: %z\r\n" + "Content-Type: %s\r\n" + "\r\n", + ENCRYPTED_VERSION, bodylen, + mime_type); + } + else { + preludelen = rspamd_snprintf(repbuf, sizeof(repbuf), "%s\r\n" + "Content-Length: %z\r\n" + "" + "\r\n", + ENCRYPTED_VERSION, bodylen); + } + } + else { + preludelen = rspamd_snprintf(repbuf, sizeof(repbuf), + "%s\r\n\r\n", + ENCRYPTED_VERSION); + } + + enclen = rspamd_cryptobox_nonce_bytes(mode) + + rspamd_cryptobox_mac_bytes(mode) + + preludelen + /* version [content-length] + 2 * CRLF */ + bodylen; + } + + if (bodylen > 0) { + priv->outlen++; + } + } + else { + if (msg->method < HTTP_SYMBOLS) { + if (msg->body_buf.len == 0 || allow_shared) { + pbody = NULL; + bodylen = 0; + priv->outlen = 2; + + if (msg->method == HTTP_INVALID) { + msg->method = HTTP_GET; + } + } + else { + pbody = (gchar *) msg->body_buf.begin; + bodylen = msg->body_buf.len; + priv->outlen = 3; + + if (msg->method == HTTP_INVALID) { + msg->method = HTTP_POST; + } + } + } + else if (msg->body_buf.len > 0) { + allow_shared = FALSE; + pbody = (gchar *) msg->body_buf.begin; + bodylen = msg->body_buf.len; + priv->outlen = 2; + } + else { + /* Invalid body for spamc method */ + abort(); + } + } + + peer_key = msg->peer_key; + + priv->wr_total = bodylen + 2; + + hdrcount = 0; + + if (msg->method < HTTP_SYMBOLS) { + kh_foreach_value (msg->headers, hdr, { + DL_FOREACH (hdr, hcur) { + /* <name: value\r\n> */ + priv->wr_total += hcur->combined->len; + enclen += hcur->combined->len; + priv->outlen ++; + hdrcount ++; + } +}); +} + +/* Allocate iov */ +priv->out = g_malloc0(sizeof(struct iovec) * priv->outlen); +priv->wr_pos = 0; + +meth_len = rspamd_http_message_write_header(mime_type, encrypted, + repbuf, sizeof(repbuf), bodylen, enclen, + host, conn, msg, + &buf, priv, peer_key); +priv->wr_total += buf->len; + +/* Setup external request body */ +priv->out[0].iov_base = buf->str; +priv->out[0].iov_len = buf->len; + +/* Buf will be used eventually for encryption */ +if (encrypted) { + gint meth_offset, nonce_offset, mac_offset; + mode = rspamd_keypair_alg(priv->local_key); + + ottery_rand_bytes(nonce, rspamd_cryptobox_nonce_bytes(mode)); + memset(mac, 0, rspamd_cryptobox_mac_bytes(mode)); + meth_offset = buf->len; + + if (conn->type == RSPAMD_HTTP_SERVER) { + buf = rspamd_fstring_append(buf, repbuf, meth_len); + } + else { + meth_len = strlen(http_method_str(msg->method)) + 1; /* + space */ + buf = rspamd_fstring_append(buf, http_method_str(msg->method), + meth_len - 1); + buf = rspamd_fstring_append(buf, " ", 1); + } + + nonce_offset = buf->len; + buf = rspamd_fstring_append(buf, nonce, + rspamd_cryptobox_nonce_bytes(mode)); + mac_offset = buf->len; + buf = rspamd_fstring_append(buf, mac, + rspamd_cryptobox_mac_bytes(mode)); + + /* Need to be encrypted */ + if (conn->type == RSPAMD_HTTP_SERVER) { + buf = rspamd_fstring_append(buf, "\r\n\r\n", 4); + } + else { + buf = rspamd_fstring_append(buf, repbuf, preludelen); + } + + meth_pos = buf->str + meth_offset; + np = buf->str + nonce_offset; + mp = buf->str + mac_offset; +} + +/* During previous writes, buf might be reallocated and changed */ +priv->buf->data = buf; + +if (encrypted) { + /* Finish external HTTP request */ + priv->out[1].iov_base = "\r\n"; + priv->out[1].iov_len = 2; + /* Encrypt the real request */ + rspamd_http_connection_encrypt_message(conn, msg, priv, pbody, bodylen, + meth_pos, meth_len, preludelen, hdrcount, np, mp, peer_key); +} +else { + i = 1; + if (msg->method < HTTP_SYMBOLS) { + kh_foreach_value (msg->headers, hdr, { + DL_FOREACH (hdr, hcur) { + priv->out[i].iov_base = hcur->combined->str; + priv->out[i++].iov_len = hcur->combined->len; + } +}); + +priv->out[i].iov_base = "\r\n"; +priv->out[i++].iov_len = 2; +} +else +{ + /* No CRLF for compatibility reply */ + priv->wr_total -= 2; +} + +if (pbody != NULL) { + priv->out[i].iov_base = pbody; + priv->out[i++].iov_len = bodylen; +} +} + +priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_RESETED; + +if ((priv->flags & RSPAMD_HTTP_CONN_FLAG_PROXY) && (conn->opts & RSPAMD_HTTP_CLIENT_SSL)) { + /* We need to disable SSL flag! */ + err = g_error_new(HTTP_ERROR, 400, "cannot use proxy for SSL connections"); + rspamd_http_connection_ref(conn); + conn->error_handler(conn, err); + rspamd_http_connection_unref(conn); + g_error_free(err); + return FALSE; +} + +rspamd_ev_watcher_stop(priv->ctx->event_loop, &priv->ev); + +if (conn->opts & RSPAMD_HTTP_CLIENT_SSL) { + gpointer ssl_ctx = (msg->flags & RSPAMD_HTTP_FLAG_SSL_NOVERIFY) ? priv->ctx->ssl_ctx_noverify : priv->ctx->ssl_ctx; + + if (!ssl_ctx) { + err = g_error_new(HTTP_ERROR, 400, "ssl message requested " + "with no ssl ctx"); + rspamd_http_connection_ref(conn); + conn->error_handler(conn, err); + rspamd_http_connection_unref(conn); + g_error_free(err); + return FALSE; + } + else { + if (!priv->ssl) { + priv->ssl = rspamd_ssl_connection_new(ssl_ctx, priv->ctx->event_loop, + !(msg->flags & RSPAMD_HTTP_FLAG_SSL_NOVERIFY), + conn->log_tag); + g_assert(priv->ssl != NULL); + + if (!rspamd_ssl_connect_fd(priv->ssl, conn->fd, host, &priv->ev, + priv->timeout, rspamd_http_event_handler, + rspamd_http_ssl_err_handler, conn)) { + + err = g_error_new(HTTP_ERROR, 400, + "ssl connection error: ssl error=%s, errno=%s", + ERR_error_string(ERR_get_error(), NULL), + strerror(errno)); + rspamd_http_connection_ref(conn); + conn->error_handler(conn, err); + rspamd_http_connection_unref(conn); + g_error_free(err); + return FALSE; + } + } + else { + /* Just restore SSL handlers */ + rspamd_ssl_connection_restore_handlers(priv->ssl, + rspamd_http_event_handler, + rspamd_http_ssl_err_handler, + conn, + EV_WRITE); + } + } +} +else { + rspamd_ev_watcher_init(&priv->ev, conn->fd, EV_WRITE, + rspamd_http_event_handler, conn); + rspamd_ev_watcher_start(priv->ctx->event_loop, &priv->ev, priv->timeout); +} + +return TRUE; +} + +gboolean +rspamd_http_connection_write_message(struct rspamd_http_connection *conn, + struct rspamd_http_message *msg, + const gchar *host, + const gchar *mime_type, + gpointer ud, + ev_tstamp timeout) +{ + return rspamd_http_connection_write_message_common(conn, msg, host, mime_type, + ud, timeout, FALSE); +} + +gboolean +rspamd_http_connection_write_message_shared(struct rspamd_http_connection *conn, + struct rspamd_http_message *msg, + const gchar *host, + const gchar *mime_type, + gpointer ud, + ev_tstamp timeout) +{ + return rspamd_http_connection_write_message_common(conn, msg, host, mime_type, + ud, timeout, TRUE); +} + + +void rspamd_http_connection_set_max_size(struct rspamd_http_connection *conn, + gsize sz) +{ + conn->max_size = sz; +} + +void rspamd_http_connection_set_key(struct rspamd_http_connection *conn, + struct rspamd_cryptobox_keypair *key) +{ + struct rspamd_http_connection_private *priv = conn->priv; + + g_assert(key != NULL); + priv->local_key = rspamd_keypair_ref(key); +} + +void rspamd_http_connection_own_socket(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv = conn->priv; + + priv->flags |= RSPAMD_HTTP_CONN_OWN_SOCKET; +} + +const struct rspamd_cryptobox_pubkey * +rspamd_http_connection_get_peer_key(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv = conn->priv; + + if (priv->peer_key) { + return priv->peer_key; + } + else if (priv->msg) { + return priv->msg->peer_key; + } + + return NULL; +} + +gboolean +rspamd_http_connection_is_encrypted(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv = conn->priv; + + if (priv->peer_key != NULL) { + return TRUE; + } + else if (priv->msg) { + return priv->msg->peer_key != NULL; + } + + return FALSE; +} + +GHashTable * +rspamd_http_message_parse_query(struct rspamd_http_message *msg) +{ + GHashTable *res; + rspamd_fstring_t *key = NULL, *value = NULL; + rspamd_ftok_t *key_tok = NULL, *value_tok = NULL; + const gchar *p, *c, *end; + struct http_parser_url u; + enum { + parse_key, + parse_eqsign, + parse_value, + parse_ampersand + } state = parse_key; + + res = g_hash_table_new_full(rspamd_ftok_icase_hash, + rspamd_ftok_icase_equal, + rspamd_fstring_mapped_ftok_free, + rspamd_fstring_mapped_ftok_free); + + if (msg->url && msg->url->len > 0) { + http_parser_parse_url(msg->url->str, msg->url->len, TRUE, &u); + + if (u.field_set & (1 << UF_QUERY)) { + p = msg->url->str + u.field_data[UF_QUERY].off; + c = p; + end = p + u.field_data[UF_QUERY].len; + + while (p <= end) { + switch (state) { + case parse_key: + if ((p == end || *p == '&') && p > c) { + /* We have a single parameter without a value */ + key = rspamd_fstring_new_init(c, p - c); + key_tok = rspamd_ftok_map(key); + key_tok->len = rspamd_url_decode(key->str, key->str, + key->len); + + value = rspamd_fstring_new_init("", 0); + value_tok = rspamd_ftok_map(value); + + g_hash_table_replace(res, key_tok, value_tok); + state = parse_ampersand; + } + else if (*p == '=' && p > c) { + /* We have something like key=value */ + key = rspamd_fstring_new_init(c, p - c); + key_tok = rspamd_ftok_map(key); + key_tok->len = rspamd_url_decode(key->str, key->str, + key->len); + + state = parse_eqsign; + } + else { + p++; + } + break; + + case parse_eqsign: + if (*p != '=') { + c = p; + state = parse_value; + } + else { + p++; + } + break; + + case parse_value: + if ((p == end || *p == '&') && p >= c) { + g_assert(key != NULL); + if (p > c) { + value = rspamd_fstring_new_init(c, p - c); + value_tok = rspamd_ftok_map(value); + value_tok->len = rspamd_url_decode(value->str, + value->str, + value->len); + /* Detect quotes for value */ + if (value_tok->begin[0] == '"') { + memmove(value->str, value->str + 1, + value_tok->len - 1); + value_tok->len--; + } + if (value_tok->begin[value_tok->len - 1] == '"') { + value_tok->len--; + } + } + else { + value = rspamd_fstring_new_init("", 0); + value_tok = rspamd_ftok_map(value); + } + + g_hash_table_replace(res, key_tok, value_tok); + key = value = NULL; + key_tok = value_tok = NULL; + state = parse_ampersand; + } + else { + p++; + } + break; + + case parse_ampersand: + if (p != end && *p != '&') { + c = p; + state = parse_key; + } + else { + p++; + } + break; + } + } + } + + if (state != parse_ampersand && key != NULL) { + rspamd_fstring_free(key); + } + } + + return res; +} + + +struct rspamd_http_message * +rspamd_http_message_ref(struct rspamd_http_message *msg) +{ + REF_RETAIN(msg); + + return msg; +} + +void rspamd_http_message_unref(struct rspamd_http_message *msg) +{ + REF_RELEASE(msg); +} + +void rspamd_http_connection_disable_encryption(struct rspamd_http_connection *conn) +{ + struct rspamd_http_connection_private *priv; + + priv = conn->priv; + + if (priv) { + if (priv->local_key) { + rspamd_keypair_unref(priv->local_key); + } + if (priv->peer_key) { + rspamd_pubkey_unref(priv->peer_key); + } + + priv->local_key = NULL; + priv->peer_key = NULL; + priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_ENCRYPTED; + } +}
\ No newline at end of file |