mirror of
https://github.com/skeeto/endlessh.git
synced 2024-11-21 21:23:07 +01:00
Initial working code
This commit is contained in:
commit
7fbba1e7b0
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
endlessh
|
11
Makefile
Normal file
11
Makefile
Normal file
@ -0,0 +1,11 @@
|
||||
.POSIX:
|
||||
CC = cc
|
||||
CFLAGS = -std=c99 -Wall -Wextra -Wno-missing-field-initializers -Os
|
||||
LDFLAGS = -ggdb3
|
||||
LDLIBS =
|
||||
|
||||
endlessh: endlessh.c
|
||||
$(CC) $(LDFLAGS) $(CFLAGS) -o $@ endlessh.c $(LDLIBS)
|
||||
|
||||
clean:
|
||||
rm -rf endlessh
|
29
README.md
Normal file
29
README.md
Normal file
@ -0,0 +1,29 @@
|
||||
# Endlessh: an SSH tarpit
|
||||
|
||||
Endlessh is an SSH tarpit that *very* slowly sends a randomized SSH
|
||||
banner. It keeps clients locked up for hours or even days at at time.
|
||||
The idea is that you put your real SSH server on another port and let
|
||||
the script kiddies themselves stuck in this tarpit instead of bothering
|
||||
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 [-vh] [-d MSECS] [-m LIMIT] [-p PORT]
|
||||
-d INT Message millisecond delay [10000]
|
||||
-h Print this help message and exit
|
||||
-m INT Maximum number of clients [4096]
|
||||
-p INT Listening port [2222]
|
||||
-v Print diagnostics to standard output (repeatable)
|
||||
```
|
||||
|
||||
The purpose of limiting the number of clients (`-m`) is to avoid tying
|
||||
up too many system resources with the tarpit. Clients beyond this limit
|
||||
are left in the accept queue, not rejected instantly.
|
24
UNLICENSE
Normal file
24
UNLICENSE
Normal file
@ -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/>
|
340
endlessh.c
Normal file
340
endlessh.c
Normal file
@ -0,0 +1,340 @@
|
||||
#define _POSIX_C_SOURCE 200112L
|
||||
#include <time.h>
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <assert.h>
|
||||
#include <limits.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
|
||||
#define DEFAULT_MAX_CLIENTS 4096
|
||||
#define DEFAULT_PORT 2222
|
||||
#define DEFAULT_DELAY 10000 // milliseconds
|
||||
|
||||
#define XSTR(s) STR(s)
|
||||
#define STR(s) #s
|
||||
|
||||
static long long
|
||||
uepoch(void)
|
||||
{
|
||||
struct timespec tv;
|
||||
clock_gettime(CLOCK_REALTIME, &tv);
|
||||
return tv.tv_sec * 1000ULL + tv.tv_nsec / 1000000ULL;
|
||||
}
|
||||
|
||||
struct client {
|
||||
long long send_next; // epoch milliseconds for next line
|
||||
struct client *next;
|
||||
int fd;
|
||||
};
|
||||
|
||||
static struct client *
|
||||
client_new(int fd, long long send_next)
|
||||
{
|
||||
struct client *c = malloc(sizeof(*c));
|
||||
if (c) {
|
||||
c->send_next = send_next;
|
||||
c->next = 0;
|
||||
c->fd = fd;
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
struct queue {
|
||||
struct client *head;
|
||||
struct client *tail;
|
||||
};
|
||||
|
||||
static void
|
||||
queue_init(struct queue *q)
|
||||
{
|
||||
q->head = q->tail = 0;
|
||||
}
|
||||
|
||||
static struct client *
|
||||
queue_remove(struct queue *q, int fd)
|
||||
{
|
||||
struct client *c;
|
||||
struct client **prev = &q->head;
|
||||
for (c = q->head; c; prev = &c->next, c = c->next) {
|
||||
if (c->fd == fd) {
|
||||
if (q->tail == c)
|
||||
q->tail = 0;
|
||||
*prev = c->next;
|
||||
c->next = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return c;
|
||||
}
|
||||
|
||||
static void
|
||||
queue_append(struct queue *q, struct client *c)
|
||||
{
|
||||
if (!q->tail) {
|
||||
q->head = q->tail = c;
|
||||
} else {
|
||||
q->tail->next = c;
|
||||
q->tail = c;
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
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);
|
||||
if (size < v->cap * sizeof(*v->fds))
|
||||
return 0; // overflow
|
||||
struct pollfd *grow = realloc(v->fds, size);
|
||||
if (!grow)
|
||||
return 0;
|
||||
v->fds = grow;
|
||||
}
|
||||
v->fds[v->fill].fd = fd;
|
||||
v->fds[v->fill].events = events;
|
||||
v->fill++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void
|
||||
check(int r)
|
||||
{
|
||||
if (r == -1) {
|
||||
fprintf(stderr, "endlessh: fatal: %s\n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
static int
|
||||
randline(char *line)
|
||||
{
|
||||
int len = 1 + rand() % 252;
|
||||
for (int i = 0; i < len - 2; i++)
|
||||
line[i] = 32 + rand() % 95;
|
||||
line[len - 2] = 13;
|
||||
line[len - 1] = 10;
|
||||
if (memcmp(line, "SSH-", 4) == 0)
|
||||
line[0] = 'X';
|
||||
return len;
|
||||
}
|
||||
|
||||
static void
|
||||
log(const char *format, ...)
|
||||
{
|
||||
long long now = uepoch();
|
||||
fprintf(stderr, "%lld.%03lld: ", now / 1000, now % 1000);
|
||||
va_list ap;
|
||||
va_start(ap, format);
|
||||
vfprintf(stderr, format, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', stderr);
|
||||
}
|
||||
|
||||
static void
|
||||
usage(FILE *f)
|
||||
{
|
||||
fprintf(f, "Usage: endlessh [-vh] [-d MSECS] [-m LIMIT] [-p PORT]\n");
|
||||
fprintf(f, " -d INT Message millisecond delay ["
|
||||
XSTR(DEFAULT_DELAY) "]\n");
|
||||
fprintf(f, " -h Print this help message and exit\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");
|
||||
}
|
||||
|
||||
int
|
||||
main(int argc, char **argv)
|
||||
{
|
||||
int verbose = 0;
|
||||
int port = DEFAULT_PORT;
|
||||
long delay = DEFAULT_DELAY;
|
||||
long max_clients = DEFAULT_MAX_CLIENTS;
|
||||
|
||||
int option;
|
||||
while ((option = getopt(argc, argv, "d:hm:p:v")) != -1) {
|
||||
long tmp;
|
||||
char *end;
|
||||
switch (option) {
|
||||
case 'd':
|
||||
delay = strtol(optarg, &end, 10);
|
||||
if (errno || *end || delay < 0) {
|
||||
fprintf(stderr, "endlessh: Invalid delay: %s\n", optarg);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
break;
|
||||
case 'h':
|
||||
usage(stdout);
|
||||
exit(EXIT_SUCCESS);
|
||||
break;
|
||||
case 'm':
|
||||
tmp = strtol(optarg, &end, 10);
|
||||
if (errno || *end || tmp < 0) {
|
||||
fprintf(stderr, "endlessh: Invalid port: %s\n", optarg);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
max_clients = tmp;
|
||||
break;
|
||||
case 'p':
|
||||
tmp = strtol(optarg, &end, 10);
|
||||
if (errno || *end || tmp < 0 || tmp > 65535) {
|
||||
fprintf(stderr, "endlessh: Invalid port: %s\n", optarg);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
port = tmp;
|
||||
break;
|
||||
case 'v':
|
||||
verbose++;
|
||||
break;
|
||||
default:
|
||||
usage(stderr);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
long nclients = 0;
|
||||
|
||||
struct queue queue[1];
|
||||
queue_init(queue);
|
||||
|
||||
struct pollvec pollvec[1];
|
||||
pollvec_init(pollvec);
|
||||
|
||||
int server = socket(AF_INET, SOCK_STREAM, 0);
|
||||
check(server);
|
||||
|
||||
int dummy = 1;
|
||||
check(setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &dummy, sizeof(dummy)));
|
||||
|
||||
struct sockaddr_in addr = {AF_INET, htons(port), {htonl(INADDR_ANY)}};
|
||||
check(bind(server, (void *)&addr, sizeof(addr)));
|
||||
check(listen(server, INT_MAX));
|
||||
|
||||
if (verbose >= 1)
|
||||
log("listen(port=%d)", port);
|
||||
|
||||
srand(time(0));
|
||||
for (;;) {
|
||||
pollvec_clear(pollvec);
|
||||
pollvec_push(pollvec, nclients < max_clients ? server : -1, POLLIN);
|
||||
|
||||
/* Poll clients that are due for another message */
|
||||
int timeout = -1;
|
||||
long long now = uepoch();
|
||||
for (struct client *c = queue->head; c; c = c->next) {
|
||||
if (c->send_next <= now) {
|
||||
pollvec_push(pollvec, c->fd, POLLOUT);
|
||||
} else {
|
||||
timeout = c->send_next - now;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Wait for next event */
|
||||
if (verbose >= 2)
|
||||
log("poll(%zu, %d)%s", pollvec->fill, timeout,
|
||||
nclients == max_clients ? " (no accept)" : "");
|
||||
int r = poll(pollvec->fds, pollvec->fill, timeout);
|
||||
if (verbose >= 2)
|
||||
log("= %d", r);
|
||||
if (r == -1) {
|
||||
switch (errno) {
|
||||
case EINTR:
|
||||
if (verbose >= 1)
|
||||
log("EINTR");
|
||||
continue;
|
||||
default:
|
||||
fprintf(stderr, "endlessh: fatal: %s\n", strerror(errno));
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
|
||||
/* Check for new incoming connections */
|
||||
if (pollvec->fds[0].revents & POLLIN) {
|
||||
int fd = accept(server, 0, 0);
|
||||
if (verbose >= 1)
|
||||
log("accept() = %d", fd);
|
||||
if (fd == -1) {
|
||||
const char *msg = strerror(errno);
|
||||
switch (errno) {
|
||||
case ECONNABORTED:
|
||||
case EINTR:
|
||||
case EMFILE:
|
||||
case ENFILE:
|
||||
case ENOBUFS:
|
||||
case ENOMEM:
|
||||
case EPROTO:
|
||||
fprintf(stderr, "endlessh: warning: %s\n", msg);
|
||||
continue;
|
||||
default:
|
||||
fprintf(stderr, "endlessh: fatal: %s\n", msg);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
} else {
|
||||
struct client *client = client_new(fd, uepoch() + delay / 2);
|
||||
if (!client) {
|
||||
fprintf(stderr, "endlessh: warning: out of memory\n");
|
||||
close(fd);
|
||||
}
|
||||
queue_append(queue, client);
|
||||
nclients++;
|
||||
}
|
||||
}
|
||||
|
||||
/* 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) {
|
||||
if (verbose >= 1)
|
||||
log("close(%d)", fd);
|
||||
close(fd);
|
||||
free(client);
|
||||
nclients--;
|
||||
|
||||
} else if (revents & POLLOUT) {
|
||||
char line[255];
|
||||
int len = randline(line);
|
||||
/* Don't really care if this send fails */
|
||||
send(fd, line, len, MSG_DONTWAIT);
|
||||
client->send_next = uepoch() + delay;
|
||||
queue_append(queue, client);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user