blob: 962e5cc7b7398c47f45dc6eef679fcce8fbfce1d [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"
Bernhard Reutner-Fischerf4701962008-01-27 12:50:12 +000027#include <syslog.h>
Rob Landley099ed502006-08-28 09:41:49 +000028
Denis Vlasenko75f8d082006-11-22 15:54:52 +000029#if DEBUG
Eric Andersen08a72202002-09-30 20:52:10 +000030#define TELCMDS
31#define TELOPTS
32#endif
33#include <arpa/telnet.h>
Eric Andersen08a72202002-09-30 20:52:10 +000034
Denis Vlasenko59d7c432007-10-15 15:19:36 +000035/* Structure that describes a session */
Eric Andersen08a72202002-09-30 20:52:10 +000036struct tsession {
37 struct tsession *next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +000038 int sockfd_read, sockfd_write, ptyfd;
Denis Vlasenko2450c452007-10-15 22:09:15 +000039 int shell_pid;
Denis Vlasenko59d7c432007-10-15 15:19:36 +000040
Eric Andersen08a72202002-09-30 20:52:10 +000041 /* two circular buffers */
Denis Vlasenko59d7c432007-10-15 15:19:36 +000042 /*char *buf1, *buf2;*/
43/*#define TS_BUF1 ts->buf1*/
44/*#define TS_BUF2 TS_BUF2*/
Denis Vlasenko10916c52007-10-15 17:28:00 +000045#define TS_BUF1 ((unsigned char*)(ts + 1))
46#define TS_BUF2 (((unsigned char*)(ts + 1)) + BUFSIZE)
Eric Andersen08a72202002-09-30 20:52:10 +000047 int rdidx1, wridx1, size1;
48 int rdidx2, wridx2, size2;
49};
50
Denis Vlasenko88308fe2007-06-25 10:35:11 +000051/* Two buffers are directly after tsession in malloced memory.
52 * Make whole thing fit in 4k */
Denis Vlasenko59d7c432007-10-15 15:19:36 +000053enum { BUFSIZE = (4 * 1024 - sizeof(struct tsession)) / 2 };
Denis Vlasenko88308fe2007-06-25 10:35:11 +000054
Eric Andersen08a72202002-09-30 20:52:10 +000055
Denis Vlasenko59d7c432007-10-15 15:19:36 +000056/* Globals */
Eric Andersen08a72202002-09-30 20:52:10 +000057static int maxfd;
Eric Andersen08a72202002-09-30 20:52:10 +000058static struct tsession *sessions;
Denis Vlasenko59d7c432007-10-15 15:19:36 +000059#if ENABLE_LOGIN
60static const char *loginpath = "/bin/login";
61#else
62static const char *loginpath = DEFAULT_SHELL;
63#endif
64static const char *issuefile = "/etc/issue.net";
Eric Andersen08a72202002-09-30 20:52:10 +000065
66
67/*
Denis Vlasenko59d7c432007-10-15 15:19:36 +000068 Remove all IAC's from buf1 (received IACs are ignored and must be removed
69 so as to not be interpreted by the terminal). Make an uninterrupted
70 string of characters fit for the terminal. Do this by packing
71 all characters meant for the terminal sequentially towards the end of buf.
Eric Andersen08a72202002-09-30 20:52:10 +000072
73 Return a pointer to the beginning of the characters meant for the terminal.
74 and make *num_totty the number of characters that should be sent to
75 the terminal.
76
77 Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
Denis Vlasenko59d7c432007-10-15 15:19:36 +000078 past (bf + len) then that IAC will be left unprocessed and *processed
79 will be less than len.
Eric Andersen08a72202002-09-30 20:52:10 +000080
81 FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
82 what is the escape character? We aren't handling that situation here.
83
Denis Vlasenko10916c52007-10-15 17:28:00 +000084 CR-LF ->'s CR mapping is also done here, for convenience.
85
86 NB: may fail to remove iacs which wrap around buffer!
Denis Vlasenko75f8d082006-11-22 15:54:52 +000087 */
Denis Vlasenko10916c52007-10-15 17:28:00 +000088static unsigned char *
Denis Vlasenko75f8d082006-11-22 15:54:52 +000089remove_iacs(struct tsession *ts, int *pnum_totty)
90{
Denis Vlasenko10916c52007-10-15 17:28:00 +000091 unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
Eric Andersen08a72202002-09-30 20:52:10 +000092 unsigned char *ptr = ptr0;
93 unsigned char *totty = ptr;
94 unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
Eric Andersen08a72202002-09-30 20:52:10 +000095 int num_totty;
96
97 while (ptr < end) {
98 if (*ptr != IAC) {
Denis Vlasenko10916c52007-10-15 17:28:00 +000099 char c = *ptr;
100
101 *totty++ = c;
102 ptr++;
Eric Andersen3752d332003-12-19 11:30:13 +0000103 /* We now map \r\n ==> \r for pragmatic reasons.
104 * Many client implementations send \r\n when
105 * the user hits the CarriageReturn key.
106 */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000107 if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
Eric Andersen3752d332003-12-19 11:30:13 +0000108 ptr++;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000109 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000110 /*
111 * TELOPT_NAWS support!
112 */
113 if ((ptr+2) >= end) {
Eric Andersen08a72202002-09-30 20:52:10 +0000114 /* only the beginning of the IAC is in the
115 buffer we were asked to process, we can't
116 process this char. */
117 break;
118 }
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000119
120 /*
121 * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
122 */
123 else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
124 struct winsize ws;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000125
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000126 if ((ptr+8) >= end)
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000127 break; /* incomplete, can't process */
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000128 ws.ws_col = (ptr[3] << 8) | ptr[4];
129 ws.ws_row = (ptr[5] << 8) | ptr[6];
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000130 ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000131 ptr += 9;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000132 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000133 /* skip 3-byte IAC non-SB cmd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000134#if DEBUG
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000135 fprintf(stderr, "Ignoring IAC %s,%s\n",
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000136 TELCMD(ptr[1]), TELOPT(ptr[2]));
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000137#endif
138 ptr += 3;
139 }
Eric Andersen08a72202002-09-30 20:52:10 +0000140 }
141 }
142
Denis Vlasenko0de37e12007-10-17 11:08:53 +0000143 num_totty = totty - ptr0;
Eric Andersen08a72202002-09-30 20:52:10 +0000144 *pnum_totty = num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000145 /* the difference between ptr and totty is number of iacs
146 we removed from the stream. Adjust buf1 accordingly. */
147 if ((ptr - totty) == 0) /* 99.999% of cases */
148 return ptr0;
149 ts->wridx1 += ptr - totty;
150 ts->size1 -= ptr - totty;
151 /* move chars meant for the terminal towards the end of the buffer */
Eric Andersen08a72202002-09-30 20:52:10 +0000152 return memmove(ptr - num_totty, ptr0, num_totty);
153}
154
155
156static int
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000157getpty(char *line, int size)
Eric Andersen08a72202002-09-30 20:52:10 +0000158{
159 int p;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000160#if ENABLE_FEATURE_DEVPTS
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000161 p = open("/dev/ptmx", O_RDWR);
Eric Andersen08a72202002-09-30 20:52:10 +0000162 if (p > 0) {
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000163 const char *name;
Eric Andersen08a72202002-09-30 20:52:10 +0000164 grantpt(p);
165 unlockpt(p);
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000166 name = ptsname(p);
167 if (!name) {
168 bb_perror_msg("ptsname error (is /dev/pts mounted?)");
169 return -1;
170 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000171 safe_strncpy(line, name, size);
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000172 return p;
Eric Andersen08a72202002-09-30 20:52:10 +0000173 }
174#else
175 struct stat stb;
176 int i;
177 int j;
178
179 strcpy(line, "/dev/ptyXX");
180
181 for (i = 0; i < 16; i++) {
182 line[8] = "pqrstuvwxyzabcde"[i];
183 line[9] = '0';
184 if (stat(line, &stb) < 0) {
185 continue;
186 }
187 for (j = 0; j < 16; j++) {
188 line[9] = j < 10 ? j + '0' : j - 10 + 'a';
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000189 if (DEBUG)
190 fprintf(stderr, "Trying to open device: %s\n", line);
Denis Vlasenkoc6ec8c92006-10-15 18:22:05 +0000191 p = open(line, O_RDWR | O_NOCTTY);
192 if (p >= 0) {
Eric Andersen08a72202002-09-30 20:52:10 +0000193 line[5] = 't';
194 return p;
195 }
196 }
197 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000198#endif /* FEATURE_DEVPTS */
Eric Andersen08a72202002-09-30 20:52:10 +0000199 return -1;
200}
201
202
Eric Andersen08a72202002-09-30 20:52:10 +0000203static struct tsession *
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000204make_new_session(
Denis Vlasenkof472b232007-10-16 21:35:17 +0000205 USE_FEATURE_TELNETD_STANDALONE(int sock)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000206 SKIP_FEATURE_TELNETD_STANDALONE(void)
207) {
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000208 const char *login_argv[2];
Eric Andersen08a72202002-09-30 20:52:10 +0000209 struct termios termbuf;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000210 int fd, pid;
Eric Andersen08a72202002-09-30 20:52:10 +0000211 char tty_name[32];
Rob Landley1ec5b292006-05-29 07:42:02 +0000212 struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
Eric Andersen08a72202002-09-30 20:52:10 +0000213
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000214 /*ts->buf1 = (char *)(ts + 1);*/
215 /*ts->buf2 = ts->buf1 + BUFSIZE;*/
Eric Andersen08a72202002-09-30 20:52:10 +0000216
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000217 /* Got a new connection, set up a tty. */
218 fd = getpty(tty_name, 32);
219 if (fd < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000220 bb_error_msg("can't create pty");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000221 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000222 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000223 if (fd > maxfd)
224 maxfd = fd;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000225 ts->ptyfd = fd;
226 ndelay_on(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000227#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenkof472b232007-10-16 21:35:17 +0000228 ts->sockfd_read = sock;
229 ndelay_on(sock);
Denis Vlasenko9e237672007-10-17 11:18:49 +0000230 if (!sock) { /* We are called with fd 0 - we are in inetd mode */
231 sock++; /* so use fd 1 for output */
232 ndelay_on(sock);
233 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000234 ts->sockfd_write = sock;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000235 if (sock > maxfd)
236 maxfd = sock;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000237#else
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000238 /* ts->sockfd_read = 0; - done by xzalloc */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000239 ts->sockfd_write = 1;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000240 ndelay_on(0);
241 ndelay_on(1);
242#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000243 /* Make the telnet client understand we will echo characters so it
244 * should not do it locally. We don't tell the client to run linemode,
245 * because we want to handle line editing and tab completion and other
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000246 * stuff that requires char-by-char support. */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000247 {
248 static const char iacs_to_send[] ALIGN1 = {
249 IAC, DO, TELOPT_ECHO,
250 IAC, DO, TELOPT_NAWS,
251 IAC, DO, TELOPT_LFLOW,
252 IAC, WILL, TELOPT_ECHO,
253 IAC, WILL, TELOPT_SGA
254 };
255 memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
256 ts->rdidx2 = sizeof(iacs_to_send);
257 ts->size2 = sizeof(iacs_to_send);
258 }
Eric Andersen08a72202002-09-30 20:52:10 +0000259
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000260 fflush(NULL); /* flush all streams */
261 pid = vfork(); /* NOMMU-friendly */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000262 if (pid < 0) {
263 free(ts);
264 close(fd);
Denis Vlasenko23c81282007-10-16 22:01:23 +0000265 /* sock will be closed by caller */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000266 bb_perror_msg("vfork");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000267 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000268 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000269 if (pid > 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000270 /* Parent */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000271 ts->shell_pid = pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000272 return ts;
Eric Andersen08a72202002-09-30 20:52:10 +0000273 }
Denis Vlasenkof7996f32007-01-11 17:20:00 +0000274
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000275 /* Child */
276 /* Careful - we are after vfork! */
Eric Andersen08a72202002-09-30 20:52:10 +0000277
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000278 /* make new session and process group */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000279 setsid();
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000280
Denis Vlasenko018b1552007-11-06 01:38:46 +0000281 /* Restore default signal handling */
282 signal(SIGCHLD, SIG_DFL);
283 signal(SIGPIPE, SIG_DFL);
284
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000285 /* open the child's side of the tty. */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000286 /* NB: setsid() disconnects from any previous ctty's. Therefore
287 * we must open child's side of the tty AFTER setsid! */
288 fd = xopen(tty_name, O_RDWR); /* becomes our ctty */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000289 dup2(fd, 0);
290 dup2(fd, 1);
291 dup2(fd, 2);
292 while (fd > 2) close(fd--);
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000293 tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
Eric Andersen08a72202002-09-30 20:52:10 +0000294
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000295 /* The pseudo-terminal allocated to the client is configured to operate in
296 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
297 tcgetattr(0, &termbuf);
298 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000299 termbuf.c_oflag |= ONLCR | XTABS;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000300 termbuf.c_iflag |= ICRNL;
301 termbuf.c_iflag &= ~IXOFF;
302 /*termbuf.c_lflag &= ~ICANON;*/
303 tcsetattr(0, TCSANOW, &termbuf);
304
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000305 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
306 * so should be safe with vfork.
307 * I fear, though, that some users will have ridiculously big
Denis Vlasenko018b1552007-11-06 01:38:46 +0000308 * issue files, and they may block writing to fd 1,
309 * (parent is supposed to read it, but parent waits
310 * for vforked child to exec!) */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000311 print_login_issue(issuefile, NULL);
312
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000313 /* Exec shell / login / whatever */
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000314 login_argv[0] = loginpath;
315 login_argv[1] = NULL;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000316 /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
317 * exec external program */
318 BB_EXECVP(loginpath, (char **)login_argv);
319 /* _exit is safer with vfork, and we shouldn't send message
Denis Vlasenko2450c452007-10-15 22:09:15 +0000320 * to remote clients anyway */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000321 _exit(1); /*bb_perror_msg_and_die("execv %s", loginpath);*/
Eric Andersen08a72202002-09-30 20:52:10 +0000322}
323
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000324/* Must match getopt32 string */
325enum {
326 OPT_WATCHCHILD = (1 << 2), /* -K */
327 OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
328 OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
329 OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
330};
331
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000332#if ENABLE_FEATURE_TELNETD_STANDALONE
333
Eric Andersen08a72202002-09-30 20:52:10 +0000334static void
335free_session(struct tsession *ts)
336{
337 struct tsession *t = sessions;
338
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000339 if (option_mask32 & OPT_INETD)
340 exit(0);
341
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000342 /* Unlink this telnet session from the session list */
Mike Frysinger731f81c2006-05-10 15:23:12 +0000343 if (t == ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000344 sessions = ts->next;
345 else {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000346 while (t->next != ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000347 t = t->next;
348 t->next = ts->next;
349 }
350
Denis Vlasenkof472b232007-10-16 21:35:17 +0000351#if 0
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000352 /* It was said that "normal" telnetd just closes ptyfd,
353 * doesn't send SIGKILL. When we close ptyfd,
354 * kernel sends SIGHUP to processes having slave side opened. */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000355 kill(ts->shell_pid, SIGKILL);
356 wait4(ts->shell_pid, NULL, 0, NULL);
357#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000358 close(ts->ptyfd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000359 close(ts->sockfd_read);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000360 /* We do not need to close(ts->sockfd_write), it's the same
361 * as sockfd_read unless we are in inetd mode. But in inetd mode
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000362 * we do not reach this */
Eric Andersen08a72202002-09-30 20:52:10 +0000363 free(ts);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000364
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000365 /* Scan all sessions and find new maxfd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000366 maxfd = 0;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000367 ts = sessions;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000368 while (ts) {
369 if (maxfd < ts->ptyfd)
370 maxfd = ts->ptyfd;
371 if (maxfd < ts->sockfd_read)
372 maxfd = ts->sockfd_read;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000373#if 0
374 /* Again, sockfd_write == sockfd_read here */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000375 if (maxfd < ts->sockfd_write)
376 maxfd = ts->sockfd_write;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000377#endif
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000378 ts = ts->next;
379 }
Eric Andersen08a72202002-09-30 20:52:10 +0000380}
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000381
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000382#else /* !FEATURE_TELNETD_STANDALONE */
383
Denis Vlasenko018b1552007-11-06 01:38:46 +0000384/* Used in main() only, thus "return 0" actually is exit(0). */
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000385#define free_session(ts) return 0
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000386
387#endif
388
Denis Vlasenko2450c452007-10-15 22:09:15 +0000389static void handle_sigchld(int sig)
390{
391 pid_t pid;
392 struct tsession *ts;
393
Denis Vlasenko018b1552007-11-06 01:38:46 +0000394 /* Looping: more than one child may have exited */
395 while (1) {
Denis Vlasenkofb0eba72008-01-02 19:55:04 +0000396 pid = wait_any_nohang(NULL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000397 if (pid <= 0)
398 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000399 ts = sessions;
400 while (ts) {
401 if (ts->shell_pid == pid) {
402 ts->shell_pid = -1;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000403 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000404 }
405 ts = ts->next;
406 }
407 }
408}
409
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000410int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenko06af2162007-02-03 17:28:39 +0000411int telnetd_main(int argc, char **argv)
Eric Andersen08a72202002-09-30 20:52:10 +0000412{
Eric Andersen08a72202002-09-30 20:52:10 +0000413 fd_set rdfdset, wrfdset;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000414 unsigned opt;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000415 int count;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000416 struct tsession *ts;
417#if ENABLE_FEATURE_TELNETD_STANDALONE
418#define IS_INETD (opt & OPT_INETD)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000419 int master_fd = master_fd; /* be happy, gcc */
Denis Vlasenko13858992006-10-08 12:49:22 +0000420 unsigned portnbr = 23;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000421 char *opt_bindaddr = NULL;
422 char *opt_portnbr;
423#else
424 enum {
425 IS_INETD = 1,
426 master_fd = -1,
427 portnbr = 23,
428 };
Glenn L McGrathc2b91862003-09-12 11:27:15 +0000429#endif
Denis Vlasenko2450c452007-10-15 22:09:15 +0000430 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000431 * don't need to guess whether it's ok to pass -i to us */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000432 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
Denis Vlasenko0e87d342006-09-22 08:50:29 +0000433 &issuefile, &loginpath
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000434 USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
Denis Vlasenko2450c452007-10-15 22:09:15 +0000435 if (!IS_INETD /*&& !re_execed*/) {
436 /* inform that we start in standalone mode?
437 * May be useful when people forget to give -i */
438 /*bb_error_msg("listening for connections");*/
439 if (!(opt & OPT_FOREGROUND)) {
440 /* DAEMON_CHDIR_ROOT was giving inconsistent
Denis Vlasenko008eda22007-10-16 10:47:27 +0000441 * behavior with/without -F, -i */
Denis Vlasenko018b1552007-11-06 01:38:46 +0000442 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000443 }
444 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000445 /* Redirect log to syslog early, if needed */
446 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
447 openlog(applet_name, 0, LOG_USER);
448 logmode = LOGMODE_SYSLOG;
449 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000450 USE_FEATURE_TELNETD_STANDALONE(
Denis Vlasenko2450c452007-10-15 22:09:15 +0000451 if (opt & OPT_PORT)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000452 portnbr = xatou16(opt_portnbr);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000453 );
Eric Andersen08a72202002-09-30 20:52:10 +0000454
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000455 /* Used to check access(loginpath, X_OK) here. Pointless.
456 * exec will do this for us for free later. */
Glenn L McGrath9e5d6c02003-01-21 20:55:56 +0000457
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000458#if ENABLE_FEATURE_TELNETD_STANDALONE
459 if (IS_INETD) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000460 sessions = make_new_session(0);
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000461 if (!sessions) /* pty opening or vfork problem, exit */
462 return 1; /* make_new_session prints error message */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000463 } else {
Denis Vlasenko9de420c2007-01-10 09:28:01 +0000464 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
Denis Vlasenkoc8717cd2006-11-22 16:10:39 +0000465 xlisten(master_fd, 1);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000466 }
Rob Landley00e76cb2005-05-10 23:53:33 +0000467#else
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000468 sessions = make_new_session();
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000469 if (!sessions) /* pty opening or vfork problem, exit */
470 return 1; /* make_new_session prints error message */
Rob Landley00e76cb2005-05-10 23:53:33 +0000471#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000472
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000473 /* We don't want to die if just one session is broken */
474 signal(SIGPIPE, SIG_IGN);
Eric Andersen08a72202002-09-30 20:52:10 +0000475
Denis Vlasenko2450c452007-10-15 22:09:15 +0000476 if (opt & OPT_WATCHCHILD)
477 signal(SIGCHLD, handle_sigchld);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000478 else /* prevent dead children from becoming zombies */
479 signal(SIGCHLD, SIG_IGN);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000480
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000481/*
482 This is how the buffers are used. The arrows indicate the movement
483 of data.
484 +-------+ wridx1++ +------+ rdidx1++ +----------+
485 | | <-------------- | buf1 | <-------------- | |
486 | | size1-- +------+ size1++ | |
487 | pty | | socket |
488 | | rdidx2++ +------+ wridx2++ | |
489 | | --------------> | buf2 | --------------> | |
490 +-------+ size2++ +------+ size2-- +----------+
491
492 size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
493 size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
494
495 Each session has got two buffers. Buffers are circular. If sizeN == 0,
496 buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
497 rdidxN == wridxN.
498*/
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000499 again:
500 FD_ZERO(&rdfdset);
501 FD_ZERO(&wrfdset);
Eric Andersen08a72202002-09-30 20:52:10 +0000502
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000503 /* Select on the master socket, all telnet sockets and their
504 * ptys if there is room in their session buffers.
505 * NB: scalability problem: we recalculate entire bitmap
506 * before each select. Can be a problem with 500+ connections. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000507 ts = sessions;
508 while (ts) {
Denis Vlasenko2450c452007-10-15 22:09:15 +0000509 struct tsession *next = ts->next; /* in case we free ts. */
510 if (ts->shell_pid == -1) {
Denis Vlasenko018b1552007-11-06 01:38:46 +0000511 /* Child died and we detected that */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000512 free_session(ts);
513 } else {
514 if (ts->size1 > 0) /* can write to pty */
515 FD_SET(ts->ptyfd, &wrfdset);
516 if (ts->size1 < BUFSIZE) /* can read from socket */
517 FD_SET(ts->sockfd_read, &rdfdset);
518 if (ts->size2 > 0) /* can write to socket */
519 FD_SET(ts->sockfd_write, &wrfdset);
520 if (ts->size2 < BUFSIZE) /* can read from pty */
521 FD_SET(ts->ptyfd, &rdfdset);
522 }
523 ts = next;
524 }
525 if (!IS_INETD) {
526 FD_SET(master_fd, &rdfdset);
527 /* This is needed because free_session() does not
Denis Vlasenko018b1552007-11-06 01:38:46 +0000528 * take master_fd into account when it finds new
Denis Vlasenko2450c452007-10-15 22:09:15 +0000529 * maxfd among remaining fd's */
530 if (master_fd > maxfd)
531 maxfd = master_fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000532 }
533
Denis Vlasenko10916c52007-10-15 17:28:00 +0000534 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
535 if (count < 0)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000536 goto again; /* EINTR or ENOMEM */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000537
538#if ENABLE_FEATURE_TELNETD_STANDALONE
539 /* First check for and accept new sessions. */
540 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000541 int fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000542 struct tsession *new_ts;
543
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000544 fd = accept(master_fd, NULL, NULL);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000545 if (fd < 0)
546 goto again;
547 /* Create a new session and link it into our active list */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000548 new_ts = make_new_session(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000549 if (new_ts) {
550 new_ts->next = sessions;
551 sessions = new_ts;
552 } else {
553 close(fd);
Eric Andersen08a72202002-09-30 20:52:10 +0000554 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000555 }
556#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000557
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000558 /* Then check for data tunneling. */
559 ts = sessions;
560 while (ts) { /* For all sessions... */
561 struct tsession *next = ts->next; /* in case we free ts. */
Eric Andersen08a72202002-09-30 20:52:10 +0000562
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000563 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000564 int num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000565 unsigned char *ptr;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000566 /* Write to pty from buffer 1. */
567 ptr = remove_iacs(ts, &num_totty);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000568 count = safe_write(ts->ptyfd, ptr, num_totty);
569 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000570 if (errno == EAGAIN)
571 goto skip1;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000572 goto kill_session;
Eric Andersen08a72202002-09-30 20:52:10 +0000573 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000574 ts->size1 -= count;
575 ts->wridx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000576 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
Eric Andersen08a72202002-09-30 20:52:10 +0000577 ts->wridx1 = 0;
Eric Andersen08a72202002-09-30 20:52:10 +0000578 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000579 skip1:
580 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000581 /* Write to socket from buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000582 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
583 count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
584 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000585 if (errno == EAGAIN)
586 goto skip2;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000587 goto kill_session;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000588 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000589 ts->size2 -= count;
590 ts->wridx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000591 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000592 ts->wridx2 = 0;
593 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000594 skip2:
Denis Vlasenko10916c52007-10-15 17:28:00 +0000595 /* Should not be needed, but... remove_iacs is actually buggy
596 * (it cannot process iacs which wrap around buffer's end)!
597 * Since properly fixing it requires writing bigger code,
Denis Vlasenkof472b232007-10-16 21:35:17 +0000598 * we rely instead on this code making it virtually impossible
Denis Vlasenko10916c52007-10-15 17:28:00 +0000599 * to have wrapped iac (people don't type at 2k/second).
600 * It also allows for bigger reads in common case. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000601 if (ts->size1 == 0) {
602 ts->rdidx1 = 0;
603 ts->wridx1 = 0;
604 }
605 if (ts->size2 == 0) {
606 ts->rdidx2 = 0;
607 ts->wridx2 = 0;
608 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000609
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000610 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
611 /* Read from socket to buffer 1. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000612 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
613 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
614 if (count <= 0) {
615 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000616 goto skip3;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000617 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000618 }
619 /* Ignore trailing NUL if it is there */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000620 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000621 --count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000622 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000623 ts->size1 += count;
624 ts->rdidx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000625 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
626 ts->rdidx1 = 0;
627 }
628 skip3:
629 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
630 /* Read from pty to buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000631 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
632 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
633 if (count <= 0) {
634 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000635 goto skip4;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000636 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000637 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000638 ts->size2 += count;
639 ts->rdidx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000640 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
641 ts->rdidx2 = 0;
642 }
643 skip4:
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000644 ts = next;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000645 continue;
646 kill_session:
647 free_session(ts);
648 ts = next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000649 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000650
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000651 goto again;
Eric Andersen08a72202002-09-30 20:52:10 +0000652}