blob: 59d609bc8ed0672c9781fddfa35481adb9a2f500 [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 Vlasenkod814c982009-02-02 23:43:57 +000038 pid_t shell_pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +000039 int sockfd_read, sockfd_write, ptyfd;
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 +000059static const char *loginpath = "/bin/login";
Denis Vlasenko59d7c432007-10-15 15:19:36 +000060static const char *issuefile = "/etc/issue.net";
Eric Andersen08a72202002-09-30 20:52:10 +000061
62
63/*
Denis Vlasenko59d7c432007-10-15 15:19:36 +000064 Remove all IAC's from buf1 (received IACs are ignored and must be removed
65 so as to not be interpreted by the terminal). Make an uninterrupted
66 string of characters fit for the terminal. Do this by packing
67 all characters meant for the terminal sequentially towards the end of buf.
Eric Andersen08a72202002-09-30 20:52:10 +000068
69 Return a pointer to the beginning of the characters meant for the terminal.
70 and make *num_totty the number of characters that should be sent to
71 the terminal.
72
73 Note - If an IAC (3 byte quantity) starts before (bf + len) but extends
Denis Vlasenko59d7c432007-10-15 15:19:36 +000074 past (bf + len) then that IAC will be left unprocessed and *processed
75 will be less than len.
Eric Andersen08a72202002-09-30 20:52:10 +000076
Denis Vlasenko10916c52007-10-15 17:28:00 +000077 CR-LF ->'s CR mapping is also done here, for convenience.
78
79 NB: may fail to remove iacs which wrap around buffer!
Denis Vlasenko75f8d082006-11-22 15:54:52 +000080 */
Denis Vlasenko10916c52007-10-15 17:28:00 +000081static unsigned char *
Denis Vlasenko75f8d082006-11-22 15:54:52 +000082remove_iacs(struct tsession *ts, int *pnum_totty)
83{
Denis Vlasenko10916c52007-10-15 17:28:00 +000084 unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
Eric Andersen08a72202002-09-30 20:52:10 +000085 unsigned char *ptr = ptr0;
86 unsigned char *totty = ptr;
87 unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
Eric Andersen08a72202002-09-30 20:52:10 +000088 int num_totty;
89
90 while (ptr < end) {
91 if (*ptr != IAC) {
Denis Vlasenko10916c52007-10-15 17:28:00 +000092 char c = *ptr;
93
94 *totty++ = c;
95 ptr++;
Denis Vlasenkob0150d22008-11-07 01:58:21 +000096 /* We map \r\n ==> \r for pragmatic reasons.
Eric Andersen3752d332003-12-19 11:30:13 +000097 * Many client implementations send \r\n when
98 * the user hits the CarriageReturn key.
99 */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000100 if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
Eric Andersen3752d332003-12-19 11:30:13 +0000101 ptr++;
Denis Vlasenkob0150d22008-11-07 01:58:21 +0000102 continue;
Eric Andersen08a72202002-09-30 20:52:10 +0000103 }
Denis Vlasenkob0150d22008-11-07 01:58:21 +0000104
105 if ((ptr+1) >= end)
106 break;
107 if (ptr[1] == NOP) { /* Ignore? (putty keepalive, etc.) */
108 ptr += 2;
109 continue;
110 }
111 if (ptr[1] == IAC) { /* Literal IAC? (emacs M-DEL) */
112 *totty++ = ptr[1];
113 ptr += 2;
114 continue;
115 }
116
117 /*
118 * TELOPT_NAWS support!
119 */
120 if ((ptr+2) >= end) {
121 /* only the beginning of the IAC is in the
122 buffer we were asked to process, we can't
123 process this char. */
124 break;
125 }
126 /*
127 * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
128 */
129 if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
130 struct winsize ws;
131 if ((ptr+8) >= end)
132 break; /* incomplete, can't process */
133 ws.ws_col = (ptr[3] << 8) | ptr[4];
134 ws.ws_row = (ptr[5] << 8) | ptr[6];
135 ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
136 ptr += 9;
137 continue;
138 }
139 /* skip 3-byte IAC non-SB cmd */
140#if DEBUG
141 fprintf(stderr, "Ignoring IAC %s,%s\n",
142 TELCMD(ptr[1]), TELOPT(ptr[2]));
143#endif
144 ptr += 3;
Eric Andersen08a72202002-09-30 20:52:10 +0000145 }
146
Denis Vlasenko0de37e12007-10-17 11:08:53 +0000147 num_totty = totty - ptr0;
Eric Andersen08a72202002-09-30 20:52:10 +0000148 *pnum_totty = num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000149 /* the difference between ptr and totty is number of iacs
150 we removed from the stream. Adjust buf1 accordingly. */
151 if ((ptr - totty) == 0) /* 99.999% of cases */
152 return ptr0;
153 ts->wridx1 += ptr - totty;
154 ts->size1 -= ptr - totty;
155 /* move chars meant for the terminal towards the end of the buffer */
Eric Andersen08a72202002-09-30 20:52:10 +0000156 return memmove(ptr - num_totty, ptr0, num_totty);
157}
158
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000159/*
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000160 * Converting single IAC into double on output
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000161 */
162static size_t iac_safe_write(int fd, const char *buf, size_t count)
163{
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000164 const char *IACptr;
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000165 size_t wr, rc, total;
166
167 total = 0;
168 while (1) {
169 if (count == 0)
170 return total;
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000171 if (*buf == (char)IAC) {
172 static const char IACIAC[] ALIGN1 = { IAC, IAC };
173 rc = safe_write(fd, IACIAC, 2);
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000174 if (rc != 2)
175 break;
176 buf++;
177 total++;
178 count--;
179 continue;
180 }
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000181 /* count != 0, *buf != IAC */
182 IACptr = memchr(buf, IAC, count);
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000183 wr = count;
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000184 if (IACptr)
185 wr = IACptr - buf;
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000186 rc = safe_write(fd, buf, wr);
187 if (rc != wr)
188 break;
189 buf += rc;
190 total += rc;
191 count -= rc;
192 }
193 /* here: rc - result of last short write */
194 if ((ssize_t)rc < 0) { /* error? */
195 if (total == 0)
196 return rc;
197 rc = 0;
198 }
199 return total + rc;
200}
Eric Andersen08a72202002-09-30 20:52:10 +0000201
Eric Andersen08a72202002-09-30 20:52:10 +0000202static struct tsession *
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000203make_new_session(
Denis Vlasenkof472b232007-10-16 21:35:17 +0000204 USE_FEATURE_TELNETD_STANDALONE(int sock)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000205 SKIP_FEATURE_TELNETD_STANDALONE(void)
206) {
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000207 const char *login_argv[2];
Eric Andersen08a72202002-09-30 20:52:10 +0000208 struct termios termbuf;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000209 int fd, pid;
Denis Vlasenko85c24712008-03-17 09:04:04 +0000210 char tty_name[GETPTY_BUFSIZE];
Rob Landley1ec5b292006-05-29 07:42:02 +0000211 struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
Eric Andersen08a72202002-09-30 20:52:10 +0000212
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000213 /*ts->buf1 = (char *)(ts + 1);*/
214 /*ts->buf2 = ts->buf1 + BUFSIZE;*/
Eric Andersen08a72202002-09-30 20:52:10 +0000215
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000216 /* Got a new connection, set up a tty. */
Bernhard Reutner-Fischerae4342c2008-05-19 08:18:50 +0000217 fd = xgetpty(tty_name);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000218 if (fd > maxfd)
219 maxfd = fd;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000220 ts->ptyfd = fd;
221 ndelay_on(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000222#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenkof472b232007-10-16 21:35:17 +0000223 ts->sockfd_read = sock;
Denis Vlasenko6d044352008-11-09 00:44:40 +0000224 /* SO_KEEPALIVE by popular demand */
225 setsockopt(sock, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenkof472b232007-10-16 21:35:17 +0000226 ndelay_on(sock);
Denis Vlasenko9e237672007-10-17 11:18:49 +0000227 if (!sock) { /* We are called with fd 0 - we are in inetd mode */
228 sock++; /* so use fd 1 for output */
229 ndelay_on(sock);
230 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000231 ts->sockfd_write = sock;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000232 if (sock > maxfd)
233 maxfd = sock;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000234#else
Denis Vlasenko6d044352008-11-09 00:44:40 +0000235 /* SO_KEEPALIVE by popular demand */
236 setsockopt(0, SOL_SOCKET, SO_KEEPALIVE, &const_int_1, sizeof(const_int_1));
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000237 /* ts->sockfd_read = 0; - done by xzalloc */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000238 ts->sockfd_write = 1;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000239 ndelay_on(0);
240 ndelay_on(1);
241#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000242 /* Make the telnet client understand we will echo characters so it
243 * should not do it locally. We don't tell the client to run linemode,
244 * because we want to handle line editing and tab completion and other
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000245 * stuff that requires char-by-char support. */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000246 {
247 static const char iacs_to_send[] ALIGN1 = {
248 IAC, DO, TELOPT_ECHO,
249 IAC, DO, TELOPT_NAWS,
Denis Vlasenkod814c982009-02-02 23:43:57 +0000250 /* This requires telnetd.ctrlSQ.patch (incomplete) */
251 /* IAC, DO, TELOPT_LFLOW, */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000252 IAC, WILL, TELOPT_ECHO,
253 IAC, WILL, TELOPT_SGA
254 };
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000255 /* This confuses iac_safe_write(), it will try to duplicate
256 * each IAC... */
257 //memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
258 //ts->rdidx2 = sizeof(iacs_to_send);
259 //ts->size2 = sizeof(iacs_to_send);
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000260 /* So just stuff it into TCP stream! (no error check...) */
261#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000262 safe_write(sock, iacs_to_send, sizeof(iacs_to_send));
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000263#else
264 safe_write(1, iacs_to_send, sizeof(iacs_to_send));
265#endif
266 /*ts->rdidx2 = 0; - xzalloc did it */
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000267 /*ts->size2 = 0;*/
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000268 }
Eric Andersen08a72202002-09-30 20:52:10 +0000269
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000270 fflush(NULL); /* flush all streams */
271 pid = vfork(); /* NOMMU-friendly */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000272 if (pid < 0) {
273 free(ts);
274 close(fd);
Denis Vlasenko23c81282007-10-16 22:01:23 +0000275 /* sock will be closed by caller */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000276 bb_perror_msg("vfork");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000277 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000278 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000279 if (pid > 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000280 /* Parent */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000281 ts->shell_pid = pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000282 return ts;
Eric Andersen08a72202002-09-30 20:52:10 +0000283 }
Denis Vlasenkof7996f32007-01-11 17:20:00 +0000284
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000285 /* Child */
286 /* Careful - we are after vfork! */
Eric Andersen08a72202002-09-30 20:52:10 +0000287
Denis Vlasenkod814c982009-02-02 23:43:57 +0000288 /* Restore default signal handling ASAP */
Denis Vlasenko25591c32008-02-16 22:58:56 +0000289 bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000290
Denis Vlasenkod814c982009-02-02 23:43:57 +0000291 /* Make new session and process group */
292 setsid();
293
294 /* Open the child's side of the tty. */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000295 /* NB: setsid() disconnects from any previous ctty's. Therefore
296 * we must open child's side of the tty AFTER setsid! */
Denis Vlasenko1101d1c2008-07-21 09:22:28 +0000297 close(0);
298 xopen(tty_name, O_RDWR); /* becomes our ctty */
299 xdup2(0, 1);
300 xdup2(0, 2);
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000301 tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
Eric Andersen08a72202002-09-30 20:52:10 +0000302
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000303 /* The pseudo-terminal allocated to the client is configured to operate in
304 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
305 tcgetattr(0, &termbuf);
306 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000307 termbuf.c_oflag |= ONLCR | XTABS;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000308 termbuf.c_iflag |= ICRNL;
309 termbuf.c_iflag &= ~IXOFF;
310 /*termbuf.c_lflag &= ~ICANON;*/
Denis Vlasenko202ac502008-11-05 13:20:58 +0000311 tcsetattr_stdin_TCSANOW(&termbuf);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000312
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000313 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
314 * so should be safe with vfork.
315 * I fear, though, that some users will have ridiculously big
Denis Vlasenko018b1552007-11-06 01:38:46 +0000316 * issue files, and they may block writing to fd 1,
317 * (parent is supposed to read it, but parent waits
318 * for vforked child to exec!) */
Denis Vlasenko1101d1c2008-07-21 09:22:28 +0000319 print_login_issue(issuefile, tty_name);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000320
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000321 /* Exec shell / login / whatever */
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000322 login_argv[0] = loginpath;
323 login_argv[1] = NULL;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000324 /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
325 * exec external program */
326 BB_EXECVP(loginpath, (char **)login_argv);
327 /* _exit is safer with vfork, and we shouldn't send message
Denis Vlasenko2450c452007-10-15 22:09:15 +0000328 * to remote clients anyway */
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000329 _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/
Eric Andersen08a72202002-09-30 20:52:10 +0000330}
331
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000332/* Must match getopt32 string */
333enum {
334 OPT_WATCHCHILD = (1 << 2), /* -K */
335 OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
336 OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
337 OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
338};
339
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000340#if ENABLE_FEATURE_TELNETD_STANDALONE
341
Eric Andersen08a72202002-09-30 20:52:10 +0000342static void
343free_session(struct tsession *ts)
344{
345 struct tsession *t = sessions;
346
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000347 if (option_mask32 & OPT_INETD)
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000348 exit(EXIT_SUCCESS);
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000349
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000350 /* Unlink this telnet session from the session list */
Mike Frysinger731f81c2006-05-10 15:23:12 +0000351 if (t == ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000352 sessions = ts->next;
353 else {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000354 while (t->next != ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000355 t = t->next;
356 t->next = ts->next;
357 }
358
Denis Vlasenkof472b232007-10-16 21:35:17 +0000359#if 0
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000360 /* It was said that "normal" telnetd just closes ptyfd,
361 * doesn't send SIGKILL. When we close ptyfd,
362 * kernel sends SIGHUP to processes having slave side opened. */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000363 kill(ts->shell_pid, SIGKILL);
Denis Vlasenkod814c982009-02-02 23:43:57 +0000364 waitpid(ts->shell_pid, NULL, 0);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000365#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000366 close(ts->ptyfd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000367 close(ts->sockfd_read);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000368 /* We do not need to close(ts->sockfd_write), it's the same
369 * as sockfd_read unless we are in inetd mode. But in inetd mode
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000370 * we do not reach this */
Eric Andersen08a72202002-09-30 20:52:10 +0000371 free(ts);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000372
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000373 /* Scan all sessions and find new maxfd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000374 maxfd = 0;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000375 ts = sessions;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000376 while (ts) {
377 if (maxfd < ts->ptyfd)
378 maxfd = ts->ptyfd;
379 if (maxfd < ts->sockfd_read)
380 maxfd = ts->sockfd_read;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000381#if 0
382 /* Again, sockfd_write == sockfd_read here */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000383 if (maxfd < ts->sockfd_write)
384 maxfd = ts->sockfd_write;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000385#endif
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000386 ts = ts->next;
387 }
Eric Andersen08a72202002-09-30 20:52:10 +0000388}
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000389
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000390#else /* !FEATURE_TELNETD_STANDALONE */
391
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000392/* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000393#define free_session(ts) return 0
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000394
395#endif
396
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000397static void handle_sigchld(int sig UNUSED_PARAM)
Denis Vlasenko2450c452007-10-15 22:09:15 +0000398{
399 pid_t pid;
400 struct tsession *ts;
401
Denis Vlasenko018b1552007-11-06 01:38:46 +0000402 /* Looping: more than one child may have exited */
403 while (1) {
Denis Vlasenkofb0eba72008-01-02 19:55:04 +0000404 pid = wait_any_nohang(NULL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000405 if (pid <= 0)
406 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000407 ts = sessions;
408 while (ts) {
409 if (ts->shell_pid == pid) {
410 ts->shell_pid = -1;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000411 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000412 }
413 ts = ts->next;
414 }
415 }
416}
417
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000418int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000419int telnetd_main(int argc UNUSED_PARAM, char **argv)
Eric Andersen08a72202002-09-30 20:52:10 +0000420{
Eric Andersen08a72202002-09-30 20:52:10 +0000421 fd_set rdfdset, wrfdset;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000422 unsigned opt;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000423 int count;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000424 struct tsession *ts;
425#if ENABLE_FEATURE_TELNETD_STANDALONE
426#define IS_INETD (opt & OPT_INETD)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000427 int master_fd = master_fd; /* be happy, gcc */
Denis Vlasenko13858992006-10-08 12:49:22 +0000428 unsigned portnbr = 23;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000429 char *opt_bindaddr = NULL;
430 char *opt_portnbr;
431#else
432 enum {
433 IS_INETD = 1,
434 master_fd = -1,
435 portnbr = 23,
436 };
Glenn L McGrathc2b91862003-09-12 11:27:15 +0000437#endif
Denis Vlasenko2450c452007-10-15 22:09:15 +0000438 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000439 * don't need to guess whether it's ok to pass -i to us */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000440 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
Denis Vlasenko0e87d342006-09-22 08:50:29 +0000441 &issuefile, &loginpath
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000442 USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
Denis Vlasenko2450c452007-10-15 22:09:15 +0000443 if (!IS_INETD /*&& !re_execed*/) {
444 /* inform that we start in standalone mode?
445 * May be useful when people forget to give -i */
446 /*bb_error_msg("listening for connections");*/
447 if (!(opt & OPT_FOREGROUND)) {
448 /* DAEMON_CHDIR_ROOT was giving inconsistent
Denis Vlasenko008eda22007-10-16 10:47:27 +0000449 * behavior with/without -F, -i */
Denis Vlasenko018b1552007-11-06 01:38:46 +0000450 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000451 }
452 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000453 /* Redirect log to syslog early, if needed */
454 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
455 openlog(applet_name, 0, LOG_USER);
456 logmode = LOGMODE_SYSLOG;
457 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000458 USE_FEATURE_TELNETD_STANDALONE(
Denis Vlasenko2450c452007-10-15 22:09:15 +0000459 if (opt & OPT_PORT)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000460 portnbr = xatou16(opt_portnbr);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000461 );
Eric Andersen08a72202002-09-30 20:52:10 +0000462
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000463 /* Used to check access(loginpath, X_OK) here. Pointless.
464 * exec will do this for us for free later. */
Glenn L McGrath9e5d6c02003-01-21 20:55:56 +0000465
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000466#if ENABLE_FEATURE_TELNETD_STANDALONE
467 if (IS_INETD) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000468 sessions = make_new_session(0);
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 */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000471 } else {
Denis Vlasenko9de420c2007-01-10 09:28:01 +0000472 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
Denis Vlasenkoc8717cd2006-11-22 16:10:39 +0000473 xlisten(master_fd, 1);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000474 }
Rob Landley00e76cb2005-05-10 23:53:33 +0000475#else
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000476 sessions = make_new_session();
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000477 if (!sessions) /* pty opening or vfork problem, exit */
478 return 1; /* make_new_session prints error message */
Rob Landley00e76cb2005-05-10 23:53:33 +0000479#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000480
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000481 /* We don't want to die if just one session is broken */
482 signal(SIGPIPE, SIG_IGN);
Eric Andersen08a72202002-09-30 20:52:10 +0000483
Denis Vlasenko2450c452007-10-15 22:09:15 +0000484 if (opt & OPT_WATCHCHILD)
485 signal(SIGCHLD, handle_sigchld);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000486 else /* prevent dead children from becoming zombies */
487 signal(SIGCHLD, SIG_IGN);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000488
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000489/*
490 This is how the buffers are used. The arrows indicate the movement
491 of data.
492 +-------+ wridx1++ +------+ rdidx1++ +----------+
493 | | <-------------- | buf1 | <-------------- | |
494 | | size1-- +------+ size1++ | |
495 | pty | | socket |
496 | | rdidx2++ +------+ wridx2++ | |
497 | | --------------> | buf2 | --------------> | |
498 +-------+ size2++ +------+ size2-- +----------+
499
500 size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
501 size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
502
503 Each session has got two buffers. Buffers are circular. If sizeN == 0,
504 buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
505 rdidxN == wridxN.
506*/
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000507 again:
508 FD_ZERO(&rdfdset);
509 FD_ZERO(&wrfdset);
Eric Andersen08a72202002-09-30 20:52:10 +0000510
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000511 /* Select on the master socket, all telnet sockets and their
512 * ptys if there is room in their session buffers.
513 * NB: scalability problem: we recalculate entire bitmap
514 * before each select. Can be a problem with 500+ connections. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000515 ts = sessions;
516 while (ts) {
Denis Vlasenko2450c452007-10-15 22:09:15 +0000517 struct tsession *next = ts->next; /* in case we free ts. */
518 if (ts->shell_pid == -1) {
Denis Vlasenko018b1552007-11-06 01:38:46 +0000519 /* Child died and we detected that */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000520 free_session(ts);
521 } else {
522 if (ts->size1 > 0) /* can write to pty */
523 FD_SET(ts->ptyfd, &wrfdset);
524 if (ts->size1 < BUFSIZE) /* can read from socket */
525 FD_SET(ts->sockfd_read, &rdfdset);
526 if (ts->size2 > 0) /* can write to socket */
527 FD_SET(ts->sockfd_write, &wrfdset);
528 if (ts->size2 < BUFSIZE) /* can read from pty */
529 FD_SET(ts->ptyfd, &rdfdset);
530 }
531 ts = next;
532 }
533 if (!IS_INETD) {
534 FD_SET(master_fd, &rdfdset);
535 /* This is needed because free_session() does not
Denis Vlasenko018b1552007-11-06 01:38:46 +0000536 * take master_fd into account when it finds new
Denis Vlasenko2450c452007-10-15 22:09:15 +0000537 * maxfd among remaining fd's */
538 if (master_fd > maxfd)
539 maxfd = master_fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000540 }
541
Denis Vlasenko10916c52007-10-15 17:28:00 +0000542 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
543 if (count < 0)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000544 goto again; /* EINTR or ENOMEM */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000545
546#if ENABLE_FEATURE_TELNETD_STANDALONE
547 /* First check for and accept new sessions. */
548 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000549 int fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000550 struct tsession *new_ts;
551
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000552 fd = accept(master_fd, NULL, NULL);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000553 if (fd < 0)
554 goto again;
555 /* Create a new session and link it into our active list */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000556 new_ts = make_new_session(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000557 if (new_ts) {
558 new_ts->next = sessions;
559 sessions = new_ts;
560 } else {
561 close(fd);
Eric Andersen08a72202002-09-30 20:52:10 +0000562 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000563 }
564#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000565
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000566 /* Then check for data tunneling. */
567 ts = sessions;
568 while (ts) { /* For all sessions... */
569 struct tsession *next = ts->next; /* in case we free ts. */
Eric Andersen08a72202002-09-30 20:52:10 +0000570
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000571 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000572 int num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000573 unsigned char *ptr;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000574 /* Write to pty from buffer 1. */
575 ptr = remove_iacs(ts, &num_totty);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000576 count = safe_write(ts->ptyfd, ptr, num_totty);
577 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000578 if (errno == EAGAIN)
579 goto skip1;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000580 goto kill_session;
Eric Andersen08a72202002-09-30 20:52:10 +0000581 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000582 ts->size1 -= count;
583 ts->wridx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000584 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
Eric Andersen08a72202002-09-30 20:52:10 +0000585 ts->wridx1 = 0;
Eric Andersen08a72202002-09-30 20:52:10 +0000586 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000587 skip1:
588 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000589 /* Write to socket from buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000590 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000591 count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2 + ts->wridx2), count);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000592 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000593 if (errno == EAGAIN)
594 goto skip2;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000595 goto kill_session;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000596 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000597 ts->size2 -= count;
598 ts->wridx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000599 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000600 ts->wridx2 = 0;
601 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000602 skip2:
Denis Vlasenko10916c52007-10-15 17:28:00 +0000603 /* Should not be needed, but... remove_iacs is actually buggy
604 * (it cannot process iacs which wrap around buffer's end)!
605 * Since properly fixing it requires writing bigger code,
Denis Vlasenkof472b232007-10-16 21:35:17 +0000606 * we rely instead on this code making it virtually impossible
Denis Vlasenko10916c52007-10-15 17:28:00 +0000607 * to have wrapped iac (people don't type at 2k/second).
608 * It also allows for bigger reads in common case. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000609 if (ts->size1 == 0) {
610 ts->rdidx1 = 0;
611 ts->wridx1 = 0;
612 }
613 if (ts->size2 == 0) {
614 ts->rdidx2 = 0;
615 ts->wridx2 = 0;
616 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000617
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000618 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
619 /* Read from socket to buffer 1. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000620 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
621 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
622 if (count <= 0) {
623 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000624 goto skip3;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000625 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000626 }
627 /* Ignore trailing NUL if it is there */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000628 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000629 --count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000630 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000631 ts->size1 += count;
632 ts->rdidx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000633 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
634 ts->rdidx1 = 0;
635 }
636 skip3:
637 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
638 /* Read from pty to buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000639 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
640 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
641 if (count <= 0) {
642 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000643 goto skip4;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000644 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000645 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000646 ts->size2 += count;
647 ts->rdidx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000648 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
649 ts->rdidx2 = 0;
650 }
651 skip4:
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000652 ts = next;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000653 continue;
654 kill_session:
655 free_session(ts);
656 ts = next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000657 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000658
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000659 goto again;
Eric Andersen08a72202002-09-30 20:52:10 +0000660}