commit e4ab475aea71d6c71aa8a5ecc86d35f5471ca203 Author: Franck STAUFFER Date: Wed Sep 2 19:09:52 2020 +0200 Initial commit diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..3f84f67 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,13 @@ +debian: + image: debian:stable-slim + before_script: + - apt-get -qq install make clang + script: + - make + +alpine: + image: alpine:latest + before_script: + - apk add make clang + script: + - make diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..3b3345d --- /dev/null +++ b/Makefile @@ -0,0 +1,39 @@ +CPPFLAGS = -D_FORTIFY_SOURCE=2 -D_DEFAULT_SOURCE +CFLAGS = $(CPPFLAGS) -std=c99 -pedantic -Wall -Wextra -Werror -Os -march=native -mtune=native -fPIE +LDFLAGS = -Wl,-z,now,-z,relro,-s,-pie + +CC = clang + +OBJS = obj/main.o +BIN = bin/stc + +PREFIX = /usr + +default: obj bin $(BIN) + +obj: + @mkdir obj + +bin: + @mkdir bin + +$(BIN): $(OBJS) + $(CC) -o $@ $^ $(LDFLAGS) + +obj/%.o: src/%.c + $(CC) -c -o $@ $^ $(CFLAGS) + +clean: + @[ -d obj ] && rm -rf obj + @[ -d bin ] && rm -rf bin + +format: + @clang-format -i -style="{BasedOnStyle: mozilla, IndentWidth: 4}" src/*.c + +install: default + install -Dm755 $(BIN) $(PREFIX)/bin/stc + +uninstall: + rm -f $(PREFIX)/bin/stc + +.PHONY: default clean format install uninstall diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c27708 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# stc + +Simple Telnet Client + +## Build + +``` +git clone https://framagit.org/franck.stauffer/stc +cd stc +make +``` + +## Install + +``` +sudo make install +``` + +## Uninstall + +``` +sudo make uninstall +``` + +## Usage + +``` +stc address|hostname [port] +``` diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..1ba488b --- /dev/null +++ b/src/main.c @@ -0,0 +1,237 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SE 0xF0 +#define SB 0xFA +#define WILL 0xFB +#define WONT 0xFC +#define DO 0xFD +#define DONT 0xFE +#define IAC 0xFF +#define CMD_WINDOW_SIZE 0x1F + +int epoll_fd; +int sock; +struct termios tin; + +void +cleanup(void) +{ + if (tcsetattr(STDIN_FILENO, TCSANOW, &tin)) + perror("ERROR: tcsetattr"); + + if (close(epoll_fd) || close(sock)) + perror("ERROR: close"); +} + +uint_fast8_t +negotiate(unsigned char* buf) +{ + if (buf[1] == DO && buf[2] == CMD_WINDOW_SIZE) { + uint8_t msg0[3] = { IAC, WILL, CMD_WINDOW_SIZE }; + if (send(sock, msg0, 3, 0) != 3) { + perror("ERROR: send"); + return 0; + } + + uint8_t msg1[9] = { IAC, SB, CMD_WINDOW_SIZE, 0, 80, 0, 24, IAC, SE }; + if (send(sock, msg1, 9, 0) != 9) { + perror("ERROR: send"); + return 0; + } + + return 1; + } + + for (uint_fast8_t i = 0; i < 3; ++i) { + if (buf[i] == DO) + buf[i] = WONT; + else if (buf[i] == WILL) + buf[i] = DO; + } + + if (send(sock, buf, 3, 0) != 3) { + perror("ERROR: send"); + return 0; + } + + return 1; +} + +uint_fast8_t +net_init(const char* host, const char* port) +{ + sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == -1) { + perror("ERROR: socket"); + return 0; + } + + struct addrinfo hints; + memset(&hints, 0, sizeof(struct addrinfo)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + + struct addrinfo* res; + int_fast8_t err = getaddrinfo(host, port, &hints, &res); + if (err) { + if (err == EAI_SYSTEM) + perror("ERROR: getaddrinfo"); + else + fprintf(stderr, "ERROR: getaddrinfo: %s\n", gai_strerror(err)); + return 0; + } + + if (connect(sock, res->ai_addr, res->ai_addrlen)) { + perror("ERROR: connect"); + if (close(sock)) + perror("ERROR: close"); + return 0; + } + + freeaddrinfo(res); + + return 1; +} + +void +sighandler(int sig) +{ + fprintf(stderr, "Received %s", (sig == SIGTERM) ? "SIGTERM" : "SIGINT"); + exit(sig); +} + +uint_fast8_t +terminal_set(void) +{ + if (tcgetattr(STDIN_FILENO, &tin)) { + perror("ERROR: tcgetattr"); + return 0; + } + + static struct termios tlocal; + memcpy(&tlocal, &tin, sizeof(struct termios)); + cfmakeraw(&tlocal); + + if (tcsetattr(STDIN_FILENO, TCSANOW, &tlocal)) { + perror("ERROR: tcsetattr"); + return 0; + } + + return 1; +} + +int +main(int argc, char** argv) +{ + if (argc < 2 || argc > 3) { + printf("USAGE: %s, address [port]\n", argv[0]); + return 1; + } + + if (!net_init(argv[1], (argc == 3) ? argv[2] : "23")) + return 2; + + if (!terminal_set()) + return 3; + + epoll_fd = epoll_create1(0); + if (epoll_fd == -1) { + perror("epoll_create1"); + return 4; + } + + atexit(cleanup); + if (signal(SIGINT, sighandler) == SIG_ERR) { + perror("signal"); + return 5; + } + + if (signal(SIGTERM, sighandler) == SIG_ERR) { + perror("signal"); + return 5; + } + + struct epoll_event ev[2]; + memset(&ev, 0, 2 * sizeof(struct epoll_event)); + + ev[0].events = EPOLLIN; + ev[0].data.fd = 0; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, 0, &ev[0])) { + perror("epoll_ctl"); + return 6; + } + + ev[1].events = EPOLLIN; + ev[1].data.fd = sock; + if (epoll_ctl(epoll_fd, EPOLL_CTL_ADD, sock, &ev[1])) { + perror("epoll_ctl"); + return 7; + } + + uint_fast8_t run = 1; + + do { + struct epoll_event events[2]; + int_fast8_t count = epoll_wait(epoll_fd, events, 2, -1); + if (count == -1) { + perror("epoll_wait"); + return 8; + } + + for (int_fast8_t i = 0; i < count; ++i) { + if (events[i].data.fd == sock) { + uint8_t buf[3]; + memset(buf, 0, 3); + int_fast8_t ret = recv(sock, buf, 1, 0); + if (ret == -1) { + perror("recv"); + return 9; + } + + if (!ret) + return 0; + + if (buf[0] == IAC) { + ret = recv(sock, buf + 1, 2, 0); + if (ret < 0) { + perror("recv"); + return 10; + } + + if (!ret) + return 0; + + negotiate(buf); + } else { + putchar(buf[0]); + fflush(stdin); + } + } else if (events[i].data.fd == 0) { + const uint8_t chr = getchar(); + if (chr == 3) { + fputs("\n\rLeaving...\n\r", stdout); + return 0; + } + + if (send(sock, &chr, 1, 0) != 1) { + perror("send"); + return 11; + } + + if (chr == '\n') + putchar('\r'); + } + } + } while (run); +}