add -fvisibility=hidden to CC flags, mark XXX_main functions
[oweals/busybox.git] / 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 /* Define struct bb_applet applets[] */
37 #include "applets.h"
38
39 #if ENABLE_FEATURE_SH_STANDALONE
40 /* -1 because last entry is NULL */
41 const unsigned short NUM_APPLETS = ARRAY_SIZE(applets) - 1;
42 #endif
43
44
45 #if ENABLE_FEATURE_COMPRESS_USAGE
46
47 #include "usage_compressed.h"
48 #include "unarchive.h"
49
50 static const char *unpack_usage_messages(void)
51 {
52         char *outbuf = NULL;
53         bunzip_data *bd;
54         int i;
55
56         i = start_bunzip(&bd,
57                         /* src_fd: */ -1,
58                         /* inbuf:  */ packed_usage,
59                         /* len:    */ sizeof(packed_usage));
60         /* read_bunzip can longjmp to start_bunzip, and ultimately
61          * end up here with i != 0 on read data errors! Not trivial */
62         if (!i) {
63                 /* Cannot use xmalloc: will leak bd in NOFORK case! */
64                 outbuf = malloc_or_warn(SIZEOF_usage_messages);
65                 if (outbuf)
66                         read_bunzip(bd, outbuf, SIZEOF_usage_messages);
67         }
68         dealloc_bunzip(bd);
69         return outbuf;
70 }
71 #define dealloc_usage_messages(s) free(s)
72
73 #else
74
75 #define unpack_usage_messages() usage_messages
76 #define dealloc_usage_messages(s) ((void)(s))
77
78 #endif /* FEATURE_COMPRESS_USAGE */
79
80
81 void bb_show_usage(void)
82 {
83         if (ENABLE_SHOW_USAGE) {
84                 const char *format_string;
85                 const char *p;
86                 const char *usage_string = p = unpack_usage_messages();
87                 const struct bb_applet *ap = find_applet_by_name(applet_name);
88                 int i;
89
90                 if (!ap) /* never happens, paranoia */
91                         xfunc_die();
92
93                 i = ap - applets;
94                 while (i) {
95                         while (*p++) continue;
96                         i--;
97                 }
98
99                 fprintf(stderr, "%s multi-call binary\n", bb_banner);
100                 format_string = "\nUsage: %s %s\n\n";
101                 if (*p == '\b')
102                         format_string = "\nNo help available.\n\n";
103                 fprintf(stderr, format_string, applet_name, p);
104                 dealloc_usage_messages((char*)usage_string);
105         }
106         xfunc_die();
107 }
108
109
110 static int applet_name_compare(const void *name, const void *vapplet)
111 {
112         const struct bb_applet *applet = vapplet;
113
114         return strcmp(name, applet->name);
115 }
116
117 const struct bb_applet *find_applet_by_name(const char *name)
118 {
119         /* Do a binary search to find the applet entry given the name. */
120         return bsearch(name, applets, ARRAY_SIZE(applets)-1, sizeof(applets[0]),
121                                 applet_name_compare);
122 }
123
124
125 #ifdef __GLIBC__
126 /* Make it reside in R/W memory: */
127 int *const bb_errno __attribute__ ((section (".data")));
128 #endif
129
130 void lbb_prepare(char **argv)
131 {
132 #ifdef __GLIBC__
133         (*(int **)&bb_errno) = __errno_location();
134 #endif
135
136         /* Set locale for everybody except 'init' */
137         if (ENABLE_LOCALE_SUPPORT && getpid() != 1)
138                 setlocale(LC_ALL, "");
139
140 #if ENABLE_FEATURE_INDIVIDUAL
141         /* Redundant for busybox (run_applet_and_exit covers that case)
142          * but needed for "individual applet" mode */
143         if (argv[1] && strcmp(argv[1], "--help") == 0)
144                 bb_show_usage();
145 #endif
146 }
147
148 /* The code below can well be in applets/applets.c, as it is used only
149  * for busybox binary, not "individual" binaries.
150  * However, keeping it here and linking it into libbusybox.so
151  * (together with remaining tiny applets/applets.o)
152  * makes it possible to avoid --whole-archive at link time.
153  * This makes (shared busybox) + libbusybox smaller.
154  * (--gc-sections would be even better....)
155  */
156
157 const char *applet_name;
158 #if !BB_MMU
159 bool re_execed;
160 #endif
161
162 USE_FEATURE_SUID(static uid_t ruid;)  /* real uid */
163
164 #if ENABLE_FEATURE_SUID_CONFIG
165
166 /* applets[] is const, so we have to define this "override" structure */
167 static struct BB_suid_config {
168         const struct bb_applet *m_applet;
169         uid_t m_uid;
170         gid_t m_gid;
171         mode_t m_mode;
172         struct BB_suid_config *m_next;
173 } *suid_config;
174
175 static bool suid_cfg_readable;
176
177 /* check if u is member of group g */
178 static int ingroup(uid_t u, gid_t g)
179 {
180         struct group *grp = getgrgid(g);
181
182         if (grp) {
183                 char **mem;
184
185                 for (mem = grp->gr_mem; *mem; mem++) {
186                         struct passwd *pwd = getpwnam(*mem);
187
188                         if (pwd && (pwd->pw_uid == u))
189                                 return 1;
190                 }
191         }
192         return 0;
193 }
194
195 /* This should probably be a libbb routine.  In that case,
196  * I'd probably rename it to something like bb_trimmed_slice.
197  */
198 static char *get_trimmed_slice(char *s, char *e)
199 {
200         /* First, consider the value at e to be nul and back up until we
201          * reach a non-space char.  Set the char after that (possibly at
202          * the original e) to nul. */
203         while (e-- > s) {
204                 if (!isspace(*e)) {
205                         break;
206                 }
207         }
208         e[1] = '\0';
209
210         /* Next, advance past all leading space and return a ptr to the
211          * first non-space char; possibly the terminating nul. */
212         return skip_whitespace(s);
213 }
214
215 /* Don't depend on the tools to combine strings. */
216 static const char config_file[] ALIGN1 = "/etc/busybox.conf";
217
218 /* We don't supply a value for the nul, so an index adjustment is
219  * necessary below.  Also, we use unsigned short here to save some
220  * space even though these are really mode_t values. */
221 static const unsigned short mode_mask[] ALIGN2 = {
222         /*  SST     sst                 xxx         --- */
223         S_ISUID,    S_ISUID|S_IXUSR,    S_IXUSR,    0,  /* user */
224         S_ISGID,    S_ISGID|S_IXGRP,    S_IXGRP,    0,  /* group */
225         0,          S_IXOTH,            S_IXOTH,    0   /* other */
226 };
227
228 #define parse_error(x)  do { errmsg = x; goto pe_label; } while (0)
229
230 static void parse_config_file(void)
231 {
232         struct BB_suid_config *sct_head;
233         struct BB_suid_config *sct;
234         const struct bb_applet *applet;
235         FILE *f;
236         const char *errmsg;
237         char *s;
238         char *e;
239         int i;
240         unsigned lc;
241         smallint section;
242         char buffer[256];
243         struct stat st;
244
245         assert(!suid_config); /* Should be set to NULL by bss init. */
246
247         ruid = getuid();
248         if (ruid == 0) /* run by root - don't need to even read config file */
249                 return;
250
251         if ((stat(config_file, &st) != 0)       /* No config file? */
252          || !S_ISREG(st.st_mode)                /* Not a regular file? */
253          || (st.st_uid != 0)                    /* Not owned by root? */
254          || (st.st_mode & (S_IWGRP | S_IWOTH))  /* Writable by non-root? */
255          || !(f = fopen(config_file, "r"))      /* Cannot open? */
256         ) {
257                 return;
258         }
259
260         suid_cfg_readable = 1;
261         sct_head = NULL;
262         section = lc = 0;
263
264         while (1) {
265                 s = buffer;
266
267                 if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */
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 = find_applet_by_name(s);
346                         if (applet) {
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;
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(const struct bb_applet *applet)
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)
459                                 goto found;
460                 }
461                 /* default: drop all privileges */
462                 xsetgid(rgid);
463                 xsetuid(ruid);
464                 return;
465  found:
466                 m = sct->m_mode;
467                 if (sct->m_uid == ruid)
468                         /* same uid */
469                         m >>= 6;
470                 else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid))
471                         /* same group / in group */
472                         m >>= 3;
473
474                 if (!(m & S_IXOTH))           /* is x bit not set ? */
475                         bb_error_msg_and_die("you have no permission to run this applet!");
476
477                 /* _both_ sgid and group_exec have to be set for setegid */
478                 if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP))
479                         rgid = sct->m_gid;
480                 /* else (no setegid) we will set egid = rgid */
481
482                 /* We set effective AND saved ids. If saved-id is not set
483                  * like we do below, seteiud(0) can still later succeed! */
484                 if (setresgid(-1, rgid, rgid))
485                         bb_perror_msg_and_die("setresgid");
486
487                 /* do we have to set effective uid? */
488                 uid = ruid;
489                 if (sct->m_mode & S_ISUID)
490                         uid = sct->m_uid;
491                 /* else (no seteuid) we will set euid = ruid */
492
493                 if (setresuid(-1, uid, uid))
494                         bb_perror_msg_and_die("setresuid");
495                 return;
496         }
497 #if !ENABLE_FEATURE_SUID_CONFIG_QUIET
498         {
499                 static bool onetime = 0;
500
501                 if (!onetime) {
502                         onetime = 1;
503                         fprintf(stderr, "Using fallback suid method\n");
504                 }
505         }
506 #endif
507 #endif
508
509         if (applet->need_suid == _BB_SUID_ALWAYS) {
510                 /* Real uid is not 0. If euid isn't 0 too, suid bit
511                  * is most probably not set on our executable */
512                 if (geteuid())
513                         bb_error_msg_and_die("applet requires root privileges!");
514         } else if (applet->need_suid == _BB_SUID_NEVER) {
515                 xsetgid(rgid);  /* drop all privileges */
516                 xsetuid(ruid);
517         }
518 }
519 #else
520 #define check_suid(x) ((void)0)
521 #endif /* FEATURE_SUID */
522
523
524 #if ENABLE_FEATURE_INSTALLER
525 /* create (sym)links for each applet */
526 static void install_links(const char *busybox, int use_symbolic_links)
527 {
528         /* directory table
529          * this should be consistent w/ the enum,
530          * busybox.h::bb_install_loc_t, or else... */
531         static const char usr_bin [] ALIGN1 = "/usr/bin";
532         static const char usr_sbin[] ALIGN1 = "/usr/sbin";
533         static const char *const install_dir[] = {
534                 &usr_bin [8], /* "", equivalent to "/" for concat_path_file() */
535                 &usr_bin [4], /* "/bin" */
536                 &usr_sbin[4], /* "/sbin" */
537                 usr_bin,
538                 usr_sbin
539         };
540
541         int (*lf)(const char *, const char *) = link;
542         char *fpc;
543         int i;
544         int rc;
545
546         if (use_symbolic_links)
547                 lf = symlink;
548
549         for (i = 0; applets[i].name != NULL; i++) {
550                 fpc = concat_path_file(
551                                 install_dir[applets[i].install_loc],
552                                 applets[i].name);
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 struct bb_applet *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-2006 Erik Andersen, Rob Landley, and others.\n"
582                        "Licensed under GPLv2. See source distribution for full notice.\n"
583                        "\n"
584                        "Usage: busybox [function] [arguments]...\n"
585                        "   or: [function] [arguments]...\n"
586                        "\n"
587                        "\tBusyBox is a multi-call binary that combines many common Unix\n"
588                        "\tutilities into a single executable.  Most people will create a\n"
589                        "\tlink to busybox for each function they wish to use and BusyBox\n"
590                        "\twill act like whatever it was invoked as!\n"
591                        "\nCurrently defined functions:\n");
592                 col = 0;
593                 a = applets;
594                 while (a->name) {
595                         if (col > output_width) {
596                                 puts(",");
597                                 col = 0;
598                         }
599                         col += printf("%s%s", (col ? ", " : "\t"), a->name);
600                         a++;
601                 }
602                 puts("\n");
603                 return 0;
604         }
605
606         if (ENABLE_FEATURE_INSTALLER && strcmp(argv[1], "--install") == 0) {
607                 const char *busybox;
608                 busybox = xmalloc_readlink(bb_busybox_exec_path);
609                 if (!busybox)
610                         busybox = bb_busybox_exec_path;
611                 /* -s makes symlinks */
612                 install_links(busybox, argv[2] && strcmp(argv[2], "-s") == 0);
613                 return 0;
614         }
615
616         if (strcmp(argv[1], "--help") == 0) {
617                 /* "busybox --help [<applet>]" */
618                 if (!argv[2])
619                         goto help;
620                 /* convert to "<applet> --help" */
621                 argv[0] = argv[2];
622                 argv[2] = NULL;
623         } else {
624                 /* "busybox <applet> arg1 arg2 ..." */
625                 argv++;
626         }
627         /* We support "busybox /a/path/to/applet args..." too. Allows for
628          * "#!/bin/busybox"-style wrappers */
629         applet_name = bb_get_last_path_component_nostrip(argv[0]);
630         run_applet_and_exit(applet_name, argv);
631         bb_error_msg_and_die("applet not found");
632 }
633
634 void run_appletstruct_and_exit(const struct bb_applet *applet, char **argv)
635 {
636         int argc = 1;
637
638         while (argv[argc])
639                 argc++;
640
641         /* Reinit some shared global data */
642         optind = 1;
643         xfunc_error_retval = EXIT_FAILURE;
644
645         applet_name = applet->name;
646         if (argc == 2 && !strcmp(argv[1], "--help"))
647                 bb_show_usage();
648         if (ENABLE_FEATURE_SUID)
649                 check_suid(applet);
650         exit(applet->main(argc, argv));
651 }
652
653 void run_applet_and_exit(const char *name, char **argv)
654 {
655         const struct bb_applet *applet = find_applet_by_name(name);
656         if (applet)
657                 run_appletstruct_and_exit(applet, argv);
658         if (!strncmp(name, "busybox", 7))
659                 exit(busybox_main(argv));
660 }
661
662
663 #if ENABLE_BUILD_LIBBUSYBOX
664 int lbb_main(int argc, char **argv)
665 #else
666 int main(int argc, char **argv)
667 #endif
668 {
669         lbb_prepare(argv);
670
671 #if !BB_MMU
672         /* NOMMU re-exec trick sets high-order bit in first byte of name */
673         if (argv[0][0] & 0x80) {
674                 re_execed = 1;
675                 argv[0][0] &= 0x7f;
676         }
677 #endif
678         applet_name = argv[0];
679         if (applet_name[0] == '-')
680                 applet_name++;
681         applet_name = bb_basename(applet_name);
682
683         parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */
684
685         run_applet_and_exit(applet_name, argv);
686         bb_error_msg_and_die("applet not found");
687 }