From: Teemu Hukkanen <tjhukkan@iki.fi>
Date: Sat, 16 Mar 2024 16:04:36 +0200
Subject: Add --chroot and --user options

Origin: upstream
Bug-Debian: https://bugs.debian.org/477401
Forwarded: not-needed

This allows chrooting the server, and running it with a different uid
& gid.
---
 ChangeLog |  6 +++++
 hts.c     | 91 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
 2 files changed, 93 insertions(+), 4 deletions(-)

diff --git a/ChangeLog b/ChangeLog
index cbc3287..28c1152 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,9 @@
+2003-01-10  Lars Brinkhoff  <lars@nocrew.org>
+
+	From Marco Michelino <michelinux@tin.it>:
+	* hts.c (usage, parse_arguments, main): add --chroot and --user
+	options.
+
 2001-06-12  lars brinkhoff  <lars@nocrew.org>
 
 	* tunnel.h: Improved documentation for tunnel programming
diff --git a/hts.c b/hts.c
index e21e136..1d7ef3a 100644
--- a/hts.c
+++ b/hts.c
@@ -13,6 +13,10 @@ two-way data path tunneled in HTTP requests.
 #include <signal.h>
 #include <sys/poll_.h>
 #include <sys/time.h>
+#include <sys/types.h>
+#include <pwd.h>
+#include <grp.h>
+#include <time.h>
 
 #include "common.h"
 
@@ -31,6 +35,8 @@ typedef struct
   int strict_content_length;
   int keep_alive;
   int max_connection_age;
+  char *root;
+  char *user;
 } Arguments;
 
 int debug_level = 0;
@@ -61,9 +67,11 @@ usage (FILE *f, const char *me)
 #endif
 "  -M, --max-connection-age SEC   maximum time a connection will stay\n"
 "                                 open is SEC seconds (default is %d)\n"
+"  -r, --chroot ROOT              change root to ROOT\n"
 "  -s, --stdin-stdout             use stdin/stdout for communication\n"
 "                                 (implies --no-daemon)\n"
 "  -S, --strict-content-length    always write Content-Length bytes in requests\n"
+"  -u, --user USER                change user to USER\n"
 "  -V, --version                  output version information and exit\n"
 "  -w, --no-daemon                don't fork into the background\n"
 "  -p, --pid-file LOCATION        write a PID file to LOCATION\n"
@@ -93,6 +101,8 @@ parse_arguments (int argc, char **argv, Arguments *arg)
   arg->strict_content_length = FALSE;
   arg->keep_alive = DEFAULT_KEEP_ALIVE;
   arg->max_connection_age = DEFAULT_CONNECTION_MAX_TIME;
+  arg->user = NULL;
+  arg->root = NULL;
   
   for (;;)
     {
@@ -100,24 +110,26 @@ parse_arguments (int argc, char **argv, Arguments *arg)
       static struct option long_options[] =
       {
 	{ "help", no_argument, 0, 'h' },
-	{ "stdin-stdout", no_argument, 0, 's' },
-	{ "strict-content-length", no_argument, 0, 'S' },
 	{ "version", no_argument, 0, 'V' },
 	{ "no-daemon", no_argument, 0, 'w' },
+	{ "user", required_argument, 0, 'u' },
 #ifdef DEBUG_MODE
 	{ "debug", required_argument, 0, 'D' },
 	{ "logfile", required_argument, 0, 'l' },
 #endif
+	{ "chroot", required_argument, 0, 'r' },
+	{ "stdin-stdout", no_argument, 0, 's' },
 	{ "device", required_argument, 0, 'd' },
 	{ "pid-file", required_argument, 0, 'p' },
 	{ "keep-alive", required_argument, 0, 'k' },
 	{ "forward-port", required_argument, 0, 'F' },
 	{ "content-length", required_argument, 0, 'c' },
+	{ "strict-content-length", no_argument, 0, 'S' },
 	{ "max-connection-age", required_argument, 0, 'M' },
 	{ 0, 0, 0, 0 }
       };
 
-      static const char *short_options = "c:d:F:hk:M:p:sSVw"
+      static const char *short_options = "c:d:F:hk:M:p:sSVwu:r:"
 #ifdef DEBUG_MODE
 	"D:l:"
 #endif
@@ -187,6 +199,10 @@ parse_arguments (int argc, char **argv, Arguments *arg)
 	  arg->max_connection_age = atoi (optarg);
 	  break;
 
+	case 'r':
+	  arg->root = optarg;
+	  break;
+
 	case 's':
 	  arg->use_std=TRUE;
 	  arg->use_daemon=FALSE;
@@ -196,6 +212,10 @@ parse_arguments (int argc, char **argv, Arguments *arg)
 	  arg->strict_content_length = TRUE;
 	  break;
 
+	case 'u':
+	  arg->user = optarg;
+	  break;
+
 	case 'V':
 	  printf ("hts (%s) %s\n", PACKAGE, VERSION);
 	  exit (0);
@@ -278,6 +298,8 @@ main (int argc, char **argv)
   Arguments arg;
   Tunnel *tunnel;
   FILE *pid_file;
+  uid_t uid;
+  gid_t gid;
 
   parse_arguments (argc, argv, &arg);
 
@@ -307,11 +329,13 @@ main (int argc, char **argv)
   log_notice ("  debug_level = %d", debug_level);
   log_notice ("  pid_filename = %s",
 	      arg.pid_filename ? arg.pid_filename : "(null)");
+  log_notice ("  chroot = %s", arg.root ? arg.root : "(null)");
+  log_notice ("  user = %s", arg.user ? arg.user : "(null)");
 
   tunnel = tunnel_new_server (arg.host, arg.port, arg.content_length);
   if (tunnel == NULL)
     {
-      log_error ("couldn't create tunnel", argv[0]);
+      log_error ("couldn't create tunnel");
       log_exit (1);
     }
 
@@ -353,6 +377,65 @@ main (int argc, char **argv)
          }
      }
 
+  /* If requested to change user, get new uid and gid before chroot so we */
+  /* don't need /etc/passwd & company in the chroot jail */
+  if (arg.user)
+    {
+      struct passwd *pwd = getpwnam (arg.user);
+      if (pwd == NULL)
+        {
+          log_error ("couldn't find user");
+          log_exit (1);
+	}
+      uid = pwd->pw_uid;
+      gid = pwd->pw_gid;
+      if (setgroups (0, (const gid_t *)0) < 0)
+        {
+          log_error ("couldn't drop supplementary groups privileges");
+          log_exit (1);
+	}
+      if (setgid (gid) < 0)
+        {
+          log_error ("couldn't change primary group");
+          log_exit (1);
+	}
+      if (initgroups (arg.user, gid) < 0)
+        {
+	  /* non critical error */
+          log_error ("couldn't add supplementary groups");
+	}
+    }
+
+  /* Change root if requested */
+  if (arg.root)
+    {
+      if (chroot (arg.root) < 0)
+        {
+          log_error ("couldn't change root");
+          log_exit (1);
+	}
+      if (chdir ("/") < 0)
+        {
+          log_error ("couldn't change dir to new root");
+          log_exit (1);
+	}
+      if (fclose (stdin) || fclose (stdout) || fclose (stderr))
+        {
+          log_error ("couldn't close stdin, stdout and/or stderr");
+          log_exit (1);
+	}
+    }
+
+  /* Change user if requested */
+  if (arg.user)
+    {
+      if (setuid (uid) < 0)
+        {
+          log_error ("couldn't change user");
+          log_exit (1);
+	}
+    }
+
   for (;;)
     {
       time_t last_tunnel_write;
