#include "libstat/stat_api.h"
#include "rspamd.h"
#include "libserver/worker_util.h"
+#include "libserver/ssl_util.h"
#include "worker_private.h"
#include "lua/lua_common.h"
#include "cryptobox.h"
session->wrk = worker;
worker->nconns++;
- rspamd_http_router_handle_socket(ctx->http, nfd, session);
+ rspamd_http_router_handle_socket_ssl(ctx->http, nfd, session,
+ rspamd_worker_is_ssl_socket(worker, w->fd));
}
static void
ctx,
G_STRUCT_OFFSET(struct rspamd_controller_worker_ctx, use_ssl),
0,
- "Unimplemented");
+ "Enable SSL for this worker");
rspamd_rcl_register_worker_option(cfg,
type,
ctx,
G_STRUCT_OFFSET(struct rspamd_controller_worker_ctx, ssl_cert),
0,
- "Unimplemented");
+ "Path to SSL certificate chain file");
rspamd_rcl_register_worker_option(cfg,
type,
ctx,
G_STRUCT_OFFSET(struct rspamd_controller_worker_ctx, ssl_key),
0,
- "Unimplemented");
+ "Path to SSL private key file");
rspamd_rcl_register_worker_option(cfg,
type,
"timeout",
rspamd_controller_finish_handler, ctx->timeout,
ctx->static_files_dir, ctx->http_ctx);
+ if (ctx->use_ssl && ctx->ssl_cert && ctx->ssl_key) {
+ gpointer server_ssl_ctx = rspamd_init_ssl_ctx_server(ctx->ssl_cert, ctx->ssl_key);
+
+ if (server_ssl_ctx) {
+ rspamd_ssl_ctx_config(ctx->cfg, server_ssl_ctx);
+ rspamd_http_router_set_ssl(ctx->http, server_ssl_ctx);
+ msg_info_ctx("enabled SSL for controller worker");
+ }
+ else {
+ msg_err_ctx("failed to create SSL context for controller worker");
+ }
+ }
+
/* Add callbacks for different methods */
rspamd_http_router_add_path(ctx->http,
PATH_AUTH,
char *name;
char *bind_line;
gboolean is_systemd;
+ gboolean is_ssl;
struct rspamd_worker_bind_conf *next;
};
auto bind_line = std::string_view{cnf->bind_line};
+ /* Check for trailing " ssl" suffix (case-insensitive) */
+ if (bind_line.size() > 4 &&
+ bind_line[bind_line.size() - 4] == ' ' &&
+ g_ascii_tolower(bind_line[bind_line.size() - 3]) == 's' &&
+ g_ascii_tolower(bind_line[bind_line.size() - 2]) == 's' &&
+ g_ascii_tolower(bind_line[bind_line.size() - 1]) == 'l') {
+ cnf->is_ssl = TRUE;
+ /* Strip the suffix for address parsing */
+ cnf->bind_line[bind_line.size() - 4] = '\0';
+ bind_line = std::string_view{cnf->bind_line};
+ }
+
if (bind_line.starts_with("systemd:")) {
/* The actual socket will be passed by systemd environment */
fdname = str + sizeof("systemd:") - 1;
}
}
}
+else if (priv->ssl) {
+ /* Server-side SSL: connection already established, restore handlers */
+ rspamd_ssl_connection_restore_handlers(priv->ssl,
+ rspamd_http_event_handler,
+ rspamd_http_ssl_err_handler,
+ conn,
+ EV_WRITE);
+}
else {
/* Watch for READ too on client to detect early server responses */
short ev_flags = (conn->type == RSPAMD_HTTP_CLIENT) ? (EV_WRITE | EV_READ) : EV_WRITE;
struct rspamd_http_connection_private *priv = conn->priv;
return (priv->ka_idle_override > 0 ? priv->ka_idle_override : default_idle);
}
+
+static void
+rspamd_http_ssl_accept_handler(int fd, short what, gpointer ud)
+{
+ struct rspamd_http_connection *conn = (struct rspamd_http_connection *) ud;
+ struct rspamd_http_connection_private *priv = conn->priv;
+
+ /* SSL handshake complete, start reading HTTP message */
+ rspamd_http_connection_read_message_common(conn, conn->ud, priv->timeout, 0);
+}
+
+static void
+rspamd_http_ssl_accept_shared_handler(int fd, short what, gpointer ud)
+{
+ struct rspamd_http_connection *conn = (struct rspamd_http_connection *) ud;
+ struct rspamd_http_connection_private *priv = conn->priv;
+
+ /* SSL handshake complete, start reading HTTP message with shared body */
+ rspamd_http_connection_read_message_common(conn, conn->ud, priv->timeout,
+ RSPAMD_HTTP_FLAG_SHMEM);
+}
+
+static gboolean
+rspamd_http_connection_accept_ssl_common(struct rspamd_http_connection *conn,
+ gpointer ssl_ctx,
+ gpointer ud,
+ ev_tstamp timeout,
+ rspamd_ssl_handler_t accept_handler)
+{
+ struct rspamd_http_connection_private *priv = conn->priv;
+ GError *err;
+
+ g_assert(conn != NULL);
+ g_assert(ssl_ctx != NULL);
+
+ conn->ud = ud;
+ priv->timeout = timeout;
+
+ priv->ssl = rspamd_ssl_connection_new(ssl_ctx, priv->ctx->event_loop,
+ FALSE, conn->log_tag);
+
+ if (priv->ssl == NULL) {
+ err = g_error_new(HTTP_ERROR, 400, "cannot create SSL connection");
+ rspamd_http_connection_ref(conn);
+ conn->error_handler(conn, err);
+ rspamd_http_connection_unref(conn);
+ g_error_free(err);
+ return FALSE;
+ }
+
+ if (!rspamd_ssl_accept_fd(priv->ssl, conn->fd, &priv->ev,
+ timeout, accept_handler,
+ rspamd_http_ssl_err_handler, conn)) {
+
+ err = g_error_new(HTTP_ERROR, 400,
+ "ssl accept 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;
+ }
+
+ return TRUE;
+}
+
+gboolean
+rspamd_http_connection_accept_ssl(struct rspamd_http_connection *conn,
+ gpointer ssl_ctx,
+ gpointer ud,
+ ev_tstamp timeout)
+{
+ return rspamd_http_connection_accept_ssl_common(conn, ssl_ctx, ud, timeout,
+ rspamd_http_ssl_accept_handler);
+}
+
+gboolean
+rspamd_http_connection_accept_ssl_shared(struct rspamd_http_connection *conn,
+ gpointer ssl_ctx,
+ gpointer ud,
+ ev_tstamp timeout)
+{
+ return rspamd_http_connection_accept_ssl_common(conn, ssl_ctx, ud, timeout,
+ rspamd_http_ssl_accept_shared_handler);
+}
void rspamd_http_connection_disable_encryption(struct rspamd_http_connection *conn);
+/**
+ * Accept SSL on an existing server connection. Performs the SSL handshake
+ * asynchronously and then starts HTTP message reading.
+ * @param conn server connection (already created via rspamd_http_connection_new_server)
+ * @param ssl_ctx server SSL context (from rspamd_init_ssl_ctx_server)
+ * @param ud opaque user data
+ * @param timeout handshake/IO timeout
+ * @return TRUE if handshake was initiated
+ */
+gboolean rspamd_http_connection_accept_ssl(struct rspamd_http_connection *conn,
+ gpointer ssl_ctx,
+ gpointer ud,
+ ev_tstamp timeout);
+
+/**
+ * Accept SSL on an existing server connection with shared memory body.
+ * Same as rspamd_http_connection_accept_ssl but starts reading in shared mode.
+ */
+gboolean rspamd_http_connection_accept_ssl_shared(struct rspamd_http_connection *conn,
+ gpointer ssl_ctx,
+ gpointer ud,
+ ev_tstamp timeout);
+
#ifdef __cplusplus
}
#endif
#include "http_connection.h"
#include "http_private.h"
#include "libserver/http_content_negotiation.h"
+#include "libserver/ssl_util.h"
#include "libutil/regexp.h"
#include "libutil/printf.h"
#include "libserver/logger.h"
}
}
-void rspamd_http_router_handle_socket(struct rspamd_http_connection_router *router,
- int fd, gpointer ud)
+void rspamd_http_router_set_ssl(struct rspamd_http_connection_router *router,
+ gpointer ssl_ctx)
+{
+ g_assert(router != NULL);
+ router->server_ssl_ctx = ssl_ctx;
+}
+
+void rspamd_http_router_handle_socket_ssl(struct rspamd_http_connection_router *router,
+ int fd, gpointer ud, gboolean ssl)
{
struct rspamd_http_connection_entry *conn;
rspamd_http_connection_set_key(conn->conn, router->key);
}
- rspamd_http_connection_read_message(conn->conn, conn, router->timeout);
+ if (ssl && router->server_ssl_ctx) {
+ rspamd_http_connection_accept_ssl(conn->conn, router->server_ssl_ctx,
+ conn, router->timeout);
+ }
+ else {
+ rspamd_http_connection_read_message(conn->conn, conn, router->timeout);
+ }
+
DL_PREPEND(router->conns, conn);
}
+void rspamd_http_router_handle_socket(struct rspamd_http_connection_router *router,
+ int fd, gpointer ud)
+{
+ rspamd_http_router_handle_socket_ssl(router, fd, ud, FALSE);
+}
+
void rspamd_http_router_free(struct rspamd_http_connection_router *router)
{
struct rspamd_http_connection_entry *conn, *tmp;
rspamd_keypair_unref(router->key);
}
+ if (router->server_ssl_ctx) {
+ rspamd_ssl_ctx_free(router->server_ssl_ctx);
+ }
+
if (router->default_fs_path != NULL) {
g_free(router->default_fs_path);
}
struct rspamd_cryptobox_keypair *key;
rspamd_http_router_error_handler_t error_handler;
rspamd_http_router_finish_handler_t finish_handler;
+ gpointer server_ssl_ctx;
};
/**
void rspamd_http_router_add_regexp(struct rspamd_http_connection_router *router,
struct rspamd_regexp_s *re, rspamd_http_router_handler_t handler);
+/**
+ * Set server SSL context for the router
+ * @param router router object
+ * @param ssl_ctx server SSL context (from rspamd_init_ssl_ctx_server)
+ */
+void rspamd_http_router_set_ssl(struct rspamd_http_connection_router *router,
+ gpointer ssl_ctx);
+
/**
* Handle new accepted socket
* @param router router object
int fd,
gpointer ud);
+/**
+ * Handle new accepted socket with optional SSL
+ * @param router router object
+ * @param fd server socket
+ * @param ud opaque userdata
+ * @param ssl whether to use SSL on this socket
+ */
+void rspamd_http_router_handle_socket_ssl(
+ struct rspamd_http_connection_router *router,
+ int fd,
+ gpointer ud,
+ gboolean ssl);
+
/**
* Free router and all connections associated
* @param router
return ssl_ctx_noverify;
}
+
+gpointer
+rspamd_init_ssl_ctx_server(const char *cert_path, const char *key_path)
+{
+ struct rspamd_ssl_ctx *ctx;
+
+ g_assert(cert_path != NULL);
+ g_assert(key_path != NULL);
+
+ ctx = rspamd_init_ssl_ctx_common();
+
+ /* Server-side session cache */
+ SSL_CTX_set_session_cache_mode(ctx->s, SSL_SESS_CACHE_SERVER);
+ /* Remove client session callback set by common init */
+ SSL_CTX_sess_set_new_cb(ctx->s, NULL);
+
+ /* Load certificate chain */
+ if (SSL_CTX_use_certificate_chain_file(ctx->s, cert_path) != 1) {
+ msg_err("cannot load certificate chain from %s: %s",
+ cert_path, ERR_error_string(ERR_get_error(), NULL));
+ rspamd_ssl_ctx_free(ctx);
+ return NULL;
+ }
+
+ /* Load private key */
+ if (SSL_CTX_use_PrivateKey_file(ctx->s, key_path, SSL_FILETYPE_PEM) != 1) {
+ msg_err("cannot load private key from %s: %s",
+ key_path, ERR_error_string(ERR_get_error(), NULL));
+ rspamd_ssl_ctx_free(ctx);
+ return NULL;
+ }
+
+ /* Verify that the key matches the certificate */
+ if (SSL_CTX_check_private_key(ctx->s) != 1) {
+ msg_err("ssl private key does not match certificate: %s",
+ ERR_error_string(ERR_get_error(), NULL));
+ rspamd_ssl_ctx_free(ctx);
+ return NULL;
+ }
+
+ /* No client certificate verification */
+ SSL_CTX_set_verify(ctx->s, SSL_VERIFY_NONE, NULL);
+
+ return ctx;
+}
#if defined(RSPAMD_LEGACY_SSL_PROVIDER) && OPENSSL_VERSION_NUMBER >= 0x30000000L
#include <openssl/provider.h>
#endif
gpointer rspamd_init_ssl_ctx(void);
gpointer rspamd_init_ssl_ctx_noverify(void);
+gpointer rspamd_init_ssl_ctx_server(const char *cert_path, const char *key_path);
void rspamd_ssl_ctx_config(struct rspamd_config *cfg, gpointer ssl_ctx);
void rspamd_ssl_ctx_free(gpointer ssl_ctx);
void rspamd_openssl_maybe_init(struct rspamd_external_libs_ctx *ctx);
/* Must be finalized and freed by caller */
return output;
}
+
+gboolean
+rspamd_worker_is_ssl_socket(struct rspamd_worker *worker, int fd)
+{
+ GList *cur;
+ struct rspamd_worker_listen_socket *ls;
+
+ if (worker == NULL || worker->cf == NULL) {
+ return FALSE;
+ }
+
+ cur = worker->cf->listen_socks;
+
+ while (cur) {
+ ls = (struct rspamd_worker_listen_socket *) cur->data;
+
+ if (ls->fd == fd) {
+ return ls->is_ssl;
+ }
+
+ cur = g_list_next(cur);
+ }
+
+ return FALSE;
+}
*/
rspamd_fstring_t *rspamd_metrics_to_prometheus_string(const ucl_object_t *top);
+/**
+ * Check if the given listen fd is an SSL socket for this worker
+ * @param worker
+ * @param fd listen fd from accept event
+ * @return TRUE if the socket is SSL
+ */
+gboolean rspamd_worker_is_ssl_socket(struct rspamd_worker *worker, int fd);
+
#ifdef WITH_HYPERSCAN
struct rspamd_control_command;
strerror(errno));
}
else {
+ /* Propagate SSL flag to listen sockets */
+ if (bcf->is_ssl) {
+ GList *cur;
+
+ for (cur = ls; cur != NULL; cur = g_list_next(cur)) {
+ struct rspamd_worker_listen_socket *cur_ls =
+ (struct rspamd_worker_listen_socket *) cur->data;
+ cur_ls->is_ssl = true;
+ }
+ }
g_hash_table_insert(listen_sockets, (gpointer) key, ls);
listen_ok = TRUE;
}
int fd;
enum rspamd_worker_socket_type type;
bool is_systemd;
+ bool is_ssl;
};
typedef struct worker_s {
#include "libmime/message.h"
#include "rspamd.h"
#include "libserver/worker_util.h"
+#include "libserver/ssl_util.h"
#include "worker_private.h"
#include "lua/lua_common.h"
#include "keypairs_cache.h"
/* Default log tag type for worker */
enum rspamd_proxy_log_tag_type log_tag_type;
struct rspamd_main *srv;
+ /* Whether we use ssl for this server */
+ gboolean use_ssl;
+ /* SSL cert */
+ char *ssl_cert;
+ /* SSL private key */
+ char *ssl_key;
+ /* Server SSL context */
+ gpointer server_ssl_ctx;
};
enum rspamd_backend_flags {
G_STRUCT_OFFSET(struct rspamd_proxy_ctx, encrypted_only),
0,
"Allow only encrypted connections");
+ rspamd_rcl_register_worker_option(cfg,
+ type,
+ "ssl",
+ rspamd_rcl_parse_struct_boolean,
+ ctx,
+ G_STRUCT_OFFSET(struct rspamd_proxy_ctx, use_ssl),
+ 0,
+ "Enable SSL for this worker");
+ rspamd_rcl_register_worker_option(cfg,
+ type,
+ "ssl_cert",
+ rspamd_rcl_parse_struct_string,
+ ctx,
+ G_STRUCT_OFFSET(struct rspamd_proxy_ctx, ssl_cert),
+ 0,
+ "Path to SSL certificate chain file");
+ rspamd_rcl_register_worker_option(cfg,
+ type,
+ "ssl_key",
+ rspamd_rcl_parse_struct_string,
+ ctx,
+ G_STRUCT_OFFSET(struct rspamd_proxy_ctx, ssl_key),
+ 0,
+ "Path to SSL private key file");
rspamd_rcl_register_worker_option(cfg,
type,
"upstream",
rspamd_inet_address_to_string(addr),
rspamd_inet_address_get_port(addr));
- rspamd_http_connection_read_message_shared(session->client_conn,
- session,
- session->ctx->timeout);
+ if (ctx->server_ssl_ctx && rspamd_worker_is_ssl_socket(worker, w->fd)) {
+ rspamd_http_connection_accept_ssl_shared(session->client_conn,
+ ctx->server_ssl_ctx,
+ session,
+ session->ctx->timeout);
+ }
+ else {
+ rspamd_http_connection_read_message_shared(session->client_conn,
+ session,
+ session->ctx->timeout);
+ }
}
else {
msg_info_session("accepted milter connection from %s port %d",
(rspamd_mempool_destruct_t) rspamd_http_context_free,
ctx->http_ctx);
+ if (ctx->use_ssl && ctx->ssl_cert && ctx->ssl_key) {
+ ctx->server_ssl_ctx = rspamd_init_ssl_ctx_server(ctx->ssl_cert, ctx->ssl_key);
+
+ if (ctx->server_ssl_ctx) {
+ rspamd_ssl_ctx_config(ctx->cfg, ctx->server_ssl_ctx);
+ msg_info("enabled SSL for proxy worker");
+ }
+ else {
+ msg_err("failed to create SSL context for proxy worker");
+ }
+ }
+
if (ctx->has_self_scan) {
/* Additional initialisation needed */
rspamd_worker_init_scanner(worker, ctx->event_loop, ctx->resolver,
#include "libstat/stat_api.h"
#include "libserver/worker_util.h"
#include "libserver/rspamd_control.h"
+#include "libserver/ssl_util.h"
#include "worker_private.h"
#include "libserver/http/http_private.h"
#include "libserver/cfg_file_private.h"
rspamd_http_connection_set_key(session->http_conn, ctx->key);
}
- rspamd_http_connection_read_message(session->http_conn,
- session,
- ctx->timeout);
+ if (ctx->server_ssl_ctx && rspamd_worker_is_ssl_socket(worker, w->fd)) {
+ rspamd_http_connection_accept_ssl(session->http_conn,
+ ctx->server_ssl_ctx,
+ session,
+ ctx->timeout);
+ }
+ else {
+ rspamd_http_connection_read_message(session->http_conn,
+ session,
+ ctx->timeout);
+ }
}
gpointer
0,
"Encryption keypair");
+ rspamd_rcl_register_worker_option(cfg,
+ type,
+ "ssl",
+ rspamd_rcl_parse_struct_boolean,
+ ctx,
+ G_STRUCT_OFFSET(struct rspamd_worker_ctx, use_ssl),
+ 0,
+ "Enable SSL for this worker");
+
+ rspamd_rcl_register_worker_option(cfg,
+ type,
+ "ssl_cert",
+ rspamd_rcl_parse_struct_string,
+ ctx,
+ G_STRUCT_OFFSET(struct rspamd_worker_ctx, ssl_cert),
+ 0,
+ "Path to SSL certificate chain file");
+
+ rspamd_rcl_register_worker_option(cfg,
+ type,
+ "ssl_key",
+ rspamd_rcl_parse_struct_string,
+ ctx,
+ G_STRUCT_OFFSET(struct rspamd_worker_ctx, ssl_key),
+ 0,
+ "Path to SSL private key file");
+
return ctx;
}
rspamd_mempool_add_destructor(ctx->cfg->cfg_pool,
(rspamd_mempool_destruct_t) rspamd_http_context_free,
ctx->http_ctx);
+
+ if (ctx->use_ssl && ctx->ssl_cert && ctx->ssl_key) {
+ ctx->server_ssl_ctx = rspamd_init_ssl_ctx_server(ctx->ssl_cert, ctx->ssl_key);
+
+ if (ctx->server_ssl_ctx) {
+ rspamd_ssl_ctx_config(ctx->cfg, ctx->server_ssl_ctx);
+ msg_info_ctx("enabled SSL for normal worker");
+ }
+ else {
+ msg_err_ctx("failed to create SSL context for normal worker");
+ }
+ }
+
rspamd_worker_init_scanner(worker, ctx->event_loop, ctx->resolver,
&ctx->lang_det);
gboolean is_mime;
/* Allow encrypted requests only using network */
gboolean encrypted_only;
+ /* Whether we use ssl for this server */
+ gboolean use_ssl;
/* Limit of tasks */
uint32_t max_tasks;
/* Maximum time for task processing */
struct rspamd_http_context *http_ctx;
/* Language detector */
struct rspamd_lang_detector *lang_det;
+ /* SSL cert */
+ char *ssl_cert;
+ /* SSL private key */
+ char *ssl_key;
+ /* Server SSL context */
+ gpointer server_ssl_ctx;
};
/*