/* vi: set sw=4 ts=4: */ /* * Utility routines. * * 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. * * 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 #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 #warning (glibc does not cope well with ld --gc-sections). #warning See sources.redhat.com/bugzilla/show_bug.cgi?id=3400 #warning Note that glibc is unsuitable for static linking anyway. #warning If you still want to do it, remove -Wl,--gc-sections #warning from top-level Makefile and remove this warning. #error Aborting compilation. #endif const struct bb_applet *current_applet; 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 * [::space::]*=[::space::]* * 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("."); } *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 []" */ if (!argv[2]) goto help; /* convert to " --help" */ argv[0] = argv[2]; argv[2] = NULL; } else { /* "busybox 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_current_applet_and_exit(char **argv) { int argc = 1; while (argv[argc]) argc++; /* Reinit some shared global data */ optind = 1; xfunc_error_retval = EXIT_FAILURE; applet_name = current_applet->name; if (argc == 2 && !strcmp(argv[1], "--help")) bb_show_usage(); if (ENABLE_FEATURE_SUID) check_suid(current_applet); exit(current_applet->main(argc, argv)); } void run_applet_and_exit(const char *name, char **argv) { current_applet = find_applet_by_name(name); if (current_applet) run_current_applet_and_exit(argv); if (!strncmp(name, "busybox", 7)) exit(busybox_main(argv)); } int main(int argc, char **argv) { bbox_prepare_main(); #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"); }