busybox/runit/chpst.c
<<
>>
Prefs
   1/*
   2Copyright (c) 2001-2006, Gerrit Pape
   3All rights reserved.
   4
   5Redistribution and use in source and binary forms, with or without
   6modification, are permitted provided that the following conditions are met:
   7
   8   1. Redistributions of source code must retain the above copyright notice,
   9      this list of conditions and the following disclaimer.
  10   2. Redistributions in binary form must reproduce the above copyright
  11      notice, this list of conditions and the following disclaimer in the
  12      documentation and/or other materials provided with the distribution.
  13   3. The name of the author may not be used to endorse or promote products
  14      derived from this software without specific prior written permission.
  15
  16THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS OR IMPLIED
  17WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  18MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
  19EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  20SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
  21PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
  22OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
  23WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
  24OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
  25ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  26*/
  27
  28/* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
  29
  30//config:config CHPST
  31//config:       bool "chpst (9 kb)"
  32//config:       default y
  33//config:       help
  34//config:       chpst changes the process state according to the given options, and
  35//config:       execs specified program.
  36//config:
  37//config:config SETUIDGID
  38//config:       bool "setuidgid (4 kb)"
  39//config:       default y
  40//config:       help
  41//config:       Sets soft resource limits as specified by options
  42//config:
  43//config:config ENVUIDGID
  44//config:       bool "envuidgid (3.9 kb)"
  45//config:       default y
  46//config:       help
  47//config:       Sets $UID to account's uid and $GID to account's gid
  48//config:
  49//config:config ENVDIR
  50//config:       bool "envdir (2.5 kb)"
  51//config:       default y
  52//config:       help
  53//config:       Sets various environment variables as specified by files
  54//config:       in the given directory
  55//config:
  56//config:config SOFTLIMIT
  57//config:       bool "softlimit (4.5 kb)"
  58//config:       default y
  59//config:       help
  60//config:       Sets soft resource limits as specified by options
  61
  62//applet:IF_CHPST(    APPLET_NOEXEC(chpst,     chpst, BB_DIR_USR_BIN, BB_SUID_DROP, chpst))
  63//                    APPLET_NOEXEC:name       main   location        suid_type     help
  64//applet:IF_ENVDIR(   APPLET_NOEXEC(envdir,    chpst, BB_DIR_USR_BIN, BB_SUID_DROP, envdir))
  65//applet:IF_ENVUIDGID(APPLET_NOEXEC(envuidgid, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, envuidgid))
  66//applet:IF_SETUIDGID(APPLET_NOEXEC(setuidgid, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, setuidgid))
  67//applet:IF_SOFTLIMIT(APPLET_NOEXEC(softlimit, chpst, BB_DIR_USR_BIN, BB_SUID_DROP, softlimit))
  68
  69//kbuild:lib-$(CONFIG_CHPST) += chpst.o
  70//kbuild:lib-$(CONFIG_ENVDIR) += chpst.o
  71//kbuild:lib-$(CONFIG_ENVUIDGID) += chpst.o
  72//kbuild:lib-$(CONFIG_SETUIDGID) += chpst.o
  73//kbuild:lib-$(CONFIG_SOFTLIMIT) += chpst.o
  74
  75//usage:#define chpst_trivial_usage
  76//usage:       "[-vP012] [-u USER[:GRP]] [-U USER[:GRP]] [-e DIR]\n"
  77//usage:       "        [-/ DIR] [-n NICE] [-m BYTES] [-d BYTES] [-o N]\n"
  78//usage:       "        [-p N] [-f BYTES] [-c BYTES] PROG ARGS"
  79//usage:#define chpst_full_usage "\n\n"
  80//usage:       "Change the process state, run PROG\n"
  81//usage:     "\n        -u USER[:GRP]   Set uid and gid"
  82//usage:     "\n        -U USER[:GRP]   Set $UID and $GID in environment"
  83//usage:     "\n        -e DIR          Set environment variables as specified by files"
  84//usage:     "\n                        in DIR: file=1st_line_of_file"
  85//usage:     "\n        -/ DIR          Chroot to DIR"
  86//usage:     "\n        -n NICE         Add NICE to nice value"
  87//usage:     "\n        -m BYTES        Same as -d BYTES -s BYTES -l BYTES"
  88//usage:     "\n        -d BYTES        Limit data segment"
  89//usage:     "\n        -o N            Limit number of open files per process"
  90//usage:     "\n        -p N            Limit number of processes per uid"
  91//usage:     "\n        -f BYTES        Limit output file sizes"
  92//usage:     "\n        -c BYTES        Limit core file size"
  93//usage:     "\n        -v              Verbose"
  94//usage:     "\n        -P              Create new process group"
  95//usage:     "\n        -0              Close stdin"
  96//usage:     "\n        -1              Close stdout"
  97//usage:     "\n        -2              Close stderr"
  98//usage:
  99//usage:#define envdir_trivial_usage
 100//usage:       "DIR PROG ARGS"
 101//usage:#define envdir_full_usage "\n\n"
 102//usage:       "Set various environment variables as specified by files\n"
 103//usage:       "in the directory DIR, run PROG"
 104//usage:
 105//usage:#define envuidgid_trivial_usage
 106//usage:       "USER PROG ARGS"
 107//usage:#define envuidgid_full_usage "\n\n"
 108//usage:       "Set $UID to USER's uid and $GID to USER's gid, run PROG"
 109//usage:
 110//usage:#define setuidgid_trivial_usage
 111//usage:       "USER PROG ARGS"
 112//usage:#define setuidgid_full_usage "\n\n"
 113//usage:       "Set uid and gid to USER's uid and gid, drop supplementary group ids,\n"
 114//usage:       "run PROG"
 115//usage:
 116//usage:#define softlimit_trivial_usage
 117//usage:       "[-a BYTES] [-m BYTES] [-d BYTES] [-s BYTES] [-l BYTES]\n"
 118//usage:       "        [-f BYTES] [-c BYTES] [-r BYTES] [-o N] [-p N] [-t N]\n"
 119//usage:       "        PROG ARGS"
 120//usage:#define softlimit_full_usage "\n\n"
 121//usage:       "Set soft resource limits, then run PROG\n"
 122//usage:     "\n        -a BYTES        Limit total size of all segments"
 123//usage:     "\n        -m BYTES        Same as -d BYTES -s BYTES -l BYTES -a BYTES"
 124//usage:     "\n        -d BYTES        Limit data segment"
 125//usage:     "\n        -s BYTES        Limit stack segment"
 126//usage:     "\n        -l BYTES        Limit locked memory size"
 127//usage:     "\n        -o N            Limit number of open files per process"
 128//usage:     "\n        -p N            Limit number of processes per uid"
 129//usage:     "\nOptions controlling file sizes:"
 130//usage:     "\n        -f BYTES        Limit output file sizes"
 131//usage:     "\n        -c BYTES        Limit core file size"
 132//usage:     "\nEfficiency opts:"
 133//usage:     "\n        -r BYTES        Limit resident set size"
 134//usage:     "\n        -t N            Limit CPU time, process receives"
 135//usage:     "\n                        a SIGXCPU after N seconds"
 136
 137#include "libbb.h"
 138
 139/*
 140Five applets here: chpst, envdir, envuidgid, setuidgid, softlimit.
 141
 142Only softlimit and chpst are taking options:
 143
 144# common
 145-o N            Limit number of open files per process
 146-p N            Limit number of processes per uid
 147-m BYTES        Same as -d BYTES -s BYTES -l BYTES [-a BYTES]
 148-d BYTES        Limit data segment
 149-f BYTES        Limit output file sizes
 150-c BYTES        Limit core file size
 151# softlimit
 152-a BYTES        Limit total size of all segments
 153-s BYTES        Limit stack segment
 154-l BYTES        Limit locked memory size
 155-r BYTES        Limit resident set size
 156-t N            Limit CPU time
 157# chpst
 158-u USER[:GRP]   Set uid and gid
 159-U USER[:GRP]   Set $UID and $GID in environment
 160-e DIR          Set environment variables as specified by files in DIR
 161-/ DIR          Chroot to DIR
 162-n NICE         Add NICE to nice value
 163-v              Verbose
 164-P              Create new process group
 165-0 -1 -2        Close fd 0,1,2
 166
 167Even though we accept all these options for both softlimit and chpst,
 168they are not to be advertised on their help texts.
 169We have enough problems with feature creep in other people's
 170software, don't want to add our own.
 171
 172envdir, envuidgid, setuidgid take no options, but they reuse code which
 173handles -e, -U and -u.
 174*/
 175
 176enum {
 177        OPT_a = (1 << 0) * ENABLE_SOFTLIMIT,
 178        OPT_c = (1 << 1) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 179        OPT_d = (1 << 2) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 180        OPT_f = (1 << 3) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 181        OPT_l = (1 << 4) * ENABLE_SOFTLIMIT,
 182        OPT_m = (1 << 5) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 183        OPT_o = (1 << 6) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 184        OPT_p = (1 << 7) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
 185        OPT_r = (1 << 8) * ENABLE_SOFTLIMIT,
 186        OPT_s = (1 << 9) * ENABLE_SOFTLIMIT,
 187        OPT_t = (1 << 10) * ENABLE_SOFTLIMIT,
 188        OPT_u = (1 << 11) * (ENABLE_CHPST || ENABLE_SETUIDGID),
 189        OPT_U = (1 << 12) * (ENABLE_CHPST || ENABLE_ENVUIDGID),
 190        OPT_e = (1 << 13) * (ENABLE_CHPST || ENABLE_ENVDIR),
 191        OPT_root = (1 << 14) * ENABLE_CHPST,
 192        OPT_n = (1 << 15) * ENABLE_CHPST,
 193        OPT_v = (1 << 16) * ENABLE_CHPST,
 194        OPT_P = (1 << 17) * ENABLE_CHPST,
 195        OPT_0 = (1 << 18) * ENABLE_CHPST,
 196        OPT_1 = (1 << 19) * ENABLE_CHPST,
 197        OPT_2 = (1 << 20) * ENABLE_CHPST,
 198};
 199
 200/* TODO: use recursive_action? */
 201static NOINLINE void edir(const char *directory_name)
 202{
 203        int wdir;
 204        DIR *dir;
 205        struct dirent *d;
 206        int fd;
 207
 208        wdir = xopen(".", O_RDONLY | O_NDELAY);
 209        xchdir(directory_name);
 210        dir = xopendir(".");
 211        for (;;) {
 212                char buf[256];
 213                char *tail;
 214                int size;
 215
 216                errno = 0;
 217                d = readdir(dir);
 218                if (!d) {
 219                        if (errno)
 220                                bb_perror_msg_and_die("readdir %s",
 221                                                directory_name);
 222                        break;
 223                }
 224                if (d->d_name[0] == '.')
 225                        continue;
 226                fd = open(d->d_name, O_RDONLY | O_NDELAY);
 227                if (fd < 0) {
 228                        if ((errno == EISDIR) && directory_name) {
 229                                if (option_mask32 & OPT_v)
 230                                        bb_perror_msg("warning: %s/%s is a directory",
 231                                                directory_name, d->d_name);
 232                                continue;
 233                        }
 234                        bb_perror_msg_and_die("open %s/%s",
 235                                                directory_name, d->d_name);
 236                }
 237                size = full_read(fd, buf, sizeof(buf)-1);
 238                close(fd);
 239                if (size < 0)
 240                        bb_perror_msg_and_die("read %s/%s",
 241                                        directory_name, d->d_name);
 242                if (size == 0) {
 243                        unsetenv(d->d_name);
 244                        continue;
 245                }
 246                buf[size] = '\n';
 247                tail = strchr(buf, '\n');
 248                /* skip trailing whitespace */
 249                while (1) {
 250                        *tail = '\0';
 251                        tail--;
 252                        if (tail < buf || !isspace(*tail))
 253                                break;
 254                }
 255                xsetenv(d->d_name, buf);
 256        }
 257        closedir(dir);
 258        xfchdir(wdir);
 259        close(wdir);
 260}
 261
 262static void limit(int what, long l)
 263{
 264        struct rlimit r;
 265
 266        /* Never fails under Linux (except if you pass it bad arguments) */
 267        getrlimit(what, &r);
 268        if ((l < 0) || (l > r.rlim_max))
 269                r.rlim_cur = r.rlim_max;
 270        else
 271                r.rlim_cur = l;
 272        if (setrlimit(what, &r) == -1)
 273                bb_perror_msg_and_die("setrlimit");
 274}
 275
 276int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 277int chpst_main(int argc UNUSED_PARAM, char **argv)
 278{
 279        struct bb_uidgid_t ugid;
 280        char *set_user = set_user; /* for compiler */
 281        char *env_dir = env_dir;
 282        char *root;
 283        char *nicestr;
 284        unsigned limita;
 285        unsigned limitc;
 286        unsigned limitd;
 287        unsigned limitf;
 288        unsigned limitl;
 289        unsigned limitm;
 290        unsigned limito;
 291        unsigned limitp;
 292        unsigned limitr;
 293        unsigned limits;
 294        unsigned limitt;
 295        unsigned opt;
 296
 297        if ((ENABLE_CHPST && applet_name[0] == 'c')
 298         || (ENABLE_SOFTLIMIT && applet_name[1] == 'o')
 299        ) {
 300                // FIXME: can we live with int-sized limits?
 301                // can we live with 40000 days?
 302                // if yes -> getopt converts strings to numbers for us
 303                opt = getopt32(argv, "^+"
 304                        "a:+c:+d:+f:+l:+m:+o:+p:+r:+s:+t:+u:U:e:"
 305                        IF_CHPST("/:n:vP012")
 306                        "\0" "-1",
 307                        &limita, &limitc, &limitd, &limitf, &limitl,
 308                        &limitm, &limito, &limitp, &limitr, &limits, &limitt,
 309                        &set_user, &set_user, &env_dir
 310                        IF_CHPST(, &root, &nicestr));
 311                argv += optind;
 312                if (opt & OPT_m) { // -m means -asld
 313                        limita = limits = limitl = limitd = limitm;
 314                        opt |= (OPT_s | OPT_l | OPT_a | OPT_d);
 315                }
 316        } else {
 317                option_mask32 = opt = 0;
 318                argv++;
 319                if (!*argv)
 320                        bb_show_usage();
 321        }
 322
 323        // envdir?
 324        if (ENABLE_ENVDIR && applet_name[3] == 'd') {
 325                env_dir = *argv++;
 326                opt |= OPT_e;
 327        }
 328
 329        // setuidgid?
 330        if (ENABLE_SETUIDGID && applet_name[1] == 'e') {
 331                set_user = *argv++;
 332                opt |= OPT_u;
 333        }
 334
 335        // envuidgid?
 336        if (ENABLE_ENVUIDGID && applet_name[0] == 'e' && applet_name[3] == 'u') {
 337                set_user = *argv++;
 338                opt |= OPT_U;
 339        }
 340
 341        // we must have PROG [ARGS]
 342        if (!*argv)
 343                bb_show_usage();
 344
 345        // set limits
 346        if (opt & OPT_d) {
 347#ifdef RLIMIT_DATA
 348                limit(RLIMIT_DATA, limitd);
 349#else
 350                if (opt & OPT_v)
 351                        bb_error_msg("system does not support RLIMIT_%s",
 352                                "DATA");
 353#endif
 354        }
 355        if (opt & OPT_s) {
 356#ifdef RLIMIT_STACK
 357                limit(RLIMIT_STACK, limits);
 358#else
 359                if (opt & OPT_v)
 360                        bb_error_msg("system does not support RLIMIT_%s",
 361                                "STACK");
 362#endif
 363        }
 364        if (opt & OPT_l) {
 365#ifdef RLIMIT_MEMLOCK
 366                limit(RLIMIT_MEMLOCK, limitl);
 367#else
 368                if (opt & OPT_v)
 369                        bb_error_msg("system does not support RLIMIT_%s",
 370                                "MEMLOCK");
 371#endif
 372        }
 373        if (opt & OPT_a) {
 374#ifdef RLIMIT_VMEM
 375                limit(RLIMIT_VMEM, limita);
 376#else
 377#ifdef RLIMIT_AS
 378                limit(RLIMIT_AS, limita);
 379#else
 380                if (opt & OPT_v)
 381                        bb_error_msg("system does not support RLIMIT_%s",
 382                                "VMEM");
 383#endif
 384#endif
 385        }
 386        if (opt & OPT_o) {
 387#ifdef RLIMIT_NOFILE
 388                limit(RLIMIT_NOFILE, limito);
 389#else
 390#ifdef RLIMIT_OFILE
 391                limit(RLIMIT_OFILE, limito);
 392#else
 393                if (opt & OPT_v)
 394                        bb_error_msg("system does not support RLIMIT_%s",
 395                                "NOFILE");
 396#endif
 397#endif
 398        }
 399        if (opt & OPT_p) {
 400#ifdef RLIMIT_NPROC
 401                limit(RLIMIT_NPROC, limitp);
 402#else
 403                if (opt & OPT_v)
 404                        bb_error_msg("system does not support RLIMIT_%s",
 405                                "NPROC");
 406#endif
 407        }
 408        if (opt & OPT_f) {
 409#ifdef RLIMIT_FSIZE
 410                limit(RLIMIT_FSIZE, limitf);
 411#else
 412                if (opt & OPT_v)
 413                        bb_error_msg("system does not support RLIMIT_%s",
 414                                "FSIZE");
 415#endif
 416        }
 417        if (opt & OPT_c) {
 418#ifdef RLIMIT_CORE
 419                limit(RLIMIT_CORE, limitc);
 420#else
 421                if (opt & OPT_v)
 422                        bb_error_msg("system does not support RLIMIT_%s",
 423                                "CORE");
 424#endif
 425        }
 426        if (opt & OPT_r) {
 427#ifdef RLIMIT_RSS
 428                limit(RLIMIT_RSS, limitr);
 429#else
 430                if (opt & OPT_v)
 431                        bb_error_msg("system does not support RLIMIT_%s",
 432                                "RSS");
 433#endif
 434        }
 435        if (opt & OPT_t) {
 436#ifdef RLIMIT_CPU
 437                limit(RLIMIT_CPU, limitt);
 438#else
 439                if (opt & OPT_v)
 440                        bb_error_msg("system does not support RLIMIT_%s",
 441                                "CPU");
 442#endif
 443        }
 444
 445        if (opt & OPT_P)
 446                setsid();
 447
 448        if (opt & OPT_e)
 449                edir(env_dir);
 450
 451        if (opt & (OPT_u|OPT_U))
 452                xget_uidgid(&ugid, set_user);
 453
 454        // chrooted jail must have /etc/passwd if we move this after chroot.
 455        // OTOH chroot fails for non-roots.
 456        // Solution: cache uid/gid before chroot, apply uid/gid after.
 457        if (opt & OPT_U) {
 458                xsetenv("GID", utoa(ugid.gid));
 459                xsetenv("UID", utoa(ugid.uid));
 460        }
 461
 462        if (opt & OPT_root) {
 463                xchroot(root);
 464        }
 465
 466        /* nice should be done before xsetuid */
 467        if (opt & OPT_n) {
 468                errno = 0;
 469                if (nice(xatoi(nicestr)) == -1)
 470                        bb_perror_msg_and_die("nice");
 471        }
 472
 473        if (opt & OPT_u) {
 474                if (setgroups(1, &ugid.gid) == -1)
 475                        bb_perror_msg_and_die("setgroups");
 476                xsetgid(ugid.gid);
 477                xsetuid(ugid.uid);
 478        }
 479
 480        if (opt & OPT_0)
 481                close(STDIN_FILENO);
 482        if (opt & OPT_1)
 483                close(STDOUT_FILENO);
 484        if (opt & OPT_2)
 485                close(STDERR_FILENO);
 486
 487        BB_EXECVP_or_die(argv);
 488}
 489