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#ifndef CRONTABS 16#define CRONTABS "/var/spool/cron/crontabs" 17#endif 18#ifndef TMPDIR 19#define TMPDIR "/var/spool/cron" 20#endif 21#ifndef CRONUPDATE 22#define CRONUPDATE "cron.update" 23#endif 24#ifndef PATH_VI 25#define PATH_VI "/bin/vi" /* location of vi */ 26#endif 27 28static const char *CDir = CRONTABS; 29 30static void EditFile(const char *user, const char *file); 31static int GetReplaceStream(const char *user, const char *file); 32static int ChangeUser(const char *user, short dochdir); 33 34int crontab_main(int ac, char **av); 35int crontab_main(int ac, char **av) 36{ 37 enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE; 38 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 != '-') 69 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 FILE *fi; 161 int fd; 162 int n; 163 char tmp[128]; 164 165 snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid()); 166 fd = xopen3(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600); 167/* race, use fchown */ 168 chown(tmp, getuid(), getgid()); 169 fi = fopen(pas->pw_name, "r"); 170 if (fi) { 171 while ((n = fread(buf, 1, sizeof(buf), fi)) > 0) 172 full_write(fd, buf, n); 173 } 174 EditFile(caller, tmp); 175 remove(tmp); 176 lseek(fd, 0L, SEEK_SET); 177 repFd = fd; 178 } 179 option = REPLACE; 180 /* fall through */ 181 case REPLACE: 182 { 183/* same here */ 184 char path[1024]; 185 int fd; 186 int n; 187 188 snprintf(path, sizeof(path), "%s.new", pas->pw_name); 189 fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600); 190 if (fd >= 0) { 191 while ((n = read(repFd, buf, sizeof(buf))) > 0) { 192 full_write(fd, buf, n); 193 } 194 close(fd); 195 rename(path, pas->pw_name); 196 } else { 197 bb_error_msg("cannot create %s/%s", CDir, path); 198 } 199 close(repFd); 200 } 201 break; 202 case DELETE: 203 remove(pas->pw_name); 204 break; 205 case NONE: 206 default: 207 break; 208 } 209 210 /* 211 * Bump notification file. Handle window where crond picks file up 212 * before we can write our entry out. 213 */ 214 215 if (option == REPLACE || option == DELETE) { 216 FILE *fo; 217 struct stat st; 218 219 while ((fo = fopen(CRONUPDATE, "a"))) { 220 fprintf(fo, "%s\n", pas->pw_name); 221 fflush(fo); 222 if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) { 223 fclose(fo); 224 break; 225 } 226 fclose(fo); 227 /* loop */ 228 } 229 if (fo == NULL) { 230 bb_error_msg("cannot append to %s/%s", CDir, CRONUPDATE); 231 } 232 } 233 return 0; 234} 235 236static int GetReplaceStream(const char *user, const char *file) 237{ 238 int filedes[2]; 239 int pid; 240 int fd; 241 int n; 242 char buf[1024]; 243 244 if (pipe(filedes) < 0) { 245 perror("pipe"); 246 return -1; 247 } 248 pid = fork(); 249 if (pid < 0) { 250 perror("fork"); 251 return -1; 252 } 253 if (pid > 0) { 254 /* 255 * PARENT 256 */ 257 258 close(filedes[1]); 259 if (read(filedes[0], buf, 1) != 1) { 260 close(filedes[0]); 261 filedes[0] = -1; 262 } 263 return filedes[0]; 264 } 265 266 /* 267 * CHILD 268 */ 269 270 close(filedes[0]); 271 272 if (ChangeUser(user, 0) < 0) 273 exit(0); 274 275 xfunc_error_retval = 0; 276 fd = xopen(file, O_RDONLY); 277 buf[0] = 0; 278 write(filedes[1], buf, 1); 279 while ((n = read(fd, buf, sizeof(buf))) > 0) { 280 write(filedes[1], buf, n); 281 } 282 exit(0); 283} 284 285static void EditFile(const char *user, const char *file) 286{ 287 int pid = fork(); 288 289 if (pid == 0) { 290 /* 291 * CHILD - change user and run editor 292 */ 293 const char *ptr; 294 295 if (ChangeUser(user, 1) < 0) 296 exit(0); 297 ptr = getenv("VISUAL"); 298 if (ptr == NULL) 299 ptr = getenv("EDITOR"); 300 if (ptr == NULL) 301 ptr = PATH_VI; 302 303 ptr = xasprintf("%s %s", ptr, file); 304 execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", ptr, NULL); 305 bb_perror_msg_and_die("exec"); 306 } 307 if (pid < 0) { 308 /* 309 * PARENT - failure 310 */ 311 bb_perror_msg_and_die("fork"); 312 } 313 wait4(pid, NULL, 0, NULL); 314} 315 316static int ChangeUser(const char *user, short dochdir) 317{ 318 struct passwd *pas; 319 320 /* 321 * Obtain password entry and change privileges 322 */ 323 324 pas = getpwnam(user); 325 if (pas == NULL) { 326 bb_perror_msg_and_die("failed to get uid for %s", user); 327 } 328 setenv("USER", pas->pw_name, 1); 329 setenv("HOME", pas->pw_dir, 1); 330 setenv("SHELL", DEFAULT_SHELL, 1); 331 332 /* 333 * Change running state to the user in question 334 */ 335 change_identity(pas); 336 337 if (dochdir) { 338 if (chdir(pas->pw_dir) < 0) { 339 bb_perror_msg("chdir(%s) by %s failed", pas->pw_dir, user); 340 xchdir(TMPDIR); 341 } 342 } 343 return pas->pw_uid; 344} 345