blob: 7f748641b7aec671415694aa35fb4d7a43968ec7 [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
77 FIXME - if we mean to send 0xFF to the terminal then it will be escaped,
78 what is the escape character? We aren't handling that situation here.
79
Denis Vlasenko10916c52007-10-15 17:28:00 +000080 CR-LF ->'s CR mapping is also done here, for convenience.
81
82 NB: may fail to remove iacs which wrap around buffer!
Denis Vlasenko75f8d082006-11-22 15:54:52 +000083 */
Denis Vlasenko10916c52007-10-15 17:28:00 +000084static unsigned char *
Denis Vlasenko75f8d082006-11-22 15:54:52 +000085remove_iacs(struct tsession *ts, int *pnum_totty)
86{
Denis Vlasenko10916c52007-10-15 17:28:00 +000087 unsigned char *ptr0 = TS_BUF1 + ts->wridx1;
Eric Andersen08a72202002-09-30 20:52:10 +000088 unsigned char *ptr = ptr0;
89 unsigned char *totty = ptr;
90 unsigned char *end = ptr + MIN(BUFSIZE - ts->wridx1, ts->size1);
Eric Andersen08a72202002-09-30 20:52:10 +000091 int num_totty;
92
93 while (ptr < end) {
94 if (*ptr != IAC) {
Denis Vlasenko10916c52007-10-15 17:28:00 +000095 char c = *ptr;
96
97 *totty++ = c;
98 ptr++;
Eric Andersen3752d332003-12-19 11:30:13 +000099 /* We now map \r\n ==> \r for pragmatic reasons.
100 * Many client implementations send \r\n when
101 * the user hits the CarriageReturn key.
102 */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000103 if (c == '\r' && ptr < end && (*ptr == '\n' || *ptr == '\0'))
Eric Andersen3752d332003-12-19 11:30:13 +0000104 ptr++;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000105 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000106 /*
107 * TELOPT_NAWS support!
108 */
109 if ((ptr+2) >= end) {
Eric Andersen08a72202002-09-30 20:52:10 +0000110 /* only the beginning of the IAC is in the
111 buffer we were asked to process, we can't
112 process this char. */
113 break;
114 }
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000115
116 /*
117 * IAC -> SB -> TELOPT_NAWS -> 4-byte -> IAC -> SE
118 */
119 else if (ptr[1] == SB && ptr[2] == TELOPT_NAWS) {
120 struct winsize ws;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000121
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000122 if ((ptr+8) >= end)
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000123 break; /* incomplete, can't process */
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000124 ws.ws_col = (ptr[3] << 8) | ptr[4];
125 ws.ws_row = (ptr[5] << 8) | ptr[6];
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000126 ioctl(ts->ptyfd, TIOCSWINSZ, (char *)&ws);
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000127 ptr += 9;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000128 } else {
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000129 /* skip 3-byte IAC non-SB cmd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000130#if DEBUG
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000131 fprintf(stderr, "Ignoring IAC %s,%s\n",
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000132 TELCMD(ptr[1]), TELOPT(ptr[2]));
Glenn L McGrath90ed9a02004-02-22 09:45:57 +0000133#endif
134 ptr += 3;
135 }
Eric Andersen08a72202002-09-30 20:52:10 +0000136 }
137 }
138
Denis Vlasenko0de37e12007-10-17 11:08:53 +0000139 num_totty = totty - ptr0;
Eric Andersen08a72202002-09-30 20:52:10 +0000140 *pnum_totty = num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000141 /* the difference between ptr and totty is number of iacs
142 we removed from the stream. Adjust buf1 accordingly. */
143 if ((ptr - totty) == 0) /* 99.999% of cases */
144 return ptr0;
145 ts->wridx1 += ptr - totty;
146 ts->size1 -= ptr - totty;
147 /* move chars meant for the terminal towards the end of the buffer */
Eric Andersen08a72202002-09-30 20:52:10 +0000148 return memmove(ptr - num_totty, ptr0, num_totty);
149}
150
151
Eric Andersen08a72202002-09-30 20:52:10 +0000152static struct tsession *
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000153make_new_session(
Denis Vlasenkof472b232007-10-16 21:35:17 +0000154 USE_FEATURE_TELNETD_STANDALONE(int sock)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000155 SKIP_FEATURE_TELNETD_STANDALONE(void)
156) {
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000157 const char *login_argv[2];
Eric Andersen08a72202002-09-30 20:52:10 +0000158 struct termios termbuf;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000159 int fd, pid;
Denis Vlasenko85c24712008-03-17 09:04:04 +0000160 char tty_name[GETPTY_BUFSIZE];
Rob Landley1ec5b292006-05-29 07:42:02 +0000161 struct tsession *ts = xzalloc(sizeof(struct tsession) + BUFSIZE * 2);
Eric Andersen08a72202002-09-30 20:52:10 +0000162
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000163 /*ts->buf1 = (char *)(ts + 1);*/
164 /*ts->buf2 = ts->buf1 + BUFSIZE;*/
Eric Andersen08a72202002-09-30 20:52:10 +0000165
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000166 /* Got a new connection, set up a tty. */
Bernhard Reutner-Fischerae4342c2008-05-19 08:18:50 +0000167 fd = xgetpty(tty_name);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000168 if (fd > maxfd)
169 maxfd = fd;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000170 ts->ptyfd = fd;
171 ndelay_on(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000172#if ENABLE_FEATURE_TELNETD_STANDALONE
Denis Vlasenkof472b232007-10-16 21:35:17 +0000173 ts->sockfd_read = sock;
174 ndelay_on(sock);
Denis Vlasenko9e237672007-10-17 11:18:49 +0000175 if (!sock) { /* We are called with fd 0 - we are in inetd mode */
176 sock++; /* so use fd 1 for output */
177 ndelay_on(sock);
178 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000179 ts->sockfd_write = sock;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000180 if (sock > maxfd)
181 maxfd = sock;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000182#else
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000183 /* ts->sockfd_read = 0; - done by xzalloc */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000184 ts->sockfd_write = 1;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000185 ndelay_on(0);
186 ndelay_on(1);
187#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000188 /* Make the telnet client understand we will echo characters so it
189 * should not do it locally. We don't tell the client to run linemode,
190 * because we want to handle line editing and tab completion and other
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000191 * stuff that requires char-by-char support. */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000192 {
193 static const char iacs_to_send[] ALIGN1 = {
194 IAC, DO, TELOPT_ECHO,
195 IAC, DO, TELOPT_NAWS,
196 IAC, DO, TELOPT_LFLOW,
197 IAC, WILL, TELOPT_ECHO,
198 IAC, WILL, TELOPT_SGA
199 };
200 memcpy(TS_BUF2, iacs_to_send, sizeof(iacs_to_send));
201 ts->rdidx2 = sizeof(iacs_to_send);
202 ts->size2 = sizeof(iacs_to_send);
203 }
Eric Andersen08a72202002-09-30 20:52:10 +0000204
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000205 fflush(NULL); /* flush all streams */
206 pid = vfork(); /* NOMMU-friendly */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000207 if (pid < 0) {
208 free(ts);
209 close(fd);
Denis Vlasenko23c81282007-10-16 22:01:23 +0000210 /* sock will be closed by caller */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000211 bb_perror_msg("vfork");
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000212 return NULL;
Eric Andersen08a72202002-09-30 20:52:10 +0000213 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000214 if (pid > 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000215 /* Parent */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000216 ts->shell_pid = pid;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000217 return ts;
Eric Andersen08a72202002-09-30 20:52:10 +0000218 }
Denis Vlasenkof7996f32007-01-11 17:20:00 +0000219
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000220 /* Child */
221 /* Careful - we are after vfork! */
Eric Andersen08a72202002-09-30 20:52:10 +0000222
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000223 /* make new session and process group */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000224 setsid();
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000225
Denis Vlasenko018b1552007-11-06 01:38:46 +0000226 /* Restore default signal handling */
Denis Vlasenko25591c32008-02-16 22:58:56 +0000227 bb_signals((1 << SIGCHLD) + (1 << SIGPIPE), SIG_DFL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000228
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000229 /* open the child's side of the tty. */
Denis Vlasenko9af7c9d2007-01-19 21:19:35 +0000230 /* NB: setsid() disconnects from any previous ctty's. Therefore
231 * we must open child's side of the tty AFTER setsid! */
Denis Vlasenko1101d1c2008-07-21 09:22:28 +0000232 close(0);
233 xopen(tty_name, O_RDWR); /* becomes our ctty */
234 xdup2(0, 1);
235 xdup2(0, 2);
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000236 tcsetpgrp(0, getpid()); /* switch this tty's process group to us */
Eric Andersen08a72202002-09-30 20:52:10 +0000237
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000238 /* The pseudo-terminal allocated to the client is configured to operate in
239 * cooked mode, and with XTABS CRMOD enabled (see tty(4)). */
240 tcgetattr(0, &termbuf);
241 termbuf.c_lflag |= ECHO; /* if we use readline we dont want this */
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000242 termbuf.c_oflag |= ONLCR | XTABS;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000243 termbuf.c_iflag |= ICRNL;
244 termbuf.c_iflag &= ~IXOFF;
245 /*termbuf.c_lflag &= ~ICANON;*/
246 tcsetattr(0, TCSANOW, &termbuf);
247
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000248 /* Uses FILE-based I/O to stdout, but does fflush(stdout),
249 * so should be safe with vfork.
250 * I fear, though, that some users will have ridiculously big
Denis Vlasenko018b1552007-11-06 01:38:46 +0000251 * issue files, and they may block writing to fd 1,
252 * (parent is supposed to read it, but parent waits
253 * for vforked child to exec!) */
Denis Vlasenko1101d1c2008-07-21 09:22:28 +0000254 print_login_issue(issuefile, tty_name);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000255
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000256 /* Exec shell / login / whatever */
Denis Vlasenko88308fe2007-06-25 10:35:11 +0000257 login_argv[0] = loginpath;
258 login_argv[1] = NULL;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000259 /* exec busybox applet (if PREFER_APPLETS=y), if that fails,
260 * exec external program */
261 BB_EXECVP(loginpath, (char **)login_argv);
262 /* _exit is safer with vfork, and we shouldn't send message
Denis Vlasenko2450c452007-10-15 22:09:15 +0000263 * to remote clients anyway */
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000264 _exit(EXIT_FAILURE); /*bb_perror_msg_and_die("execv %s", loginpath);*/
Eric Andersen08a72202002-09-30 20:52:10 +0000265}
266
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000267/* Must match getopt32 string */
268enum {
269 OPT_WATCHCHILD = (1 << 2), /* -K */
270 OPT_INETD = (1 << 3) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -i */
271 OPT_PORT = (1 << 4) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -p */
272 OPT_FOREGROUND = (1 << 6) * ENABLE_FEATURE_TELNETD_STANDALONE, /* -F */
273};
274
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000275#if ENABLE_FEATURE_TELNETD_STANDALONE
276
Eric Andersen08a72202002-09-30 20:52:10 +0000277static void
278free_session(struct tsession *ts)
279{
280 struct tsession *t = sessions;
281
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000282 if (option_mask32 & OPT_INETD)
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000283 exit(EXIT_SUCCESS);
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000284
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000285 /* Unlink this telnet session from the session list */
Mike Frysinger731f81c2006-05-10 15:23:12 +0000286 if (t == ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000287 sessions = ts->next;
288 else {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000289 while (t->next != ts)
Eric Andersen08a72202002-09-30 20:52:10 +0000290 t = t->next;
291 t->next = ts->next;
292 }
293
Denis Vlasenkof472b232007-10-16 21:35:17 +0000294#if 0
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000295 /* It was said that "normal" telnetd just closes ptyfd,
296 * doesn't send SIGKILL. When we close ptyfd,
297 * kernel sends SIGHUP to processes having slave side opened. */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000298 kill(ts->shell_pid, SIGKILL);
299 wait4(ts->shell_pid, NULL, 0, NULL);
300#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000301 close(ts->ptyfd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000302 close(ts->sockfd_read);
Denis Vlasenkof472b232007-10-16 21:35:17 +0000303 /* We do not need to close(ts->sockfd_write), it's the same
304 * as sockfd_read unless we are in inetd mode. But in inetd mode
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000305 * we do not reach this */
Eric Andersen08a72202002-09-30 20:52:10 +0000306 free(ts);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000307
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000308 /* Scan all sessions and find new maxfd */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000309 maxfd = 0;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000310 ts = sessions;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000311 while (ts) {
312 if (maxfd < ts->ptyfd)
313 maxfd = ts->ptyfd;
314 if (maxfd < ts->sockfd_read)
315 maxfd = ts->sockfd_read;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000316#if 0
317 /* Again, sockfd_write == sockfd_read here */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000318 if (maxfd < ts->sockfd_write)
319 maxfd = ts->sockfd_write;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000320#endif
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000321 ts = ts->next;
322 }
Eric Andersen08a72202002-09-30 20:52:10 +0000323}
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000324
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000325#else /* !FEATURE_TELNETD_STANDALONE */
326
Bernhard Reutner-Fischer636a1f82008-05-19 09:29:47 +0000327/* Used in main() only, thus "return 0" actually is exit(EXIT_SUCCESS). */
Denis Vlasenkoe87b8682007-10-17 14:33:31 +0000328#define free_session(ts) return 0
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000329
330#endif
331
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000332static void handle_sigchld(int sig UNUSED_PARAM)
Denis Vlasenko2450c452007-10-15 22:09:15 +0000333{
334 pid_t pid;
335 struct tsession *ts;
336
Denis Vlasenko018b1552007-11-06 01:38:46 +0000337 /* Looping: more than one child may have exited */
338 while (1) {
Denis Vlasenkofb0eba72008-01-02 19:55:04 +0000339 pid = wait_any_nohang(NULL);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000340 if (pid <= 0)
341 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000342 ts = sessions;
343 while (ts) {
344 if (ts->shell_pid == pid) {
345 ts->shell_pid = -1;
Denis Vlasenko018b1552007-11-06 01:38:46 +0000346 break;
Denis Vlasenko2450c452007-10-15 22:09:15 +0000347 }
348 ts = ts->next;
349 }
350 }
351}
352
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000353int telnetd_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000354int telnetd_main(int argc UNUSED_PARAM, char **argv)
Eric Andersen08a72202002-09-30 20:52:10 +0000355{
Eric Andersen08a72202002-09-30 20:52:10 +0000356 fd_set rdfdset, wrfdset;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000357 unsigned opt;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000358 int count;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000359 struct tsession *ts;
360#if ENABLE_FEATURE_TELNETD_STANDALONE
361#define IS_INETD (opt & OPT_INETD)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000362 int master_fd = master_fd; /* be happy, gcc */
Denis Vlasenko13858992006-10-08 12:49:22 +0000363 unsigned portnbr = 23;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000364 char *opt_bindaddr = NULL;
365 char *opt_portnbr;
366#else
367 enum {
368 IS_INETD = 1,
369 master_fd = -1,
370 portnbr = 23,
371 };
Glenn L McGrathc2b91862003-09-12 11:27:15 +0000372#endif
Denis Vlasenko2450c452007-10-15 22:09:15 +0000373 /* Even if !STANDALONE, we accept (and ignore) -i, thus people
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000374 * don't need to guess whether it's ok to pass -i to us */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000375 opt = getopt32(argv, "f:l:Ki" USE_FEATURE_TELNETD_STANDALONE("p:b:F"),
Denis Vlasenko0e87d342006-09-22 08:50:29 +0000376 &issuefile, &loginpath
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000377 USE_FEATURE_TELNETD_STANDALONE(, &opt_portnbr, &opt_bindaddr));
Denis Vlasenko2450c452007-10-15 22:09:15 +0000378 if (!IS_INETD /*&& !re_execed*/) {
379 /* inform that we start in standalone mode?
380 * May be useful when people forget to give -i */
381 /*bb_error_msg("listening for connections");*/
382 if (!(opt & OPT_FOREGROUND)) {
383 /* DAEMON_CHDIR_ROOT was giving inconsistent
Denis Vlasenko008eda22007-10-16 10:47:27 +0000384 * behavior with/without -F, -i */
Denis Vlasenko018b1552007-11-06 01:38:46 +0000385 bb_daemonize_or_rexec(0 /*was DAEMON_CHDIR_ROOT*/, argv);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000386 }
387 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000388 /* Redirect log to syslog early, if needed */
389 if (IS_INETD || !(opt & OPT_FOREGROUND)) {
390 openlog(applet_name, 0, LOG_USER);
391 logmode = LOGMODE_SYSLOG;
392 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000393 USE_FEATURE_TELNETD_STANDALONE(
Denis Vlasenko2450c452007-10-15 22:09:15 +0000394 if (opt & OPT_PORT)
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000395 portnbr = xatou16(opt_portnbr);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000396 );
Eric Andersen08a72202002-09-30 20:52:10 +0000397
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000398 /* Used to check access(loginpath, X_OK) here. Pointless.
399 * exec will do this for us for free later. */
Glenn L McGrath9e5d6c02003-01-21 20:55:56 +0000400
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000401#if ENABLE_FEATURE_TELNETD_STANDALONE
402 if (IS_INETD) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000403 sessions = make_new_session(0);
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000404 if (!sessions) /* pty opening or vfork problem, exit */
405 return 1; /* make_new_session prints error message */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000406 } else {
Denis Vlasenko9de420c2007-01-10 09:28:01 +0000407 master_fd = create_and_bind_stream_or_die(opt_bindaddr, portnbr);
Denis Vlasenkoc8717cd2006-11-22 16:10:39 +0000408 xlisten(master_fd, 1);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000409 }
Rob Landley00e76cb2005-05-10 23:53:33 +0000410#else
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000411 sessions = make_new_session();
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000412 if (!sessions) /* pty opening or vfork problem, exit */
413 return 1; /* make_new_session prints error message */
Rob Landley00e76cb2005-05-10 23:53:33 +0000414#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000415
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000416 /* We don't want to die if just one session is broken */
417 signal(SIGPIPE, SIG_IGN);
Eric Andersen08a72202002-09-30 20:52:10 +0000418
Denis Vlasenko2450c452007-10-15 22:09:15 +0000419 if (opt & OPT_WATCHCHILD)
420 signal(SIGCHLD, handle_sigchld);
Denis Vlasenko018b1552007-11-06 01:38:46 +0000421 else /* prevent dead children from becoming zombies */
422 signal(SIGCHLD, SIG_IGN);
Denis Vlasenko2450c452007-10-15 22:09:15 +0000423
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000424/*
425 This is how the buffers are used. The arrows indicate the movement
426 of data.
427 +-------+ wridx1++ +------+ rdidx1++ +----------+
428 | | <-------------- | buf1 | <-------------- | |
429 | | size1-- +------+ size1++ | |
430 | pty | | socket |
431 | | rdidx2++ +------+ wridx2++ | |
432 | | --------------> | buf2 | --------------> | |
433 +-------+ size2++ +------+ size2-- +----------+
434
435 size1: "how many bytes are buffered for pty between rdidx1 and wridx1?"
436 size2: "how many bytes are buffered for socket between rdidx2 and wridx2?"
437
438 Each session has got two buffers. Buffers are circular. If sizeN == 0,
439 buffer is empty. If sizeN == BUFSIZE, buffer is full. In both these cases
440 rdidxN == wridxN.
441*/
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000442 again:
443 FD_ZERO(&rdfdset);
444 FD_ZERO(&wrfdset);
Eric Andersen08a72202002-09-30 20:52:10 +0000445
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000446 /* Select on the master socket, all telnet sockets and their
447 * ptys if there is room in their session buffers.
448 * NB: scalability problem: we recalculate entire bitmap
449 * before each select. Can be a problem with 500+ connections. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000450 ts = sessions;
451 while (ts) {
Denis Vlasenko2450c452007-10-15 22:09:15 +0000452 struct tsession *next = ts->next; /* in case we free ts. */
453 if (ts->shell_pid == -1) {
Denis Vlasenko018b1552007-11-06 01:38:46 +0000454 /* Child died and we detected that */
Denis Vlasenko2450c452007-10-15 22:09:15 +0000455 free_session(ts);
456 } else {
457 if (ts->size1 > 0) /* can write to pty */
458 FD_SET(ts->ptyfd, &wrfdset);
459 if (ts->size1 < BUFSIZE) /* can read from socket */
460 FD_SET(ts->sockfd_read, &rdfdset);
461 if (ts->size2 > 0) /* can write to socket */
462 FD_SET(ts->sockfd_write, &wrfdset);
463 if (ts->size2 < BUFSIZE) /* can read from pty */
464 FD_SET(ts->ptyfd, &rdfdset);
465 }
466 ts = next;
467 }
468 if (!IS_INETD) {
469 FD_SET(master_fd, &rdfdset);
470 /* This is needed because free_session() does not
Denis Vlasenko018b1552007-11-06 01:38:46 +0000471 * take master_fd into account when it finds new
Denis Vlasenko2450c452007-10-15 22:09:15 +0000472 * maxfd among remaining fd's */
473 if (master_fd > maxfd)
474 maxfd = master_fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000475 }
476
Denis Vlasenko10916c52007-10-15 17:28:00 +0000477 count = select(maxfd + 1, &rdfdset, &wrfdset, NULL, NULL);
478 if (count < 0)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000479 goto again; /* EINTR or ENOMEM */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000480
481#if ENABLE_FEATURE_TELNETD_STANDALONE
482 /* First check for and accept new sessions. */
483 if (!IS_INETD && FD_ISSET(master_fd, &rdfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000484 int fd;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000485 struct tsession *new_ts;
486
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000487 fd = accept(master_fd, NULL, NULL);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000488 if (fd < 0)
489 goto again;
490 /* Create a new session and link it into our active list */
Denis Vlasenkof472b232007-10-16 21:35:17 +0000491 new_ts = make_new_session(fd);
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000492 if (new_ts) {
493 new_ts->next = sessions;
494 sessions = new_ts;
495 } else {
496 close(fd);
Eric Andersen08a72202002-09-30 20:52:10 +0000497 }
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000498 }
499#endif
Eric Andersen08a72202002-09-30 20:52:10 +0000500
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000501 /* Then check for data tunneling. */
502 ts = sessions;
503 while (ts) { /* For all sessions... */
504 struct tsession *next = ts->next; /* in case we free ts. */
Eric Andersen08a72202002-09-30 20:52:10 +0000505
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000506 if (/*ts->size1 &&*/ FD_ISSET(ts->ptyfd, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000507 int num_totty;
Denis Vlasenko10916c52007-10-15 17:28:00 +0000508 unsigned char *ptr;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000509 /* Write to pty from buffer 1. */
510 ptr = remove_iacs(ts, &num_totty);
Denis Vlasenko10916c52007-10-15 17:28:00 +0000511 count = safe_write(ts->ptyfd, ptr, num_totty);
512 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000513 if (errno == EAGAIN)
514 goto skip1;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000515 goto kill_session;
Eric Andersen08a72202002-09-30 20:52:10 +0000516 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000517 ts->size1 -= count;
518 ts->wridx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000519 if (ts->wridx1 >= BUFSIZE) /* actually == BUFSIZE */
Eric Andersen08a72202002-09-30 20:52:10 +0000520 ts->wridx1 = 0;
Eric Andersen08a72202002-09-30 20:52:10 +0000521 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000522 skip1:
523 if (/*ts->size2 &&*/ FD_ISSET(ts->sockfd_write, &wrfdset)) {
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000524 /* Write to socket from buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000525 count = MIN(BUFSIZE - ts->wridx2, ts->size2);
526 count = safe_write(ts->sockfd_write, TS_BUF2 + ts->wridx2, count);
527 if (count < 0) {
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000528 if (errno == EAGAIN)
529 goto skip2;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000530 goto kill_session;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000531 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000532 ts->size2 -= count;
533 ts->wridx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000534 if (ts->wridx2 >= BUFSIZE) /* actually == BUFSIZE */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000535 ts->wridx2 = 0;
536 }
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000537 skip2:
Denis Vlasenko10916c52007-10-15 17:28:00 +0000538 /* Should not be needed, but... remove_iacs is actually buggy
539 * (it cannot process iacs which wrap around buffer's end)!
540 * Since properly fixing it requires writing bigger code,
Denis Vlasenkof472b232007-10-16 21:35:17 +0000541 * we rely instead on this code making it virtually impossible
Denis Vlasenko10916c52007-10-15 17:28:00 +0000542 * to have wrapped iac (people don't type at 2k/second).
543 * It also allows for bigger reads in common case. */
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000544 if (ts->size1 == 0) {
545 ts->rdidx1 = 0;
546 ts->wridx1 = 0;
547 }
548 if (ts->size2 == 0) {
549 ts->rdidx2 = 0;
550 ts->wridx2 = 0;
551 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000552
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000553 if (/*ts->size1 < BUFSIZE &&*/ FD_ISSET(ts->sockfd_read, &rdfdset)) {
554 /* Read from socket to buffer 1. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000555 count = MIN(BUFSIZE - ts->rdidx1, BUFSIZE - ts->size1);
556 count = safe_read(ts->sockfd_read, TS_BUF1 + ts->rdidx1, count);
557 if (count <= 0) {
558 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000559 goto skip3;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000560 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000561 }
562 /* Ignore trailing NUL if it is there */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000563 if (!TS_BUF1[ts->rdidx1 + count - 1]) {
Denis Vlasenkof472b232007-10-16 21:35:17 +0000564 --count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000565 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000566 ts->size1 += count;
567 ts->rdidx1 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000568 if (ts->rdidx1 >= BUFSIZE) /* actually == BUFSIZE */
569 ts->rdidx1 = 0;
570 }
571 skip3:
572 if (/*ts->size2 < BUFSIZE &&*/ FD_ISSET(ts->ptyfd, &rdfdset)) {
573 /* Read from pty to buffer 2. */
Denis Vlasenko10916c52007-10-15 17:28:00 +0000574 count = MIN(BUFSIZE - ts->rdidx2, BUFSIZE - ts->size2);
575 count = safe_read(ts->ptyfd, TS_BUF2 + ts->rdidx2, count);
576 if (count <= 0) {
577 if (count < 0 && errno == EAGAIN)
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000578 goto skip4;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000579 goto kill_session;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000580 }
Denis Vlasenko10916c52007-10-15 17:28:00 +0000581 ts->size2 += count;
582 ts->rdidx2 += count;
Denis Vlasenko59d7c432007-10-15 15:19:36 +0000583 if (ts->rdidx2 >= BUFSIZE) /* actually == BUFSIZE */
584 ts->rdidx2 = 0;
585 }
586 skip4:
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000587 ts = next;
Denis Vlasenkof472b232007-10-16 21:35:17 +0000588 continue;
589 kill_session:
590 free_session(ts);
591 ts = next;
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000592 }
Denis Vlasenkof472b232007-10-16 21:35:17 +0000593
Denis Vlasenko75f8d082006-11-22 15:54:52 +0000594 goto again;
Eric Andersen08a72202002-09-30 20:52:10 +0000595}