| /* 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) |
| * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 |
| * |
| * Licensed under GPLv2 or later, see file LICENSE in this source tree. |
| */ |
| |
| #include "libbb.h" |
| |
| #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" |
| #ifndef CRONUPDATE |
| #define CRONUPDATE "cron.update" |
| #endif |
| |
| static void edit_file(const struct passwd *pas, const char *file) |
| { |
| const char *ptr; |
| pid_t pid; |
| |
| pid = xvfork(); |
| if (pid) { /* parent */ |
| wait4pid(pid); |
| return; |
| } |
| |
| /* CHILD - change user and run editor */ |
| /* initgroups, setgid, setuid */ |
| change_identity(pas); |
| setup_environment(pas->pw_shell, |
| SETUP_ENV_CHANGEENV | SETUP_ENV_TO_TMP, |
| pas); |
| ptr = getenv("VISUAL"); |
| if (!ptr) { |
| ptr = getenv("EDITOR"); |
| if (!ptr) |
| ptr = "vi"; |
| } |
| |
| BB_EXECLP(ptr, ptr, file, NULL); |
| bb_perror_msg_and_die("can't execute '%s'", ptr); |
| } |
| |
| static int open_as_user(const struct passwd *pas, const char *file) |
| { |
| pid_t pid; |
| char c; |
| |
| pid = xvfork(); |
| if (pid) { /* PARENT */ |
| if (wait4pid(pid) == 0) { |
| /* exitcode 0: child says it can read */ |
| return open(file, O_RDONLY); |
| } |
| return -1; |
| } |
| |
| /* 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); |
| } |
| |
| int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
| int crontab_main(int argc UNUSED_PARAM, char **argv) |
| { |
| 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. |
| */ |
| 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(bb_msg_you_must_be_root); |
| } |
| |
| if (opt_ler & OPT_u) { |
| pas = xgetpwnam(user_name); |
| } else { |
| pas = xgetpwuid(getuid()); |
| } |
| |
| #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]); |
| } |
| } |
| |
| /* cd to our crontab directory */ |
| xchdir(crontab_dir); |
| |
| tmp_fname = NULL; |
| |
| /* Handle requested operation */ |
| switch (opt_ler) { |
| |
| default: /* case OPT_r: Delete */ |
| unlink(pas->pw_name); |
| break; |
| |
| 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 */ |
| } |
| |
| 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("can't create %s/%s", |
| crontab_dir, new_fname); |
| } |
| if (tmp_fname) |
| unlink(tmp_fname); |
| /*free(tmp_fname);*/ |
| /*free(new_fname);*/ |
| |
| } /* switch */ |
| |
| /* 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; |
| |
| 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("can't append to %s/%s", |
| crontab_dir, CRONUPDATE); |
| } |
| return 0; |
| } |