diff options
Diffstat (limited to 'farsh.c')
| -rw-r--r-- | farsh.c | 353 |
1 files changed, 353 insertions, 0 deletions
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 <sys/types.h> +#include <netdb.h> + +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 <host> <port>\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; +} + + + |