/*- * 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 "../../contrib/mumhash/mum.h" #include "http_private.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 "unix-std.h" #include "libutil/ssl_util.h" #include "libutil/regexp.h" #include "libserver/url.h" #include #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 = 1 << 0, RSPAMD_HTTP_CONN_FLAG_NEW_HEADER = 1 << 1, RSPAMD_HTTP_CONN_FLAG_RESETED = 1 << 2, RSPAMD_HTTP_CONN_FLAG_TOO_LARGE = 1 << 3, }; #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 { gpointer ssl_ctx; struct rspamd_ssl_connection *ssl; struct _rspamd_http_privbuf *buf; 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 event ev; struct timeval tv; struct timeval *ptv; struct rspamd_http_message *msg; struct iovec *out; guint outlen; enum rspamd_http_priv_flags flags; gsize wr_pos; gsize wr_total; }; enum http_magic_type { HTTP_MAGIC_PLAIN = 0, HTTP_MAGIC_HTML, HTTP_MAGIC_CSS, HTTP_MAGIC_JS, HTTP_MAGIC_PNG, HTTP_MAGIC_JPG }; static const struct _rspamd_http_magic { const gchar *ext; const gchar *ct; } http_file_types[] = { [HTTP_MAGIC_PLAIN] = { "txt", "text/plain" }, [HTTP_MAGIC_HTML] = { "html", "text/html" }, [HTTP_MAGIC_CSS] = { "css", "text/css" }, [HTTP_MAGIC_JS] = { "js", "application/javascript" }, [HTTP_MAGIC_PNG] = { "png", "image/png" }, [HTTP_MAGIC_JPG] = { "jpg", "image/jpeg" }, }; static const gchar *http_week[] = { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" }; static const gchar *http_month[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" }; 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_message_storage_cleanup (struct rspamd_http_message *msg); static gboolean rspamd_http_message_grow_body (struct rspamd_http_message *msg, gsize len); #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"; } /* * Obtained from nginx * Copyright (C) Igor Sysoev */ static guint mday[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; time_t rspamd_http_parse_date (const gchar *header, gsize len) { const gchar *p, *end; gint month; guint day, year, hour, min, sec; guint64 time; enum { no = 0, rfc822, /* Tue, 10 Nov 2002 23:50:13 */ rfc850, /* Tuesday, 10-Dec-02 23:50:13 */ isoc /* Tue Dec 10 23:50:13 2002 */ } fmt; fmt = 0; if (len > 0) { end = header + len; } else { end = header + strlen (header); } day = 32; year = 2038; for (p = header; p < end; p++) { if (*p == ',') { break; } if (*p == ' ') { fmt = isoc; break; } } for (p++; p < end; p++) if (*p != ' ') { break; } if (end - p < 18) { return (time_t)-1; } if (fmt != isoc) { if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { return (time_t)-1; } day = (*p - '0') * 10 + *(p + 1) - '0'; p += 2; if (*p == ' ') { if (end - p < 18) { return (time_t)-1; } fmt = rfc822; } else if (*p == '-') { fmt = rfc850; } else { return (time_t)-1; } p++; } switch (*p) { case 'J': month = *(p + 1) == 'a' ? 0 : *(p + 2) == 'n' ? 5 : 6; break; case 'F': month = 1; break; case 'M': month = *(p + 2) == 'r' ? 2 : 4; break; case 'A': month = *(p + 1) == 'p' ? 3 : 7; break; case 'S': month = 8; break; case 'O': month = 9; break; case 'N': month = 10; break; case 'D': month = 11; break; default: return (time_t)-1; } p += 3; if ((fmt == rfc822 && *p != ' ') || (fmt == rfc850 && *p != '-')) { return (time_t)-1; } p++; if (fmt == rfc822) { if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9' || *(p + 2) < '0' || *(p + 2) > '9' || *(p + 3) < '0' || *(p + 3) > '9') { return (time_t)-1; } year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100 + (*(p + 2) - '0') * 10 + *(p + 3) - '0'; p += 4; } else if (fmt == rfc850) { if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { return (time_t)-1; } year = (*p - '0') * 10 + *(p + 1) - '0'; year += (year < 70) ? 2000 : 1900; p += 2; } if (fmt == isoc) { if (*p == ' ') { p++; } if (*p < '0' || *p > '9') { return (time_t)-1; } day = *p++ - '0'; if (*p != ' ') { if (*p < '0' || *p > '9') { return (time_t)-1; } day = day * 10 + *p++ - '0'; } if (end - p < 14) { return (time_t)-1; } } if (*p++ != ' ') { return (time_t)-1; } if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { return (time_t)-1; } hour = (*p - '0') * 10 + *(p + 1) - '0'; p += 2; if (*p++ != ':') { return (time_t)-1; } if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { return (time_t)-1; } min = (*p - '0') * 10 + *(p + 1) - '0'; p += 2; if (*p++ != ':') { return (time_t)-1; } if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9') { return (time_t)-1; } sec = (*p - '0') * 10 + *(p + 1) - '0'; if (fmt == isoc) { p += 2; if (*p++ != ' ') { return (time_t)-1; } if (*p < '0' || *p > '9' || *(p + 1) < '0' || *(p + 1) > '9' || *(p + 2) < '0' || *(p + 2) > '9' || *(p + 3) < '0' || *(p + 3) > '9') { return (time_t)-1; } year = (*p - '0') * 1000 + (*(p + 1) - '0') * 100 + (*(p + 2) - '0') * 10 + *(p + 3) - '0'; } if (hour > 23 || min > 59 || sec > 59) { return (time_t)-1; } if (day == 29 && month == 1) { if ((year & 3) || ((year % 100 == 0) && (year % 400) != 0)) { return (time_t)-1; } } else if (day > mday[month]) { return (time_t)-1; } /* * shift new year to March 1 and start months from 1 (not 0), * it is needed for Gauss' formula */ if (--month <= 0) { month += 12; year -= 1; } /* Gauss' formula for Gregorian days since March 1, 1 BC */ time = (guint64) ( /* days in years including leap years since March 1, 1 BC */ 365 * year + year / 4 - year / 100 + year / 400 /* days before the month */ + 367 * month / 12 - 30 /* days before the day */ + day - 1 /* * 719527 days were between March 1, 1 BC and March 1, 1970, * 31 and 28 days were in January and February 1970 */ - 719527 + 31 + 28) * 86400 + hour * 3600 + min * 60 + sec; return (time_t) time; } 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); 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 (conn->cache && priv->msg->peer_key) { rspamd_keypair_cache_process (conn->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; 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; HASH_FIND (hh, priv->msg->headers, priv->header->name.begin, priv->header->name.len, hdr); if (hdr == NULL) { HASH_ADD_KEYPTR (hh, priv->msg->headers, priv->header->name.begin, priv->header->name.len, priv->header); } 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 */ if (rspamd_event_pending (&priv->ev, EV_READ)) { event_del (&priv->ev); } msg->code = parser->status_code; rspamd_http_connection_ref (conn); ret = conn->finish_handler (conn, msg); 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) { 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 */ if (rspamd_event_pending (&priv->ev, EV_READ)) { event_del (&priv->ev); } msg->code = parser->status_code; rspamd_http_connection_ref (conn); ret = conn->finish_handler (conn, msg); 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, *hdrtmp, *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 */ HASH_ITER (hh, msg->headers, hdr, hdrtmp) { HASH_DELETE (hh, msg->headers, hdr); DL_FOREACH_SAFE (hdr, hcur, hcurtmp) { rspamd_fstring_free (hcur->combined); g_free (hcur); } } msg->headers = NULL; 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_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) { if (rspamd_event_pending (&priv->ev, EV_READ)) { event_del (&priv->ev); } rspamd_http_connection_ref (conn); ret = conn->finish_handler (conn, priv->msg); conn->finished = TRUE; rspamd_http_connection_unref (conn); } return ret; } static void rspamd_http_simple_client_helper (struct rspamd_http_connection *conn) { struct event_base *base; struct rspamd_http_connection_private *priv; gpointer ssl; gint request_method; priv = conn->priv; base = conn->priv->ev.ev_base; ssl = priv->ssl; priv->ssl = NULL; request_method = priv->msg->method; 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->fd, conn->priv->ptv, base); } else { rspamd_http_connection_read_message (conn, conn->ud, conn->fd, conn->priv->ptv, base); } priv->msg->method = request_method; } 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 */ 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); } else { r = sendmsg (conn->fd, &msg, flags); } if (r == -1) { if (!priv->ssl) { err = g_error_new (HTTP_ERROR, errno, "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; event_add (&priv->ev, priv->ptv); } return; call_finish_handler: 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 { err = g_error_new (HTTP_ERROR, priv->parser.http_errno, "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 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, errno, "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, errno, "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) { /* 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, priv->parser.http_errno, "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 if (r == 0) { if (!conn->finished) { err = g_error_new (HTTP_ERROR, ETIMEDOUT, "IO timeout"); conn->error_handler (conn, err); g_error_free (err); } REF_RELEASE (pbuf); rspamd_http_connection_unref (conn); return; } else { err = g_error_new (HTTP_ERROR, ETIMEDOUT, "IO timeout"); conn->error_handler (conn, err); g_error_free (err); 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; } struct rspamd_http_connection * rspamd_http_connection_new ( 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, struct rspamd_keypair_cache *cache, gpointer ssl_ctx) { struct rspamd_http_connection *conn; struct rspamd_http_connection_private *priv; if (error_handler == NULL || finish_handler == NULL) { return NULL; } 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 = -1; conn->ref = 1; conn->finished = FALSE; conn->cache = cache; /* Init priv */ priv = g_malloc0 (sizeof (struct rspamd_http_connection_private)); conn->priv = priv; priv->ssl_ctx = ssl_ctx; rspamd_http_parser_reset (conn); priv->parser.data = conn; 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 */ if (!(priv->flags & RSPAMD_HTTP_CONN_FLAG_RESETED)) { if (rspamd_event_pending (&priv->ev, EV_READ|EV_WRITE|EV_TIMEOUT)) { event_del (&priv->ev); } 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, *thdr, *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 maping 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 = rspamd_fstring_new_init (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; HASH_ITER (hh, msg->headers, hdr, thdr) { 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); } HASH_ADD_KEYPTR (hh, new_msg->headers, nhdrs->name.begin, nhdrs->name.len, 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); } g_free (priv); } g_free (conn); } static void rspamd_http_connection_read_message_common (struct rspamd_http_connection *conn, gpointer ud, gint fd, struct timeval *timeout, struct event_base *base, gint flags) { struct rspamd_http_connection_private *priv = conn->priv; struct rspamd_http_message *req; conn->fd = fd; 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; } if (timeout == NULL) { priv->ptv = NULL; } else if (&priv->tv != timeout) { memcpy (&priv->tv, timeout, sizeof (struct timeval)); priv->ptv = &priv->tv; } 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; event_set (&priv->ev, fd, EV_READ | EV_PERSIST, rspamd_http_event_handler, conn); if (base != NULL) { event_base_set (base, &priv->ev); } priv->flags &= ~RSPAMD_HTTP_CONN_FLAG_RESETED; event_add (&priv->ev, priv->ptv); } void rspamd_http_connection_read_message (struct rspamd_http_connection *conn, gpointer ud, gint fd, struct timeval *timeout, struct event_base *base) { rspamd_http_connection_read_message_common (conn, ud, fd, timeout, base, 0); } void rspamd_http_connection_read_message_shared (struct rspamd_http_connection *conn, gpointer ud, gint fd, struct timeval *timeout, struct event_base *base) { rspamd_http_connection_read_message_common (conn, ud, fd, timeout, base, 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, *htmp, *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; } HASH_ITER (hh, msg->headers, hdr, htmp) { 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; struct tm t; if (conn->type == RSPAMD_HTTP_SERVER) { /* Format reply */ if (msg->method < HTTP_SYMBOLS) { rspamd_ftok_t status; rspamd_gmtime (msg->date, &t); rspamd_snprintf (datebuf, sizeof(datebuf), "%s, %02d %s %4d %02d:%02d:%02d GMT", http_week[t.tm_wday], t.tm_mday, http_month[t.tm_mon], t.tm_year + 1900, t.tm_hour, t.tm_min, t.tm_sec); 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, "rspamd/" RVERSION, 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, "rspamd/" RVERSION, datebuf, bodylen); } enclen += meth_len; /* External reply */ rspamd_printf_fstring (buf, "HTTP/1.1 200 OK\r\n" "Connection: close\r\n" "Server: rspamd\r\n" "Date: %s\r\n" "Content-Length: %z\r\n" "Content-Type: application/octet-stream\r\n", 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, "rspamd/" RVERSION, 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, "rspamd/" RVERSION, 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 { /* Format request */ enclen += msg->url->len + 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", "POST", "/post", enclen); } else { rspamd_printf_fstring (buf, "%s %V HTTP/1.0\r\n" "Content-Length: %z\r\n", http_method_str (msg->method), msg->url, bodylen); if (bodylen > 0) { if (mime_type == NULL) { mime_type = "text/plain"; } rspamd_printf_fstring (buf, "Content-Type: %s\r\n", mime_type); } } } else { if (encrypted) { if (host != NULL) { rspamd_printf_fstring (buf, "%s %s HTTP/1.1\r\n" "Connection: close\r\n" "Host: %s\r\n" "Content-Length: %z\r\n" "Content-Type: application/octet-stream\r\n", "POST", "/post", host, enclen); } else { rspamd_printf_fstring (buf, "%s %s HTTP/1.1\r\n" "Connection: close\r\n" "Host: %V\r\n" "Content-Length: %z\r\n" "Content-Type: application/octet-stream\r\n", "POST", "/post", msg->host, enclen); } } else { if (host != NULL) { rspamd_printf_fstring (buf, "%s %V HTTP/1.1\r\nConnection: close\r\n" "Host: %s\r\n" "Content-Length: %z\r\n", http_method_str (msg->method), msg->url, host, bodylen); } else { rspamd_printf_fstring (buf, "%s %V HTTP/1.1\r\n" "Connection: close\r\n" "Host: %V\r\n" "Content-Length: %z\r\n", http_method_str (msg->method), msg->url, msg->host, 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 void 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, gint fd, struct timeval *timeout, struct event_base *base, gboolean allow_shared) { struct rspamd_http_connection_private *priv = conn->priv; struct rspamd_http_header *hdr, *htmp, *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->fd = fd; conn->ud = ud; priv->msg = msg; if (timeout == NULL) { priv->ptv = NULL; } else if (timeout != &priv->tv) { memcpy (&priv->tv, timeout, sizeof (struct timeval)); priv->ptv = &priv->tv; } 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 (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 (conn->cache) { rspamd_keypair_cache_process (conn->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 (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); } 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\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 */ g_assert (0); } } peer_key = msg->peer_key; priv->wr_total = bodylen + 2; hdrcount = 0; if (msg->method < HTTP_SYMBOLS) { HASH_ITER (hh, msg->headers, hdr, htmp) { DL_FOREACH (hdr, hcur) { /* */ 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) { HASH_ITER (hh, msg->headers, hdr, htmp) { 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 (rspamd_event_pending (&priv->ev, EV_TIMEOUT|EV_WRITE|EV_READ)) { event_del (&priv->ev); } if (msg->flags & RSPAMD_HTTP_FLAG_SSL) { if (base != NULL) { event_base_set (base, &priv->ev); } if (!priv->ssl_ctx) { err = g_error_new (HTTP_ERROR, errno, "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; } else { if (priv->ssl) { /* Cleanup the existing connection */ rspamd_ssl_connection_free (priv->ssl); } priv->ssl = rspamd_ssl_connection_new (priv->ssl_ctx, base, !(msg->flags & RSPAMD_HTTP_FLAG_SSL_NOVERIFY)); g_assert (priv->ssl != NULL); if (!rspamd_ssl_connect_fd (priv->ssl, fd, host, &priv->ev, priv->ptv, rspamd_http_event_handler, rspamd_http_ssl_err_handler, conn)) { err = g_error_new (HTTP_ERROR, errno, "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; } } } else { event_set (&priv->ev, fd, EV_WRITE, rspamd_http_event_handler, conn); if (base != NULL) { event_base_set (base, &priv->ev); } event_add (&priv->ev, priv->ptv); } } void rspamd_http_connection_write_message (struct rspamd_http_connection *conn, struct rspamd_http_message *msg, const gchar *host, const gchar *mime_type, gpointer ud, gint fd, struct timeval *timeout, struct event_base *base) { rspamd_http_connection_write_message_common (conn, msg, host, mime_type, ud, fd, timeout, base, FALSE); } void 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, gint fd, struct timeval *timeout, struct event_base *base) { rspamd_http_connection_write_message_common (conn, msg, host, mime_type, ud, fd, timeout, base, TRUE); } struct rspamd_http_message * rspamd_http_new_message (enum http_parser_type type) { struct rspamd_http_message *new; new = g_malloc0 (sizeof (struct rspamd_http_message)); if (type == HTTP_REQUEST) { new->url = rspamd_fstring_new (); } else { new->url = NULL; new->code = 200; } new->port = 80; new->type = type; new->method = HTTP_INVALID; REF_INIT_RETAIN (new, rspamd_http_message_free); return new; } struct rspamd_http_message* rspamd_http_message_from_url (const gchar *url) { struct http_parser_url pu; struct rspamd_http_message *msg; const gchar *host, *path; size_t pathlen, urllen; guint flags = 0; if (url == NULL) { return NULL; } urllen = strlen (url); memset (&pu, 0, sizeof (pu)); if (http_parser_parse_url (url, urllen, FALSE, &pu) != 0) { msg_warn ("cannot parse URL: %s", url); return NULL; } if ((pu.field_set & (1 << UF_HOST)) == 0) { msg_warn ("no host argument in URL: %s", url); return NULL; } if ((pu.field_set & (1 << UF_SCHEMA))) { if (pu.field_data[UF_SCHEMA].len == sizeof ("https") - 1 && memcmp (url + pu.field_data[UF_SCHEMA].off, "https", 5) == 0) { flags |= RSPAMD_HTTP_FLAG_SSL; } } if ((pu.field_set & (1 << UF_PATH)) == 0) { path = "/"; pathlen = 1; } else { path = url + pu.field_data[UF_PATH].off; pathlen = urllen - pu.field_data[UF_PATH].off; } msg = rspamd_http_new_message (HTTP_REQUEST); host = url + pu.field_data[UF_HOST].off; msg->flags = flags; if ((pu.field_set & (1 << UF_PORT)) != 0) { msg->port = pu.port; } else { /* XXX: magic constant */ if (flags & RSPAMD_HTTP_FLAG_SSL) { msg->port = 443; } else { msg->port = 80; } } msg->host = rspamd_fstring_new_init (host, pu.field_data[UF_HOST].len); msg->url = rspamd_fstring_append (msg->url, path, pathlen); REF_INIT_RETAIN (msg, rspamd_http_message_free); return msg; } const gchar * rspamd_http_message_get_body (struct rspamd_http_message *msg, gsize *blen) { const gchar *ret = NULL; if (msg->body_buf.len > 0) { ret = msg->body_buf.begin; } if (blen) { *blen = msg->body_buf.len; } return ret; } static void rspamd_http_shname_dtor (void *p) { struct rspamd_storage_shmem *n = p; #ifdef HAVE_SANE_SHMEM shm_unlink (n->shm_name); #else unlink (n->shm_name); #endif g_free (n->shm_name); g_free (n); } struct rspamd_storage_shmem * rspamd_http_message_shmem_ref (struct rspamd_http_message *msg) { if ((msg->flags & RSPAMD_HTTP_FLAG_SHMEM) && msg->body_buf.c.shared.name) { REF_RETAIN (msg->body_buf.c.shared.name); return msg->body_buf.c.shared.name; } return NULL; } guint rspamd_http_message_get_flags (struct rspamd_http_message *msg) { return msg->flags; } void rspamd_http_message_shmem_unref (struct rspamd_storage_shmem *p) { REF_RELEASE (p); } gboolean rspamd_http_message_set_body (struct rspamd_http_message *msg, const gchar *data, gsize len) { union _rspamd_storage_u *storage; storage = &msg->body_buf.c; rspamd_http_message_storage_cleanup (msg); if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { storage->shared.name = g_malloc (sizeof (*storage->shared.name)); REF_INIT_RETAIN (storage->shared.name, rspamd_http_shname_dtor); #ifdef HAVE_SANE_SHMEM #if defined(__DragonFly__) // DragonFly uses regular files for shm. User rspamd is not allowed to create // files in the root. storage->shared.name->shm_name = g_strdup ("/tmp/rhm.XXXXXXXXXXXXXXXXXXXX"); #else storage->shared.name->shm_name = g_strdup ("/rhm.XXXXXXXXXXXXXXXXXXXX"); #endif storage->shared.shm_fd = rspamd_shmem_mkstemp (storage->shared.name->shm_name); #else /* XXX: assume that tempdir is /tmp */ storage->shared.name->shm_name = g_strdup ("/tmp/rhm.XXXXXXXXXXXXXXXXXXXX"); storage->shared.shm_fd = mkstemp (storage->shared.name->shm_name); #endif if (storage->shared.shm_fd == -1) { return FALSE; } if (len != 0 && len != ULLONG_MAX) { if (ftruncate (storage->shared.shm_fd, len) == -1) { return FALSE; } msg->body_buf.str = mmap (NULL, len, PROT_WRITE|PROT_READ, MAP_SHARED, storage->shared.shm_fd, 0); if (msg->body_buf.str == MAP_FAILED) { return FALSE; } msg->body_buf.begin = msg->body_buf.str; msg->body_buf.allocated_len = len; if (data != NULL) { memcpy (msg->body_buf.str, data, len); msg->body_buf.len = len; } } else { msg->body_buf.len = 0; msg->body_buf.begin = NULL; msg->body_buf.str = NULL; msg->body_buf.allocated_len = 0; } } else { if (len != 0 && len != ULLONG_MAX) { if (data == NULL) { storage->normal = rspamd_fstring_sized_new (len); msg->body_buf.len = 0; } else { storage->normal = rspamd_fstring_new_init (data, len); msg->body_buf.len = len; } } else { storage->normal = rspamd_fstring_new (); } msg->body_buf.begin = storage->normal->str; msg->body_buf.str = storage->normal->str; msg->body_buf.allocated_len = storage->normal->allocated; } msg->flags |= RSPAMD_HTTP_FLAG_HAS_BODY; return TRUE; } void rspamd_http_message_set_method (struct rspamd_http_message *msg, const gchar *method) { gint i; /* Linear search: not very efficient method */ for (i = 0; i < HTTP_METHOD_MAX; i ++) { if (g_ascii_strcasecmp (method, http_method_str (i)) == 0) { msg->method = i; } } } gboolean rspamd_http_message_set_body_from_fd (struct rspamd_http_message *msg, gint fd) { union _rspamd_storage_u *storage; struct stat st; rspamd_http_message_storage_cleanup (msg); storage = &msg->body_buf.c; msg->flags |= RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE; storage->shared.shm_fd = dup (fd); msg->body_buf.str = MAP_FAILED; if (storage->shared.shm_fd == -1) { return FALSE; } if (fstat (storage->shared.shm_fd, &st) == -1) { return FALSE; } msg->body_buf.str = mmap (NULL, st.st_size, PROT_READ, MAP_SHARED, storage->shared.shm_fd, 0); if (msg->body_buf.str == MAP_FAILED) { return FALSE; } msg->body_buf.begin = msg->body_buf.str; msg->body_buf.len = st.st_size; msg->body_buf.allocated_len = st.st_size; return TRUE; } gboolean rspamd_http_message_set_body_from_fstring_steal (struct rspamd_http_message *msg, rspamd_fstring_t *fstr) { union _rspamd_storage_u *storage; rspamd_http_message_storage_cleanup (msg); storage = &msg->body_buf.c; msg->flags &= ~(RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE); storage->normal = fstr; msg->body_buf.str = fstr->str; msg->body_buf.begin = msg->body_buf.str; msg->body_buf.len = fstr->len; msg->body_buf.allocated_len = fstr->allocated; return TRUE; } gboolean rspamd_http_message_set_body_from_fstring_copy (struct rspamd_http_message *msg, const rspamd_fstring_t *fstr) { union _rspamd_storage_u *storage; rspamd_http_message_storage_cleanup (msg); storage = &msg->body_buf.c; msg->flags &= ~(RSPAMD_HTTP_FLAG_SHMEM|RSPAMD_HTTP_FLAG_SHMEM_IMMUTABLE); storage->normal = rspamd_fstring_new_init (fstr->str, fstr->len); msg->body_buf.str = storage->normal->str; msg->body_buf.begin = msg->body_buf.str; msg->body_buf.len = storage->normal->len; msg->body_buf.allocated_len = storage->normal->allocated; return TRUE; } static gboolean rspamd_http_message_grow_body (struct rspamd_http_message *msg, gsize len) { struct stat st; union _rspamd_storage_u *storage; gsize newlen; storage = &msg->body_buf.c; if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { if (storage->shared.shm_fd == -1) { return FALSE; } if (fstat (storage->shared.shm_fd, &st) == -1) { return FALSE; } /* Check if we need to grow */ if ((gsize)st.st_size < msg->body_buf.len + len) { /* Need to grow */ newlen = rspamd_fstring_suggest_size (msg->body_buf.len, st.st_size, len); /* Unmap as we need another size of segment */ if (msg->body_buf.str != MAP_FAILED) { munmap (msg->body_buf.str, st.st_size); } if (ftruncate (storage->shared.shm_fd, newlen) == -1) { return FALSE; } msg->body_buf.str = mmap (NULL, newlen, PROT_WRITE|PROT_READ, MAP_SHARED, storage->shared.shm_fd, 0); if (msg->body_buf.str == MAP_FAILED) { return FALSE; } msg->body_buf.begin = msg->body_buf.str; msg->body_buf.allocated_len = newlen; } } else { storage->normal = rspamd_fstring_grow (storage->normal, len); /* Append might cause realloc */ msg->body_buf.begin = storage->normal->str; msg->body_buf.len = storage->normal->len; msg->body_buf.str = storage->normal->str; msg->body_buf.allocated_len = storage->normal->allocated; } return TRUE; } gboolean rspamd_http_message_append_body (struct rspamd_http_message *msg, const gchar *data, gsize len) { union _rspamd_storage_u *storage; storage = &msg->body_buf.c; if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { if (!rspamd_http_message_grow_body (msg, len)) { return FALSE; } memcpy (msg->body_buf.str + msg->body_buf.len, data, len); msg->body_buf.len += len; } else { storage->normal = rspamd_fstring_append (storage->normal, data, len); /* Append might cause realloc */ msg->body_buf.begin = storage->normal->str; msg->body_buf.len = storage->normal->len; msg->body_buf.str = storage->normal->str; msg->body_buf.allocated_len = storage->normal->allocated; } return TRUE; } static void rspamd_http_message_storage_cleanup (struct rspamd_http_message *msg) { union _rspamd_storage_u *storage; struct stat st; if (msg->flags & RSPAMD_HTTP_FLAG_SHMEM) { storage = &msg->body_buf.c; if (storage->shared.shm_fd > 0) { g_assert (fstat (storage->shared.shm_fd, &st) != -1); if (msg->body_buf.str != MAP_FAILED) { munmap (msg->body_buf.str, st.st_size); } close (storage->shared.shm_fd); } if (storage->shared.name != NULL) { REF_RELEASE (storage->shared.name); } storage->shared.shm_fd = -1; msg->body_buf.str = MAP_FAILED; } else { if (msg->body_buf.c.normal) { rspamd_fstring_free (msg->body_buf.c.normal); } msg->body_buf.c.normal = NULL; } msg->body_buf.len = 0; } void rspamd_http_connection_set_max_size (struct rspamd_http_connection *conn, gsize sz) { conn->max_size = sz; } void rspamd_http_message_free (struct rspamd_http_message *msg) { struct rspamd_http_header *hdr, *htmp, *hcur, *hcurtmp; HASH_ITER (hh, msg->headers, hdr, htmp) { HASH_DEL (msg->headers, hdr); DL_FOREACH_SAFE (hdr, hcur, hcurtmp) { rspamd_fstring_free (hcur->combined); g_free (hcur); } } rspamd_http_message_storage_cleanup (msg); if (msg->url != NULL) { rspamd_fstring_free (msg->url); } if (msg->status != NULL) { rspamd_fstring_free (msg->status); } if (msg->host != NULL) { rspamd_fstring_free (msg->host); } if (msg->peer_key != NULL) { rspamd_pubkey_unref (msg->peer_key); } g_free (msg); } void rspamd_http_message_set_peer_key (struct rspamd_http_message *msg, struct rspamd_cryptobox_pubkey *pk) { if (msg->peer_key != NULL) { rspamd_pubkey_unref (msg->peer_key); } if (pk) { msg->peer_key = rspamd_pubkey_ref (pk); } else { msg->peer_key = NULL; } } void rspamd_http_message_add_header_len (struct rspamd_http_message *msg, const gchar *name, const gchar *value, gsize len) { struct rspamd_http_header *hdr, *found = NULL; guint nlen, vlen; if (msg != NULL && name != NULL && value != NULL) { hdr = g_malloc0 (sizeof (struct rspamd_http_header)); nlen = strlen (name); vlen = len; hdr->combined = rspamd_fstring_sized_new (nlen + vlen + 4); rspamd_printf_fstring (&hdr->combined, "%s: %*s\r\n", name, (gint)vlen, value); hdr->name.begin = hdr->combined->str; hdr->name.len = nlen; hdr->value.begin = hdr->combined->str + nlen + 2; hdr->value.len = vlen; HASH_FIND (hh, msg->headers, hdr->name.begin, hdr->name.len, found); if (found == NULL) { HASH_ADD_KEYPTR (hh, msg->headers, hdr->name.begin, hdr->name.len, hdr); } DL_APPEND (found, hdr); } } void rspamd_http_message_add_header (struct rspamd_http_message *msg, const gchar *name, const gchar *value) { if (value) { rspamd_http_message_add_header_len (msg, name, value, strlen (value)); } } void rspamd_http_message_add_header_fstr (struct rspamd_http_message *msg, const gchar *name, rspamd_fstring_t *value) { struct rspamd_http_header *hdr, *found = NULL; guint nlen, vlen; if (msg != NULL && name != NULL && value != NULL) { hdr = g_malloc0 (sizeof (struct rspamd_http_header)); nlen = strlen (name); vlen = value->len; hdr->combined = rspamd_fstring_sized_new (nlen + vlen + 4); rspamd_printf_fstring (&hdr->combined, "%s: %V\r\n", name, value); hdr->name.begin = hdr->combined->str; hdr->name.len = nlen; hdr->value.begin = hdr->combined->str + nlen + 2; hdr->value.len = vlen; HASH_FIND (hh, msg->headers, hdr->name.begin, hdr->name.len, found); if (found == NULL) { HASH_ADD_KEYPTR (hh, msg->headers, hdr->name.begin, hdr->name.len, hdr); } DL_APPEND (found, hdr); } } const rspamd_ftok_t * rspamd_http_message_find_header (struct rspamd_http_message *msg, const gchar *name) { struct rspamd_http_header *hdr; const rspamd_ftok_t *res = NULL; guint slen = strlen (name); if (msg != NULL) { HASH_FIND (hh, msg->headers, name, slen, hdr); if (hdr) { res = &hdr->value; } } return res; } GPtrArray* rspamd_http_message_find_header_multiple ( struct rspamd_http_message *msg, const gchar *name) { GPtrArray *res = NULL; struct rspamd_http_header *hdr, *cur; guint slen = strlen (name); if (msg != NULL) { HASH_FIND (hh, msg->headers, name, slen, hdr); if (hdr) { res = g_ptr_array_sized_new (4); LL_FOREACH (hdr, cur) { g_ptr_array_add (res, &cur->value); } } } return res; } gboolean rspamd_http_message_remove_header (struct rspamd_http_message *msg, const gchar *name) { struct rspamd_http_header *hdr, *hcur, *hcurtmp; gboolean res = FALSE; guint slen = strlen (name); if (msg != NULL) { HASH_FIND (hh, msg->headers, name, slen, hdr); if (hdr) { HASH_DEL (msg->headers, hdr); res = TRUE; DL_FOREACH_SAFE (hdr, hcur, hcurtmp) { rspamd_fstring_free (hcur->combined); g_free (hcur); } } } return res; } /* * HTTP router functions */ static void rspamd_http_entry_free (struct rspamd_http_connection_entry *entry) { if (entry != NULL) { close (entry->conn->fd); rspamd_http_connection_unref (entry->conn); if (entry->rt->finish_handler) { entry->rt->finish_handler (entry); } DL_DELETE (entry->rt->conns, entry); g_free (entry); } } static void rspamd_http_router_error_handler (struct rspamd_http_connection *conn, GError *err) { struct rspamd_http_connection_entry *entry = conn->ud; struct rspamd_http_message *msg; if (entry->is_reply) { /* At this point we need to finish this session and close owned socket */ if (entry->rt->error_handler != NULL) { entry->rt->error_handler (entry, err); } rspamd_http_entry_free (entry); } else { /* Here we can write a reply to a client */ if (entry->rt->error_handler != NULL) { entry->rt->error_handler (entry, err); } msg = rspamd_http_new_message (HTTP_RESPONSE); msg->date = time (NULL); msg->code = err->code; rspamd_http_message_set_body (msg, err->message, strlen (err->message)); rspamd_http_connection_reset (entry->conn); rspamd_http_connection_write_message (entry->conn, msg, NULL, "text/plain", entry, entry->conn->fd, entry->rt->ptv, entry->rt->ev_base); entry->is_reply = TRUE; } } static const gchar * rspamd_http_router_detect_ct (const gchar *path) { const gchar *dot; guint i; dot = strrchr (path, '.'); if (dot == NULL) { return http_file_types[HTTP_MAGIC_PLAIN].ct; } dot++; for (i = 0; i < G_N_ELEMENTS (http_file_types); i++) { if (strcmp (http_file_types[i].ext, dot) == 0) { return http_file_types[i].ct; } } return http_file_types[HTTP_MAGIC_PLAIN].ct; } static gboolean rspamd_http_router_is_subdir (const gchar *parent, const gchar *sub) { if (parent == NULL || sub == NULL || *parent == '\0') { return FALSE; } while (*parent != '\0') { if (*sub != *parent) { return FALSE; } parent++; sub++; } parent--; if (*parent == G_DIR_SEPARATOR) { return TRUE; } return (*sub == G_DIR_SEPARATOR || *sub == '\0'); } static gboolean rspamd_http_router_try_file (struct rspamd_http_connection_entry *entry, rspamd_ftok_t *lookup, gboolean expand_path) { struct stat st; gint fd; gchar filebuf[PATH_MAX], realbuf[PATH_MAX], *dir; struct rspamd_http_message *reply_msg; rspamd_snprintf (filebuf, sizeof (filebuf), "%s%c%T", entry->rt->default_fs_path, G_DIR_SEPARATOR, lookup); if (realpath (filebuf, realbuf) == NULL || lstat (realbuf, &st) == -1) { return FALSE; } if (S_ISDIR (st.st_mode) && expand_path) { /* Try to append 'index.html' to the url */ rspamd_fstring_t *nlookup; rspamd_ftok_t tok; gboolean ret; nlookup = rspamd_fstring_sized_new (lookup->len + sizeof ("index.html")); rspamd_printf_fstring (&nlookup, "%T%c%s", lookup, G_DIR_SEPARATOR, "index.html"); tok.begin = nlookup->str; tok.len = nlookup->len; ret = rspamd_http_router_try_file (entry, &tok, FALSE); rspamd_fstring_free (nlookup); return ret; } else if (!S_ISREG (st.st_mode)) { return FALSE; } /* We also need to ensure that file is inside the defined dir */ rspamd_strlcpy (filebuf, realbuf, sizeof (filebuf)); dir = dirname (filebuf); if (dir == NULL || !rspamd_http_router_is_subdir (entry->rt->default_fs_path, dir)) { return FALSE; } fd = open (realbuf, O_RDONLY); if (fd == -1) { return FALSE; } reply_msg = rspamd_http_new_message (HTTP_RESPONSE); reply_msg->date = time (NULL); reply_msg->code = 200; rspamd_http_router_insert_headers (entry->rt, reply_msg); if (!rspamd_http_message_set_body_from_fd (reply_msg, fd)) { close (fd); return FALSE; } close (fd); rspamd_http_connection_reset (entry->conn); msg_debug ("requested file %s", realbuf); rspamd_http_connection_write_message (entry->conn, reply_msg, NULL, rspamd_http_router_detect_ct (realbuf), entry, entry->conn->fd, entry->rt->ptv, entry->rt->ev_base); return TRUE; } static void rspamd_http_router_send_error (GError *err, struct rspamd_http_connection_entry *entry) { struct rspamd_http_message *err_msg; err_msg = rspamd_http_new_message (HTTP_RESPONSE); err_msg->date = time (NULL); err_msg->code = err->code; rspamd_http_message_set_body (err_msg, err->message, strlen (err->message)); entry->is_reply = TRUE; err_msg->status = rspamd_fstring_new_init (err->message, strlen (err->message)); rspamd_http_router_insert_headers (entry->rt, err_msg); rspamd_http_connection_reset (entry->conn); rspamd_http_connection_write_message (entry->conn, err_msg, NULL, "text/plain", entry, entry->conn->fd, entry->rt->ptv, entry->rt->ev_base); } static int rspamd_http_router_finish_handler (struct rspamd_http_connection *conn, struct rspamd_http_message *msg) { struct rspamd_http_connection_entry *entry = conn->ud; rspamd_http_router_handler_t handler = NULL; gpointer found; GError *err; rspamd_ftok_t lookup; const rspamd_ftok_t *encoding; struct http_parser_url u; guint i; rspamd_regexp_t *re; struct rspamd_http_connection_router *router; G_STATIC_ASSERT (sizeof (rspamd_http_router_handler_t) == sizeof (gpointer)); memset (&lookup, 0, sizeof (lookup)); router = entry->rt; if (entry->is_reply) { /* Request is finished, it is safe to free a connection */ rspamd_http_entry_free (entry); } else { if (G_UNLIKELY (msg->method != HTTP_GET && msg->method != HTTP_POST)) { if (router->unknown_method_handler) { return router->unknown_method_handler (entry, msg); } else { err = g_error_new (HTTP_ERROR, 500, "Invalid method"); if (entry->rt->error_handler != NULL) { entry->rt->error_handler (entry, err); } rspamd_http_router_send_error (err, entry); g_error_free (err); return 0; } } /* Search for path */ if (msg->url != NULL && msg->url->len != 0) { http_parser_parse_url (msg->url->str, msg->url->len, TRUE, &u); if (u.field_set & (1 << UF_PATH)) { guint unnorm_len; lookup.begin = msg->url->str + u.field_data[UF_PATH].off; lookup.len = u.field_data[UF_PATH].len; rspamd_http_normalize_path_inplace ((gchar *)lookup.begin, lookup.len, &unnorm_len); lookup.len = unnorm_len; } else { lookup.begin = msg->url->str; lookup.len = msg->url->len; } found = g_hash_table_lookup (entry->rt->paths, &lookup); memcpy (&handler, &found, sizeof (found)); msg_debug ("requested known path: %T", &lookup); } else { err = g_error_new (HTTP_ERROR, 404, "Empty path requested"); if (entry->rt->error_handler != NULL) { entry->rt->error_handler (entry, err); } rspamd_http_router_send_error (err, entry); g_error_free (err); return 0; } entry->is_reply = TRUE; encoding = rspamd_http_message_find_header (msg, "Accept-Encoding"); if (encoding && rspamd_substring_search (encoding->begin, encoding->len, "gzip", 4) != -1) { entry->support_gzip = TRUE; } if (handler != NULL) { return handler (entry, msg); } else { /* Try regexps */ for (i = 0; i < router->regexps->len; i ++) { re = g_ptr_array_index (router->regexps, i); if (rspamd_regexp_match (re, lookup.begin, lookup.len, TRUE)) { found = rspamd_regexp_get_ud (re); memcpy (&handler, &found, sizeof (found)); return handler (entry, msg); } } /* Now try plain file */ if (entry->rt->default_fs_path == NULL || lookup.len == 0 || !rspamd_http_router_try_file (entry, &lookup, TRUE)) { err = g_error_new (HTTP_ERROR, 404, "Not found"); if (entry->rt->error_handler != NULL) { entry->rt->error_handler (entry, err); } msg_info ("path: %T not found", &lookup); rspamd_http_router_send_error (err, entry); g_error_free (err); } } } return 0; } struct rspamd_http_connection_router * rspamd_http_router_new (rspamd_http_router_error_handler_t eh, rspamd_http_router_finish_handler_t fh, struct timeval *timeout, struct event_base *base, const char *default_fs_path, struct rspamd_keypair_cache *cache) { struct rspamd_http_connection_router * new; struct stat st; new = g_malloc0 (sizeof (struct rspamd_http_connection_router)); new->paths = g_hash_table_new_full (rspamd_ftok_icase_hash, rspamd_ftok_icase_equal, rspamd_fstring_mapped_ftok_free, NULL); new->regexps = g_ptr_array_new (); new->conns = NULL; new->error_handler = eh; new->finish_handler = fh; new->ev_base = base; new->response_headers = g_hash_table_new_full (rspamd_strcase_hash, rspamd_strcase_equal, g_free, g_free); if (timeout) { new->tv = *timeout; new->ptv = &new->tv; } else { new->ptv = NULL; } new->default_fs_path = NULL; if (default_fs_path != NULL) { if (stat (default_fs_path, &st) == -1) { msg_err ("cannot stat %s", default_fs_path); } else { if (!S_ISDIR (st.st_mode)) { msg_err ("path %s is not a directory", default_fs_path); } else { new->default_fs_path = realpath (default_fs_path, NULL); } } } new->cache = cache; return new; } void rspamd_http_router_set_key (struct rspamd_http_connection_router *router, struct rspamd_cryptobox_keypair *key) { g_assert (key != NULL); router->key = rspamd_keypair_ref (key); } void rspamd_http_router_add_path (struct rspamd_http_connection_router *router, const gchar *path, rspamd_http_router_handler_t handler) { gpointer ptr; rspamd_ftok_t *key; rspamd_fstring_t *storage; G_STATIC_ASSERT (sizeof (rspamd_http_router_handler_t) == sizeof (gpointer)); if (path != NULL && handler != NULL && router != NULL) { memcpy (&ptr, &handler, sizeof (ptr)); storage = rspamd_fstring_new_init (path, strlen (path)); key = g_malloc0 (sizeof (*key)); key->begin = storage->str; key->len = storage->len; g_hash_table_insert (router->paths, key, ptr); } } void rspamd_http_router_set_unknown_handler (struct rspamd_http_connection_router *router, rspamd_http_router_handler_t handler) { if (router != NULL) { router->unknown_method_handler = handler; } } void rspamd_http_router_add_header (struct rspamd_http_connection_router *router, const gchar *name, const gchar *value) { if (name != NULL && value != NULL && router != NULL) { g_hash_table_replace (router->response_headers, g_strdup (name), g_strdup (value)); } } void rspamd_http_router_insert_headers (struct rspamd_http_connection_router *router, struct rspamd_http_message *msg) { GHashTableIter it; gpointer k, v; if (router && msg) { g_hash_table_iter_init (&it, router->response_headers); while (g_hash_table_iter_next (&it, &k, &v)) { rspamd_http_message_add_header (msg, k, v); } } } void rspamd_http_router_add_regexp (struct rspamd_http_connection_router *router, struct rspamd_regexp_s *re, rspamd_http_router_handler_t handler) { gpointer ptr; G_STATIC_ASSERT (sizeof (rspamd_http_router_handler_t) == sizeof (gpointer)); if (re != NULL && handler != NULL && router != NULL) { memcpy (&ptr, &handler, sizeof (ptr)); rspamd_regexp_set_ud (re, ptr); g_ptr_array_add (router->regexps, rspamd_regexp_ref (re)); } } void rspamd_http_router_handle_socket (struct rspamd_http_connection_router *router, gint fd, gpointer ud) { struct rspamd_http_connection_entry *conn; conn = g_malloc0 (sizeof (struct rspamd_http_connection_entry)); conn->rt = router; conn->ud = ud; conn->is_reply = FALSE; conn->conn = rspamd_http_connection_new (NULL, rspamd_http_router_error_handler, rspamd_http_router_finish_handler, 0, RSPAMD_HTTP_SERVER, router->cache, NULL); if (router->key) { rspamd_http_connection_set_key (conn->conn, router->key); } rspamd_http_connection_read_message (conn->conn, conn, fd, router->ptv, router->ev_base); DL_PREPEND (router->conns, conn); } void rspamd_http_router_free (struct rspamd_http_connection_router *router) { struct rspamd_http_connection_entry *conn, *tmp; rspamd_regexp_t *re; guint i; if (router) { DL_FOREACH_SAFE (router->conns, conn, tmp) { rspamd_http_entry_free (conn); } if (router->key) { rspamd_keypair_unref (router->key); } if (router->cache) { rspamd_keypair_cache_destroy (router->cache); } if (router->default_fs_path != NULL) { g_free (router->default_fs_path); } for (i = 0; i < router->regexps->len; i ++) { re = g_ptr_array_index (router->regexps, i); rspamd_regexp_unref (re); } g_ptr_array_free (router->regexps, TRUE); g_hash_table_unref (router->paths); g_hash_table_unref (router->response_headers); g_free (router); } } 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); } 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; } glong rspamd_http_date_format (gchar *buf, gsize len, time_t time) { struct tm tms; rspamd_gmtime (time, &tms); return rspamd_snprintf (buf, len, "%s, %02d %s %4d %02d:%02d:%02d GMT", http_week[tms.tm_wday], tms.tm_mday, http_month[tms.tm_mon], tms.tm_year + 1900, tms.tm_hour, tms.tm_min, tms.tm_sec); } 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_normalize_path_inplace (gchar *path, guint len, guint *nlen) { const gchar *p, *end, *slash = NULL, *dot = NULL; gchar *o; enum { st_normal = 0, st_got_dot, st_got_dot_dot, st_got_slash, st_got_slash_slash, } state = st_normal; p = path; end = path + len; o = path; while (p < end) { switch (state) { case st_normal: if (G_UNLIKELY (*p == '/')) { state = st_got_slash; slash = p; } else if (G_UNLIKELY (*p == '.')) { state = st_got_dot; dot = p; } else { *o++ = *p; } p ++; break; case st_got_slash: if (G_UNLIKELY (*p == '/')) { /* Ignore double slash */ *o++ = *p; state = st_got_slash_slash; } else if (G_UNLIKELY (*p == '.')) { dot = p; state = st_got_dot; } else { *o++ = '/'; *o++ = *p; slash = NULL; dot = NULL; state = st_normal; } p ++; break; case st_got_slash_slash: if (G_LIKELY (*p != '/')) { slash = p - 1; dot = NULL; state = st_normal; continue; } p ++; break; case st_got_dot: if (G_UNLIKELY (*p == '/')) { /* Remove any /./ or ./ paths */ if (((o > path && *(o - 1) != '/') || (o == path)) && slash) { /* Preserve one slash */ *o++ = '/'; } slash = p; dot = NULL; /* Ignore last slash */ state = st_normal; } else if (*p == '.') { /* Double dot character */ state = st_got_dot_dot; } else { /* We have something like .some or /.some */ if (dot && p > dot) { if (slash == dot - 1 && (o > path && *(o - 1) != '/')) { /* /.blah */ memmove (o, slash, p - slash); o += p - slash; } else { memmove (o, dot, p - dot); o += p - dot; } } slash = NULL; dot = NULL; state = st_normal; continue; } p ++; break; case st_got_dot_dot: if (*p == '/') { /* We have something like /../ or ../ */ if (slash) { /* We need to remove the last component from o if it is there */ if (o > path + 2 && *(o - 1) == '/') { slash = rspamd_memrchr (path, '/', o - path - 2); } else if (o > path + 1) { slash = rspamd_memrchr (path, '/', o - path - 1); } else { slash = NULL; } if (slash) { o = (gchar *)slash; } /* Otherwise we keep these dots */ slash = p; state = st_got_slash; } else { /* We have something like bla../, so we need to copy it as is */ if (o > path && dot && p > dot) { memmove (o, dot, p - dot); o += p - dot; } slash = NULL; dot = NULL; state = st_normal; continue; } } else { /* We have something like ..bla or ... */ if (slash) { *o ++ = '/'; } if (dot && p > dot) { memmove (o, dot, p - dot); o += p - dot; } slash = NULL; dot = NULL; state = st_normal; continue; } p ++; break; } } /* Leftover */ switch (state) { case st_got_dot_dot: /* Trailing .. */ if (slash) { /* We need to remove the last component from o if it is there */ if (o > path + 2 && *(o - 1) == '/') { slash = rspamd_memrchr (path, '/', o - path - 2); } else if (o > path + 1) { slash = rspamd_memrchr (path, '/', o - path - 1); } else { if (o == path) { /* Corner case */ *o++ = '/'; } slash = NULL; } if (slash) { /* Remove last / */ o = (gchar *)slash; } } else { /* Corner case */ if (o == path) { *o++ = '/'; } else { if (dot && p > dot) { memmove (o, dot, p - dot); o += p - dot; } } } break; case st_got_slash: *o++ = '/'; break; default: if (o > path + 1 && *(o - 1) == '/') { o --; } break; } if (nlen) { *nlen = (o - path); } } 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; } }