Last active
October 21, 2025 21:51
-
-
Save congwang-mk/9d8f5a710b7bf87f1f8667a451cd3172 to your computer and use it in GitHub Desktop.
telnetd for demo
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /* tiny telnet-like daemon with CR/LF normalization | |
| * build: musl-gcc -static -O2 -s -o mytelnetd mytelnetd.c | |
| * usage: ./mytelnetd -p 2323 -l /bin/sh | |
| */ | |
| #define _POSIX_C_SOURCE 200112L | |
| #define _GNU_SOURCE | |
| #include <sys/types.h> | |
| #include <sys/socket.h> | |
| #include <netinet/in.h> | |
| #include <arpa/inet.h> | |
| #include <netdb.h> | |
| #include <signal.h> | |
| #include <unistd.h> | |
| #include <stdlib.h> | |
| #include <stdio.h> | |
| #include <string.h> | |
| #include <errno.h> | |
| #include <sys/wait.h> | |
| #include <poll.h> | |
| #include <fcntl.h> | |
| #ifndef SA_RESTART | |
| #define SA_RESTART 0 | |
| #endif | |
| static void sigchld_handler(int signum) { | |
| (void)signum; | |
| while (waitpid(-1, NULL, WNOHANG) > 0) {} | |
| } | |
| static int create_and_bind(const char *portstr) { | |
| struct addrinfo hints, *res, *rp; | |
| int sfd = -1; | |
| int yes = 1; | |
| int rc; | |
| memset(&hints, 0, sizeof hints); | |
| hints.ai_family = AF_UNSPEC; | |
| hints.ai_socktype = SOCK_STREAM; | |
| hints.ai_flags = AI_PASSIVE; | |
| rc = getaddrinfo(NULL, portstr, &hints, &res); | |
| if (rc != 0) { | |
| fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rc)); | |
| return -1; | |
| } | |
| for (rp = res; rp != NULL; rp = rp->ai_next) { | |
| sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol); | |
| if (sfd == -1) continue; | |
| setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes); | |
| if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) break; | |
| close(sfd); | |
| sfd = -1; | |
| } | |
| freeaddrinfo(res); | |
| return sfd; | |
| } | |
| static ssize_t write_all(int fd, const void *buf, size_t len) { | |
| const char *p = (const char*)buf; | |
| while (len) { | |
| ssize_t n = write(fd, p, len); | |
| if (n < 0) { | |
| if (errno == EINTR) continue; | |
| return -1; | |
| } | |
| p += n; | |
| len -= (size_t)n; | |
| } | |
| return 0; | |
| } | |
| /* translate incoming from socket -> shell: | |
| * drop all '\r' so CRLF or CRNUL become '\n' for the shell */ | |
| static size_t xlate_in(char *dst, const char *src, size_t n) { | |
| size_t w = 0; | |
| for (size_t i = 0; i < n; i++) { | |
| if (src[i] == '\r') continue; /* drop */ | |
| dst[w++] = src[i]; | |
| } | |
| return w; | |
| } | |
| /* translate outgoing from shell -> socket: | |
| * expand '\n' to "\r\n" for telnet clients */ | |
| static size_t xlate_out(char *dst, const char *src, size_t n) { | |
| size_t w = 0; | |
| for (size_t i = 0; i < n; i++) { | |
| if (src[i] == '\n') { | |
| dst[w++] = '\r'; | |
| dst[w++] = '\n'; | |
| } else { | |
| dst[w++] = src[i]; | |
| } | |
| } | |
| return w; | |
| } | |
| static void handle_client(int clientfd, const char *shell) { | |
| int inpipe[2]; /* parent writes -> child stdin */ | |
| int outpipe[2]; /* child stdout -> parent reads */ | |
| if (pipe(inpipe) < 0 || pipe(outpipe) < 0) { | |
| perror("pipe"); | |
| close(clientfd); | |
| _exit(1); | |
| } | |
| pid_t pid = fork(); | |
| if (pid < 0) { | |
| perror("fork"); | |
| close(inpipe[0]); close(inpipe[1]); | |
| close(outpipe[0]); close(outpipe[1]); | |
| close(clientfd); | |
| _exit(1); | |
| } | |
| if (pid == 0) { | |
| /* child: connect pipes to stdio and exec shell */ | |
| dup2(inpipe[0], STDIN_FILENO); | |
| dup2(outpipe[1], STDOUT_FILENO); | |
| dup2(outpipe[1], STDERR_FILENO); | |
| close(inpipe[0]); close(inpipe[1]); | |
| close(outpipe[0]); close(outpipe[1]); | |
| close(clientfd); | |
| setenv("PATH", "/bin:/sbin:/usr/bin:/usr/sbin", 1); | |
| execl(shell, shell, (char*)NULL); | |
| perror("execl"); | |
| _exit(127); | |
| } | |
| /* parent: relay between clientfd and child pipes with CR/LF normalization */ | |
| close(inpipe[0]); /* we write to inpipe[1] */ | |
| close(outpipe[1]); /* we read from outpipe[0] */ | |
| struct pollfd pfds[2]; | |
| pfds[0].fd = clientfd; pfds[0].events = POLLIN; | |
| pfds[1].fd = outpipe[0]; pfds[1].events = POLLIN; | |
| char inbuf[4096], xbuf[8192]; /* xbuf big enough for \n -> \r\n expansion */ | |
| for (;;) { | |
| int pr = poll(pfds, 2, -1); | |
| if (pr < 0) { | |
| if (errno == EINTR) continue; | |
| perror("poll"); | |
| break; | |
| } | |
| /* socket -> shell (strip '\r') */ | |
| if (pfds[0].revents & POLLIN) { | |
| ssize_t n = read(clientfd, inbuf, sizeof inbuf); | |
| if (n <= 0) break; /* EOF or error */ | |
| size_t wlen = xlate_in(xbuf, inbuf, (size_t)n); | |
| if (wlen && write_all(inpipe[1], xbuf, wlen) < 0) break; | |
| } | |
| /* shell -> socket (expand '\n' to "\r\n") */ | |
| if (pfds[1].revents & POLLIN) { | |
| ssize_t n = read(outpipe[0], inbuf, sizeof inbuf); | |
| if (n <= 0) break; | |
| size_t wlen = xlate_out(xbuf, inbuf, (size_t)n); | |
| if (wlen && write_all(clientfd, xbuf, wlen) < 0) break; | |
| } | |
| /* handle hangups/errors */ | |
| if ((pfds[0].revents & (POLLHUP|POLLERR|POLLNVAL)) || | |
| (pfds[1].revents & (POLLHUP|POLLERR|POLLNVAL))) { | |
| break; | |
| } | |
| } | |
| close(inpipe[1]); | |
| close(outpipe[0]); | |
| close(clientfd); | |
| /* let SIGCHLD reap the child */ | |
| _exit(0); | |
| } | |
| int main(int argc, char **argv) { | |
| const char *port = "2323"; | |
| const char *shell = "/bin/sh"; | |
| int opt; | |
| while ((opt = getopt(argc, argv, "p:l:")) != -1) { | |
| switch (opt) { | |
| case 'p': port = optarg; break; | |
| case 'l': shell = optarg; break; | |
| default: | |
| fprintf(stderr, "Usage: %s [-p port] [-l /path/to/shell]\n", argv[0]); | |
| exit(2); | |
| } | |
| } | |
| int listenfd = create_and_bind(port); | |
| if (listenfd < 0) { | |
| fprintf(stderr, "Failed to bind to port %s\n", port); | |
| return 1; | |
| } | |
| if (listen(listenfd, 16) < 0) { | |
| perror("listen"); | |
| close(listenfd); | |
| return 1; | |
| } | |
| struct sigaction sa; | |
| memset(&sa, 0, sizeof sa); | |
| sa.sa_handler = sigchld_handler; | |
| sigemptyset(&sa.sa_mask); | |
| sa.sa_flags = SA_RESTART; | |
| sigaction(SIGCHLD, &sa, NULL); | |
| fprintf(stderr, "telnetd: listening on port %s, exec %s\n", port, shell); | |
| for (;;) { | |
| struct sockaddr_storage ss; | |
| socklen_t slen = sizeof ss; | |
| int clientfd = accept(listenfd, (struct sockaddr*)&ss, &slen); | |
| if (clientfd < 0) { | |
| if (errno == EINTR) continue; | |
| perror("accept"); | |
| continue; | |
| } | |
| pid_t pid = fork(); | |
| if (pid < 0) { | |
| perror("fork"); | |
| close(clientfd); | |
| continue; | |
| } else if (pid == 0) { | |
| /* per-connection handler */ | |
| close(listenfd); | |
| handle_client(clientfd, shell); | |
| /* never returns */ | |
| } else { | |
| close(clientfd); | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment