/* // AUTHOR // Stef Walter // // 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