Skip to content

Commit

Permalink
Merge branch 'proxy'
Browse files Browse the repository at this point in the history
  • Loading branch information
jengelh committed May 21, 2024
2 parents 491a5f4 + fae8b46 commit f3c43f6
Show file tree
Hide file tree
Showing 17 changed files with 243 additions and 197 deletions.
10 changes: 10 additions & 0 deletions doc/delivery-queue.8gx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,16 @@ Request that the file descriptor table be at least this large. The magic value
should be used.
.br
Default: \fI0\fP
.TP
\fBlda_support_haproxy\fP
This flag controls the acceptance of the HAProxy "PROXY" command at the start
of a connection. When a (reverse) proxy is placed in front of
gromox\-delivery\-queue, the address that the LDA normally sees is the proxy
address (e.g. ::1). A proxy can use this protocol extension to convey the
actual client address, and the LDA can pick this up for its own reporting,
which in turn is useful for e.g. fail2ban setups.
.br
Default: \fIno\fP
.SH Configuration directives (smtp.cfg)
The following directives are recognized when reading from /etc/gromox/smtp.cfg,
or when the \fB\-c\fP option is used to specify a custom file:
Expand Down
10 changes: 10 additions & 0 deletions doc/imap.8gx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,16 @@ Request that the file descriptor table be at least this large. The magic value
should be used.
.br
Default: \fI0\fP
.TP
\fBimap_support_haproxy\fP
This flag controls the acceptance of the HAProxy "PROXY" command at the start
of a connection. When a (reverse) proxy is placed in front of gromox\-imap, the
address that gximap normally sees is the proxy address (e.g. ::1). A proxy can
use this protocol extension to convey the actual client address, and gximap can
pick this up for its own reporting, which in turn is useful for e.g. fail2ban
setups.
.br
Default: \fIno\fP
.SH Configuration directives (imap.cfg)
The following directives are recognized when reading from /etc/gromox/imap.cfg,
or when the \fB\-c\fP option is used to specify a custom file:
Expand Down
10 changes: 10 additions & 0 deletions doc/pop3.8gx
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,16 @@ Request that the file descriptor table be at least this large. The magic value
should be used.
.br
Default: \fI0\fP
.TP
\fBpop3_support_haproxy\fP
This flag controls the acceptance of the HAProxy "PROXY" command at the start
of a connection. When a (reverse) proxy is placed in front of gromox\-pop3, the
address that gxpop3 normally sees is the proxy address (e.g. ::1). A proxy can
use this protocol extension to convey the actual client address, and gxpop3 can
pick this up for its own reporting, which in turn is useful for e.g. fail2ban
setups.
.br
Default: \fIno\fP
.SH Configuration directives (pop3.cfg)
The following directives are recognized when reading from /etc/gromox/pop3.cfg,
or when the \fB\-c\fP option is used to specify a custom file:
Expand Down
10 changes: 4 additions & 6 deletions mda/smtp/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,8 @@ static std::vector<static_module> g_dfl_svc_plugins = {
static constexpr cfg_directive gromox_cfg_defaults[] = {
{"daemons_fd_limit", "lda_fd_limit", CFG_ALIAS},
{"lda_fd_limit", "0", CFG_SIZE},
{"lda_recipient_delimiter", ""},
{"lda_support_haproxy", "0", CFG_BOOL},
CFG_TABLE_END,
};

Expand Down Expand Up @@ -109,16 +111,11 @@ static constexpr cfg_directive smtp_cfg_defaults[] = {

static void term_handler(int signo);

static constexpr const cfg_directive dq_gxcfg_dflt[] = {
{"lda_recipient_delimiter", ""},
CFG_TABLE_END,
};

static bool dq_reload_config(std::shared_ptr<CONFIG_FILE> gxcfg = nullptr,
std::shared_ptr<CONFIG_FILE> pconfig = nullptr)
{
if (gxcfg == nullptr)
gxcfg = config_file_prg(opt_config_file, "gromox.cfg", dq_gxcfg_dflt);
gxcfg = config_file_prg(opt_config_file, "gromox.cfg", gromox_cfg_defaults);
if (opt_config_file != nullptr && gxcfg == nullptr) {
mlog(LV_ERR, "config_file_init %s: %s", opt_config_file, strerror(errno));
return false;
Expand Down Expand Up @@ -498,6 +495,7 @@ int main(int argc, const char **argv)
}
auto cleanup_8 = make_scope_exit(system_services_stop);

scfg.support_haproxy = gxconfig->get_ll("lda_support_haproxy");
smtp_parser_init(scfg);
if (0 != smtp_parser_run()) {
mlog(LV_ERR, "system: failed to start SMTP parser");
Expand Down
21 changes: 21 additions & 0 deletions mda/smtp/smtp_cmd_handler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,27 @@ int smtp_cmd_handler_starttls(const char *cmd_line, int line_length,
return 210;
}

int smtp_cmd_handler_proxy(const char *cmd_line, int len, smtp_context *ctx) try
{
auto argv = gx_split(std::string_view(cmd_line, len), ' ');
if (argv.size() < 2)
return 500;
if (strcmp(argv[1].c_str(), "TCP6") == 0 || strcmp(argv[1].c_str(), "TCP4") == 0) {
if (argv.size() < 6)
return 500;
auto &cn = ctx->connection;
gx_strlcpy(cn.client_ip, argv[3].c_str(), std::size(cn.client_ip));
cn.client_port = strtoul(argv[5].c_str(), nullptr, 0);
} else if (strcmp(argv[1].c_str(), "UNKNOWN") == 0) {
} else {
return 500;
}
return 0;
} catch (const std::bad_alloc &) {
mlog(LV_ERR, "E-1249: ENOMEM");
return 416;
}

int smtp_cmd_handler_auth(const char* cmd_line, int line_length,
SMTP_CONTEXT *pcontext)
{
Expand Down
1 change: 1 addition & 0 deletions mda/smtp/smtp_cmd_handler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ int smtp_cmd_handler_ehlo(const char* cmd_line, int line_length,
SMTP_CONTEXT *pcontext);
int smtp_cmd_handler_starttls(const char *cmd_line, int line_length,
SMTP_CONTEXT *pcontext);
extern int smtp_cmd_handler_proxy(const char *cmd, int len, smtp_context *);
int smtp_cmd_handler_auth(const char* cmd_line, int line_length,
SMTP_CONTEXT *pcontext);
int smtp_cmd_handler_mail(const char* cmd_line, int line_length,
Expand Down
56 changes: 31 additions & 25 deletions mda/smtp/smtp_parser.cpp
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// SPDX-License-Identifier: GPL-2.0-only WITH linking exception
// SPDX-FileCopyrightText: 2021–2024 grommunio GmbH
// This file is part of Gromox.
/* smtp parser is a module, which first read data from socket, parses the smtp
* commands and then do the corresponding action.
*/
#include <algorithm>
#include <cerrno>
#include <cstdarg>
#include <cstdio>
Expand All @@ -13,6 +16,7 @@
#include <unistd.h>
#include <utility>
#include <vector>
#include <libHX/ctype_helper.h>
#include <libHX/io.h>
#include <libHX/string.h>
#include <openssl/err.h>
Expand Down Expand Up @@ -531,11 +535,32 @@ SCHEDULE_CONTEXT **smtp_parser_get_contexts_list()
static int smtp_parser_dispatch_cmd2(const char *cmd_line, int line_length,
SMTP_CONTEXT *pcontext)
{
static constexpr struct {
char cmd[9];
unsigned int len;
int (*func)(const char *, int, smtp_context *);
} proc[] = {
{"AUTH", 4, smtp_cmd_handler_auth},
{"DATA", 4, smtp_cmd_handler_data},
{"ETRN", 4, smtp_cmd_handler_etrn},
{"HELP", 4, smtp_cmd_handler_help},
{"MAIL", 4, smtp_cmd_handler_mail},
{"NOOP", 4, smtp_cmd_handler_noop},
{"QUIT", 4, smtp_cmd_handler_quit},
{"RCPT", 4, smtp_cmd_handler_rcpt},
{"RSET", 4, smtp_cmd_handler_rset},
{"STARTTLS", 8, smtp_cmd_handler_starttls},
{"VRFY", 4, smtp_cmd_handler_vrfy},
};
/* check the line length */
if (line_length > 1000) {
/* 500 syntax error - line too long */
return 502;
}
auto fc = !pcontext->past_first_command;
pcontext->past_first_command = true;
if (fc && strncasecmp(cmd_line, "PROXY", 5) == 0)
return smtp_cmd_handler_proxy(cmd_line, line_length, pcontext);
if (g_param.cmd_prot & HT_LMTP) {
if (strncasecmp(cmd_line, "LHLO", 4) == 0)
return smtp_cmd_handler_lhlo(cmd_line, line_length, pcontext);
Expand All @@ -546,31 +571,12 @@ static int smtp_parser_dispatch_cmd2(const char *cmd_line, int line_length,
if (strncasecmp(cmd_line, "EHLO", 4) == 0)
return smtp_cmd_handler_ehlo(cmd_line, line_length, pcontext);
}
if (strncasecmp(cmd_line, "STARTTLS", 8) == 0) {
return smtp_cmd_handler_starttls(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "AUTH", 4)) {
return smtp_cmd_handler_auth(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "MAIL", 4)) {
return smtp_cmd_handler_mail(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "RCPT", 4)) {
return smtp_cmd_handler_rcpt(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "DATA", 4)) {
return smtp_cmd_handler_data(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "RSET", 4)) {
return smtp_cmd_handler_rset(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "NOOP", 4)) {
return smtp_cmd_handler_noop(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "HELP", 4)) {
return smtp_cmd_handler_help(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "VRFY", 4)) {
return smtp_cmd_handler_vrfy(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "QUIT", 4)) {
return smtp_cmd_handler_quit(cmd_line, line_length, pcontext);
} else if (0 == strncasecmp(cmd_line, "ETRN", 4)) {
return smtp_cmd_handler_etrn(cmd_line, line_length, pcontext);
} else {
return smtp_cmd_handler_else(cmd_line, line_length, pcontext);
}
auto scmp = [](decltype(*proc) &p, const char *line) { return strncasecmp(line, p.cmd, p.len) < 0; };
auto it = std::lower_bound(std::begin(proc), std::end(proc), cmd_line, scmp);
if (it != std::end(proc) && strncasecmp(cmd_line, it->cmd, it->len) == 0 &&
(cmd_line[it->len] == '\0' || HX_isspace(cmd_line[it->len])))
return it->func(cmd_line, line_length, pcontext);
return smtp_cmd_handler_else(cmd_line, line_length, pcontext);
}

static int smtp_parser_dispatch_cmd(const char *cmd, int len, SMTP_CONTEXT *ctx)
Expand Down
2 changes: 2 additions & 0 deletions mda/smtp/smtp_parser.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ struct smtp_context final : public schedule_context {
envelope_info menv; /* for recording the mail information */
FLUSH_INFO flusher{}; /* the flusher for saving mail information */
BOOL is_spam = false; /* whether the mail is spam */
bool past_first_command = false;
unsigned int session_num = 0; /* session number of the context */
size_t total_length = 0; /* mail total length */
char last_bytes[4]{}; /* last bytes for part mail */
Expand All @@ -81,6 +82,7 @@ struct smtp_param {
unsigned int context_num = 0;
BOOL support_pipeline = TRUE;
BOOL support_starttls = false, force_starttls = false;
bool support_haproxy = false;
size_t max_mail_length = 64ULL * 1024 * 1024;
size_t flushing_size = 0;
gromox::time_duration timeout{std::chrono::seconds{0x7fffffff}};
Expand Down
5 changes: 3 additions & 2 deletions mra/imap/imap.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -129,10 +129,10 @@ struct imap_context final : public schedule_context {
STREAM stream; /* stream for writing to imap client */
int auth_times = 0;
char username[UADDR_SIZE]{}, maildir[256]{}, lang[32]{}, defcharset[32]{};
bool synchronizing_literal = true;
bool synchronizing_literal = true, past_first_command = false;
};

extern void imap_parser_init(int context_num, int average_num, size_t cache_size, gromox::time_duration timeout, gromox::time_duration autologout_time, int max_auth_times, int block_auth_fail, bool support_tls, bool force_tls, const char *certificate_path, const char *cb_passwd, const char *key_path);
extern void imap_parser_init(int context_num, int average_num, size_t cache_size, gromox::time_duration timeout, gromox::time_duration autologout_time, int max_auth_times, int block_auth_fail, bool support_tls, bool force_tls, const char *certificate_path, const char *cb_passwd, const char *key_path, bool support_haproxy = false);
extern int imap_parser_run();
extern tproc_status imap_parser_process(schedule_context *);
extern void imap_parser_stop();
Expand All @@ -156,6 +156,7 @@ extern int imap_cmd_parser_id(int argc, char **argv, imap_context *);
extern int imap_cmd_parser_noop(int argc, char **argv, imap_context *);
extern int imap_cmd_parser_logout(int argc, char **argv, imap_context *);
extern int imap_cmd_parser_starttls(int argc, char **argv, imap_context *);
extern int imap_cmd_parser_proxy(int argc, char **argv, imap_context *);
extern int imap_cmd_parser_authenticate(int argc, char **argv, imap_context *);
extern int imap_cmd_parser_username(int argc, char **argv, imap_context *);
extern int imap_cmd_parser_password(int argc, char **argv, imap_context *);
Expand Down
19 changes: 19 additions & 0 deletions mra/imap/imap_cmd_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1344,6 +1344,25 @@ int imap_cmd_parser_starttls(int argc, char **argv, imap_context *pcontext)
return 1704;
}

int imap_cmd_parser_proxy(int argc, char **argv, imap_context *ctx)
{
if (argc < 2)
return DISPATCH_SHOULD_CLOSE;
if (strcmp(argv[1], "TCP6") == 0 || strcmp(argv[1], "TCP4") == 0) {
if (argc < 6)
return DISPATCH_SHOULD_CLOSE;
gx_strlcpy(ctx->connection.client_ip, argv[2], std::size(ctx->connection.client_ip));
char *e = nullptr;
ctx->connection.client_port = strtoul(argv[4], &e, 0);
if (e == nullptr || *e != '\0')
return DISPATCH_SHOULD_CLOSE;
} else if (strcmp(argv[1], "UNKNOWN") == 0) {
} else {
return DISPATCH_SHOULD_CLOSE;
}
return DISPATCH_CONTINUE;
}

int imap_cmd_parser_authenticate(int argc, char **argv, imap_context *pcontext)
{
if (g_support_tls && g_force_tls && pcontext->connection.ssl == nullptr)
Expand Down
11 changes: 9 additions & 2 deletions mra/imap/imap_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ static int imap_parser_wrdat_retrieve(imap_context *);
unsigned int g_imapcmd_debug;
int g_max_auth_times, g_block_auth_fail;
bool g_support_tls, g_force_tls;
static bool g_support_haproxy;
static std::atomic<int> g_sequence_id;
static int g_average_num;
static size_t g_context_num, g_cache_size;
Expand All @@ -85,7 +86,8 @@ static std::unique_ptr<std::mutex[]> g_ssl_mutex_buf;
void imap_parser_init(int context_num, int average_num, size_t cache_size,
time_duration timeout, time_duration autologout_time, int max_auth_times,
int block_auth_fail, bool support_tls, bool force_tls,
const char *certificate_path, const char *cb_passwd, const char *key_path)
const char *certificate_path, const char *cb_passwd, const char *key_path,
bool support_haproxy)
{
g_context_num = context_num;
g_average_num = average_num;
Expand All @@ -95,6 +97,7 @@ void imap_parser_init(int context_num, int average_num, size_t cache_size,
g_max_auth_times = max_auth_times;
g_block_auth_fail = block_auth_fail;
g_support_tls = support_tls;
g_support_haproxy = support_haproxy;
g_ssl_mutex_buf = NULL;
g_notify_stop = true;
g_sequence_id = 0;
Expand Down Expand Up @@ -1372,7 +1375,11 @@ static int imap_parser_dispatch_cmd2(int argc, char **argv,
};

auto scmp = [](decltype(*proc) &p, const char *cmd) { return strcasecmp(p.first, cmd) < 0; };
if (strcasecmp(argv[1], "UID") == 0) {
auto fc = !pcontext->past_first_command;
pcontext->past_first_command = true;
if (fc && g_support_haproxy && strcmp(argv[0], "PROXY") == 0) {
return imap_cmd_parser_proxy(argc, argv, pcontext);
} else if (strcasecmp(argv[1], "UID") == 0) {
auto it = std::lower_bound(std::begin(proc_uid), std::end(proc_uid), argv[2], scmp);
if (it != std::end(proc_uid) && strcasecmp(argv[2], it->first) == 0)
return it->second(argc, argv, pcontext);
Expand Down
5 changes: 4 additions & 1 deletion mra/imap/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ static std::vector<static_module> g_dfl_svc_plugins = {
static constexpr cfg_directive gromox_cfg_defaults[] = {
{"daemons_fd_limit", "imap_fd_limit", CFG_ALIAS},
{"imap_fd_limit", "0", CFG_SIZE},
{"imap_support_haproxy", "0", CFG_BOOL},
CFG_TABLE_END,
};

Expand Down Expand Up @@ -605,6 +606,7 @@ int main(int argc, const char **argv)
auto cleanup_4 = make_scope_exit(listener_stop);

filedes_limit_bump(gxconfig->get_ll("imap_fd_limit"));
auto support_haproxy = gxconfig->get_ll("imap_support_haproxy");
service_init({g_config_file, g_dfl_svc_plugins, context_num});
if (service_run_early() != 0) {
printf("[system]: failed to run PLUGIN_EARLY_INIT\n");
Expand All @@ -629,7 +631,8 @@ int main(int argc, const char **argv)
imap_parser_init(context_num, context_aver_mitem, context_max_mem,
imap_conn_timeout, autologout_time, imap_auth_times,
block_interval_auth, imap_support_tls, imap_force_tls,
certificate_path, cb_passwd, private_key_path);
certificate_path, cb_passwd, private_key_path,
support_haproxy);

if (0 != imap_parser_run()) {
printf("[system]: failed to run imap parser\n");
Expand Down
4 changes: 3 additions & 1 deletion mra/pop3/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ static std::vector<static_module> g_dfl_svc_plugins = {
static constexpr cfg_directive gromox_cfg_defaults[] = {
{"daemons_fd_limit", "pop3_fd_limit", CFG_ALIAS},
{"pop3_fd_limit", "0", CFG_SIZE},
{"pop3_support_haproxy", "0", CFG_BOOL},
CFG_TABLE_END,
};

Expand Down Expand Up @@ -528,6 +529,7 @@ int main(int argc, const char **argv)
auto cleanup_4 = make_scope_exit(listener_stop);

filedes_limit_bump(gxconfig->get_ll("pop3_fd_limit"));
auto support_haproxy = gxconfig->get_ll("pop3_support_haproxy");
service_init({g_config_file, g_dfl_svc_plugins, context_num});
if (service_run_early() != 0) {
printf("[system]: failed to run PLUGIN_EARLY_INIT\n");
Expand All @@ -551,7 +553,7 @@ int main(int argc, const char **argv)
pop3_parser_init(context_num, context_max_mem, pop3_conn_timeout,
pop3_auth_times, block_interval_auth, pop3_support_tls,
pop3_force_tls, certificate_path, cb_passwd,
private_key_path);
private_key_path, support_haproxy);

if (0 != pop3_parser_run()) {
printf("[system]: failed to run pop3 parser\n");
Expand Down
Loading

0 comments on commit f3c43f6

Please sign in to comment.