Index: usr.sbin/inetd/inetd.8
===================================================================
RCS file: /cvsroot/src/usr.sbin/inetd/inetd.8,v
retrieving revision 1.68
diff -p -u -r1.68 inetd.8
--- usr.sbin/inetd/inetd.8	24 May 2024 21:55:13 -0000	1.68
+++ usr.sbin/inetd/inetd.8	22 Dec 2025 07:19:26 -0000
@@ -133,7 +133,7 @@ The fields of the configuration file are
 .Pp
 .Bd -unfilled -offset indent -compact
 [listen-addr:]service-spec
-socket-type[:accept-filter]
+socket-type[,accept-max][:accept-filter]
 protocol[,sndbuf=size][,rcvbuf=size]
 wait/nowait[:max]
 user[:group]
@@ -220,16 +220,23 @@ or
 depending on whether the socket is a stream, datagram, raw,
 reliably delivered message, or sequenced packet socket.
 .Pp
-Optionally, for Internet services, an accept filter
+Optionally, for Internet services, an accept limit
+and an accept filter
 (see
 .Xr accept_filter 9 )
-can be specified by appending a colon to
+can be specified.
+The accept limit is appended with a comma followed by a decimal number,
+the accept filter is appended with a colon to
 .Em socket-type ,
 followed by the name of the desired accept filter.
 In this case
 .Nm
-will not see new connections for the specified service until the accept
-filter decides they are ready to be handled.
+will not see new connections for the specified service until the
+number of running instances is below the accept limit and the
+accept filter decides they are ready to be handled.
+The accept limit is ignored for services marked as
+.Dq wait,
+because the socket is handed over to the server program.
 .\" XXX: do accept filters work for AF_UNIX sockets? nobody probably
 .\" cares, but...
 .Pp
@@ -475,6 +482,10 @@ is specified and is
 .Li udp{4,6}
 or
 .Li tcp{4,6} .
+.It Sy accept_max
+Equivalent to
+.Em accept-max
+in positional notation.
 .It Sy acceptfilter
 An accept filter, equivalent to
 .Em accept
Index: usr.sbin/inetd/inetd.c
===================================================================
RCS file: /cvsroot/src/usr.sbin/inetd/inetd.c,v
retrieving revision 1.141
diff -p -u -r1.141 inetd.c
--- usr.sbin/inetd/inetd.c	10 Aug 2022 08:37:53 -0000	1.141
+++ usr.sbin/inetd/inetd.c	22 Dec 2025 07:19:26 -0000
@@ -288,6 +288,12 @@ static struct kevent	*allocchange(void);
 static int	get_line(int, char *, int);
 static void	spawn(struct servtab *, int);
 
+static void	suspend_sep(struct servtab *);
+static void	resume_sep(struct servtab *);
+
+static void	add_child(struct servtab *, pid_t);
+static void	remove_child(struct servtab *, pid_t);
+
 struct biltin {
 	const char *bi_service;		/* internally provided service name */
 	int	bi_socktype;		/* type of socket supported */
@@ -405,7 +411,7 @@ main(int argc, char *argv[])
 	}
 
 	for (;;) {
-		int		ctrl;
+		int ctrl;
 		struct kevent	eventbuf[64], *ev;
 		struct servtab	*sep;
 
@@ -449,12 +455,16 @@ main(int argc, char *argv[])
 				DPRINTF(SERV_FMT ": accept, ctrl fd %d",
 				    SERV_PARAMS(sep), ctrl);
 				if (ctrl < 0) {
-					if (errno != EINTR)
+					if (errno != EINTR && errno != EBADF)
 						syslog(LOG_WARNING,
 						    SERV_FMT ": accept: %m",
 						    SERV_PARAMS(sep));
 					continue;
 				}
+				sep->se_accept_count++;
+				if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T &&
+				    sep->se_accept_count >= sep->se_accept_max)
+					suspend_sep(sep);
 			} else
 				ctrl = sep->se_fd;
 			spawn(sep, ctrl);
@@ -481,11 +491,18 @@ spawn(struct servtab *sep, int ctrl)
 		pid = fork();
 		if (pid < 0) {
 			syslog(LOG_ERR, "fork: %m");
-			if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM)
+			if (sep->se_wait == 0 && sep->se_socktype == SOCK_STREAM) {
+				sep->se_accept_count--;
+				if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T &&
+				    sep->se_accept_count < sep->se_accept_max)
+					resume_sep(sep);
 				close(ctrl);
+			}
 			sleep(1);
 			return;
 		}
+		if (pid != 0 && sep->se_accept_children != NULL)
+			add_child(sep, pid);
 		if (pid != 0 && sep->se_wait != 0) {
 			struct kevent	*ev;
 
@@ -633,6 +650,39 @@ run_service(int ctrl, struct servtab *se
 }
 
 static void
+add_child(struct servtab *sep, pid_t pid)
+{
+	if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T &&
+	    sep->se_accept_count <= sep->se_accept_max &&
+	    sep->se_accept_count > 0)
+		sep->se_accept_children[sep->se_accept_count-1] = pid;
+}
+
+static void
+remove_child(struct servtab *sep, pid_t pid)
+{
+	size_t i, tail;
+
+	for (i=0; i<sep->se_accept_count; ++i) {
+		if (sep->se_accept_children[i] != pid)
+			continue;
+
+		if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T &&
+		    sep->se_accept_count <= sep->se_accept_max)
+			resume_sep(sep);
+
+		sep->se_accept_count--;
+		tail = sep->se_accept_count - i;
+
+		memmove(&sep->se_accept_children[i],
+		    &sep->se_accept_children[i+1],
+		    tail * sizeof(pid_t));
+
+		break;
+	} 
+}
+
+static void
 reapchild(void)
 {
 	int status;
@@ -644,7 +694,7 @@ reapchild(void)
 		if (pid <= 0)
 			break;
 		DPRINTF("%d reaped, status %#x", pid, status);
-		for (sep = servtab; sep != NULL; sep = sep->se_next)
+		for (sep = servtab; sep != NULL; sep = sep->se_next) {
 			if (sep->se_wait == pid) {
 				struct kevent	*ev;
 
@@ -663,6 +713,10 @@ reapchild(void)
 				DPRINTF("restored %s, fd %d",
 				    sep->se_service, sep->se_fd);
 			}
+
+			if (sep->se_accept_children != NULL)
+				remove_child(sep, pid);
+		}
 	}
 }
 
@@ -799,7 +853,7 @@ setsockopt(fd, SOL_SOCKET, opt, &on, (so
 		return;
 	}
 	if (sep->se_socktype == SOCK_STREAM)
-		listen(sep->se_fd, 10);
+		listen(sep->se_fd, 128);
 
 	/* for internal dgram, setsockopt() is required for recvfromto() */
 	if (sep->se_socktype == SOCK_DGRAM && sep->se_bi != NULL) {
@@ -821,6 +875,18 @@ setsockopt(fd, SOL_SOCKET, opt, &on, (so
 		}
 	}
 
+	/* Allocate list of children */
+	if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T &&
+	    sep->se_accept_max > 0) {
+		sep->se_accept_children = calloc(sep->se_accept_max,
+		    sizeof(pid_t));
+		if (sep->se_accept_children == NULL) {
+			syslog(LOG_ERR, SERV_FMT ": accept_max too large: %m",
+			    SERV_PARAMS(sep));
+			sep->se_accept_max = SERVTAB_UNSPEC_SIZE_T;
+		}
+	}
+
 	/* Set the accept filter, if specified. To be done after listen.*/
 	if (sep->se_accf.af_name[0] != 0 && setsockopt(sep->se_fd, SOL_SOCKET,
 	    SO_ACCEPTFILTER, &sep->se_accf,
@@ -856,6 +922,58 @@ close_sep(struct servtab *sep)
 	}
 }
 
+/*
+ * Suspend service by ignoring connect events 
+ */
+static void
+suspend_sep(struct servtab *sep)
+{
+	struct kevent	*ev;
+
+	if (sep->se_accept_count == sep->se_accept_max) {
+		ev = allocchange();
+		EV_SET(ev, sep->se_fd, EVFILT_READ, EV_DISABLE, 0, 0,
+		    (intptr_t)sep);
+	}
+
+	if (sep->se_accept_mark == 0)
+		syslog(LOG_ERR, SERV_FMT
+		    ": accept_max reached (%zu);"
+		    "service suspended",
+		    SERV_PARAMS(sep),
+		    sep->se_accept_max);
+
+	if (sep->se_accept_max > 0)
+		sep->se_accept_mark = sep->se_accept_max - 1;
+	else
+		sep->se_accept_mark = 0;
+}
+
+/*
+ * Resume service by listening to connect events
+ */
+static void
+resume_sep(struct servtab *sep)
+{
+	struct kevent	*ev;
+
+	if (sep->se_accept_count == sep->se_accept_max) {
+		ev = allocchange();
+		EV_SET(ev, sep->se_fd, EVFILT_READ, EV_ENABLE, 0, 0,
+		    (intptr_t)sep);
+	}
+
+	if (sep->se_accept_mark == 0 ||
+	    sep->se_accept_count < sep->se_accept_mark) {
+		syslog(LOG_ERR, SERV_FMT
+		    ": fell below accept_max (%zu);"
+		    "service resumed",
+		    SERV_PARAMS(sep),
+		    sep->se_accept_max);
+		sep->se_accept_mark = 0;
+	}
+}
+
 void
 register_rpc(struct servtab *sep)
 {
Index: usr.sbin/inetd/inetd.h
===================================================================
RCS file: /cvsroot/src/usr.sbin/inetd/inetd.h,v
retrieving revision 1.7
diff -p -u -r1.7 inetd.h
--- usr.sbin/inetd/inetd.h	16 Dec 2025 18:24:46 -0000	1.7
+++ usr.sbin/inetd/inetd.h	22 Dec 2025 07:19:26 -0000
@@ -190,6 +190,10 @@ struct	servtab {
 	size_t	se_ip_max;  		/* max # of instances of this service per ip per minute */
 	SLIST_HEAD(iplist, rl_ip_node) se_rl_ip_list; /* per-address (IP) rate limiting */
 	time_t se_time;	/* start of se_count and ip_max counts, in seconds from arbitrary point */
+	size_t	se_accept_max;		/* max # of connections to accept */
+	size_t	se_accept_count;	/* number of accepted connections */
+	pid_t	*se_accept_children;	/* identify child */
+	size_t	se_accept_mark;		/* mark when to enable messages */
 	
 	/* TODO convert to using SLIST */
 	struct	servtab *se_next;
@@ -255,6 +259,7 @@ int 	parse_wait(struct servtab *, int);
 int 	parse_server(struct servtab *, const char *);
 void 	parse_socktype(char *, struct servtab *);
 void 	parse_accept_filter(char *, struct servtab *);
+void 	parse_accept_max(char *, struct servtab *);
 char 	*nextline(FILE *);
 char 	*newstr(const char *);
 
Index: usr.sbin/inetd/parse.c
===================================================================
RCS file: /cvsroot/src/usr.sbin/inetd/parse.c,v
retrieving revision 1.5
diff -p -u -r1.5 parse.c
--- usr.sbin/inetd/parse.c	10 Aug 2022 08:37:53 -0000	1.5
+++ usr.sbin/inetd/parse.c	22 Dec 2025 07:19:26 -0000
@@ -184,6 +184,7 @@ config(void)
 			SWAP(service_type, cp->se_type, sep->se_type);
 			SWAP(size_t, cp->se_service_max, sep->se_service_max);
 			SWAP(size_t, cp->se_ip_max, sep->se_ip_max);
+			SWAP(size_t, cp->se_accept_max, sep->se_accept_max);
 #undef SWAP
 			if (isrpcservice(sep))
 				unregister_rpc(sep);
@@ -572,6 +573,9 @@ more:
 		/* continue parsing v1 */
 		parse_socktype(arg, sep);
 		if (sep->se_socktype == SOCK_STREAM) {
+			parse_accept_max(arg, sep);
+		}
+		if (sep->se_socktype == SOCK_STREAM) {
 			parse_accept_filter(arg, sep);
 		}
 		if (sep->se_hostaddr == NULL) {
@@ -1128,6 +1132,32 @@ parse_accept_filter(char *arg, struct se
 }
 
 void
+parse_accept_max(char *arg, struct servtab *sep)
+{
+	char *cp1;
+	int rstatus;
+
+	if (strncmp(arg, "stream,", sizeof("stream,") - 1) != 0)
+		return;
+	cp1 = arg + (sizeof("stream,") - 1);
+
+	sep->se_accept_max = (size_t)strtou(cp1, NULL, 10, 0,
+	    SERVTAB_COUNT_MAX, &rstatus);
+	if (rstatus != 0) {
+		if (rstatus != ERANGE) {
+			/* For compatibility w/ atoi parsing */
+			sep->se_accept_max = 0;
+		}
+
+		WRN("Improper \"accept_max\" value '%s', "
+		    "using '%zu' instead: %s",
+		    cp1,
+		    sep->se_accept_max,
+		    strerror(rstatus));
+	}
+}
+
+void
 parse_socktype(char* arg, struct servtab* sep)
 {
 	/* stream socket may have an accept filter, only check first chars */
@@ -1156,6 +1186,7 @@ init_servtab(void)
 		 */
 		.se_service_max = SERVTAB_UNSPEC_SIZE_T,
 		.se_ip_max = SERVTAB_UNSPEC_SIZE_T,
+		.se_accept_max = SERVTAB_UNSPEC_SIZE_T,
 		.se_wait = SERVTAB_UNSPEC_VAL,
 		.se_socktype = SERVTAB_UNSPEC_VAL,
 		.se_rl_ip_list = SLIST_HEAD_INITIALIZER(se_ip_list_head)
Index: usr.sbin/inetd/parse_v2.c
===================================================================
RCS file: /cvsroot/src/usr.sbin/inetd/parse_v2.c,v
retrieving revision 1.7
diff -p -u -r1.7 parse_v2.c
--- usr.sbin/inetd/parse_v2.c	8 Feb 2024 20:51:25 -0000	1.7
+++ usr.sbin/inetd/parse_v2.c	22 Dec 2025 07:19:26 -0000
@@ -75,6 +75,7 @@ static hresult	filter_handler(struct ser
 static hresult	group_handler(struct servtab *, vlist);
 static hresult	service_max_handler(struct servtab *, vlist);
 static hresult	ip_max_handler(struct servtab *, vlist);
+static hresult	accept_max_handler(struct servtab *, vlist);
 static hresult	protocol_handler(struct servtab *, vlist);
 static hresult	recv_buf_handler(struct servtab *, vlist);
 static hresult	send_buf_handler(struct servtab *, vlist);
@@ -124,6 +125,7 @@ static struct key_handler {
 	{ "exec", exec_handler },
 	{ "args", args_handler },
 	{ "ip_max", ip_max_handler },
+	{ "accept_max", accept_max_handler },
 #ifdef IPSEC
 	{ "ipsec", ipsec_handler }
 #endif
@@ -1002,6 +1004,44 @@ ip_max_handler(struct servtab *sep, vlis
 	return KEY_HANDLER_SUCCESS;
 }
 
+/* Set max connections to accept */
+static hresult
+accept_max_handler(struct servtab *sep, vlist values)
+{
+	char *count_str;
+	int rstatus;
+
+	if (sep->se_accept_max != SERVTAB_UNSPEC_SIZE_T) {
+		TMD("accept_max");
+		return KEY_HANDLER_FAILURE;
+	}
+
+	count_str = next_value(values);
+
+	if (count_str == NULL) {
+		TFA("accept_max");
+		return KEY_HANDLER_FAILURE;
+	}
+
+	size_t count = (size_t)strtou(count_str, NULL, 10, 0,
+	    SERVTAB_COUNT_MAX, &rstatus);
+
+	if (rstatus != 0) {
+		ERR("Invalid accept_max '%s': %s", count_str,
+		    strerror(rstatus));
+		return KEY_HANDLER_FAILURE;
+	}
+
+	if (next_value(values) != NULL) {
+		TMA("accept_max");
+		return KEY_HANDLER_FAILURE;
+	}
+
+	sep->se_accept_max = count;
+
+	return KEY_HANDLER_SUCCESS;
+}
+
 /* Set user to execute as */
 static hresult
 user_handler(struct servtab *sep, vlist values)
