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