1 Commits

Author SHA1 Message Date
Christopher Wellons
30e3ff1f32 Example of making the first line special 2019-03-26 10:48:21 -04:00
9 changed files with 196 additions and 615 deletions

View File

@@ -3,18 +3,9 @@ CC = cc
CFLAGS = -std=c99 -Wall -Wextra -Wno-missing-field-initializers -Os CFLAGS = -std=c99 -Wall -Wextra -Wno-missing-field-initializers -Os
LDFLAGS = -ggdb3 LDFLAGS = -ggdb3
LDLIBS = LDLIBS =
PREFIX = /usr/local
all: endlessh
endlessh: endlessh.c endlessh: endlessh.c
$(CC) $(LDFLAGS) $(CFLAGS) -o $@ endlessh.c $(LDLIBS) $(CC) $(LDFLAGS) $(CFLAGS) -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: clean:
rm -rf endlessh rm -rf endlessh

View File

@@ -19,8 +19,6 @@ Usage information is printed with `-h`.
``` ```
Usage: endlessh [-vh] [-d MS] [-f CONFIG] [-l LEN] [-m LIMIT] [-p PORT] Usage: endlessh [-vh] [-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] -d INT Message millisecond delay [10000]
-f Set and load config file [/etc/endlessh/config] -f Set and load config file [/etc/endlessh/config]
-h Print this help message and exit -h Print this help message and exit
@@ -45,8 +43,6 @@ write a complete, consistent log.
A SIGHUP signal requests a reload of the configuration file (`-f`). A SIGHUP signal requests a reload of the configuration file (`-f`).
A SIGUSR1 signal will print connections stats to the log.
## Sample Configuration File ## Sample Configuration File
The configuration file has similar syntax to OpenSSH. The configuration file has similar syntax to OpenSSH.
@@ -73,40 +69,15 @@ MaxClients 4096
# 1 = Standard, useful log messages # 1 = Standard, useful log messages
# 2 = Very noisy debugging information # 2 = Very noisy debugging information
LogLevel 0 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 ## Build issues
Some more esoteric systems require extra configuration when building. RHEL 6 and CentOS 6 use a version of glibc older than 2.17 (December
2012), and `clock_gettime(2)` is still in librt. For these systems you
### RHEL 6 / CentOS 6 will need to link against librt:
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 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`.
[np]: https://nullprogram.com/blog/2019/03/22/ [np]: https://nullprogram.com/blog/2019/03/22/

View File

@@ -1,81 +0,0 @@
.Dd $Mdocdate: April 12 2019 $
.Dt ENDLESSH 1
.Os
.Sh NAME
.Nm endless
.Nd An SSH tarpit
.Sh SYNOPSIS
.Nm endless
.Op Fl 46chvV
.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 v
Print diagnostics to standard output. Can be specified
numerous times to increase verbosity.
.It Fl V
Causes
.Nm
to print version information and exit.
.El
.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.

View File

@@ -1,4 +1,22 @@
#define _XOPEN_SOURCE 600 #ifdef __FreeBSD__
# define _WITH_GETLINE
/* The MSG_DONTWAIT send(2) flag is non-standard, but widely available.
* However, FreeBSD doesn't define this flag when using POSIX feature
* test macros. Normally feature test macros are required to expose
* POSIX functionality, though FreeBSD isn't strict about this. In a
* sense it's technically correct to hide a non-standard flag when
* asking for strict standards compliance, but this behavior makes this
* flag impossible to use in portable programs, at least without this
* sort of special case.
*
* To get the prototype for getline(3), we need either a POSIX feature
* test macro or use the FreeBSD-specific _WITH_GETLINE macro. Since we
* can't use the former, we'll have to go with the latter.
*/
#else
# define _POSIX_C_SOURCE 200809L
#endif
#include <time.h> #include <time.h>
#include <errno.h> #include <errno.h>
#include <stdio.h> #include <stdio.h>
@@ -9,27 +27,28 @@
#include <string.h> #include <string.h>
#include <poll.h> #include <poll.h>
#include <fcntl.h>
#include <unistd.h> #include <unistd.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/socket.h> #include <sys/socket.h>
#include <arpa/inet.h> #include <arpa/inet.h>
#include <netinet/in.h> #include <netinet/in.h>
#define ENDLESSH_VERSION 1.0 #define ENDLESSH_VERSION 0.1
#define DEFAULT_PORT 2222 #define DEFAULT_PORT 2222
#define DEFAULT_DELAY 10000 /* milliseconds */ #define DEFAULT_DELAY 10000 /* milliseconds */
#define DEFAULT_MAX_LINE_LENGTH 32 #define DEFAULT_MAX_LINE_LENGTH 32
#define DEFAULT_MAX_CLIENTS 4096 #define DEFAULT_MAX_CLIENTS 4096
#define DEFAULT_CONFIG_FILE "/etc/endlessh/config" #define DEFAULT_CONFIG_FILE "/etc/endlessh/config"
#define DEFAULT_BIND_FAMILY AF_UNSPEC
#define FLAG_SPECIAL_SENT (1 << 0)
#define SPECIAL_MESSAGE "Stay awhile and listen\r\n"
#define XSTR(s) STR(s) #define XSTR(s) STR(s)
#define STR(s) #s #define STR(s) #s
static long long static long long
epochms(void) uepoch(void)
{ {
struct timespec tv; struct timespec tv;
clock_gettime(CLOCK_REALTIME, &tv); clock_gettime(CLOCK_REALTIME, &tv);
@@ -49,7 +68,7 @@ logmsg(enum loglevel level, const char *format, ...)
int save = errno; int save = errno;
/* Print a timestamp */ /* Print a timestamp */
long long now = epochms(); long long now = uepoch();
time_t t = now / 1000; time_t t = now / 1000;
char date[64]; char date[64];
struct tm tm[1]; struct tm tm[1];
@@ -67,12 +86,6 @@ logmsg(enum loglevel level, const char *format, ...)
} }
} }
struct {
long long connects;
long long milliseconds;
long long bytes_sent;
} statistics;
struct client { struct client {
char ipaddr[INET6_ADDRSTRLEN]; char ipaddr[INET6_ADDRSTRLEN];
long long connect_time; long long connect_time;
@@ -81,6 +94,7 @@ struct client {
struct client *next; struct client *next;
int port; int port;
int fd; int fd;
int flags;
}; };
static struct client * static struct client *
@@ -89,12 +103,12 @@ client_new(int fd, long long send_next)
struct client *c = malloc(sizeof(*c)); struct client *c = malloc(sizeof(*c));
if (c) { if (c) {
c->ipaddr[0] = 0; c->ipaddr[0] = 0;
c->connect_time = epochms(); c->connect_time = uepoch();
c->send_next = send_next; c->send_next = send_next;
c->bytes_sent = 0; c->bytes_sent = 0;
c->next = 0; c->next = 0;
c->fd = fd; c->fd = fd;
c->port = 0; c->flags = 0;
/* Set the smallest possible recieve buffer. This reduces local /* Set the smallest possible recieve buffer. This reduces local
* resource usage and slows down the remote end. * resource usage and slows down the remote end.
@@ -129,57 +143,59 @@ static void
client_destroy(struct client *client) 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; long long dt = uepoch() - client->connect_time;
logmsg(LOG_INFO, logmsg(LOG_INFO,
"CLOSE host=%s port=%d fd=%d " "CLOSE host=%s port=%d fd=%d "
"time=%lld.%03lld bytes=%lld", "time=%lld.%03lld bytes=%lld",
client->ipaddr, client->port, client->fd, client->ipaddr, client->port, client->fd,
dt / 1000, dt % 1000, dt / 1000, dt % 1000,
client->bytes_sent); client->bytes_sent);
statistics.milliseconds += dt;
close(client->fd); close(client->fd);
free(client); free(client);
} }
static void struct queue {
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 *head;
struct client *tail; struct client *tail;
int length; int length;
}; };
static void static void
fifo_init(struct fifo *q) queue_init(struct queue *q)
{ {
q->head = q->tail = 0; q->head = q->tail = 0;
q->length = 0; q->length = 0;
} }
static struct client * static struct client *
fifo_pop(struct fifo *q) queue_remove(struct queue *q, int fd)
{ {
struct client *removed = q->head; /* Yes, this is a linear search, but the element we're looking for
q->head = q->head->next; * is virtually always one of the first few elements.
removed->next = 0; */
if (!--q->length) struct client *c;
q->tail = 0; struct client *prev = 0;
return removed; 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;
} }
static void static void
fifo_append(struct fifo *q, struct client *c) queue_append(struct queue *q, struct client *c)
{ {
if (!q->tail) { if (!q->tail) {
q->head = q->tail = c; q->head = q->tail = c;
@@ -191,7 +207,7 @@ fifo_append(struct fifo *q, struct client *c)
} }
static void static void
fifo_destroy(struct fifo *q) queue_destroy(struct queue *q)
{ {
struct client *c = q->head; struct client *c = q->head;
while (c) { while (c) {
@@ -203,6 +219,56 @@ fifo_destroy(struct fifo *q)
q->length = 0; 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 static void
die(void) die(void)
{ {
@@ -248,21 +314,11 @@ sighup_handler(int signal)
reload = 1; reload = 1;
} }
static volatile sig_atomic_t dumpstats = 0;
static void
sigusr1_handler(int signal)
{
(void)signal;
dumpstats = 1;
}
struct config { struct config {
int port; int port;
int delay; int delay;
int max_line_length; int max_line_length;
int max_clients; int max_clients;
int bind_family;
}; };
#define CONFIG_DEFAULT { \ #define CONFIG_DEFAULT { \
@@ -270,7 +326,6 @@ struct config {
.delay = DEFAULT_DELAY, \ .delay = DEFAULT_DELAY, \
.max_line_length = DEFAULT_MAX_LINE_LENGTH, \ .max_line_length = DEFAULT_MAX_LINE_LENGTH, \
.max_clients = DEFAULT_MAX_CLIENTS, \ .max_clients = DEFAULT_MAX_CLIENTS, \
.bind_family = DEFAULT_BIND_FAMILY, \
} }
static void static void
@@ -333,27 +388,6 @@ 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 { enum config_key {
KEY_INVALID, KEY_INVALID,
KEY_PORT, KEY_PORT,
@@ -361,7 +395,6 @@ enum config_key {
KEY_MAX_LINE_LENGTH, KEY_MAX_LINE_LENGTH,
KEY_MAX_CLIENTS, KEY_MAX_CLIENTS,
KEY_LOG_LEVEL, KEY_LOG_LEVEL,
KEY_BIND_FAMILY,
}; };
static enum config_key static enum config_key
@@ -373,7 +406,6 @@ config_key_parse(const char *tok)
[KEY_MAX_LINE_LENGTH] = "MaxLineLength", [KEY_MAX_LINE_LENGTH] = "MaxLineLength",
[KEY_MAX_CLIENTS] = "MaxClients", [KEY_MAX_CLIENTS] = "MaxClients",
[KEY_LOG_LEVEL] = "LogLevel", [KEY_LOG_LEVEL] = "LogLevel",
[KEY_BIND_FAMILY] = "BindFamily"
}; };
for (size_t i = 1; i < sizeof(table) / sizeof(*table); i++) for (size_t i = 1; i < sizeof(table) / sizeof(*table); i++)
if (!strcmp(tok, table[i])) if (!strcmp(tok, table[i]))
@@ -387,8 +419,9 @@ config_load(struct config *c, const char *file, int hardfail)
long lineno = 0; long lineno = 0;
FILE *f = fopen(file, "r"); FILE *f = fopen(file, "r");
if (f) { if (f) {
char line[256]; size_t len = 0;
while (fgets(line, sizeof(line), f)) { char *line = 0;
while (getline(&line, &len, f) != -1) {
lineno++; lineno++;
/* Remove comments */ /* Remove comments */
@@ -440,9 +473,6 @@ config_load(struct config *c, const char *file, int hardfail)
case KEY_MAX_CLIENTS: case KEY_MAX_CLIENTS:
config_set_max_clients(c, tokens[1], hardfail); config_set_max_clients(c, tokens[1], hardfail);
break; break;
case KEY_BIND_FAMILY:
config_set_bind_family(c, tokens[1], hardfail);
break;
case KEY_LOG_LEVEL: { case KEY_LOG_LEVEL: {
errno = 0; errno = 0;
char *end; char *end;
@@ -458,6 +488,7 @@ config_load(struct config *c, const char *file, int hardfail)
} }
} }
free(line);
fclose(f); fclose(f);
} }
} }
@@ -469,19 +500,13 @@ config_log(const struct config *c)
logmsg(LOG_INFO, "Delay %ld", c->delay); logmsg(LOG_INFO, "Delay %ld", c->delay);
logmsg(LOG_INFO, "MaxLineLength %d", c->max_line_length); logmsg(LOG_INFO, "MaxLineLength %d", c->max_line_length);
logmsg(LOG_INFO, "MaxClients %d", c->max_clients); 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 static void
usage(FILE *f) usage(FILE *f)
{ {
fprintf(f, "Usage: endlessh [-vh] [-46] [-d MS] [-f CONFIG] [-l LEN] " fprintf(f, "Usage: endlessh [-vh] [-d MS] [-f CONFIG] [-l LEN] "
"[-m LIMIT] [-p PORT]\n"); "[-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 [" fprintf(f, " -d INT Message millisecond delay ["
XSTR(DEFAULT_DELAY) "]\n"); XSTR(DEFAULT_DELAY) "]\n");
fprintf(f, " -f Set and load config file [" fprintf(f, " -f Set and load config file ["
@@ -504,11 +529,11 @@ print_version(void)
} }
static int static int
server_create(int port, int family) server_create(int port)
{ {
int r, s, value; int r, s, value;
s = socket(family == AF_UNSPEC ? AF_INET6 : family, SOCK_STREAM, 0); s = socket(AF_INET6, SOCK_STREAM, 0);
logmsg(LOG_DEBUG, "socket() = %d", s); logmsg(LOG_DEBUG, "socket() = %d", s);
if (s == -1) die(); if (s == -1) die();
@@ -519,37 +544,12 @@ server_create(int port, int family)
if (r == -1) 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 = {
* With OpenBSD IPv6 sockets are always IPv6-only, so the socket option .sin6_family = AF_INET6,
* is read-only (not modifiable). .sin6_port = htons(port),
* http://man.openbsd.org/ip6#IPV6_V6ONLY .sin6_addr = in6addr_any
*/ };
#ifndef __OpenBSD__ r = bind(s, (void *)&addr, sizeof(addr));
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); logmsg(LOG_DEBUG, "bind(%d, port=%d) = %d", s, port, r);
if (r == -1) die(); if (r == -1) die();
@@ -560,33 +560,6 @@ server_create(int port, int family)
return s; 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 int
main(int argc, char **argv) main(int argc, char **argv)
{ {
@@ -595,14 +568,8 @@ main(int argc, char **argv)
config_load(&config, config_file, 1); config_load(&config, config_file, 1);
int option; int option;
while ((option = getopt(argc, argv, "46d:f:hl:m:p:vV")) != -1) { while ((option = getopt(argc, argv, "d:f:hl:m:p:vV")) != -1) {
switch (option) { switch (option) {
case '4':
config_set_bind_family(&config, "4", 1);
break;
case '6':
config_set_bind_family(&config, "6", 1);
break;
case 'd': case 'd':
config_set_delay(&config, optarg, 1); config_set_delay(&config, optarg, 1);
break; break;
@@ -624,7 +591,8 @@ main(int argc, char **argv)
config_set_port(&config, optarg, 1); config_set_port(&config, optarg, 1);
break; break;
case 'v': case 'v':
loglevel++; if (!loglevel++)
setvbuf(stdout, 0, _IOLBF, 0);
break; break;
case 'V': case 'V':
print_version(); print_version();
@@ -641,9 +609,6 @@ main(int argc, char **argv)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
/* Set output (log) to line buffered */
setvbuf(stdout, 0, _IOLBF, 0);
/* Log configuration */ /* Log configuration */
config_log(&config); config_log(&config);
@@ -661,60 +626,53 @@ main(int argc, char **argv)
if (r == -1) if (r == -1)
die(); die();
} }
{
struct sigaction sa = {.sa_handler = sigusr1_handler};
int r = sigaction(SIGUSR1, &sa, 0);
if (r == -1)
die();
}
struct fifo fifo[1]; struct queue queue[1];
fifo_init(fifo); queue_init(queue);
unsigned long rng = epochms(); struct pollvec pollvec[1];
pollvec_init(pollvec);
int server = server_create(config.port, config.bind_family); unsigned long rng = uepoch();
int server = server_create(config.port);
while (running) { while (running) {
if (reload) { if (reload) {
/* Configuration reload requested (SIGHUP) */ /* Configuration reload requested (SIGHUP) */
int oldport = config.port; int oldport = config.port;
int oldfamily = config.bind_family;
config_load(&config, config_file, 0); config_load(&config, config_file, 0);
config_log(&config); config_log(&config);
if (oldport != config.port || oldfamily != config.bind_family) { if (oldport != config.port) {
close(server); close(server);
server = server_create(config.port, config.bind_family); server = server_create(config.port);
} }
reload = 0; reload = 0;
} }
if (dumpstats) {
/* print stats requested (SIGUSR1) */ /* Enqueue the listening socket first */
statistics_log_totals(fifo->head); pollvec_clear(pollvec);
dumpstats = 0; if (queue->length < config.max_clients)
} pollvec_push(pollvec, server, POLLIN);
else
pollvec_push(pollvec, -1, 0);
/* Enqueue clients that are due for another message */ /* Enqueue clients that are due for another message */
int timeout = -1; int timeout = -1;
long long now = epochms(); long long now = uepoch();
while (fifo->head) { for (struct client *c = queue->head; c; c = c->next) {
if (fifo->head->send_next <= now) { if (c->send_next <= now) {
struct client *c = fifo_pop(fifo); pollvec_push(pollvec, c->fd, POLLOUT);
if (sendline(c, config.max_line_length, &rng)) {
c->send_next = now + config.delay;
fifo_append(fifo, c);
}
} else { } else {
timeout = fifo->head->send_next - now; timeout = c->send_next - now;
break; break;
} }
} }
/* Wait for next event */ /* Wait for next event */
struct pollfd fds = {server, POLLIN, 0}; logmsg(LOG_DEBUG, "poll(%zu, %d)%s", pollvec->fill, timeout,
int nfds = fifo->length < config.max_clients; queue->length >= config.max_clients ? " (no accept)" : "");
logmsg(LOG_DEBUG, "poll(%d, %d)", nfds, timeout); int r = poll(pollvec->fds, pollvec->fill, timeout);
int r = poll(&fds, nfds, timeout);
logmsg(LOG_DEBUG, "= %d", r); logmsg(LOG_DEBUG, "= %d", r);
if (r == -1) { if (r == -1) {
switch (errno) { switch (errno) {
@@ -728,19 +686,18 @@ main(int argc, char **argv)
} }
/* Check for new incoming connections */ /* Check for new incoming connections */
if (fds.revents & POLLIN) { if (pollvec->fds[0].revents & POLLIN) {
int fd = accept(server, 0, 0); int fd = accept(server, 0, 0);
logmsg(LOG_DEBUG, "accept() = %d", fd); logmsg(LOG_DEBUG, "accept() = %d", fd);
statistics.connects++;
if (fd == -1) { if (fd == -1) {
const char *msg = strerror(errno); const char *msg = strerror(errno);
switch (errno) { switch (errno) {
case EMFILE: case EMFILE:
case ENFILE: case ENFILE:
config.max_clients = fifo->length; config.max_clients = queue->length;
logmsg(LOG_INFO, logmsg(LOG_INFO,
"MaxClients %d", "MaxClients %d",
fifo->length); queue->length);
break; break;
case ECONNABORTED: case ECONNABORTED:
case EINTR: case EINTR:
@@ -754,22 +711,59 @@ main(int argc, char **argv)
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
} else { } else {
long long send_next = epochms() + config.delay; long long send_next = uepoch() + config.delay / 2;
struct client *client = client_new(fd, send_next); 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) { if (!client) {
fprintf(stderr, "endlessh: warning: out of memory\n"); fprintf(stderr, "endlessh: warning: out of memory\n");
close(fd); close(fd);
} }
fifo_append(fifo, client); queue_append(queue, client);
logmsg(LOG_INFO, "ACCEPT host=%s port=%d fd=%d n=%d/%d", logmsg(LOG_INFO, "ACCEPT host=%s port=%d fd=%d n=%d/%d",
client->ipaddr, client->port, client->fd, client->ipaddr, client->port, client->fd,
fifo->length, config.max_clients); 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;
if (!(client->flags & FLAG_SPECIAL_SENT)) {
static const char special[] = SPECIAL_MESSAGE;
len = sizeof(special) - 1;
memcpy(line, special, len);
client->flags |= FLAG_SPECIAL_SENT;
} else {
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;
}
}
} }
} }
} }
fifo_destroy(fifo); pollvec_free(pollvec);
statistics_log_totals(0); queue_destroy(queue);
} }

View File

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

View File

@@ -1,25 +0,0 @@
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! :)

View File

@@ -1,27 +0,0 @@
# 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

View File

@@ -1,96 +0,0 @@
<?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>

View File

@@ -1,137 +0,0 @@
#!/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