| /* 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. | |
| */ | |
| //config:config CRONTAB | |
| //config: bool "crontab (10 kb)" | |
| //config: default y | |
| //config: help | |
| //config: Crontab manipulates the crontab for a particular user. Only | |
| //config: the superuser may specify a different user and/or crontab directory. | |
| //config: Note that busybox binary must be setuid root for this applet to | |
| //config: work properly. | |
| /* Needs to be run by root or be suid root - needs to change /var/spool/cron* files: */ | |
| //applet:IF_CRONTAB(APPLET(crontab, BB_DIR_USR_BIN, BB_SUID_REQUIRE)) | |
| //kbuild:lib-$(CONFIG_CRONTAB) += crontab.o | |
| //usage:#define crontab_trivial_usage | |
| //usage: "[-c DIR] [-u USER] [-ler]|[FILE]" | |
| //usage:#define crontab_full_usage "\n\n" | |
| //usage: " -c Crontab directory" | |
| //usage: "\n -u User" | |
| //usage: "\n -l List crontab" | |
| //usage: "\n -e Edit crontab" | |
| //usage: "\n -r Delete crontab" | |
| //usage: "\n FILE Replace crontab by FILE ('-': stdin)" | |
| #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); | |
| } | |
| 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_ler = getopt32(argv, "^" "u:c:lerd" "\0" "?1:dr"/*max one arg; -d implies -r*/, | |
| &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_simple_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 = xopen_as_uid_gid(argv[0], O_RDONLY, pas->pw_uid, pas->pw_gid); | |
| } | |
| } | |
| /* 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; | |
| } |