24d1d6e2c81b24129dd4abf976d33864ea0d7c06
[oweals/busybox.git] / runit / chpst.c
1 /*
2 Copyright (c) 2001-2006, Gerrit Pape
3 All rights reserved.
4
5 Redistribution and use in source and binary forms, with or without
6 modification, 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
16 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
17 WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
18 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
19 EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
20 SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
21 PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
22 OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
23 WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
24 OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
25 ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27
28 /* Busyboxed by Denys Vlasenko <vda.linux@googlemail.com> */
29 /* Dependencies on runit_lib.c removed */
30
31 #include "libbb.h"
32 #include <dirent.h>
33
34 /*
35 Five applets here: chpst, envdir, envuidgid, setuidgid, softlimit.
36
37 Only softlimit and chpst are taking options:
38
39 # common
40 -o N            Limit number of open files per process
41 -p N            Limit number of processes per uid
42 -m BYTES        Same as -d BYTES -s BYTES -l BYTES [-a BYTES]
43 -d BYTES        Limit data segment
44 -f BYTES        Limit output file sizes
45 -c BYTES        Limit core file size
46 # softlimit
47 -a BYTES        Limit total size of all segments
48 -s BYTES        Limit stack segment
49 -l BYTES        Limit locked memory size
50 -r BYTES        Limit resident set size
51 -t N            Limit CPU time
52 # chpst
53 -u USER[:GRP]   Set uid and gid
54 -U USER[:GRP]   Set $UID and $GID in environment
55 -e DIR          Set environment variables as specified by files in DIR
56 -/ DIR          Chroot to DIR
57 -n NICE         Add NICE to nice value
58 -v              Verbose
59 -P              Create new process group
60 -0 -1 -2        Close fd 0,1,2
61
62 Even though we accept all these options for both softlimit and chpst,
63 they are not to be advertised on their help texts.
64 We have enough problems with feature creep in other people's
65 software, don't want to add our own.
66
67 envdir, envuidgid, setuidgid take no options, but they reuse code which
68 handles -e, -U and -u.
69 */
70
71 enum {
72         OPT_a = (1 << 0) * ENABLE_SOFTLIMIT,
73         OPT_c = (1 << 1) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
74         OPT_d = (1 << 2) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
75         OPT_f = (1 << 3) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
76         OPT_l = (1 << 4) * ENABLE_SOFTLIMIT,
77         OPT_m = (1 << 5) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
78         OPT_o = (1 << 6) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
79         OPT_p = (1 << 7) * (ENABLE_SOFTLIMIT || ENABLE_CHPST),
80         OPT_r = (1 << 8) * ENABLE_SOFTLIMIT,
81         OPT_s = (1 << 9) * ENABLE_SOFTLIMIT,
82         OPT_t = (1 << 10) * ENABLE_SOFTLIMIT,
83         OPT_u = (1 << 11) * (ENABLE_CHPST || ENABLE_SETUIDGID),
84         OPT_U = (1 << 12) * (ENABLE_CHPST || ENABLE_ENVUIDGID),
85         OPT_e = (1 << 13) * (ENABLE_CHPST || ENABLE_ENVDIR),
86         OPT_root = (1 << 14) * ENABLE_CHPST,
87         OPT_n = (1 << 15) * ENABLE_CHPST,
88         OPT_v = (1 << 16) * ENABLE_CHPST,
89         OPT_P = (1 << 17) * ENABLE_CHPST,
90         OPT_0 = (1 << 18) * ENABLE_CHPST,
91         OPT_1 = (1 << 19) * ENABLE_CHPST,
92         OPT_2 = (1 << 20) * ENABLE_CHPST,
93 };
94
95 static void edir(const char *directory_name)
96 {
97         int wdir;
98         DIR *dir;
99         struct dirent *d;
100         int fd;
101
102         wdir = xopen(".", O_RDONLY | O_NDELAY);
103         xchdir(directory_name);
104         dir = opendir(".");
105         if (!dir)
106                 bb_perror_msg_and_die("opendir %s", directory_name);
107         for (;;) {
108                 char buf[256];
109                 char *tail;
110                 int size;
111
112                 errno = 0;
113                 d = readdir(dir);
114                 if (!d) {
115                         if (errno)
116                                 bb_perror_msg_and_die("readdir %s",
117                                                 directory_name);
118                         break;
119                 }
120                 if (d->d_name[0] == '.')
121                         continue;
122                 fd = open(d->d_name, O_RDONLY | O_NDELAY);
123                 if (fd < 0) {
124                         if ((errno == EISDIR) && directory_name) {
125                                 if (option_mask32 & OPT_v)
126                                         bb_perror_msg("warning: %s/%s is a directory",
127                                                 directory_name, d->d_name);
128                                 continue;
129                         } else
130                                 bb_perror_msg_and_die("open %s/%s",
131                                                 directory_name, d->d_name);
132                 }
133                 size = full_read(fd, buf, sizeof(buf)-1);
134                 close(fd);
135                 if (size < 0)
136                         bb_perror_msg_and_die("read %s/%s",
137                                         directory_name, d->d_name);
138                 if (size == 0) {
139                         unsetenv(d->d_name);
140                         continue;
141                 }
142                 buf[size] = '\n';
143                 tail = strchr(buf, '\n');
144                 /* skip trailing whitespace */
145                 while (1) {
146                         *tail = '\0';
147                         tail--;
148                         if (tail < buf || !isspace(*tail))
149                                 break;
150                 }
151                 xsetenv(d->d_name, buf);
152         }
153         closedir(dir);
154         if (fchdir(wdir) == -1)
155                 bb_perror_msg_and_die("fchdir");
156         close(wdir);
157 }
158
159 static void limit(int what, long l)
160 {
161         struct rlimit r;
162
163         /* Never fails under Linux (except if you pass it bad arguments) */
164         getrlimit(what, &r);
165         if ((l < 0) || (l > r.rlim_max))
166                 r.rlim_cur = r.rlim_max;
167         else
168                 r.rlim_cur = l;
169         if (setrlimit(what, &r) == -1)
170                 bb_perror_msg_and_die("setrlimit");
171 }
172
173 int chpst_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
174 int chpst_main(int argc UNUSED_PARAM, char **argv)
175 {
176         struct bb_uidgid_t ugid;
177         char *set_user;
178         char *env_user;
179         char *env_dir;
180         char *root;
181         char *nicestr;
182         unsigned limita;
183         unsigned limitc;
184         unsigned limitd;
185         unsigned limitf;
186         unsigned limitl;
187         unsigned limitm;
188         unsigned limito;
189         unsigned limitp;
190         unsigned limitr;
191         unsigned limits;
192         unsigned limitt;
193         unsigned opt;
194
195         if ((ENABLE_CHPST && applet_name[0] == 'c')
196          || (ENABLE_SOFTLIMIT && applet_name[1] == 'o')
197         ) {
198                 // FIXME: can we live with int-sized limits?
199                 // can we live with 40000 days?
200                 // if yes -> getopt converts strings to numbers for us
201                 opt_complementary = "-1:a+:c+:d+:f+:l+:m+:o+:p+:r+:s+:t+";
202                 opt = getopt32(argv, "+a:c:d:f:l:m:o:p:r:s:t:u:U:e:"
203                         USE_CHPST("/:n:vP012"),
204                         &limita, &limitc, &limitd, &limitf, &limitl,
205                         &limitm, &limito, &limitp, &limitr, &limits, &limitt,
206                         &set_user, &env_user, &env_dir
207                         USE_CHPST(, &root, &nicestr));
208                 argv += optind;
209                 if (opt & OPT_m) { // -m means -asld
210                         limita = limits = limitl = limitd = limitm;
211                         opt |= (OPT_s | OPT_l | OPT_a | OPT_d);
212                 }
213         } else {
214                 option_mask32 = opt = 0;
215                 argv++;
216         }
217
218         // envdir?
219         if (ENABLE_ENVDIR && applet_name[3] == 'd') {
220                 env_dir = *argv++;
221                 opt |= OPT_e;
222         }
223
224         // setuidgid?
225         if (ENABLE_SETUIDGID && applet_name[0] == 's') {
226                 set_user = *argv++;
227                 opt |= OPT_u;
228         }
229
230         // envuidgid?
231         if (ENABLE_ENVUIDGID && applet_name[0] == 'e') {
232                 env_user = *argv++;
233                 opt |= OPT_U;
234         }
235
236         // we must have PROG [ARGS]
237         if (!*argv)
238                 bb_show_usage();
239
240         // set limits
241         if (opt & OPT_d) {
242 #ifdef RLIMIT_DATA
243                 limit(RLIMIT_DATA, limitd);
244 #else
245                 if (opt & OPT_v)
246                         bb_error_msg("system does not support RLIMIT_%s",
247                                 "DATA");
248 #endif
249         }
250         if (opt & OPT_s) {
251 #ifdef RLIMIT_STACK
252                 limit(RLIMIT_STACK, limits);
253 #else
254                 if (opt & OPT_v)
255                         bb_error_msg("system does not support RLIMIT_%s",
256                                 "STACK");
257 #endif
258         }
259         if (opt & OPT_l) {
260 #ifdef RLIMIT_MEMLOCK
261                 limit(RLIMIT_MEMLOCK, limitl);
262 #else
263                 if (opt & OPT_v)
264                         bb_error_msg("system does not support RLIMIT_%s",
265                                 "MEMLOCK");
266 #endif
267         }
268         if (opt & OPT_a) {
269 #ifdef RLIMIT_VMEM
270                 limit(RLIMIT_VMEM, limita);
271 #else
272 #ifdef RLIMIT_AS
273                 limit(RLIMIT_AS, limita);
274 #else
275                 if (opt & OPT_v)
276                         bb_error_msg("system does not support RLIMIT_%s",
277                                 "VMEM");
278 #endif
279 #endif
280         }
281         if (opt & OPT_o) {
282 #ifdef RLIMIT_NOFILE
283                 limit(RLIMIT_NOFILE, limito);
284 #else
285 #ifdef RLIMIT_OFILE
286                 limit(RLIMIT_OFILE, limito);
287 #else
288                 if (opt & OPT_v)
289                         bb_error_msg("system does not support RLIMIT_%s",
290                                 "NOFILE");
291 #endif
292 #endif
293         }
294         if (opt & OPT_p) {
295 #ifdef RLIMIT_NPROC
296                 limit(RLIMIT_NPROC, limitp);
297 #else
298                 if (opt & OPT_v)
299                         bb_error_msg("system does not support RLIMIT_%s",
300                                 "NPROC");
301 #endif
302         }
303         if (opt & OPT_f) {
304 #ifdef RLIMIT_FSIZE
305                 limit(RLIMIT_FSIZE, limitf);
306 #else
307                 if (opt & OPT_v)
308                         bb_error_msg("system does not support RLIMIT_%s",
309                                 "FSIZE");
310 #endif
311         }
312         if (opt & OPT_c) {
313 #ifdef RLIMIT_CORE
314                 limit(RLIMIT_CORE, limitc);
315 #else
316                 if (opt & OPT_v)
317                         bb_error_msg("system does not support RLIMIT_%s",
318                                 "CORE");
319 #endif
320         }
321         if (opt & OPT_r) {
322 #ifdef RLIMIT_RSS
323                 limit(RLIMIT_RSS, limitr);
324 #else
325                 if (opt & OPT_v)
326                         bb_error_msg("system does not support RLIMIT_%s",
327                                 "RSS");
328 #endif
329         }
330         if (opt & OPT_t) {
331 #ifdef RLIMIT_CPU
332                 limit(RLIMIT_CPU, limitt);
333 #else
334                 if (opt & OPT_v)
335                         bb_error_msg("system does not support RLIMIT_%s",
336                                 "CPU");
337 #endif
338         }
339
340         if (opt & OPT_P)
341                 setsid();
342
343         if (opt & OPT_e)
344                 edir(env_dir);
345
346         // FIXME: chrooted jail must have /etc/passwd if we move this after chroot!
347         // OTOH chroot fails for non-roots!
348         // SOLUTION: cache uid/gid before chroot, apply uid/gid after
349         if (opt & OPT_U) {
350                 xget_uidgid(&ugid, env_user);
351                 xsetenv("GID", utoa(ugid.gid));
352                 xsetenv("UID", utoa(ugid.uid));
353         }
354
355         if (opt & OPT_u) {
356                 xget_uidgid(&ugid, set_user);
357         }
358
359         if (opt & OPT_root) {
360                 xchdir(root);
361                 xchroot(".");
362         }
363
364         if (opt & OPT_u) {
365                 if (setgroups(1, &ugid.gid) == -1)
366                         bb_perror_msg_and_die("setgroups");
367                 xsetgid(ugid.gid);
368                 xsetuid(ugid.uid);
369         }
370
371         if (opt & OPT_n) {
372                 errno = 0;
373                 if (nice(xatoi(nicestr)) == -1)
374                         bb_perror_msg_and_die("nice");
375         }
376
377         if (opt & OPT_0)
378                 close(STDIN_FILENO);
379         if (opt & OPT_1)
380                 close(STDOUT_FILENO);
381         if (opt & OPT_2)
382                 close(STDERR_FILENO);
383
384         BB_EXECVP(argv[0], argv);
385         bb_perror_msg_and_die("exec %s", argv[0]);
386 }