1/* vi: set sw=4 ts=4: */ 2/* 3 * CRONTAB 4 * 5 * usually setuid root, -c option only works if getuid() == geteuid() 6 * 7 * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) 8 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 9 * 10 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball. 11 */ 12 13#include "libbb.h" 14 15#define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" 16#ifndef CRONUPDATE 17#define CRONUPDATE "cron.update" 18#endif 19 20static void edit_file(const struct passwd *pas, const char *file) 21{ 22 const char *ptr; 23 int pid = xvfork(); 24 25 if (pid) { /* parent */ 26 wait4pid(pid); 27 return; 28 } 29 30 /* CHILD - change user and run editor */ 31 /* initgroups, setgid, setuid */ 32 change_identity(pas); 33 setup_environment(DEFAULT_SHELL, 34 SETUP_ENV_CHANGEENV | SETUP_ENV_TO_TMP, 35 pas); 36 ptr = getenv("VISUAL"); 37 if (!ptr) { 38 ptr = getenv("EDITOR"); 39 if (!ptr) 40 ptr = "vi"; 41 } 42 43 BB_EXECLP(ptr, ptr, file, NULL); 44 bb_perror_msg_and_die("exec %s", ptr); 45} 46 47static int open_as_user(const struct passwd *pas, const char *file) 48{ 49 pid_t pid; 50 char c; 51 52 pid = xvfork(); 53 if (pid) { /* PARENT */ 54 if (wait4pid(pid) == 0) { 55 /* exitcode 0: child says it can read */ 56 return open(file, O_RDONLY); 57 } 58 return -1; 59 } 60 61 /* CHILD */ 62 /* initgroups, setgid, setuid */ 63 change_identity(pas); 64 /* We just try to read one byte. If it works, file is readable 65 * under this user. We signal that by exiting with 0. */ 66 _exit(safe_read(xopen(file, O_RDONLY), &c, 1) < 0); 67} 68 69int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 70int crontab_main(int argc UNUSED_PARAM, char **argv) 71{ 72 const struct passwd *pas; 73 const char *crontab_dir = CRONTABS; 74 char *tmp_fname; 75 char *new_fname; 76 char *user_name; /* -u USER */ 77 int fd; 78 int src_fd; 79 int opt_ler; 80 81 /* file [opts] Replace crontab from file 82 * - [opts] Replace crontab from stdin 83 * -u user User 84 * -c dir Crontab directory 85 * -l List crontab for user 86 * -e Edit crontab for user 87 * -r Delete crontab for user 88 * bbox also supports -d == -r, but most other crontab 89 * implementations do not. Deprecated. 90 */ 91 enum { 92 OPT_u = (1 << 0), 93 OPT_c = (1 << 1), 94 OPT_l = (1 << 2), 95 OPT_e = (1 << 3), 96 OPT_r = (1 << 4), 97 OPT_ler = OPT_l + OPT_e + OPT_r, 98 }; 99 100 opt_complementary = "?1:dr"; /* max one argument; -d implies -r */ 101 opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir); 102 argv += optind; 103 104 if (sanitize_env_if_suid()) { /* Clears dangerous stuff, sets PATH */ 105 /* Run by non-root */ 106 if (opt_ler & (OPT_u|OPT_c)) 107 bb_error_msg_and_die(bb_msg_you_must_be_root); 108 } 109 110 if (opt_ler & OPT_u) { 111 pas = xgetpwnam(user_name); 112 } else { 113 pas = xgetpwuid(getuid()); 114 } 115 116#define user_name DONT_USE_ME_BEYOND_THIS_POINT 117 118 /* From now on, keep only -l, -e, -r bits */ 119 opt_ler &= OPT_ler; 120 if ((opt_ler - 1) & opt_ler) /* more than one bit set? */ 121 bb_show_usage(); 122 123 /* Read replacement file under user's UID/GID/group vector */ 124 src_fd = STDIN_FILENO; 125 if (!opt_ler) { /* Replace? */ 126 if (!argv[0]) 127 bb_show_usage(); 128 if (NOT_LONE_DASH(argv[0])) { 129 src_fd = open_as_user(pas, argv[0]); 130 if (src_fd < 0) 131 bb_error_msg_and_die("user %s cannot read %s", 132 pas->pw_name, argv[0]); 133 } 134 } 135 136 /* cd to our crontab directory */ 137 xchdir(crontab_dir); 138 139 tmp_fname = NULL; 140 141 /* Handle requested operation */ 142 switch (opt_ler) { 143 144 default: /* case OPT_r: Delete */ 145 unlink(pas->pw_name); 146 break; 147 148 case OPT_l: /* List */ 149 { 150 char *args[2] = { pas->pw_name, NULL }; 151 return bb_cat(args); 152 /* list exits, 153 * the rest go play with cron update file */ 154 } 155 156 case OPT_e: /* Edit */ 157 tmp_fname = xasprintf("%s.%u", crontab_dir, (unsigned)getpid()); 158 /* No O_EXCL: we don't want to be stuck if earlier crontabs 159 * were killed, leaving stale temp file behind */ 160 src_fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600); 161 fchown(src_fd, pas->pw_uid, pas->pw_gid); 162 fd = open(pas->pw_name, O_RDONLY); 163 if (fd >= 0) { 164 bb_copyfd_eof(fd, src_fd); 165 close(fd); 166 xlseek(src_fd, 0, SEEK_SET); 167 } 168 close_on_exec_on(src_fd); /* don't want editor to see this fd */ 169 edit_file(pas, tmp_fname); 170 /* fall through */ 171 172 case 0: /* Replace (no -l, -e, or -r were given) */ 173 new_fname = xasprintf("%s.new", pas->pw_name); 174 fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600); 175 if (fd >= 0) { 176 bb_copyfd_eof(src_fd, fd); 177 close(fd); 178 xrename(new_fname, pas->pw_name); 179 } else { 180 bb_error_msg("can't create %s/%s", 181 crontab_dir, new_fname); 182 } 183 if (tmp_fname) 184 unlink(tmp_fname); 185 /*free(tmp_fname);*/ 186 /*free(new_fname);*/ 187 188 } /* switch */ 189 190 /* Bump notification file. Handle window where crond picks file up 191 * before we can write our entry out. 192 */ 193 while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) { 194 struct stat st; 195 196 fdprintf(fd, "%s\n", pas->pw_name); 197 if (fstat(fd, &st) != 0 || st.st_nlink != 0) { 198 /*close(fd);*/ 199 break; 200 } 201 /* st.st_nlink == 0: 202 * file was deleted, maybe crond missed our notification */ 203 close(fd); 204 /* loop */ 205 } 206 if (fd < 0) { 207 bb_error_msg("can't append to %s/%s", 208 crontab_dir, CRONUPDATE); 209 } 210 return 0; 211} 212