* FIXME: ipset create bouncer4 hash:ip family inet
* FIXME: ipset create bouncer6 hash:ip family inet6
* FIXME: sshd_config: UseDNS no
+ * FIXME: PAM module names should be made configurable
+ * FIXME: Parse "N more failures" messages
*/
#define LOCAL_DEBUG
#include <ucw/lib.h>
#include <ucw/clists.h>
#include <ucw/conf.h>
+#include <ucw/log.h>
#include <ucw/mainloop.h>
#include <ucw/opt.h>
#include <ucw/string.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
+#include <sys/types.h>
#include <sys/socket.h>
+#include <sys/stat.h>
#include <sys/un.h>
#include <time.h>
#include <unistd.h>
/*** Configuration ***/
+static char *listen_on = "/var/run/bouncer.sock";
static uns max_failures = ~0U;
static uns max_suspect_time = 86400;
static uns max_banned_time = 86400;
static uns max_suspects = ~0U;
static uns max_banned = ~0U;
-
-/*
- * FIXME:
- * - names of ipsets
- * - name of socket
- * - logging
- * - PAM module name(s) to match
- */
+static char *ipv4_set;
+static char *ipv6_set;
+static char *config_log_stream;
static struct cf_section bouncer_cf = {
CF_ITEMS {
+ CF_STRING("ListenOn", &listen_on),
CF_UNS("MaxSuspects", &max_suspects),
CF_UNS("MaxBanned", &max_banned),
CF_UNS("MaxSuspectTime", &max_suspect_time),
CF_UNS("MaxBannedTime", &max_banned_time),
CF_UNS("MaxFailures", &max_failures),
+ CF_STRING("IPv4Set", &ipv4_set),
+ CF_STRING("IPv6Set", &ipv6_set),
+ CF_STRING("LogStream", &config_log_stream),
CF_END
}
};
static struct ipset_session *is_sess;
-enum is_index {
- IS_IPV4,
- IS_IPV6,
-};
-
-static const char * const is_names[] = {
- "bouncer4",
- "bouncer6",
-};
-
static const char *trim_eol(const char *msg)
{
int len = strlen(msg);
die("Unable to initialize ipset session");
}
-static void is_setup(int set)
+static bool is_setup(char *set)
{
- if (ipset_parse_setname(is_sess, IPSET_SETNAME, is_names[set]) < 0)
+ if (!set)
+ return 0;
+
+ if (ipset_parse_setname(is_sess, IPSET_SETNAME, set) < 0)
is_die("ipset_parse_setname");
+ return 1;
}
-static void is_flush(int set)
+static void is_flush(char *set)
{
- is_setup(set);
+ if (!is_setup(set))
+ return;
if (ipset_cmd(is_sess, IPSET_CMD_FLUSH, 0) < 0)
return is_err("IPSET_CMD_FLUSH");
}
-static void is_modify(bool add, struct addr addr)
+static bool is_modify(bool add, struct addr addr)
{
- is_setup(addr_is_v4(addr) ? IS_IPV4 : IS_IPV6);
int cmd = add ? IPSET_CMD_ADD : IPSET_CMD_DEL;
+ char *set = addr_is_v4(addr) ? ipv4_set : ipv6_set;
+ if (!is_setup(set))
+ return 0;
if (ipset_envopt_parse(is_sess, IPSET_ENV_EXIST, NULL) < 0)
is_die("IPSET_ENV_EXIST");
char buf[ADDR_BUFSIZE];
addr_format(buf, addr);
if (ipset_parse_elem(is_sess, 0, buf) < 0)
- return is_err("ipset_parse_elem");
+ {
+ is_err("ipset_parse_elem");
+ return 0;
+ }
if (ipset_cmd(is_sess, cmd, 0) < 0)
- return is_err(add ? "IPSET_CMD_ADD" : "IPSET_CMD_DEL");
+ {
+ is_err(add ? "IPSET_CMD_ADD" : "IPSET_CMD_DEL");
+ return 0;
+ }
+
+ return 1;
}
/*** Handling of login failures ***/
timestamp_t expire_in = c->last_fail + max_time - now;
if (*counter > max_count)
{
- // FIXME: Warn with rate limit
+ static timestamp_t last_overflow_warning;
+ if (last_overflow_warning + 60000 < now)
+ {
+ last_overflow_warning = now;
+ if (c->banned)
+ msg(L_WARN, "Too many bans, dropping some. Try increasing MaxBanned.");
+ else
+ msg(L_WARN, "Too many suspects, dropping some. Try increasing MaxSuspects.");
+ }
expire_in = 0;
}
if (c->banned)
{
- DBG("%s: unbanned", AFMT(c->addr));
+ msg(L_INFO, "Unbanning %s", AFMT(c->addr));
is_modify(0, c->addr);
}
else
- {
- DBG("%s: removed from LRU", AFMT(c->addr));
- }
+ msg(L_DEBUG, "Suspect %s: acquitted", AFMT(c->addr));
clist_remove(&c->n);
culprit_remove(c);
c->banned = 0;
clist_add_tail(&suspect_list, &c->n);
num_suspects++;
- DBG("%s: first fail", AFMT(addr));
+ msg(L_DEBUG, "Suspect %s: new", AFMT(addr));
}
else if (!c->banned)
{
c->fail_count++;
clist_remove(&c->n);
clist_add_tail(&suspect_list, &c->n);
- DBG("%s: next fail, cnt=%u", AFMT(addr), c->fail_count);
+ msg(L_DEBUG, "Suspect %s: failures=%u", AFMT(addr), c->fail_count);
}
if (!c->banned && c->fail_count >= max_failures)
{
- DBG("%s: banned", AFMT(addr));
+ msg(L_INFO, "Banning %s: failures=%u", AFMT(addr), c->fail_count);
c->banned = 1;
clist_remove(&c->n);
num_suspects--;
return;
DBG("Parse 2: <%s>", p);
- // pam_unix(something), colon, space (FIXME: make configurable)
+ // pam_unix(something), colon, space
if (!check_next(&p, "pam_unix("))
return;
do
/*** Socket for receiving messages from rsyslog ***/
-static const char sk_name[] = "/var/run/bouncer.sock";
struct main_file sk_file;
static int sk_read(struct main_file *mf)
static void sk_init(void)
{
- unlink(sk_name);
+ unlink(listen_on);
+ mode_t old_umask = umask(0077);
int fd;
if ((fd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0)
die("Cannot create PF_UNIX socket: %m");
struct sockaddr_un sa = { .sun_family = AF_UNIX };
- strcpy(sa.sun_path, sk_name);
+ strcpy(sa.sun_path, listen_on);
if (bind(fd, (struct sockaddr *) &sa, sizeof(sa)) < 0)
- die("Cannot bind socket %s: %m", sk_name);
-
- // FIXME: Permissions
+ die("Cannot bind socket %s: %m", listen_on);
sk_file.fd = fd;
sk_file.read_handler = sk_read;
file_add(&sk_file);
+
+ umask(old_umask);
}
/*** Main ***/
cf_declare_section("Bouncer", &bouncer_cf, 0);
opt_parse(&options, argv+1);
+ if (config_log_stream)
+ log_configured(config_log_stream);
+
main_init();
is_init();
fail_init();
-
- // FIXME msg(L_INFO, "Clearing previous state");
- is_flush(IS_IPV4);
- is_flush(IS_IPV6);
-
sk_init();
+ is_flush(ipv4_set);
+ is_flush(ipv6_set);
+
+ msg(L_INFO, "Starting");
main_loop();
return 0;
}