Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lmtp ratelimit #5032

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions changes/next/lmtp-ratelimit
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
Description:

Support user and lmtp rate limits in lmtp


Config changes:

This changes the behaviour of the maxlogins_per_user and maxlogins_per_host
configuration options to limit each item per service, so 5 logins from a
host for imap won't also limit http logins.

It also limits lmtp connections for delivery to a locked mailbox,
returning a 4xx code if additional connections are made that try to
deliver to the same mailbox if there are already N connections waiting.


Upgrade instructions:

No config changes required, it's rare that you have more than one LMTP
connection waiting on a mailbox so it's very unlikely that existing
limits will affect lmtp - though you may want to double check that your
limits still make sense given that each service now has a separate count.


GitHub issue:

If theres a github issue number for this, put it here.
6 changes: 6 additions & 0 deletions imap/imap_err.et
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,12 @@ ec IMAP_SYS_ERROR,
ec IMAP_NOSPACE,
"mail system storage has been exceeded"

ec IMAP_LIMIT_USER,
"Too many connections from this user"

ec IMAP_LIMIT_HOST,
"Too many connections from this host"

ec IMAP_PERMISSION_DENIED,
"Permission denied"

Expand Down
2 changes: 1 addition & 1 deletion imap/imapd.c
Original file line number Diff line number Diff line change
Expand Up @@ -2973,7 +2973,7 @@ static int checklimits(const char *tag)
{
struct proc_limits limits;

limits.procname = "imapd";
limits.servicename = config_ident;
limits.clienthost = imapd_clienthost;
limits.userid = imapd_userid;

Expand Down
3 changes: 3 additions & 0 deletions imap/lmtp_err.et
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ ec LMTP_SERVER_FULL,
ec LMTP_SERVER_FAILURE,
"451 4.4.3 %s"

ec LMTP_SERVER_BUSY,
"451 4.4.5 %s"

ec LMTP_MAILBOX_FULL,
"452 4.2.2 %s SESSIONID=<%s>"

Expand Down
29 changes: 27 additions & 2 deletions imap/lmtpd.c
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@
#include "mupdate.h"
#include "notify.h"
#include "prometheus.h"
#include "proc.h"
#include "prot.h"
#include "proxy.h"
#include "slowio.h"
Expand Down Expand Up @@ -151,6 +152,8 @@ static int dupelim = 1; /* eliminate duplicate messages with
static int singleinstance = 1; /* attempt single instance store */
static int isproxy = 0;
static strarray_t *excluded_specialuse = NULL;
static const char *lmtpd_clienthost = "[local]";
static struct proc_handle *proc_handle = NULL;

static struct stagemsg *stage = NULL;

Expand Down Expand Up @@ -256,6 +259,7 @@ int service_init(int argc __attribute__((unused)),
int service_main(int argc, char **argv,
char **envp __attribute__((unused)))
{
const char *localip, *remoteip;
int opt;

struct io_count *io_count_start = NULL;
Expand All @@ -276,6 +280,9 @@ int service_main(int argc, char **argv,
prot_setflushonread(deliver_in, deliver_out);
prot_settimeout(deliver_in, 360);

lmtpd_clienthost = get_clienthost(0, &localip, &remoteip);
proc_register(&proc_handle, 0, config_ident, lmtpd_clienthost, NULL, NULL, NULL);

while ((opt = getopt(argc, argv, "Ha")) != EOF) {
switch(opt) {
case 'H': /* expect HAProxy protocol header */
Expand Down Expand Up @@ -327,6 +334,9 @@ int service_main(int argc, char **argv,

prometheus_increment(CYRUS_LMTP_READY_LISTENERS);

proc_cleanup(&proc_handle);
lmtpd_clienthost = "[local]";

slowio_reset();

return 0;
Expand Down Expand Up @@ -883,11 +893,25 @@ int deliver(message_data_t *msgdata, char *authuser,
strarray_t flags = STRARRAY_INITIALIZER;
struct imap4flags imap4flags = { &flags, authstate };

const char *userid = mbname_userid(mbname);
struct proc_limits limits;
limits.servicename = config_ident;
limits.clienthost = lmtpd_clienthost;
limits.userid = userid;
if (proc_checklimits(&limits)) {
if (limits.maxhost && limits.maxhost <= limits.host) r = IMAP_LIMIT_HOST;
else if (limits.maxuser && limits.maxuser <= limits.user) r = IMAP_LIMIT_USER;
else r = IMAP_SERVER_UNAVAILABLE;
goto setstatus;
}

proc_register(&proc_handle, 0, config_ident, lmtpd_clienthost, userid, NULL, NULL);

// lock conversations for the duration of delivery, so nothing else can read
// the state of any mailbox while the delivery is half done
struct conversations_state *state = NULL;
if (mbname_userid(mbname)) {
r = conversations_open_user(mbname_userid(mbname), 0/*shared*/, &state);
if (userid) {
r = conversations_open_user(userid, 0/*shared*/, &state);
if (r) goto setstatus;
}

Expand Down Expand Up @@ -919,6 +943,7 @@ int deliver(message_data_t *msgdata, char *authuser,
}
strarray_fini(&flags);
conversations_commit(&state);
proc_register(&proc_handle, 0, config_ident, lmtpd_clienthost, NULL, NULL, NULL);
}

telemetry_rusage(mbname_userid(mbname));
Expand Down
5 changes: 5 additions & 0 deletions imap/lmtpengine.c
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ static void send_lmtp_error(struct protstream *pout, int r, strarray_t *resp)
code = LMTP_OK;
break;

case IMAP_LIMIT_HOST:
case IMAP_LIMIT_USER:
code = LMTP_SERVER_BUSY;
break;

case IMAP_SERVER_UNAVAILABLE:
case MUPDATE_NOCONN:
case MUPDATE_NOAUTH:
Expand Down
2 changes: 1 addition & 1 deletion imap/nntpd.c
Original file line number Diff line number Diff line change
Expand Up @@ -2210,7 +2210,7 @@ static void cmd_authinfo_sasl(char *cmd, char *mech, char *resp)
}
}

limits.procname = "nntpd";
limits.servicename = config_ident;
limits.clienthost = nntp_clienthost;
limits.userid = nntp_userid;
if (proc_checklimits(&limits)) {
Expand Down
2 changes: 1 addition & 1 deletion imap/pop3d.c
Original file line number Diff line number Diff line change
Expand Up @@ -1839,7 +1839,7 @@ int openinbox(void)
mailbox_unlock_index(popd_mailbox, NULL);
}

limits.procname = "pop3d";
limits.servicename = config_ident;
limits.clienthost = popd_clienthost;
limits.userid = popd_userid;
if (proc_checklimits(&limits)) {
Expand Down
6 changes: 5 additions & 1 deletion lib/proc.c
Original file line number Diff line number Diff line change
Expand Up @@ -330,7 +330,7 @@ EXPORTED int proc_foreach(procdata_t *func, void *rock)
}

static int procusage_cb(pid_t pid __attribute__((unused)),
const char *servicename __attribute__((unused)),
const char *servicename,
const char *clienthost,
const char *userid,
const char *mboxname __attribute__((unused)),
Expand All @@ -342,6 +342,10 @@ static int procusage_cb(pid_t pid __attribute__((unused)),
/* we only count logged in sessions */
if (!userid) return 0;

/* only check for logins to the particular protocol */
if (limitsp->servicename && strcmp(servicename, limitsp->servicename))
rsto marked this conversation as resolved.
Show resolved Hide resolved
return 0;

if (limitsp->clienthost && !strcmp(clienthost, limitsp->clienthost))
limitsp->host++;
if (limitsp->userid && !strcmp(userid, limitsp->userid))
Expand Down
2 changes: 1 addition & 1 deletion lib/proc.h
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ typedef int procdata_t(pid_t pid,
extern int proc_foreach(procdata_t *func, void *rock);

struct proc_limits {
const char *procname;
const char *servicename;
const char *clienthost;
const char *userid;
int user;
Expand Down