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