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