1
0
mirror of https://github.com/skeeto/endlessh.git synced 2024-05-19 06:49:58 +00:00

24 Commits
1.0 ... master

Author SHA1 Message Date
dfe44eb2c5 Mark file local statistics struct static (#63) 2021-04-30 10:00:40 -04:00
1ecaafd577 Fix format string defect in log message (#63) 2021-04-30 09:52:48 -04:00
a5913cbbb2 Three notes on OpenBSD. 2020-12-24 00:43:20 +01:00
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
8daa5992f1 Bump to version 1.1 2020-01-31 11:47:27 -05:00
ad7031f79a Enable logging to syslog with -s 2020-01-31 17:28:30 +01:00
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
e4f8c9f8f4 Limit the maximum log level settable from the command line 2020-01-29 19:49:11 +01:00
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
2602caa459 Don't dereference NULL pointer on OOM
Fixes #37.
2019-12-30 13:11:02 -05:00
715f30c3a7 Add public domain dedication to the source header 2019-08-06 20:31:35 -04:00
3d6aec6080 Prevent access to /run and /var in endlessh.service
Closes #34.
2019-08-06 20:23:11 -04:00
ae7473536e Add Documentation link in endlessh.service
Closes #33.
2019-08-06 20:18:39 -04:00
33dff0cfc9 Balance list begin (.Bl) and end (.El) in man page
Ref: #35
2019-08-06 20:10:35 -04:00
f465f2dcbb Tweak some macro formatting for consistency 2019-08-06 19:57:49 -04:00
df0ffbf629 Use unveil(2) to restrict reading config file only
Closes #36.
2019-08-06 19:57:38 -04:00
b2c811ecf7 Add pledge for OpenBSD
Closes #32.
2019-08-05 16:55:33 -04:00
a154fcaf43 better name for the config file 2019-05-14 21:05:48 +03:00
6b721e58ac config file location on FreeBSD 2019-05-14 18:56:01 +03:00
8ec96ea899 fix typo 2019-05-13 15:25:24 +03:00
44b3285bb2 PrivateUsers=true prevents privileged port mapping 2019-05-13 15:25:04 +03:00
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
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
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
6 changed files with 198 additions and 60 deletions

View File

@ -1,14 +1,15 @@
.POSIX:
CC = cc
CFLAGS = -std=c99 -Wall -Wextra -Wno-missing-field-initializers -Os
LDFLAGS = -ggdb3
LDLIBS =
PREFIX = /usr/local
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

View File

@ -11,14 +11,12 @@ 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 [-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]
@ -27,7 +25,8 @@ Usage: endlessh [-vh] [-d MS] [-f CONFIG] [-l LEN] [-m LIMIT] [-p PORT]
-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`
@ -36,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
@ -108,5 +108,26 @@ to remove GCC-specific options. For example, on Solaris:
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/

View File

@ -1,4 +1,4 @@
.Dd $Mdocdate: April 12 2019 $
.Dd $Mdocdate: January 29 2020 $
.Dt ENDLESSH 1
.Os
.Sh NAME
@ -6,7 +6,7 @@
.Nd An SSH tarpit
.Sh SYNOPSIS
.Nm endless
.Op Fl 46chvV
.Op Fl 46chsvV
.Op Fl d Ar delay
.Op Fl f Ar config
.Op Fl l Ar max banner length
@ -55,15 +55,17 @@ Maximum number of clients. Default: 4096
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 to standard output. Can be specified
numerous times to increase verbosity.
Print diagnostics. Can be specified up to twice to increase verbosity.
.It Fl V
Causes
.Nm
to print version information and exit.
.El
.El
.Pp
If
.Nm
@ -79,3 +81,4 @@ A SIGUSR1 signal will print connections stats to the log.
The default
.Nm
configuration file.
.El

View File

@ -1,4 +1,13 @@
#define _XOPEN_SOURCE 600
/* 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>
@ -15,14 +24,21 @@
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <syslog.h>
#define ENDLESSH_VERSION 1.0
#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)
@ -37,13 +53,15 @@ epochms(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;
@ -67,7 +85,27 @@ logmsg(enum loglevel level, const char *format, ...)
}
}
struct {
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;
@ -101,9 +139,9 @@ client_new(int fd, long long send_next)
*/
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;
@ -128,9 +166,9 @@ client_new(int fd, long long send_next)
static void
client_destroy(struct client *client)
{
logmsg(LOG_DEBUG, "close(%d)", client->fd);
logmsg(log_debug, "close(%d)", client->fd);
long long dt = epochms() - client->connect_time;
logmsg(LOG_INFO,
logmsg(log_info,
"CLOSE host=%s port=%d fd=%d "
"time=%lld.%03lld bytes=%lld",
client->ipaddr, client->port, client->fd,
@ -147,7 +185,7 @@ 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",
logmsg(log_info, "TOTALS connects=%lld seconds=%lld.%03lld bytes=%lld",
statistics.connects,
milliseconds / 1000,
milliseconds % 1000,
@ -447,7 +485,7 @@ config_load(struct config *c, const char *file, int hardfail)
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,11 +503,11 @@ 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, "BindFamily %s",
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");
@ -509,15 +547,15 @@ 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);
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));
/*
* With OpenBSD IPv6 sockets are always IPv6-only, so the socket option
@ -529,9 +567,9 @@ server_create(int port, int family)
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);
logmsg(log_debug, "setsockopt(%d, IPV6_V6ONLY, 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));
}
#endif
@ -550,11 +588,11 @@ server_create(int port, int family)
};
r = bind(s, (void *)&addr6, sizeof(addr6));
}
logmsg(LOG_DEBUG, "bind(%d, port=%d) = %d", s, port, r);
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;
@ -568,7 +606,7 @@ sendline(struct client *client, int max_line_length, unsigned long *rng)
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);
logmsg(log_debug, "write(%d) = %d", client->fd, (int)out);
if (out == -1) {
if (errno == EINTR) {
continue; /* try again */
@ -590,12 +628,20 @@ sendline(struct client *client, int max_line_length, unsigned long *rng)
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: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);
@ -608,6 +654,13 @@ main(int argc, char **argv)
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':
@ -623,8 +676,12 @@ main(int argc, char **argv)
case 'p':
config_set_port(&config, optarg, 1);
break;
case 's':
logmsg = logsyslog;
break;
case 'v':
loglevel++;
if (loglevel < log_debug)
loglevel++;
break;
case 'V':
print_version();
@ -641,8 +698,15 @@ main(int argc, char **argv)
exit(EXIT_FAILURE);
}
/* Set output (log) to line buffered */
setvbuf(stdout, 0, _IOLBF, 0);
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);
@ -713,13 +777,13 @@ main(int argc, char **argv)
/* 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);
logmsg(log_debug, "poll(%d, %d)", nfds, timeout);
int r = poll(&fds, nfds, timeout);
logmsg(LOG_DEBUG, "= %d", r);
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));
@ -730,7 +794,7 @@ main(int argc, char **argv)
/* Check for new incoming connections */
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);
@ -738,7 +802,7 @@ main(int argc, char **argv)
case EMFILE:
case ENFILE:
config.max_clients = fifo->length;
logmsg(LOG_INFO,
logmsg(log_info,
"MaxClients %d",
fifo->length);
break;
@ -761,15 +825,19 @@ main(int argc, char **argv)
if (!client) {
fprintf(stderr, "endlessh: warning: out of memory\n");
close(fd);
} 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);
}
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();
}

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