blob: 481c932db55f1723df158ea284e00bc256522fae [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 +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,
250 IAC, DO, TELOPT_LFLOW,
251 IAC, WILL, TELOPT_ECHO,
252 IAC, WILL, TELOPT_SGA
253 };
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000254 /* This confuses iac_safe_write(), it will try to duplicate
255 * each IAC... */
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);
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000259 /* So just stuff it into TCP stream! (no error check...) */
260#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000261 safe_write(sock, iacs_to_send, sizeof(iacs_to_send));
Denis Vlasenko81c6a912008-11-12 21:14:50 +0000262#else
263 safe_write(1, iacs_to_send, sizeof(iacs_to_send));
264#endif
265 /*ts->rdidx2 = 0; - xzalloc did it */
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000266 /*ts->size2 = 0;*/
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000267 }
Eric Andersen08a72202002-09-30 20:52:10 +0000268
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000269 fflush(NULL); /* flush all streams */
270 pid = vfork(); /* NOMMU-friendly */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000271 if (pid < 0) {
272 free(ts);
273 close(fd);
Denis Vlasenko23c81282007-10-16 22:01:23 +0000274 /* sock will be closed by caller */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000275 bb_perror_msg("vfork");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000276 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000277 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000278 if (pid > 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000279 /* Parent */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000280 ts->shell_pid = pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000281 return ts;
Eric Andersen08a72202002-09-30 20:52:10 +0000282 }
Denis Vlasenkof7996f32007-01-11 17:20:00 +0000283
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000284 /* Child */
285 /* Careful - we are after vfork! */
Eric Andersen08a72202002-09-30 20:52:10 +0000286
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000287 /* make new session and process group */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000288 setsid();
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000289
Denis Vlasenko018b1552007-11-06 01:38:46 +0000290 /* Restore default signal handling */
Denis Vlasenko25591c32008-02-16 22:58:56 +0000291 bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000292
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000293 /* open the child's side of the tty. */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000294 /* NB: setsid() disconnects from any previous ctty's. Therefore
295 * we must open child's side of the tty AFTER setsid! */
Denis Vlasenko1101d1c2008-07-21 09:22:28 +0000296 close(0);
297 xopen(tty_name, O_RDWR); /* becomes our ctty */
298 xdup2(0, 1);
299 xdup2(0, 2);
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000300 tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
Eric Andersen08a72202002-09-30 20:52:10 +0000301
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000302 /* The pseudo-terminal allocated to the client is configured to operate in
303 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
304 tcgetattr(0, &termbuf);
305 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000306 termbuf.c_oflag |= ONLCR | XTABS;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000307 termbuf.c_iflag |= ICRNL;
308 termbuf.c_iflag &= ~IXOFF;
309 /*termbuf.c_lflag &= ~ICANON;*/
Denis Vlasenko202ac502008-11-05 13:20:58 +0000310 tcsetattr_stdin_TCSANOW(&termbuf);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000311
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000312 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
313 * so should be safe with vfork.
314 * I fear, though, that some users will have ridiculously big
Denis Vlasenko018b1552007-11-06 01:38:46 +0000315 * issue files, and they may block writing to fd 1,
316 * (parent is supposed to read it, but parent waits
317 * for vforked child to exec!) */
Denis Vlasenko1101d1c2008-07-21 09:22:28 +0000318 print_login_issue(issuefile, tty_name);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000319
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000320 /* Exec shell / login / whatever */
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000321 login_argv[0] = loginpath;
322 login_argv[1] = NULL;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000323 /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
324 * exec external program */
325 BB_EXECVP(loginpath, (char **)login_argv);
326 /* _exit is safer with vfork, and we shouldn't send message
Denis Vlasenko2450c452007-10-15 22:09:15 +0000327 * to remote clients anyway */
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000328 _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/
Eric Andersen08a72202002-09-30 20:52:10 +0000329}
330
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000331/* Must match getopt32 string */
332enum {
333 OPT_WATCHCHILD = (1 << 2), /* -K */
334 OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
335 OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
336 OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
337};
338
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000339#if ENABLE_FEATURE_TELNETD_STANDALONE
340
Eric Andersen08a72202002-09-30 20:52:10 +0000341static void
342free_session(struct tsession *ts)
343{
344 struct tsession *t = sessions;
345
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000346 if (option_mask32 & OPT_INETD)
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000347 exit(EXIT_SUCCESS);
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000348
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000349 /* Unlink this telnet session from the session list */
Mike Frysinger731f81c2006-05-10 15:23:12 +0000350 if (t == ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000351 sessions = ts->next;
352 else {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000353 while (t->next != ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000354 t = t->next;
355 t->next = ts->next;
356 }
357
Denis Vlasenkof472b232007-10-16 21:35:17 +0000358#if 0
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000359 /* It was said that "normal" telnetd just closes ptyfd,
360 * doesn't send SIGKILL. When we close ptyfd,
361 * kernel sends SIGHUP to processes having slave side opened. */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000362 kill(ts->shell_pid, SIGKILL);
363 wait4(ts->shell_pid, NULL, 0, NULL);
364#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000365 close(ts->ptyfd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000366 close(ts->sockfd_read);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000367 /* We do not need to close(ts->sockfd_write), it's the same
368 * as sockfd_read unless we are in inetd mode. But in inetd mode
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000369 * we do not reach this */
Eric Andersen08a72202002-09-30 20:52:10 +0000370 free(ts);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000371
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000372 /* Scan all sessions and find new maxfd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000373 maxfd = 0;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000374 ts = sessions;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000375 while (ts) {
376 if (maxfd < ts->ptyfd)
377 maxfd = ts->ptyfd;
378 if (maxfd < ts->sockfd_read)
379 maxfd = ts->sockfd_read;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000380#if 0
381 /* Again, sockfd_write == sockfd_read here */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000382 if (maxfd < ts->sockfd_write)
383 maxfd = ts->sockfd_write;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000384#endif
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000385 ts = ts->next;
386 }
Eric Andersen08a72202002-09-30 20:52:10 +0000387}
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000388
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000389#else /* !FEATURE_TELNETD_STANDALONE */
390
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000391/* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000392#define free_session(ts) return 0
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000393
394#endif
395
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000396static void handle_sigchld(int sig UNUSED_PARAM)
Denis Vlasenko2450c452007-10-15 22:09:15 +0000397{
398 pid_t pid;
399 struct tsession *ts;
400
Denis Vlasenko018b1552007-11-06 01:38:46 +0000401 /* Looping: more than one child may have exited */
402 while (1) {
Denis Vlasenkofb0eba72008-01-02 19:55:04 +0000403 pid = wait_any_nohang(NULL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000404 if (pid <= 0)
405 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000406 ts = sessions;
407 while (ts) {
408 if (ts->shell_pid == pid) {
409 ts->shell_pid = -1;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000410 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000411 }
412 ts = ts->next;
413 }
414 }
415}
416
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000417int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000418int telnetd_main(int argc UNUSED_PARAM, char **argv)
Eric Andersen08a72202002-09-30 20:52:10 +0000419{
Eric Andersen08a72202002-09-30 20:52:10 +0000420 fd_set rdfdset, wrfdset;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000421 unsigned opt;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000422 int count;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000423 struct tsession *ts;
424#if ENABLE_FEATURE_TELNETD_STANDALONE
425#define IS_INETD (opt & OPT_INETD)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000426 int master_fd = master_fd; /* be happy, gcc */
Denis Vlasenko13858992006-10-08 12:49:22 +0000427 unsigned portnbr = 23;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000428 char *opt_bindaddr = NULL;
429 char *opt_portnbr;
430#else
431 enum {
432 IS_INETD = 1,
433 master_fd = -1,
434 portnbr = 23,
435 };
Glenn L McGrathc2b91862003-09-12 11:27:15 +0000436#endif
Denis Vlasenko2450c452007-10-15 22:09:15 +0000437 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000438 * don't need to guess whether it's ok to pass -i to us */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000439 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
Denis Vlasenko0e87d342006-09-22 08:50:29 +0000440 &issuefile, &loginpath
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000441 USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
Denis Vlasenko2450c452007-10-15 22:09:15 +0000442 if (!IS_INETD /*&& !re_execed*/) {
443 /* inform that we start in standalone mode?
444 * May be useful when people forget to give -i */
445 /*bb_error_msg("listening for connections");*/
446 if (!(opt & OPT_FOREGROUND)) {
447 /* DAEMON_CHDIR_ROOT was giving inconsistent
Denis Vlasenko008eda22007-10-16 10:47:27 +0000448 * behavior with/without -F, -i */
Denis Vlasenko018b1552007-11-06 01:38:46 +0000449 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000450 }
451 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000452 /* Redirect log to syslog early, if needed */
453 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
454 openlog(applet_name, 0, LOG_USER);
455 logmode = LOGMODE_SYSLOG;
456 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000457 USE_FEATURE_TELNETD_STANDALONE(
Denis Vlasenko2450c452007-10-15 22:09:15 +0000458 if (opt & OPT_PORT)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000459 portnbr = xatou16(opt_portnbr);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000460 );
Eric Andersen08a72202002-09-30 20:52:10 +0000461
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000462 /* Used to check access(loginpath, X_OK) here. Pointless.
463 * exec will do this for us for free later. */
Glenn L McGrath9e5d6c02003-01-21 20:55:56 +0000464
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000465#if ENABLE_FEATURE_TELNETD_STANDALONE
466 if (IS_INETD) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000467 sessions = make_new_session(0);
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000468 if (!sessions) /* pty opening or vfork problem, exit */
469 return 1; /* make_new_session prints error message */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000470 } else {
Denis Vlasenko9de420c2007-01-10 09:28:01 +0000471 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
Denis Vlasenkoc8717cd2006-11-22 16:10:39 +0000472 xlisten(master_fd, 1);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000473 }
Rob Landley00e76cb2005-05-10 23:53:33 +0000474#else
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000475 sessions = make_new_session();
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000476 if (!sessions) /* pty opening or vfork problem, exit */
477 return 1; /* make_new_session prints error message */
Rob Landley00e76cb2005-05-10 23:53:33 +0000478#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000479
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000480 /* We don't want to die if just one session is broken */
481 signal(SIGPIPE, SIG_IGN);
Eric Andersen08a72202002-09-30 20:52:10 +0000482
Denis Vlasenko2450c452007-10-15 22:09:15 +0000483 if (opt & OPT_WATCHCHILD)
484 signal(SIGCHLD, handle_sigchld);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000485 else /* prevent dead children from becoming zombies */
486 signal(SIGCHLD, SIG_IGN);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000487
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000488/*
489 This is how the buffers are used. The arrows indicate the movement
490 of data.
491 +-------+ wridx1++ +------+ rdidx1++ +----------+
492 | | <-------------- | buf1 | <-------------- | |
493 | | size1-- +------+ size1++ | |
494 | pty | | socket |
495 | | rdidx2++ +------+ wridx2++ | |
496 | | --------------> | buf2 | --------------> | |
497 +-------+ size2++ +------+ size2-- +----------+
498
499 size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
500 size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
501
502 Each session has got two buffers. Buffers are circular. If sizeN == 0,
503 buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
504 rdidxN == wridxN.
505*/
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000506 again:
507 FD_ZERO(&rdfdset);
508 FD_ZERO(&wrfdset);
Eric Andersen08a72202002-09-30 20:52:10 +0000509
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000510 /* Select on the master socket, all telnet sockets and their
511 * ptys if there is room in their session buffers.
512 * NB: scalability problem: we recalculate entire bitmap
513 * before each select. Can be a problem with 500+ connections. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000514 ts = sessions;
515 while (ts) {
Denis Vlasenko2450c452007-10-15 22:09:15 +0000516 struct tsession *next = ts->next; /* in case we free ts. */
517 if (ts->shell_pid == -1) {
Denis Vlasenko018b1552007-11-06 01:38:46 +0000518 /* Child died and we detected that */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000519 free_session(ts);
520 } else {
521 if (ts->size1 > 0) /* can write to pty */
522 FD_SET(ts->ptyfd, &wrfdset);
523 if (ts->size1 < BUFSIZE) /* can read from socket */
524 FD_SET(ts->sockfd_read, &rdfdset);
525 if (ts->size2 > 0) /* can write to socket */
526 FD_SET(ts->sockfd_write, &wrfdset);
527 if (ts->size2 < BUFSIZE) /* can read from pty */
528 FD_SET(ts->ptyfd, &rdfdset);
529 }
530 ts = next;
531 }
532 if (!IS_INETD) {
533 FD_SET(master_fd, &rdfdset);
534 /* This is needed because free_session() does not
Denis Vlasenko018b1552007-11-06 01:38:46 +0000535 * take master_fd into account when it finds new
Denis Vlasenko2450c452007-10-15 22:09:15 +0000536 * maxfd among remaining fd's */
537 if (master_fd > maxfd)
538 maxfd = master_fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000539 }
540
Denis Vlasenko10916c52007-10-15 17:28:00 +0000541 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
542 if (count < 0)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000543 goto again; /* EINTR or ENOMEM */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000544
545#if ENABLE_FEATURE_TELNETD_STANDALONE
546 /* First check for and accept new sessions. */
547 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000548 int fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000549 struct tsession *new_ts;
550
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000551 fd = accept(master_fd, NULL, NULL);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000552 if (fd < 0)
553 goto again;
554 /* Create a new session and link it into our active list */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000555 new_ts = make_new_session(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000556 if (new_ts) {
557 new_ts->next = sessions;
558 sessions = new_ts;
559 } else {
560 close(fd);
Eric Andersen08a72202002-09-30 20:52:10 +0000561 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000562 }
563#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000564
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000565 /* Then check for data tunneling. */
566 ts = sessions;
567 while (ts) { /* For all sessions... */
568 struct tsession *next = ts->next; /* in case we free ts. */
Eric Andersen08a72202002-09-30 20:52:10 +0000569
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000570 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000571 int num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000572 unsigned char *ptr;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000573 /* Write to pty from buffer 1. */
574 ptr = remove_iacs(ts, &num_totty);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000575 count = safe_write(ts->ptyfd, ptr, num_totty);
576 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000577 if (errno == EAGAIN)
578 goto skip1;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000579 goto kill_session;
Eric Andersen08a72202002-09-30 20:52:10 +0000580 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000581 ts->size1 -= count;
582 ts->wridx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000583 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
Eric Andersen08a72202002-09-30 20:52:10 +0000584 ts->wridx1 = 0;
Eric Andersen08a72202002-09-30 20:52:10 +0000585 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000586 skip1:
587 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000588 /* Write to socket from buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000589 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
Denis Vlasenko9f2f8082008-11-11 02:56:39 +0000590 count = iac_safe_write(ts->sockfd_write, (void*)(TS_BUF2 + ts->wridx2), count);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000591 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000592 if (errno == EAGAIN)
593 goto skip2;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000594 goto kill_session;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000595 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000596 ts->size2 -= count;
597 ts->wridx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000598 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000599 ts->wridx2 = 0;
600 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000601 skip2:
Denis Vlasenko10916c52007-10-15 17:28:00 +0000602 /* Should not be needed, but... remove_iacs is actually buggy
603 * (it cannot process iacs which wrap around buffer's end)!
604 * Since properly fixing it requires writing bigger code,
Denis Vlasenkof472b232007-10-16 21:35:17 +0000605 * we rely instead on this code making it virtually impossible
Denis Vlasenko10916c52007-10-15 17:28:00 +0000606 * to have wrapped iac (people don't type at 2k/second).
607 * It also allows for bigger reads in common case. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000608 if (ts->size1 == 0) {
609 ts->rdidx1 = 0;
610 ts->wridx1 = 0;
611 }
612 if (ts->size2 == 0) {
613 ts->rdidx2 = 0;
614 ts->wridx2 = 0;
615 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000616
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000617 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
618 /* Read from socket to buffer 1. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000619 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
620 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
621 if (count <= 0) {
622 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000623 goto skip3;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000624 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000625 }
626 /* Ignore trailing NUL if it is there */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000627 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000628 --count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000629 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000630 ts->size1 += count;
631 ts->rdidx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000632 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
633 ts->rdidx1 = 0;
634 }
635 skip3:
636 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
637 /* Read from pty to buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000638 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
639 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
640 if (count <= 0) {
641 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000642 goto skip4;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000643 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000644 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000645 ts->size2 += count;
646 ts->rdidx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000647 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
648 ts->rdidx2 = 0;
649 }
650 skip4:
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000651 ts = next;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000652 continue;
653 kill_session:
654 free_session(ts);
655 ts = next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000656 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000657
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000658 goto again;
Eric Andersen08a72202002-09-30 20:52:10 +0000659}