Index | Thread | Search

From:
Kirill A. Korinsky <kirill@korins.ky>
Subject:
Re: smtpd: implement RFC9422 LIMITS extension utilization
To:
Martijn van Duren <openbsd+tech@list.imperialat.at>
Cc:
tech@openbsd.org
Date:
Wed, 11 Mar 2026 13:35:41 +0100

Download raw body.

Thread
On Wed, 11 Mar 2026 08:30:25 +0100,
Martijn van Duren <openbsd+tech@list.imperialat.at> wrote:
> 
> On 3/11/26 00:18, Martijn van Duren wrote:
> > EHLO,
> > 
> > While I haven't seen this in the wild, it might be nice to announce our
> > limits to the world. RFC9422 specifies 3 limits that can be announced:
> > MAILMAX, RCPTMAX, and RCPTDOMAINMAX. RCPTDOMAINMAX isn't a feature we
> > support as far as I'm aware, but MAILMAX, and RCPTMAX are implemented,
> > so why not announce them and save everybody a bit of time where
> > supported.
> > 
> > OK?
> > 
> > martijn@
> > And here's a diff to parse and use MAILMAX and RCPTMAX.
> 
> OK?
> 
> martijn@
> 
> diff d8042db37a6bbfc3219d85f0b9c4cafc1a1924c2 b90a19db558de4a73e6a07a023332ad78d9d5e86
> commit - d8042db37a6bbfc3219d85f0b9c4cafc1a1924c2
> commit + b90a19db558de4a73e6a07a023332ad78d9d5e86
> blob - 3524b378129873422b2d238b563adcef08f705be
> blob + a207b2ef8f6cf664368b54952de97d344cc2f094
> --- usr.sbin/smtpd/limit.c
> +++ usr.sbin/smtpd/limit.c
> @@ -40,6 +40,7 @@ limit_mta_set_defaults(struct mta_limits *limits)
>  	limits->discdelay_route = 3;
>  
>  	limits->max_mail_per_session = 100;
> +	limits->max_rcpt_per_transaction = 0;
>  	limits->sessdelay_transaction = 0;
>  	limits->sessdelay_keepalive = 10;
>  
> @@ -86,6 +87,8 @@ limit_mta_set(struct mta_limits *limits, const char *k
>  
>  	else if (!strcmp(key, "session-mail-max"))
>  		limits->max_mail_per_session = value;
> +	else if (!strcmp(key, "transaction-rcpt-max"))
> +		limits->max_rcpt_per_transaction = value;
>  	else if (!strcmp(key, "session-transaction-delay"))
>  		limits->sessdelay_transaction = value;
>  	else if (!strcmp(key, "session-keepalive"))
> blob - 8022d23ecb02e4654968912bb112c2042701e89e
> blob + 91785c5acc5aa4266c25d68ba7a1e23ac683a16e
> --- usr.sbin/smtpd/mta.c
> +++ usr.sbin/smtpd/mta.c
> @@ -44,10 +44,12 @@
>  #define RELAY_HOLDQ		0x02
>  
>  static void mta_setup_dispatcher(struct dispatcher *);
> +static void mta_task_split(struct mta_task *, size_t);
>  static void mta_handle_envelope(struct envelope *, const char *);
>  static void mta_query_smarthost(struct envelope *);
>  static void mta_on_smarthost(struct envelope *, const char *);
>  static void mta_query_mx(struct mta_relay *);
> +static void mta_query_limits(struct mta_relay *);
>  static void mta_query_secret(struct mta_relay *);
>  static void mta_query_preference(struct mta_relay *);
>  static void mta_query_source(struct mta_relay *);
> @@ -649,11 +651,21 @@ mta_route_collect(struct mta_relay *relay, struct mta_
>  }
>  
>  struct mta_task *
> -mta_route_next_task(struct mta_relay *relay, struct mta_route *route)
> +mta_route_next_task(struct mta_relay *relay, size_t rcptmax)
>  {
>  	struct mta_task	*task;
> +	size_t min;
>  
>  	if ((task = TAILQ_FIRST(&relay->tasks))) {
> +		min = 0;
> +		if (relay->limits->max_rcpt_per_transaction != 0)
> +			min = relay->limits->max_rcpt_per_transaction;
> +		if (rcptmax != 0 && rcptmax < min)
> +			min = rcptmax;
> +		if (min == 0)
> +			min = 100;

It reads like min is default limits->max_mail_per_session, if it is, why not
reuse that value here?

> +		mta_task_split(task, min);
> +
>  		TAILQ_REMOVE(&relay->tasks, task, entry);
>  		relay->ntask -= 1;
>  		task->relay = NULL;
> @@ -684,6 +696,49 @@ mta_route_next_task(struct mta_relay *relay, struct mt
>  }
>  
>  static void
> +mta_task_split(struct mta_task *task0, size_t limit)
> +{
> +	struct mta_relay *relay;
> +	struct mta_task *task;
> +	struct mta_envelope *e0, *e;
> +	size_t n;
> +
> +	if (limit == 0 || task0->nenvelopes <= limit)
> +		return;
> +
> +	log_debug("debug: mta: msgid:%08" PRIx32 " %s "
> +	    "too many envelopes (limit %zu), splitting %zu "
> +	    "envelopes over %zu tasks",
> +	    task0->msgid, mta_relay_to_text(task0->relay), limit, task0->nenvelopes,
> +	    (task0->nenvelopes / limit) + (task0->nenvelopes % limit == 0 ? 0 : 1));
> +
> +	n = 0;
> +	TAILQ_FOREACH(e0, &task0->envelopes, entry) {
> +		if (++n == limit)
> +			break;
> +	}
> +	relay = task0->relay;
> +	task = NULL;
> +	n = 0;
> +	while ((e = TAILQ_NEXT(e0, entry)) != NULL) {
> +		if (task == NULL) {
> +			task = xmemdup(task0, sizeof *task0);
> +			TAILQ_INIT(&task->envelopes);
> +			task->nenvelopes = 0;
> +			relay->ntask += 1;
> +			TAILQ_INSERT_TAIL(&relay->tasks, task, entry);
> +			task->sender = xstrdup(task0->sender);
> +			stat_increment("mta.task", 1);
> +		}
> +		TAILQ_REMOVE(&task0->envelopes, e, entry);
> +		TAILQ_INSERT_TAIL(&task->envelopes, e, entry);
> +		e->task = task;
> +		if (++task->nenvelopes == limit)
> +			task = NULL;
> +	}
> +}
> +
> +static void
>  mta_handle_envelope(struct envelope *evp, const char *smarthost)
>  {
>  	struct mta_relay	*relay;
> @@ -770,9 +825,18 @@ mta_handle_envelope(struct envelope *evp, const char *
>  		if (task->msgid == evpid_to_msgid(evp->id))
>  			break;
>  
> +	if (task != NULL) {
> +		if (task->relay->limits == NULL)
> +			mta_query_limits(task->relay);
> +		if (task->relay->limits->max_rcpt_per_transaction != 0 &&
> +		    task->relay->limits->max_rcpt_per_transaction <
> +		    task->nenvelopes)
> +			task = NULL;
> +	}
>  	if (task == NULL) {
>  		task = xmalloc(sizeof *task);
>  		TAILQ_INIT(&task->envelopes);
> +		task->nenvelopes = 0;
>  		task->relay = relay;
>  		relay->ntask += 1;
>  		TAILQ_INSERT_TAIL(&relay->tasks, task, entry);
> @@ -817,6 +881,7 @@ mta_handle_envelope(struct envelope *evp, const char *
>  	e->dsn_ret = evp->dsn_ret;
>  
>  	TAILQ_INSERT_TAIL(&task->envelopes, e, entry);
> +	task->nenvelopes++;
>  	log_debug("debug: mta: received evp:%016" PRIx64
>  	    " for <%s>", e->id, e->dest);
>  
> blob - 8aa23779f8f46fb4ec69517a724563eae2a0515f
> blob + eb63136ea840a44d9480ec844036ec0def638ff8
> --- usr.sbin/smtpd/mta_session.c
> +++ usr.sbin/smtpd/mta_session.c
> @@ -76,12 +76,14 @@ enum mta_state {
>  #define MTA_HANGON		0x2000
>  #define MTA_RECONN		0x4000
>  
> -#define MTA_EXT_STARTTLS	0x01
> -#define MTA_EXT_PIPELINING	0x02
> -#define MTA_EXT_AUTH		0x04
> -#define MTA_EXT_AUTH_PLAIN     	0x08
> -#define MTA_EXT_AUTH_LOGIN     	0x10
> -#define MTA_EXT_SIZE     	0x20
> +#define MTA_EXT_STARTTLS	0x001
> +#define MTA_EXT_PIPELINING	0x002
> +#define MTA_EXT_AUTH		0x004
> +#define MTA_EXT_AUTH_PLAIN     	0x008
> +#define MTA_EXT_AUTH_LOGIN     	0x010
> +#define MTA_EXT_SIZE     	0x020
> +#define MTA_EXT_LIMITS_MAILMAX	0x100
> +#define MTA_EXT_LIMITS_RCPTMAX	0x200
>  
>  struct mta_session {
>  	uint64_t		 id;
> @@ -105,6 +107,8 @@ struct mta_session {
>  	int			 ext;
>  
>  	size_t			 ext_size;
> +	size_t			 ext_mailmax;
> +	size_t			 ext_rcptmax;
>  
>  	size_t			 msgtried;
>  	size_t			 msgcount;
> @@ -712,7 +716,9 @@ again:
>  			break;
>  		}
>  
> -		if (s->msgcount >= s->relay->limits->max_mail_per_session) {
> +		if (s->msgcount >= s->relay->limits->max_mail_per_session ||
> +		    (s->ext & MTA_EXT_LIMITS_MAILMAX &&
> +		    s->msgcount >= s->ext_mailmax)) {
>  			log_debug("debug: mta: "
>  			    "%p: cannot send more message to relay %s", s,
>  			    mta_relay_to_text(s->relay));
> @@ -730,7 +736,8 @@ again:
>  			fatalx("task should be NULL at this point");
>  
>  		if (s->task == NULL)
> -			s->task = mta_route_next_task(s->relay, s->route);
> +			s->task = mta_route_next_task(s->relay,
> +			    s->ext & MTA_EXT_LIMITS_RCPTMAX ? s->ext_rcptmax : 0);
>  		if (s->task == NULL) {
>  			log_debug("debug: mta: %p: no task for relay %s",
>  			    s, mta_relay_to_text(s->relay));
> @@ -1198,10 +1205,12 @@ static void
>  mta_io(struct io *io, int evt, void *arg)
>  {
>  	struct mta_session	*s = arg;
> -	char			*line, *msg, *p;
> +	char			*line, *msg, *p0, *p;
>  	size_t			 len;
>  	const char		*error;
> -	int			 cont;
> +	int			 cont, ext, fail;
> +	long			 l;
> +	size_t			*limit;
>  
>  	log_trace(TRACE_IO, "mta: %p: %s %s", s, io_strevent(evt),
>  	    io_strio(io));
> @@ -1280,6 +1289,41 @@ mta_io(struct io *io, int evt, void *arg)
>  				s->ext_size = strtonum(msg+5, 0, UINT32_MAX, &error);
>  				if (error == NULL)
>  					s->ext |= MTA_EXT_SIZE;
> +			} else if (strncmp(msg, "LIMITS ", 7) == 0) {
> +				p0 = msg + 7;
> +				while (p0[0] != '\0') {
> +					limit = NULL;
> +					ext = 0;
> +					if (strncmp(p0, "MAILMAX=", 8) == 0) {
> +						ext = MTA_EXT_LIMITS_MAILMAX;
> +						p0 += 8;
> +						limit = &s->ext_mailmax;
> +					} else if (strncmp(p0, "RCPTMAX=", 8) == 0) {
> +						ext = MTA_EXT_LIMITS_RCPTMAX;
> +						p0 += 8;
> +						limit = &s->ext_rcptmax;
> +					}
> +
> +					if (limit != NULL) {
> +						errno = 0;
> +						l = strtol(p0, &p, 10);
> +
> +						fail = errno != 0 || p0 == p;
> +						fail |= l <= 0;
> +						fail |=
> +						    p[0] != ' ' && p[0] != '\0';
> +
> +						if (!fail) {
> +							*limit = (size_t)l;
> +							s->ext |= ext;
> +						}
> +					}
> +					p = strchr(p0, ' ');
> +					if (p != NULL)
> +						p0 = p + 1;
> +					else
> +						p0 = strchr(p0, '\0');
> +				}
>  			}
>  		}
>  
> blob - 3574db4c03d611801672016b1d0d4314746802cf
> blob + e147f099d6f6289e686af7bb3401dae0d1e3a929
> --- usr.sbin/smtpd/smtpd.h
> +++ usr.sbin/smtpd/smtpd.h
> @@ -781,6 +781,7 @@ struct mta_limits {
>  	time_t	discdelay_route;
>  
>  	size_t	max_mail_per_session;
> +	size_t	max_rcpt_per_transaction;
>  	time_t	sessdelay_transaction;
>  	time_t	sessdelay_keepalive;
>  
> @@ -867,6 +868,7 @@ struct mta_task {
>  	TAILQ_ENTRY(mta_task)		 entry;
>  	struct mta_relay		*relay;
>  	uint32_t			 msgid;
> +	size_t				 nenvelopes;
>  	TAILQ_HEAD(, mta_envelope)	 envelopes;
>  	char				*sender;
>  };
> @@ -1502,7 +1504,7 @@ void mta_route_collect(struct mta_relay *, struct mta_
>  void mta_source_error(struct mta_relay *, struct mta_route *, const char *);
>  void mta_delivery_log(struct mta_envelope *, const char *, const char *, int, const char *);
>  void mta_delivery_notify(struct mta_envelope *);
> -struct mta_task *mta_route_next_task(struct mta_relay *, struct mta_route *);
> +struct mta_task *mta_route_next_task(struct mta_relay *, size_t);
>  const char *mta_host_to_text(struct mta_host *);
>  const char *mta_relay_to_text(struct mta_relay *);
>  
> 

-- 
wbr, Kirill