From 8bd6fc0a98539e50d96fe4b499be40c06ca63f5e Mon Sep 17 00:00:00 2001 From: anonymous Date: Fri, 27 Jun 2003 22:56:31 +0000 Subject: Initial revision --- src/jailer.c | 494 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 494 insertions(+) create mode 100755 src/jailer.c (limited to 'src/jailer.c') diff --git a/src/jailer.c b/src/jailer.c new file mode 100755 index 0000000..8b49489 --- /dev/null +++ b/src/jailer.c @@ -0,0 +1,494 @@ +/* +// AUTHOR +// N. Nielsen +// +// LICENSE +// This software is in the public domain. +// +// The software is provided "as is", without warranty of any kind, +// express or implied, including but not limited to the warranties +// of merchantability, fitness for a particular purpose, and +// noninfringement. In no event shall the author(s) be liable for any +// claim, damages, or other liability, whether in an action of +// contract, tort, or otherwise, arising from, out of, or in connection +// with the software or the use or other dealings in the software. +// +// SUPPORT +// Send bug reports to: +// +// CHANGES +// 1.1 +// console support +// restart support +// 1.1.1 +// checks if in jail (contributed by jimquick@mac.com) +// changes process title with state +// Uses path for /dev/console from paths.h +// Doesn't fail stop if all processes in jail died during rc.shutdown +// 1.1.2 +// use select when piping script output +// +// 1.2 +// conditionally remove console support for use on FBSD 5 +*/ + +#define SYSLOG_NAMES + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_CONFIG_H +#include "../config.h" +#endif + +/* Length of buffer used for reading status file */ +#define FILE_BUFF_LEN 1024 + +/* Maximum length of a jail name */ +#define MAX_JAIL_NAME 256 + +/* Used when restarting before doing a SIGKILL */ +#define EXIT_TIMEOUT 5 + +#ifndef NO_CONSOLE + /* The default file for the console messages to go to */ + #define CONSOLE_LOG "/var/log/console" +#endif + +/* Headers to go to the console for startup and shutdown */ +#define START_HEADER "\nJAIL BOOT -- %s -- %s\n" +#define STOP_HEADER "\nJAIL HALT -- %s -- %s\n" + +/* The startup and shutdown commands */ +#define JAIL_START "exec 2>&1; . /etc/rc" +#define JAIL_STOP "exec 2>&1; . /etc/rc.shutdown" + +#define TITLE_START "%s (running)" +#define TITLE_STOP "%s (halted)" + +/* Set by signal handlers */ +int g_doShutdown = 0; +int g_doRestart = 0; +int g_doExit = 0; + +/* Other state */ +int g_isBackground = 0; +int g_isJailRunning = 0; +char g_jailName[MAX_JAIL_NAME]; + +/* Signal handlers */ +static void onTERM(int); +static void onHUP(int); +static void onQUIT(int); + + +/* Error handlers */ +static void doWarn(const char* msg, ...); +static void doQuit(const char* msg, ...); + +/* Main functionality */ +static int stopJail(); +static int startJail(); + +/* Helper functions */ +static void getJailName(char* buff, int buffLen); +static int runCommand(char* command, char* header); +int injail(); + +#ifndef NO_CONSOLE +static int createConsole(); +#endif + +int main(int argc, char* argv[]) +{ +#ifndef NO_CONSOLE + char* consoleFile = CONSOLE_LOG; +#endif + + /* Check if we're in a jail */ + switch(injail()) + { + case 0: + doQuit("must be run in a jail."); + break; + + case -1: + doQuit("couldn't determine if running in a jail."); + break; + } + + /* Get the name of the current jail */ + getJailName(g_jailName, MAX_JAIL_NAME); + +#ifndef NO_CONSOLE + if(argc > 1) + consoleFile = argv[1]; + + /* Create the console device properly */ + if(createConsole(consoleFile) != 0) + return 1; +#endif + + /* Start the jail */ + if(startJail() != 0) + return 1; + + /* Go into daemon mode */ + daemon(0, 0); + g_isBackground = 1; + + + + /* Catch appropriate signals */ + siginterrupt(SIGTERM, 1); + siginterrupt(SIGHUP, 1); + siginterrupt(SIGQUIT, 1); + + signal(SIGTERM, onTERM); + signal(SIGHUP, onHUP); + signal(SIGQUIT, onQUIT); + + + /* And wait! */ + while(1) + { + sleep(1); + + /* Got SIGQUIT Shutdown jail, but keep us running */ + if(g_doShutdown) + { + if(g_isJailRunning) + { + doWarn("shutting down jail: %s", g_jailName); + stopJail(); + } + + g_doShutdown = 0; + } + + + /* Got SIGHUP Stop if necessary and start jail */ + if(g_doRestart) + { + doWarn("restarting jail: %s", g_jailName); + + if(g_isJailRunning) + stopJail(); + + /* TODO: Should we start the jail again if stop fails? */ + startJail(); + + g_doRestart = 0; + } + + + /* Got SIGTERM Stop jail if necessary, then exit */ + else if(g_doExit) + { + if(g_isJailRunning) + { + doWarn("shutting down jail: %s", g_jailName); + return stopJail(); + } + + return 0; + } + } +} + + +static int startJail() +{ + /* Just run the startup command */ + int ret = runCommand(JAIL_START, START_HEADER); + if(ret < 0) + { + doWarn("couldn't run jail start script: %s\n", strerror(errno)); + return -1; + } + else + { + g_isJailRunning = 1; + setproctitle(TITLE_START, g_jailName); + return 0; + } +} + +static int stopJail() +{ + /* Run the rc.shutdown script */ + int ret = runCommand(JAIL_STOP, STOP_HEADER); + if(ret < 0) + { + doWarn("couldn't run jail shutdown script: %s\n", strerror(errno)); + return -1; + } + + + /* Kill all processes still running nicely */ + if(kill(-1, SIGTERM) == -1 || + sleep(EXIT_TIMEOUT) || + kill(-1, SIGKILL) == -1) + { + if(errno != ESRCH) + { + doWarn("couldn't stop jail %s: %s", g_jailName, strerror(errno)); + return -1; + } + } + + + setproctitle(TITLE_STOP, g_jailName); + g_isJailRunning = 0; + return 0; +} + + +/* Special function to duplicate output of a command to two + files. + + NOTE: Yes, I know this may seem like overkill, but system, + popen and all those guys would hang with certain rc scripts. + Those which opened a daemon in the background ('&') but still + kept their output going to the same stdin/stdout handles. +*/ + +/* read & write ends of a pipe */ +#define READ_END 0 +#define WRITE_END 1 + +/* pre-set file descriptors */ +#define STDIN 0 +#define STDOUT 1 +#define STDERR 2 + +static int runCommand(char* command, char* header) +{ + int outpipe[2]; + int pid; + + /* Create a pipe for the child process */ + if(pipe(outpipe) < 0) + return -1; + + /* Now fork*/ + switch(pid = fork()) + { + case -1: + return -1; + + /* This is the child here */ + case 0: + + { + int ret = 0; + + /* Fix up our end of the pipe */ + if(dup2(outpipe[WRITE_END], STDOUT) < 0) + return -1; + + /* Okay, now run whatever command it was */ + execl("/bin/sh", "sh", "-c", command, NULL); + + /* In case it returns then have to do this to get + children to disconnect from stdout */ + fflush(stdout); + fclose(stdout); + close(outpipe[WRITE_END]); + + exit(errno); + } + break; + + + /* And this is the parent */ + default: + { +#ifndef NO_CONSOLE + FILE* console = NULL; +#endif + time_t tm; + char buff[256]; + int ret; + int status = 0; + fd_set readmask; + struct timeval timeout = { 0, 10000 }; + + FD_ZERO(&readmask); + +#ifndef NO_CONSOLE + /* Open the console file and write the header */ + if(console = fopen(_PATH_CONSOLE, "a")) + { + setvbuf(console, NULL, _IONBF, 0); + + /* Write this stuff to the console */ + tm = time(NULL); + fprintf(console, header, g_jailName, asctime(localtime(&tm))); + } +#endif + + /* No blocking on the child processes pipe */ + fcntl(outpipe[READ_END], F_SETFL, fcntl(outpipe[READ_END], F_GETFL, 0) | O_NONBLOCK); + + /* Loop until the process dies or no more output */ + while(1) + { + FD_SET(outpipe[READ_END], &readmask); + + if(select(FD_SETSIZE, &readmask, NULL, NULL, &timeout) == -1) + doQuit("panic: select: %s", strerror(errno)); + + if(FD_ISSET(outpipe[READ_END], &readmask)) + { + /* Read a character */ + while((ret = read(outpipe[READ_END], buff, 256)) > 0) + { + /* Write it to our stdout */ + write(STDOUT, buff, ret); + +#ifndef NO_CONSOLE + /* And to our console */ + if(console) + write(fileno(console), buff, ret); +#endif + } + } + + /* If the processes exited then break out */ + if(waitpid(pid, &status, WNOHANG) == pid) + break; + + /* Or if there's an error or end of file */ + if(ret == -1 && errno != EAGAIN || ret == 0) + break; + } + + /* Return any status codes */ + if(status != 0) + return -1; + + /* Clean up */ + close(outpipe[READ_END]); + +#ifndef NO_CONSOLE + if(console) + fclose(console); +#endif + } + } + + return 0; +} + + +void doQuit(const char* msg, ...) +{ + va_list ap; + va_start(ap, msg); + + /* Either to syslog or stderr */ + if(g_isBackground) + vsyslog(LOG_ALERT, msg, ap); + else + vwarnx(msg, ap); + + exit(2); + + va_end(ap); +} + +void doWarn(const char* msg, ...) +{ + va_list ap; + va_start(ap, msg); + + /* Either to syslog or stderr */ + if(g_isBackground) + vsyslog(LOG_ALERT, msg, ap); + else + vwarnx(msg, ap); + + va_end(ap); +} + + +RETSIGTYPE onTERM(int x) +{ + g_doExit = 1; +} + +RETSIGTYPE onQUIT(int x) +{ + g_doShutdown = 1; +} + +RETSIGTYPE onHUP(int x) +{ + g_doRestart = 1; +} + + + +static void getJailName(char* buff, int buffLen) +{ + if(gethostname(buff, buffLen) == -1) + doQuit("couldn't get host name: %s", strerror(errno)); +} + +#ifndef NO_CONSOLE +static int createConsole(char* consoleFile) +{ + FILE* file = NULL; + struct stat sb; + + /* + * If we have a /dev/console already, only overwrite + * if it's not legit. ie: it's a link or a normal file. + */ + if(stat(_PATH_CONSOLE, &sb) == 0) + { + if(!(sb.st_mode & S_IFREG || sb.st_mode & S_IFLNK)) + { + doWarn("%s may be a valid console device.", _PATH_CONSOLE); + return 0; + } + } + + unlink(_PATH_CONSOLE); + + + /* + * Create the log file and console link + * Note that we overwrite the old console log file. + * TODO: We may want to make this an option to jailer + */ + if(!(file = fopen(consoleFile, "w")) || + link(consoleFile, _PATH_CONSOLE) == -1) + doWarn("couldn't do console link (%s to %s): %s", consoleFile, _PATH_CONSOLE, strerror(errno)); + + if(file) + fclose(file); + + + return 0; +} +#endif -- cgit v1.2.3