Move applets/applet.c into libbb, allows to get rid of --whole-archive (smaller code). Tested in static and shared mode.
No binary (build failed)
—
(log yok)
| @@ -1,21 +1,15 @@ | |
| /* vi: set sw=4 ts=4: */ | |
| /* | |
| - | * Utility routines. |
| + | * Stub for linking busybox binary against libbusybox. |
| * | |
| - | * Copyright (C) tons of folks. Tracking down who wrote what |
| - | * isn't something I'm going to worry about... If you wrote something |
| - | * here, please feel free to acknowledge your work. |
| + | * Copyright (C) 2007 Denis Vlasenko |
| * | |
| - | * Based in part on code from sash, Copyright (c) 1999 by David I. Bell |
| - | * Permission has been granted to redistribute this code under the GPL. |
| - | * |
| - | * Licensed under GPLv2 or later, see file License in this tarball for details. |
| + | * Licensed under GPLv2, see file License in this tarball for details. |
| */ | |
| #include <assert.h> | |
| #include "busybox.h" | |
| - | |
| /* Apparently uclibc defines __GLIBC__ (compat trick?). Oh well. */ | |
| #if ENABLE_STATIC && defined(__GLIBC__) && !defined(__UCLIBC__) | |
| #warning Static linking against glibc produces buggy executables | |
| @@ -27,532 +21,9 @@ | |
| #error Aborting compilation. | |
| #endif | |
| - | |
| - | const char *applet_name; |
| - | #if !BB_MMU |
| - | bool re_execed; |
| - | #endif |
| - | |
| - | USE_FEATURE_SUID(static uid_t ruid;) /* real uid */ |
| - | |
| - | #if ENABLE_FEATURE_SUID_CONFIG |
| - | |
| - | /* applets[] is const, so we have to define this "override" structure */ |
| - | static struct BB_suid_config { |
| - | const struct bb_applet *m_applet; |
| - | uid_t m_uid; |
| - | gid_t m_gid; |
| - | mode_t m_mode; |
| - | struct BB_suid_config *m_next; |
| - | } *suid_config; |
| - | |
| - | static bool suid_cfg_readable; |
| - | |
| - | /* check if u is member of group g */ |
| - | static int ingroup(uid_t u, gid_t g) |
| - | { |
| - | struct group *grp = getgrgid(g); |
| - | |
| - | if (grp) { |
| - | char **mem; |
| - | |
| - | for (mem = grp->gr_mem; *mem; mem++) { |
| - | struct passwd *pwd = getpwnam(*mem); |
| - | |
| - | if (pwd && (pwd->pw_uid == u)) |
| - | return 1; |
| - | } |
| - | } |
| - | return 0; |
| - | } |
| - | |
| - | /* This should probably be a libbb routine. In that case, |
| - | * I'd probably rename it to something like bb_trimmed_slice. |
| - | */ |
| - | static char *get_trimmed_slice(char *s, char *e) |
| - | { |
| - | /* First, consider the value at e to be nul and back up until we |
| - | * reach a non-space char. Set the char after that (possibly at |
| - | * the original e) to nul. */ |
| - | while (e-- > s) { |
| - | if (!isspace(*e)) { |
| - | break; |
| - | } |
| - | } |
| - | e[1] = '\0'; |
| - | |
| - | /* Next, advance past all leading space and return a ptr to the |
| - | * first non-space char; possibly the terminating nul. */ |
| - | return skip_whitespace(s); |
| - | } |
| - | |
| - | /* Don't depend on the tools to combine strings. */ |
| - | static const char config_file[] ALIGN1 = "/etc/busybox.conf"; |
| - | |
| - | /* We don't supply a value for the nul, so an index adjustment is |
| - | * necessary below. Also, we use unsigned short here to save some |
| - | * space even though these are really mode_t values. */ |
| - | static const unsigned short mode_mask[] ALIGN2 = { |
| - | /* SST sst xxx --- */ |
| - | S_ISUID, S_ISUID|S_IXUSR, S_IXUSR, 0, /* user */ |
| - | S_ISGID, S_ISGID|S_IXGRP, S_IXGRP, 0, /* group */ |
| - | 0, S_IXOTH, S_IXOTH, 0 /* other */ |
| - | }; |
| - | |
| - | #define parse_error(x) do { errmsg = x; goto pe_label; } while (0) |
| - | |
| - | static void parse_config_file(void) |
| - | { |
| - | struct BB_suid_config *sct_head; |
| - | struct BB_suid_config *sct; |
| - | const struct bb_applet *applet; |
| - | FILE *f; |
| - | const char *errmsg; |
| - | char *s; |
| - | char *e; |
| - | int i; |
| - | unsigned lc; |
| - | smallint section; |
| - | char buffer[256]; |
| - | struct stat st; |
| - | |
| - | assert(!suid_config); /* Should be set to NULL by bss init. */ |
| - | |
| - | ruid = getuid(); |
| - | if (ruid == 0) /* run by root - don't need to even read config file */ |
| - | return; |
| - | |
| - | if ((stat(config_file, &st) != 0) /* No config file? */ |
| - | || !S_ISREG(st.st_mode) /* Not a regular file? */ |
| - | || (st.st_uid != 0) /* Not owned by root? */ |
| - | || (st.st_mode & (S_IWGRP | S_IWOTH)) /* Writable by non-root? */ |
| - | || !(f = fopen(config_file, "r")) /* Cannot open? */ |
| - | ) { |
| - | return; |
| - | } |
| - | |
| - | suid_cfg_readable = 1; |
| - | sct_head = NULL; |
| - | section = lc = 0; |
| - | |
| - | while (1) { |
| - | s = buffer; |
| - | |
| - | if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */ |
| - | if (ferror(f)) { /* Make sure it wasn't a read error. */ |
| - | parse_error("reading"); |
| - | } |
| - | fclose(f); |
| - | suid_config = sct_head; /* Success, so set the pointer. */ |
| - | return; |
| - | } |
| - | |
| - | lc++; /* Got a (partial) line. */ |
| - | |
| - | /* If a line is too long for our buffer, we consider it an error. |
| - | * The following test does mistreat one corner case though. |
| - | * If the final line of the file does not end with a newline and |
| - | * yet exactly fills the buffer, it will be treated as too long |
| - | * even though there isn't really a problem. But it isn't really |
| - | * worth adding code to deal with such an unlikely situation, and |
| - | * we do err on the side of caution. Besides, the line would be |
| - | * too long if it did end with a newline. */ |
| - | if (!strchr(s, '\n') && !feof(f)) { |
| - | parse_error("line too long"); |
| - | } |
| - | |
| - | /* Trim leading and trailing whitespace, ignoring comments, and |
| - | * check if the resulting string is empty. */ |
| - | s = get_trimmed_slice(s, strchrnul(s, '#')); |
| - | if (!*s) { |
| - | continue; |
| - | } |
| - | |
| - | /* Check for a section header. */ |
| - | |
| - | if (*s == '[') { |
| - | /* Unlike the old code, we ignore leading and trailing |
| - | * whitespace for the section name. We also require that |
| - | * there are no stray characters after the closing bracket. */ |
| - | e = strchr(s, ']'); |
| - | if (!e /* Missing right bracket? */ |
| - | || e[1] /* Trailing characters? */ |
| - | || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */ |
| - | ) { |
| - | parse_error("section header"); |
| - | } |
| - | /* Right now we only have one section so just check it. |
| - | * If more sections are added in the future, please don't |
| - | * resort to cascading ifs with multiple strcasecmp calls. |
| - | * That kind of bloated code is all too common. A loop |
| - | * and a string table would be a better choice unless the |
| - | * number of sections is very small. */ |
| - | if (strcasecmp(s, "SUID") == 0) { |
| - | section = 1; |
| - | continue; |
| - | } |
| - | section = -1; /* Unknown section so set to skip. */ |
| - | continue; |
| - | } |
| - | |
| - | /* Process sections. */ |
| - | |
| - | if (section == 1) { /* SUID */ |
| - | /* Since we trimmed leading and trailing space above, we're |
| - | * now looking for strings of the form |
| - | * <key>[::space::]*=[::space::]*<value> |
| - | * where both key and value could contain inner whitespace. */ |
| - | |
| - | /* First get the key (an applet name in our case). */ |
| - | e = strchr(s, '='); |
| - | if (e) { |
| - | s = get_trimmed_slice(s, e); |
| - | } |
| - | if (!e || !*s) { /* Missing '=' or empty key. */ |
| - | parse_error("keyword"); |
| - | } |
| - | |
| - | /* Ok, we have an applet name. Process the rhs if this |
| - | * applet is currently built in and ignore it otherwise. |
| - | * Note: this can hide config file bugs which only pop |
| - | * up when the busybox configuration is changed. */ |
| - | applet = find_applet_by_name(s); |
| - | if (applet) { |
| - | /* Note: We currently don't check for duplicates! |
| - | * The last config line for each applet will be the |
| - | * one used since we insert at the head of the list. |
| - | * I suppose this could be considered a feature. */ |
| - | sct = xmalloc(sizeof(struct BB_suid_config)); |
| - | sct->m_applet = applet; |
| - | sct->m_mode = 0; |
| - | sct->m_next = sct_head; |
| - | sct_head = sct; |
| - | |
| - | /* Get the specified mode. */ |
| - | |
| - | e = skip_whitespace(e+1); |
| - | |
| - | for (i = 0; i < 3; i++) { |
| - | /* There are 4 chars + 1 nul for each of user/group/other. */ |
| - | static const char mode_chars[] ALIGN1 = "Ssx-\0" "Ssx-\0" "Ttx-"; |
| - | |
| - | const char *q; |
| - | q = strchrnul(mode_chars + 5*i, *e++); |
| - | if (!*q) { |
| - | parse_error("mode"); |
| - | } |
| - | /* Adjust by -i to account for nul. */ |
| - | sct->m_mode |= mode_mask[(q - mode_chars) - i]; |
| - | } |
| - | |
| - | /* Now get the the user/group info. */ |
| - | |
| - | s = skip_whitespace(e); |
| - | |
| - | /* Note: we require whitespace between the mode and the |
| - | * user/group info. */ |
| - | if ((s == e) || !(e = strchr(s, '.'))) { |
| - | parse_error("<uid>.<gid>"); |
| - | } |
| - | *e++ = '\0'; |
| - | |
| - | /* We can't use get_ug_id here since it would exit() |
| - | * if a uid or gid was not found. Oh well... */ |
| - | sct->m_uid = bb_strtoul(s, NULL, 10); |
| - | if (errno) { |
| - | struct passwd *pwd = getpwnam(s); |
| - | if (!pwd) { |
| - | parse_error("user"); |
| - | } |
| - | sct->m_uid = pwd->pw_uid; |
| - | } |
| - | |
| - | sct->m_gid = bb_strtoul(e, NULL, 10); |
| - | if (errno) { |
| - | struct group *grp; |
| - | grp = getgrnam(e); |
| - | if (!grp) { |
| - | parse_error("group"); |
| - | } |
| - | sct->m_gid = grp->gr_gid; |
| - | } |
| - | } |
| - | continue; |
| - | } |
| - | |
| - | /* Unknown sections are ignored. */ |
| - | |
| - | /* Encountering configuration lines prior to seeing a |
| - | * section header is treated as an error. This is how |
| - | * the old code worked, but it may not be desirable. |
| - | * We may want to simply ignore such lines in case they |
| - | * are used in some future version of busybox. */ |
| - | if (!section) { |
| - | parse_error("keyword outside section"); |
| - | } |
| - | |
| - | } /* while (1) */ |
| - | |
| - | pe_label: |
| - | fprintf(stderr, "Parse error in %s, line %d: %s\n", |
| - | config_file, lc, errmsg); |
| - | |
| - | fclose(f); |
| - | /* Release any allocated memory before returning. */ |
| - | while (sct_head) { |
| - | sct = sct_head->m_next; |
| - | free(sct_head); |
| - | sct_head = sct; |
| - | } |
| - | } |
| - | #else |
| - | static inline void parse_config_file(void) |
| - | { |
| - | USE_FEATURE_SUID(ruid = getuid();) |
| - | } |
| - | #endif /* FEATURE_SUID_CONFIG */ |
| - | |
| - | |
| - | #if ENABLE_FEATURE_SUID |
| - | static void check_suid(const struct bb_applet *applet) |
| - | { |
| - | gid_t rgid; /* real gid */ |
| - | |
| - | if (ruid == 0) /* set by parse_config_file() */ |
| - | return; /* run by root - no need to check more */ |
| - | rgid = getgid(); |
| - | |
| - | #if ENABLE_FEATURE_SUID_CONFIG |
| - | if (suid_cfg_readable) { |
| - | uid_t uid; |
| - | struct BB_suid_config *sct; |
| - | mode_t m; |
| - | |
| - | for (sct = suid_config; sct; sct = sct->m_next) { |
| - | if (sct->m_applet == applet) |
| - | goto found; |
| - | } |
| - | /* default: drop all privileges */ |
| - | xsetgid(rgid); |
| - | xsetuid(ruid); |
| - | return; |
| - | found: |
| - | m = sct->m_mode; |
| - | if (sct->m_uid == ruid) |
| - | /* same uid */ |
| - | m >>= 6; |
| - | else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid)) |
| - | /* same group / in group */ |
| - | m >>= 3; |
| - | |
| - | if (!(m & S_IXOTH)) /* is x bit not set ? */ |
| - | bb_error_msg_and_die("you have no permission to run this applet!"); |
| - | |
| - | /* _both_ sgid and group_exec have to be set for setegid */ |
| - | if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) |
| - | rgid = sct->m_gid; |
| - | /* else (no setegid) we will set egid = rgid */ |
| - | |
| - | /* We set effective AND saved ids. If saved-id is not set |
| - | * like we do below, seteiud(0) can still later succeed! */ |
| - | if (setresgid(-1, rgid, rgid)) |
| - | bb_perror_msg_and_die("setresgid"); |
| - | |
| - | /* do we have to set effective uid? */ |
| - | uid = ruid; |
| - | if (sct->m_mode & S_ISUID) |
| - | uid = sct->m_uid; |
| - | /* else (no seteuid) we will set euid = ruid */ |
| - | |
| - | if (setresuid(-1, uid, uid)) |
| - | bb_perror_msg_and_die("setresuid"); |
| - | return; |
| - | } |
| - | #if !ENABLE_FEATURE_SUID_CONFIG_QUIET |
| - | { |
| - | static bool onetime = 0; |
| - | |
| - | if (!onetime) { |
| - | onetime = 1; |
| - | fprintf(stderr, "Using fallback suid method\n"); |
| - | } |
| - | } |
| - | #endif |
| - | #endif |
| - | |
| - | if (applet->need_suid == _BB_SUID_ALWAYS) { |
| - | /* Real uid is not 0. If euid isn't 0 too, suid bit |
| - | * is most probably not set on our executable */ |
| - | if (geteuid()) |
| - | bb_error_msg_and_die("applet requires root privileges!"); |
| - | } else if (applet->need_suid == _BB_SUID_NEVER) { |
| - | xsetgid(rgid); /* drop all privileges */ |
| - | xsetuid(ruid); |
| - | } |
| - | } |
| - | #else |
| - | #define check_suid(x) ((void)0) |
| - | #endif /* FEATURE_SUID */ |
| - | |
| - | |
| - | #if ENABLE_FEATURE_INSTALLER |
| - | /* create (sym)links for each applet */ |
| - | static void install_links(const char *busybox, int use_symbolic_links) |
| - | { |
| - | /* directory table |
| - | * this should be consistent w/ the enum, |
| - | * busybox.h::bb_install_loc_t, or else... */ |
| - | static const char usr_bin [] ALIGN1 = "/usr/bin"; |
| - | static const char usr_sbin[] ALIGN1 = "/usr/sbin"; |
| - | static const char *const install_dir[] = { |
| - | &usr_bin [8], /* "", equivalent to "/" for concat_path_file() */ |
| - | &usr_bin [4], /* "/bin" */ |
| - | &usr_sbin[4], /* "/sbin" */ |
| - | usr_bin, |
| - | usr_sbin |
| - | }; |
| - | |
| - | int (*lf)(const char *, const char *) = link; |
| - | char *fpc; |
| - | int i; |
| - | int rc; |
| - | |
| - | if (use_symbolic_links) |
| - | lf = symlink; |
| - | |
| - | for (i = 0; applets[i].name != NULL; i++) { |
| - | fpc = concat_path_file( |
| - | install_dir[applets[i].install_loc], |
| - | applets[i].name); |
| - | rc = lf(busybox, fpc); |
| - | if (rc != 0 && errno != EEXIST) { |
| - | bb_simple_perror_msg(fpc); |
| - | } |
| - | free(fpc); |
| - | } |
| - | } |
| - | #else |
| - | #define install_links(x,y) ((void)0) |
| - | #endif /* FEATURE_INSTALLER */ |
| - | |
| - | |
| - | /* If we were called as "busybox..." */ |
| - | static int busybox_main(char **argv) |
| - | { |
| - | if (!argv[1]) { |
| - | /* Called without arguments */ |
| - | const struct bb_applet *a; |
| - | int col, output_width; |
| - | help: |
| - | output_width = 80; |
| - | if (ENABLE_FEATURE_AUTOWIDTH) { |
| - | /* Obtain the terminal width */ |
| - | get_terminal_width_height(0, &output_width, NULL); |
| - | } |
| - | /* leading tab and room to wrap */ |
| - | output_width -= sizeof("start-stop-daemon, ") + 8; |
| - | |
| - | printf("%s multi-call binary\n", bb_banner); /* reuse const string... */ |
| - | printf("Copyright (C) 1998-2006 Erik Andersen, Rob Landley, and others.\n" |
| - | "Licensed under GPLv2. See source distribution for full notice.\n" |
| - | "\n" |
| - | "Usage: busybox [function] [arguments]...\n" |
| - | " or: [function] [arguments]...\n" |
| - | "\n" |
| - | "\tBusyBox is a multi-call binary that combines many common Unix\n" |
| - | "\tutilities into a single executable. Most people will create a\n" |
| - | "\tlink to busybox for each function they wish to use and BusyBox\n" |
| - | "\twill act like whatever it was invoked as!\n" |
| - | "\nCurrently defined functions:\n"); |
| - | col = 0; |
| - | a = applets; |
| - | while (a->name) { |
| - | if (col > output_width) { |
| - | puts(","); |
| - | col = 0; |
| - | } |
| - | col += printf("%s%s", (col ? ", " : "\t"), a->name); |
| - | a++; |
| - | } |
| - | puts("\n"); |
| - | return 0; |
| - | } |
| - | |
| - | if (ENABLE_FEATURE_INSTALLER && strcmp(argv[1], "--install") == 0) { |
| - | const char *busybox; |
| - | busybox = xmalloc_readlink(bb_busybox_exec_path); |
| - | if (!busybox) |
| - | busybox = bb_busybox_exec_path; |
| - | /* -s makes symlinks */ |
| - | install_links(busybox, argv[2] && strcmp(argv[2], "-s") == 0); |
| - | return 0; |
| - | } |
| - | |
| - | if (strcmp(argv[1], "--help") == 0) { |
| - | /* "busybox --help [<applet>]" */ |
| - | if (!argv[2]) |
| - | goto help; |
| - | /* convert to "<applet> --help" */ |
| - | argv[0] = argv[2]; |
| - | argv[2] = NULL; |
| - | } else { |
| - | /* "busybox <applet> arg1 arg2 ..." */ |
| - | argv++; |
| - | } |
| - | /* We support "busybox /a/path/to/applet args..." too. Allows for |
| - | * "#!/bin/busybox"-style wrappers */ |
| - | applet_name = bb_get_last_path_component_nostrip(argv[0]); |
| - | run_applet_and_exit(applet_name, argv); |
| - | bb_error_msg_and_die("applet not found"); |
| - | } |
| - | |
| - | void run_appletstruct_and_exit(const struct bb_applet *applet, char **argv) |
| - | { |
| - | int argc = 1; |
| - | |
| - | while (argv[argc]) |
| - | argc++; |
| - | |
| - | /* Reinit some shared global data */ |
| - | optind = 1; |
| - | xfunc_error_retval = EXIT_FAILURE; |
| - | |
| - | applet_name = applet->name; |
| - | if (argc == 2 && !strcmp(argv[1], "--help")) |
| - | bb_show_usage(); |
| - | if (ENABLE_FEATURE_SUID) |
| - | check_suid(applet); |
| - | exit(applet->main(argc, argv)); |
| - | } |
| - | |
| - | void run_applet_and_exit(const char *name, char **argv) |
| - | { |
| - | const struct bb_applet *applet = find_applet_by_name(name); |
| - | if (applet) |
| - | run_appletstruct_and_exit(applet, argv); |
| - | if (!strncmp(name, "busybox", 7)) |
| - | exit(busybox_main(argv)); |
| - | } |
| - | |
| - | |
| + | #if ENABLE_BUILD_LIBBUSYBOX |
| int main(int argc, char **argv) | |
| { | |
| - | bbox_prepare_main(argv); |
| - | |
| - | #if !BB_MMU |
| - | /* NOMMU re-exec trick sets high-order bit in first byte of name */ |
| - | if (argv[0][0] & 0x80) { |
| - | re_execed = 1; |
| - | argv[0][0] &= 0x7f; |
| - | } |
| - | #endif |
| - | applet_name = argv[0]; |
| - | if (applet_name[0] == '-') |
| - | applet_name++; |
| - | applet_name = bb_basename(applet_name); |
| - | |
| - | parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */ |
| - | |
| - | run_applet_and_exit(applet_name, argv); |
| - | bb_error_msg_and_die("applet not found"); |
| + | return libbusybox_main(argc, argv); |
| } | |
| + | #endif |
| @@ -52,5 +52,8 @@ struct bb_applet { | |
| extern const struct bb_applet applets[]; | |
| extern const unsigned short NUM_APPLETS; | |
| void bbox_prepare_main(char **argv); | |
| + | #if ENABLE_BUILD_LIBBUSYBOX |
| + | int libbusybox_main(int argc, char **argv); |
| + | #endif |
| #endif /* _BB_INTERNAL_H_ */ | |
| @@ -1,11 +1,15 @@ | |
| /* vi: set sw=4 ts=4: */ | |
| /* | |
| - | * Support for main() which needs to end up in libbusybox, not busybox, |
| - | * if one builds libbusybox. |
| + | * Utility routines. |
| * | |
| - | * Copyright (C) 2007 Denys Vlasenko <[email protected]> |
| + | * Copyright (C) tons of folks. Tracking down who wrote what |
| + | * isn't something I'm going to worry about... If you wrote something |
| + | * here, please feel free to acknowledge your work. |
| * | |
| - | * Licensed under GPLv2, see file License in this tarball for details. |
| + | * Based in part on code from sash, Copyright (c) 1999 by David I. Bell |
| + | * Permission has been granted to redistribute this code under the GPL. |
| + | * |
| + | * Licensed under GPLv2 or later, see file License in this tarball for details. |
| */ | |
| #include <assert.h> | |
| @@ -140,3 +144,544 @@ void bbox_prepare_main(char **argv) | |
| bb_show_usage(); | |
| #endif | |
| } | |
| + | |
| + | /* The code below can well be in applets/applets.c, as it is used only |
| + | * for busybox binary, not "individual" binaries. |
| + | * However, keeping it here and linking it into libbusybox.so |
| + | * (together with remaining tiny applets/applets.o) |
| + | * makes it possible to avoid --whole-archive at link time. |
| + | * This makes (shared busybox) + libbusybox smaller. |
| + | * (--gc-sections would be even better....) |
| + | */ |
| + | |
| + | const char *applet_name; |
| + | #if !BB_MMU |
| + | bool re_execed; |
| + | #endif |
| + | |
| + | USE_FEATURE_SUID(static uid_t ruid;) /* real uid */ |
| + | |
| + | #if ENABLE_FEATURE_SUID_CONFIG |
| + | |
| + | /* applets[] is const, so we have to define this "override" structure */ |
| + | static struct BB_suid_config { |
| + | const struct bb_applet *m_applet; |
| + | uid_t m_uid; |
| + | gid_t m_gid; |
| + | mode_t m_mode; |
| + | struct BB_suid_config *m_next; |
| + | } *suid_config; |
| + | |
| + | static bool suid_cfg_readable; |
| + | |
| + | /* check if u is member of group g */ |
| + | static int ingroup(uid_t u, gid_t g) |
| + | { |
| + | struct group *grp = getgrgid(g); |
| + | |
| + | if (grp) { |
| + | char **mem; |
| + | |
| + | for (mem = grp->gr_mem; *mem; mem++) { |
| + | struct passwd *pwd = getpwnam(*mem); |
| + | |
| + | if (pwd && (pwd->pw_uid == u)) |
| + | return 1; |
| + | } |
| + | } |
| + | return 0; |
| + | } |
| + | |
| + | /* This should probably be a libbb routine. In that case, |
| + | * I'd probably rename it to something like bb_trimmed_slice. |
| + | */ |
| + | static char *get_trimmed_slice(char *s, char *e) |
| + | { |
| + | /* First, consider the value at e to be nul and back up until we |
| + | * reach a non-space char. Set the char after that (possibly at |
| + | * the original e) to nul. */ |
| + | while (e-- > s) { |
| + | if (!isspace(*e)) { |
| + | break; |
| + | } |
| + | } |
| + | e[1] = '\0'; |
| + | |
| + | /* Next, advance past all leading space and return a ptr to the |
| + | * first non-space char; possibly the terminating nul. */ |
| + | return skip_whitespace(s); |
| + | } |
| + | |
| + | /* Don't depend on the tools to combine strings. */ |
| + | static const char config_file[] ALIGN1 = "/etc/busybox.conf"; |
| + | |
| + | /* We don't supply a value for the nul, so an index adjustment is |
| + | * necessary below. Also, we use unsigned short here to save some |
| + | * space even though these are really mode_t values. */ |
| + | static const unsigned short mode_mask[] ALIGN2 = { |
| + | /* SST sst xxx --- */ |
| + | S_ISUID, S_ISUID|S_IXUSR, S_IXUSR, 0, /* user */ |
| + | S_ISGID, S_ISGID|S_IXGRP, S_IXGRP, 0, /* group */ |
| + | 0, S_IXOTH, S_IXOTH, 0 /* other */ |
| + | }; |
| + | |
| + | #define parse_error(x) do { errmsg = x; goto pe_label; } while (0) |
| + | |
| + | static void parse_config_file(void) |
| + | { |
| + | struct BB_suid_config *sct_head; |
| + | struct BB_suid_config *sct; |
| + | const struct bb_applet *applet; |
| + | FILE *f; |
| + | const char *errmsg; |
| + | char *s; |
| + | char *e; |
| + | int i; |
| + | unsigned lc; |
| + | smallint section; |
| + | char buffer[256]; |
| + | struct stat st; |
| + | |
| + | assert(!suid_config); /* Should be set to NULL by bss init. */ |
| + | |
| + | ruid = getuid(); |
| + | if (ruid == 0) /* run by root - don't need to even read config file */ |
| + | return; |
| + | |
| + | if ((stat(config_file, &st) != 0) /* No config file? */ |
| + | || !S_ISREG(st.st_mode) /* Not a regular file? */ |
| + | || (st.st_uid != 0) /* Not owned by root? */ |
| + | || (st.st_mode & (S_IWGRP | S_IWOTH)) /* Writable by non-root? */ |
| + | || !(f = fopen(config_file, "r")) /* Cannot open? */ |
| + | ) { |
| + | return; |
| + | } |
| + | |
| + | suid_cfg_readable = 1; |
| + | sct_head = NULL; |
| + | section = lc = 0; |
| + | |
| + | while (1) { |
| + | s = buffer; |
| + | |
| + | if (!fgets(s, sizeof(buffer), f)) { /* Are we done? */ |
| + | if (ferror(f)) { /* Make sure it wasn't a read error. */ |
| + | parse_error("reading"); |
| + | } |
| + | fclose(f); |
| + | suid_config = sct_head; /* Success, so set the pointer. */ |
| + | return; |
| + | } |
| + | |
| + | lc++; /* Got a (partial) line. */ |
| + | |
| + | /* If a line is too long for our buffer, we consider it an error. |
| + | * The following test does mistreat one corner case though. |
| + | * If the final line of the file does not end with a newline and |
| + | * yet exactly fills the buffer, it will be treated as too long |
| + | * even though there isn't really a problem. But it isn't really |
| + | * worth adding code to deal with such an unlikely situation, and |
| + | * we do err on the side of caution. Besides, the line would be |
| + | * too long if it did end with a newline. */ |
| + | if (!strchr(s, '\n') && !feof(f)) { |
| + | parse_error("line too long"); |
| + | } |
| + | |
| + | /* Trim leading and trailing whitespace, ignoring comments, and |
| + | * check if the resulting string is empty. */ |
| + | s = get_trimmed_slice(s, strchrnul(s, '#')); |
| + | if (!*s) { |
| + | continue; |
| + | } |
| + | |
| + | /* Check for a section header. */ |
| + | |
| + | if (*s == '[') { |
| + | /* Unlike the old code, we ignore leading and trailing |
| + | * whitespace for the section name. We also require that |
| + | * there are no stray characters after the closing bracket. */ |
| + | e = strchr(s, ']'); |
| + | if (!e /* Missing right bracket? */ |
| + | || e[1] /* Trailing characters? */ |
| + | || !*(s = get_trimmed_slice(s+1, e)) /* Missing name? */ |
| + | ) { |
| + | parse_error("section header"); |
| + | } |
| + | /* Right now we only have one section so just check it. |
| + | * If more sections are added in the future, please don't |
| + | * resort to cascading ifs with multiple strcasecmp calls. |
| + | * That kind of bloated code is all too common. A loop |
| + | * and a string table would be a better choice unless the |
| + | * number of sections is very small. */ |
| + | if (strcasecmp(s, "SUID") == 0) { |
| + | section = 1; |
| + | continue; |
| + | } |
| + | section = -1; /* Unknown section so set to skip. */ |
| + | continue; |
| + | } |
| + | |
| + | /* Process sections. */ |
| + | |
| + | if (section == 1) { /* SUID */ |
| + | /* Since we trimmed leading and trailing space above, we're |
| + | * now looking for strings of the form |
| + | * <key>[::space::]*=[::space::]*<value> |
| + | * where both key and value could contain inner whitespace. */ |
| + | |
| + | /* First get the key (an applet name in our case). */ |
| + | e = strchr(s, '='); |
| + | if (e) { |
| + | s = get_trimmed_slice(s, e); |
| + | } |
| + | if (!e || !*s) { /* Missing '=' or empty key. */ |
| + | parse_error("keyword"); |
| + | } |
| + | |
| + | /* Ok, we have an applet name. Process the rhs if this |
| + | * applet is currently built in and ignore it otherwise. |
| + | * Note: this can hide config file bugs which only pop |
| + | * up when the busybox configuration is changed. */ |
| + | applet = find_applet_by_name(s); |
| + | if (applet) { |
| + | /* Note: We currently don't check for duplicates! |
| + | * The last config line for each applet will be the |
| + | * one used since we insert at the head of the list. |
| + | * I suppose this could be considered a feature. */ |
| + | sct = xmalloc(sizeof(struct BB_suid_config)); |
| + | sct->m_applet = applet; |
| + | sct->m_mode = 0; |
| + | sct->m_next = sct_head; |
| + | sct_head = sct; |
| + | |
| + | /* Get the specified mode. */ |
| + | |
| + | e = skip_whitespace(e+1); |
| + | |
| + | for (i = 0; i < 3; i++) { |
| + | /* There are 4 chars + 1 nul for each of user/group/other. */ |
| + | static const char mode_chars[] ALIGN1 = "Ssx-\0" "Ssx-\0" "Ttx-"; |
| + | |
| + | const char *q; |
| + | q = strchrnul(mode_chars + 5*i, *e++); |
| + | if (!*q) { |
| + | parse_error("mode"); |
| + | } |
| + | /* Adjust by -i to account for nul. */ |
| + | sct->m_mode |= mode_mask[(q - mode_chars) - i]; |
| + | } |
| + | |
| + | /* Now get the the user/group info. */ |
| + | |
| + | s = skip_whitespace(e); |
| + | |
| + | /* Note: we require whitespace between the mode and the |
| + | * user/group info. */ |
| + | if ((s == e) || !(e = strchr(s, '.'))) { |
| + | parse_error("<uid>.<gid>"); |
| + | } |
| + | *e++ = '\0'; |
| + | |
| + | /* We can't use get_ug_id here since it would exit() |
| + | * if a uid or gid was not found. Oh well... */ |
| + | sct->m_uid = bb_strtoul(s, NULL, 10); |
| + | if (errno) { |
| + | struct passwd *pwd = getpwnam(s); |
| + | if (!pwd) { |
| + | parse_error("user"); |
| + | } |
| + | sct->m_uid = pwd->pw_uid; |
| + | } |
| + | |
| + | sct->m_gid = bb_strtoul(e, NULL, 10); |
| + | if (errno) { |
| + | struct group *grp; |
| + | grp = getgrnam(e); |
| + | if (!grp) { |
| + | parse_error("group"); |
| + | } |
| + | sct->m_gid = grp->gr_gid; |
| + | } |
| + | } |
| + | continue; |
| + | } |
| + | |
| + | /* Unknown sections are ignored. */ |
| + | |
| + | /* Encountering configuration lines prior to seeing a |
| + | * section header is treated as an error. This is how |
| + | * the old code worked, but it may not be desirable. |
| + | * We may want to simply ignore such lines in case they |
| + | * are used in some future version of busybox. */ |
| + | if (!section) { |
| + | parse_error("keyword outside section"); |
| + | } |
| + | |
| + | } /* while (1) */ |
| + | |
| + | pe_label: |
| + | fprintf(stderr, "Parse error in %s, line %d: %s\n", |
| + | config_file, lc, errmsg); |
| + | |
| + | fclose(f); |
| + | /* Release any allocated memory before returning. */ |
| + | while (sct_head) { |
| + | sct = sct_head->m_next; |
| + | free(sct_head); |
| + | sct_head = sct; |
| + | } |
| + | } |
| + | #else |
| + | static inline void parse_config_file(void) |
| + | { |
| + | USE_FEATURE_SUID(ruid = getuid();) |
| + | } |
| + | #endif /* FEATURE_SUID_CONFIG */ |
| + | |
| + | |
| + | #if ENABLE_FEATURE_SUID |
| + | static void check_suid(const struct bb_applet *applet) |
| + | { |
| + | gid_t rgid; /* real gid */ |
| + | |
| + | if (ruid == 0) /* set by parse_config_file() */ |
| + | return; /* run by root - no need to check more */ |
| + | rgid = getgid(); |
| + | |
| + | #if ENABLE_FEATURE_SUID_CONFIG |
| + | if (suid_cfg_readable) { |
| + | uid_t uid; |
| + | struct BB_suid_config *sct; |
| + | mode_t m; |
| + | |
| + | for (sct = suid_config; sct; sct = sct->m_next) { |
| + | if (sct->m_applet == applet) |
| + | goto found; |
| + | } |
| + | /* default: drop all privileges */ |
| + | xsetgid(rgid); |
| + | xsetuid(ruid); |
| + | return; |
| + | found: |
| + | m = sct->m_mode; |
| + | if (sct->m_uid == ruid) |
| + | /* same uid */ |
| + | m >>= 6; |
| + | else if ((sct->m_gid == rgid) || ingroup(ruid, sct->m_gid)) |
| + | /* same group / in group */ |
| + | m >>= 3; |
| + | |
| + | if (!(m & S_IXOTH)) /* is x bit not set ? */ |
| + | bb_error_msg_and_die("you have no permission to run this applet!"); |
| + | |
| + | /* _both_ sgid and group_exec have to be set for setegid */ |
| + | if ((sct->m_mode & (S_ISGID | S_IXGRP)) == (S_ISGID | S_IXGRP)) |
| + | rgid = sct->m_gid; |
| + | /* else (no setegid) we will set egid = rgid */ |
| + | |
| + | /* We set effective AND saved ids. If saved-id is not set |
| + | * like we do below, seteiud(0) can still later succeed! */ |
| + | if (setresgid(-1, rgid, rgid)) |
| + | bb_perror_msg_and_die("setresgid"); |
| + | |
| + | /* do we have to set effective uid? */ |
| + | uid = ruid; |
| + | if (sct->m_mode & S_ISUID) |
| + | uid = sct->m_uid; |
| + | /* else (no seteuid) we will set euid = ruid */ |
| + | |
| + | if (setresuid(-1, uid, uid)) |
| + | bb_perror_msg_and_die("setresuid"); |
| + | return; |
| + | } |
| + | #if !ENABLE_FEATURE_SUID_CONFIG_QUIET |
| + | { |
| + | static bool onetime = 0; |
| + | |
| + | if (!onetime) { |
| + | onetime = 1; |
| + | fprintf(stderr, "Using fallback suid method\n"); |
| + | } |
| + | } |
| + | #endif |
| + | #endif |
| + | |
| + | if (applet->need_suid == _BB_SUID_ALWAYS) { |
| + | /* Real uid is not 0. If euid isn't 0 too, suid bit |
| + | * is most probably not set on our executable */ |
| + | if (geteuid()) |
| + | bb_error_msg_and_die("applet requires root privileges!"); |
| + | } else if (applet->need_suid == _BB_SUID_NEVER) { |
| + | xsetgid(rgid); /* drop all privileges */ |
| + | xsetuid(ruid); |
| + | } |
| + | } |
| + | #else |
| + | #define check_suid(x) ((void)0) |
| + | #endif /* FEATURE_SUID */ |
| + | |
| + | |
| + | #if ENABLE_FEATURE_INSTALLER |
| + | /* create (sym)links for each applet */ |
| + | static void install_links(const char *busybox, int use_symbolic_links) |
| + | { |
| + | /* directory table |
| + | * this should be consistent w/ the enum, |
| + | * busybox.h::bb_install_loc_t, or else... */ |
| + | static const char usr_bin [] ALIGN1 = "/usr/bin"; |
| + | static const char usr_sbin[] ALIGN1 = "/usr/sbin"; |
| + | static const char *const install_dir[] = { |
| + | &usr_bin [8], /* "", equivalent to "/" for concat_path_file() */ |
| + | &usr_bin [4], /* "/bin" */ |
| + | &usr_sbin[4], /* "/sbin" */ |
| + | usr_bin, |
| + | usr_sbin |
| + | }; |
| + | |
| + | int (*lf)(const char *, const char *) = link; |
| + | char *fpc; |
| + | int i; |
| + | int rc; |
| + | |
| + | if (use_symbolic_links) |
| + | lf = symlink; |
| + | |
| + | for (i = 0; applets[i].name != NULL; i++) { |
| + | fpc = concat_path_file( |
| + | install_dir[applets[i].install_loc], |
| + | applets[i].name); |
| + | rc = lf(busybox, fpc); |
| + | if (rc != 0 && errno != EEXIST) { |
| + | bb_simple_perror_msg(fpc); |
| + | } |
| + | free(fpc); |
| + | } |
| + | } |
| + | #else |
| + | #define install_links(x,y) ((void)0) |
| + | #endif /* FEATURE_INSTALLER */ |
| + | |
| + | /* If we were called as "busybox..." */ |
| + | static int busybox_main(char **argv) |
| + | { |
| + | if (!argv[1]) { |
| + | /* Called without arguments */ |
| + | const struct bb_applet *a; |
| + | int col, output_width; |
| + | help: |
| + | output_width = 80; |
| + | if (ENABLE_FEATURE_AUTOWIDTH) { |
| + | /* Obtain the terminal width */ |
| + | get_terminal_width_height(0, &output_width, NULL); |
| + | } |
| + | /* leading tab and room to wrap */ |
| + | output_width -= sizeof("start-stop-daemon, ") + 8; |
| + | |
| + | printf("%s multi-call binary\n", bb_banner); /* reuse const string... */ |
| + | printf("Copyright (C) 1998-2006 Erik Andersen, Rob Landley, and others.\n" |
| + | "Licensed under GPLv2. See source distribution for full notice.\n" |
| + | "\n" |
| + | "Usage: busybox [function] [arguments]...\n" |
| + | " or: [function] [arguments]...\n" |
| + | "\n" |
| + | "\tBusyBox is a multi-call binary that combines many common Unix\n" |
| + | "\tutilities into a single executable. Most people will create a\n" |
| + | "\tlink to busybox for each function they wish to use and BusyBox\n" |
| + | "\twill act like whatever it was invoked as!\n" |
| + | "\nCurrently defined functions:\n"); |
| + | col = 0; |
| + | a = applets; |
| + | while (a->name) { |
| + | if (col > output_width) { |
| + | puts(","); |
| + | col = 0; |
| + | } |
| + | col += printf("%s%s", (col ? ", " : "\t"), a->name); |
| + | a++; |
| + | } |
| + | puts("\n"); |
| + | return 0; |
| + | } |
| + | |
| + | if (ENABLE_FEATURE_INSTALLER && strcmp(argv[1], "--install") == 0) { |
| + | const char *busybox; |
| + | busybox = xmalloc_readlink(bb_busybox_exec_path); |
| + | if (!busybox) |
| + | busybox = bb_busybox_exec_path; |
| + | /* -s makes symlinks */ |
| + | install_links(busybox, argv[2] && strcmp(argv[2], "-s") == 0); |
| + | return 0; |
| + | } |
| + | |
| + | if (strcmp(argv[1], "--help") == 0) { |
| + | /* "busybox --help [<applet>]" */ |
| + | if (!argv[2]) |
| + | goto help; |
| + | /* convert to "<applet> --help" */ |
| + | argv[0] = argv[2]; |
| + | argv[2] = NULL; |
| + | } else { |
| + | /* "busybox <applet> arg1 arg2 ..." */ |
| + | argv++; |
| + | } |
| + | /* We support "busybox /a/path/to/applet args..." too. Allows for |
| + | * "#!/bin/busybox"-style wrappers */ |
| + | applet_name = bb_get_last_path_component_nostrip(argv[0]); |
| + | run_applet_and_exit(applet_name, argv); |
| + | bb_error_msg_and_die("applet not found"); |
| + | } |
| + | |
| + | void run_appletstruct_and_exit(const struct bb_applet *applet, char **argv) |
| + | { |
| + | int argc = 1; |
| + | |
| + | while (argv[argc]) |
| + | argc++; |
| + | |
| + | /* Reinit some shared global data */ |
| + | optind = 1; |
| + | xfunc_error_retval = EXIT_FAILURE; |
| + | |
| + | applet_name = applet->name; |
| + | if (argc == 2 && !strcmp(argv[1], "--help")) |
| + | bb_show_usage(); |
| + | if (ENABLE_FEATURE_SUID) |
| + | check_suid(applet); |
| + | exit(applet->main(argc, argv)); |
| + | } |
| + | |
| + | void run_applet_and_exit(const char *name, char **argv) |
| + | { |
| + | const struct bb_applet *applet = find_applet_by_name(name); |
| + | if (applet) |
| + | run_appletstruct_and_exit(applet, argv); |
| + | if (!strncmp(name, "busybox", 7)) |
| + | exit(busybox_main(argv)); |
| + | } |
| + | |
| + | |
| + | #if ENABLE_BUILD_LIBBUSYBOX |
| + | int libbusybox_main(int argc, char **argv) |
| + | #else |
| + | int main(int argc, char **argv) |
| + | #endif |
| + | { |
| + | bbox_prepare_main(argv); |
| + | |
| + | #if !BB_MMU |
| + | /* NOMMU re-exec trick sets high-order bit in first byte of name */ |
| + | if (argv[0][0] & 0x80) { |
| + | re_execed = 1; |
| + | argv[0][0] &= 0x7f; |
| + | } |
| + | #endif |
| + | applet_name = argv[0]; |
| + | if (applet_name[0] == '-') |
| + | applet_name++; |
| + | applet_name = bb_basename(applet_name); |
| + | |
| + | parse_config_file(); /* ...maybe, if FEATURE_SUID_CONFIG */ |
| + | |
| + | run_applet_and_exit(applet_name, argv); |
| + | bb_error_msg_and_die("applet not found"); |
| + | } |
| @@ -19,18 +19,16 @@ debug=false | |
| # List of files to link: | |
| # $l_list == --start-group -llib1 -llib2 --end-group | |
| # --start-group $O_FILES $A_FILES --end-group | |
| - | # or for shlib: |
| - | # --start-group --whole-archive $A_FILES --no-whole-archive --end-group |
| - | # otherwise we don't pull in all parts of *.a files. Now we pull in too much. |
| - | # FIXME: make it work without --whole-archive, or better, with --gc-sections. |
| # | |
| # Shared library link: | |
| # -shared self-explanatory | |
| # -fPIC position-independent code | |
| - | # -Wl,--enable-new-dtags ? |
| - | # -Wl,-z,combreloc ? |
| - | # -Wl,-soname="libbusybox.so.$BB_VER" |
| - | # -static Not used, but may be useful! manpage: |
| + | # --enable-new-dtags ? |
| + | # -z,combreloc ? |
| + | # -soname="libbusybox.so.$BB_VER" |
| + | # --undefined=libbusybox_main Seed name to start pulling from |
| + | # (otherwise we'll need --whole-archive) |
| + | # -static Not used, but may be useful! manpage: |
| # "... This option can be used with -shared. | |
| # Doing so means that a shared library | |
| # is being created but that all of the library's | |
| @@ -174,9 +172,10 @@ if test "$CONFIG_BUILD_LIBBUSYBOX" = y; then | |
| -Wl,--enable-new-dtags \ | |
| -Wl,-z,combreloc \ | |
| -Wl,-soname="libbusybox.so.$BB_VER" \ | |
| + | -Wl,--undefined=libbusybox_main \ |
| -Wl,--sort-common \ | |
| -Wl,--sort-section -Wl,alignment \ | |
| - | -Wl,--start-group -Wl,--whole-archive $A_FILES -Wl,--no-whole-archive -Wl,--end-group \ |
| + | -Wl,--start-group $A_FILES -Wl,--end-group \ |
| $l_list \ | |
| -Wl,--warn-common \ | |
| -Wl,-Map -Wl,$EXE.map \ | |