445 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			445 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
/*
 | 
						|
Copyright (c) 2001-2006, Gerrit Pape
 | 
						|
All rights reserved.
 | 
						|
 | 
						|
Redistribution and use in source and binary forms, with or without
 | 
						|
modification, are permitted provided that the following conditions are met:
 | 
						|
 | 
						|
   1. Redistributions of source code must retain the above copyright notice,
 | 
						|
      this list of conditions and the following disclaimer.
 | 
						|
   2. Redistributions in binary form must reproduce the above copyright
 | 
						|
      notice, this list of conditions and the following disclaimer in the
 | 
						|
      documentation and/or other materials provided with the distribution.
 | 
						|
   3. The name of the author may not be used to endorse or promote products
 | 
						|
      derived from this software without specific prior written permission.
 | 
						|
 | 
						|
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
 | 
						|
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 | 
						|
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
 | 
						|
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 | 
						|
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 | 
						|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
 | 
						|
OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 | 
						|
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
 | 
						|
OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 | 
						|
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 | 
						|
*/
 | 
						|
 | 
						|
/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
 | 
						|
/* Dependencies on runit_lib.c removed */
 | 
						|
 | 
						|
//usage:#define chpst_trivial_usage
 | 
						|
//usage:       "[-vP012] [-u USER[:GRP]] [-U USER[:GRP]] [-e DIR]\n"
 | 
						|
//usage:       "	[-/ DIR] [-n NICE] [-m BYTES] [-d BYTES] [-o N]\n"
 | 
						|
//usage:       "	[-p N] [-f BYTES] [-c BYTES] PROG ARGS"
 | 
						|
//usage:#define chpst_full_usage "\n\n"
 | 
						|
//usage:       "Change the process state, run PROG\n"
 | 
						|
//usage:     "\n	-u USER[:GRP]	Set uid and gid"
 | 
						|
//usage:     "\n	-U USER[:GRP]	Set $UID and $GID in environment"
 | 
						|
//usage:     "\n	-e DIR		Set environment variables as specified by files"
 | 
						|
//usage:     "\n			in DIR: file=1st_line_of_file"
 | 
						|
//usage:     "\n	-/ DIR		Chroot to DIR"
 | 
						|
//usage:     "\n	-n NICE		Add NICE to nice value"
 | 
						|
//usage:     "\n	-m BYTES	Same as -d BYTES -s BYTES -l BYTES"
 | 
						|
//usage:     "\n	-d BYTES	Limit data segment"
 | 
						|
//usage:     "\n	-o N		Limit number of open files per process"
 | 
						|
//usage:     "\n	-p N		Limit number of processes per uid"
 | 
						|
//usage:     "\n	-f BYTES	Limit output file sizes"
 | 
						|
//usage:     "\n	-c BYTES	Limit core file size"
 | 
						|
//usage:     "\n	-v		Verbose"
 | 
						|
//usage:     "\n	-P		Create new process group"
 | 
						|
//usage:     "\n	-0		Close stdin"
 | 
						|
//usage:     "\n	-1		Close stdout"
 | 
						|
//usage:     "\n	-2		Close stderr"
 | 
						|
//usage:
 | 
						|
//usage:#define envdir_trivial_usage
 | 
						|
//usage:       "DIR PROG ARGS"
 | 
						|
//usage:#define envdir_full_usage "\n\n"
 | 
						|
//usage:       "Set various environment variables as specified by files\n"
 | 
						|
//usage:       "in the directory DIR, run PROG"
 | 
						|
//usage:
 | 
						|
//usage:#define envuidgid_trivial_usage
 | 
						|
//usage:       "USER PROG ARGS"
 | 
						|
//usage:#define envuidgid_full_usage "\n\n"
 | 
						|
//usage:       "Set $UID to USER's uid and $GID to USER's gid, run PROG"
 | 
						|
//usage:
 | 
						|
//usage:#define setuidgid_trivial_usage
 | 
						|
//usage:       "USER PROG ARGS"
 | 
						|
//usage:#define setuidgid_full_usage "\n\n"
 | 
						|
//usage:       "Set uid and gid to USER's uid and gid, drop supplementary group ids,\n"
 | 
						|
//usage:       "run PROG"
 | 
						|
//usage:
 | 
						|
//usage:#define softlimit_trivial_usage
 | 
						|
//usage:       "[-a BYTES] [-m BYTES] [-d BYTES] [-s BYTES] [-l BYTES]\n"
 | 
						|
//usage:       "	[-f BYTES] [-c BYTES] [-r BYTES] [-o N] [-p N] [-t N]\n"
 | 
						|
//usage:       "	PROG ARGS"
 | 
						|
//usage:#define softlimit_full_usage "\n\n"
 | 
						|
//usage:       "Set soft resource limits, then run PROG\n"
 | 
						|
//usage:     "\n	-a BYTES	Limit total size of all segments"
 | 
						|
//usage:     "\n	-m BYTES	Same as -d BYTES -s BYTES -l BYTES -a BYTES"
 | 
						|
//usage:     "\n	-d BYTES	Limit data segment"
 | 
						|
//usage:     "\n	-s BYTES	Limit stack segment"
 | 
						|
//usage:     "\n	-l BYTES	Limit locked memory size"
 | 
						|
//usage:     "\n	-o N		Limit number of open files per process"
 | 
						|
//usage:     "\n	-p N		Limit number of processes per uid"
 | 
						|
//usage:     "\nOptions controlling file sizes:"
 | 
						|
//usage:     "\n	-f BYTES	Limit output file sizes"
 | 
						|
//usage:     "\n	-c BYTES	Limit core file size"
 | 
						|
//usage:     "\nEfficiency opts:"
 | 
						|
//usage:     "\n	-r BYTES	Limit resident set size"
 | 
						|
//usage:     "\n	-t N		Limit CPU time, process receives"
 | 
						|
//usage:     "\n			a SIGXCPU after N seconds"
 | 
						|
 | 
						|
#include "libbb.h"
 | 
						|
#include <sys/resource.h> /* getrlimit */
 | 
						|
 | 
						|
/*
 | 
						|
Five applets here: chpst, envdir, envuidgid, setuidgid, softlimit.
 | 
						|
 | 
						|
Only softlimit and chpst are taking options:
 | 
						|
 | 
						|
# common
 | 
						|
-o N            Limit number of open files per process
 | 
						|
-p N            Limit number of processes per uid
 | 
						|
-m BYTES        Same as -d BYTES -s BYTES -l BYTES [-a BYTES]
 | 
						|
-d BYTES        Limit data segment
 | 
						|
-f BYTES        Limit output file sizes
 | 
						|
-c BYTES        Limit core file size
 | 
						|
# softlimit
 | 
						|
-a BYTES        Limit total size of all segments
 | 
						|
-s BYTES        Limit stack segment
 | 
						|
-l BYTES        Limit locked memory size
 | 
						|
-r BYTES        Limit resident set size
 | 
						|
-t N            Limit CPU time
 | 
						|
# chpst
 | 
						|
-u USER[:GRP]   Set uid and gid
 | 
						|
-U USER[:GRP]   Set $UID and $GID in environment
 | 
						|
-e DIR          Set environment variables as specified by files in DIR
 | 
						|
-/ DIR          Chroot to DIR
 | 
						|
-n NICE         Add NICE to nice value
 | 
						|
-v              Verbose
 | 
						|
-P              Create new process group
 | 
						|
-0 -1 -2        Close fd 0,1,2
 | 
						|
 | 
						|
Even though we accept all these options for both softlimit and chpst,
 | 
						|
they are not to be advertised on their help texts.
 | 
						|
We have enough problems with feature creep in other people's
 | 
						|
software, don't want to add our own.
 | 
						|
 | 
						|
envdir, envuidgid, setuidgid take no options, but they reuse code which
 | 
						|
handles -e, -U and -u.
 | 
						|
*/
 | 
						|
 | 
						|
enum {
 | 
						|
	OPT_a = (1 << 0) * ENABLE_SOFTLIMIT,
 | 
						|
	OPT_c = (1 << 1) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 | 
						|
	OPT_d = (1 << 2) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 | 
						|
	OPT_f = (1 << 3) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 | 
						|
	OPT_l = (1 << 4) * ENABLE_SOFTLIMIT,
 | 
						|
	OPT_m = (1 << 5) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 | 
						|
	OPT_o = (1 << 6) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 | 
						|
	OPT_p = (1 << 7) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 | 
						|
	OPT_r = (1 << 8) * ENABLE_SOFTLIMIT,
 | 
						|
	OPT_s = (1 << 9) * ENABLE_SOFTLIMIT,
 | 
						|
	OPT_t = (1 << 10) * ENABLE_SOFTLIMIT,
 | 
						|
	OPT_u = (1 << 11) * (ENABLE_CHPST || ENABLE_SETUIDGID),
 | 
						|
	OPT_U = (1 << 12) * (ENABLE_CHPST || ENABLE_ENVUIDGID),
 | 
						|
	OPT_e = (1 << 13) * (ENABLE_CHPST || ENABLE_ENVDIR),
 | 
						|
	OPT_root = (1 << 14) * ENABLE_CHPST,
 | 
						|
	OPT_n = (1 << 15) * ENABLE_CHPST,
 | 
						|
	OPT_v = (1 << 16) * ENABLE_CHPST,
 | 
						|
	OPT_P = (1 << 17) * ENABLE_CHPST,
 | 
						|
	OPT_0 = (1 << 18) * ENABLE_CHPST,
 | 
						|
	OPT_1 = (1 << 19) * ENABLE_CHPST,
 | 
						|
	OPT_2 = (1 << 20) * ENABLE_CHPST,
 | 
						|
};
 | 
						|
 | 
						|
/* TODO: use recursive_action? */
 | 
						|
static NOINLINE void edir(const char *directory_name)
 | 
						|
{
 | 
						|
	int wdir;
 | 
						|
	DIR *dir;
 | 
						|
	struct dirent *d;
 | 
						|
	int fd;
 | 
						|
 | 
						|
	wdir = xopen(".", O_RDONLY | O_NDELAY);
 | 
						|
	xchdir(directory_name);
 | 
						|
	dir = xopendir(".");
 | 
						|
	for (;;) {
 | 
						|
		char buf[256];
 | 
						|
		char *tail;
 | 
						|
		int size;
 | 
						|
 | 
						|
		errno = 0;
 | 
						|
		d = readdir(dir);
 | 
						|
		if (!d) {
 | 
						|
			if (errno)
 | 
						|
				bb_perror_msg_and_die("readdir %s",
 | 
						|
						directory_name);
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		if (d->d_name[0] == '.')
 | 
						|
			continue;
 | 
						|
		fd = open(d->d_name, O_RDONLY | O_NDELAY);
 | 
						|
		if (fd < 0) {
 | 
						|
			if ((errno == EISDIR) && directory_name) {
 | 
						|
				if (option_mask32 & OPT_v)
 | 
						|
					bb_perror_msg("warning: %s/%s is a directory",
 | 
						|
						directory_name, d->d_name);
 | 
						|
				continue;
 | 
						|
			}
 | 
						|
			bb_perror_msg_and_die("open %s/%s",
 | 
						|
						directory_name, d->d_name);
 | 
						|
		}
 | 
						|
		size = full_read(fd, buf, sizeof(buf)-1);
 | 
						|
		close(fd);
 | 
						|
		if (size < 0)
 | 
						|
			bb_perror_msg_and_die("read %s/%s",
 | 
						|
					directory_name, d->d_name);
 | 
						|
		if (size == 0) {
 | 
						|
			unsetenv(d->d_name);
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		buf[size] = '\n';
 | 
						|
		tail = strchr(buf, '\n');
 | 
						|
		/* skip trailing whitespace */
 | 
						|
		while (1) {
 | 
						|
			*tail = '\0';
 | 
						|
			tail--;
 | 
						|
			if (tail < buf || !isspace(*tail))
 | 
						|
				break;
 | 
						|
		}
 | 
						|
		xsetenv(d->d_name, buf);
 | 
						|
	}
 | 
						|
	closedir(dir);
 | 
						|
	if (fchdir(wdir) == -1)
 | 
						|
		bb_perror_msg_and_die("fchdir");
 | 
						|
	close(wdir);
 | 
						|
}
 | 
						|
 | 
						|
static void limit(int what, long l)
 | 
						|
{
 | 
						|
	struct rlimit r;
 | 
						|
 | 
						|
	/* Never fails under Linux (except if you pass it bad arguments) */
 | 
						|
	getrlimit(what, &r);
 | 
						|
	if ((l < 0) || (l > r.rlim_max))
 | 
						|
		r.rlim_cur = r.rlim_max;
 | 
						|
	else
 | 
						|
		r.rlim_cur = l;
 | 
						|
	if (setrlimit(what, &r) == -1)
 | 
						|
		bb_perror_msg_and_die("setrlimit");
 | 
						|
}
 | 
						|
 | 
						|
int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 | 
						|
int chpst_main(int argc UNUSED_PARAM, char **argv)
 | 
						|
{
 | 
						|
	struct bb_uidgid_t ugid;
 | 
						|
	char *set_user = set_user; /* for compiler */
 | 
						|
	char *env_dir = env_dir;
 | 
						|
	char *root;
 | 
						|
	char *nicestr;
 | 
						|
	unsigned limita;
 | 
						|
	unsigned limitc;
 | 
						|
	unsigned limitd;
 | 
						|
	unsigned limitf;
 | 
						|
	unsigned limitl;
 | 
						|
	unsigned limitm;
 | 
						|
	unsigned limito;
 | 
						|
	unsigned limitp;
 | 
						|
	unsigned limitr;
 | 
						|
	unsigned limits;
 | 
						|
	unsigned limitt;
 | 
						|
	unsigned opt;
 | 
						|
 | 
						|
	if ((ENABLE_CHPST && applet_name[0] == 'c')
 | 
						|
	 || (ENABLE_SOFTLIMIT && applet_name[1] == 'o')
 | 
						|
	) {
 | 
						|
		// FIXME: can we live with int-sized limits?
 | 
						|
		// can we live with 40000 days?
 | 
						|
		// if yes -> getopt converts strings to numbers for us
 | 
						|
		opt_complementary = "-1:a+:c+:d+:f+:l+:m+:o+:p+:r+:s+:t+";
 | 
						|
		opt = getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:u:U:e:"
 | 
						|
			IF_CHPST("/:n:vP012"),
 | 
						|
			&limita, &limitc, &limitd, &limitf, &limitl,
 | 
						|
			&limitm, &limito, &limitp, &limitr, &limits, &limitt,
 | 
						|
			&set_user, &set_user, &env_dir
 | 
						|
			IF_CHPST(, &root, &nicestr));
 | 
						|
		argv += optind;
 | 
						|
		if (opt & OPT_m) { // -m means -asld
 | 
						|
			limita = limits = limitl = limitd = limitm;
 | 
						|
			opt |= (OPT_s | OPT_l | OPT_a | OPT_d);
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		option_mask32 = opt = 0;
 | 
						|
		argv++;
 | 
						|
		if (!*argv)
 | 
						|
			bb_show_usage();
 | 
						|
	}
 | 
						|
 | 
						|
	// envdir?
 | 
						|
	if (ENABLE_ENVDIR && applet_name[3] == 'd') {
 | 
						|
		env_dir = *argv++;
 | 
						|
		opt |= OPT_e;
 | 
						|
	}
 | 
						|
 | 
						|
	// setuidgid?
 | 
						|
	if (ENABLE_SETUIDGID && applet_name[1] == 'e') {
 | 
						|
		set_user = *argv++;
 | 
						|
		opt |= OPT_u;
 | 
						|
	}
 | 
						|
 | 
						|
	// envuidgid?
 | 
						|
	if (ENABLE_ENVUIDGID && applet_name[0] == 'e' && applet_name[3] == 'u') {
 | 
						|
		set_user = *argv++;
 | 
						|
		opt |= OPT_U;
 | 
						|
	}
 | 
						|
 | 
						|
	// we must have PROG [ARGS]
 | 
						|
	if (!*argv)
 | 
						|
		bb_show_usage();
 | 
						|
 | 
						|
	// set limits
 | 
						|
	if (opt & OPT_d) {
 | 
						|
#ifdef RLIMIT_DATA
 | 
						|
		limit(RLIMIT_DATA, limitd);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"DATA");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_s) {
 | 
						|
#ifdef RLIMIT_STACK
 | 
						|
		limit(RLIMIT_STACK, limits);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"STACK");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_l) {
 | 
						|
#ifdef RLIMIT_MEMLOCK
 | 
						|
		limit(RLIMIT_MEMLOCK, limitl);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"MEMLOCK");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_a) {
 | 
						|
#ifdef RLIMIT_VMEM
 | 
						|
		limit(RLIMIT_VMEM, limita);
 | 
						|
#else
 | 
						|
#ifdef RLIMIT_AS
 | 
						|
		limit(RLIMIT_AS, limita);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"VMEM");
 | 
						|
#endif
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_o) {
 | 
						|
#ifdef RLIMIT_NOFILE
 | 
						|
		limit(RLIMIT_NOFILE, limito);
 | 
						|
#else
 | 
						|
#ifdef RLIMIT_OFILE
 | 
						|
		limit(RLIMIT_OFILE, limito);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"NOFILE");
 | 
						|
#endif
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_p) {
 | 
						|
#ifdef RLIMIT_NPROC
 | 
						|
		limit(RLIMIT_NPROC, limitp);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"NPROC");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_f) {
 | 
						|
#ifdef RLIMIT_FSIZE
 | 
						|
		limit(RLIMIT_FSIZE, limitf);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"FSIZE");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_c) {
 | 
						|
#ifdef RLIMIT_CORE
 | 
						|
		limit(RLIMIT_CORE, limitc);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"CORE");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_r) {
 | 
						|
#ifdef RLIMIT_RSS
 | 
						|
		limit(RLIMIT_RSS, limitr);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"RSS");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
	if (opt & OPT_t) {
 | 
						|
#ifdef RLIMIT_CPU
 | 
						|
		limit(RLIMIT_CPU, limitt);
 | 
						|
#else
 | 
						|
		if (opt & OPT_v)
 | 
						|
			bb_error_msg("system does not support RLIMIT_%s",
 | 
						|
				"CPU");
 | 
						|
#endif
 | 
						|
	}
 | 
						|
 | 
						|
	if (opt & OPT_P)
 | 
						|
		setsid();
 | 
						|
 | 
						|
	if (opt & OPT_e)
 | 
						|
		edir(env_dir);
 | 
						|
 | 
						|
	if (opt & (OPT_u|OPT_U))
 | 
						|
		xget_uidgid(&ugid, set_user);
 | 
						|
 | 
						|
	// chrooted jail must have /etc/passwd if we move this after chroot.
 | 
						|
	// OTOH chroot fails for non-roots.
 | 
						|
	// Solution: cache uid/gid before chroot, apply uid/gid after.
 | 
						|
	if (opt & OPT_U) {
 | 
						|
		xsetenv("GID", utoa(ugid.gid));
 | 
						|
		xsetenv("UID", utoa(ugid.uid));
 | 
						|
	}
 | 
						|
 | 
						|
	if (opt & OPT_root) {
 | 
						|
		xchroot(root);
 | 
						|
	}
 | 
						|
 | 
						|
	if (opt & OPT_u) {
 | 
						|
		if (setgroups(1, &ugid.gid) == -1)
 | 
						|
			bb_perror_msg_and_die("setgroups");
 | 
						|
		xsetgid(ugid.gid);
 | 
						|
		xsetuid(ugid.uid);
 | 
						|
	}
 | 
						|
 | 
						|
	if (opt & OPT_n) {
 | 
						|
		errno = 0;
 | 
						|
		if (nice(xatoi(nicestr)) == -1)
 | 
						|
			bb_perror_msg_and_die("nice");
 | 
						|
	}
 | 
						|
 | 
						|
	if (opt & OPT_0)
 | 
						|
		close(STDIN_FILENO);
 | 
						|
	if (opt & OPT_1)
 | 
						|
		close(STDOUT_FILENO);
 | 
						|
	if (opt & OPT_2)
 | 
						|
		close(STDERR_FILENO);
 | 
						|
 | 
						|
	BB_EXECVP_or_die(argv);
 | 
						|
}
 |