add more convenient defines for [NO]MMU:
[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 #endif
27
28
29 /* Declare <applet>_main() */
30 #define PROTOTYPES
31 #include "applets.h"
32 #undef PROTOTYPES
33
34 #if ENABLE_SHOW_USAGE && !ENABLE_FEATURE_COMPRESS_USAGE
35 /* Define usage_messages[] */
36 static const char usage_messages[] =
37 #define MAKE_USAGE
38 #include "usage.h"
39 #include "applets.h"
40 ;
41 #undef MAKE_USAGE
42 #else
43 #define usage_messages 0
44 #endif /* SHOW_USAGE */
45
46 /* Define struct bb_applet applets[] */
47 #include "applets.h"
48 /* The -1 arises because of the {0,NULL,0,-1} entry. */
49 const unsigned short NUM_APPLETS = sizeof(applets) / sizeof(applets[0]) - 1;
50
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
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 /* There are 4 chars + 1 nul for each of user/group/other. */
115 static const char mode_chars[] = "Ssx-\0Ssx-\0Ttx-";
116
117 /* We don't supply a value for the nul, so an index adjustment is
118  * necessary below.  Also, we use unsigned short here to save some
119  * space even though these are really mode_t values. */
120 static const unsigned short mode_mask[] = {
121         /*  SST     sst                 xxx         --- */
122         S_ISUID,    S_ISUID|S_IXUSR,    S_IXUSR,    0,  /* user */
123         S_ISGID,    S_ISGID|S_IXGRP,    S_IXGRP,    0,  /* group */
124         0,          S_IXOTH,            S_IXOTH,    0   /* other */
125 };
126
127 #define parse_error(x)  do { errmsg = x; goto pe_label; } while(0)
128
129 static void parse_config_file(void)
130 {
131         struct BB_suid_config *sct_head;
132         struct BB_suid_config *sct;
133         const struct bb_applet *applet;
134         FILE *f;
135         const char *errmsg;
136         char *s;
137         char *e;
138         int i;
139         unsigned lc;
140         smallint section;
141         char buffer[256];
142         struct stat st;
143
144         assert(!suid_config); /* Should be set to NULL by bss init. */
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                                         const char *q;
258                                         q = strchrnul(mode_chars + 5*i, *e++);
259                                         if (!*q) {
260                                                 parse_error("mode");
261                                         }
262                                         /* Adjust by -i to account for nul. */
263                                         sct->m_mode |= mode_mask[(q - mode_chars) - i];
264                                 }
265
266                                 /* Now get the the user/group info. */
267
268                                 s = skip_whitespace(e);
269
270                                 /* Note: we require whitespace between the mode and the
271                                  * user/group info. */
272                                 if ((s == e) || !(e = strchr(s, '.'))) {
273                                         parse_error("<uid>.<gid>");
274                                 }
275                                 *e++ = '\0';
276
277                                 /* We can't use get_ug_id here since it would exit()
278                                  * if a uid or gid was not found.  Oh well... */
279                                 sct->m_uid = bb_strtoul(s, NULL, 10);
280                                 if (errno) {
281                                         struct passwd *pwd = getpwnam(s);
282                                         if (!pwd) {
283                                                 parse_error("user");
284                                         }
285                                         sct->m_uid = pwd->pw_uid;
286                                 }
287
288                                 sct->m_gid = bb_strtoul(e, NULL, 10);
289                                 if (errno) {
290                                         struct group *grp;
291                                         grp = getgrnam(e);
292                                         if (!grp) {
293                                                 parse_error("group");
294                                         }
295                                         sct->m_gid = grp->gr_gid;
296                                 }
297                         }
298                         continue;
299                 }
300
301                 /* Unknown sections are ignored. */
302
303                 /* Encountering configuration lines prior to seeing a
304                  * section header is treated as an error.  This is how
305                  * the old code worked, but it may not be desirable.
306                  * We may want to simply ignore such lines in case they
307                  * are used in some future version of busybox. */
308                 if (!section) {
309                         parse_error("keyword outside section");
310                 }
311
312         } /* while (1) */
313
314  pe_label:
315         fprintf(stderr, "Parse error in %s, line %d: %s\n",
316                         config_file, lc, errmsg);
317
318         fclose(f);
319         /* Release any allocated memory before returning. */
320         while (sct_head) {
321                 sct = sct_head->m_next;
322                 free(sct_head);
323                 sct_head = sct;
324         }
325 }
326 #else
327 #define parse_config_file() ((void)0)
328 #endif /* FEATURE_SUID_CONFIG */
329
330
331 #if ENABLE_FEATURE_SUID
332 static void check_suid(const struct bb_applet *applet)
333 {
334         uid_t ruid = getuid();               /* real [ug]id */
335         uid_t rgid = getgid();
336
337 #if ENABLE_FEATURE_SUID_CONFIG
338         if (suid_cfg_readable) {
339                 struct BB_suid_config *sct;
340                 mode_t m;
341
342                 for (sct = suid_config; sct; sct = sct->m_next) {
343                         if (sct->m_applet == applet)
344                                 goto found;
345                 }
346                 /* default: drop all privileges */
347                 xsetgid(rgid);
348                 xsetuid(ruid);
349                 return;
350  found:
351                 m = sct->m_mode;
352                 if (sct->m_uid == ruid)
353                         /* same uid */
354                         m >>= 6;
355                 else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid))
356                         /* same group / in group */
357                         m >>= 3;
358
359                 if (!(m & S_IXOTH))           /* is x bit not set ? */
360                         bb_error_msg_and_die("you have no permission to run this applet!");
361
362                 if (sct->m_gid != 0) {
363                         /* _both_ have to be set for sgid */
364                         if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
365                                 xsetgid(sct->m_gid);
366                         } else xsetgid(rgid); /* no sgid -> drop */
367                 }
368                 if (sct->m_uid != 0) {
369                         if (sct->m_mode & S_ISUID) xsetuid(sct->m_uid);
370                         else xsetuid(ruid); /* no suid -> drop */
371                 }
372                 return;
373         }
374 #if !ENABLE_FEATURE_SUID_CONFIG_QUIET
375         {
376                 static bool onetime = 0;
377
378                 if (!onetime) {
379                         onetime = 1;
380                         fprintf(stderr, "Using fallback suid method\n");
381                 }
382         }
383 #endif
384 #endif
385
386         if (applet->need_suid == _BB_SUID_ALWAYS) {
387                 if (geteuid())
388                         bb_error_msg_and_die("applet requires root privileges!");
389         } else if (applet->need_suid == _BB_SUID_NEVER) {
390                 xsetgid(rgid);                          /* drop all privileges */
391                 xsetuid(ruid);
392         }
393 }
394 #else
395 #define check_suid(x) ((void)0)
396 #endif /* FEATURE_SUID */
397
398
399 #if ENABLE_FEATURE_COMPRESS_USAGE
400
401 #include "usage_compressed.h"
402 #include "unarchive.h"
403
404 static const char *unpack_usage_messages(void)
405 {
406         char *outbuf = NULL;
407         bunzip_data *bd;
408         int i;
409
410         i = start_bunzip(&bd,
411                         /* src_fd: */ -1,
412                         /* inbuf:  */ packed_usage,
413                         /* len:    */ sizeof(packed_usage));
414         /* read_bunzip can longjmp to start_bunzip, and ultimately
415          * end up here with i != 0 on read data errors! Not trivial */
416         if (!i) {
417                 /* Cannot use xmalloc: will leak bd in NOFORK case! */
418                 outbuf = malloc_or_warn(SIZEOF_usage_messages);
419                 if (outbuf)
420                         read_bunzip(bd, outbuf, SIZEOF_usage_messages);
421         }
422         dealloc_bunzip(bd);
423         return outbuf;
424 }
425 #define dealloc_usage_messages(s) free(s)
426
427 #else
428
429 #define unpack_usage_messages() usage_messages
430 #define dealloc_usage_messages(s) ((void)(s))
431
432 #endif /* FEATURE_COMPRESS_USAGE */
433
434
435 void bb_show_usage(void)
436 {
437         if (ENABLE_SHOW_USAGE) {
438                 const char *format_string;
439                 const char *p;
440                 const char *usage_string = p = unpack_usage_messages();
441                 int i;
442
443                 i = current_applet - applets;
444                 while (i) {
445                         while (*p++) continue;
446                         i--;
447                 }
448
449                 format_string = "%s\n\nUsage: %s %s\n\n";
450                 if (*p == '\b')
451                         format_string = "%s\n\nNo help available.\n\n";
452                 fprintf(stderr, format_string, bb_msg_full_version,
453                                         applet_name, p);
454                 dealloc_usage_messages((char*)usage_string);
455         }
456         xfunc_die();
457 }
458
459
460 static int applet_name_compare(const void *name, const void *vapplet)
461 {
462         const struct bb_applet *applet = vapplet;
463
464         return strcmp(name, applet->name);
465 }
466
467 const struct bb_applet *find_applet_by_name(const char *name)
468 {
469         /* Do a binary search to find the applet entry given the name. */
470         return bsearch(name, applets, NUM_APPLETS, sizeof(applets[0]),
471                                 applet_name_compare);
472 }
473
474
475 #if ENABLE_FEATURE_INSTALLER
476 /* create (sym)links for each applet */
477 static void install_links(const char *busybox, int use_symbolic_links)
478 {
479         /* directory table
480          * this should be consistent w/ the enum,
481          * busybox.h::bb_install_loc_t, or else... */
482         static const char usr_bin [] = "/usr/bin";
483         static const char usr_sbin[] = "/usr/sbin";
484         static const char *const install_dir[] = {
485                 &usr_bin [8], /* "", equivalent to "/" for concat_path_file() */
486                 &usr_bin [4], /* "/bin" */
487                 &usr_sbin[4], /* "/sbin" */
488                 usr_bin,
489                 usr_sbin
490         };
491
492         int (*lf)(const char *, const char *) = link;
493         char *fpc;
494         int i;
495         int rc;
496
497         if (use_symbolic_links)
498                 lf = symlink;
499
500         for (i = 0; applets[i].name != NULL; i++) {
501                 fpc = concat_path_file(
502                                 install_dir[applets[i].install_loc],
503                                 applets[i].name);
504                 rc = lf(busybox, fpc);
505                 if (rc != 0 && errno != EEXIST) {
506                         bb_perror_msg("%s", fpc);
507                 }
508                 free(fpc);
509         }
510 }
511 #else
512 #define install_links(x,y) ((void)0)
513 #endif /* FEATURE_INSTALLER */
514
515
516 /* If we were called as "busybox..." */
517 static int busybox_main(int argc, char **argv)
518 {
519         if (ENABLE_FEATURE_INSTALLER && argc > 1 && !strcmp(argv[1], "--install")) {
520                 int use_symbolic_links = 0;
521                 char *busybox;
522
523                 /* to use symlinks, or not to use symlinks... */
524                 if (argc > 2)
525                         if (strcmp(argv[2], "-s") == 0)
526                                 use_symbolic_links = 1;
527
528                 /* link */
529                 busybox = xmalloc_readlink_or_warn("/proc/self/exe");
530                 if (!busybox)
531                         return 1;
532                 install_links(busybox, use_symbolic_links);
533                 if (ENABLE_FEATURE_CLEAN_UP)
534                         free(busybox);
535                 return 0;
536         }
537
538         /* Deal with --help. Also print help when called with no arguments */
539
540         if (argc == 1 || !strcmp(argv[1], "--help") ) {
541                 if (argc > 2) {
542                         /* set name for proper "<name>: applet not found" */
543                         applet_name = argv[2];
544                         run_applet_by_name(applet_name, 2, argv);
545                 } else {
546                         const struct bb_applet *a;
547                         int col, output_width;
548
549                         output_width = 80 - sizeof("start-stop-daemon, ") - 8;
550                         if (ENABLE_FEATURE_AUTOWIDTH) {
551                                 /* Obtain the terminal width.  */
552                                 get_terminal_width_height(0, &output_width, NULL);
553                                 /* leading tab and room to wrap */
554                                 output_width -= sizeof("start-stop-daemon, ") + 8;
555                         }
556
557                         printf("%s\n"
558                                "Copyright (C) 1998-2006  Erik Andersen, Rob Landley, and others.\n"
559                                "Licensed under GPLv2.  See source distribution for full notice.\n"
560                                "\n"
561                                "Usage: busybox [function] [arguments]...\n"
562                                "   or: [function] [arguments]...\n"
563                                "\n"
564                                "\tBusyBox is a multi-call binary that combines many common Unix\n"
565                                "\tutilities into a single executable.  Most people will create a\n"
566                                "\tlink to busybox for each function they wish to use and BusyBox\n"
567                                "\twill act like whatever it was invoked as!\n"
568                                "\nCurrently defined functions:\n", bb_msg_full_version);
569                         col = 0;
570                         a = applets;
571                         while (a->name) {
572                                 col += printf("%s%s", (col ? ", " : "\t"), a->name);
573                                 a++;
574                                 if (col > output_width && a->name) {
575                                         puts(",");
576                                         col = 0;
577                                 }
578                         }
579                         puts("\n");
580                         return 0;
581                 }
582         } else {
583                 /* we want "<argv[1]>: applet not found", not "busybox: ..." */
584                 applet_name = argv[1];
585                 run_applet_by_name(argv[1], argc - 1, argv + 1);
586         }
587
588         bb_error_msg_and_die("applet not found");
589 }
590
591 void run_current_applet_and_exit(int argc, char **argv)
592 {
593         applet_name = current_applet->name;
594         if (argc == 2 && !strcmp(argv[1], "--help"))
595                 bb_show_usage();
596         if (ENABLE_FEATURE_SUID)
597                 check_suid(current_applet);
598         exit(current_applet->main(argc, argv));
599 }
600
601 void run_applet_by_name(const char *name, int argc, char **argv)
602 {
603         current_applet = find_applet_by_name(name);
604         if (current_applet)
605                 run_current_applet_and_exit(argc, argv);
606         if (!strncmp(name, "busybox", 7))
607                 exit(busybox_main(argc, argv));
608 }
609
610
611 int main(int argc, char **argv)
612 {
613         const char *s;
614
615 #if !BB_MMU
616         /* NOMMU re-exec trick sets high-order bit in first byte of name */
617         if (argv[0][0] & 0x80) {
618                 re_execed = 1;
619                 argv[0][0] &= 0x7f;
620         }
621 #endif
622         applet_name = argv[0];
623         if (applet_name[0] == '-')
624                 applet_name++;
625         s = strrchr(applet_name, '/');
626         if (s)
627                 applet_name = s + 1;
628
629         if (ENABLE_FEATURE_SUID_CONFIG)
630                 parse_config_file();
631
632         /* Set locale for everybody except 'init' */
633         if (ENABLE_LOCALE_SUPPORT && getpid() != 1)
634                 setlocale(LC_ALL, "");
635
636         run_applet_by_name(applet_name, argc, argv);
637         bb_error_msg_and_die("applet not found");
638 }