blob: 0201d2636775038736b36c1138c464e388061387 [file] [log] [blame]
Bernhard Reutner-Fischer2c998512006-04-12 18:09:26 +00001/* vi: set sw=4 ts=4: */
Bernhard Reutner-Fischerdac7ff12006-04-12 17:55:51 +00002/*
Eric Andersen08a72202002-09-30 20:52:10 +00003 * Simple telnet server
4 * Bjorn Wesen, Axis Communications AB (bjornw@axis.com)
5 *
Rob Landley1ec5b292006-05-29 07:42:02 +00006 * Licensed under GPLv2 or later, see file LICENSE in this tarball for details.
Eric Andersen08a72202002-09-30 20:52:10 +00007 *
8 * ---------------------------------------------------------------------------
9 * (C) Copyright 2000, Axis Communications AB, LUND, SWEDEN
10 ****************************************************************************
11 *
12 * The telnetd manpage says it all:
13 *
14 * Telnetd operates by allocating a pseudo-terminal device (see pty(4)) for
15 * a client, then creating a login process which has the slave side of the
16 * pseudo-terminal as stdin, stdout, and stderr. Telnetd manipulates the
17 * master side of the pseudo-terminal, implementing the telnet protocol and
18 * passing characters between the remote client and the login process.
19 *
20 * Vladimir Oleynik <dzo@simtreas.ru> 2001
21 * Set process group corrections, initial busybox port
22 */
23
Denis Vlasenko75f8d082006-11-22 15:54:52 +000024#define DEBUG 0
Eric Andersen08a72202002-09-30 20:52:10 +000025
Denis Vlasenkob6adbf12007-05-26 19:00:18 +000026#include "libbb.h"
Rob Landley099ed502006-08-28 09:41:49 +000027
Denis Vlasenko75f8d082006-11-22 15:54:52 +000028#if DEBUG
Eric Andersen08a72202002-09-30 20:52:10 +000029#define TELCMDS
30#define TELOPTS
31#endif
32#include <arpa/telnet.h>
Eric Andersen08a72202002-09-30 20:52:10 +000033#include <sys/syslog.h>
34
Eric Andersen08a72202002-09-30 20:52:10 +000035
Denis Vlasenko59d7c432007-10-15 15:19:36 +000036/* Structure that describes a session */
Eric Andersen08a72202002-09-30 20:52:10 +000037struct tsession {
38 struct tsession *next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +000039 int sockfd_read, sockfd_write, ptyfd;
Denis Vlasenko2450c452007-10-15 22:09:15 +000040 int shell_pid;
Denis Vlasenko59d7c432007-10-15 15:19:36 +000041
Eric Andersen08a72202002-09-30 20:52:10 +000042 /* two circular buffers */
Denis Vlasenko59d7c432007-10-15 15:19:36 +000043 /*char *buf1, *buf2;*/
44/*#define TS_BUF1 ts->buf1*/
45/*#define TS_BUF2 TS_BUF2*/
Denis Vlasenko10916c52007-10-15 17:28:00 +000046#define TS_BUF1 ((unsigned char*)(ts + 1))
47#define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE)
Eric Andersen08a72202002-09-30 20:52:10 +000048 int rdidx1, wridx1, size1;
49 int rdidx2, wridx2, size2;
50};
51
Denis Vlasenko88308fe2007-06-25 10:35:11 +000052/* Two buffers are directly after tsession in malloced memory.
53 * Make whole thing fit in 4k */
Denis Vlasenko59d7c432007-10-15 15:19:36 +000054enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
Denis Vlasenko88308fe2007-06-25 10:35:11 +000055
Eric Andersen08a72202002-09-30 20:52:10 +000056
Denis Vlasenko59d7c432007-10-15 15:19:36 +000057/* Globals */
Eric Andersen08a72202002-09-30 20:52:10 +000058static int maxfd;
Eric Andersen08a72202002-09-30 20:52:10 +000059static struct tsession *sessions;
Denis Vlasenko59d7c432007-10-15 15:19:36 +000060#if ENABLE_LOGIN
61static const char *loginpath = "/bin/login";
62#else
63static const char *loginpath = DEFAULT_SHELL;
64#endif
65static const char *issuefile = "/etc/issue.net";
Eric Andersen08a72202002-09-30 20:52:10 +000066
67
68/*
Denis Vlasenko59d7c432007-10-15 15:19:36 +000069 Remove all IAC's from buf1 (received IACs are ignored and must be removed
70 so as to not be interpreted by the terminal). Make an uninterrupted
71 string of characters fit for the terminal. Do this by packing
72 all characters meant for the terminal sequentially towards the end of buf.
Eric Andersen08a72202002-09-30 20:52:10 +000073
74 Return a pointer to the beginning of the characters meant for the terminal.
75 and make *num_totty the number of characters that should be sent to
76 the terminal.
77
78 Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
Denis Vlasenko59d7c432007-10-15 15:19:36 +000079 past (bf + len) then that IAC will be left unprocessed and *processed
80 will be less than len.
Eric Andersen08a72202002-09-30 20:52:10 +000081
82 FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
83 what is the escape character? We aren't handling that situation here.
84
Denis Vlasenko10916c52007-10-15 17:28:00 +000085 CR-LF ->'s CR mapping is also done here, for convenience.
86
87 NB: may fail to remove iacs which wrap around buffer!
Denis Vlasenko75f8d082006-11-22 15:54:52 +000088 */
Denis Vlasenko10916c52007-10-15 17:28:00 +000089static unsigned char *
Denis Vlasenko75f8d082006-11-22 15:54:52 +000090remove_iacs(struct tsession *ts, int *pnum_totty)
91{
Denis Vlasenko10916c52007-10-15 17:28:00 +000092 unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
Eric Andersen08a72202002-09-30 20:52:10 +000093 unsigned char *ptr = ptr0;
94 unsigned char *totty = ptr;
95 unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
Eric Andersen08a72202002-09-30 20:52:10 +000096 int num_totty;
97
98 while (ptr < end) {
99 if (*ptr != IAC) {
Denis Vlasenko10916c52007-10-15 17:28:00 +0000100 char c = *ptr;
101
102 *totty++ = c;
103 ptr++;
Eric Andersen3752d332003-12-19 11:30:13 +0000104 /* We now map \r\n ==> \r for pragmatic reasons.
105 * Many client implementations send \r\n when
106 * the user hits the CarriageReturn key.
107 */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000108 if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
Eric Andersen3752d332003-12-19 11:30:13 +0000109 ptr++;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000110 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000111 /*
112 * TELOPT_NAWS support!
113 */
114 if ((ptr+2) >= end) {
Eric Andersen08a72202002-09-30 20:52:10 +0000115 /* only the beginning of the IAC is in the
116 buffer we were asked to process, we can't
117 process this char. */
118 break;
119 }
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000120
121 /*
122 * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
123 */
124 else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
125 struct winsize ws;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000126
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000127 if ((ptr+8) >= end)
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000128 break; /* incomplete, can't process */
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000129 ws.ws_col = (ptr[3] << 8) | ptr[4];
130 ws.ws_row = (ptr[5] << 8) | ptr[6];
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000131 ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000132 ptr += 9;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000133 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000134 /* skip 3-byte IAC non-SB cmd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000135#if DEBUG
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000136 fprintf(stderr, "Ignoring IAC %s,%s\n",
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000137 TELCMD(ptr[1]), TELOPT(ptr[2]));
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000138#endif
139 ptr += 3;
140 }
Eric Andersen08a72202002-09-30 20:52:10 +0000141 }
142 }
143
Denis Vlasenko0de37e12007-10-17 11:08:53 +0000144 num_totty = totty - ptr0;
Eric Andersen08a72202002-09-30 20:52:10 +0000145 *pnum_totty = num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000146 /* the difference between ptr and totty is number of iacs
147 we removed from the stream. Adjust buf1 accordingly. */
148 if ((ptr - totty) == 0) /* 99.999% of cases */
149 return ptr0;
150 ts->wridx1 += ptr - totty;
151 ts->size1 -= ptr - totty;
152 /* move chars meant for the terminal towards the end of the buffer */
Eric Andersen08a72202002-09-30 20:52:10 +0000153 return memmove(ptr - num_totty, ptr0, num_totty);
154}
155
156
157static int
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000158getpty(char *line, int size)
Eric Andersen08a72202002-09-30 20:52:10 +0000159{
160 int p;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000161#if ENABLE_FEATURE_DEVPTS
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000162 p = open("/dev/ptmx", O_RDWR);
Eric Andersen08a72202002-09-30 20:52:10 +0000163 if (p > 0) {
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000164 const char *name;
Eric Andersen08a72202002-09-30 20:52:10 +0000165 grantpt(p);
166 unlockpt(p);
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000167 name = ptsname(p);
168 if (!name) {
169 bb_perror_msg("ptsname error (is /dev/pts mounted?)");
170 return -1;
171 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000172 safe_strncpy(line, name, size);
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000173 return p;
Eric Andersen08a72202002-09-30 20:52:10 +0000174 }
175#else
176 struct stat stb;
177 int i;
178 int j;
179
180 strcpy(line, "/dev/ptyXX");
181
182 for (i = 0; i < 16; i++) {
183 line[8] = "pqrstuvwxyzabcde"[i];
184 line[9] = '0';
185 if (stat(line, &stb) < 0) {
186 continue;
187 }
188 for (j = 0; j < 16; j++) {
189 line[9] = j < 10 ? j + '0' : j - 10 + 'a';
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000190 if (DEBUG)
191 fprintf(stderr, "Trying to open device: %s\n", line);
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000192 p = open(line, O_RDWR | O_NOCTTY);
193 if (p >= 0) {
Eric Andersen08a72202002-09-30 20:52:10 +0000194 line[5] = 't';
195 return p;
196 }
197 }
198 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000199#endif /* FEATURE_DEVPTS */
Eric Andersen08a72202002-09-30 20:52:10 +0000200 return -1;
201}
202
203
Eric Andersen08a72202002-09-30 20:52:10 +0000204static struct tsession *
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000205make_new_session(
Denis Vlasenkof472b232007-10-16 21:35:17 +0000206 USE_FEATURE_TELNETD_STANDALONE(int sock)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000207 SKIP_FEATURE_TELNETD_STANDALONE(void)
208) {
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000209 const char *login_argv[2];
Eric Andersen08a72202002-09-30 20:52:10 +0000210 struct termios termbuf;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000211 int fd, pid;
Eric Andersen08a72202002-09-30 20:52:10 +0000212 char tty_name[32];
Rob Landley1ec5b292006-05-29 07:42:02 +0000213 struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
Eric Andersen08a72202002-09-30 20:52:10 +0000214
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000215 /*ts->buf1 = (char *)(ts + 1);*/
216 /*ts->buf2 = ts->buf1 + BUFSIZE;*/
Eric Andersen08a72202002-09-30 20:52:10 +0000217
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000218 /* Got a new connection, set up a tty. */
219 fd = getpty(tty_name, 32);
220 if (fd < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000221 bb_error_msg("can't create pty");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000222 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000223 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000224 if (fd > maxfd)
225 maxfd = fd;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000226 ts->ptyfd = fd;
227 ndelay_on(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000228#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenkof472b232007-10-16 21:35:17 +0000229 ts->sockfd_read = sock;
230 ndelay_on(sock);
Denis Vlasenko9e237672007-10-17 11:18:49 +0000231 if (!sock) { /* We are called with fd 0 - we are in inetd mode */
232 sock++; /* so use fd 1 for output */
233 ndelay_on(sock);
234 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000235 ts->sockfd_write = sock;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000236 if (sock > maxfd)
237 maxfd = sock;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000238#else
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000239 /* ts->sockfd_read = 0; - done by xzalloc */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000240 ts->sockfd_write = 1;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000241 ndelay_on(0);
242 ndelay_on(1);
243#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000244 /* Make the telnet client understand we will echo characters so it
245 * should not do it locally. We don't tell the client to run linemode,
246 * because we want to handle line editing and tab completion and other
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000247 * stuff that requires char-by-char support. */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000248 {
249 static const char iacs_to_send[] ALIGN1 = {
250 IAC, DO, TELOPT_ECHO,
251 IAC, DO, TELOPT_NAWS,
252 IAC, DO, TELOPT_LFLOW,
253 IAC, WILL, TELOPT_ECHO,
254 IAC, WILL, TELOPT_SGA
255 };
256 memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
257 ts->rdidx2 = sizeof(iacs_to_send);
258 ts->size2 = sizeof(iacs_to_send);
259 }
Eric Andersen08a72202002-09-30 20:52:10 +0000260
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000261 fflush(NULL); /* flush all streams */
262 pid = vfork(); /* NOMMU-friendly */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000263 if (pid < 0) {
264 free(ts);
265 close(fd);
Denis Vlasenko23c81282007-10-16 22:01:23 +0000266 /* sock will be closed by caller */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000267 bb_perror_msg("vfork");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000268 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000269 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000270 if (pid > 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000271 /* Parent */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000272 ts->shell_pid = pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000273 return ts;
Eric Andersen08a72202002-09-30 20:52:10 +0000274 }
Denis Vlasenkof7996f32007-01-11 17:20:00 +0000275
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000276 /* Child */
277 /* Careful - we are after vfork! */
Eric Andersen08a72202002-09-30 20:52:10 +0000278
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000279 /* make new session and process group */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000280 setsid();
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000281
Denis Vlasenko018b1552007-11-06 01:38:46 +0000282 /* Restore default signal handling */
283 signal(SIGCHLD, SIG_DFL);
284 signal(SIGPIPE, SIG_DFL);
285
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000286 /* open the child's side of the tty. */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000287 /* NB: setsid() disconnects from any previous ctty's. Therefore
288 * we must open child's side of the tty AFTER setsid! */
289 fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000290 dup2(fd, 0);
291 dup2(fd, 1);
292 dup2(fd, 2);
293 while (fd > 2) close(fd--);
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000294 tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
Eric Andersen08a72202002-09-30 20:52:10 +0000295
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000296 /* The pseudo-terminal allocated to the client is configured to operate in
297 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
298 tcgetattr(0, &termbuf);
299 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000300 termbuf.c_oflag |= ONLCR | XTABS;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000301 termbuf.c_iflag |= ICRNL;
302 termbuf.c_iflag &= ~IXOFF;
303 /*termbuf.c_lflag &= ~ICANON;*/
304 tcsetattr(0, TCSANOW, &termbuf);
305
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000306 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
307 * so should be safe with vfork.
308 * I fear, though, that some users will have ridiculously big
Denis Vlasenko018b1552007-11-06 01:38:46 +0000309 * issue files, and they may block writing to fd 1,
310 * (parent is supposed to read it, but parent waits
311 * for vforked child to exec!) */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000312 print_login_issue(issuefile, NULL);
313
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000314 /* Exec shell / login / whatever */
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000315 login_argv[0] = loginpath;
316 login_argv[1] = NULL;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000317 /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
318 * exec external program */
319 BB_EXECVP(loginpath, (char **)login_argv);
320 /* _exit is safer with vfork, and we shouldn't send message
Denis Vlasenko2450c452007-10-15 22:09:15 +0000321 * to remote clients anyway */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000322 _exit(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
Eric Andersen08a72202002-09-30 20:52:10 +0000323}
324
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000325/* Must match getopt32 string */
326enum {
327 OPT_WATCHCHILD = (1 << 2), /* -K */
328 OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
329 OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
330 OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
331};
332
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000333#if ENABLE_FEATURE_TELNETD_STANDALONE
334
Eric Andersen08a72202002-09-30 20:52:10 +0000335static void
336free_session(struct tsession *ts)
337{
338 struct tsession *t = sessions;
339
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000340 if (option_mask32 & OPT_INETD)
341 exit(0);
342
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000343 /* Unlink this telnet session from the session list */
Mike Frysinger731f81c2006-05-10 15:23:12 +0000344 if (t == ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000345 sessions = ts->next;
346 else {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000347 while (t->next != ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000348 t = t->next;
349 t->next = ts->next;
350 }
351
Denis Vlasenkof472b232007-10-16 21:35:17 +0000352#if 0
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000353 /* It was said that "normal" telnetd just closes ptyfd,
354 * doesn't send SIGKILL. When we close ptyfd,
355 * kernel sends SIGHUP to processes having slave side opened. */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000356 kill(ts->shell_pid, SIGKILL);
357 wait4(ts->shell_pid, NULL, 0, NULL);
358#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000359 close(ts->ptyfd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000360 close(ts->sockfd_read);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000361 /* We do not need to close(ts->sockfd_write), it's the same
362 * as sockfd_read unless we are in inetd mode. But in inetd mode
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000363 * we do not reach this */
Eric Andersen08a72202002-09-30 20:52:10 +0000364 free(ts);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000365
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000366 /* Scan all sessions and find new maxfd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000367 maxfd = 0;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000368 ts = sessions;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000369 while (ts) {
370 if (maxfd < ts->ptyfd)
371 maxfd = ts->ptyfd;
372 if (maxfd < ts->sockfd_read)
373 maxfd = ts->sockfd_read;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000374#if 0
375 /* Again, sockfd_write == sockfd_read here */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000376 if (maxfd < ts->sockfd_write)
377 maxfd = ts->sockfd_write;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000378#endif
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000379 ts = ts->next;
380 }
Eric Andersen08a72202002-09-30 20:52:10 +0000381}
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000382
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000383#else /* !FEATURE_TELNETD_STANDALONE */
384
Denis Vlasenko018b1552007-11-06 01:38:46 +0000385/* Used in main() only, thus "return 0" actually is exit(0). */
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000386#define free_session(ts) return 0
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000387
388#endif
389
Denis Vlasenko2450c452007-10-15 22:09:15 +0000390static void handle_sigchld(int sig)
391{
392 pid_t pid;
393 struct tsession *ts;
394
Denis Vlasenko018b1552007-11-06 01:38:46 +0000395 /* Looping: more than one child may have exited */
396 while (1) {
Denis Vlasenkofb0eba72008-01-02 19:55:04 +0000397 pid = wait_any_nohang(NULL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000398 if (pid <= 0)
399 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000400 ts = sessions;
401 while (ts) {
402 if (ts->shell_pid == pid) {
403 ts->shell_pid = -1;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000404 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000405 }
406 ts = ts->next;
407 }
408 }
409}
410
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000411int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko06af2162007-02-03 17:28:39 +0000412int telnetd_main(int argc, char **argv)
Eric Andersen08a72202002-09-30 20:52:10 +0000413{
Eric Andersen08a72202002-09-30 20:52:10 +0000414 fd_set rdfdset, wrfdset;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000415 unsigned opt;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000416 int count;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000417 struct tsession *ts;
418#if ENABLE_FEATURE_TELNETD_STANDALONE
419#define IS_INETD (opt & OPT_INETD)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000420 int master_fd = master_fd; /* be happy, gcc */
Denis Vlasenko13858992006-10-08 12:49:22 +0000421 unsigned portnbr = 23;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000422 char *opt_bindaddr = NULL;
423 char *opt_portnbr;
424#else
425 enum {
426 IS_INETD = 1,
427 master_fd = -1,
428 portnbr = 23,
429 };
Glenn L McGrathc2b91862003-09-12 11:27:15 +0000430#endif
Denis Vlasenko2450c452007-10-15 22:09:15 +0000431 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000432 * don't need to guess whether it's ok to pass -i to us */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000433 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
Denis Vlasenko0e87d342006-09-22 08:50:29 +0000434 &issuefile, &loginpath
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000435 USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
Denis Vlasenko2450c452007-10-15 22:09:15 +0000436 if (!IS_INETD /*&& !re_execed*/) {
437 /* inform that we start in standalone mode?
438 * May be useful when people forget to give -i */
439 /*bb_error_msg("listening for connections");*/
440 if (!(opt & OPT_FOREGROUND)) {
441 /* DAEMON_CHDIR_ROOT was giving inconsistent
Denis Vlasenko008eda22007-10-16 10:47:27 +0000442 * behavior with/without -F, -i */
Denis Vlasenko018b1552007-11-06 01:38:46 +0000443 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000444 }
445 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000446 /* Redirect log to syslog early, if needed */
447 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
448 openlog(applet_name, 0, LOG_USER);
449 logmode = LOGMODE_SYSLOG;
450 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000451 USE_FEATURE_TELNETD_STANDALONE(
Denis Vlasenko2450c452007-10-15 22:09:15 +0000452 if (opt & OPT_PORT)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000453 portnbr = xatou16(opt_portnbr);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000454 );
Eric Andersen08a72202002-09-30 20:52:10 +0000455
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000456 /* Used to check access(loginpath, X_OK) here. Pointless.
457 * exec will do this for us for free later. */
Glenn L McGrath9e5d6c02003-01-21 20:55:56 +0000458
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000459#if ENABLE_FEATURE_TELNETD_STANDALONE
460 if (IS_INETD) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000461 sessions = make_new_session(0);
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000462 if (!sessions) /* pty opening or vfork problem, exit */
463 return 1; /* make_new_session prints error message */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000464 } else {
Denis Vlasenko9de420c2007-01-10 09:28:01 +0000465 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
Denis Vlasenkoc8717cd2006-11-22 16:10:39 +0000466 xlisten(master_fd, 1);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000467 }
Rob Landley00e76cb2005-05-10 23:53:33 +0000468#else
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000469 sessions = make_new_session();
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000470 if (!sessions) /* pty opening or vfork problem, exit */
471 return 1; /* make_new_session prints error message */
Rob Landley00e76cb2005-05-10 23:53:33 +0000472#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000473
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000474 /* We don't want to die if just one session is broken */
475 signal(SIGPIPE, SIG_IGN);
Eric Andersen08a72202002-09-30 20:52:10 +0000476
Denis Vlasenko2450c452007-10-15 22:09:15 +0000477 if (opt & OPT_WATCHCHILD)
478 signal(SIGCHLD, handle_sigchld);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000479 else /* prevent dead children from becoming zombies */
480 signal(SIGCHLD, SIG_IGN);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000481
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000482/*
483 This is how the buffers are used. The arrows indicate the movement
484 of data.
485 +-------+ wridx1++ +------+ rdidx1++ +----------+
486 | | <-------------- | buf1 | <-------------- | |
487 | | size1-- +------+ size1++ | |
488 | pty | | socket |
489 | | rdidx2++ +------+ wridx2++ | |
490 | | --------------> | buf2 | --------------> | |
491 +-------+ size2++ +------+ size2-- +----------+
492
493 size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
494 size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
495
496 Each session has got two buffers. Buffers are circular. If sizeN == 0,
497 buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
498 rdidxN == wridxN.
499*/
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000500 again:
501 FD_ZERO(&rdfdset);
502 FD_ZERO(&wrfdset);
Eric Andersen08a72202002-09-30 20:52:10 +0000503
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000504 /* Select on the master socket, all telnet sockets and their
505 * ptys if there is room in their session buffers.
506 * NB: scalability problem: we recalculate entire bitmap
507 * before each select. Can be a problem with 500+ connections. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000508 ts = sessions;
509 while (ts) {
Denis Vlasenko2450c452007-10-15 22:09:15 +0000510 struct tsession *next = ts->next; /* in case we free ts. */
511 if (ts->shell_pid == -1) {
Denis Vlasenko018b1552007-11-06 01:38:46 +0000512 /* Child died and we detected that */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000513 free_session(ts);
514 } else {
515 if (ts->size1 > 0) /* can write to pty */
516 FD_SET(ts->ptyfd, &wrfdset);
517 if (ts->size1 < BUFSIZE) /* can read from socket */
518 FD_SET(ts->sockfd_read, &rdfdset);
519 if (ts->size2 > 0) /* can write to socket */
520 FD_SET(ts->sockfd_write, &wrfdset);
521 if (ts->size2 < BUFSIZE) /* can read from pty */
522 FD_SET(ts->ptyfd, &rdfdset);
523 }
524 ts = next;
525 }
526 if (!IS_INETD) {
527 FD_SET(master_fd, &rdfdset);
528 /* This is needed because free_session() does not
Denis Vlasenko018b1552007-11-06 01:38:46 +0000529 * take master_fd into account when it finds new
Denis Vlasenko2450c452007-10-15 22:09:15 +0000530 * maxfd among remaining fd's */
531 if (master_fd > maxfd)
532 maxfd = master_fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000533 }
534
Denis Vlasenko10916c52007-10-15 17:28:00 +0000535 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
536 if (count < 0)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000537 goto again; /* EINTR or ENOMEM */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000538
539#if ENABLE_FEATURE_TELNETD_STANDALONE
540 /* First check for and accept new sessions. */
541 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000542 int fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000543 struct tsession *new_ts;
544
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000545 fd = accept(master_fd, NULL, NULL);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000546 if (fd < 0)
547 goto again;
548 /* Create a new session and link it into our active list */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000549 new_ts = make_new_session(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000550 if (new_ts) {
551 new_ts->next = sessions;
552 sessions = new_ts;
553 } else {
554 close(fd);
Eric Andersen08a72202002-09-30 20:52:10 +0000555 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000556 }
557#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000558
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000559 /* Then check for data tunneling. */
560 ts = sessions;
561 while (ts) { /* For all sessions... */
562 struct tsession *next = ts->next; /* in case we free ts. */
Eric Andersen08a72202002-09-30 20:52:10 +0000563
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000564 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000565 int num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000566 unsigned char *ptr;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000567 /* Write to pty from buffer 1. */
568 ptr = remove_iacs(ts, &num_totty);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000569 count = safe_write(ts->ptyfd, ptr, num_totty);
570 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000571 if (errno == EAGAIN)
572 goto skip1;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000573 goto kill_session;
Eric Andersen08a72202002-09-30 20:52:10 +0000574 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000575 ts->size1 -= count;
576 ts->wridx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000577 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
Eric Andersen08a72202002-09-30 20:52:10 +0000578 ts->wridx1 = 0;
Eric Andersen08a72202002-09-30 20:52:10 +0000579 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000580 skip1:
581 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000582 /* Write to socket from buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000583 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
584 count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
585 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000586 if (errno == EAGAIN)
587 goto skip2;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000588 goto kill_session;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000589 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000590 ts->size2 -= count;
591 ts->wridx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000592 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000593 ts->wridx2 = 0;
594 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000595 skip2:
Denis Vlasenko10916c52007-10-15 17:28:00 +0000596 /* Should not be needed, but... remove_iacs is actually buggy
597 * (it cannot process iacs which wrap around buffer's end)!
598 * Since properly fixing it requires writing bigger code,
Denis Vlasenkof472b232007-10-16 21:35:17 +0000599 * we rely instead on this code making it virtually impossible
Denis Vlasenko10916c52007-10-15 17:28:00 +0000600 * to have wrapped iac (people don't type at 2k/second).
601 * It also allows for bigger reads in common case. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000602 if (ts->size1 == 0) {
603 ts->rdidx1 = 0;
604 ts->wridx1 = 0;
605 }
606 if (ts->size2 == 0) {
607 ts->rdidx2 = 0;
608 ts->wridx2 = 0;
609 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000610
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000611 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
612 /* Read from socket to buffer 1. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000613 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
614 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
615 if (count <= 0) {
616 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000617 goto skip3;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000618 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000619 }
620 /* Ignore trailing NUL if it is there */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000621 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000622 --count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000623 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000624 ts->size1 += count;
625 ts->rdidx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000626 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
627 ts->rdidx1 = 0;
628 }
629 skip3:
630 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
631 /* Read from pty to buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000632 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
633 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
634 if (count <= 0) {
635 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000636 goto skip4;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000637 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000638 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000639 ts->size2 += count;
640 ts->rdidx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000641 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
642 ts->rdidx2 = 0;
643 }
644 skip4:
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000645 ts = next;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000646 continue;
647 kill_session:
648 free_session(ts);
649 ts = next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000650 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000651
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000652 goto again;
Eric Andersen08a72202002-09-30 20:52:10 +0000653}