diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..6753b35
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+endlessh
+
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 0000000..a819488
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,15 @@
+FROM alpine:3.9 as builder
+RUN apk add --no-cache build-base
+ADD endlessh.c Makefile /
+RUN make
+
+
+FROM alpine:3.9
+
+COPY --from=builder /endlessh /
+
+EXPOSE 2222/tcp
+
+ENTRYPOINT ["/endlessh"]
+
+CMD ["-v"]
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..119347a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,21 @@
+.POSIX:
+CC = cc
+CFLAGS = -std=c99 -Wall -Wextra -Wno-missing-field-initializers -Os
+CPPFLAGS =
+LDFLAGS = -ggdb3
+LDLIBS =
+PREFIX = /usr/local
+
+all: endlessh
+
+endlessh: endlessh.c
+ $(CC) $(CFLAGS) $(CPPFLAGS) $(LDFLAGS) -o $@ endlessh.c $(LDLIBS)
+
+install: endlessh
+ install -d $(DESTDIR)$(PREFIX)/bin
+ install -m 755 endlessh $(DESTDIR)$(PREFIX)/bin/
+ install -d $(DESTDIR)$(PREFIX)/share/man/man1
+ install -m 644 endlessh.1 $(DESTDIR)$(PREFIX)/share/man/man1/
+
+clean:
+ rm -rf endlessh
diff --git a/README.md b/README.md
index 1e3725b..ff4a2f8 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,143 @@
# aperturessh
+A fork of [EndleSSH: an SSH tarpit](https://github.com/skeeto/endlessh) by skeeto on GitHub.
+
+Modified (hopefully one day) to spit out the lyrics to Still Alive from the Portal credits.
+
+Origial README from the GitHub repo is below:
+
+----
+
+# Endlessh: an SSH tarpit
+
+Endlessh is an SSH tarpit [that *very* slowly sends an endless, random
+SSH banner][np]. It keeps SSH clients locked up for hours or even days
+at a time. The purpose is to put your real SSH server on another port
+and then let the script kiddies get stuck in this tarpit instead of
+bothering a real server.
+
+Since the tarpit is in the banner before any cryptographic exchange
+occurs, this program doesn't depend on any cryptographic libraries. It's
+a simple, single-threaded, standalone C program. It uses `poll()` to
+trap multiple clients at a time.
+
+## Usage
+
+Usage information is printed with `-h`.
+
+```
+Usage: endlessh [-vhs] [-d MS] [-f CONFIG] [-l LEN] [-m LIMIT] [-p PORT]
+ -4 Bind to IPv4 only
+ -6 Bind to IPv6 only
+ -d INT Message millisecond delay [10000]
+ -f Set and load config file [/etc/endlessh/config]
+ -h Print this help message and exit
+ -l INT Maximum banner line length (3-255) [32]
+ -m INT Maximum number of clients [4096]
+ -p INT Listening port [2222]
+ -s Print diagnostics to syslog instead of standard output
+ -v Print diagnostics (repeatable)
+```
+
+Argument order matters. The configuration file is loaded when the `-f`
+argument is processed, so only the options that follow will override the
+configuration file.
+
+By default no log messages are produced. The first `-v` enables basic
+logging and a second `-v` enables debugging logging (noisy). All log
+messages are sent to standard output by default. `-s` causes them to be
+sent to syslog.
+
+ endlessh -v >endlessh.log 2>endlessh.err
+
+A SIGTERM signal will gracefully shut down the daemon, allowing it to
+write a complete, consistent log.
+
+A SIGHUP signal requests a reload of the configuration file (`-f`).
+
+A SIGUSR1 signal will print connections stats to the log.
+
+## Sample Configuration File
+
+The configuration file has similar syntax to OpenSSH.
+
+```
+# The port on which to listen for new SSH connections.
+Port 2222
+
+# The endless banner is sent one line at a time. This is the delay
+# in milliseconds between individual lines.
+Delay 10000
+
+# The length of each line is randomized. This controls the maximum
+# length of each line. Shorter lines may keep clients on for longer if
+# they give up after a certain number of bytes.
+MaxLineLength 32
+
+# Maximum number of connections to accept at a time. Connections beyond
+# this are not immediately rejected, but will wait in the queue.
+MaxClients 4096
+
+# Set the detail level for the log.
+# 0 = Quiet
+# 1 = Standard, useful log messages
+# 2 = Very noisy debugging information
+LogLevel 0
+
+# Set the family of the listening socket
+# 0 = Use IPv4 Mapped IPv6 (Both v4 and v6, default)
+# 4 = Use IPv4 only
+# 6 = Use IPv6 only
+BindFamily 0
+```
+
+## Build issues
+
+Some more esoteric systems require extra configuration when building.
+
+### RHEL 6 / CentOS 6
+
+This system uses a version of glibc older than 2.17 (December 2012), and
+`clock_gettime(2)` is still in librt. For these systems you will need to
+link against librt:
+
+ make LDLIBS=-lrt
+
+### Solaris / illumos
+
+These systems don't include all the necessary functionality in libc and
+the linker requires some extra libraries:
+
+ make CC=gcc LDLIBS='-lnsl -lrt -lsocket'
+
+If you're not using GCC or Clang, also override `CFLAGS` and `LDFLAGS`
+to remove GCC-specific options. For example, on Solaris:
+
+ make CFLAGS=-fast LDFLAGS= LDLIBS='-lnsl -lrt -lsocket'
+
+The feature test macros on these systems isn't reliable, so you may also
+need to use `-D__EXTENSIONS__` in `CFLAGS`.
+
+### OpenBSD
+
+The man page needs to go into a different path for OpenBSD's `man` command:
+
+```
+diff --git a/Makefile b/Makefile
+index 119347a..dedf69d 100644
+--- a/Makefile
++++ b/Makefile
+@@ -14,8 +14,8 @@ endlessh: endlessh.c
+ install: endlessh
+ install -d $(DESTDIR)$(PREFIX)/bin
+ install -m 755 endlessh $(DESTDIR)$(PREFIX)/bin/
+- install -d $(DESTDIR)$(PREFIX)/share/man/man1
+- install -m 644 endlessh.1 $(DESTDIR)$(PREFIX)/share/man/man1/
++ install -d $(DESTDIR)$(PREFIX)/man/man1
++ install -m 644 endlessh.1 $(DESTDIR)$(PREFIX)/man/man1/
+
+ clean:
+ rm -rf endlessh
+```
+
+[np]: https://nullprogram.com/blog/2019/03/22/
\ No newline at end of file
diff --git a/UNLICENSE b/UNLICENSE
new file mode 100644
index 0000000..68a49da
--- /dev/null
+++ b/UNLICENSE
@@ -0,0 +1,24 @@
+This is free and unencumbered software released into the public domain.
+
+Anyone is free to copy, modify, publish, use, compile, sell, or
+distribute this software, either in source code form or as a compiled
+binary, for any purpose, commercial or non-commercial, and by any
+means.
+
+In jurisdictions that recognize copyright laws, the author or authors
+of this software dedicate any and all copyright interest in the
+software to the public domain. We make this dedication for the benefit
+of the public at large and to the detriment of our heirs and
+successors. We intend this dedication to be an overt act of
+relinquishment in perpetuity of all present and future rights to this
+software under copyright law.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
+ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+
+For more information, please refer to
diff --git a/endlessh.1 b/endlessh.1
new file mode 100644
index 0000000..43834df
--- /dev/null
+++ b/endlessh.1
@@ -0,0 +1,84 @@
+.Dd $Mdocdate: January 29 2020 $
+.Dt ENDLESSH 1
+.Os
+.Sh NAME
+.Nm endless
+.Nd An SSH tarpit
+.Sh SYNOPSIS
+.Nm endless
+.Op Fl 46chsvV
+.Op Fl d Ar delay
+.Op Fl f Ar config
+.Op Fl l Ar max banner length
+.Op Fl m Ar max clients
+.Op Fl p Ar port
+.Sh DESCRIPTION
+.Nm
+is an SSH tarpit that very slowly
+sends an endless, random SSH banner.
+.Pp
+.Nm
+keeps SSH clients locked up for hours or even days at a time.
+The purpose is to put your real SSH server on another port
+and then let the script kiddies get stuck in this tarpit
+instead of bothering a real server.
+.Pp
+Since the tarpit is in the banner before any cryptographic
+exchange occurs, this program doesn't depend on any cryptographic
+libraries. It's a simple, single-threaded, standalone C program.
+It uses poll() to trap multiple clients at a time.
+.Pp
+The options are as follows:
+.Bl -tag -width Ds
+.It Fl 4
+Forces
+.Nm
+to use IPv4 addresses only.
+.It Fl 6
+Forces
+.Nm
+to use IPv6 addresses only.
+.It Fl d Ar delay
+Message milliseconds delay. Default: 10000
+.It Fl f Ar config
+Set and load config file.
+By default
+.Nm
+looks for /etc/endlessh/config.
+.It Fl h
+Print the help message and exit.
+.It Fl l Ar max banner length
+Maximum banner line length (3-255). Default: 32
+.It Fl m Ar max clients
+Maximum number of clients. Default: 4096
+.It Fl p Ar port
+Set the listening port. By default
+.Nm
+listens on port 2222.
+.It Fl s
+Print diagnostics to syslog. By default
+.Nm
+prints them to standard output.
+.It Fl v
+Print diagnostics. Can be specified up to twice to increase verbosity.
+.It Fl V
+Causes
+.Nm
+to print version information and exit.
+.El
+.Pp
+If
+.Nm
+receives the SIGTERM signal it will gracefully shut
+down the daemon, allowing it to write a complete, consistent log.
+.Pp
+A SIGHUP signal requests a reload of its configuration file.
+.Pp
+A SIGUSR1 signal will print connections stats to the log.
+.Sh FILES
+.Bl -tag -width /etc/endlessh/config -compact
+.It Pa /etc/endlessh/config
+The default
+.Nm
+configuration file.
+.El
diff --git a/endlessh.c b/endlessh.c
new file mode 100644
index 0000000..b14de73
--- /dev/null
+++ b/endlessh.c
@@ -0,0 +1,847 @@
+/* Endlessh: an SSH tarpit
+ *
+ * This is free and unencumbered software released into the public domain.
+ */
+#if defined(__OpenBSD__)
+# define _BSD_SOURCE /* for pledge(2) and unveil(2) */
+#else
+# define _XOPEN_SOURCE 600
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#define ENDLESSH_VERSION 1.1
+
+#define DEFAULT_PORT 2222
+#define DEFAULT_DELAY 10000 /* milliseconds */
+#define DEFAULT_MAX_LINE_LENGTH 32
+#define DEFAULT_MAX_CLIENTS 4096
+
+#if defined(__FreeBSD__)
+# define DEFAULT_CONFIG_FILE "/usr/local/etc/endlessh.config"
+#else
+# define DEFAULT_CONFIG_FILE "/etc/endlessh/config"
+#endif
+
+#define DEFAULT_BIND_FAMILY AF_UNSPEC
+
+#define XSTR(s) STR(s)
+#define STR(s) #s
+
+static long long
+epochms(void)
+{
+ struct timespec tv;
+ clock_gettime(CLOCK_REALTIME, &tv);
+ return tv.tv_sec * 1000ULL + tv.tv_nsec / 1000000ULL;
+}
+
+static enum loglevel {
+ log_none,
+ log_info,
+ log_debug
+} loglevel = log_none;
+
+static void (*logmsg)(enum loglevel level, const char *, ...);
+
+static void
+logstdio(enum loglevel level, const char *format, ...)
+{
+ if (loglevel >= level) {
+ int save = errno;
+
+ /* Print a timestamp */
+ long long now = epochms();
+ time_t t = now / 1000;
+ char date[64];
+ struct tm tm[1];
+ strftime(date, sizeof(date), "%Y-%m-%dT%H:%M:%S", gmtime_r(&t, tm));
+ printf("%s.%03lldZ ", date, now % 1000);
+
+ /* Print the rest of the log message */
+ va_list ap;
+ va_start(ap, format);
+ vprintf(format, ap);
+ va_end(ap);
+ fputc('\n', stdout);
+
+ errno = save;
+ }
+}
+
+static void
+logsyslog(enum loglevel level, const char *format, ...)
+{
+ static const int prio_map[] = { LOG_NOTICE, LOG_INFO, LOG_DEBUG };
+
+ if (loglevel >= level) {
+ int save = errno;
+
+ /* Output the log message */
+ va_list ap;
+ va_start(ap, format);
+ char buf[256];
+ vsnprintf(buf, sizeof buf, format, ap);
+ va_end(ap);
+ syslog(prio_map[level], "%s", buf);
+
+ errno = save;
+ }
+}
+
+static struct {
+ long long connects;
+ long long active;
+ long long milliseconds;
+ long long bytes_sent;
+} statistics;
+
+struct client {
+ char ipaddr[INET6_ADDRSTRLEN];
+ long long connect_time;
+ long long send_next;
+ long long bytes_sent;
+ struct client *next;
+ int port;
+ int fd;
+};
+
+static struct client *
+client_new(int fd, long long send_next)
+{
+ struct client *c = malloc(sizeof(*c));
+ if (c) {
+ c->ipaddr[0] = 0;
+ c->connect_time = epochms();
+ c->send_next = send_next;
+ c->bytes_sent = 0;
+ c->next = 0;
+ c->fd = fd;
+ c->port = 0;
+
+ /* Set the smallest possible recieve buffer. This reduces local
+ * resource usage and slows down the remote end.
+ */
+ int value = 1;
+ int r = setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &value, sizeof(value));
+ logmsg(log_debug, "setsockopt(%d, SO_RCVBUF, %d) = %d", fd, value, r);
+ if (r == -1)
+ logmsg(log_debug, "errno = %d, %s", errno, strerror(errno));
+
+ /* Get IP address */
+ struct sockaddr_storage addr;
+ socklen_t len = sizeof(addr);
+ if (getpeername(fd, (struct sockaddr *)&addr, &len) != -1) {
+ if (addr.ss_family == AF_INET) {
+ struct sockaddr_in *s = (struct sockaddr_in *)&addr;
+ c->port = ntohs(s->sin_port);
+ inet_ntop(AF_INET, &s->sin_addr,
+ c->ipaddr, sizeof(c->ipaddr));
+ } else {
+ struct sockaddr_in6 *s = (struct sockaddr_in6 *)&addr;
+ c->port = ntohs(s->sin6_port);
+ inet_ntop(AF_INET6, &s->sin6_addr,
+ c->ipaddr, sizeof(c->ipaddr));
+ }
+ }
+ }
+ return c;
+}
+
+static void
+client_destroy(struct client *client)
+{
+ logmsg(log_debug, "close(%d)", client->fd);
+ long long dt = epochms() - client->connect_time;
+ logmsg(log_info,
+ "CLOSE host=%s port=%d fd=%d "
+ "time=%lld.%03lld bytes=%lld",
+ client->ipaddr, client->port, client->fd,
+ dt / 1000, dt % 1000,
+ client->bytes_sent);
+ statistics.milliseconds += dt;
+ statistics.active-=1;
+ close(client->fd);
+ free(client);
+}
+
+static void
+statistics_log_totals(struct client *clients)
+{
+ long long milliseconds = statistics.milliseconds;
+ for (long long now = epochms(); clients; clients = clients->next)
+ milliseconds += now - clients->connect_time;
+ logmsg(log_info, "TOTALS connects=%lld active=%lld seconds=%lld.%03lld bytes=%lld",
+ statistics.connects,
+ statistics.active,
+ milliseconds / 1000,
+ milliseconds % 1000,
+ statistics.bytes_sent);
+}
+
+struct fifo {
+ struct client *head;
+ struct client *tail;
+ int length;
+};
+
+static void
+fifo_init(struct fifo *q)
+{
+ q->head = q->tail = 0;
+ q->length = 0;
+}
+
+static struct client *
+fifo_pop(struct fifo *q)
+{
+ struct client *removed = q->head;
+ q->head = q->head->next;
+ removed->next = 0;
+ if (!--q->length)
+ q->tail = 0;
+ return removed;
+}
+
+static void
+fifo_append(struct fifo *q, struct client *c)
+{
+ if (!q->tail) {
+ q->head = q->tail = c;
+ } else {
+ q->tail->next = c;
+ q->tail = c;
+ }
+ q->length++;
+}
+
+static void
+fifo_destroy(struct fifo *q)
+{
+ struct client *c = q->head;
+ while (c) {
+ struct client *dead = c;
+ c = c->next;
+ client_destroy(dead);
+ }
+ q->head = q->tail = 0;
+ q->length = 0;
+}
+
+static void
+die(void)
+{
+ fprintf(stderr, "endlessh: fatal: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+}
+
+static unsigned
+rand16(unsigned long s[1])
+{
+ s[0] = s[0] * 1103515245UL + 12345UL;
+ return (s[0] >> 16) & 0xffff;
+}
+
+static int
+randline(char *line, int maxlen, unsigned long s[1])
+{
+ int len = 3 + rand16(s) % (maxlen - 2);
+ for (int i = 0; i < len - 2; i++)
+ line[i] = 32 + rand16(s) % 95;
+ line[len - 2] = 13;
+ line[len - 1] = 10;
+ if (memcmp(line, "SSH-", 4) == 0)
+ line[0] = 'X';
+ return len;
+}
+
+static volatile sig_atomic_t running = 1;
+
+static void
+sigterm_handler(int signal)
+{
+ (void)signal;
+ running = 0;
+}
+
+static volatile sig_atomic_t reload = 0;
+
+static void
+sighup_handler(int signal)
+{
+ (void)signal;
+ reload = 1;
+}
+
+static volatile sig_atomic_t dumpstats = 0;
+
+static void
+sigusr1_handler(int signal)
+{
+ (void)signal;
+ dumpstats = 1;
+}
+
+struct config {
+ int port;
+ int delay;
+ int max_line_length;
+ int max_clients;
+ int bind_family;
+};
+
+#define CONFIG_DEFAULT { \
+ .port = DEFAULT_PORT, \
+ .delay = DEFAULT_DELAY, \
+ .max_line_length = DEFAULT_MAX_LINE_LENGTH, \
+ .max_clients = DEFAULT_MAX_CLIENTS, \
+ .bind_family = DEFAULT_BIND_FAMILY, \
+}
+
+static void
+config_set_port(struct config *c, const char *s, int hardfail)
+{
+ errno = 0;
+ char *end;
+ long tmp = strtol(s, &end, 10);
+ if (errno || *end || tmp < 1 || tmp > 65535) {
+ fprintf(stderr, "endlessh: Invalid port: %s\n", s);
+ if (hardfail)
+ exit(EXIT_FAILURE);
+ } else {
+ c->port = tmp;
+ }
+}
+
+static void
+config_set_delay(struct config *c, const char *s, int hardfail)
+{
+ errno = 0;
+ char *end;
+ long tmp = strtol(s, &end, 10);
+ if (errno || *end || tmp < 1 || tmp > INT_MAX) {
+ fprintf(stderr, "endlessh: Invalid delay: %s\n", s);
+ if (hardfail)
+ exit(EXIT_FAILURE);
+ } else {
+ c->delay = tmp;
+ }
+}
+
+static void
+config_set_max_clients(struct config *c, const char *s, int hardfail)
+{
+ errno = 0;
+ char *end;
+ long tmp = strtol(s, &end, 10);
+ if (errno || *end || tmp < 1 || tmp > INT_MAX) {
+ fprintf(stderr, "endlessh: Invalid max clients: %s\n", s);
+ if (hardfail)
+ exit(EXIT_FAILURE);
+ } else {
+ c->max_clients = tmp;
+ }
+}
+
+static void
+config_set_max_line_length(struct config *c, const char *s, int hardfail)
+{
+ errno = 0;
+ char *end;
+ long tmp = strtol(s, &end, 10);
+ if (errno || *end || tmp < 3 || tmp > 255) {
+ fprintf(stderr, "endlessh: Invalid line length: %s\n", s);
+ if (hardfail)
+ exit(EXIT_FAILURE);
+ } else {
+ c->max_line_length = tmp;
+ }
+}
+
+static void
+config_set_bind_family(struct config *c, const char *s, int hardfail)
+{
+ switch (*s) {
+ case '4':
+ c->bind_family = AF_INET;
+ break;
+ case '6':
+ c->bind_family = AF_INET6;
+ break;
+ case '0':
+ c->bind_family = AF_UNSPEC;
+ break;
+ default:
+ fprintf(stderr, "endlessh: Invalid address family: %s\n", s);
+ if (hardfail)
+ exit(EXIT_FAILURE);
+ break;
+ }
+}
+
+enum config_key {
+ KEY_INVALID,
+ KEY_PORT,
+ KEY_DELAY,
+ KEY_MAX_LINE_LENGTH,
+ KEY_MAX_CLIENTS,
+ KEY_LOG_LEVEL,
+ KEY_BIND_FAMILY,
+};
+
+static enum config_key
+config_key_parse(const char *tok)
+{
+ static const char *const table[] = {
+ [KEY_PORT] = "Port",
+ [KEY_DELAY] = "Delay",
+ [KEY_MAX_LINE_LENGTH] = "MaxLineLength",
+ [KEY_MAX_CLIENTS] = "MaxClients",
+ [KEY_LOG_LEVEL] = "LogLevel",
+ [KEY_BIND_FAMILY] = "BindFamily"
+ };
+ for (size_t i = 1; i < sizeof(table) / sizeof(*table); i++)
+ if (!strcmp(tok, table[i]))
+ return i;
+ return KEY_INVALID;
+}
+
+static void
+config_load(struct config *c, const char *file, int hardfail)
+{
+ long lineno = 0;
+ FILE *f = fopen(file, "r");
+ if (f) {
+ char line[256];
+ while (fgets(line, sizeof(line), f)) {
+ lineno++;
+
+ /* Remove comments */
+ char *comment = strchr(line, '#');
+ if (comment)
+ *comment = 0;
+
+ /* Parse tokes on line */
+ char *save = 0;
+ char *tokens[3];
+ int ntokens = 0;
+ for (; ntokens < 3; ntokens++) {
+ char *tok = strtok_r(ntokens ? 0 : line, " \r\n", &save);
+ if (!tok)
+ break;
+ tokens[ntokens] = tok;
+ }
+
+ switch (ntokens) {
+ case 0: /* Empty line */
+ continue;
+ case 1:
+ fprintf(stderr, "%s:%ld: Missing value\n", file, lineno);
+ if (hardfail) exit(EXIT_FAILURE);
+ continue;
+ case 2: /* Expected */
+ break;
+ case 3:
+ fprintf(stderr, "%s:%ld: Too many values\n", file, lineno);
+ if (hardfail) exit(EXIT_FAILURE);
+ continue;
+ }
+
+ enum config_key key = config_key_parse(tokens[0]);
+ switch (key) {
+ case KEY_INVALID:
+ fprintf(stderr, "%s:%ld: Unknown option '%s'\n",
+ file, lineno, tokens[0]);
+ break;
+ case KEY_PORT:
+ config_set_port(c, tokens[1], hardfail);
+ break;
+ case KEY_DELAY:
+ config_set_delay(c, tokens[1], hardfail);
+ break;
+ case KEY_MAX_LINE_LENGTH:
+ config_set_max_line_length(c, tokens[1], hardfail);
+ break;
+ case KEY_MAX_CLIENTS:
+ config_set_max_clients(c, tokens[1], hardfail);
+ break;
+ case KEY_BIND_FAMILY:
+ config_set_bind_family(c, tokens[1], hardfail);
+ break;
+ case KEY_LOG_LEVEL: {
+ errno = 0;
+ char *end;
+ long v = strtol(tokens[1], &end, 10);
+ if (errno || *end || v < log_none || v > log_debug) {
+ fprintf(stderr, "%s:%ld: Invalid log level '%s'\n",
+ file, lineno, tokens[1]);
+ if (hardfail) exit(EXIT_FAILURE);
+ } else {
+ loglevel = v;
+ }
+ } break;
+ }
+ }
+
+ fclose(f);
+ }
+}
+
+static void
+config_log(const struct config *c)
+{
+ logmsg(log_info, "Port %d", c->port);
+ logmsg(log_info, "Delay %d", c->delay);
+ logmsg(log_info, "MaxLineLength %d", c->max_line_length);
+ logmsg(log_info, "MaxClients %d", c->max_clients);
+ logmsg(log_info, "BindFamily %s",
+ c->bind_family == AF_INET6 ? "IPv6 Only" :
+ c->bind_family == AF_INET ? "IPv4 Only" :
+ "IPv4 Mapped IPv6");
+}
+
+static void
+usage(FILE *f)
+{
+ fprintf(f, "Usage: endlessh [-vh] [-46] [-d MS] [-f CONFIG] [-l LEN] "
+ "[-m LIMIT] [-p PORT]\n");
+ fprintf(f, " -4 Bind to IPv4 only\n");
+ fprintf(f, " -6 Bind to IPv6 only\n");
+ fprintf(f, " -d INT Message millisecond delay ["
+ XSTR(DEFAULT_DELAY) "]\n");
+ fprintf(f, " -f Set and load config file ["
+ DEFAULT_CONFIG_FILE "]\n");
+ fprintf(f, " -h Print this help message and exit\n");
+ fprintf(f, " -l INT Maximum banner line length (3-255) ["
+ XSTR(DEFAULT_MAX_LINE_LENGTH) "]\n");
+ fprintf(f, " -m INT Maximum number of clients ["
+ XSTR(DEFAULT_MAX_CLIENTS) "]\n");
+ fprintf(f, " -p INT Listening port [" XSTR(DEFAULT_PORT) "]\n");
+ fprintf(f, " -v Print diagnostics to standard output "
+ "(repeatable)\n");
+ fprintf(f, " -V Print version information and exit\n");
+}
+
+static void
+print_version(void)
+{
+ puts("Endlessh " XSTR(ENDLESSH_VERSION));
+}
+
+static int
+server_create(int port, int family)
+{
+ int r, s, value;
+
+ s = socket(family == AF_UNSPEC ? AF_INET6 : family, SOCK_STREAM, 0);
+ logmsg(log_debug, "socket() = %d", s);
+ if (s == -1) die();
+
+ /* Socket options are best effort, allowed to fail */
+ value = 1;
+ r = setsockopt(s, SOL_SOCKET, SO_REUSEADDR, &value, sizeof(value));
+ logmsg(log_debug, "setsockopt(%d, SO_REUSEADDR, true) = %d", s, r);
+ if (r == -1)
+ logmsg(log_debug, "errno = %d, %s", errno, strerror(errno));
+
+ /*
+ * With OpenBSD IPv6 sockets are always IPv6-only, so the socket option
+ * is read-only (not modifiable).
+ * http://man.openbsd.org/ip6#IPV6_V6ONLY
+ */
+#ifndef __OpenBSD__
+ if (family == AF_INET6 || family == AF_UNSPEC) {
+ errno = 0;
+ value = (family == AF_INET6);
+ r = setsockopt(s, IPPROTO_IPV6, IPV6_V6ONLY, &value, sizeof(value));
+ logmsg(log_debug, "setsockopt(%d, IPV6_V6ONLY, true) = %d", s, r);
+ if (r == -1)
+ logmsg(log_debug, "errno = %d, %s", errno, strerror(errno));
+ }
+#endif
+
+ if (family == AF_INET) {
+ struct sockaddr_in addr4 = {
+ .sin_family = AF_INET,
+ .sin_port = htons(port),
+ .sin_addr = {INADDR_ANY}
+ };
+ r = bind(s, (void *)&addr4, sizeof(addr4));
+ } else {
+ struct sockaddr_in6 addr6 = {
+ .sin6_family = AF_INET6,
+ .sin6_port = htons(port),
+ .sin6_addr = in6addr_any
+ };
+ r = bind(s, (void *)&addr6, sizeof(addr6));
+ }
+ logmsg(log_debug, "bind(%d, port=%d) = %d", s, port, r);
+ if (r == -1) die();
+
+ r = listen(s, INT_MAX);
+ logmsg(log_debug, "listen(%d) = %d", s, r);
+ if (r == -1) die();
+
+ return s;
+}
+
+/* Write a line to a client, returning client if it's still up. */
+static struct client *
+sendline(struct client *client, int max_line_length, unsigned long *rng)
+{
+ char line[256];
+ int len = randline(line, max_line_length, rng);
+ for (;;) {
+ ssize_t out = write(client->fd, line, len);
+ logmsg(log_debug, "write(%d) = %d", client->fd, (int)out);
+ if (out == -1) {
+ if (errno == EINTR) {
+ continue; /* try again */
+ } else if (errno == EAGAIN || errno == EWOULDBLOCK) {
+ return client; /* don't care */
+ } else {
+ client_destroy(client);
+ return 0;
+ }
+ } else {
+ client->bytes_sent += out;
+ statistics.bytes_sent += out;
+ return client;
+ }
+ }
+}
+
+
+int
+main(int argc, char **argv)
+{
+ logmsg = logstdio;
+ struct config config = CONFIG_DEFAULT;
+ const char *config_file = DEFAULT_CONFIG_FILE;
+
+#if defined(__OpenBSD__)
+ unveil(config_file, "r"); /* return ignored as the file may not exist */
+ if (pledge("inet stdio rpath unveil", 0) == -1)
+ die();
+#endif
+
+ config_load(&config, config_file, 1);
+
+ int option;
+ while ((option = getopt(argc, argv, "46d:f:hl:m:p:svV")) != -1) {
+ switch (option) {
+ case '4':
+ config_set_bind_family(&config, "4", 1);
+ break;
+ case '6':
+ config_set_bind_family(&config, "6", 1);
+ break;
+ case 'd':
+ config_set_delay(&config, optarg, 1);
+ break;
+ case 'f':
+ config_file = optarg;
+
+#if defined(__OpenBSD__)
+ unveil(config_file, "r");
+ if (unveil(0, 0) == -1)
+ die();
+#endif
+
+ config_load(&config, optarg, 1);
+ break;
+ case 'h':
+ usage(stdout);
+ exit(EXIT_SUCCESS);
+ break;
+ case 'l':
+ config_set_max_line_length(&config, optarg, 1);
+ break;
+ case 'm':
+ config_set_max_clients(&config, optarg, 1);
+ break;
+ case 'p':
+ config_set_port(&config, optarg, 1);
+ break;
+ case 's':
+ logmsg = logsyslog;
+ break;
+ case 'v':
+ if (loglevel < log_debug)
+ loglevel++;
+ break;
+ case 'V':
+ print_version();
+ exit(EXIT_SUCCESS);
+ break;
+ default:
+ usage(stderr);
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ if (argv[optind]) {
+ fprintf(stderr, "endlessh: too many arguments\n");
+ exit(EXIT_FAILURE);
+ }
+
+ if (logmsg == logsyslog) {
+ /* Prepare the syslog */
+ const char *prog = strrchr(argv[0], '/');
+ prog = prog ? prog + 1 : argv[0];
+ openlog(prog, LOG_PID, LOG_DAEMON);
+ } else {
+ /* Set output (log) to line buffered */
+ setvbuf(stdout, 0, _IOLBF, 0);
+ }
+
+ /* Log configuration */
+ config_log(&config);
+
+ /* Install the signal handlers */
+ signal(SIGPIPE, SIG_IGN);
+ {
+ struct sigaction sa = {.sa_handler = sigterm_handler};
+ int r = sigaction(SIGTERM, &sa, 0);
+ if (r == -1)
+ die();
+ }
+ {
+ struct sigaction sa = {.sa_handler = sighup_handler};
+ int r = sigaction(SIGHUP, &sa, 0);
+ if (r == -1)
+ die();
+ }
+ {
+ struct sigaction sa = {.sa_handler = sigusr1_handler};
+ int r = sigaction(SIGUSR1, &sa, 0);
+ if (r == -1)
+ die();
+ }
+
+ struct fifo fifo[1];
+ fifo_init(fifo);
+
+ unsigned long rng = epochms();
+
+ int server = server_create(config.port, config.bind_family);
+
+ while (running) {
+ if (reload) {
+ /* Configuration reload requested (SIGHUP) */
+ int oldport = config.port;
+ int oldfamily = config.bind_family;
+ config_load(&config, config_file, 0);
+ config_log(&config);
+ if (oldport != config.port || oldfamily != config.bind_family) {
+ close(server);
+ server = server_create(config.port, config.bind_family);
+ }
+ reload = 0;
+ }
+ if (dumpstats) {
+ /* print stats requested (SIGUSR1) */
+ statistics_log_totals(fifo->head);
+ dumpstats = 0;
+ }
+
+ /* Enqueue clients that are due for another message */
+ int timeout = -1;
+ long long now = epochms();
+ while (fifo->head) {
+ if (fifo->head->send_next <= now) {
+ struct client *c = fifo_pop(fifo);
+ if (sendline(c, config.max_line_length, &rng)) {
+ c->send_next = now + config.delay;
+ fifo_append(fifo, c);
+ }
+ } else {
+ timeout = fifo->head->send_next - now;
+ break;
+ }
+ }
+
+ /* Wait for next event */
+ struct pollfd fds = {server, POLLIN, 0};
+ int nfds = fifo->length < config.max_clients;
+ logmsg(log_debug, "poll(%d, %d)", nfds, timeout);
+ int r = poll(&fds, nfds, timeout);
+ logmsg(log_debug, "= %d", r);
+ if (r == -1) {
+ switch (errno) {
+ case EINTR:
+ logmsg(log_debug, "EINTR");
+ continue;
+ default:
+ fprintf(stderr, "endlessh: fatal: %s\n", strerror(errno));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ /* Check for new incoming connections */
+ if (fds.revents & POLLIN) {
+ int fd = accept(server, 0, 0);
+ logmsg(log_debug, "accept() = %d", fd);
+ statistics.connects++;
+ if (fd == -1) {
+ const char *msg = strerror(errno);
+ switch (errno) {
+ case EMFILE:
+ case ENFILE:
+ config.max_clients = fifo->length;
+ logmsg(log_info,
+ "MaxClients %d",
+ fifo->length);
+ break;
+ case ECONNABORTED:
+ case EINTR:
+ case ENOBUFS:
+ case ENOMEM:
+ case EPROTO:
+ fprintf(stderr, "endlessh: warning: %s\n", msg);
+ break;
+ default:
+ fprintf(stderr, "endlessh: fatal: %s\n", msg);
+ exit(EXIT_FAILURE);
+ }
+ } else {
+ long long send_next = epochms() + config.delay;
+ struct client *client = client_new(fd, send_next);
+ int flags = fcntl(fd, F_GETFL, 0); /* cannot fail */
+ fcntl(fd, F_SETFL, flags | O_NONBLOCK); /* cannot fail */
+ if (!client) {
+ fprintf(stderr, "endlessh: warning: out of memory\n");
+ close(fd);
+ } else {
+ statistics.active+=1;
+ fifo_append(fifo, client);
+ logmsg(log_info, "ACCEPT host=%s port=%d fd=%d n=%d/%d",
+ client->ipaddr, client->port, client->fd,
+ fifo->length, config.max_clients);
+ }
+ }
+ }
+ }
+
+ fifo_destroy(fifo);
+ statistics_log_totals(0);
+
+ if (logmsg == logsyslog)
+ closelog();
+}
diff --git a/util/endlessh.service b/util/endlessh.service
new file mode 100644
index 0000000..fd12a00
--- /dev/null
+++ b/util/endlessh.service
@@ -0,0 +1,44 @@
+[Unit]
+Description=Endlessh SSH Tarpit
+Documentation=man:endlessh(1)
+Requires=network-online.target
+
+[Service]
+Type=simple
+Restart=always
+RestartSec=30sec
+ExecStart=/usr/local/bin/endlessh
+KillSignal=SIGTERM
+
+# Stop trying to restart the service if it restarts too many times in a row
+StartLimitInterval=5min
+StartLimitBurst=4
+
+StandardOutput=journal
+StandardError=journal
+StandardInput=null
+
+PrivateTmp=true
+PrivateDevices=true
+ProtectSystem=full
+ProtectHome=true
+InaccessiblePaths=/run /var
+
+## If you want Endlessh to bind on ports < 1024
+## 1) run:
+## setcap 'cap_net_bind_service=+ep' /usr/local/bin/endlessh
+## 2) uncomment following line
+#AmbientCapabilities=CAP_NET_BIND_SERVICE
+## 3) comment following line
+PrivateUsers=true
+
+NoNewPrivileges=true
+ConfigurationDirectory=endlessh
+ProtectKernelTunables=true
+ProtectKernelModules=true
+ProtectControlGroups=true
+MemoryDenyWriteExecute=true
+
+[Install]
+WantedBy=multi-user.target
+
diff --git a/util/openbsd/README.md b/util/openbsd/README.md
new file mode 100644
index 0000000..d3e24bc
--- /dev/null
+++ b/util/openbsd/README.md
@@ -0,0 +1,34 @@
+# Running `endlessh` on OpenBSD
+
+## Covering IPv4 and IPv6
+
+If you want to cover both IPv4 and IPv6 you'll need to run *two* instances of
+`endlessh` due to OpenBSD limitations. Here's how I did it:
+
+- copy the `endlessh` script to `rc.d` twice, as `endlessh` and `endlessh6`
+- copy the `config` file to `/etc/endlessh` twice, as `config` and `config6`
+ - use `BindFamily 4` in `config`
+ - use `BindFamily 6` in `config6`
+- in `rc.conf.local` force `endlessh6` to load `config6` like so:
+
+```
+endlessh6_flags=-s -f /etc/endlessh/config6
+endlessh_flags=-s
+```
+
+## Covering more than 128 connections
+
+The defaults in OpenBSD only allow for 128 open file descriptors per process,
+so regardless of the `MaxClients` setting in `/etc/config` you'll end up with
+something like 124 clients at the most.
+You can increase these limits in `/etc/login.conf` for `endlessh` (and
+`endlessh6`) like so:
+
+```
+endlessh:\
+ :openfiles=1024:\
+ :tc=daemon:
+endlessh6:\
+ :openfiles=1024:\
+ :tc=daemon:
+```
diff --git a/util/openbsd/endlessh b/util/openbsd/endlessh
new file mode 100755
index 0000000..f7b2313
--- /dev/null
+++ b/util/openbsd/endlessh
@@ -0,0 +1,9 @@
+#!/bin/ksh
+#
+
+daemon="/usr/local/bin/endlessh"
+rc_bg=YES
+
+. /etc/rc.d/rc.subr
+
+rc_cmd $1
diff --git a/util/pivot.py b/util/pivot.py
new file mode 100755
index 0000000..4e3ab72
--- /dev/null
+++ b/util/pivot.py
@@ -0,0 +1,35 @@
+#!/usr/bin/env python3
+
+# This script accepts a log on standard input and produces a CSV table
+# with one connection per row.
+#
+# $ util/pivot.py 0:
+ print('warning: %d hanging entries' % len(table), file=sys.stderr)
diff --git a/util/schema.sql b/util/schema.sql
new file mode 100644
index 0000000..78bf50a
--- /dev/null
+++ b/util/schema.sql
@@ -0,0 +1,8 @@
+CREATE TABLE IF NOT EXISTS log (
+ host TEXT,
+ port INTEGER,
+ time REAL,
+ bytes INTEGER
+);
+.mode csv
+.import /dev/stdin log
diff --git a/util/smf/README b/util/smf/README
new file mode 100644
index 0000000..e9feaf8
--- /dev/null
+++ b/util/smf/README
@@ -0,0 +1,25 @@
+Solaris SMF installation
+========================
+
+Before installing SMF:
+
+1. Put endlessh binary to /usr/local/bin
+2. Edit endlessh.conf and put it to /usr/local/etc
+
+To install SMF:
+
+1. Put endlessh.xml to /var/svc/manifest/network
+2. Run svccfg import endlessh.xml
+3. Put init.endlessh to /lib/svc/method
+4. Run svcadm enable endlessh
+
+Note: Log will write to /var/log/endlessh.log by default.
+
+To uninstall SMF:
+
+1. Run svcadm disable endlessh
+2. rm -f /lib/svc/method/init.endlessh
+3. svccfg delete svc:/network/endlessh:default
+4. rm -f /var/svc/manifest/network/endlessh.xml
+
+Enjoy! :)
\ No newline at end of file
diff --git a/util/smf/endlessh.conf b/util/smf/endlessh.conf
new file mode 100644
index 0000000..5137178
--- /dev/null
+++ b/util/smf/endlessh.conf
@@ -0,0 +1,27 @@
+# The port on which to listen for new SSH connections.
+Port 22
+
+# The endless banner is sent one line at a time. This is the delay
+# in milliseconds between individual lines.
+Delay 10000
+
+# The length of each line is randomized. This controls the maximum
+# length of each line. Shorter lines may keep clients on for longer if
+# they give up after a certain number of bytes.
+MaxLineLength 32
+
+# Maximum number of connections to accept at a time. Connections beyond
+# this are not immediately rejected, but will wait in the queue.
+MaxClients 4096
+
+# Set the detail level for the log.
+# 0 = Quiet
+# 1 = Standard, useful log messages
+# 2 = Very noisy debugging information
+LogLevel 1
+
+# Set the family of the listening socket
+# 0 = Use IPv4 Mapped IPv6 (Both v4 and v6, default)
+# 4 = Use IPv4 only
+# 6 = Use IPv6 only
+BindFamily 0
diff --git a/util/smf/endlessh.xml b/util/smf/endlessh.xml
new file mode 100644
index 0000000..fe4e803
--- /dev/null
+++ b/util/smf/endlessh.xml
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ endlessh service
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/util/smf/init.endlessh b/util/smf/init.endlessh
new file mode 100644
index 0000000..c254bb9
--- /dev/null
+++ b/util/smf/init.endlessh
@@ -0,0 +1,137 @@
+#!/sbin/sh
+
+#
+# Control Method for endlessh (/lib/svc/method/init.endlessh)
+# Written by Yuri Voinov (C) 2007,2019
+#
+# ident "@(#)endlessh.sh 1.8 19/27/03 YV"
+#
+
+#############
+# Variables #
+#############
+
+# Base installation directory
+BASE_DIR="/usr/local"
+BASE_CONFIG_DIR=$BASE_DIR"/etc"
+
+# endlessh files paths
+ENDLESSH_PATH="$BASE_DIR""/bin"
+ENDLESSH_CONF_PATH="$BASE_CONFIG_DIR"
+
+# endlessh files
+ENDLESSH_BIN_FILE="endlessh"
+ENDLESSH_CONF_FILE=$ENDLESSH_BIN_FILE".conf"
+
+# Daemon settings
+ENDLESSH_CONF="$ENDLESSH_CONF_PATH/$ENDLESSH_CONF_FILE"
+
+# Log
+LOG_DIR="/var/log"
+LOGFILE=$LOG_DIR/$ENDLESSH_BIN_FILE".log"
+
+#
+# OS Commands location variables
+#
+CUT=`which cut`
+ECHO=`which echo`
+KILL=`which kill`
+PGREP=`which pgrep`
+UNAME=`which uname`
+
+# OS release
+OS_VER=`$UNAME -r|$CUT -f2 -d"."`
+OS_NAME=`$UNAME -s|$CUT -f1 -d" "`
+
+###############
+# Subroutines #
+###############
+
+check_endlessh ()
+{
+ # Check endlessh installed
+ program=$1
+ if [ ! -f "$ENDLESSH_PATH/$program" -a ! -x "$ENDLESSH_PATH/$program" ]; then
+ $ECHO "ERROR: endlessh not found!"
+ $ECHO "Exiting..."
+ exit 1
+ fi
+}
+
+check_os ()
+{
+ # Check OS version
+ if [ ! "$OS_NAME" = "SunOS" -a ! "$OS_VER" -lt "10" ]; then
+ $ECHO "ERROR: Unsupported OS $OS_NAME $OS_VER"
+ $ECHO "Exiting..."
+ exit 1
+ fi
+}
+
+checkconf ()
+{
+# Check endlessh config file
+ config=$1
+ if [ -f "$ENDLESSH_CONF_PATH"/"$config" ]; then
+ $ECHO "1"
+ else
+ $ECHO "0"
+ fi
+}
+
+startproc()
+{
+# Start endlessh daemon
+ program=$1
+ if [ "`checkconf $ENDLESSH_CONF_FILE`" != "1" ]; then
+ $ECHO "ERROR: Config file $ENDLESSH_CONF_PATH/$ENDLESSH_CONF_FILE not found."
+ $ECHO "Exiting..."
+ exit 2
+ else
+ $ENDLESSH_PATH/$program -f $ENDLESSH_CONF_PATH/$ENDLESSH_CONF_FILE -v >$LOGFILE &
+ fi
+}
+
+stopproc()
+{
+# Stop endlessh daemon
+ program=$1
+ if [ "`checkconf $ENDLESSH_CONF_FILE`" != "1" ]; then
+ $ECHO "ERROR: Config file $ENDLESSH_CONF_PATH/$ENDLESSH_CONF_FILE not found."
+ $ECHO "Exiting..."
+ exit 2
+ else
+ $KILL -s TERM `$PGREP $program`>/dev/null 2>&1
+ fi
+}
+
+##############
+# Main block #
+##############
+
+# Check endlessh installed
+check_endlessh $ENDLESSH_BIN_FILE
+
+# Check OS version
+check_os
+
+case "$1" in
+"start")
+ startproc $ENDLESSH_BIN_FILE
+ ;;
+"stop")
+ stopproc $ENDLESSH_BIN_FILE
+ ;;
+"refresh")
+ $KILL -s HUP `$PGREP $ENDLESSH_BIN_FILE`>/dev/null 2>&1
+ ;;
+"restart")
+ stopproc $ENDLESSH_BIN_FILE
+ startproc $ENDLESSH_BIN_FILE
+ ;;
+*)
+ $ECHO "Usage: $0 { start | stop | restart | refresh }"
+ exit 1
+esac
+
+exit 0