From 7c625e57d8bf86196fa434fa2343c50582cdbc9e Mon Sep 17 00:00:00 2001 From: Nakidai Date: Wed, 9 Jul 2025 18:17:21 +0300 Subject: Add code --- Makefile | 3 + README_RU | 96 +++++++++++++ common.h | 83 +++++++++++ config-common.h | 10 ++ config-farsh.h | 8 ++ config-farshd.h | 14 ++ crypto-sponge.h | 276 +++++++++++++++++++++++++++++++++++++ farsh.c | 353 +++++++++++++++++++++++++++++++++++++++++++++++ farshd.c | 420 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ shorttypes.h | 36 +++++ 10 files changed, 1299 insertions(+) create mode 100644 Makefile create mode 100644 README_RU create mode 100644 common.h create mode 100644 config-common.h create mode 100644 config-farsh.h create mode 100644 config-farshd.h create mode 100644 crypto-sponge.h create mode 100644 farsh.c create mode 100644 farshd.c create mode 100644 shorttypes.h diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..9347fa2 --- /dev/null +++ b/Makefile @@ -0,0 +1,3 @@ +all: farsh farshd +clean: + rm -rf farsh farshd diff --git a/README_RU b/README_RU new file mode 100644 index 0000000..f82d001 --- /dev/null +++ b/README_RU @@ -0,0 +1,96 @@ +FAt Remoute SHell + +Перед сборкой убедиться, что в config-farshd.h выставлен нужный шелл. +Выставить переменные окружения CC, CFLAGS и т.п. и собрать: + + export CFLAGS="-O2" + make farsh + make farshd + +Ну или вручную: + + cc -O2 -o farsh farsh.c + cc -O2 -o farshd farshd.c + + + +Приблизительный алгоритм работы программы. + +Максимальный размер пакета задаётся MAX_PKT_SZ, это нужно чтоб не возиться с +Path MTU Discovery. Интервал отправки пакетов задаётся в PKT_INT. Эти +значения ограничивают макс. пропускную способность. Значение PKT_INT на +клиенте рекоммендуется выставить меньше чем на сервере. + +Бесконечный цикл на клиенте: + +1) Если есть данные с терминала, то пишем их в кольцевой буфер +2) Если пришёл пакет, то читаем из него данные и пишем их в stdout +3) Если прошло PKT_INT микросекунд или около того (PKT_INT - MIN_SLEEP), то + пора сформировать пакет: + 1) если соединение не установлено, то выставляем в пакете бит CONNECT и + добавляем в него значение перемнной окружения TERM. + 2) Получаем размеры терминала, если они отличаются от тех, что мы раньше + отправляли на сервер, то выставляем в пакете бит PING и добавляем + туда новые размеры терминала. + 3) Если в кольцевом буфере есть данные, то добавляем их в пакет, а также + добавляем состояния потоков: количество принятых от сервера байт + данных (не просто байт а именно тех что пишем в терминал) и + количество байт данных которое сервер получил от клиента (клиент + получает эти данные с сервера). Выставляем бит SSTATE в пакете. + 4) Если данных нет, но состояния потоков разные на сервере и клиенте, то + выставляем бит SSTATE в пакете и добавляем состояния потоков в пакет. + 5) Если получился не пустой пакет, то отправляем его. Если с момента + последнего полученного от сервера пакета прошло TIMEOUT_P секунд, то + добавляем в пакет PING, размеры терминала и шлём. + +Клиент перестаёт слать пакеты с битом CONNECT после получения пакета от +сервера с таким битом. + +Бесконечный цикл на сервере: + +1) Если что-то есть в клиентских псевдотерминалах, то читаем это в + клиентские колцевые буферы. +2) Если есть пакет от клиента, то пишем данные из пакета в клиентский + псевдотерминал. Обновляем состояния потоков. Обновляем размеры терминала, + если они есть в паете. +3) Прошло PKT_INT микросекунд или около того. Пробегаем по всем клиентам и + формируем пакет если: + а) был пакет от клиента с битом PING + б) был пакет от клиента с битом SSTATE + в) в кольцевом буфере клиента есть данные. + +После установки соединения сервер шлёт пакеты с битом CONNECT пока не +получит от клиента подряд NOT_CONNECTED пакетов без бита CONNECT. После +этого сервер перстаёт добавлять CONNECT бит в пакет и если получит пакет с +этим битом от клиента, то отключит его. NOT_CONNECTED пакетов подряд нужно +на случай перестановки пакетов (хоть это и маловероятно из-за большого +интервала между пакетами). + + + +Формат пакета: + +Первые два байта это u16 переменная, которая может содержать флаги: +CONNECT, PING, SSTATE. + +Вторые 2 байта, это u16 переменная, в которой будет размер данных в пакете +(может быть 0). + +Флаг CONNECT озанчает что сразу за первыми 4 байтами пакета идёт буфер +размером CONNECT байт с содержимым клиентской переменной TERM (если пакет от +сервера, то ничего не добавляется). + +Флаг PING означает что сразу за первыми 4 байтами (или за первыми 4+CONNECT +байтами) идут две u16 переменных, в которых содержатся размеры терминала. +Сервер должен ответить на этот пакет, и он шлёт те значения которые получил +последние. + +Флаг SSTATE означает что в пакет добавлены состояния потоков: две u32 +переменные. + +Значения флагов выбраны таким образом что образуемая ими u16 переменная +содержит количество данных в пакете помимо данных для терминала (кроме +случая, когда сервер шлёт пакет с битом CONNECT). Так добавив к этой +переменной 4 (первые байты) можно получить смещение данных в пакете. + + diff --git a/common.h b/common.h new file mode 100644 index 0000000..2059d81 --- /dev/null +++ b/common.h @@ -0,0 +1,83 @@ +/* common (not config, need code patch after change) */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "shorttypes.h" +#include "crypto-sponge.h" + +#define ERRDIE(a, b) if ((a) == -1) perror((b)), exit(1) + +#define NONCE_SZ 16 +#define HMAC_SZ 16 + +#define MIN_PKT_SZ NONCE_SZ + HMAC_SZ + 4 + 4 + 8 + 1 +#if MAX_PKT_SZ < MIN_PKT_SZ +#error "MAX_PKT_SZ < MIN_PKT_SZ" +#endif + +#define CONNECT (u16)32 +#define PING (u16)4 +#define SSTATE (u16)8 + +u8 key[32]; +u8 password[PASSWORD_BUF_SZ]; +u32 prng_state[14]; + + +static void send_pkt +(s32 skt, void *buf, u16 sz, struct sockaddr_storage *addr){ + ssize_t res; + u8 *nonce, *tag; + + /* nonce, tag */ + nonce = (u8*)buf + sz; + tag = nonce + NONCE_SZ; + duplex257_prng_rand16(prng_state, nonce); + + /* encrypt */ + duplex257_ae_encrypt(key, nonce, tag, buf, sz); + sz += NONCE_SZ + HMAC_SZ; + + /* send */ + res = sendto(skt, buf, sz, 0,(struct sockaddr*)addr, sizeof(*addr)); + ERRDIE(res, "sendto"); + return; +} + +static s32 recv_pkt +(s32 skt, void *buf, u16 sz, struct sockaddr_storage *addr){ + socklen_t skl; + s32 res; + u8 *nonce, *rtag; + u8 tag[HMAC_SZ]; + + /* recv */ + skl = sizeof(*addr); + res = recvfrom(skt, buf, sz, 0, (struct sockaddr*)addr, &skl); + ERRDIE(res, "recvfrom"); + + /* drop packet if it too small */ + if (res < 4 + NONCE_SZ + HMAC_SZ) return -1; + + /* nonce, recieved tag */ + rtag = buf + res - HMAC_SZ; + nonce = rtag - NONCE_SZ; + + /* decrypt */ + res -= NONCE_SZ + HMAC_SZ; + duplex257_ae_decrypt(key, nonce, tag, buf, res); + if (0 != memcmp(tag, rtag, HMAC_SZ)) return -1; + + return res; +} + diff --git a/config-common.h b/config-common.h new file mode 100644 index 0000000..64c1840 --- /dev/null +++ b/config-common.h @@ -0,0 +1,10 @@ +/* common config */ + +#define PASSWORD_BUF_SZ 512 /* 256 russian utf8 chars */ +#define MAX_PKT_SZ 1024 +#define TIMEOUT_D 180 /* timeout (no packets) in seconds for disconnect */ + +/* send packet every X microseconds, where + * PKT_INT >= X >= PKT_INT - MIN_SLEEP */ +#define MIN_SLEEP 3000 + diff --git a/config-farsh.h b/config-farsh.h new file mode 100644 index 0000000..4465b31 --- /dev/null +++ b/config-farsh.h @@ -0,0 +1,8 @@ +/* client config */ + +#define DISCONNECT_KEY '~' /* press ESC and this key to disconnect */ +#define PKT_INT 24000u /* packet sending interval in microseconds */ +#define RB_SZ 0x1000u /* ring buf size, 4kb, must be power of 2, 64kb max */ +#define TIMEOUT_P 10 /* timeout (no packets) in seconds for send ping */ +#define TIMEOUT_C 5 /* connection timeout in seconds */ + diff --git a/config-farshd.h b/config-farshd.h new file mode 100644 index 0000000..3a6e48e --- /dev/null +++ b/config-farshd.h @@ -0,0 +1,14 @@ +/* server config */ + +#define SHELL "/bin/bash" +#define LOGFILE "/tmp/farshd.log" +#define MAX_PEERS 16 +#define PKT_INT 32000u /* packet sending interval in microseconds */ + +/* ring buf size, 32kb, must be power of 2, 64kb max */ +#define RB_SZ 0x8000u + +/* the client is not fully connected until the server receives NOT_CONNECTED + * packets without the CONNECT flag */ +#define NOT_CONNECTED 8 + diff --git a/crypto-sponge.h b/crypto-sponge.h new file mode 100644 index 0000000..39cda11 --- /dev/null +++ b/crypto-sponge.h @@ -0,0 +1,276 @@ +#ifndef SPONGE_CRYPTO_H +#define SPONGE_CRYPTO_H + +#include +#include +#include "shorttypes.h" + +#define ROTL32(x, n) (((x)<<(n)) | ((x)>>(32-(n)))) + +static void gimli(void *s){ + u32 round, column; + u32 x, y, z; + u32 *state = s; + + state[ 0] = ntohl(state[ 0]); + state[ 1] = ntohl(state[ 1]); + state[ 2] = ntohl(state[ 2]); + state[ 3] = ntohl(state[ 3]); + state[ 4] = ntohl(state[ 4]); + state[ 5] = ntohl(state[ 5]); + state[ 6] = ntohl(state[ 6]); + state[ 7] = ntohl(state[ 7]); + state[ 8] = ntohl(state[ 8]); + state[ 9] = ntohl(state[ 9]); + state[10] = ntohl(state[10]); + state[11] = ntohl(state[11]); + + for (round = 24; round != 0; --round){ + for (column = 0; column < 4; ++column){ + x = ROTL32(state[ column], 24); + y = ROTL32(state[4 + column], 9); + z = state[8 + column]; + + state[8 + column] = x ^ (z << 1) ^ ((y&z) << 2); + state[4 + column] = y ^ x ^ ((x|z) << 1); + state[ column] = z ^ y ^ ((x&y) << 3); + } + + if ((round & 3) == 0) { // small swap: pattern s...s...s... + x = state[0]; + state[0] = state[1]; + state[1] = x; + x = state[2]; + state[2] = state[3]; + state[3] = x; + } + + if ((round & 3) == 2) { // big swap: ..S...S...S. etc. + x = state[0]; + state[0] = state[2]; + state[2] = x; + x = state[1]; + state[1] = state[3]; + state[3] = x; + } + + if ((round & 3) == 0) { // add constant: c...c...c... etc. + state[0] ^= (0x9e377900 | round); + } + } + + state[ 0] = htonl(state[ 0]); + state[ 1] = htonl(state[ 1]); + state[ 2] = htonl(state[ 2]); + state[ 3] = htonl(state[ 3]); + state[ 4] = htonl(state[ 4]); + state[ 5] = htonl(state[ 5]); + state[ 6] = htonl(state[ 6]); + state[ 7] = htonl(state[ 7]); + state[ 8] = htonl(state[ 8]); + state[ 9] = htonl(state[ 9]); + state[10] = htonl(state[10]); + state[11] = htonl(state[11]); + +} + +#define FPERMUTE gimli + +#define MEMXOR16(dst, src) \ + ((u32*)dst)[0] ^= ((u32*)src)[0]; \ + ((u32*)dst)[1] ^= ((u32*)src)[1]; \ + ((u32*)dst)[2] ^= ((u32*)src)[2]; \ + ((u32*)dst)[3] ^= ((u32*)src)[3] + +#define MEMXOR32(dst, src) \ + ((u32*)dst)[0] ^= ((u32*)src)[0]; \ + ((u32*)dst)[1] ^= ((u32*)src)[1]; \ + ((u32*)dst)[2] ^= ((u32*)src)[2]; \ + ((u32*)dst)[3] ^= ((u32*)src)[3]; \ + ((u32*)dst)[4] ^= ((u32*)src)[4]; \ + ((u32*)dst)[5] ^= ((u32*)src)[5]; \ + ((u32*)dst)[6] ^= ((u32*)src)[6]; \ + ((u32*)dst)[7] ^= ((u32*)src)[7] + + +/* reseedable prng + * state[12]: input buffer index + * state[13]: output buffer index + */ + +static void duplex257_prng_feed(u32 state[14], u32 food){ + state[13] = 0; + state[state[12]] ^= food; + state[12]++; + if (state[12] == 8){ + ((u8*)state)[32] ^= 0x80; + state[12] = 0; + FPERMUTE(state); + } + return; +} + +static u32 duplex257_prng_rand(u32 state[14]){ + u32 res; + if (state[12]){ + ((u8*)state)[state[12]] ^= 0x80; + FPERMUTE(state); + state[12] = 0; + state[13] = 1; + return state[0]; + } + + res = state[state[13]]; + state[13]++; + if (state[13] == 8){ + state[13] = 0; + ((u8*)state)[0] ^= 0x80; + FPERMUTE(state); + } + + return res; +} + +static void duplex257_prng_rand16(u32 state[14], u8 dst[16]){ + if (state[12]){ + ((u8*)state)[state[12]] ^= 0x80; + FPERMUTE(state); + state[12] = 0; + state[13] = 4; + memcpy(dst, state, 16); + return; + } + + if (state[13] == 0){ + state[13] = 4; + memcpy(dst, state, 16); + } else if (state[13] < 5){ + state[13] = 0; + memcpy(dst, &state[4], 16); + ((u8*)state)[0] ^= 0x80; + FPERMUTE(state); + } else { + ((u8*)state)[0] ^= 0x80; + FPERMUTE(state); + state[13] = 4; + memcpy(dst, state, 16); + } + return; +} + + +/* duplex257 AE */ + +static void duplex257_ae_encrypt + (const u8 key[32], const u8 nonce[16], u8 tag[16], u8 *src_dst, u32 sz){ + static u8 state[48]; + u32 i, n; + + /* duplex_mute(key) */ + memcpy(state, key, 32); + state[32] = 0x80; + FPERMUTE(state); + + /* duplex_mute(nonce) */ + MEMXOR16(state, nonce); + state[16] ^= 0x80; + FPERMUTE(state); + + /* 32 bytes steps */ + n = sz >> 5; + while (n){ + /* duplex(src_dst), encrypt */ + MEMXOR32(src_dst, state); + memcpy(state, src_dst, 32); + state[32] ^= 0x80; + FPERMUTE(state); + src_dst += 32; + n--; + } + + /* 1 byte steps */ + n = sz & 0x1Fu; + /* duplex(src_dst), encrypt */ + for (i = 0; i < n; i++) src_dst[i] ^= state[i]; + memcpy(state, src_dst, n); + state[n] ^= 0x80; + FPERMUTE(state); + + memcpy(tag, state, 16); + memset(state, 0, sizeof(state)); + return; +} + +static void duplex257_ae_decrypt + (const u8 key[32], const u8 nonce[16], u8 tag[16], u8 *src_dst, u32 sz){ + static u8 state[48]; + u32 i, n; + + /* duplex_mute(key) */ + memcpy(state, key, 32); + state[32] = 0x80; + FPERMUTE(state); + + /* duplex_mute(nonce) */ + MEMXOR16(state, nonce); + state[16] ^= 0x80; + FPERMUTE(state); + + /* 32 bytes steps */ + n = sz >> 5; + while (n){ + /* duplex(src_dst), encrypt */ + MEMXOR32(src_dst, state); + MEMXOR32(state, src_dst); + state[32] ^= 0x80; + FPERMUTE(state); + src_dst += 32; + n--; + } + + /* 1 byte steps */ + n = sz & 0x1Fu; + /* duplex(src_dst), encrypt */ + for (i = 0; i < n; i++){ + src_dst[i] ^= state[i]; + state[i] ^= src_dst[i]; + } + state[n] ^= 0x80; + FPERMUTE(state); + + memcpy(tag, state, 16); + memset(state, 0, sizeof(state)); + return; +} + + +/* sponge */ +static void sponge256_absorb(u8 state[48], const u8 *src, u32 sz){ + u32 n, i; + + n = sz >> 5; + while (n){ /* 32 byte steps */ + MEMXOR32(state, src); + FPERMUTE(state); + src += 32; + n--; + } + + /* 1 byte steps */ + n = sz & 0x1Fu; + for (i=0; i < n; i++) state[i] ^= src[i]; + state[i] ^= 0x80; + FPERMUTE(state); + return; +} + +/* sponge hash */ +static void sponge256_hash32(u8 dst[32], u8 *src, u32 sz){ + static u8 state[48]; + sponge256_absorb(state, src, sz); + memcpy(dst, state, 32); + memset(state, 0, sizeof(state)); + return; +} + +#endif /* SPONGE_CRYPTO_H */ diff --git a/farsh.c b/farsh.c new file mode 100644 index 0000000..cd3d085 --- /dev/null +++ b/farsh.c @@ -0,0 +1,353 @@ +/* FAt Remote SHell client by eeeee + * Public Domain */ + +#include "config-common.h" +#include "config-farsh.h" +#include "common.h" + +#include +#include + +struct termios old_attr; +static void exyt(){ tcsetattr(STDOUT_FILENO, TCSANOW, &old_attr); return; } + +int main(int argc, char *argv[]){ + s32 skt, res, fdmax; + s32 pkt_int = PKT_INT; + u32 temp = 0; + u32 connected = 0; + u32 lptime; /* last packet time */ + u16 *pkt, pos; + u8 term[CONNECT]; + u8 esc = 1; + struct sockaddr_storage sender; + struct sockaddr_in *srv4, *snd4; + struct sockaddr_in6 *srv6, *snd6; + struct addrinfo *srv; + struct termios attr; + struct timeval slp; + struct timespec t1, t2; + struct winsize wsc, wss; /* client, server */ + fd_set rset; + + /* ring buffer */ + u8 *rb; + u32 rb_mask = RB_SZ - 1; + u32 rb_w = 0; /* written to ring buffer */ + u32 rb_wm = 0; /* written masked (mod RB_SZ) */ + u32 rb_rm = 0; /* readed masked */ + u32 rb_free = RB_SZ; + + /* stream state */ + u32 rcvd = 0; /* recieved data bytes by client */ + u32 rcvd_s = 0; /* recieved data bytes by client (server version) */ + u32 rb_r = 0; /* readed from ring buffer by server */ + u32 rb_r_s = 0; /* server version */ + + if (argc != 3){ + fprintf(stderr, "Using:\n\t" + "%s \n\n" + "Example:\n\t" + "%s 127.0.0.1 2222\n\t" + "%s example.org 2222\n\n", + argv[0], argv[0], argv[0]); + exit(1); + } + + do { /* dns, ip/port str to num */ + res = getaddrinfo(argv[1], argv[2], NULL, &srv); + temp++; + usleep(10000); /* 10 ms */ + } while (res == EAI_AGAIN && temp < 5); + if (res){ + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(res)); + exit(1); + } + + /* tty */ + if (isatty(STDOUT_FILENO) && isatty(STDIN_FILENO)){ + /* save term attrs */ + res = tcgetattr(STDOUT_FILENO, &old_attr); + ERRDIE(res, "tcgetattr"); + + /* restore term attrs at exit */ + res = atexit(exyt); ERRDIE(res, "atexit"); + + /* echo off */ + memcpy(&attr, &old_attr, sizeof(attr)); + attr.c_lflag &= ~ECHO; + res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr); + ERRDIE(res, "tcsetattr"); + + /* get password */ + fputs("Password: ", stdout); + rb = fgets(password, PASSWORD_BUF_SZ, stdin); + fputs("\nconnecting...\n", stdout); + if (rb == NULL) + fputs("can't read password\n", stderr), exit(1); + + /* switch to raw mode */ + cfmakeraw(&attr); + res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr); + ERRDIE(res, "tcsetattr"); + } else { + fputs("this program can't work without tty\n", stderr); + exit(1); + } + + /* password without '\r\n' */ + res = 0; + while (password[res] != '\0' + && password[res] != '\r' + && password[res] != '\n') res++; + + /* hash password */ + sponge256_hash32(key, password, res); + memset(password, 0, PASSWORD_BUF_SZ); + + /* feed prng with key and current time */ + duplex257_prng_feed(prng_state, ((u32*)key)[0]); + duplex257_prng_feed(prng_state, ((u32*)key)[1]); + duplex257_prng_feed(prng_state, ((u32*)key)[2]); + duplex257_prng_feed(prng_state, ((u32*)key)[3]); + duplex257_prng_feed(prng_state, ((u32*)key)[4]); + duplex257_prng_feed(prng_state, ((u32*)key)[5]); + duplex257_prng_feed(prng_state, ((u32*)key)[6]); + duplex257_prng_feed(prng_state, ((u32*)key)[7]); + clock_gettime(CLOCK_MONOTONIC, &t1); + duplex257_prng_feed(prng_state, (u32)t1.tv_sec); + duplex257_prng_feed(prng_state, (u32)t1.tv_nsec); + + lptime = t1.tv_sec; /* init last packet time */ + + /* get term size */ + res = ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsc); + ERRDIE(res, "TIOCGWINSZ"); + memset(&wss, 0, sizeof(wss)); + + /* get TERM var */ + memset(term, '\0', CONNECT); + rb = getenv("TERM"); + if (rb != NULL){ + res = strlen(rb); + if (res >= CONNECT) + fprintf(stderr, "strlen(TERM) > %d\n", CONNECT-1), + exit(1); + + strcpy(term, rb); + } + + /* packet buffer (recv/send) */ + pkt = malloc(MAX_PKT_SZ); + if (pkt == NULL) fputs("pkt = malloc() failed\r\n",stderr), exit(1); + + /* ring buffer */ + rb = malloc(RB_SZ); + if (rb == NULL) fputs("ringbuf malloc failed\r\n", stderr), exit(1); + + /* socket, setsockopt */ + skt = socket(srv->ai_family, SOCK_DGRAM, 0); ERRDIE(skt, "socket"); + res = 1; + res = setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res)); + ERRDIE(res, "SO_REUSEADDR"); + + /* sockaddr */ + srv4 = (struct sockaddr_in *) srv->ai_addr; + srv6 = (struct sockaddr_in6 *) srv->ai_addr; + snd4 = (struct sockaddr_in *) &sender; + snd6 = (struct sockaddr_in6 *) &sender; + + /* fdmax for select */ + fdmax = skt; + if (STDIN_FILENO > skt) fdmax = STDIN_FILENO; + fdmax++; + + +inf_loop: + /* select */ + FD_ZERO(&rset); + FD_SET(skt, &rset); + if (rb_free) FD_SET(STDIN_FILENO, &rset); + + slp.tv_sec = 0; + slp.tv_usec = pkt_int; + + /* the contents of the slp structure after calling select() is + * undefined (not portable), so I manually count the sleep time */ + clock_gettime(CLOCK_MONOTONIC, &t1); + select(fdmax, &rset, NULL, NULL, &slp); + + /* recieve packet */ + if (FD_ISSET(skt, &rset)){ + res = recv_pkt(skt, pkt, MAX_PKT_SZ, &sender); + if (res < 4) goto ignore_p; /* ignore too small packets */ + + /* check ipv4 packet addr */ + if (srv->ai_addr->sa_family == AF_INET){ + if (srv4->sin_port != snd4->sin_port || + srv4->sin_addr.s_addr != snd4->sin_addr.s_addr) + goto ignore_p; + } else { /* check ipv6 packet addr */ + res = memcmp(srv6->sin6_addr.s6_addr, + snd6->sin6_addr.s6_addr, 16); + if (0 != res || srv6->sin6_port != snd6->sin6_port) + goto ignore_p; + } + + pkt[0] = ntohs(pkt[0]); + pkt[1] = ntohs(pkt[1]); + if (pkt[0] == 0) + fputs("\r\nserver closed connection\r\n", stdout), + exit(0); + + /* ignore packet with wrong size */ + if (res != 4 + (pkt[0] & ~CONNECT) + pkt[1]) goto ignore_p; + + /* packet recv time for entropy and last packet time */ + clock_gettime(CLOCK_MONOTONIC, &t2); + duplex257_prng_feed(prng_state, t2.tv_nsec); + lptime = t2.tv_sec; + + pos = 2; + connected |= pkt[0] & CONNECT; + + if (pkt[0] & PING){ /* read server term size */ + wss.ws_row = ntohs(pkt[pos++]); + wss.ws_col = ntohs(pkt[pos++]); + } + + if (pkt[0] & SSTATE){ + rcvd_s = ntohl(*(u32*)&pkt[pos]), pos += 2; + rb_r_s = ntohl(*(u32*)&pkt[pos]), pos += 2; + + /* rb_w can overflow, so I can't ignore a packet if + * rb_w < rb_r_s. That's why I do these checks */ + if (RB_SZ < rb_w - rb_r_s || + RB_SZ < rb_r_s - rb_r) goto ignore_p; + + /* rb_r_s is ok, update state of ring buffer */ + rb_r = rb_r_s; + rb_free = RB_SZ - (rb_w - rb_r); + rb_rm = rb_r & rb_mask; + + if (pkt[1]){ /* have data in packet */ + /* temp: already recieved amount of data*/ + temp = rcvd - rcvd_s; + if (temp < pkt[1]){ + /* packet also have data that is not + * recieved yet */ + pos = (pos<<1) + temp; + pkt[1] -= temp; + write(STDOUT_FILENO, (u8*)pkt+pos, + pkt[1]); + rcvd += pkt[1]; + } + } + } + } +ignore_p: + + /* read stdin to ringbuffer */ + if (FD_ISSET(STDIN_FILENO, &rset)){ + res = read(STDIN_FILENO, rb + rb_wm, 1); + + if (res != 1) + fputs("\r\nreading stdin return res<1\r\n", stderr), + exit(1); + + if (esc == 0 && rb[rb_wm] == DISCONNECT_KEY){ + memset(pkt, 0, 4 + CONNECT); + pkt[0] = htons(CONNECT); + send_pkt(skt, pkt, 4 + CONNECT, + (struct sockaddr_storage*) srv->ai_addr); + fputs("\r\ndisconnected\r\n", stderr); + exit(0); + } + + esc = rb[rb_wm] - 0x1B; + + rb_w++; + rb_wm = rb_w & rb_mask; + rb_free--; + } + + /* send packet every PKT_INT microseconds */ + clock_gettime(CLOCK_MONOTONIC, &t2); + pkt_int -= abs((s32)t2.tv_nsec - (s32)t1.tv_nsec) / 1000; + if (pkt_int < MIN_SLEEP){ + t2.tv_sec -= lptime; + if (!connected && t2.tv_sec >= TIMEOUT_C) + fputs("can't connect\r\n", stderr), exit(1); + + if (t2.tv_sec >= TIMEOUT_P){ + /* send PING if no packets from server for + * TIMEOUT_P seconds */ + wss.ws_row = 65535; + if (t2.tv_sec >= TIMEOUT_D) + fputs("\r\nserver timeout\r\n", stdout), + exit(0); + } + + pkt_int = PKT_INT; + pos = 2; /* pos in array of u16 */ + pkt[0] = 0; + + /* add something to packet */ + if (!connected){ + /* add TERM var */ + pkt[0] |= CONNECT; + memcpy(&pkt[pos], term, CONNECT); + pos += CONNECT >> 1; + } + + ioctl(STDOUT_FILENO, TIOCGWINSZ, &wsc); + if ((wsc.ws_row - wss.ws_row) | (wsc.ws_col - wss.ws_col)){ + /* add term sizes */ + pkt[0] |= PING; + pkt[pos++] = htons(wsc.ws_row); + pkt[pos++] = htons(wsc.ws_col); + } + + pkt[1] = rb_w - rb_r; + if (pkt[1]){ /* have data */ + /* add state of streams */ + pkt[0] |= SSTATE; + *(u32*)&pkt[pos] = htonl(rcvd), pos += 2; + *(u32*)&pkt[pos] = htonl(rb_r), pos += 2; + + /* add data */ + pos <<= 1; /* now it pos in array of u8 */ + res = MAX_PKT_SZ - HMAC_SZ - NONCE_SZ - pos; + if (res < pkt[1]) pkt[1] = res; + memcpy((u8*)pkt + pos, rb + rb_rm, pkt[1]); + pos += pkt[1]; + } else { + if (rcvd != rcvd_s || rb_r != rb_r_s){ + /* add state of streams */ + pkt[0] |= SSTATE; + *(u32*)&pkt[pos] = htonl(rcvd), pos += 2; + *(u32*)&pkt[pos] = htonl(rb_r), pos += 2; + + /* send state only once */ + rcvd_s = rcvd; + rb_r_s = rb_r; + } + pos <<= 1; /* now it pos in array of u8 */ + } + + if (pkt[0]){ + /* send packet */ + pkt[0] = htons(pkt[0]); + pkt[1] = htons(pkt[1]); + send_pkt(skt, pkt, pos, + (struct sockaddr_storage*) srv->ai_addr); + } + } + + goto inf_loop; + + return 0; +} + + + diff --git a/farshd.c b/farshd.c new file mode 100644 index 0000000..d20d17e --- /dev/null +++ b/farshd.c @@ -0,0 +1,420 @@ +/* FAt Remote SHell daemon by eeeee + * Public Domain */ + +#define _XOPEN_SOURCE 600 + +#include "config-common.h" +#include "config-farshd.h" +#include "common.h" + +#include +#include + +struct { + struct sockaddr_in6 a; + struct winsize ws; + u32 flags, notconnected; + s32 ptm, pid; + u32 lptime; /* time of last packet */ + /* stream state */ + u32 rcvd; /* recieved data bytes from client */ + u32 rcvd_c; /* client version */ + u32 rb_r; /* readed from ring buffer (recieved data by client) */ + u32 rb_r_c; /* client version */ + /* ring buffer */ + u32 rb_w; /* written to ring buffer */ + u32 rb_wm; /* written masked (mod RB_SZ) */ + u32 rb_rm; /* readed masked */ + u32 rb_free, rb_free_for_read; + u8 rb[RB_SZ]; +} peer[MAX_PEERS]; + +u32 ccnt; + +static void disconnect(s32 skt, s32 n){ + u8 pkt[4 + NONCE_SZ + HMAC_SZ]; + + *(u32*)pkt = 0; + send_pkt(skt, &pkt, 4, (struct sockaddr_storage*) &peer[n].a); + kill(peer[n].pid, SIGKILL); + close(peer[n].ptm); + if (n != ccnt-1) memcpy(&peer[n], &peer[ccnt-1], sizeof(peer[0])); + ccnt--; + memset(&peer[ccnt], 0, sizeof(peer[0])); + return; +} + +int main(int argc, char *argv[]){ + u8 term[CONNECT]; + u8 *home; + u16 *pkt; + u16 pos; + u16 port; + s32 skt, res, fdmax, pts, i; + s32 pkt_int = PKT_INT; + u32 arcv; + u32 rb_mask = RB_SZ - 1; + struct sockaddr_in6 c; + struct sigaction sa; + struct timeval slp; + struct timespec t1, t2; + struct termios attr; + fd_set rset; + + /* check shell */ + res = access(SHELL, F_OK); ERRDIE(res, SHELL); + res = access(SHELL, X_OK); ERRDIE(res, SHELL); + + /* arguments */ + if (argc != 2 && argc != 3){ + fprintf(stderr, "Using:\n\t%s [address] \n\n" + "Example:\n\t%s 2222\n\t%s 127.0.0.1 2222\n\n", + argv[0], argv[0], argv[0]); + exit(1); + } + + memset(&c, 0, sizeof(c)); + port = (u16)atoi(argv[argc-1]); + if (argc == 3){ + res = inet_pton(AF_INET6, argv[1], c.sin6_addr.s6_addr); + if (res != 1){ + res = inet_pton(AF_INET, argv[1], &arcv); + if (res != 1){ + fprintf(stderr, "wrong addr: %s\n",argv[1]); + exit(1); + } + c.sin6_addr.s6_addr[10] = 0xFF; + c.sin6_addr.s6_addr[11] = 0xFF; + memcpy(&c.sin6_addr.s6_addr[12], &arcv, 4); + } + } + + /* get term attrs */ + res = tcgetattr(STDOUT_FILENO, &attr); ERRDIE(res, "tcgetattr"); + + /* echo off */ + attr.c_lflag &= ~ECHO; + res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr); + ERRDIE(res, "tcsetattr"); + + /* get password */ + fputs("Password: ", stdout); + home = fgets(password, PASSWORD_BUF_SZ, stdin); + fputs("\n", stdout); + if (home == NULL) + fputs("can't read password\n", stderr), exit(1); + + /* echo on */ + attr.c_lflag |= ECHO; + res = tcsetattr(STDOUT_FILENO, TCSANOW, &attr); + ERRDIE(res, "tcsetattr"); + + /* password without '\r\n' */ + res = 0; + while (password[res] != '\0' + && password[res] != '\r' + && password[res] != '\n') res++; + + /* hash password */ + sponge256_hash32(key, password, res); + memset(password, 0, PASSWORD_BUF_SZ); + + /* feed prng with key and current time */ + duplex257_prng_feed(prng_state, ((u32*)key)[0]); + duplex257_prng_feed(prng_state, ((u32*)key)[1]); + duplex257_prng_feed(prng_state, ((u32*)key)[2]); + duplex257_prng_feed(prng_state, ((u32*)key)[3]); + duplex257_prng_feed(prng_state, ((u32*)key)[4]); + duplex257_prng_feed(prng_state, ((u32*)key)[5]); + duplex257_prng_feed(prng_state, ((u32*)key)[6]); + duplex257_prng_feed(prng_state, ((u32*)key)[7]); + clock_gettime(CLOCK_MONOTONIC, &t1); + duplex257_prng_feed(prng_state, (u32)t1.tv_sec); + duplex257_prng_feed(prng_state, (u32)t1.tv_nsec); + + /* daemonize: redirect std streams */ + close(STDIN_FILENO); + close(STDOUT_FILENO); + close(STDERR_FILENO); + res = open("/dev/null", O_RDONLY); ERRDIE(res, "open"); + res = open(LOGFILE, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + ERRDIE(res, "open"); + res = open(LOGFILE, O_CREAT | O_WRONLY, S_IRUSR | S_IWUSR); + ERRDIE(res, "open"); + + /* daemonize: fork 1 */ + res = fork(); ERRDIE(res, "fork 1"); + if (res != 0) exit(0); + + /* daemonize: become session leader */ + setsid(); + + /* daemonize: fork 2 */ + res = fork(); ERRDIE(res, "fork 2"); + if (res != 0) exit(0); /* kill session leader */ + + /* daemonized! */ + + /* chdir to HOME */ + home = getenv("HOME"); + if (home != NULL) chdir(home); + + /* kill zombie */ + memset(&sa, 0, sizeof(sa)); + sa.sa_handler = SIG_IGN; + sa.sa_flags = SA_NOCLDWAIT; + sigaction(SIGCHLD, &sa, NULL); + + /* init some vars */ + memset(peer, 0, sizeof(peer)); + ccnt = 0; + + /* cur pkt buf */ + pkt = malloc(MAX_PKT_SZ); + if (pkt == NULL) fputs("pkt = malloc() failed\n",stderr), exit(1); + + /* socket, setsockopt */ + skt = socket(PF_INET6, SOCK_DGRAM, 0); ERRDIE(skt, "socket"); + res = 1; + res = setsockopt(skt, SOL_SOCKET, SO_REUSEADDR, &res, sizeof(res)); + ERRDIE(res, "SO_REUSEADDR"); + + /* bind */ + c.sin6_family = AF_INET6; + c.sin6_port = htons(port); + res = bind(skt, (struct sockaddr*) &c, sizeof(c)); + ERRDIE(res, "bind"); + +inf_loop: + /* select */ + fdmax = skt + 1; + FD_ZERO(&rset); + FD_SET(skt, &rset); + for (i=0; i < ccnt; i++){ + if (peer[i].rb_free == 0) continue; + FD_SET(peer[i].ptm, &rset); + if (fdmax <= peer[i].ptm) fdmax = peer[i].ptm + 1; + } + + slp.tv_sec = 0; + slp.tv_usec = pkt_int; + + /* the contents of the slp structure after calling select() is + * undefined (not portable), so I manually count the sleep time */ + clock_gettime(CLOCK_MONOTONIC, &t1); + select(fdmax, &rset, NULL, NULL, &slp); + + /* recv packet from client */ + if (FD_ISSET(skt, &rset)){ + res = recv_pkt(skt, pkt, MAX_PKT_SZ, + (struct sockaddr_storage*) &c); + + pkt[0] = ntohs(pkt[0]); + pkt[1] = ntohs(pkt[1]); + if (res < 4 || res != 4 + pkt[0] + pkt[1]) goto ignore_p; + for (i=0; i < ccnt; i++){ + res = memcmp(peer[i].a.sin6_addr.s6_addr, + c.sin6_addr.s6_addr, 16); + if (res == 0 && c.sin6_port == peer[i].a.sin6_port) + break; + } + + if (i == ccnt){ + /* client not connected */ + if (ccnt == MAX_PEERS || (pkt[0] & CONNECT) == 0) + goto ignore_p; + + /* add peer to array */ + memcpy(&peer[i].a, &c, sizeof(c)); + + /* peer is not fully connected yet */ + peer[i].notconnected = NOT_CONNECTED; + + /* init ring buffer */ + peer[i].rb_free = peer[i].rb_free_for_read = RB_SZ; + + /* pseudoterminal */ + peer[i].ptm = posix_openpt(O_RDWR); + ERRDIE(peer[i].ptm, "posix_openpt"); + res = grantpt (peer[i].ptm); ERRDIE(res,"grantpt"); + res = unlockpt(peer[i].ptm); ERRDIE(res,"unlockpt"); + pts = open(ptsname(peer[i].ptm), O_RDWR); + ERRDIE(pts, "pts = open()"); + + /* read TERM from packet */ + memset(term, '\0', CONNECT); + memcpy(term, &pkt[2], CONNECT-1); + + /* fork */ + peer[i].pid = fork(); ERRDIE(peer[i].pid, "fork"); + if (peer[i].pid == 0){ /* child */ + close(peer[i].ptm); + close(0), close(1), close(2); + dup(pts), dup(pts), dup(pts); + close(pts); + setsid(); + res = ioctl(0, TIOCSCTTY, 1); + ERRDIE(res, "TIOCSCTTY"); + setenv("TERM", term, 1); + + res = execlp(SHELL, SHELL, NULL); + ERRDIE(res, "execlp"); + } + close(pts); + ccnt++; + } + + /* client connected */ + clock_gettime(CLOCK_MONOTONIC, &t2); + duplex257_prng_feed(prng_state, t2.tv_nsec); + peer[i].lptime = t2.tv_sec; + pos = 2; + if (pkt[0] & CONNECT){ + if (!peer[i].notconnected){ + /* fully connected client sent packet with + * CONNECT flag, disconnect him */ + disconnect(skt, i); + goto ignore_p; + } + peer[i].notconnected = NOT_CONNECTED; + pos += CONNECT >> 1; + } else if (peer[i].notconnected) peer[i].notconnected--; + + /* copy flags for responce */ + peer[i].flags |= pkt[0]; + + /* ping, term sizes */ + if (pkt[0] & PING){ + peer[i].ws.ws_row = ntohs(pkt[pos++]); + peer[i].ws.ws_col = ntohs(pkt[pos++]); + ioctl(peer[i].ptm, TIOCSWINSZ, &peer[i].ws); + } + + /* stream state */ + if (pkt[0] & SSTATE){ + peer[i].rb_r_c = ntohl(*(u32*)&pkt[pos]), pos += 2; + peer[i].rcvd_c = ntohl(*(u32*)&pkt[pos]), pos += 2; + + /* rb_w can overflow, so I can't ignore a packet if + * rb_w < rb_r_c. That's why I do these checks */ + if (RB_SZ < peer[i].rb_w - peer[i].rb_r_c || + RB_SZ < peer[i].rb_r_c - peer[i].rb_r) + goto ignore_p; + + /* rb_r_c is ok, update state of ring buffer */ + peer[i].rb_r = peer[i].rb_r_c; + peer[i].rb_free = RB_SZ - + (peer[i].rb_w - peer[i].rb_r); + peer[i].rb_rm = peer[i].rb_r & rb_mask; + peer[i].rb_free_for_read = peer[i].rb_free; + if (peer[i].rb_free > peer[i].rb_rm) + peer[i].rb_free_for_read -= peer[i].rb_rm; + + if (pkt[1]){ /* have data in packet */ + /* arcv: already recieved amount of data*/ + arcv = peer[i].rcvd - peer[i].rcvd_c; + if (arcv < pkt[1]){ + /* packet also have data that is not + * recieved yet */ + pos = (pos<<1) + arcv; + pkt[1] -= arcv; + write(peer[i].ptm, (u8*)pkt+pos, + pkt[1]); + peer[i].rcvd += pkt[1]; + } + } + } + } +ignore_p: + + /* read ptms to ring buffers */ + for (i=0; i < ccnt; i++){ + if (0 == FD_ISSET(peer[i].ptm, &rset)) continue; + + res = read(peer[i].ptm, peer[i].rb + peer[i].rb_wm, + peer[i].rb_free_for_read); + + if (res < 1){/* disconnect */ + disconnect(skt, i); + if (i != ccnt-1) i--; + continue; + } + + peer[i].rb_w += res; + peer[i].rb_wm = peer[i].rb_w & rb_mask; + peer[i].rb_free -= res; + peer[i].rb_free_for_read = peer[i].rb_free; + if (peer[i].rb_free > peer[i].rb_rm) + peer[i].rb_free_for_read -= peer[i].rb_rm; + } + + /* send packets to clients every PKT_INT (+- MIN_SLEEP) microsecs */ + clock_gettime(CLOCK_MONOTONIC, &t2); + pkt_int -= abs((s32)t2.tv_nsec - (s32)t1.tv_nsec) / 1000; + if (pkt_int < MIN_SLEEP) + for (pkt_int=PKT_INT, i=0; i < ccnt; i++){ + /* make packet for peer i */ + pos = 2; + pkt[0] = 0; + + /* disconnect on timeout */ + if (t2.tv_sec - peer[i].lptime >= TIMEOUT_D){ + disconnect(skt, i); + continue; + } + + /* set PING bit after getting packet with PING from peer */ + if (peer[i].flags & PING){ + pkt[0] |= PING; + pkt[pos++] = htons(peer[i].ws.ws_row); + pkt[pos++] = htons(peer[i].ws.ws_col); + } + + /* add streams state and data if data exist */ + pkt[1] = peer[i].rb_w - peer[i].rb_r; + if (pkt[1]) { + /* add state of streams */ + pkt[0] |= SSTATE; + *(u32*)&pkt[pos] = htonl(peer[i].rb_r), pos += 2; + *(u32*)&pkt[pos] = htonl(peer[i].rcvd), pos += 2; + + /* add data */ + pos <<= 1; /* now it pos in array of u8 */ + res = MAX_PKT_SZ - HMAC_SZ - NONCE_SZ - pos; + if (res < pkt[1]) pkt[1] = res; + memcpy((u8*)pkt + pos, peer[i].rb + peer[i].rb_rm, + pkt[1]); + pos += pkt[1]; + } else { /* no data to send */ + /* send streams state if it differ from peer state*/ + if (peer[i].rb_r != peer[i].rb_r_c || + peer[i].rcvd != peer[i].rcvd_c){ + pkt[0] |= SSTATE; + *(u32*)&pkt[pos] = htonl(peer[i].rb_r); + pos += 2; + *(u32*)&pkt[pos] = htonl(peer[i].rcvd); + pos += 2; + + /* send state only once */ + peer[i].rb_r_c = peer[i].rb_r; + peer[i].rcvd_c = peer[i].rcvd; + } + pos <<= 1; + } + + peer[i].flags = 0; + if (pkt[0]){ + /* add CONNECT bit if peer is not fully connected */ + if (peer[i].notconnected) pkt[0] |= CONNECT; + pkt[0] = htons(pkt[0]); + pkt[1] = htons(pkt[1]); + send_pkt(skt, pkt, pos, + (struct sockaddr_storage*) &peer[i].a); + } + } + + goto inf_loop; + + return 0; +} + diff --git a/shorttypes.h b/shorttypes.h new file mode 100644 index 0000000..7b2df58 --- /dev/null +++ b/shorttypes.h @@ -0,0 +1,36 @@ +#ifndef SHORTTYPES_H +#define SHORTTYPES_H +#include + +#if CHAR_BIT != 8 + #error "CHAR_BIT != 8" +#endif + +#if USHRT_MAX != 65535 + #error "USHRT_MAX != 65535" +#endif + +#if UINT_MAX != 4294967295U + #error "UINT_MAX != 4294967295U" +#endif + +typedef signed char s8; +typedef signed short s16; +typedef signed int s32; +typedef unsigned char u8; +typedef unsigned short u16; +typedef unsigned int u32; + +#ifdef ULLONG_MAX + #if ULLONG_MAX == 18446744073709551615ULL + typedef signed long long s64; + typedef unsigned long long u64; + #else + #error "ULLONG_MAX != 18446744073709551615ULL" + #endif +#else + #error "no long long type" +#endif + + +#endif /* SHORTTYPES_H */ -- cgit 1.4.1