From a37a83f6143e672a71ee4436fa24aaa2f9c81877 Mon Sep 17 00:00:00 2001 From: Sergey Poznyakoff Date: Wed, 23 Dec 2009 23:51:39 +0200 Subject: Limit number of connections per socket (IP). * src/inetd-bi.c (fd_write): Remove. Use fd_report instead. * src/pies.c (component_keywords): New keywords: max-instances-message, max-ip-connections, max-ip-connections-message, access-denied-message. * src/pies.h (struct component): New members: max_ip_connections, access_denied_message, max_instances_message, max_ip_connections_message. (fd_report): New extern. * src/progman.c (conn_class): New struct. (struct prog.p): New member cclass. (conn_tab): New static. (conn_class_lookup, conn_class_report): New functions. (progman_run_comp): Set cclass. (fd_report): New function. (_prog_accept): In case of failure (access denied, etc.) optionally send response strings over the fd. Limit number of connections per socket (IP). (progman_cleanup): Update cclass counter. --- src/inetd-bi.c | 21 +++----- src/pies.c | 25 +++++++++ src/pies.h | 8 +++ src/progman.c | 161 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 199 insertions(+), 16 deletions(-) (limited to 'src') diff --git a/src/inetd-bi.c b/src/inetd-bi.c index d002e4c..b771936 100644 --- a/src/inetd-bi.c +++ b/src/inetd-bi.c @@ -295,12 +295,6 @@ fd_getline (int fd, char *buf, int len) return count; } -static int -fd_write (int fd, const char *text) -{ - return write (fd, text, strlen (text)); -} - static int tcpmux_help (struct component *comp, void *data) { @@ -308,8 +302,8 @@ tcpmux_help (struct component *comp, void *data) if (!(comp->flags & CF_DISABLED) && ISCF_TCPMUX (comp->flags)) { - fd_write (*pfd, comp->service); - fd_write (*pfd, "\r\n"); + fd_report (*pfd, comp->service); + fd_report (*pfd, "\r\n"); } return 0; } @@ -327,7 +321,7 @@ tcpmux (int fd, struct component const *comp) /* Read service name */ if ((len = fd_getline (fd, service, MAX_SERV_LEN)) < 0) { - fd_write (fd, "-Error reading service name\r\n"); + fd_report (fd, "-Error reading service name\r\n"); return; } service[len] = 0; @@ -343,7 +337,7 @@ tcpmux (int fd, struct component const *comp) srv_comp = progman_lookup_tcpmux (service, comp->tag); if (!srv_comp) { - fd_write (fd, "-Service not available\r\n"); + fd_report (fd, "-Service not available\r\n"); return; } @@ -356,19 +350,20 @@ tcpmux (int fd, struct component const *comp) { if (rc) { - fd_write (fd, "-Service not available\r\n"); + fd_report (fd, "-Service not available\r\n"); return; } if (check_acl (comp->acl, (struct sockaddr *) &sa, salen)) { - fd_write (fd, "-Service not available\r\n"); + fd_report (fd, "-Service not available\r\n"); return; } } + /* FIXME: What about max-instances, etc.? */ if (srv_comp->flags & CF_TCPMUXPLUS) - fd_write (fd, "+Go\r\n"); + fd_report (fd, "+Go\r\n"); progman_run_comp (srv_comp, fd, &sa, salen); } diff --git a/src/pies.c b/src/pies.c index 91fabca..29f3ed0 100644 --- a/src/pies.c +++ b/src/pies.c @@ -988,6 +988,25 @@ struct grecs_keyword component_keywords[] = { grecs_type_size, NULL, offsetof (struct component, max_instances), NULL }, + {"max-instances-message", + NULL, + N_("Text to send back if max-instances is reached (inetd-components only)."), + grecs_type_string, NULL, + offsetof (struct component, max_instances_message), + NULL }, + {"max-ip-connections", + NULL, + N_("Maximum number of simultaneous connections per IP address (inetd only)."), + grecs_type_size, NULL, + offsetof (struct component, max_ip_connections), + NULL }, + {"max-ip-connections-message", + NULL, + N_("Text to send back if max-ip-connections-message is reached (inetd only)."), + grecs_type_string, NULL, + offsetof (struct component, max_ip_connections_message), + NULL }, + {"max-rate", NULL, N_("Maximum number of times an inetd component can be invoked in one minute."), @@ -1020,6 +1039,12 @@ struct grecs_keyword component_keywords[] = { N_("Set ACL."), grecs_type_section, NULL, offsetof (struct component, acl), acl_section_parser, NULL, acl_keywords}, + {"access-denied-message", + NULL, + N_("Text to send back if access is denied (inetd-components only)."), + grecs_type_string, NULL, + offsetof (struct component, access_denied_message), + NULL }, {"remove-file", N_("file"), N_("Remove file before starting the component."), diff --git a/src/pies.h b/src/pies.h index d6347ce..25db08a 100644 --- a/src/pies.h +++ b/src/pies.h @@ -177,6 +177,7 @@ struct component /* For inetd components */ size_t max_rate; /* Maximum number of invocations per minute */ + size_t max_ip_connections; /* Max. number of connections per IP address */ int socket_type; /* Socket type */ struct inetd_builtin *builtin; /* Builtin function */ char *service; @@ -189,6 +190,12 @@ struct component become available. */ pies_acl_t acl; char *tcpmux; /* Master service for TCPMUX */ + + /* Optional error messages to be sent back on the socket: */ + char *access_denied_message; + char *max_instances_message; + char *max_ip_connections_message; + /* Redirectors: */ int facility; /* Syslog facility. */ struct redirector redir[2]; /* Repeaters for stdout and stderr */ @@ -239,6 +246,7 @@ void progman_run_comp (struct component *comp, int fd, void progman_iterate_comp (int (*fun) (struct component *, void *), void *data); +void fd_report (int fd, const char *msg); int check_acl (pies_acl_t acl, struct sockaddr *s, socklen_t salen); diff --git a/src/progman.c b/src/progman.c index 9143b99..5e8d331 100644 --- a/src/progman.c +++ b/src/progman.c @@ -15,6 +15,7 @@ along with GNU Pies. If not, see . */ #include "pies.h" +#include enum prog_type { @@ -34,6 +35,14 @@ enum prog_status status_stopping, /* Component is being stopped */ }; +struct conn_class +{ + const char *tag; + union pies_sockaddr_storage sa_storage; + size_t sa_len; + size_t count; +}; + struct prog { struct prog *next, *prev; @@ -59,6 +68,7 @@ struct prog struct prog *listener; union pies_sockaddr_storage sa_storage; size_t sa_len; + struct conn_class *cclass; } p; struct @@ -79,6 +89,7 @@ static size_t numcomp; static struct prog *proghead, *progtail; static pies_depmap_t depmap; static int recompute_alarm; +static Hash_table *conn_tab; void progman_iterate_comp (int (*fun) (struct component *, void *), void *data) @@ -510,6 +521,96 @@ open_redirector (struct prog *master, int stream) } +static size_t +conn_class_hasher (void const *data, size_t n_buckets) +{ + struct conn_class const *pcclass = data; + unsigned char const *tag = (unsigned char const *)pcclass->tag; + size_t value = 0; + unsigned char ch; + size_t len; + + while ((ch = *tag++)) + value = (value * 31 + ch) % n_buckets; + + for (tag = (const unsigned char *)&pcclass->sa_storage, + len = pcclass->sa_len; + len; + tag++, len--) + value = (value * 31 + *tag) % n_buckets; + return value; +} + +/* Compare two strings for equality. */ +static bool +conn_class_compare (void const *data1, void const *data2) +{ + struct conn_class const *p1 = data1; + struct conn_class const *p2 = data2; + + return p1->sa_len == p2->sa_len && + memcmp (&p1->sa_storage, &p2->sa_storage, p1->sa_len) == 0 && + strcmp (p1->tag, p2->tag) == 0; +} + +static void +conn_class_free (void *data) +{ + free (data); +} + +static struct conn_class * +conn_class_lookup (const char *tag, + union pies_sockaddr_storage const *sa_storage_ptr, + size_t sa_len) +{ + struct conn_class *probe, *ret; + + probe = xmalloc (sizeof (probe[0])); + probe->tag = tag; + probe->sa_storage = *sa_storage_ptr; + probe->sa_len = sa_len; + switch (probe->sa_storage.s.sa_family) + { + case AF_INET: + probe->sa_storage.s_in.sin_port = 0; + break; + + case AF_UNIX: + break; + + default: + logmsg (LOG_ERR, _("unexpected socket family: %d"), + probe->sa_storage.s.sa_family); + break; + } + + probe->count = 0; + + if (!((conn_tab + || (conn_tab = hash_initialize (0, 0, + conn_class_hasher, + conn_class_compare, + conn_class_free))) + && (ret = hash_insert (conn_tab, probe)))) + xalloc_die (); + + if (probe != ret) + free (probe); + return ret; +} + +static void +conn_class_report (struct conn_class *pcclass) +{ + char *s = sockaddr_to_astr ((struct sockaddr *)&pcclass->sa_storage, + pcclass->sa_len); + logmsg (LOG_DEBUG, _("connections in class %s/%s: %lu"), + pcclass->tag, s, (unsigned long)pcclass->count); + free (s); +} + + extern char **environ; /* Environment */ static size_t envsize; /* Size of environ. If it is not 0, then we have allocated space for the environ. */ @@ -963,6 +1064,7 @@ progman_run_comp (struct component *comp, int fd, prog->v.p.socket = fd; prog->v.p.sa_storage = *sa; prog->v.p.sa_len = salen; + prog->v.p.cclass = conn_class_lookup (comp->tag, sa, salen); prog_start_prologue (prog); prog_sockenv (prog); prog_execute (prog); @@ -1149,6 +1251,31 @@ check_acl (pies_acl_t acl, struct sockaddr *s, socklen_t salen) return 0; } +void +fd_report (int fd, const char *msg) +{ + size_t len; + + if (!msg) + return; + + for (len = strlen (msg); len; ) + { + ssize_t rc = write (fd, msg, len); + if (rc == -1) + { + logmsg (LOG_ERR, + _("error writing to socket: %s"), + strerror (errno)); + break; + } + else if (rc == 0) + break; + len -= rc; + msg += rc; + } +} + static int _prog_accept (struct prog *p) { @@ -1156,7 +1283,8 @@ _prog_accept (struct prog *p) struct prog *pinst; union pies_sockaddr_storage addr; socklen_t addrlen = sizeof addr; - + struct conn_class *pcclass; + fd = accept (p->v.p.socket, (struct sockaddr*) &addr, &addrlen); if (fd == -1) { @@ -1174,6 +1302,7 @@ _prog_accept (struct prog *p) if (check_acl (p->v.p.comp->acl, (struct sockaddr *)&addr, addrlen) || check_acl (pies_acl, (struct sockaddr *)&addr, addrlen)) { + fd_report (fd, p->v.p.comp->access_denied_message); close (fd); return 1; } @@ -1183,25 +1312,47 @@ _prog_accept (struct prog *p) { char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen); logmsg (LOG_ERR, - _("%s: too many instances running, access from %s denied"), + _("%s: access from %s denied: too many instances running"), p->tag, s); free (s); + fd_report (fd, p->v.p.comp->max_instances_message); close (fd); return 1; } + pcclass = conn_class_lookup (p->tag, &addr, addrlen); + if (p->v.p.comp->max_ip_connections && + pcclass->count >= p->v.p.comp->max_ip_connections) + { + char *s = sockaddr_to_astr ((struct sockaddr *)&addr, addrlen); + logmsg (LOG_ERR, + _("%s: access from %s denied: " + "too many connections from that ip"), + p->tag, s); + free (s); + fd_report (fd, p->v.p.comp->max_ip_connections_message); + close (fd); + return 1; + } + if (check_connection_rate (p)) { disable_socket (p->v.p.socket); close (fd); return 1; } - + + pcclass->count++; + + if (debug_level > 1) + conn_class_report (pcclass); + pinst = register_prog0 (p->v.p.comp, -1); pinst->v.p.socket = fd; pinst->v.p.listener = p; pinst->v.p.sa_storage = addr; pinst->v.p.sa_len = addrlen; + pinst->v.p.cclass = pcclass; prog_start (pinst); close (fd); pinst->v.p.socket = -1; @@ -2142,6 +2293,10 @@ progman_cleanup (int expect_term) struct prog *listener = prog->v.p.listener; listener->v.p.num_instances--; + prog->v.p.cclass->count--; + if (debug_level > 1) + conn_class_report (prog->v.p.cclass); + prog_stop_redirectors (prog); destroy_prog (&prog); react (listener, status, pid); -- cgit v1.2.1