summaryrefslogtreecommitdiffstats
path: root/usr.sbin
diff options
context:
space:
mode:
authorume <ume@FreeBSD.org>2002-08-07 17:03:14 +0000
committerume <ume@FreeBSD.org>2002-08-07 17:03:14 +0000
commitc9b1c6c3c7785ed179bd750d6e398ddbb1bff1c9 (patch)
tree88891ceb8e2b942dccd7389e9b876a7080525b53 /usr.sbin
parent919d00b11333feaf446ac8dbde0e32d08b6f19fb (diff)
downloadFreeBSD-src-c9b1c6c3c7785ed179bd750d6e398ddbb1bff1c9.zip
FreeBSD-src-c9b1c6c3c7785ed179bd750d6e398ddbb1bff1c9.tar.gz
Add capability for limiting the maximum number of simultaneous
invocations of each service from a single IP address. Requested by: matusita Reviewed by: dwmalone Tested by: matusita on snapshots.jp.FreeBSD.org MFC after: 2 weeks
Diffstat (limited to 'usr.sbin')
-rw-r--r--usr.sbin/inetd/inetd.815
-rw-r--r--usr.sbin/inetd/inetd.c286
-rw-r--r--usr.sbin/inetd/inetd.h18
3 files changed, 315 insertions, 4 deletions
diff --git a/usr.sbin/inetd/inetd.8 b/usr.sbin/inetd/inetd.8
index 939a534..1ad7d3a 100644
--- a/usr.sbin/inetd/inetd.8
+++ b/usr.sbin/inetd/inetd.8
@@ -50,6 +50,7 @@
.Op Fl a Ar address | hostname
.Op Fl p Ar filename
.Op Fl R Ar rate
+.Op Fl s Ar maximum
.Op Ar configuration file
.Sh DESCRIPTION
The
@@ -102,6 +103,12 @@ May be overridden on a per-service basis with the
Specify the maximum number of times a service can be invoked
in one minute; the default is 256.
A rate of 0 allows an unlimited number of invocations.
+.It Fl s Ar maximum
+Specify the default maximum number of
+simultaneous invocations of each service from a single IP address;
+the default is unlimited.
+May be overridden on a per-service basis with the "max-child-per-ip"
+parameter.
.It Fl a
Specify one specific IP address to bind to.
Alternatively, a hostname can be specified,
@@ -155,7 +162,7 @@ fields of the configuration file are as follows:
service name
socket type
protocol
-{wait|nowait}[/max-child[/max-connections-per-ip-per-minute]]
+{wait|nowait}[/max-child[/max-connections-per-ip-per-minute[/max-child-per-ip]]]
user[:group][/login-class]
server program
server program arguments
@@ -396,6 +403,12 @@ outstanding child processes.
Once the maximum is reached, further
connections from this IP address will be dropped until the end of the
minute.
+In addition, you can specify the maximum number of simultaneous
+invocations of each service from a single IP address by appending a
+.Dq /
+followed by the number to the maximum number of outstanding child
+processes. Once the maximum is reached, further connections from this
+IP address will be dropped.
.Pp
The
.Em user
diff --git a/usr.sbin/inetd/inetd.c b/usr.sbin/inetd/inetd.c
index 163e0df..59a6dfe 100644
--- a/usr.sbin/inetd/inetd.c
+++ b/usr.sbin/inetd/inetd.c
@@ -196,6 +196,12 @@ __FBSDID("$FreeBSD$");
< 0 = no limit */
#endif
+#ifndef MAXPERIP
+#define MAXPERIP -1 /* maximum number of this service
+ from a single remote address,
+ < 0 = no limit */
+#endif
+
#ifndef TOOMANY
#define TOOMANY 256 /* don't start more than TOOMANY */
#endif
@@ -229,6 +235,16 @@ void setup(struct servtab *);
void ipsecsetup(struct servtab *);
#endif
void unregisterrpc(register struct servtab *sep);
+static struct conninfo *search_conn(struct servtab *sep, int ctrl);
+static int room_conn(struct servtab *sep, struct conninfo *conn);
+static void addchild_conn(struct conninfo *conn, pid_t pid);
+static void reapchild_conn(pid_t pid);
+static void free_conn(struct conninfo *conn);
+static void resize_conn(struct servtab *sep, int maxperip);
+static void free_connlist(struct servtab *sep);
+static void free_proc(struct procinfo *);
+static struct procinfo *search_proc(pid_t pid, int add);
+static int hashval(char *p, int len);
int allow_severity;
int deny_severity;
@@ -243,6 +259,7 @@ int timingout;
int toomany = TOOMANY;
int maxchild = MAXCHILD;
int maxcpm = MAXCPM;
+int maxperip = MAXPERIP;
struct servent *sp;
struct rpcent *rpc;
char *hostname = NULL;
@@ -269,6 +286,8 @@ const char *pid_file = _PATH_INETDPID;
struct netconfig *udpconf, *tcpconf, *udp6conf, *tcp6conf;
+static LIST_HEAD(, procinfo) proctable[PERIPSIZE];
+
int
getvalue(const char *arg, int *value, const char *whine)
{
@@ -314,10 +333,11 @@ main(int argc, char **argv)
struct addrinfo hints, *res;
const char *servname;
int error;
+ struct conninfo *conn;
openlog("inetd", LOG_PID | LOG_NOWAIT | LOG_PERROR, LOG_DAEMON);
- while ((ch = getopt(argc, argv, "dlwWR:a:c:C:p:")) != -1)
+ while ((ch = getopt(argc, argv, "dlwWR:a:c:C:p:s:")) != -1)
switch(ch) {
case 'd':
debug = 1;
@@ -344,6 +364,10 @@ main(int argc, char **argv)
case 'p':
pid_file = optarg;
break;
+ case 's':
+ getvalue(optarg, &maxperip,
+ "-s %s: bad value for maximum children per source address");
+ break;
case 'w':
wrap_ex++;
break;
@@ -454,6 +478,9 @@ main(int argc, char **argv)
}
}
+ for (i = 0; i < PERIPSIZE; ++i)
+ LIST_INIT(&proctable[i]);
+
if (!no_v4bind) {
udpconf = getnetconfigent("udp");
tcpconf = getnetconfigent("tcp");
@@ -563,6 +590,8 @@ main(int argc, char **argv)
n--;
if (debug)
warnx("someone wants %s", sep->se_service);
+ dofork = !sep->se_bi || sep->se_bi->bi_fork || ISWRAP(sep);
+ conn = NULL;
if (sep->se_accept && sep->se_socktype == SOCK_STREAM) {
i = 1;
if (ioctl(sep->se_fd, FIONBIO, &i) < 0)
@@ -590,6 +619,12 @@ main(int argc, char **argv)
close(ctrl);
continue;
}
+ if (dofork &&
+ (conn = search_conn(sep, ctrl)) != NULL &&
+ !room_conn(sep, conn)) {
+ close(ctrl);
+ continue;
+ }
} else
ctrl = sep->se_fd;
if (log && !ISWRAP(sep)) {
@@ -627,7 +662,6 @@ main(int argc, char **argv)
* fork and anything we're wrapping (as wrapping might
* block or use hosts_options(5) twist).
*/
- dofork = !sep->se_bi || sep->se_bi->bi_fork || ISWRAP(sep);
if (dofork) {
if (sep->se_count++ == 0)
(void)gettimeofday(&sep->se_time, (struct timezone *)NULL);
@@ -647,6 +681,7 @@ main(int argc, char **argv)
sep->se_socktype == SOCK_STREAM)
close(ctrl);
close_sep(sep);
+ free_conn(conn);
sigsetmask(0L);
if (!timingout) {
timingout = 1;
@@ -662,12 +697,15 @@ main(int argc, char **argv)
if (sep->se_accept &&
sep->se_socktype == SOCK_STREAM)
close(ctrl);
+ free_conn(conn);
sigsetmask(0L);
sleep(1);
continue;
}
- if (pid)
+ if (pid) {
+ addchild_conn(conn, pid);
addchild(sep, pid);
+ }
sigsetmask(0L);
if (pid == 0) {
if (dofork) {
@@ -895,6 +933,7 @@ reapchild(void)
sep->se_server, pid, status);
break;
}
+ reapchild_conn(pid);
}
}
@@ -908,6 +947,7 @@ void
config(void)
{
struct servtab *sep, *new, **sepp;
+ struct conninfo *conn;
long omask;
int new_nomapped;
@@ -970,6 +1010,8 @@ config(void)
sep->se_maxchild = new->se_maxchild;
sep->se_numchild = new->se_numchild;
sep->se_maxcpm = new->se_maxcpm;
+ resize_conn(sep, new->se_maxperip);
+ sep->se_maxperip = new->se_maxperip;
sep->se_bi = new->se_bi;
/* might need to turn on or off service now */
if (sep->se_fd >= 0) {
@@ -1546,6 +1588,7 @@ getconfigent(void)
#ifdef INET6
int v6bind = 0;
#endif
+ int i;
more:
while ((cp = nextline(fconfig)) != NULL) {
@@ -1786,6 +1829,7 @@ more:
}
sep->se_maxchild = -1;
sep->se_maxcpm = -1;
+ sep->se_maxperip = -1;
if ((s = strchr(arg, '/')) != NULL) {
char *eptr;
u_long val;
@@ -1804,6 +1848,8 @@ more:
sep->se_maxchild = val;
if (*eptr == '/')
sep->se_maxcpm = strtol(eptr + 1, &eptr, 10);
+ if (*eptr == '/')
+ sep->se_maxperip = strtol(eptr + 1, &eptr, 10);
/*
* explicitly do not check for \0 for future expansion /
* backwards compatibility
@@ -1861,6 +1907,8 @@ more:
sep->se_bi = bi;
} else
sep->se_bi = NULL;
+ if (sep->se_maxperip < 0)
+ sep->se_maxperip = maxperip;
if (sep->se_maxcpm < 0)
sep->se_maxcpm = maxcpm;
if (sep->se_maxchild < 0) { /* apply default max-children */
@@ -1890,6 +1938,8 @@ more:
}
while (argc <= MAXARGV)
sep->se_argv[argc++] = NULL;
+ for (i = 0; i < PERIPSIZE; ++i)
+ LIST_INIT(&sep->se_conn[i]);
#ifdef IPSEC
sep->se_policy = policy ? newstr(policy) : NULL;
#endif
@@ -1920,6 +1970,7 @@ freeconfig(struct servtab *cp)
for (i = 0; i < MAXARGV; i++)
if (cp->se_argv[i])
free(cp->se_argv[i]);
+ free_connlist(cp);
#ifdef IPSEC
if (cp->se_policy)
free(cp->se_policy);
@@ -2253,3 +2304,232 @@ cpmip(const struct servtab *sep, int ctrl)
}
return(r);
}
+
+static struct conninfo *
+search_conn(struct servtab *sep, int ctrl)
+{
+ struct sockaddr_storage ss;
+ socklen_t sslen = sizeof(ss);
+ struct conninfo *conn;
+ int hv;
+ char pname[NI_MAXHOST], pname2[NI_MAXHOST];
+
+ if (sep->se_maxperip <= 0)
+ return NULL;
+
+ /*
+ * If getpeername() fails, just let it through (if logging is
+ * enabled the condition is caught elsewhere)
+ */
+ if (getpeername(ctrl, (struct sockaddr *)&ss, &sslen) != 0)
+ return NULL;
+
+ switch (ss.ss_family) {
+ case AF_INET:
+ hv = hashval((char *)&((struct sockaddr_in *)&ss)->sin_addr,
+ sizeof(struct in_addr));
+ break;
+#ifdef INET6
+ case AF_INET6:
+ hv = hashval((char *)&((struct sockaddr_in6 *)&ss)->sin6_addr,
+ sizeof(struct in6_addr));
+ break;
+#endif
+ default:
+ /*
+ * Since we only support AF_INET and AF_INET6, just
+ * let other than AF_INET and AF_INET6 through.
+ */
+ return NULL;
+ }
+
+ if (getnameinfo((struct sockaddr *)&ss, sslen, pname, sizeof(pname),
+ NULL, 0, NI_NUMERICHOST | NI_WITHSCOPEID) != 0)
+ return NULL;
+
+ LIST_FOREACH(conn, &sep->se_conn[hv], co_link) {
+ if (getnameinfo((struct sockaddr *)&conn->co_addr,
+ conn->co_addr.ss_len, pname2, sizeof(pname2), NULL, 0,
+ NI_NUMERICHOST | NI_WITHSCOPEID) == 0 &&
+ strcmp(pname, pname2) == 0)
+ break;
+ }
+
+ if (conn == NULL) {
+ if ((conn = malloc(sizeof(struct conninfo))) == NULL) {
+ syslog(LOG_ERR, "malloc: %m");
+ exit(EX_OSERR);
+ }
+ conn->co_proc = malloc(sep->se_maxperip * sizeof(*conn->co_proc));
+ if (conn->co_proc == NULL) {
+ syslog(LOG_ERR, "malloc: %m");
+ exit(EX_OSERR);
+ }
+ memcpy(&conn->co_addr, (struct sockaddr *)&ss, sslen);
+ conn->co_numchild = 0;
+ LIST_INSERT_HEAD(&sep->se_conn[hv], conn, co_link);
+ }
+
+ /*
+ * Since a child process is not invoked yet, we cannot
+ * determine a pid of a child. So, co_proc and co_numchild
+ * should be filled leter.
+ */
+
+ return conn;
+}
+
+static int
+room_conn(struct servtab *sep, struct conninfo *conn)
+{
+ char pname[NI_MAXHOST];
+
+ if (conn->co_numchild >= sep->se_maxperip) {
+ getnameinfo((struct sockaddr *)&conn->co_addr,
+ conn->co_addr.ss_len, pname, sizeof(pname), NULL, 0,
+ NI_NUMERICHOST | NI_WITHSCOPEID);
+ syslog(LOG_ERR, "%s from %s exceeded counts (limit %d)",
+ sep->se_service, pname, sep->se_maxperip);
+ return 0;
+ }
+ return 1;
+}
+
+static void
+addchild_conn(struct conninfo *conn, pid_t pid)
+{
+ struct procinfo *proc;
+
+ if (conn == NULL)
+ return;
+
+ if ((proc = search_proc(pid, 1)) != NULL) {
+ if (proc->pr_conn != NULL) {
+ syslog(LOG_ERR,
+ "addchild_conn: child already on process list");
+ exit(EX_OSERR);
+ }
+ proc->pr_conn = conn;
+ }
+
+ conn->co_proc[conn->co_numchild++] = proc;
+}
+
+static void
+reapchild_conn(pid_t pid)
+{
+ struct procinfo *proc;
+ struct conninfo *conn;
+ int i;
+
+ if ((proc = search_proc(pid, 0)) == NULL)
+ return;
+ if ((conn = proc->pr_conn) == NULL)
+ return;
+ for (i = 0; i < conn->co_numchild; ++i)
+ if (conn->co_proc[i] == proc) {
+ conn->co_proc[i] = conn->co_proc[--conn->co_numchild];
+ break;
+ }
+ free_proc(proc);
+ free_conn(conn);
+}
+
+static void
+resize_conn(struct servtab *sep, int maxperip)
+{
+ struct conninfo *conn;
+ int i, j;
+
+ if (sep->se_maxperip <= 0)
+ return;
+ if (maxperip <= 0) {
+ free_connlist(sep);
+ return;
+ }
+ for (i = 0; i < PERIPSIZE; ++i) {
+ LIST_FOREACH(conn, &sep->se_conn[i], co_link) {
+ for (j = maxperip; j < conn->co_numchild; ++j)
+ free_proc(conn->co_proc[j]);
+ conn->co_proc = realloc(conn->co_proc,
+ maxperip * sizeof(*conn->co_proc));
+ if (conn->co_proc == NULL) {
+ syslog(LOG_ERR, "realloc: %m");
+ exit(EX_OSERR);
+ }
+ if (conn->co_numchild > maxperip)
+ conn->co_numchild = maxperip;
+ }
+ }
+}
+
+static void
+free_connlist(struct servtab *sep)
+{
+ struct conninfo *conn;
+ int i, j;
+
+ for (i = 0; i < PERIPSIZE; ++i) {
+ while ((conn = LIST_FIRST(&sep->se_conn[i])) != NULL) {
+ for (j = 0; j < conn->co_numchild; ++j)
+ free_proc(conn->co_proc[j]);
+ conn->co_numchild = 0;
+ free_conn(conn);
+ }
+ }
+}
+
+static void
+free_conn(struct conninfo *conn)
+{
+ if (conn == NULL)
+ return;
+ if (conn->co_numchild <= 0) {
+ LIST_REMOVE(conn, co_link);
+ free(conn->co_proc);
+ free(conn);
+ }
+}
+
+static struct procinfo *
+search_proc(pid_t pid, int add)
+{
+ struct procinfo *proc;
+ int hv;
+
+ hv = hashval((char *)&pid, sizeof(pid));
+ LIST_FOREACH(proc, &proctable[hv], pr_link) {
+ if (proc->pr_pid == pid)
+ break;
+ }
+ if (proc == NULL && add) {
+ if ((proc = malloc(sizeof(struct procinfo))) == NULL) {
+ syslog(LOG_ERR, "malloc: %m");
+ exit(EX_OSERR);
+ }
+ proc->pr_pid = pid;
+ proc->pr_conn = NULL;
+ LIST_INSERT_HEAD(&proctable[hv], proc, pr_link);
+ }
+ return proc;
+}
+
+static void
+free_proc(struct procinfo *proc)
+{
+ if (proc == NULL)
+ return;
+ LIST_REMOVE(proc, pr_link);
+ free(proc);
+}
+
+static int
+hashval(char *p, int len)
+{
+ int i, hv = 0xABC3D20F;
+
+ for (i = 0; i < len; ++i, ++p)
+ hv = (hv << 5) ^ (hv >> 23) ^ *p;
+ hv = (hv ^ (hv >> 16)) & (PERIPSIZE - 1);
+ return hv;
+}
diff --git a/usr.sbin/inetd/inetd.h b/usr.sbin/inetd/inetd.h
index 1e0f088..3293364 100644
--- a/usr.sbin/inetd/inetd.h
+++ b/usr.sbin/inetd/inetd.h
@@ -36,6 +36,7 @@
#include <sys/time.h>
#include <sys/socket.h>
#include <sys/un.h>
+#include <sys/queue.h>
#include <netinet/in.h>
@@ -54,6 +55,21 @@
#define ISMUXPLUS(sep) ((sep)->se_type == MUXPLUS_TYPE)
#define ISTTCP(sep) ((sep)->se_type == TTCP_TYPE)
+struct procinfo {
+ LIST_ENTRY(procinfo) pr_link;
+ pid_t pr_pid; /* child pid */
+ struct conninfo *pr_conn;
+};
+
+struct conninfo {
+ LIST_ENTRY(conninfo) co_link;
+ struct sockaddr_storage co_addr; /* source address */
+ int co_numchild; /* current number of children */
+ struct procinfo **co_proc; /* array of child proc entry */
+};
+
+#define PERIPSIZE 256
+
struct servtab {
char *se_service; /* name of service */
int se_socktype; /* type of socket to use */
@@ -105,6 +121,8 @@ struct servtab {
u_int se_nomapped : 1;
u_int se_reset : 1;
} se_flags;
+ int se_maxperip; /* max number of children per src */
+ LIST_HEAD(, conninfo) se_conn[PERIPSIZE];
};
#define se_nomapped se_flags.se_nomapped
OpenPOWER on IntegriCloud