From 4aca87515a5083ae0e31ce3177189fd43b6d05ac Mon Sep 17 00:00:00 2001 From: Andreas Baumann Date: Sat, 3 Jan 2015 13:58:15 +0100 Subject: patch to Vanilla Tomato 1.28 --- release/src/router/busybox/miscutils/crontab.c | 528 +++++++++---------------- 1 file changed, 182 insertions(+), 346 deletions(-) (limited to 'release/src/router/busybox/miscutils/crontab.c') diff --git a/release/src/router/busybox/miscutils/crontab.c b/release/src/router/busybox/miscutils/crontab.c index 6b944646..34b80ea3 100644 --- a/release/src/router/busybox/miscutils/crontab.c +++ b/release/src/router/busybox/miscutils/crontab.c @@ -1,391 +1,227 @@ +/* vi: set sw=4 ts=4: */ /* * CRONTAB * * usually setuid root, -c option only works if getuid() == geteuid() * * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) - * May be distributed under the GNU General Public License - * - * Vladimir Oleynik (C) 2002 to be used in busybox + * Vladimir Oleynik (C) 2002 * + * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. */ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include +#include "libbb.h" -#ifndef CRONTABS -#define CRONTABS "/var/spool/cron/crontabs" -#endif -#ifndef TMPDIR -#define TMPDIR "/var/spool/cron" -#endif +#define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" #ifndef CRONUPDATE #define CRONUPDATE "cron.update" #endif -#ifndef PATH_VI -#define PATH_VI "/bin/vi" /* location of vi */ -#endif -#include "busybox.h" - -static const char *CDir = CRONTABS; - -static void EditFile(const char *user, const char *file); -static int GetReplaceStream(const char *user, const char *file); -static int ChangeUser(const char *user, short dochdir); - -int -crontab_main(int ac, char **av) +static void change_user(const struct passwd *pas) { - enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE; - const struct passwd *pas; - const char *repFile = NULL; - int repFd = 0; - int i; - char caller[256]; /* user that ran program */ - int UserId; - - UserId = getuid(); - if ((pas = getpwuid(UserId)) == NULL) - bb_perror_msg_and_die("getpwuid"); + xsetenv("USER", pas->pw_name); + xsetenv("HOME", pas->pw_dir); + xsetenv("SHELL", DEFAULT_SHELL); - strncpy(caller, pas->pw_name, sizeof(caller)); + /* initgroups, setgid, setuid */ + change_identity(pas); - i = 1; - if (ac > 1) { - if (av[1][0] == '-' && av[1][1] == 0) { - option = REPLACE; - ++i; - } else if (av[1][0] != '-') { - option = REPLACE; - ++i; - repFile = av[1]; + if (chdir(pas->pw_dir) < 0) { + bb_perror_msg("chdir(%s) by %s failed", + pas->pw_dir, pas->pw_name); + xchdir("/tmp"); } - } - - for (; i < ac; ++i) { - char *ptr = av[i]; - - if (*ptr != '-') - break; - ptr += 2; +} - switch(ptr[-1]) { - case 'l': - if (ptr[-1] == 'l') - option = LIST; - /* fall through */ - case 'e': - if (ptr[-1] == 'e') - option = EDIT; - /* fall through */ - case 'd': - if (ptr[-1] == 'd') - option = DELETE; - /* fall through */ - case 'u': - if (i + 1 < ac && av[i+1][0] != '-') { - ++i; - if (getuid() == geteuid()) { - pas = getpwnam(av[i]); - if (pas) { - UserId = pas->pw_uid; - } else { - bb_error_msg_and_die("user %s unknown", av[i]); - } - } else { - bb_error_msg_and_die("only the superuser may specify a user"); - } - } - break; - case 'c': - if (getuid() == geteuid()) { - CDir = (*ptr) ? ptr : av[++i]; - } else { - bb_error_msg_and_die("-c option: superuser only"); - } - break; - default: - i = ac; - break; +static void edit_file(const struct passwd *pas, const char *file) +{ + const char *ptr; + int pid = vfork(); + + if (pid < 0) /* failure */ + bb_perror_msg_and_die("vfork"); + if (pid) { /* parent */ + wait4pid(pid); + return; } - } - if (i != ac || option == NONE) - bb_show_usage(); - - /* - * Get password entry - */ - if ((pas = getpwuid(UserId)) == NULL) - bb_perror_msg_and_die("getpwuid"); - - /* - * If there is a replacement file, obtain a secure descriptor to it. - */ - - if (repFile) { - repFd = GetReplaceStream(caller, repFile); - if (repFd < 0) - bb_error_msg_and_die("unable to read replacement file"); - } - - /* - * Change directory to our crontab directory - */ - - if (chdir(CDir) < 0) - bb_perror_msg_and_die("cannot change dir to %s", CDir); - - /* - * Handle options as appropriate - */ - - switch(option) { - case LIST: - { - FILE *fi; - char buf[1024]; - - if ((fi = fopen(pas->pw_name, "r"))) { - while (fgets(buf, sizeof(buf), fi) != NULL) - fputs(buf, stdout); - fclose(fi); - } else { - bb_error_msg("no crontab for %s", pas->pw_name); - } + /* CHILD - change user and run editor */ + change_user(pas); + ptr = getenv("VISUAL"); + if (!ptr) { + ptr = getenv("EDITOR"); + if (!ptr) + ptr = "vi"; } - break; - case EDIT: - { - FILE *fi; - int fd; - int n; - char tmp[128]; - char buf[1024]; - snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid()); - if ((fd = open(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600)) >= 0) { - chown(tmp, getuid(), getgid()); - if ((fi = fopen(pas->pw_name, "r"))) { - while ((n = fread(buf, 1, sizeof(buf), fi)) > 0) - write(fd, buf, n); - } - EditFile(caller, tmp); - remove(tmp); - lseek(fd, 0L, 0); - repFd = fd; - } else { - bb_error_msg_and_die("unable to create %s", tmp); - } - - } - option = REPLACE; - /* fall through */ - case REPLACE: - { - char buf[1024]; - char path[1024]; - int fd; - int n; + BB_EXECLP(ptr, ptr, file, NULL); + bb_perror_msg_and_die("exec %s", ptr); +} - snprintf(path, sizeof(path), "%s.new", pas->pw_name); - if ((fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600)) >= 0) { - while ((n = read(repFd, buf, sizeof(buf))) > 0) { - write(fd, buf, n); +static int open_as_user(const struct passwd *pas, const char *file) +{ + pid_t pid; + char c; + + pid = vfork(); + if (pid < 0) /* ERROR */ + bb_perror_msg_and_die("vfork"); + if (pid) { /* PARENT */ + if (wait4pid(pid) == 0) { + /* exitcode 0: child says it can read */ + return open(file, O_RDONLY); } - close(fd); - rename(path, pas->pw_name); - } else { - bb_error_msg("unable to create %s/%s", CDir, path); - } - close(repFd); + return -1; } - break; - case DELETE: - remove(pas->pw_name); - break; - case NONE: - default: - break; - } - - /* - * Bump notification file. Handle window where crond picks file up - * before we can write our entry out. - */ - - if (option == REPLACE || option == DELETE) { - FILE *fo; - struct stat st; - while ((fo = fopen(CRONUPDATE, "a"))) { - fprintf(fo, "%s\n", pas->pw_name); - fflush(fo); - if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) { - fclose(fo); - break; - } - fclose(fo); - /* loop */ - } - if (fo == NULL) { - bb_error_msg("unable to append to %s/%s", CDir, CRONUPDATE); - } - } - return 0; + /* CHILD */ + /* initgroups, setgid, setuid */ + change_identity(pas); + /* We just try to read one byte. If it works, file is readable + * under this user. We signal that by exiting with 0. */ + _exit(safe_read(xopen(file, O_RDONLY), &c, 1) < 0); } -static int -GetReplaceStream(const char *user, const char *file) +int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int crontab_main(int argc UNUSED_PARAM, char **argv) { - int filedes[2]; - int pid; - int fd; - int n; - char buf[1024]; - - if (pipe(filedes) < 0) { - perror("pipe"); - return(-1); - } - if ((pid = fork()) < 0) { - perror("fork"); - return(-1); - } - if (pid > 0) { - /* - * PARENT + const struct passwd *pas; + const char *crontab_dir = CRONTABS; + char *tmp_fname; + char *new_fname; + char *user_name; /* -u USER */ + int fd; + int src_fd; + int opt_ler; + + /* file [opts] Replace crontab from file + * - [opts] Replace crontab from stdin + * -u user User + * -c dir Crontab directory + * -l List crontab for user + * -e Edit crontab for user + * -r Delete crontab for user + * bbox also supports -d == -r, but most other crontab + * implementations do not. Deprecated. */ - - close(filedes[1]); - if (read(filedes[0], buf, 1) != 1) { - close(filedes[0]); - filedes[0] = -1; + enum { + OPT_u = (1 << 0), + OPT_c = (1 << 1), + OPT_l = (1 << 2), + OPT_e = (1 << 3), + OPT_r = (1 << 4), + OPT_ler = OPT_l + OPT_e + OPT_r, + }; + + opt_complementary = "?1:dr"; /* max one argument; -d implies -r */ + opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir); + argv += optind; + + if (sanitize_env_if_suid()) { /* Clears dangerous stuff, sets PATH */ + /* run by non-root? */ + if (opt_ler & (OPT_u|OPT_c)) + bb_error_msg_and_die("only root can use -c or -u"); } - return(filedes[0]); - } - - /* - * CHILD - */ - - close(filedes[0]); - - if (ChangeUser(user, 0) < 0) - exit(0); - fd = open(file, O_RDONLY); - if (fd < 0) { - bb_error_msg("unable to open %s", file); - exit(0); - } - buf[0] = 0; - write(filedes[1], buf, 1); - while ((n = read(fd, buf, sizeof(buf))) > 0) { - write(filedes[1], buf, n); - } - exit(0); -} - -static void -EditFile(const char *user, const char *file) -{ - int pid; + if (opt_ler & OPT_u) { + pas = xgetpwnam(user_name); + } else { + pas = xgetpwuid(getuid()); + } - if ((pid = fork()) == 0) { - /* - * CHILD - change user and run editor - */ - char *ptr; - char visual[1024]; +#define user_name DONT_USE_ME_BEYOND_THIS_POINT + + /* From now on, keep only -l, -e, -r bits */ + opt_ler &= OPT_ler; + if ((opt_ler - 1) & opt_ler) /* more than one bit set? */ + bb_show_usage(); + + /* Read replacement file under user's UID/GID/group vector */ + src_fd = STDIN_FILENO; + if (!opt_ler) { /* Replace? */ + if (!argv[0]) + bb_show_usage(); + if (NOT_LONE_DASH(argv[0])) { + src_fd = open_as_user(pas, argv[0]); + if (src_fd < 0) + bb_error_msg_and_die("user %s cannot read %s", + pas->pw_name, argv[0]); + } + } - if (ChangeUser(user, 1) < 0) - exit(0); - if ((ptr = getenv("VISUAL")) == NULL || strlen(ptr) > 256) - ptr = PATH_VI; + /* cd to our crontab directory */ + xchdir(crontab_dir); - snprintf(visual, sizeof(visual), "%s %s", ptr, file); - execl("/bin/sh", "/bin/sh", "-c", visual, NULL); - perror("exec"); - exit(0); - } - if (pid < 0) { - /* - * PARENT - failure - */ - bb_perror_msg_and_die("fork"); - } - wait4(pid, NULL, 0, NULL); -} + tmp_fname = NULL; -static void -log(const char *ctl, ...) -{ - va_list va; - char buf[1024]; + /* Handle requested operation */ + switch (opt_ler) { - va_start(va, ctl); - vsnprintf(buf, sizeof(buf), ctl, va); - syslog(LOG_NOTICE, "%s",buf ); - va_end(va); -} + default: /* case OPT_r: Delete */ + unlink(pas->pw_name); + break; -static int -ChangeUser(const char *user, short dochdir) -{ - struct passwd *pas; + case OPT_l: /* List */ + { + char *args[2] = { pas->pw_name, NULL }; + return bb_cat(args); + /* list exits, + * the rest go play with cron update file */ + } - /* - * Obtain password entry and change privilages - */ + case OPT_e: /* Edit */ + tmp_fname = xasprintf("%s.%u", crontab_dir, (unsigned)getpid()); + /* No O_EXCL: we don't want to be stuck if earlier crontabs + * were killed, leaving stale temp file behind */ + src_fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600); + fchown(src_fd, pas->pw_uid, pas->pw_gid); + fd = open(pas->pw_name, O_RDONLY); + if (fd >= 0) { + bb_copyfd_eof(fd, src_fd); + close(fd); + xlseek(src_fd, 0, SEEK_SET); + } + close_on_exec_on(src_fd); /* don't want editor to see this fd */ + edit_file(pas, tmp_fname); + /* fall through */ + + case 0: /* Replace (no -l, -e, or -r were given) */ + new_fname = xasprintf("%s.new", pas->pw_name); + fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600); + if (fd >= 0) { + bb_copyfd_eof(src_fd, fd); + close(fd); + xrename(new_fname, pas->pw_name); + } else { + bb_error_msg("cannot create %s/%s", + crontab_dir, new_fname); + } + if (tmp_fname) + unlink(tmp_fname); + /*free(tmp_fname);*/ + /*free(new_fname);*/ - if ((pas = getpwnam(user)) == 0) { - log("failed to get uid for %s", user); - return(-1); - } - setenv("USER", pas->pw_name, 1); - setenv("HOME", pas->pw_dir, 1); - setenv("SHELL", "/bin/sh", 1); + } /* switch */ - /* - * Change running state to the user in question - */ + /* Bump notification file. Handle window where crond picks file up + * before we can write our entry out. + */ + while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) { + struct stat st; - if (initgroups(user, pas->pw_gid) < 0) { - log("initgroups failed: %s %m", user); - return(-1); - } - if (setregid(pas->pw_gid, pas->pw_gid) < 0) { - log("setregid failed: %s %d", user, pas->pw_gid); - return(-1); - } - if (setreuid(pas->pw_uid, pas->pw_uid) < 0) { - log("setreuid failed: %s %d", user, pas->pw_uid); - return(-1); - } - if (dochdir) { - if (chdir(pas->pw_dir) < 0) { - if (chdir(TMPDIR) < 0) { - log("chdir failed: %s %s", user, pas->pw_dir); - log("chdir failed: %s " TMPDIR, user); - return(-1); - } + fdprintf(fd, "%s\n", pas->pw_name); + if (fstat(fd, &st) != 0 || st.st_nlink != 0) { + /*close(fd);*/ + break; + } + /* st.st_nlink == 0: + * file was deleted, maybe crond missed our notification */ + close(fd); + /* loop */ + } + if (fd < 0) { + bb_error_msg("cannot append to %s/%s", + crontab_dir, CRONUPDATE); } - } - return(pas->pw_uid); + return 0; } -- cgit v1.2.3-54-g00ecf