c8e85cdb1c5f0ca5a960e87f0f13ef080d0ac060
[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(struct BB_applet) - 1;
50
51
52 const struct BB_applet *current_applet;
53 const char *applet_name ATTRIBUTE_EXTERNALLY_VISIBLE;
54 #ifdef BB_NOMMU
55 smallint 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 smallint 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, lc, section;
139         char buffer[256];
140         struct stat st;
141
142         assert(!suid_config); /* Should be set to NULL by bss init. */
143
144         if ((stat(config_file, &st) != 0)       /* No config file? */
145          || !S_ISREG(st.st_mode)                /* Not a regular file? */
146          || (st.st_uid != 0)                    /* Not owned by root? */
147          || (st.st_mode & (S_IWGRP | S_IWOTH))  /* Writable by non-root? */
148          || !(f = fopen(config_file, "r"))      /* Cannot open? */
149         ) {
150                 return;
151         }
152
153         suid_cfg_readable = 1;
154         sct_head = NULL;
155         section = lc = 0;
156
157         while (1) {
158                 s = buffer;
159
160                 if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */
161                         if (ferror(f)) {   /* Make sure it wasn't a read error. */
162                                 parse_error("reading");
163                         }
164                         fclose(f);
165                         suid_config = sct_head; /* Success, so set the pointer. */
166                         return;
167                 }
168
169                 lc++;                                   /* Got a (partial) line. */
170
171                 /* If a line is too long for our buffer, we consider it an error.
172                  * The following test does mistreat one corner case though.
173                  * If the final line of the file does not end with a newline and
174                  * yet exactly fills the buffer, it will be treated as too long
175                  * even though there isn't really a problem.  But it isn't really
176                  * worth adding code to deal with such an unlikely situation, and
177                  * we do err on the side of caution.  Besides, the line would be
178                  * too long if it did end with a newline. */
179                 if (!strchr(s, '\n') && !feof(f)) {
180                         parse_error("line too long");
181                 }
182
183                 /* Trim leading and trailing whitespace, ignoring comments, and
184                  * check if the resulting string is empty. */
185                 s = get_trimmed_slice(s, strchrnul(s, '#'));
186                 if (!*s) {
187                         continue;
188                 }
189
190                 /* Check for a section header. */
191
192                 if (*s == '[') {
193                         /* Unlike the old code, we ignore leading and trailing
194                          * whitespace for the section name.  We also require that
195                          * there are no stray characters after the closing bracket. */
196                         e = strchr(s, ']');
197                         if (!e   /* Missing right bracket? */
198                          || e[1] /* Trailing characters? */
199                          || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */
200                         ) {
201                                 parse_error("section header");
202                         }
203                         /* Right now we only have one section so just check it.
204                          * If more sections are added in the future, please don't
205                          * resort to cascading ifs with multiple strcasecmp calls.
206                          * That kind of bloated code is all too common.  A loop
207                          * and a string table would be a better choice unless the
208                          * number of sections is very small. */
209                         if (strcasecmp(s, "SUID") == 0) {
210                                 section = 1;
211                                 continue;
212                         }
213                         section = -1;   /* Unknown section so set to skip. */
214                         continue;
215                 }
216
217                 /* Process sections. */
218
219                 if (section == 1) {             /* SUID */
220                         /* Since we trimmed leading and trailing space above, we're
221                          * now looking for strings of the form
222                          *    <key>[::space::]*=[::space::]*<value>
223                          * where both key and value could contain inner whitespace. */
224
225                         /* First get the key (an applet name in our case). */
226                         e = strchr(s, '=');
227                         if (e) {
228                                 s = get_trimmed_slice(s, e);
229                         }
230                         if (!e || !*s) {        /* Missing '=' or empty key. */
231                                 parse_error("keyword");
232                         }
233
234                         /* Ok, we have an applet name.  Process the rhs if this
235                          * applet is currently built in and ignore it otherwise.
236                          * Note: this can hide config file bugs which only pop
237                          * up when the busybox configuration is changed. */
238                         applet = find_applet_by_name(s);
239                         if (applet) {
240                                 /* Note: We currently don't check for duplicates!
241                                  * The last config line for each applet will be the
242                                  * one used since we insert at the head of the list.
243                                  * I suppose this could be considered a feature. */
244                                 sct = xmalloc(sizeof(struct BB_suid_config));
245                                 sct->m_applet = applet;
246                                 sct->m_mode = 0;
247                                 sct->m_next = sct_head;
248                                 sct_head = sct;
249
250                                 /* Get the specified mode. */
251
252                                 e = skip_whitespace(e+1);
253
254                                 for (i = 0; i < 3; i++) {
255                                         const char *q;
256                                         q = strchrnul(mode_chars + 5*i, *e++);
257                                         if (!*q) {
258                                                 parse_error("mode");
259                                         }
260                                         /* Adjust by -i to account for nul. */
261                                         sct->m_mode |= mode_mask[(q - mode_chars) - i];
262                                 }
263
264                                 /* Now get the the user/group info. */
265
266                                 s = skip_whitespace(e);
267
268                                 /* Note: we require whitespace between the mode and the
269                                  * user/group info. */
270                                 if ((s == e) || !(e = strchr(s, '.'))) {
271                                         parse_error("<uid>.<gid>");
272                                 }
273                                 *e++ = '\0';
274
275                                 /* We can't use get_ug_id here since it would exit()
276                                  * if a uid or gid was not found.  Oh well... */
277                                 sct->m_uid = bb_strtoul(s, NULL, 10);
278                                 if (errno) {
279                                         struct passwd *pwd = getpwnam(s);
280                                         if (!pwd) {
281                                                 parse_error("user");
282                                         }
283                                         sct->m_uid = pwd->pw_uid;
284                                 }
285
286                                 sct->m_gid = bb_strtoul(e, NULL, 10);
287                                 if (errno) {
288                                         struct group *grp;
289                                         grp = getgrnam(e);
290                                         if (!grp) {
291                                                 parse_error("group");
292                                         }
293                                         sct->m_gid = grp->gr_gid;
294                                 }
295                         }
296                         continue;
297                 }
298
299                 /* Unknown sections are ignored. */
300
301                 /* Encountering configuration lines prior to seeing a
302                  * section header is treated as an error.  This is how
303                  * the old code worked, but it may not be desirable.
304                  * We may want to simply ignore such lines in case they
305                  * are used in some future version of busybox. */
306                 if (!section) {
307                         parse_error("keyword outside section");
308                 }
309
310         } /* while (1) */
311
312  pe_label:
313         fprintf(stderr, "Parse error in %s, line %d: %s\n",
314                         config_file, lc, errmsg);
315
316         fclose(f);
317         /* Release any allocated memory before returning. */
318         while (sct_head) {
319                 sct = sct_head->m_next;
320                 free(sct_head);
321                 sct_head = sct;
322         }
323 }
324 #else
325 #define parse_config_file() ((void)0)
326 #endif /* FEATURE_SUID_CONFIG */
327
328
329 #if ENABLE_FEATURE_SUID
330 static void check_suid(const struct BB_applet *applet)
331 {
332         uid_t ruid = getuid();               /* real [ug]id */
333         uid_t rgid = getgid();
334
335 #if ENABLE_FEATURE_SUID_CONFIG
336         if (suid_cfg_readable) {
337                 struct BB_suid_config *sct;
338                 mode_t m;
339
340                 for (sct = suid_config; sct; sct = sct->m_next) {
341                         if (sct->m_applet == applet)
342                                 goto found;
343                 }
344                 /* default: drop all privileges */
345                 xsetgid(rgid);
346                 xsetuid(ruid);
347                 return;
348  found:
349                 m = sct->m_mode;
350                 if (sct->m_uid == ruid)
351                         /* same uid */
352                         m >>= 6;
353                 else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid))
354                         /* same group / in group */
355                         m >>= 3;
356
357                 if (!(m & S_IXOTH))           /* is x bit not set ? */
358                         bb_error_msg_and_die("you have no permission to run this applet!");
359
360                 if (sct->m_gid != 0) {
361                         /* _both_ have to be set for sgid */
362                         if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) {
363                                 xsetgid(sct->m_gid);
364                         } else xsetgid(rgid); /* no sgid -> drop */
365                 }
366                 if (sct->m_uid != 0) {
367                         if (sct->m_mode & S_ISUID) xsetuid(sct->m_uid);
368                         else xsetuid(ruid); /* no suid -> drop */
369                 }
370                 return;
371         }
372 #if !ENABLE_FEATURE_SUID_CONFIG_QUIET
373         {
374                 static smallint onetime = 0;
375
376                 if (!onetime) {
377                         onetime = 1;
378                         fprintf(stderr, "Using fallback suid method\n");
379                 }
380         }
381 #endif
382 #endif
383
384         if (applet->need_suid == _BB_SUID_ALWAYS) {
385                 if (geteuid())
386                         bb_error_msg_and_die("applet requires root privileges!");
387         } else if (applet->need_suid == _BB_SUID_NEVER) {
388                 xsetgid(rgid);                          /* drop all privileges */
389                 xsetuid(ruid);
390         }
391 }
392 #else
393 #define check_suid(x) ((void)0)
394 #endif /* FEATURE_SUID */
395
396
397 #if ENABLE_FEATURE_COMPRESS_USAGE
398
399 #include "usage_compressed.h"
400 #include "unarchive.h"
401
402 static const char *unpack_usage_messages(void)
403 {
404         int input[2], output[2], pid;
405         char *buf;
406
407         if (pipe(input) < 0 || pipe(output) < 0)
408                 exit(1);
409
410 //TODO: not NOMMU friendly!
411         pid = fork();
412         switch (pid) {
413         case -1: /* error */
414                 exit(1);
415         case 0: /* child */
416                 close(input[1]);
417                 close(output[0]);
418                 uncompressStream(input[0], output[1]);
419                 exit(0);
420         }
421         /* parent */
422
423         close(input[0]);
424         close(output[1]);
425         pid = fork();
426         switch (pid) {
427         case -1: /* error */
428                 exit(1);
429         case 0: /* child */
430                 full_write(input[1], packed_usage, sizeof(packed_usage));
431                 exit(0);
432         }
433         /* parent */
434         close(input[1]);
435
436         buf = xmalloc(SIZEOF_usage_messages);
437         full_read(output[0], buf, SIZEOF_usage_messages);
438         return buf;
439 }
440 #else
441 #define unpack_usage_messages() usage_messages
442 #endif /* FEATURE_COMPRESS_USAGE */
443
444
445 void bb_show_usage(void)
446 {
447         if (ENABLE_SHOW_USAGE) {
448                 const char *format_string;
449                 const char *usage_string = unpack_usage_messages();
450                 int i;
451
452                 i = current_applet - applets;
453                 while (i) {
454                         while (*usage_string++) continue;
455                         i--;
456                 }
457
458                 format_string = "%s\n\nUsage: %s %s\n\n";
459                 if (*usage_string == '\b')
460                         format_string = "%s\n\nNo help available.\n\n";
461                 fprintf(stderr, format_string, bb_msg_full_version,
462                                         applet_name, usage_string);
463         }
464
465         exit(xfunc_error_retval);
466 }
467
468
469 static int applet_name_compare(const void *name, const void *vapplet)
470 {
471         const struct BB_applet *applet = vapplet;
472
473         return strcmp(name, applet->name);
474 }
475
476 const struct BB_applet *find_applet_by_name(const char *name)
477 {
478         /* Do a binary search to find the applet entry given the name. */
479         return bsearch(name, applets, NUM_APPLETS, sizeof(struct BB_applet),
480                                 applet_name_compare);
481 }
482
483
484 #if ENABLE_FEATURE_INSTALLER
485 /*
486  * directory table
487  *              this should be consistent w/ the enum, busybox.h::Location,
488  *              or else...
489  */
490 static const char usr_bin [] = "/usr/bin";
491 static const char usr_sbin[] = "/usr/sbin";
492
493 static const char *const install_dir[] = {
494         &usr_bin [8], /* "", equivalent to "/" for concat_path_file() */
495         &usr_bin [4], /* "/bin" */
496         &usr_sbin[4], /* "/sbin" */
497         usr_bin,
498         usr_sbin
499 };
500
501 /* create (sym)links for each applet */
502 static void install_links(const char *busybox, int use_symbolic_links)
503 {
504         int (*lf)(const char *, const char *) = link;
505         char *fpc;
506         int i;
507         int rc;
508
509         if (use_symbolic_links)
510                 lf = symlink;
511
512         for (i = 0; applets[i].name != NULL; i++) {
513                 fpc = concat_path_file(
514                                 install_dir[applets[i].location],
515                                 applets[i].name);
516                 rc = lf(busybox, fpc);
517                 if (rc != 0 && errno != EEXIST) {
518                         bb_perror_msg("%s", fpc);
519                 }
520                 free(fpc);
521         }
522 }
523 #else
524 #define install_links(x,y) ((void)0)
525 #endif /* FEATURE_INSTALLER */
526
527
528 /* If we were called as "busybox..." */
529 static int busybox_main(int argc, char **argv)
530 {
531         if (ENABLE_FEATURE_INSTALLER && argc > 1 && !strcmp(argv[1], "--install")) {
532                 int use_symbolic_links = 0;
533                 char *busybox;
534
535                 /* to use symlinks, or not to use symlinks... */
536                 if (argc > 2)
537                         if (strcmp(argv[2], "-s") == 0)
538                                 use_symbolic_links = 1;
539
540                 /* link */
541                 busybox = xmalloc_readlink_or_warn("/proc/self/exe");
542                 if (!busybox)
543                         return 1;
544                 install_links(busybox, use_symbolic_links);
545                 if (ENABLE_FEATURE_CLEAN_UP)
546                         free(busybox);
547                 return 0;
548         }
549
550         /* Deal with --help. Also print help when called with no arguments */
551
552         if (argc == 1 || !strcmp(argv[1], "--help") ) {
553                 if (argc > 2) {
554                         /* set name for proper "<name>: applet not found" */
555                         applet_name = argv[2];
556                         run_applet_by_name(applet_name, 2, 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 run_applet_by_name(argv[1], argc - 1, argv + 1);
595
596         bb_error_msg_and_die("applet not found");
597 }
598
599 void run_current_applet_and_exit(int argc, char **argv)
600 {
601         applet_name = current_applet->name;
602         if (argc == 2 && !strcmp(argv[1], "--help"))
603                 bb_show_usage();
604         if (ENABLE_FEATURE_SUID)
605                 check_suid(current_applet);
606         exit(current_applet->main(argc, argv));
607 }
608
609 void run_applet_by_name(const char *name, int argc, char **argv)
610 {
611         current_applet = find_applet_by_name(name);
612         if (current_applet)
613                 run_current_applet_and_exit(argc, argv);
614         if (!strncmp(name, "busybox", 7))
615                 exit(busybox_main(argc, argv));
616 }
617
618
619 int main(int argc, char **argv)
620 {
621         const char *s;
622
623         applet_name = argv[0];
624 #ifdef BB_NOMMU
625         /* NOMMU re-exec trick sets high-order bit in first byte of name */
626         if (applet_name[0] & 0x80) {
627                 re_execed = 1;
628                 applet_name[0] &= 0x7f;
629         }
630 #endif
631         if (applet_name[0] == '-')
632                 applet_name++;
633         s = strrchr(applet_name, '/');
634         if (s)
635                 applet_name = s + 1;
636
637         if (ENABLE_FEATURE_SUID_CONFIG)
638                 parse_config_file();
639
640         /* Set locale for everybody except 'init' */
641         if (ENABLE_LOCALE_SUPPORT && getpid() != 1)
642                 setlocale(LC_ALL, "");
643
644         run_applet_by_name(applet_name, argc, argv);
645         bb_error_msg_and_die("applet not found");
646 }