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