*: a bit of code shrink
[platform/upstream/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 = set_user; /* for compiler */
178         char *env_user = env_user;
179         char *env_dir = 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                 if (!*argv)
217                         bb_show_usage();
218         }
219
220         // envdir?
221         if (ENABLE_ENVDIR && applet_name[3] == 'd') {
222                 env_dir = *argv++;
223                 opt |= OPT_e;
224         }
225
226         // setuidgid?
227         if (ENABLE_SETUIDGID && applet_name[1] == 'e') {
228                 set_user = *argv++;
229                 opt |= OPT_u;
230         }
231
232         // envuidgid?
233         if (ENABLE_ENVUIDGID && applet_name[0] == 'e' && applet_name[3] == 'u') {
234                 env_user = *argv++;
235                 opt |= OPT_U;
236         }
237
238         // we must have PROG [ARGS]
239         if (!*argv)
240                 bb_show_usage();
241
242         // set limits
243         if (opt & OPT_d) {
244 #ifdef RLIMIT_DATA
245                 limit(RLIMIT_DATA, limitd);
246 #else
247                 if (opt & OPT_v)
248                         bb_error_msg("system does not support RLIMIT_%s",
249                                 "DATA");
250 #endif
251         }
252         if (opt & OPT_s) {
253 #ifdef RLIMIT_STACK
254                 limit(RLIMIT_STACK, limits);
255 #else
256                 if (opt & OPT_v)
257                         bb_error_msg("system does not support RLIMIT_%s",
258                                 "STACK");
259 #endif
260         }
261         if (opt & OPT_l) {
262 #ifdef RLIMIT_MEMLOCK
263                 limit(RLIMIT_MEMLOCK, limitl);
264 #else
265                 if (opt & OPT_v)
266                         bb_error_msg("system does not support RLIMIT_%s",
267                                 "MEMLOCK");
268 #endif
269         }
270         if (opt & OPT_a) {
271 #ifdef RLIMIT_VMEM
272                 limit(RLIMIT_VMEM, limita);
273 #else
274 #ifdef RLIMIT_AS
275                 limit(RLIMIT_AS, limita);
276 #else
277                 if (opt & OPT_v)
278                         bb_error_msg("system does not support RLIMIT_%s",
279                                 "VMEM");
280 #endif
281 #endif
282         }
283         if (opt & OPT_o) {
284 #ifdef RLIMIT_NOFILE
285                 limit(RLIMIT_NOFILE, limito);
286 #else
287 #ifdef RLIMIT_OFILE
288                 limit(RLIMIT_OFILE, limito);
289 #else
290                 if (opt & OPT_v)
291                         bb_error_msg("system does not support RLIMIT_%s",
292                                 "NOFILE");
293 #endif
294 #endif
295         }
296         if (opt & OPT_p) {
297 #ifdef RLIMIT_NPROC
298                 limit(RLIMIT_NPROC, limitp);
299 #else
300                 if (opt & OPT_v)
301                         bb_error_msg("system does not support RLIMIT_%s",
302                                 "NPROC");
303 #endif
304         }
305         if (opt & OPT_f) {
306 #ifdef RLIMIT_FSIZE
307                 limit(RLIMIT_FSIZE, limitf);
308 #else
309                 if (opt & OPT_v)
310                         bb_error_msg("system does not support RLIMIT_%s",
311                                 "FSIZE");
312 #endif
313         }
314         if (opt & OPT_c) {
315 #ifdef RLIMIT_CORE
316                 limit(RLIMIT_CORE, limitc);
317 #else
318                 if (opt & OPT_v)
319                         bb_error_msg("system does not support RLIMIT_%s",
320                                 "CORE");
321 #endif
322         }
323         if (opt & OPT_r) {
324 #ifdef RLIMIT_RSS
325                 limit(RLIMIT_RSS, limitr);
326 #else
327                 if (opt & OPT_v)
328                         bb_error_msg("system does not support RLIMIT_%s",
329                                 "RSS");
330 #endif
331         }
332         if (opt & OPT_t) {
333 #ifdef RLIMIT_CPU
334                 limit(RLIMIT_CPU, limitt);
335 #else
336                 if (opt & OPT_v)
337                         bb_error_msg("system does not support RLIMIT_%s",
338                                 "CPU");
339 #endif
340         }
341
342         if (opt & OPT_P)
343                 setsid();
344
345         if (opt & OPT_e)
346                 edir(env_dir);
347
348         // FIXME: chrooted jail must have /etc/passwd if we move this after chroot!
349         // OTOH chroot fails for non-roots!
350         // SOLUTION: cache uid/gid before chroot, apply uid/gid after
351         if (opt & OPT_U) {
352                 xget_uidgid(&ugid, env_user);
353                 xsetenv("GID", utoa(ugid.gid));
354                 xsetenv("UID", utoa(ugid.uid));
355         }
356
357         if (opt & OPT_u) {
358                 xget_uidgid(&ugid, set_user);
359         }
360
361         if (opt & OPT_root) {
362                 xchdir(root);
363                 xchroot(".");
364         }
365
366         if (opt & OPT_u) {
367                 if (setgroups(1, &ugid.gid) == -1)
368                         bb_perror_msg_and_die("setgroups");
369                 xsetgid(ugid.gid);
370                 xsetuid(ugid.uid);
371         }
372
373         if (opt & OPT_n) {
374                 errno = 0;
375                 if (nice(xatoi(nicestr)) == -1)
376                         bb_perror_msg_and_die("nice");
377         }
378
379         if (opt & OPT_0)
380                 close(STDIN_FILENO);
381         if (opt & OPT_1)
382                 close(STDOUT_FILENO);
383         if (opt & OPT_2)
384                 close(STDERR_FILENO);
385
386         BB_EXECVP(argv[0], argv);
387         bb_perror_msg_and_die("exec %s", argv[0]);
388 }