46 Commits
0.1 ... master

Author SHA1 Message Date
Christian Göttsche
dfe44eb2c5 Mark file local statistics struct static (#63) 2021-04-30 10:00:40 -04:00
Christian Göttsche
1ecaafd577 Fix format string defect in log message (#63) 2021-04-30 09:52:48 -04:00
Peter H. Froehlich
a5913cbbb2 Three notes on OpenBSD. 2020-12-24 00:43:20 +01:00
Christopher Wellons
4cb4fc6eac Use CPPFLAGS in the Makefile (closes #43)
Debian uses CPPFLAGS to pass arguments like -D_FORTIFY_SOURCE=2.
2020-02-16 10:10:14 -05:00
Christopher Wellons
8daa5992f1 Bump to version 1.1 2020-01-31 11:47:27 -05:00
Beat Bolli
ad7031f79a Enable logging to syslog with -s 2020-01-31 17:28:30 +01:00
Beat Bolli
5b7dc86a47 Route all logging through a function pointer
Prepare for a second logging function that logs to syslog.
2020-01-29 19:51:05 +01:00
Beat Bolli
e4f8c9f8f4 Limit the maximum log level settable from the command line 2020-01-29 19:49:11 +01:00
Beat Bolli
585a4b1d96 Rename all log levels
We're going to include <syslog.h> which #defines some of the same
identifiers to a numeric value. This will clash with the current usage
in the enum.
2020-01-29 19:47:25 +01:00
Christopher Wellons
2602caa459 Don't dereference NULL pointer on OOM
Fixes #37.
2019-12-30 13:11:02 -05:00
Christopher Wellons
715f30c3a7 Add public domain dedication to the source header 2019-08-06 20:31:35 -04:00
Nicolas Braud-Santoni
3d6aec6080 Prevent access to /run and /var in endlessh.service
Closes #34.
2019-08-06 20:23:11 -04:00
Nicolas Braud-Santoni
ae7473536e Add Documentation link in endlessh.service
Closes #33.
2019-08-06 20:18:39 -04:00
Christopher Wellons
33dff0cfc9 Balance list begin (.Bl) and end (.El) in man page
Ref: #35
2019-08-06 20:10:35 -04:00
Christopher Wellons
f465f2dcbb Tweak some macro formatting for consistency 2019-08-06 19:57:49 -04:00
arun
df0ffbf629 Use unveil(2) to restrict reading config file only
Closes #36.
2019-08-06 19:57:38 -04:00
arun
b2c811ecf7 Add pledge for OpenBSD
Closes #32.
2019-08-05 16:55:33 -04:00
Paavo-Einari Kaipila
a154fcaf43 better name for the config file 2019-05-14 21:05:48 +03:00
Paavo-Einari Kaipila
6b721e58ac config file location on FreeBSD 2019-05-14 18:56:01 +03:00
Cengiz Can
8ec96ea899 fix typo 2019-05-13 15:25:24 +03:00
Cengiz Can
44b3285bb2 PrivateUsers=true prevents privileged port mapping 2019-05-13 15:25:04 +03:00
Cengiz Can
4321fe93e5 add optional AmbientCapabilities to systemd unit
If a user wants to bind to a privileged port (<1024) our current systemd unit fails to provide enough capabilities to endlessh binary.

So, a user can modify `/etc/endlessh/config` to have `Port=22` or similar and then check out the systemd unit to enable the extra attribute.
2019-05-13 15:14:06 +03:00
Cengiz Can
964a860634 fix ConfigurationDirectory in systemd unit
`ConfigurationDirectory=endlessh` should be enough. Previous assignment throws a warning with systemd 237

```
May 13 08:57:18 kernelwtf systemd[1]: Started Endlessh SSH Tarpit.
May 13 08:58:20 kernelwtf systemd[1]: /etc/systemd/system/endlessh.service:25: ConfigurationDirectory= path is not valid, ignoring assignment: /etc/endlessh
```
2019-05-13 12:02:53 +03:00
Cengiz Can
8794f02d22 fix Exec value in systemd unit
Thanks for creating this wonderful tool! 

By default `make install` installs the binary into `/usr/local/bin` so I think this should be changed accordingly.

Cheers
2019-05-13 11:49:17 +03:00
Christopher Wellons
edf414caf6 Bump to version 1.0
Fixes #28.
2019-04-29 07:41:07 -04:00
Christopher Wellons
370cb5bdb7 Use correct format specifier when printing totals
Fixes #26.
2019-04-20 08:40:11 -04:00
Christopher Wellons
24bc7639d1 Always set standard output to line-buffered
When logging was enabled via the config file, standard output was left
fully buffered. Logs should never be fully buffered.

Thanks to Chang-Li for pointing this out.
2019-04-19 13:01:33 -04:00
Christopher Wellons
6f621b90b1 Tweak SIGUSR1 statistics totals logging
* Don't dynamically allocate the struct since that's not necessary.
* Use a more concise log message.
* Include current clients when counting the time.
* Print final statistics message *after* closing all clients.
* Don't include total connections in ACCEPT.
* Mention SIGUSR1 in the new man page, too.

Adjusts #24.
2019-04-12 18:49:04 -04:00
Christopher Wellons
c50585f759 Rename uepoch() to epochms()
It returns milliseconds, not microseconds.
2019-04-12 18:37:45 -04:00
Felix Kronlage
3473beb868 Gather statistics and LOG_INFO them upon SIGUSR1
Merges and closes #24.
2019-04-12 18:25:49 -04:00
Felix Kronlage
f8398093b1 Proper unix tools come with make install target
Merges and closes #22.
2019-04-12 18:09:08 -04:00
Felix Kronlage
f6ee532b50 A unix tool deserves a proper manual page 2019-04-12 18:06:33 -04:00
Felix Kronlage
95235af2fb add rc.d script for openbsd 2019-04-12 23:08:56 +02:00
Christopher Wellons
38e35ad4c9 Add note about __EXTENSIONS__ to README (illumos) 2019-04-04 21:44:44 -04:00
Anton Rieger
8347377bb7 Merge pull request #1 from fkr/master
newlines for -4 and -6 help options
2019-04-04 21:51:33 +02:00
Felix Kronlage
9a0bfe683a newlines for -4 and -6 help options 2019-04-04 16:56:29 +02:00
Anton Rieger
badf3dd69e Added -4 -6 Switches and default tcp46
As *BSD defaults to use IPv6 only sockets and Linux to IPv4 mapped IPv6
sockets, switches to support explicit binding address families are required.

Now set explicitly if you want IPv6 only, IPv4 only or mapped IPv4.

Caveat:
OpenBSD explicitly states to not support IPv4 mapped IPv6 via setsock-API
2019-04-03 17:11:04 +02:00
yvoinov
cb7ee90cf5 Solaris SMF
Closes #18.
2019-03-28 10:53:13 -04:00
Christopher Wellons
494c68b8d7 Use _XOPEN_SOURCE instead of _POSIX_C_SOURCE
It seems this is required to get all the IPv6 definitions, though most
systems aren't very picky. NetBSD seems to be one of those picky
systems, though.
2019-03-27 09:40:20 -04:00
Christopher Wellons
9f4bdc123d Greatly simplify a bunch of things
1. If sockets are non-blocking, no need to use poll() on them. Just try
to write regardless and ignore the outcome. This means we don't need the
pollvec stuff anymore.

2. getline(3) is apparently still too new to POSIX that it's hard to use
portably. We don't really need it that badly anyway.

3. Don't use MSG_DONTWAIT. It's non-standard and apparently still hard
to use portably. Set sockets to non-blocking instead.

4. Since we're not polling on client sockets, the queue is a whole lot
simpler.
2019-03-26 20:30:03 -04:00
Christopher Wellons
b1c1599d32 Improve support for Solaris and its derivatives
Requires extensions to be enabled due to incomplete support for
POSIX.1-2008. However one of the now-exposed extensions is tragically
named "struct queue". This struct was been renamed to "struct fifo".

Thanks to @yvoinov for reporting this issue and collaborating on a
solution.

Closes #14, #17.
2019-03-26 18:23:15 -04:00
Dmitry Marakasov
a6d5425cba Makefile: add all target 2019-03-26 19:45:00 +03:00
Christopher Wellons
e7c93c6664 Add link to main article in README 2019-03-25 09:37:34 -04:00
Christopher Wellons
a6d4565a10 Fix -V typo in usage documentation (#11) 2019-03-25 09:26:17 -04:00
Christopher Wellons
09e55cc553 Check for FreeBSD and adjust feature test macros
Fixes #2.
2019-03-25 09:24:44 -04:00
Christopher Wellons
44219a5e05 Add note about RHEL 6 / CentOS 6
Fixes #10.
2019-03-25 09:17:48 -04:00
11 changed files with 805 additions and 204 deletions

View File

@@ -1,11 +1,21 @@
.POSIX:
CC = cc
CFLAGS = -std=c99 -Wall -Wextra -Wno-missing-field-initializers -Os
LDFLAGS = -ggdb3
LDLIBS =
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) $(LDFLAGS) $(CFLAGS) -o $@ endlessh.c $(LDLIBS)
$(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

View File

@@ -1,9 +1,9 @@
# Endlessh: an SSH tarpit
Endlessh is an SSH tarpit that *very* slowly sends an endless, random
SSH banner. 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
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
@@ -16,14 +16,17 @@ trap multiple clients at a time.
Usage information is printed with `-h`.
```
Usage: endlessh [-vh] [-d MS] [-f CONFIG] [-l LEN] [-m LIMIT] [-p PORT]
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]
-v Print diagnostics to standard output (repeatable)
-s Print diagnostics to syslog instead of standard output
-v Print diagnostics (repeatable)
```
Argument order matters. The configuration file is loaded when the `-f`
@@ -32,7 +35,8 @@ 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.
messages are sent to standard output by default. `-s` causes them to be
sent to syslog.
endlessh -v >endlessh.log 2>endlessh.err
@@ -41,6 +45,8 @@ 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.
@@ -67,4 +73,61 @@ MaxClients 4096
# 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/

84
endlessh.1 Normal file
View File

@@ -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

View File

@@ -1,4 +1,13 @@
#define _POSIX_C_SOURCE 200809L
/* 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 <time.h>
#include <errno.h>
#include <stdio.h>
@@ -9,25 +18,34 @@
#include <string.h>
#include <poll.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <syslog.h>
#define ENDLESSH_VERSION 0.1
#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
#define DEFAULT_CONFIG_FILE "/etc/endlessh/config"
#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
uepoch(void)
epochms(void)
{
struct timespec tv;
clock_gettime(CLOCK_REALTIME, &tv);
@@ -35,19 +53,21 @@ uepoch(void)
}
static enum loglevel {
LOG_NONE,
LOG_INFO,
LOG_DEBUG
} loglevel = LOG_NONE;
log_none,
log_info,
log_debug
} loglevel = log_none;
static void (*logmsg)(enum loglevel level, const char *, ...);
static void
logmsg(enum loglevel level, const char *format, ...)
logstdio(enum loglevel level, const char *format, ...)
{
if (loglevel >= level) {
int save = errno;
/* Print a timestamp */
long long now = uepoch();
long long now = epochms();
time_t t = now / 1000;
char date[64];
struct tm tm[1];
@@ -65,6 +85,32 @@ logmsg(enum loglevel level, const char *format, ...)
}
}
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 milliseconds;
long long bytes_sent;
} statistics;
struct client {
char ipaddr[INET6_ADDRSTRLEN];
long long connect_time;
@@ -81,20 +127,21 @@ client_new(int fd, long long send_next)
struct client *c = malloc(sizeof(*c));
if (c) {
c->ipaddr[0] = 0;
c->connect_time = uepoch();
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);
logmsg(log_debug, "setsockopt(%d, SO_RCVBUF, %d) = %d", fd, value, r);
if (r == -1)
logmsg(LOG_DEBUG, "errno = %d, %s", errno, strerror(errno));
logmsg(log_debug, "errno = %d, %s", errno, strerror(errno));
/* Get IP address */
struct sockaddr_storage addr;
@@ -119,60 +166,58 @@ client_new(int fd, long long send_next)
static void
client_destroy(struct client *client)
{
logmsg(LOG_DEBUG, "close(%d)", client->fd);
long long dt = uepoch() - client->connect_time;
logmsg(LOG_INFO,
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;
close(client->fd);
free(client);
}
struct queue {
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 seconds=%lld.%03lld bytes=%lld",
statistics.connects,
milliseconds / 1000,
milliseconds % 1000,
statistics.bytes_sent);
}
struct fifo {
struct client *head;
struct client *tail;
int length;
};
static void
queue_init(struct queue *q)
fifo_init(struct fifo *q)
{
q->head = q->tail = 0;
q->length = 0;
}
static struct client *
queue_remove(struct queue *q, int fd)
fifo_pop(struct fifo *q)
{
/* Yes, this is a linear search, but the element we're looking for
* is virtually always one of the first few elements.
*/
struct client *c;
struct client *prev = 0;
for (c = q->head; c; prev = c, c = c->next) {
if (c->fd == fd) {
if (!--q->length) {
q->head = q->tail = 0;
} else if (q->tail == c) {
q->tail = prev;
prev->next = 0;
} else if (prev) {
prev->next = c->next;
} else {
q->head = c->next;
}
c->next = 0;
break;
}
}
return c;
struct client *removed = q->head;
q->head = q->head->next;
removed->next = 0;
if (!--q->length)
q->tail = 0;
return removed;
}
static void
queue_append(struct queue *q, struct client *c)
fifo_append(struct fifo *q, struct client *c)
{
if (!q->tail) {
q->head = q->tail = c;
@@ -184,7 +229,7 @@ queue_append(struct queue *q, struct client *c)
}
static void
queue_destroy(struct queue *q)
fifo_destroy(struct fifo *q)
{
struct client *c = q->head;
while (c) {
@@ -196,56 +241,6 @@ queue_destroy(struct queue *q)
q->length = 0;
}
struct pollvec {
size_t cap;
size_t fill;
struct pollfd *fds;
};
static void
pollvec_init(struct pollvec *v)
{
v->cap = 4;
v->fill = 0;
v->fds = malloc(v->cap * sizeof(v->fds[0]));
if (!v->fds) {
fprintf(stderr, "endlessh: pollvec initialization failed\n");
exit(EXIT_FAILURE);
}
}
static void
pollvec_clear(struct pollvec *v)
{
v->fill = 0;
}
static int
pollvec_push(struct pollvec *v, int fd, short events)
{
if (v->cap == v->fill) {
size_t size = v->cap * 2 * sizeof(v->fds[0]);
if (size < v->cap * sizeof(v->fds[0]))
return 0; // overflow
struct pollfd *grow = realloc(v->fds, size);
if (!grow)
return 0;
v->fds = grow;
v->cap *= 2;
}
v->fds[v->fill].fd = fd;
v->fds[v->fill].events = events;
v->fill++;
return 1;
}
static void
pollvec_free(struct pollvec *v)
{
free(v->fds);
v->fds = 0;
}
static void
die(void)
{
@@ -291,11 +286,21 @@ sighup_handler(int 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 { \
@@ -303,6 +308,7 @@ struct config {
.delay = DEFAULT_DELAY, \
.max_line_length = DEFAULT_MAX_LINE_LENGTH, \
.max_clients = DEFAULT_MAX_CLIENTS, \
.bind_family = DEFAULT_BIND_FAMILY, \
}
static void
@@ -365,6 +371,27 @@ config_set_max_line_length(struct config *c, const char *s, int hardfail)
}
}
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,
@@ -372,6 +399,7 @@ enum config_key {
KEY_MAX_LINE_LENGTH,
KEY_MAX_CLIENTS,
KEY_LOG_LEVEL,
KEY_BIND_FAMILY,
};
static enum config_key
@@ -383,6 +411,7 @@ config_key_parse(const char *tok)
[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]))
@@ -396,9 +425,8 @@ config_load(struct config *c, const char *file, int hardfail)
long lineno = 0;
FILE *f = fopen(file, "r");
if (f) {
size_t len = 0;
char *line = 0;
while (getline(&line, &len, f) != -1) {
char line[256];
while (fgets(line, sizeof(line), f)) {
lineno++;
/* Remove comments */
@@ -450,11 +478,14 @@ config_load(struct config *c, const char *file, int hardfail)
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) {
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);
@@ -465,7 +496,6 @@ config_load(struct config *c, const char *file, int hardfail)
}
}
free(line);
fclose(f);
}
}
@@ -473,17 +503,23 @@ config_load(struct config *c, const char *file, int hardfail)
static void
config_log(const struct config *c)
{
logmsg(LOG_INFO, "Port %d", c->port);
logmsg(LOG_INFO, "Delay %ld", c->delay);
logmsg(LOG_INFO, "MaxLineLength %d", c->max_line_length);
logmsg(LOG_INFO, "MaxClients %d", c->max_clients);
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] [-d MS] [-f CONFIG] [-l LEN] "
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 ["
@@ -496,7 +532,7 @@ usage(FILE *f)
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");
fprintf(f, " -V Print version information and exit\n");
}
static void
@@ -506,52 +542,125 @@ print_version(void)
}
static int
server_create(int port)
server_create(int port, int family)
{
int r, s, value;
s = socket(AF_INET6, SOCK_STREAM, 0);
logmsg(LOG_DEBUG, "socket() = %d", s);
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);
logmsg(log_debug, "setsockopt(%d, SO_REUSEADDR, true) = %d", s, r);
if (r == -1)
logmsg(LOG_DEBUG, "errno = %d, %s", errno, strerror(errno));
logmsg(log_debug, "errno = %d, %s", errno, strerror(errno));
struct sockaddr_in6 addr = {
.sin6_family = AF_INET6,
.sin6_port = htons(port),
.sin6_addr = in6addr_any
};
r = bind(s, (void *)&addr, sizeof(addr));
logmsg(LOG_DEBUG, "bind(%d, port=%d) = %d", s, port, r);
/*
* 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);
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, "d:f:hl:m:p:vV")) != -1) {
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':
@@ -567,9 +676,12 @@ main(int argc, char **argv)
case 'p':
config_set_port(&config, optarg, 1);
break;
case 's':
logmsg = logsyslog;
break;
case 'v':
if (!loglevel++)
setvbuf(stdout, 0, _IOLBF, 0);
if (loglevel < log_debug)
loglevel++;
break;
case 'V':
print_version();
@@ -586,6 +698,16 @@ main(int argc, char **argv)
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);
@@ -603,58 +725,65 @@ main(int argc, char **argv)
if (r == -1)
die();
}
{
struct sigaction sa = {.sa_handler = sigusr1_handler};
int r = sigaction(SIGUSR1, &sa, 0);
if (r == -1)
die();
}
struct queue queue[1];
queue_init(queue);
struct fifo fifo[1];
fifo_init(fifo);
struct pollvec pollvec[1];
pollvec_init(pollvec);
unsigned long rng = epochms();
unsigned long rng = uepoch();
int server = server_create(config.port);
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) {
if (oldport != config.port || oldfamily != config.bind_family) {
close(server);
server = server_create(config.port);
server = server_create(config.port, config.bind_family);
}
reload = 0;
}
/* Enqueue the listening socket first */
pollvec_clear(pollvec);
if (queue->length < config.max_clients)
pollvec_push(pollvec, server, POLLIN);
else
pollvec_push(pollvec, -1, 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 = uepoch();
for (struct client *c = queue->head; c; c = c->next) {
if (c->send_next <= now) {
pollvec_push(pollvec, c->fd, POLLOUT);
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 = c->send_next - now;
timeout = fifo->head->send_next - now;
break;
}
}
/* Wait for next event */
logmsg(LOG_DEBUG, "poll(%zu, %d)%s", pollvec->fill, timeout,
queue->length >= config.max_clients ? " (no accept)" : "");
int r = poll(pollvec->fds, pollvec->fill, timeout);
logmsg(LOG_DEBUG, "= %d", r);
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");
logmsg(log_debug, "EINTR");
continue;
default:
fprintf(stderr, "endlessh: fatal: %s\n", strerror(errno));
@@ -663,18 +792,19 @@ main(int argc, char **argv)
}
/* Check for new incoming connections */
if (pollvec->fds[0].revents & POLLIN) {
if (fds.revents & POLLIN) {
int fd = accept(server, 0, 0);
logmsg(LOG_DEBUG, "accept() = %d", fd);
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 = queue->length;
logmsg(LOG_INFO,
config.max_clients = fifo->length;
logmsg(log_info,
"MaxClients %d",
queue->length);
fifo->length);
break;
case ECONNABORTED:
case EINTR:
@@ -688,51 +818,26 @@ main(int argc, char **argv)
exit(EXIT_FAILURE);
}
} else {
long long send_next = uepoch() + config.delay / 2;
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);
}
queue_append(queue, client);
logmsg(LOG_INFO, "ACCEPT host=%s port=%d fd=%d n=%d/%d",
client->ipaddr, client->port, client->fd,
queue->length, config.max_clients);
}
}
/* Write lines to ready clients */
for (size_t i = 1; i < pollvec->fill; i++) {
short fd = pollvec->fds[i].fd;
short revents = pollvec->fds[i].revents;
struct client *client = queue_remove(queue, fd);
if (revents & POLLHUP) {
client_destroy(client);
} else if (revents & POLLOUT) {
char line[256];
int len = randline(line, config.max_line_length, &rng);
for (;;) {
/* Don't really care if send is short */
ssize_t out = send(fd, line, len, MSG_DONTWAIT);
if (out == -1 && errno == EINTR) {
continue; /* try again */
} else if (out == -1) {
client_destroy(client);
break;
} else {
logmsg(LOG_DEBUG, "send(%d) = %d", fd, (int)out);
client->bytes_sent += out;
client->send_next = uepoch() + config.delay;
queue_append(queue, client);
break;
}
} else {
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);
}
}
}
}
pollvec_free(pollvec);
queue_destroy(queue);
fifo_destroy(fifo);
statistics_log_totals(0);
if (logmsg == logsyslog)
closelog();
}

View File

@@ -1,12 +1,13 @@
[Unit]
Description=Endlessh SSH Tarpit
Documentation=man:endlessh(1)
Requires=network-online.target
[Service]
Type=simple
Restart=always
RestartSec=30sec
ExecStart=/opt/endlessh/endlessh
ExecStart=/usr/local/bin/endlessh
KillSignal=SIGTERM
# Stop trying to restart the service if it restarts too many times in a row
@@ -21,9 +22,18 @@ PrivateTmp=true
PrivateDevices=true
ProtectSystem=full
ProtectHome=true
NoNewPrivileges=true
ConfigurationDirectory=/etc/endlessh
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
@@ -31,3 +41,4 @@ MemoryDenyWriteExecute=true
[Install]
WantedBy=multi-user.target

34
util/openbsd/README.md Normal file
View File

@@ -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:
```

9
util/openbsd/endlessh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/ksh
#
daemon="/usr/local/bin/endlessh"
rc_bg=YES
. /etc/rc.d/rc.subr
rc_cmd $1

25
util/smf/README Normal file
View File

@@ -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! :)

27
util/smf/endlessh.conf Normal file
View File

@@ -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

96
util/smf/endlessh.xml Normal file
View File

@@ -0,0 +1,96 @@
<?xml version="1.0"?>
<!DOCTYPE service_bundle SYSTEM "/usr/share/lib/xml/dtd/service_bundle.dtd.1">
<!-- Manifest-file for endlessh, put this file in
/var/svc/manifest/network/endlessh.xml
and run #svccfg import /var/svc/manifest/network/endlessh.xml
Fixed by Yuri Voinov (C) 2007,2019 -->
<service_bundle type='manifest' name='endlessh'>
<service
name='network/endlessh'
type='service'
version='1'>
<create_default_instance enabled='false' />
<single_instance />
<dependency name='fs-local'
grouping='require_all'
restart_on='none'
type='service'>
<service_fmri
value='svc:/system/filesystem/local' />
</dependency>
<dependency name='net-loopback'
grouping='require_all'
restart_on='none'
type='service'>
<service_fmri value='svc:/network/loopback' />
</dependency>
<dependency name='net-physical'
grouping='require_all'
restart_on='none'
type='service'>
<service_fmri value='svc:/network/physical' />
</dependency>
<dependency name='utmp'
grouping='require_all'
restart_on='none'
type='service'>
<service_fmri value='svc:/system/utmp' />
</dependency>
<dependency name='endlessh_config_data'
grouping='require_all'
restart_on='refresh'
type='path'>
<service_fmri value='file://localhost/usr/local/etc/endlessh.conf' />
</dependency>
<exec_method
type='method'
name='start'
exec='/lib/svc/method/init.endlessh %m'
timeout_seconds='60'/>
<exec_method
type='method'
name='stop'
exec=':kill'
timeout_seconds='60' />
<exec_method
type='method'
name='restart'
exec='/lib/svc/method/init.endlessh %m'
timeout_seconds='60' />
<exec_method
type='method'
name='refresh'
exec='/lib/svc/method/init.endlessh %m'
timeout_seconds='60' />
<property_group name='general' type='framework'>
<!-- to start stop endlessh -->
<propval name='action_authorization' type='astring'
value='solaris.smf.manage' />
</property_group>
<stability value='Unstable' />
<template>
<common_name>
<loctext xml:lang='C'>
endlessh service
</loctext>
</common_name>
</template>
</service>
</service_bundle>

137
util/smf/init.endlessh Normal file
View File

@@ -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