diff --git a/Makefile.common b/Makefile.common index b58412e..8cf9815 100644 --- a/Makefile.common +++ b/Makefile.common @@ -20,6 +20,7 @@ TELNETD_SRCS = \ libtelnet/getent.c \ libtelnet/posix_openpt.c \ libtelnet/vasprintf.c \ + libtelnet/vfork.and.run.c \ telnetd/global.c \ telnetd/slc.c \ telnetd/state.c \ diff --git a/libtelnet/vfork.and.run.c b/libtelnet/vfork.and.run.c new file mode 100644 index 0000000..0b863ab --- /dev/null +++ b/libtelnet/vfork.and.run.c @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2014, 2016 Stephen Heumann + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#include +#include +#ifdef __GNO__ +#include +#endif + +/* Like vfork, but calls fn(arg) in the child instead of returning. + * This is designed to match the semantics of GNO's fork2 call. + */ +#ifndef __GNO__ + +pid_t vfork_and_run(void (*fn)(void*) /*NORETURN*/, void *arg) { + /* Use fork rather than vfork because vfork is problematic on OS X. */ + pid_t pid = fork(); + + if (pid == 0) { + fn(arg); + } + + return pid; +} + +#else + +# include +# include +# include + +/* Turn off all ORCA/C stack repair code to avoid corruption. */ +# ifdef __ORCAC__ +# pragma optimize 72 +# endif + +# pragma databank 1 +void fork_thunk(void (*fn)(void*) /*NORETURN*/, void *arg, long sigmask) { + sigsetmask(sigmask); + fn(arg); +} +# pragma databank 0 + +const char *forked_child_name = "telnetd pty slave proc"; +#define CHILD_STACKSIZE 1024 + +pid_t vfork_and_run(void (*fn)(void*) /*NORETURN*/, void *arg) { + /* GNO's fork2 call will return immediately and allow the parent and + * child processes to execute concurrently using the same memory + * space. To prevent them stomping on each other, we want to get + * behavior like a traditional vfork() implementation, where the + * parent blocks until the child terminates or execs. + * + * Our approach is to check the process tables to make sure the + * child has actually finished or exec'd. If not, we loop and try again. + * We can't just rely on the fact that the child signaled us, because + * it may still be running in libc's implementation of exec*. + */ + + long oldmask; + pid_t pid; + kvmt *kvm_context; + struct pentry *proc_entry; + int done = 0; + + /* Isolate child process's environment from parent */ + if (environPush() != 0) + return -1; + + /* Block all signals for now */ + oldmask = sigblock(-1); + + pid = fork2(fork_thunk, CHILD_STACKSIZE, 0, forked_child_name, + (sizeof(fn) + sizeof(arg) + sizeof(oldmask) + 1) / 2, + fn, arg, oldmask); + if (pid < 0) + goto ret; + + while (!done) { + /* Wait for ~100 ms. If procsend worked, the child could send a + * message with it to end the waiting earlier, but this isn't + * possible in GNO 2.0.6 because procsend is broken. This isn't + * too big an issue, since 100ms isn't very long to wait anyhow. */ + procrecvtim(1); + + /* Check if the child is really dead or forked by inspecting + * the kernel's process entry for it. */ + kvm_context = kvm_open(); + if (kvm_context == NULL) + break; + proc_entry = kvmgetproc(kvm_context, pid); + if (proc_entry == NULL + || (proc_entry->args != NULL + && strcmp(forked_child_name, proc_entry->args + 8) != 0)) + done = 1; + kvm_close(kvm_context); + } + +ret: + sigsetmask(oldmask); + environPop(); + return pid; +} + +#endif diff --git a/libtelnet/vfork.and.run.h b/libtelnet/vfork.and.run.h new file mode 100644 index 0000000..81e72fa --- /dev/null +++ b/libtelnet/vfork.and.run.h @@ -0,0 +1 @@ +pid_t vfork_and_run(void (*fn)(void*) /*NORETURN*/, void *arg); diff --git a/telnetd/ext.h b/telnetd/ext.h index 61db4a8..33e6595 100644 --- a/telnetd/ext.h +++ b/telnetd/ext.h @@ -83,6 +83,8 @@ extern int SYNCHing; /* we are in TELNET SYNCH mode */ /* Buffer for miscellaneous uses in various functions */ extern char buf[BUFSIZ > 1024 ? BUFSIZ : 1024]; +extern int parent_pid; /* pid of parent (server) process */ + extern void _termstat(void), add_slc(char, char, cc_t), @@ -166,6 +168,7 @@ extern int tty_linemode(void); extern void + safe_exit(int), tty_rspeed(int), tty_setecho(int), tty_setedit(int), @@ -194,7 +197,7 @@ extern char *nclearto; */ extern struct { - int + long system, /* what the current time is */ echotoggle, /* last time user entered echo character */ modenegotiated, /* last time operating mode negotiated */ diff --git a/telnetd/sys_term.c b/telnetd/sys_term.c index 05116d1..5cd8ad2 100644 --- a/telnetd/sys_term.c +++ b/telnetd/sys_term.c @@ -45,8 +45,13 @@ static const char sccsid[] = "@(#)sys_term.c 8.4+1 (Berkeley) 5/30/95"; #endif #include +#ifdef __GNO__ +#include +#endif + #include "telnetd.h" #include "pathnames.h" +#include "libtelnet/vfork.and.run.h" #ifdef AUTHENTICATION #include "libtelnet/auth.h" @@ -922,22 +927,20 @@ cleanopen(char *li) return(t); } -#ifdef __ORCAC__ -# pragma databank 1 -#endif +struct slaveargs { + char *host; + int autologin; + char *autoname; +}; + static void -slaveproc(char *host, int autologin, char *autoname) +slaveproc(void *slaveargs) { -#ifdef __GNO__ - environPush(); -#endif + struct slaveargs *args = slaveargs; getptyslave(); - start_login(host, autologin, autoname); + start_login(args->host, args->autologin, args->autoname); /*NOTREACHED*/ } -#ifdef __ORCAC__ -# pragma databank 0 -#endif /* * startslave(host) @@ -951,6 +954,7 @@ void startslave(char *host, int autologin, char *autoname) { int i; + struct slaveargs slaveargs = {host, autologin, autoname}; #ifdef AUTHENTICATION if (!autoname || !autoname[0]) @@ -962,25 +966,9 @@ startslave(char *host, int autologin, char *autoname) } #endif -#ifndef __GNO__ - i = fork(); -#else - i = fork2(slaveproc, 1024, 0, "telnetd pty slave proc", - (sizeof(host) + sizeof(autologin) + sizeof(autoname))/2, - host, autologin, autoname); -#endif + i = vfork_and_run(slaveproc, &slaveargs); if (i < 0) fatalperror(net, "fork"); - if (i) { -#ifdef __GNO__ - do { - errno = 0; - (void)wait(NULL); - } while (errno == EINTR); -#endif - } else { - slaveproc(host, autologin, autoname); - } } void @@ -1276,6 +1264,47 @@ scrub_env(void) environ = new_environ; } + +#ifdef __GNO__ +static QuitRecGS quitRec = {0, NULL, 0}; +#endif + +/* + * safe_exit() + * + * Exit in a way that's safe for either the forked child or + * the parent. + */ +void +safe_exit(int status) +{ + if (getpid() == parent_pid) { + exit(status); + } else { +#ifndef __GNO__ + _exit(status); +#else + /* _exit (contrary to its documentation) performs clean-up + * that's inappropriate for a forked child process (this + * usually results in corruption of the memory allocator + * state, and maybe other problems), so we define our own + * function without this problem. We call QuitGS in assembly + * so we can push the return value on the stack. + */ + while (1) { + asm { + lda status + pha + jsl 0xE100A8 + dcw 0x2029 /* QuitGS */ + dcl quitRec; + pla + } + } +#endif + } +} + /* * cleanup() * @@ -1288,5 +1317,5 @@ cleanup(int sig __unused) { (void) shutdown(net, SHUT_RDWR); - exit(1); + safe_exit(1); } diff --git a/telnetd/telnetd.c b/telnetd/telnetd.c index 4f3821a..66e14ca 100644 --- a/telnetd/telnetd.c +++ b/telnetd/telnetd.c @@ -156,13 +156,14 @@ main(int argc, char *argv[]) _reportStack(); /* Make sure our environment is isolated from the parent process's */ - if (environPush() == 0) + if (environPush() == 0 && environInit() == 0) atexit(environPop); else exit(1); - environInit(); #endif + parent_pid = getpid(); + pfrontp = pbackp = ptyobuf; netip = netibuf; nfrontp = nbackp = netobuf; diff --git a/telnetd/utility.c b/telnetd/utility.c index 111bcfc..aa8a785 100644 --- a/telnetd/utility.c +++ b/telnetd/utility.c @@ -339,7 +339,7 @@ fatal(int f, const char *msg) #endif /* ENCRYPTION */ (void) write(f, buf, (int)strlen(buf)); sleep(1); /*XXX*/ - exit(1); + safe_exit(1); } void