Changeset 2725 in MondoRescue for branches/2.2.9/mindi-busybox/miscutils/crontab.c
- Timestamp:
- Feb 25, 2011, 9:26:54 PM (13 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
branches/2.2.9/mindi-busybox/miscutils/crontab.c
r1765 r2725 8 8 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 9 9 * 10 * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.10 * Licensed under GPLv2 or later, see file LICENSE in this source tree. 11 11 */ 12 12 13 13 #include "libbb.h" 14 14 15 #ifndef CRONTABS 16 #define CRONTABS "/var/spool/cron/crontabs" 17 #endif 18 #ifndef TMPDIR 19 #define TMPDIR "/var/spool/cron" 20 #endif 15 #define CRONTABS CONFIG_FEATURE_CROND_DIR "/crontabs" 21 16 #ifndef CRONUPDATE 22 17 #define CRONUPDATE "cron.update" 23 18 #endif 24 #ifndef PATH_VI 25 #define PATH_VI "/bin/vi" /* location of vi */ 26 #endif 27 28 static const char *CDir = CRONTABS; 29 30 static void EditFile(const char *user, const char *file); 31 static int GetReplaceStream(const char *user, const char *file); 32 static int ChangeUser(const char *user, short dochdir); 33 34 int crontab_main(int ac, char **av); 35 int crontab_main(int ac, char **av) 19 20 static void edit_file(const struct passwd *pas, const char *file) 36 21 { 37 enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE; 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 47 static 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 69 int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; 70 int crontab_main(int argc UNUSED_PARAM, char **argv) 71 { 38 72 const struct passwd *pas; 39 const char *repFile = NULL; 40 int repFd = 0; 41 int i; 42 char caller[256]; /* user that ran program */ 43 char buf[1024]; 44 int UserId; 45 46 UserId = getuid(); 47 pas = getpwuid(UserId); 48 if (pas == NULL) 49 bb_perror_msg_and_die("getpwuid"); 50 51 safe_strncpy(caller, pas->pw_name, sizeof(caller)); 52 53 i = 1; 54 if (ac > 1) { 55 if (LONE_DASH(av[1])) { 56 option = REPLACE; 57 ++i; 58 } else if (av[1][0] != '-') { 59 option = REPLACE; 60 ++i; 61 repFile = av[1]; 62 } 63 } 64 65 for (; i < ac; ++i) { 66 char *ptr = av[i]; 67 68 if (*ptr != '-') 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);*/ 69 199 break; 70 ptr += 2; 71 72 switch (ptr[-1]) { 73 case 'l': 74 if (ptr[-1] == 'l') 75 option = LIST; 76 /* fall through */ 77 case 'e': 78 if (ptr[-1] == 'e') 79 option = EDIT; 80 /* fall through */ 81 case 'd': 82 if (ptr[-1] == 'd') 83 option = DELETE; 84 /* fall through */ 85 case 'u': 86 if (i + 1 < ac && av[i+1][0] != '-') { 87 ++i; 88 if (getuid() == geteuid()) { 89 pas = getpwnam(av[i]); 90 if (pas) { 91 UserId = pas->pw_uid; 92 } else { 93 bb_error_msg_and_die("user %s unknown", av[i]); 94 } 95 } else { 96 bb_error_msg_and_die("only the superuser may specify a user"); 97 } 98 } 99 break; 100 case 'c': 101 if (getuid() == geteuid()) { 102 CDir = (*ptr) ? ptr : av[++i]; 103 } else { 104 bb_error_msg_and_die("-c option: superuser only"); 105 } 106 break; 107 default: 108 i = ac; 109 break; 110 } 111 } 112 if (i != ac || option == NONE) 113 bb_show_usage(); 114 115 /* 116 * Get password entry 117 */ 118 119 pas = getpwuid(UserId); 120 if (pas == NULL) 121 bb_perror_msg_and_die("getpwuid"); 122 123 /* 124 * If there is a replacement file, obtain a secure descriptor to it. 125 */ 126 127 if (repFile) { 128 repFd = GetReplaceStream(caller, repFile); 129 if (repFd < 0) 130 bb_error_msg_and_die("cannot read replacement file"); 131 } 132 133 /* 134 * Change directory to our crontab directory 135 */ 136 137 xchdir(CDir); 138 139 /* 140 * Handle options as appropriate 141 */ 142 143 switch (option) { 144 case LIST: 145 { 146 FILE *fi; 147 148 fi = fopen(pas->pw_name, "r"); 149 if (fi) { 150 while (fgets(buf, sizeof(buf), fi) != NULL) 151 fputs(buf, stdout); 152 fclose(fi); 153 } else { 154 bb_error_msg("no crontab for %s", pas->pw_name); 155 } 156 } 157 break; 158 case EDIT: 159 { 160 /* FIXME: messy code here! we have file copying helpers for this! */ 161 FILE *fi; 162 int fd; 163 int n; 164 char tmp[128]; 165 166 snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid()); 167 fd = xopen3(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600); 168 /* race, use fchown */ 169 chown(tmp, getuid(), getgid()); 170 fi = fopen(pas->pw_name, "r"); 171 if (fi) { 172 while ((n = fread(buf, 1, sizeof(buf), fi)) > 0) 173 full_write(fd, buf, n); 174 } 175 EditFile(caller, tmp); 176 remove(tmp); 177 lseek(fd, 0L, SEEK_SET); 178 repFd = fd; 179 } 180 option = REPLACE; 181 /* fall through */ 182 case REPLACE: 183 { 184 /* same here */ 185 char path[1024]; 186 int fd; 187 int n; 188 189 snprintf(path, sizeof(path), "%s.new", pas->pw_name); 190 fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600); 191 if (fd >= 0) { 192 while ((n = read(repFd, buf, sizeof(buf))) > 0) { 193 full_write(fd, buf, n); 194 } 195 close(fd); 196 rename(path, pas->pw_name); 197 } else { 198 bb_error_msg("cannot create %s/%s", CDir, path); 199 } 200 close(repFd); 201 } 202 break; 203 case DELETE: 204 remove(pas->pw_name); 205 break; 206 case NONE: 207 default: 208 break; 209 } 210 211 /* 212 * Bump notification file. Handle window where crond picks file up 213 * before we can write our entry out. 214 */ 215 216 if (option == REPLACE || option == DELETE) { 217 FILE *fo; 218 struct stat st; 219 220 while ((fo = fopen(CRONUPDATE, "a"))) { 221 fprintf(fo, "%s\n", pas->pw_name); 222 fflush(fo); 223 if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) { 224 fclose(fo); 225 break; 226 } 227 fclose(fo); 228 /* loop */ 229 } 230 if (fo == NULL) { 231 bb_error_msg("cannot append to %s/%s", CDir, CRONUPDATE); 232 } 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); 233 209 } 234 210 return 0; 235 211 } 236 237 static int GetReplaceStream(const char *user, const char *file)238 {239 int filedes[2];240 int pid;241 int fd;242 int n;243 char buf[1024];244 245 if (pipe(filedes) < 0) {246 perror("pipe");247 return -1;248 }249 pid = fork();250 if (pid < 0) {251 perror("fork");252 return -1;253 }254 if (pid > 0) {255 /*256 * PARENT257 */258 259 close(filedes[1]);260 if (read(filedes[0], buf, 1) != 1) {261 close(filedes[0]);262 filedes[0] = -1;263 }264 return filedes[0];265 }266 267 /*268 * CHILD269 */270 271 close(filedes[0]);272 273 if (ChangeUser(user, 0) < 0)274 exit(0);275 276 xfunc_error_retval = 0;277 fd = xopen(file, O_RDONLY);278 buf[0] = 0;279 write(filedes[1], buf, 1);280 while ((n = read(fd, buf, sizeof(buf))) > 0) {281 write(filedes[1], buf, n);282 }283 exit(0);284 }285 286 static void EditFile(const char *user, const char *file)287 {288 int pid = fork();289 290 if (pid == 0) {291 /*292 * CHILD - change user and run editor293 */294 const char *ptr;295 296 if (ChangeUser(user, 1) < 0)297 exit(0);298 ptr = getenv("VISUAL");299 if (ptr == NULL)300 ptr = getenv("EDITOR");301 if (ptr == NULL)302 ptr = PATH_VI;303 304 ptr = xasprintf("%s %s", ptr, file);305 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", ptr, NULL);306 bb_perror_msg_and_die("exec");307 }308 if (pid < 0) {309 /*310 * PARENT - failure311 */312 bb_perror_msg_and_die("fork");313 }314 wait4(pid, NULL, 0, NULL);315 }316 317 static int ChangeUser(const char *user, short dochdir)318 {319 struct passwd *pas;320 321 /*322 * Obtain password entry and change privileges323 */324 325 pas = getpwnam(user);326 if (pas == NULL) {327 bb_perror_msg_and_die("failed to get uid for %s", user);328 }329 setenv("USER", pas->pw_name, 1);330 setenv("HOME", pas->pw_dir, 1);331 setenv("SHELL", DEFAULT_SHELL, 1);332 333 /*334 * Change running state to the user in question335 */336 change_identity(pas);337 338 if (dochdir) {339 if (chdir(pas->pw_dir) < 0) {340 bb_perror_msg("chdir(%s) by %s failed", pas->pw_dir, user);341 xchdir(TMPDIR);342 }343 }344 return pas->pw_uid;345 }
Note:
See TracChangeset
for help on using the changeset viewer.