summary refs log tree commit diff
path: root/farshd.c
diff options
context:
space:
mode:
authorNakidai <nakidai@disroot.org>2025-07-09 18:17:21 +0300
committerNakidai <nakidai@disroot.org>2025-07-09 18:17:21 +0300
commit7c625e57d8bf86196fa434fa2343c50582cdbc9e (patch)
tree1afc167d41155eaae2a2c1544b8dc528bc275530 /farshd.c
downloadfarsh-b6012759bcec19787b393640daaeaf0ee4bd9e38.tar.gz
farsh-b6012759bcec19787b393640daaeaf0ee4bd9e38.zip
Add code HEAD v1.0.0 master
Diffstat (limited to 'farshd.c')
-rw-r--r--farshd.c420
1 files changed, 420 insertions, 0 deletions
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 <signal.h>
+#include <fcntl.h>
+
+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] <port>\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;
+}
+