parent
96eec86c5b
commit
4c3beb8bcd
@ -0,0 +1,2 @@
|
|||||||
|
endlessh
|
||||||
|
|
@ -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"]
|
@ -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
|
@ -1,2 +1,143 @@
|
|||||||
# aperturessh
|
# 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/
|
@ -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 <http://unlicense.org/>
|
@ -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
|
@ -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 <time.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <limits.h>
|
||||||
|
#include <signal.h>
|
||||||
|
#include <stdarg.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#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 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();
|
||||||
|
}
|
@ -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
|
||||||
|
|
@ -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:
|
||||||
|
```
|
@ -0,0 +1,9 @@
|
|||||||
|
#!/bin/ksh
|
||||||
|
#
|
||||||
|
|
||||||
|
daemon="/usr/local/bin/endlessh"
|
||||||
|
rc_bg=YES
|
||||||
|
|
||||||
|
. /etc/rc.d/rc.subr
|
||||||
|
|
||||||
|
rc_cmd $1
|
@ -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 <log | sqlite3 -init util/schema.sql log.db
|
||||||
|
|
||||||
|
import sys
|
||||||
|
import pyrfc3339
|
||||||
|
|
||||||
|
table = {}
|
||||||
|
for line in sys.stdin:
|
||||||
|
parts = line.split(' ')
|
||||||
|
entry = {}
|
||||||
|
entry['logtime'] = pyrfc3339.parse(parts[0])
|
||||||
|
action = parts[1]
|
||||||
|
if action == 'ACCEPT' or action == 'CLOSE':
|
||||||
|
for item in parts[2:]:
|
||||||
|
key, value = item.split('=')
|
||||||
|
entry[key] = value
|
||||||
|
if action == 'ACCEPT':
|
||||||
|
table[entry['fd']] = entry
|
||||||
|
else:
|
||||||
|
accept = table[entry['fd']]
|
||||||
|
del table[entry['fd']]
|
||||||
|
delta = (entry['logtime'] - accept['logtime']).total_seconds()
|
||||||
|
host = entry['host']
|
||||||
|
port = entry['port']
|
||||||
|
if host.startswith('::ffff:'):
|
||||||
|
host = host[7:]
|
||||||
|
nbytes = int(entry['bytes'])
|
||||||
|
print('%s,%s,%.3f,%d' % (host, port, delta, nbytes))
|
||||||
|
|
||||||
|
if len(table) > 0:
|
||||||
|
print('warning: %d hanging entries' % len(table), file=sys.stderr)
|
@ -0,0 +1,8 @@
|
|||||||
|
CREATE TABLE IF NOT EXISTS log (
|
||||||
|
host TEXT,
|
||||||
|
port INTEGER,
|
||||||
|
time REAL,
|
||||||
|
bytes INTEGER
|
||||||
|
);
|
||||||
|
.mode csv
|
||||||
|
.import /dev/stdin log
|
@ -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! :)
|
@ -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
|
@ -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>
|
@ -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
|
Loading…
Reference in new issue