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