Index: usr.sbin/inetd/inetd.8 diff -u usr.sbin/inetd/inetd.8.orig usr.sbin/inetd/inetd.8 --- usr.sbin/inetd/inetd.8.orig Thu Jun 13 05:38:37 2002 +++ usr.sbin/inetd/inetd.8 Thu Jun 20 16:52:22 2002 @@ -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 @@ -103,6 +104,12 @@ 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, @@ -156,7 +163,7 @@ 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 Index: usr.sbin/inetd/inetd.c diff -u usr.sbin/inetd/inetd.c.orig usr.sbin/inetd/inetd.c --- usr.sbin/inetd/inetd.c.orig Thu Jun 13 05:38:40 2002 +++ usr.sbin/inetd/inetd.c Thu Jun 20 16:53:29 2002 @@ -195,6 +195,12 @@ < 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 @@ -228,6 +234,11 @@ void ipsecsetup __P((struct servtab *)); #endif void unregisterrpc __P((register struct servtab *sep)); +static struct conninfo *search_ip __P((struct servtab *sep, int ctrl)); +static int room_ip __P((struct servtab *sep, struct conninfo *conn)); +static void addchild_ip __P((struct servtab *sep, struct conninfo *conn, + int ctrl, pid_t pid)); +static void reapchild_ip __P((pid_t pid)); int allow_severity; int deny_severity; @@ -242,6 +253,7 @@ int toomany = TOOMANY; int maxchild = MAXCHILD; int maxcpm = MAXCPM; +int maxperip = MAXPERIP; struct servent *sp; struct rpcent *rpc; char *hostname = NULL; @@ -316,10 +328,11 @@ 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; @@ -346,6 +359,10 @@ 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; @@ -545,6 +562,7 @@ n--; if (debug) warnx("someone wants %s", sep->se_service); + conn = NULL; if (sep->se_accept && sep->se_socktype == SOCK_STREAM) { i = 1; if (ioctl(sep->se_fd, FIONBIO, &i) < 0) @@ -572,6 +590,11 @@ close(ctrl); continue; } + if ((conn = search_ip(sep, ctrl)) != NULL && + !room_ip(sep, conn)) { + close(ctrl); + continue; + } } else ctrl = sep->se_fd; if (log && !ISWRAP(sep)) { @@ -648,8 +671,10 @@ sleep(1); continue; } - if (pid) + if (pid) { + addchild_ip(sep, conn, ctrl, pid); addchild(sep, pid); + } sigsetmask(0L); if (pid == 0) { if (dofork) { @@ -879,6 +904,7 @@ sep->se_server, pid, status); break; } + reapchild_ip(pid); } } @@ -893,6 +919,7 @@ config() { struct servtab *sep, *new, **sepp; + struct conninfo *conn; long omask; if (!setconfig()) { @@ -947,9 +974,28 @@ new->se_numchild * sizeof(*new->se_pids)); } SWAP(sep->se_pids, new->se_pids); + if (new->se_maxperip > 0) { + LIST_FOREACH(conn, &sep->se_conn, co_link) { + if (conn->co_numchild > new->se_maxperip) + conn->co_numchild = new->se_maxperip; + conn->co_pids = realloc(conn->co_pids, + new->se_maxperip * sizeof(*conn->co_pids)); + if (conn->co_pids == NULL) { + syslog(LOG_ERR, "realloc: %m"); + exit(EX_OSERR); + } + } + } else { + while ((conn = LIST_FIRST(&sep->se_conn)) != NULL) { + LIST_REMOVE(conn, co_link); + free(conn->co_pids); + free(conn); + } + } sep->se_maxchild = new->se_maxchild; sep->se_numchild = new->se_numchild; sep->se_maxcpm = new->se_maxcpm; + 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) { @@ -1736,6 +1782,7 @@ } sep->se_maxchild = -1; sep->se_maxcpm = -1; + sep->se_maxperip = -1; if ((s = strchr(arg, '/')) != NULL) { char *eptr; u_long val; @@ -1754,6 +1801,8 @@ 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 @@ -1811,6 +1860,8 @@ 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 */ @@ -1840,6 +1891,7 @@ } while (argc <= MAXARGV) sep->se_argv[argc++] = NULL; + LIST_INIT(&sep->se_conn); #ifdef IPSEC sep->se_policy = policy ? newstr(policy) : NULL; #endif @@ -1851,6 +1903,7 @@ struct servtab *cp; { int i; + struct conninfo *conn; if (cp->se_service) free(cp->se_service); @@ -1871,6 +1924,11 @@ for (i = 0; i < MAXARGV; i++) if (cp->se_argv[i]) free(cp->se_argv[i]); + while ((conn = LIST_FIRST(&cp->se_conn)) != NULL) { + LIST_REMOVE(conn, co_link); + free(conn->co_pids); + free(conn); + } #ifdef IPSEC if (cp->se_policy) free(cp->se_policy); @@ -2215,4 +2273,138 @@ } } return(r); +} + +static struct conninfo * +search_ip(struct servtab *sep, int ctrl) +{ + struct conninfo *conn; + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + 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 *)&addr, &len) != 0) + return NULL; + + switch (addr.ss_family) { + case AF_INET: +#ifdef INET6 + case AF_INET6: +#endif + break; + default: + /* + * Since we don't support AF_INET and AF_INET6, just + * let it through. + */ + return NULL; + } + + if (getnameinfo((struct sockaddr *)&addr, len, + pname, sizeof(pname), NULL, 0, + NI_NUMERICHOST | NI_WITHSCOPEID) != 0) + return NULL; + + LIST_FOREACH(conn, &sep->se_conn, 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; + } + + return conn; + } + +static int +room_ip(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_ip(struct servtab *sep, struct conninfo *conn, int ctrl, pid_t pid) +{ + struct sockaddr_storage addr; + socklen_t len = sizeof(addr); + + if (sep->se_maxperip <= 0) + return; + + if (!sep->se_accept || sep->se_socktype != SOCK_STREAM) + return; + + if (conn == NULL) { + if (getpeername(ctrl, (struct sockaddr *)&addr, &len) != 0) + return; + switch (addr.ss_family) { + case AF_INET: +#ifdef INET6 + case AF_INET6: +#endif + break; + default: + return; + } + + conn = malloc(sizeof(struct conninfo)); + if (conn == NULL) { + syslog(LOG_ERR, "malloc: %m"); + exit(EX_OSERR); + } + conn->co_pids = malloc(sep->se_maxperip * sizeof(*conn->co_pids)); + if (conn->co_pids == NULL) { + syslog(LOG_ERR, "malloc: %m"); + exit(EX_OSERR); + } + memcpy(&conn->co_addr, &addr, len); + conn->co_numchild = 0; + LIST_INSERT_HEAD(&sep->se_conn, conn, co_link); + } + + if (conn != NULL) + conn->co_pids[conn->co_numchild++] = pid; +} + +static void +reapchild_ip(pid_t pid) +{ + struct servtab *sep; + struct conninfo *conn; + int k; + + for (sep = servtab; sep; sep = sep->se_next) { + LIST_FOREACH(conn, &sep->se_conn, co_link) { + for (k = 0; k < conn->co_numchild; k++) + if (conn->co_pids[k] == pid) { + if (conn->co_numchild > 0) + conn->co_pids[k] = conn->co_pids[--conn->co_numchild]; + if (conn->co_numchild <= 0) { + LIST_REMOVE(conn, co_link); + free(conn->co_pids); + free(conn); + } + return; + } + } + } } Index: usr.sbin/inetd/inetd.h diff -u usr.sbin/inetd/inetd.h.orig usr.sbin/inetd/inetd.h --- usr.sbin/inetd/inetd.h.orig Thu Jun 13 05:38:40 2002 +++ usr.sbin/inetd/inetd.h Thu Jun 20 16:52:22 2002 @@ -36,6 +36,7 @@ #include #include #include +#include #include @@ -54,6 +55,13 @@ #define ISMUXPLUS(sep) ((sep)->se_type == MUXPLUS_TYPE) #define ISTTCP(sep) ((sep)->se_type == TTCP_TYPE) +struct conninfo { + LIST_ENTRY(conninfo) co_link; + struct sockaddr_storage co_addr; /* source address */ + int co_numchild; /* current number of children */ + pid_t *co_pids; /* array of child pids */ +}; + struct servtab { char *se_service; /* name of service */ int se_socktype; /* type of socket to use */ @@ -105,6 +113,8 @@ 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; }; #define se_nomapped se_flags.se_nomapped