blob: 7808df501de6921f5dab1a293a76a7a3e19d0f44 [file] [log] [blame]
Denis Vlasenkoac7d0e32007-10-08 19:32:12 +00001/* vi: set sw=4 ts=4: */
2/*
Denis Vlasenko724d1962007-10-10 14:41:07 +00003 * Utility routines.
Denis Vlasenkoac7d0e32007-10-08 19:32:12 +00004 *
Denis Vlasenko724d1962007-10-10 14:41:07 +00005 * Copyright (C) tons of folks. Tracking down who wrote what
6 * isn't something I'm going to worry about... If you wrote something
7 * here, please feel free to acknowledge your work.
Denis Vlasenkoac7d0e32007-10-08 19:32:12 +00008 *
Denis Vlasenko724d1962007-10-10 14:41:07 +00009 * Based in part on code from sash, Copyright (c) 1999 by David I. Bell
10 * Permission has been granted to redistribute this code under the GPL.
11 *
12 * Licensed under GPLv2 or later, see file License in this tarball for details.
Denis Vlasenkoac7d0e32007-10-08 19:32:12 +000013 */
14
15#include <assert.h>
16#include "busybox.h"
17
18
19/* Declare <applet>_main() */
20#define PROTOTYPES
21#include "applets.h"
22#undef PROTOTYPES
23
24#if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE
25/* Define usage_messages[] */
26static const char usage_messages[] ALIGN1 = ""
27#define MAKE_USAGE
28#include "usage.h"
29#include "applets.h"
30;
31#undef MAKE_USAGE
32#else
33#define usage_messages 0
34#endif /* SHOW_USAGE */
35
36/* Define struct bb_applet applets[] */
37#include "applets.h"
38
39#if ENABLE_FEATURE_SH_STANDALONE
40/* -1 because last entry is NULL */
41const unsigned short NUM_APPLETS = ARRAY_SIZE(applets) - 1;
42#endif
43
44
45#if ENABLE_FEATURE_COMPRESS_USAGE
46
47#include "usage_compressed.h"
48#include "unarchive.h"
49
50static const char *unpack_usage_messages(void)
51{
52 char *outbuf = NULL;
53 bunzip_data *bd;
54 int i;
55
56 i = start_bunzip(&bd,
57 /* src_fd: */ -1,
58 /* inbuf: */ packed_usage,
59 /* len: */ sizeof(packed_usage));
60 /* read_bunzip can longjmp to start_bunzip, and ultimately
61 * end up here with i != 0 on read data errors! Not trivial */
62 if (!i) {
63 /* Cannot use xmalloc: will leak bd in NOFORK case! */
64 outbuf = malloc_or_warn(SIZEOF_usage_messages);
65 if (outbuf)
66 read_bunzip(bd, outbuf, SIZEOF_usage_messages);
67 }
68 dealloc_bunzip(bd);
69 return outbuf;
70}
71#define dealloc_usage_messages(s) free(s)
72
73#else
74
75#define unpack_usage_messages() usage_messages
76#define dealloc_usage_messages(s) ((void)(s))
77
78#endif /* FEATURE_COMPRESS_USAGE */
79
80
81void bb_show_usage(void)
82{
83 if (ENABLE_SHOW_USAGE) {
84 const char *format_string;
85 const char *p;
86 const char *usage_string = p = unpack_usage_messages();
87 const struct bb_applet *ap = find_applet_by_name(applet_name);
88 int i;
89
90 if (!ap) /* never happens, paranoia */
91 xfunc_die();
92
93 i = ap - applets;
94 while (i) {
95 while (*p++) continue;
96 i--;
97 }
98
99 fprintf(stderr, "%s multi-call binary\n", bb_banner);
100 format_string = "\nUsage: %s %s\n\n";
101 if (*p == '\b')
102 format_string = "\nNo help available.\n\n";
103 fprintf(stderr, format_string, applet_name, p);
104 dealloc_usage_messages((char*)usage_string);
105 }
106 xfunc_die();
107}
108
109
110static int applet_name_compare(const void *name, const void *vapplet)
111{
112 const struct bb_applet *applet = vapplet;
113
114 return strcmp(name, applet->name);
115}
116
117const struct bb_applet *find_applet_by_name(const char *name)
118{
119 /* Do a binary search to find the applet entry given the name. */
120 return bsearch(name, applets, ARRAY_SIZE(applets)-1, sizeof(applets[0]),
121 applet_name_compare);
122}
123
124
125#ifdef __GLIBC__
126/* Make it reside in R/W memory: */
127int *const bb_errno __attribute__ ((section (".data")));
128#endif
129
130void bbox_prepare_main(char **argv)
131{
132#ifdef __GLIBC__
133 (*(int **)&bb_errno) = __errno_location();
134#endif
135
136 /* Set locale for everybody except 'init' */
137 if (ENABLE_LOCALE_SUPPORT && getpid() != 1)
138 setlocale(LC_ALL, "");
139
Denis Vlasenko82d38da2007-10-10 14:38:47 +0000140#if ENABLE_FEATURE_INDIVIDUAL
141 /* Redundant for busybox (run_applet_and_exit covers that case)
142 * but needed for "individual applet" mode */
Denis Vlasenkod419a9f2007-10-08 20:45:42 +0000143 if (argv[1] && strcmp(argv[1], "--help") == 0)
Denis Vlasenkoac7d0e32007-10-08 19:32:12 +0000144 bb_show_usage();
Denis Vlasenko82d38da2007-10-10 14:38:47 +0000145#endif
Denis Vlasenkoac7d0e32007-10-08 19:32:12 +0000146}
Denis Vlasenko724d1962007-10-10 14:41:07 +0000147
148/* The code below can well be in applets/applets.c, as it is used only
149 * for busybox binary, not "individual" binaries.
150 * However, keeping it here and linking it into libbusybox.so
151 * (together with remaining tiny applets/applets.o)
152 * makes it possible to avoid --whole-archive at link time.
153 * This makes (shared busybox) + libbusybox smaller.
154 * (--gc-sections would be even better....)
155 */
156
157const char *applet_name;
158#if !BB_MMU
159bool re_execed;
160#endif
161
162USE_FEATURE_SUID(static uid_t ruid;) /* real uid */
163
164#if ENABLE_FEATURE_SUID_CONFIG
165
166/* applets[] is const, so we have to define this "override" structure */
167static struct BB_suid_config {
168 const struct bb_applet *m_applet;
169 uid_t m_uid;
170 gid_t m_gid;
171 mode_t m_mode;
172 struct BB_suid_config *m_next;
173} *suid_config;
174
175static bool suid_cfg_readable;
176
177/* check if u is member of group g */
178static int ingroup(uid_t u, gid_t g)
179{
180 struct group *grp = getgrgid(g);
181
182 if (grp) {
183 char **mem;
184
185 for (mem = grp->gr_mem; *mem; mem++) {
186 struct passwd *pwd = getpwnam(*mem);
187
188 if (pwd && (pwd->pw_uid == u))
189 return 1;
190 }
191 }
192 return 0;
193}
194
195/* This should probably be a libbb routine. In that case,
196 * I'd probably rename it to something like bb_trimmed_slice.
197 */
198static char *get_trimmed_slice(char *s, char *e)
199{
200 /* First, consider the value at e to be nul and back up until we
201 * reach a non-space char. Set the char after that (possibly at
202 * the original e) to nul. */
203 while (e-- > s) {
204 if (!isspace(*e)) {
205 break;
206 }
207 }
208 e[1] = '\0';
209
210 /* Next, advance past all leading space and return a ptr to the
211 * first non-space char; possibly the terminating nul. */
212 return skip_whitespace(s);
213}
214
215/* Don't depend on the tools to combine strings. */
216static const char config_file[] ALIGN1 = "/etc/busybox.conf";
217
218/* We don't supply a value for the nul, so an index adjustment is
219 * necessary below. Also, we use unsigned short here to save some
220 * space even though these are really mode_t values. */
221static const unsigned short mode_mask[] ALIGN2 = {
222 /* SST sst xxx --- */
223 S_ISUID, S_ISUID|S_IXUSR, S_IXUSR, 0, /* user */
224 S_ISGID, S_ISGID|S_IXGRP, S_IXGRP, 0, /* group */
225 0, S_IXOTH, S_IXOTH, 0 /* other */
226};
227
228#define parse_error(x) do { errmsg = x; goto pe_label; } while (0)
229
230static void parse_config_file(void)
231{
232 struct BB_suid_config *sct_head;
233 struct BB_suid_config *sct;
234 const struct bb_applet *applet;
235 FILE *f;
236 const char *errmsg;
237 char *s;
238 char *e;
239 int i;
240 unsigned lc;
241 smallint section;
242 char buffer[256];
243 struct stat st;
244
245 assert(!suid_config); /* Should be set to NULL by bss init. */
246
247 ruid = getuid();
248 if (ruid == 0) /* run by root - don't need to even read config file */
249 return;
250
251 if ((stat(config_file, &st) != 0) /* No config file? */
252 || !S_ISREG(st.st_mode) /* Not a regular file? */
253 || (st.st_uid != 0) /* Not owned by root? */
254 || (st.st_mode & (S_IWGRP | S_IWOTH)) /* Writable by non-root? */
255 || !(f = fopen(config_file, "r")) /* Cannot open? */
256 ) {
257 return;
258 }
259
260 suid_cfg_readable = 1;
261 sct_head = NULL;
262 section = lc = 0;
263
264 while (1) {
265 s = buffer;
266
267 if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */
268 if (ferror(f)) { /* Make sure it wasn't a read error. */
269 parse_error("reading");
270 }
271 fclose(f);
272 suid_config = sct_head; /* Success, so set the pointer. */
273 return;
274 }
275
276 lc++; /* Got a (partial) line. */
277
278 /* If a line is too long for our buffer, we consider it an error.
279 * The following test does mistreat one corner case though.
280 * If the final line of the file does not end with a newline and
281 * yet exactly fills the buffer, it will be treated as too long
282 * even though there isn't really a problem. But it isn't really
283 * worth adding code to deal with such an unlikely situation, and
284 * we do err on the side of caution. Besides, the line would be
285 * too long if it did end with a newline. */
286 if (!strchr(s, '\n') && !feof(f)) {
287 parse_error("line too long");
288 }
289
290 /* Trim leading and trailing whitespace, ignoring comments, and
291 * check if the resulting string is empty. */
292 s = get_trimmed_slice(s, strchrnul(s, '#'));
293 if (!*s) {
294 continue;
295 }
296
297 /* Check for a section header. */
298
299 if (*s == '[') {
300 /* Unlike the old code, we ignore leading and trailing
301 * whitespace for the section name. We also require that
302 * there are no stray characters after the closing bracket. */
303 e = strchr(s, ']');
304 if (!e /* Missing right bracket? */
305 || e[1] /* Trailing characters? */
306 || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */
307 ) {
308 parse_error("section header");
309 }
310 /* Right now we only have one section so just check it.
311 * If more sections are added in the future, please don't
312 * resort to cascading ifs with multiple strcasecmp calls.
313 * That kind of bloated code is all too common. A loop
314 * and a string table would be a better choice unless the
315 * number of sections is very small. */
316 if (strcasecmp(s, "SUID") == 0) {
317 section = 1;
318 continue;
319 }
320 section = -1; /* Unknown section so set to skip. */
321 continue;
322 }
323
324 /* Process sections. */
325
326 if (section == 1) { /* SUID */
327 /* Since we trimmed leading and trailing space above, we're
328 * now looking for strings of the form
329 * <key>[::space::]*=[::space::]*<value>
330 * where both key and value could contain inner whitespace. */
331
332 /* First get the key (an applet name in our case). */
333 e = strchr(s, '=');
334 if (e) {
335 s = get_trimmed_slice(s, e);
336 }
337 if (!e || !*s) { /* Missing '=' or empty key. */
338 parse_error("keyword");
339 }
340
341 /* Ok, we have an applet name. Process the rhs if this
342 * applet is currently built in and ignore it otherwise.
343 * Note: this can hide config file bugs which only pop
344 * up when the busybox configuration is changed. */
345 applet = find_applet_by_name(s);
346 if (applet) {
347 /* Note: We currently don't check for duplicates!
348 * The last config line for each applet will be the
349 * one used since we insert at the head of the list.
350 * I suppose this could be considered a feature. */
351 sct = xmalloc(sizeof(struct BB_suid_config));
352 sct->m_applet = applet;
353 sct->m_mode = 0;
354 sct->m_next = sct_head;
355 sct_head = sct;
356
357 /* Get the specified mode. */
358
359 e = skip_whitespace(e+1);
360
361 for (i = 0; i < 3; i++) {
362 /* There are 4 chars + 1 nul for each of user/group/other. */
363 static const char mode_chars[] ALIGN1 = "Ssx-\0" "Ssx-\0" "Ttx-";
364
365 const char *q;
366 q = strchrnul(mode_chars + 5*i, *e++);
367 if (!*q) {
368 parse_error("mode");
369 }
370 /* Adjust by -i to account for nul. */
371 sct->m_mode |= mode_mask[(q - mode_chars) - i];
372 }
373
374 /* Now get the the user/group info. */
375
376 s = skip_whitespace(e);
377
378 /* Note: we require whitespace between the mode and the
379 * user/group info. */
380 if ((s == e) || !(e = strchr(s, '.'))) {
381 parse_error("<uid>.<gid>");
382 }
383 *e++ = '\0';
384
385 /* We can't use get_ug_id here since it would exit()
386 * if a uid or gid was not found. Oh well... */
387 sct->m_uid = bb_strtoul(s, NULL, 10);
388 if (errno) {
389 struct passwd *pwd = getpwnam(s);
390 if (!pwd) {
391 parse_error("user");
392 }
393 sct->m_uid = pwd->pw_uid;
394 }
395
396 sct->m_gid = bb_strtoul(e, NULL, 10);
397 if (errno) {
398 struct group *grp;
399 grp = getgrnam(e);
400 if (!grp) {
401 parse_error("group");
402 }
403 sct->m_gid = grp->gr_gid;
404 }
405 }
406 continue;
407 }
408
409 /* Unknown sections are ignored. */
410
411 /* Encountering configuration lines prior to seeing a
412 * section header is treated as an error. This is how
413 * the old code worked, but it may not be desirable.
414 * We may want to simply ignore such lines in case they
415 * are used in some future version of busybox. */
416 if (!section) {
417 parse_error("keyword outside section");
418 }
419
420 } /* while (1) */
421
422 pe_label:
423 fprintf(stderr, "Parse error in %s, line %d: %s\n",
424 config_file, lc, errmsg);
425
426 fclose(f);
427 /* Release any allocated memory before returning. */
428 while (sct_head) {
429 sct = sct_head->m_next;
430 free(sct_head);
431 sct_head = sct;
432 }
433}
434#else
435static inline void parse_config_file(void)
436{
437 USE_FEATURE_SUID(ruid = getuid();)
438}
439#endif /* FEATURE_SUID_CONFIG */
440
441
442#if ENABLE_FEATURE_SUID
443static void check_suid(const struct bb_applet *applet)
444{
445 gid_t rgid; /* real gid */
446
447 if (ruid == 0) /* set by parse_config_file() */
448 return; /* run by root - no need to check more */
449 rgid = getgid();
450
451#if ENABLE_FEATURE_SUID_CONFIG
452 if (suid_cfg_readable) {
453 uid_t uid;
454 struct BB_suid_config *sct;
455 mode_t m;
456
457 for (sct = suid_config; sct; sct = sct->m_next) {
458 if (sct->m_applet == applet)
459 goto found;
460 }
461 /* default: drop all privileges */
462 xsetgid(rgid);
463 xsetuid(ruid);
464 return;
465 found:
466 m = sct->m_mode;
467 if (sct->m_uid == ruid)
468 /* same uid */
469 m >>= 6;
470 else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid))
471 /* same group / in group */
472 m >>= 3;
473
474 if (!(m & S_IXOTH)) /* is x bit not set ? */
475 bb_error_msg_and_die("you have no permission to run this applet!");
476
477 /* _both_ sgid and group_exec have to be set for setegid */
478 if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
479 rgid = sct->m_gid;
480 /* else (no setegid) we will set egid = rgid */
481
482 /* We set effective AND saved ids. If saved-id is not set
483 * like we do below, seteiud(0) can still later succeed! */
484 if (setresgid(-1, rgid, rgid))
485 bb_perror_msg_and_die("setresgid");
486
487 /* do we have to set effective uid? */
488 uid = ruid;
489 if (sct->m_mode & S_ISUID)
490 uid = sct->m_uid;
491 /* else (no seteuid) we will set euid = ruid */
492
493 if (setresuid(-1, uid, uid))
494 bb_perror_msg_and_die("setresuid");
495 return;
496 }
497#if !ENABLE_FEATURE_SUID_CONFIG_QUIET
498 {
499 static bool onetime = 0;
500
501 if (!onetime) {
502 onetime = 1;
503 fprintf(stderr, "Using fallback suid method\n");
504 }
505 }
506#endif
507#endif
508
509 if (applet->need_suid == _BB_SUID_ALWAYS) {
510 /* Real uid is not 0. If euid isn't 0 too, suid bit
511 * is most probably not set on our executable */
512 if (geteuid())
513 bb_error_msg_and_die("applet requires root privileges!");
514 } else if (applet->need_suid == _BB_SUID_NEVER) {
515 xsetgid(rgid); /* drop all privileges */
516 xsetuid(ruid);
517 }
518}
519#else
520#define check_suid(x) ((void)0)
521#endif /* FEATURE_SUID */
522
523
524#if ENABLE_FEATURE_INSTALLER
525/* create (sym)links for each applet */
526static void install_links(const char *busybox, int use_symbolic_links)
527{
528 /* directory table
529 * this should be consistent w/ the enum,
530 * busybox.h::bb_install_loc_t, or else... */
531 static const char usr_bin [] ALIGN1 = "/usr/bin";
532 static const char usr_sbin[] ALIGN1 = "/usr/sbin";
533 static const char *const install_dir[] = {
534 &usr_bin [8], /* "", equivalent to "/" for concat_path_file() */
535 &usr_bin [4], /* "/bin" */
536 &usr_sbin[4], /* "/sbin" */
537 usr_bin,
538 usr_sbin
539 };
540
541 int (*lf)(const char *, const char *) = link;
542 char *fpc;
543 int i;
544 int rc;
545
546 if (use_symbolic_links)
547 lf = symlink;
548
549 for (i = 0; applets[i].name != NULL; i++) {
550 fpc = concat_path_file(
551 install_dir[applets[i].install_loc],
552 applets[i].name);
553 rc = lf(busybox, fpc);
554 if (rc != 0 && errno != EEXIST) {
555 bb_simple_perror_msg(fpc);
556 }
557 free(fpc);
558 }
559}
560#else
561#define install_links(x,y) ((void)0)
562#endif /* FEATURE_INSTALLER */
563
564/* If we were called as "busybox..." */
565static int busybox_main(char **argv)
566{
567 if (!argv[1]) {
568 /* Called without arguments */
569 const struct bb_applet *a;
570 int col, output_width;
571 help:
572 output_width = 80;
573 if (ENABLE_FEATURE_AUTOWIDTH) {
574 /* Obtain the terminal width */
575 get_terminal_width_height(0, &output_width, NULL);
576 }
577 /* leading tab and room to wrap */
578 output_width -= sizeof("start-stop-daemon, ") + 8;
579
580 printf("%s multi-call binary\n", bb_banner); /* reuse const string... */
581 printf("Copyright (C) 1998-2006 Erik Andersen, Rob Landley, and others.\n"
582 "Licensed under GPLv2. See source distribution for full notice.\n"
583 "\n"
584 "Usage: busybox [function] [arguments]...\n"
585 " or: [function] [arguments]...\n"
586 "\n"
587 "\tBusyBox is a multi-call binary that combines many common Unix\n"
588 "\tutilities into a single executable. Most people will create a\n"
589 "\tlink to busybox for each function they wish to use and BusyBox\n"
590 "\twill act like whatever it was invoked as!\n"
591 "\nCurrently defined functions:\n");
592 col = 0;
593 a = applets;
594 while (a->name) {
595 if (col > output_width) {
596 puts(",");
597 col = 0;
598 }
599 col += printf("%s%s", (col ? ", " : "\t"), a->name);
600 a++;
601 }
602 puts("\n");
603 return 0;
604 }
605
606 if (ENABLE_FEATURE_INSTALLER && strcmp(argv[1], "--install") == 0) {
607 const char *busybox;
608 busybox = xmalloc_readlink(bb_busybox_exec_path);
609 if (!busybox)
610 busybox = bb_busybox_exec_path;
611 /* -s makes symlinks */
612 install_links(busybox, argv[2] && strcmp(argv[2], "-s") == 0);
613 return 0;
614 }
615
616 if (strcmp(argv[1], "--help") == 0) {
617 /* "busybox --help [<applet>]" */
618 if (!argv[2])
619 goto help;
620 /* convert to "<applet> --help" */
621 argv[0] = argv[2];
622 argv[2] = NULL;
623 } else {
624 /* "busybox <applet> arg1 arg2 ..." */
625 argv++;
626 }
627 /* We support "busybox /a/path/to/applet args..." too. Allows for
628 * "#!/bin/busybox"-style wrappers */
629 applet_name = bb_get_last_path_component_nostrip(argv[0]);
630 run_applet_and_exit(applet_name, argv);
631 bb_error_msg_and_die("applet not found");
632}
633
634void run_appletstruct_and_exit(const struct bb_applet *applet, char **argv)
635{
636 int argc = 1;
637
638 while (argv[argc])
639 argc++;
640
641 /* Reinit some shared global data */
642 optind = 1;
643 xfunc_error_retval = EXIT_FAILURE;
644
645 applet_name = applet->name;
646 if (argc == 2 && !strcmp(argv[1], "--help"))
647 bb_show_usage();
648 if (ENABLE_FEATURE_SUID)
649 check_suid(applet);
650 exit(applet->main(argc, argv));
651}
652
653void run_applet_and_exit(const char *name, char **argv)
654{
655 const struct bb_applet *applet = find_applet_by_name(name);
656 if (applet)
657 run_appletstruct_and_exit(applet, argv);
658 if (!strncmp(name, "busybox", 7))
659 exit(busybox_main(argv));
660}
661
662
663#if ENABLE_BUILD_LIBBUSYBOX
664int libbusybox_main(int argc, char **argv)
665#else
666int main(int argc, char **argv)
667#endif
668{
669 bbox_prepare_main(argv);
670
671#if !BB_MMU
672 /* NOMMU re-exec trick sets high-order bit in first byte of name */
673 if (argv[0][0] & 0x80) {
674 re_execed = 1;
675 argv[0][0] &= 0x7f;
676 }
677#endif
678 applet_name = argv[0];
679 if (applet_name[0] == '-')
680 applet_name++;
681 applet_name = bb_basename(applet_name);
682
683 parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */
684
685 run_applet_and_exit(applet_name, argv);
686 bb_error_msg_and_die("applet not found");
687}