Skip to content

Instantly share code, notes, and snippets.

@congwang-mk
Last active October 21, 2025 21:51
Show Gist options
  • Select an option

  • Save congwang-mk/9d8f5a710b7bf87f1f8667a451cd3172 to your computer and use it in GitHub Desktop.

Select an option

Save congwang-mk/9d8f5a710b7bf87f1f8667a451cd3172 to your computer and use it in GitHub Desktop.
telnetd for demo
/* 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