summaryrefslogtreecommitdiff
path: root/src/jailer.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/jailer.c')
-rwxr-xr-xsrc/jailer.c494
1 files changed, 494 insertions, 0 deletions
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: <nielsen@memberwebs.com>
+//
+// 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 <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/param.h>
+#include <sys/user.h>
+#include <sys/sysctl.h>
+#include <sys/file.h>
+
+#include <kvm.h>
+#include <paths.h>
+#include <signal.h>
+#include <stdio.h>
+#include <syslog.h>
+#include <ctype.h>
+#include <err.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <limits.h>
+
+#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