Repositories / jai.git

jai.git

Clone (read-only): git clone http://git.guha-anderson.com/git/jai.git

Branch

add --command

Author
David Mazieres <dm@uun.org>
Date
2026-03-15 02:01:47 -0700
Commit
65752b5140119b05b27084bd74d8cfca66c9cde4
configure.ac
index d584f92..2e04942 100644
--- a/configure.ac
+++ b/configure.ac
@@ -19,6 +19,9 @@ AC_LANG_POP([C++])
 PKG_CHECK_MODULES([MOUNT], [mount])
 PKG_CHECK_MODULES([LIBACL], [libacl])
 
+AC_PATH_PROG([PATH_BASH], [bash], [/bin/bash])
+AC_DEFINE_UNQUOTED([PATH_BASH], ["$PATH_BASH"], [Path to bash])
+
 # --with-untrusted-user=USER (default: auto-detect from password file or "jai")
 AC_ARG_WITH([untrusted-user],
   [AS_HELP_STRING([--with-untrusted-user=USER],
jai.cc
index ba1ea7e..1fb59d7 100644
--- a/jai.cc
+++ b/jai.cc
@@ -27,6 +27,7 @@ struct Config {
   bool grant_cwd_{true};
   std::set<std::string> env_filter_;
   path cwd_;
+  std::string shellcmd_;
 
   std::string user_;
   path homepath_;
@@ -47,6 +48,8 @@ struct Config {
   void unmount();
   void unmountall();
   Options opt_parser();
+  void make_default_conf();
+  void sanitize_env();
 
   [[nodiscard]] static Defer asuser(const Credentials *crp);
   [[nodiscard]] Defer asuser() { return asuser(&user_cred_); }
@@ -434,7 +437,7 @@ Config::make_mnt_ns()
       if (cwd() == homepath_) {
         std::string name = prog.filename().string();
         std::println(
-R"({0}: Refusing to expose your entire home directory to sandbox.  Did
+            R"({0}: Refusing to expose your entire home directory to sandbox.  Did
 {1:>{2}}  you forget to specify the -D option?  If you really want to grant
 {1:>{2}}  permissions on your entire home directory, use both -D and -d, as in
 {1:>{2}}    {0} -Dd {3} ...)",
@@ -601,17 +604,37 @@ auto env_blacklist = std::to_array<const char *>({
     "*_TOKEN",
 });
 
+void
+Config::make_default_conf()
+{
+  auto fd = xopenat(home_jai(), ".", O_RDWR | O_TMPFILE | O_CLOEXEC, 0600);
+  std::string text;
+  for (auto e : env_blacklist)
+    text += std::format("unsetenv {}\n", e);
+  // The executed process will have PID 1, which can confuse some programs,
+  // so add "; exit $?" to force bash to stay around and be PID 1.
+  text += R"(command "$0" "$@"; exit $?)"
+          "\n";
+
+  errno = EAGAIN;
+  if (write(*fd, text.data(), text.size()) != text.size())
+    syserr("write(O_TMPFILE for default.conf)");
+  if (linkat(*fd, "", home_jai(), "default.conf", AT_EMPTY_PATH) &&
+      errno != EEXIST)
+    syserr("linkat({})", fdpath(home_jai(), "default.conf"));
+}
+
 extern "C" char **environ;
 
 void
-sanitize_env()
+Config::sanitize_env()
 {
   std::vector<std::string_view> patterns;
 
-  for (const char *v : env_blacklist) {
-    if (!std::strchr(v, '*'))
-      unsetenv(v);
-    else if (std::count(v, v + std::strlen(v), '*') <= 4)
+  for (const auto &v : env_filter_) {
+    if (v.find('*') == v.npos)
+      unsetenv(v.c_str());
+    else if (std::ranges::count(v, '*') <= 4)
       patterns.push_back(v);
     else
       // Too many *s could cause a lot of backtracking
@@ -674,8 +697,20 @@ Config::exec(int nsfd, char **argv)
     syserr("chdir({})", cwd().string());
   sanitize_env();
   umask(old_umask_);
-  execvp(argv[0], argv);
-  perror(argv[0]);
+  const char *argv0 = argv[0];
+  std::vector<const char *> bashcmd;
+  if (!shellcmd_.empty()) {
+    argv0 = PATH_BASH;
+    bashcmd.push_back("init");
+    bashcmd.push_back("-c");
+    bashcmd.push_back(shellcmd_.c_str());
+    while(*argv)
+      bashcmd.push_back(*(argv++));
+    bashcmd.push_back(nullptr);
+    argv = const_cast<char **>(bashcmd.data());
+  }
+  execvp(argv0, argv);
+  perror(argv0);
   _exit(1);
 }
 
@@ -709,6 +744,9 @@ Config::opt_parser()
       "--unsetenv",
       [this](std::string var) { env_filter_.emplace(std::move(var)); },
       "Remove VAR from environment (VAR can contain wildcard '*')", "VAR");
+  opts(
+      "--command", [this](std::string cmd) { shellcmd_ = std::move(cmd); },
+      R"(Bash command line to execute program (default: "$0" "$@"))", "CMD");
   return opts;
 }
 
@@ -770,26 +808,28 @@ do_main(int argc, char **argv)
     return;
   }
 
-  std::vector<path> cfs;
-  if (!cmd.empty() && conf.name_ok(cmd[0]))
-    cfs.push_back(cmd[0]);
-  cfs.push_back("default");
-  for (path p : cfs) {
+  auto tryparse = [&opts, &conf, argc, argv](path p) {
     if (!conf.name_ok(p))
-      continue;
+      return false;
     p += ".conf";
     if (Fd cf = openat(conf.home_jai(), p.c_str(), O_RDONLY)) {
       try {
         conf.opt_parser().parse_file(read_file(*cf));
+        // Re-parse argv so it takes precedence
+        opts.parse_argv(argc, argv);
+        return true;
       } catch (const Options::Error &e) {
         err<Options::Error>("{}:{}", fdpath(conf.home_jai(), p), e.what());
       }
-      // Re-parse CLI so it takes precedence
-      opts.parse_argv(argc, argv);
-      break;
     }
     if (errno != ENOENT)
       syserr("{}", fdpath(conf.home_jai(), p));
+    return false;
+  };
+
+  if (!(!cmd.empty() && tryparse(cmd[0])) && !tryparse("default")) {
+    conf.make_default_conf();
+    tryparse("default");
   }
 
   restore.reset();
@@ -809,8 +849,8 @@ main(int argc, char **argv)
   else
     prog = PACKAGE_TARNAME;
 
-  //do_main(argc, argv);
-  //return 0;
+  // do_main(argc, argv);
+  // return 0;
 
   try {
     do_main(argc, argv);