12311Sjkh/* Copyright 1988,1990,1993,1994 by Paul Vixie 22311Sjkh * All rights reserved 32311Sjkh * 42311Sjkh * Distribute freely, except: don't remove my name from the source or 52311Sjkh * documentation (don't take credit for my work), mark your changes (don't 62311Sjkh * get me blamed for your possible bugs), don't alter or remove this 72311Sjkh * notice. May be sold if buildable source is provided to buyer. No 82311Sjkh * warrantee of any kind, express or implied, is included with this 92311Sjkh * software; use at your own risk, responsibility for damages (if any) to 102311Sjkh * anyone resulting from the use of this software rests entirely with the 112311Sjkh * user. 122311Sjkh * 132311Sjkh * Send bug reports, bug fixes, enhancements, requests, flames, etc., and 142311Sjkh * I'll try to keep a version up to date. I can be reached as follows: 152311Sjkh * Paul Vixie <paul@vix.com> uunet!decwrl!vixie!paul 165176Sache * From Id: crontab.c,v 2.13 1994/01/17 03:20:37 vixie Exp 172311Sjkh */ 182311Sjkh 192311Sjkh#if !defined(lint) && !defined(LINT) 2029452Scharnierstatic const char rcsid[] = 2150479Speter "$FreeBSD$"; 222311Sjkh#endif 232311Sjkh 242311Sjkh/* crontab - install and manage per-user crontab files 252311Sjkh * vix 02may87 [RCS has the rest of the log] 262311Sjkh * vix 26jan87 [original] 272311Sjkh */ 282311Sjkh 292311Sjkh#define MAIN_PROGRAM 302311Sjkh 312311Sjkh#include "cron.h" 322311Sjkh#include <errno.h> 332311Sjkh#include <fcntl.h> 34135242Sdds#include <md5.h> 3569793Sobrien#include <paths.h> 362311Sjkh#include <sys/file.h> 372311Sjkh#include <sys/stat.h> 382311Sjkh#ifdef USE_UTIMES 392311Sjkh# include <sys/time.h> 402311Sjkh#else 412311Sjkh# include <time.h> 422311Sjkh# include <utime.h> 432311Sjkh#endif 442311Sjkh#if defined(POSIX) 452311Sjkh# include <locale.h> 462311Sjkh#endif 472311Sjkh 48135242Sdds#define MD5_SIZE 33 492311Sjkh#define NHEADER_LINES 3 502311Sjkh 512311Sjkh 522311Sjkhenum opt_t { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace }; 532311Sjkh 542311Sjkh#if DEBUGGING 552311Sjkhstatic char *Options[] = { "???", "list", "delete", "edit", "replace" }; 562311Sjkh#endif 572311Sjkh 582311Sjkh 592311Sjkhstatic PID_T Pid; 602311Sjkhstatic char User[MAX_UNAME], RealUser[MAX_UNAME]; 612311Sjkhstatic char Filename[MAX_FNAME]; 622311Sjkhstatic FILE *NewCrontab; 632311Sjkhstatic int CheckErrorCount; 642311Sjkhstatic enum opt_t Option; 652311Sjkhstatic struct passwd *pw; 66173412Skevlostatic void list_cmd(void), 67173412Skevlo delete_cmd(void), 68173412Skevlo edit_cmd(void), 69173412Skevlo poke_daemon(void), 70173412Skevlo check_error(char *), 71173412Skevlo parse_args(int c, char *v[]); 72173412Skevlostatic int replace_cmd(void); 732311Sjkh 742311Sjkh 752311Sjkhstatic void 76184809Smatteousage(char *msg) 772311Sjkh{ 7829452Scharnier fprintf(stderr, "crontab: usage error: %s\n", msg); 7929452Scharnier fprintf(stderr, "%s\n%s\n", 8029452Scharnier "usage: crontab [-u user] file", 8129452Scharnier " crontab [-u user] { -e | -l | -r }"); 822311Sjkh exit(ERROR_EXIT); 832311Sjkh} 842311Sjkh 852311Sjkh 862311Sjkhint 87184809Smatteomain(int argc, char *argv[]) 882311Sjkh{ 892311Sjkh int exitstatus; 902311Sjkh 912311Sjkh Pid = getpid(); 922311Sjkh ProgramName = argv[0]; 932311Sjkh 942311Sjkh#if defined(POSIX) 952311Sjkh setlocale(LC_ALL, ""); 962311Sjkh#endif 972311Sjkh 982311Sjkh#if defined(BSD) 992311Sjkh setlinebuf(stderr); 1002311Sjkh#endif 1012311Sjkh parse_args(argc, argv); /* sets many globals, opens a file */ 1022311Sjkh set_cron_uid(); 1032311Sjkh set_cron_cwd(); 1042311Sjkh if (!allowed(User)) { 10529452Scharnier warnx("you (%s) are not allowed to use this program", User); 1062311Sjkh log_it(RealUser, Pid, "AUTH", "crontab command not allowed"); 1072311Sjkh exit(ERROR_EXIT); 1082311Sjkh } 1092311Sjkh exitstatus = OK_EXIT; 1102311Sjkh switch (Option) { 1112311Sjkh case opt_list: list_cmd(); 1122311Sjkh break; 1132311Sjkh case opt_delete: delete_cmd(); 1142311Sjkh break; 1152311Sjkh case opt_edit: edit_cmd(); 1162311Sjkh break; 1172311Sjkh case opt_replace: if (replace_cmd() < 0) 1182311Sjkh exitstatus = ERROR_EXIT; 1192311Sjkh break; 12029452Scharnier case opt_unknown: 12129452Scharnier break; 1222311Sjkh } 123104326Sdd exit(exitstatus); 1242311Sjkh /*NOTREACHED*/ 1252311Sjkh} 1262311Sjkh 1278857Srgrimes 1282311Sjkhstatic void 1292311Sjkhparse_args(argc, argv) 1302311Sjkh int argc; 1312311Sjkh char *argv[]; 1322311Sjkh{ 1332311Sjkh int argch; 134154166Sbrooks char resolved_path[PATH_MAX]; 1352311Sjkh 13629452Scharnier if (!(pw = getpwuid(getuid()))) 13729452Scharnier errx(ERROR_EXIT, "your UID isn't in the passwd file, bailing out"); 138184706Smatteo bzero(pw->pw_passwd, strlen(pw->pw_passwd)); 13920573Spst (void) strncpy(User, pw->pw_name, (sizeof User)-1); 14020573Spst User[(sizeof User)-1] = '\0'; 1412311Sjkh strcpy(RealUser, User); 1422311Sjkh Filename[0] = '\0'; 1432311Sjkh Option = opt_unknown; 14424428Simp while ((argch = getopt(argc, argv, "u:lerx:")) != -1) { 1452311Sjkh switch (argch) { 1462311Sjkh case 'x': 1472311Sjkh if (!set_debug_flags(optarg)) 1482311Sjkh usage("bad debug option"); 1492311Sjkh break; 1502311Sjkh case 'u': 1512311Sjkh if (getuid() != ROOT_UID) 15229452Scharnier errx(ERROR_EXIT, "must be privileged to use -u"); 1532311Sjkh if (!(pw = getpwnam(optarg))) 15429452Scharnier errx(ERROR_EXIT, "user `%s' unknown", optarg); 155184706Smatteo bzero(pw->pw_passwd, strlen(pw->pw_passwd)); 15620573Spst (void) strncpy(User, pw->pw_name, (sizeof User)-1); 15720573Spst User[(sizeof User)-1] = '\0'; 1582311Sjkh break; 1592311Sjkh case 'l': 1602311Sjkh if (Option != opt_unknown) 1612311Sjkh usage("only one operation permitted"); 1622311Sjkh Option = opt_list; 1632311Sjkh break; 1642311Sjkh case 'r': 1652311Sjkh if (Option != opt_unknown) 1662311Sjkh usage("only one operation permitted"); 1672311Sjkh Option = opt_delete; 1682311Sjkh break; 1692311Sjkh case 'e': 1702311Sjkh if (Option != opt_unknown) 1712311Sjkh usage("only one operation permitted"); 1722311Sjkh Option = opt_edit; 1732311Sjkh break; 1742311Sjkh default: 1752311Sjkh usage("unrecognized option"); 1762311Sjkh } 1772311Sjkh } 1782311Sjkh 1792311Sjkh endpwent(); 1802311Sjkh 1812311Sjkh if (Option != opt_unknown) { 1822311Sjkh if (argv[optind] != NULL) { 1832311Sjkh usage("no arguments permitted after this option"); 1842311Sjkh } 1852311Sjkh } else { 1862311Sjkh if (argv[optind] != NULL) { 1872311Sjkh Option = opt_replace; 18820573Spst (void) strncpy (Filename, argv[optind], (sizeof Filename)-1); 18920573Spst Filename[(sizeof Filename)-1] = '\0'; 19020573Spst 1912311Sjkh } else { 1922311Sjkh usage("file name must be specified for replace"); 1932311Sjkh } 1942311Sjkh } 1952311Sjkh 1962311Sjkh if (Option == opt_replace) { 197232537Sdelphij /* relinquish the setuid status of the binary during 198232537Sdelphij * the open, lest nonroot users read files they should 199232537Sdelphij * not be able to read. we can't use access() here 200232537Sdelphij * since there's a race condition. thanks go out to 201232537Sdelphij * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting 202232537Sdelphij * the race. 203232537Sdelphij */ 204232537Sdelphij 205232537Sdelphij if (swap_uids() < OK) 206232537Sdelphij err(ERROR_EXIT, "swapping uids"); 207232537Sdelphij 2082311Sjkh /* we have to open the file here because we're going to 2092311Sjkh * chdir(2) into /var/cron before we get around to 2102311Sjkh * reading the file. 2112311Sjkh */ 2122311Sjkh if (!strcmp(Filename, "-")) { 2132311Sjkh NewCrontab = stdin; 214154166Sbrooks } else if (realpath(Filename, resolved_path) != NULL && 215161964Sru !strcmp(resolved_path, SYSCRONTAB)) { 216161964Sru err(ERROR_EXIT, SYSCRONTAB " must be edited manually"); 2172311Sjkh } else { 21829452Scharnier if (!(NewCrontab = fopen(Filename, "r"))) 21929452Scharnier err(ERROR_EXIT, "%s", Filename); 2202311Sjkh } 221232537Sdelphij if (swap_uids_back() < OK) 222232537Sdelphij err(ERROR_EXIT, "swapping uids back"); 2232311Sjkh } 2242311Sjkh 2252311Sjkh Debug(DMISC, ("user=%s, file=%s, option=%s\n", 2262311Sjkh User, Filename, Options[(int)Option])) 2272311Sjkh} 2282311Sjkh 229135174Sddsstatic void 230135174Sddscopy_file(FILE *in, FILE *out) { 231135174Sdds int x, ch; 2322311Sjkh 233135174Sdds Set_LineNum(1) 234135174Sdds /* ignore the top few comments since we probably put them there. 235135174Sdds */ 236135174Sdds for (x = 0; x < NHEADER_LINES; x++) { 237135174Sdds ch = get_char(in); 238135174Sdds if (EOF == ch) 239135174Sdds break; 240135174Sdds if ('#' != ch) { 241135174Sdds putc(ch, out); 242135174Sdds break; 243135174Sdds } 244135174Sdds while (EOF != (ch = get_char(in))) 245135174Sdds if (ch == '\n') 246135174Sdds break; 247135174Sdds if (EOF == ch) 248135174Sdds break; 249135174Sdds } 250135174Sdds 251135174Sdds /* copy the rest of the crontab (if any) to the output file. 252135174Sdds */ 253135174Sdds if (EOF != ch) 254135174Sdds while (EOF != (ch = get_char(in))) 255135174Sdds putc(ch, out); 256135174Sdds} 257135174Sdds 2582311Sjkhstatic void 2592311Sjkhlist_cmd() { 2602311Sjkh char n[MAX_FNAME]; 2612311Sjkh FILE *f; 2622311Sjkh 2632311Sjkh log_it(RealUser, Pid, "LIST", User); 264184779Smatteo (void) snprintf(n, sizeof(n), CRON_TAB(User)); 2652311Sjkh if (!(f = fopen(n, "r"))) { 2662311Sjkh if (errno == ENOENT) 26729452Scharnier errx(ERROR_EXIT, "no crontab for %s", User); 2682311Sjkh else 26929452Scharnier err(ERROR_EXIT, "%s", n); 2702311Sjkh } 2712311Sjkh 2722311Sjkh /* file is open. copy to stdout, close. 2732311Sjkh */ 274135174Sdds copy_file(f, stdout); 2752311Sjkh fclose(f); 2762311Sjkh} 2772311Sjkh 2782311Sjkh 2792311Sjkhstatic void 2802311Sjkhdelete_cmd() { 2812311Sjkh char n[MAX_FNAME]; 28267127Spaul int ch, first; 2832311Sjkh 28467127Spaul if (isatty(STDIN_FILENO)) { 28567127Spaul (void)fprintf(stderr, "remove crontab for %s? ", User); 28667127Spaul first = ch = getchar(); 28767127Spaul while (ch != '\n' && ch != EOF) 28867127Spaul ch = getchar(); 28967127Spaul if (first != 'y' && first != 'Y') 29067127Spaul return; 29167127Spaul } 29267127Spaul 2932311Sjkh log_it(RealUser, Pid, "DELETE", User); 294184779Smatteo (void) snprintf(n, sizeof(n), CRON_TAB(User)); 2952311Sjkh if (unlink(n)) { 2962311Sjkh if (errno == ENOENT) 29729452Scharnier errx(ERROR_EXIT, "no crontab for %s", User); 2982311Sjkh else 29929452Scharnier err(ERROR_EXIT, "%s", n); 3002311Sjkh } 3012311Sjkh poke_daemon(); 3022311Sjkh} 3032311Sjkh 3042311Sjkh 3052311Sjkhstatic void 3062311Sjkhcheck_error(msg) 3072311Sjkh char *msg; 3082311Sjkh{ 3092311Sjkh CheckErrorCount++; 3102311Sjkh fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg); 3112311Sjkh} 3122311Sjkh 3132311Sjkh 3142311Sjkhstatic void 3152311Sjkhedit_cmd() { 3162311Sjkh char n[MAX_FNAME], q[MAX_TEMPSTR], *editor; 3172311Sjkh FILE *f; 318135174Sdds int t; 31968388Sdwmalone struct stat statbuf, fsbuf; 3202311Sjkh WAIT_T waiter; 3212311Sjkh PID_T pid, xpid; 32220573Spst mode_t um; 323135165Sdds int syntax_error = 0; 324135242Sdds char orig_md5[MD5_SIZE]; 325135242Sdds char new_md5[MD5_SIZE]; 3262311Sjkh 3272311Sjkh log_it(RealUser, Pid, "BEGIN EDIT", User); 328184779Smatteo (void) snprintf(n, sizeof(n), CRON_TAB(User)); 3292311Sjkh if (!(f = fopen(n, "r"))) { 33029452Scharnier if (errno != ENOENT) 33129452Scharnier err(ERROR_EXIT, "%s", n); 33229452Scharnier warnx("no crontab for %s - using an empty one", User); 33369793Sobrien if (!(f = fopen(_PATH_DEVNULL, "r"))) 33469793Sobrien err(ERROR_EXIT, _PATH_DEVNULL); 3352311Sjkh } 3362311Sjkh 33720573Spst um = umask(077); 338184779Smatteo (void) snprintf(Filename, sizeof(Filename), "/tmp/crontab.XXXXXXXXXX"); 33920573Spst if ((t = mkstemp(Filename)) == -1) { 34029452Scharnier warn("%s", Filename); 34120573Spst (void) umask(um); 3422311Sjkh goto fatal; 3432311Sjkh } 34420573Spst (void) umask(um); 3452311Sjkh#ifdef HAS_FCHOWN 3462311Sjkh if (fchown(t, getuid(), getgid()) < 0) { 3472311Sjkh#else 3482311Sjkh if (chown(Filename, getuid(), getgid()) < 0) { 3492311Sjkh#endif 35029452Scharnier warn("fchown"); 3512311Sjkh goto fatal; 3522311Sjkh } 35368388Sdwmalone if (!(NewCrontab = fdopen(t, "r+"))) { 35429452Scharnier warn("fdopen"); 3552311Sjkh goto fatal; 3562311Sjkh } 3572311Sjkh 358135174Sdds copy_file(f, NewCrontab); 3592311Sjkh fclose(f); 36068388Sdwmalone if (fflush(NewCrontab)) 36129452Scharnier err(ERROR_EXIT, "%s", Filename); 36268388Sdwmalone if (fstat(t, &fsbuf) < 0) { 36368388Sdwmalone warn("unable to fstat temp file"); 36468388Sdwmalone goto fatal; 36568388Sdwmalone } 3662311Sjkh again: 367232537Sdelphij if (swap_uids() < OK) 368232537Sdelphij err(ERROR_EXIT, "swapping uids"); 3695176Sache if (stat(Filename, &statbuf) < 0) { 37029452Scharnier warn("stat"); 3712311Sjkh fatal: unlink(Filename); 3722311Sjkh exit(ERROR_EXIT); 3732311Sjkh } 374232537Sdelphij if (swap_uids_back() < OK) 375232537Sdelphij err(ERROR_EXIT, "swapping uids back"); 37668388Sdwmalone if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino) 37768388Sdwmalone errx(ERROR_EXIT, "temp file must be edited in place"); 378135242Sdds if (MD5File(Filename, orig_md5) == NULL) { 379135242Sdds warn("MD5"); 380135242Sdds goto fatal; 381135242Sdds } 3822311Sjkh 3832311Sjkh if ((!(editor = getenv("VISUAL"))) 3842311Sjkh && (!(editor = getenv("EDITOR"))) 3852311Sjkh ) { 3862311Sjkh editor = EDITOR; 3872311Sjkh } 3882311Sjkh 3892311Sjkh /* we still have the file open. editors will generally rewrite the 3902311Sjkh * original file rather than renaming/unlinking it and starting a 3912311Sjkh * new one; even backup files are supposed to be made by copying 3922311Sjkh * rather than by renaming. if some editor does not support this, 3932311Sjkh * then don't use it. the security problems are more severe if we 3942311Sjkh * close and reopen the file around the edit. 3952311Sjkh */ 3962311Sjkh 3972311Sjkh switch (pid = fork()) { 3982311Sjkh case -1: 39929452Scharnier warn("fork"); 4002311Sjkh goto fatal; 4012311Sjkh case 0: 4022311Sjkh /* child */ 40329452Scharnier if (setuid(getuid()) < 0) 40429452Scharnier err(ERROR_EXIT, "setuid(getuid())"); 40529452Scharnier if (chdir("/tmp") < 0) 40629452Scharnier err(ERROR_EXIT, "chdir(/tmp)"); 40729452Scharnier if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) 40829452Scharnier errx(ERROR_EXIT, "editor or filename too long"); 40979452Sbrian execlp(editor, editor, Filename, (char *)NULL); 41029452Scharnier err(ERROR_EXIT, "%s", editor); 4112311Sjkh /*NOTREACHED*/ 4122311Sjkh default: 4132311Sjkh /* parent */ 4142311Sjkh break; 4152311Sjkh } 4162311Sjkh 4172311Sjkh /* parent */ 41815161Sscrappy { 419184809Smatteo void (*sig[3])(int signal); 420184809Smatteo sig[0] = signal(SIGHUP, SIG_IGN); 421184809Smatteo sig[1] = signal(SIGINT, SIG_IGN); 422184809Smatteo sig[2] = signal(SIGTERM, SIG_IGN); 4232311Sjkh xpid = wait(&waiter); 424184809Smatteo signal(SIGHUP, sig[0]); 425184809Smatteo signal(SIGINT, sig[1]); 426184809Smatteo signal(SIGTERM, sig[2]); 42715161Sscrappy } 4282311Sjkh if (xpid != pid) { 42929452Scharnier warnx("wrong PID (%d != %d) from \"%s\"", xpid, pid, editor); 4302311Sjkh goto fatal; 4312311Sjkh } 4322311Sjkh if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { 43329452Scharnier warnx("\"%s\" exited with status %d", editor, WEXITSTATUS(waiter)); 4342311Sjkh goto fatal; 4352311Sjkh } 4362311Sjkh if (WIFSIGNALED(waiter)) { 43729452Scharnier warnx("\"%s\" killed; signal %d (%score dumped)", 43829452Scharnier editor, WTERMSIG(waiter), WCOREDUMP(waiter) ?"" :"no "); 4392311Sjkh goto fatal; 4402311Sjkh } 441232537Sdelphij if (swap_uids() < OK) 442232537Sdelphij err(ERROR_EXIT, "swapping uids"); 4435176Sache if (stat(Filename, &statbuf) < 0) { 44429452Scharnier warn("stat"); 4452311Sjkh goto fatal; 4462311Sjkh } 44768388Sdwmalone if (statbuf.st_dev != fsbuf.st_dev || statbuf.st_ino != fsbuf.st_ino) 44868388Sdwmalone errx(ERROR_EXIT, "temp file must be edited in place"); 449135242Sdds if (MD5File(Filename, new_md5) == NULL) { 450135242Sdds warn("MD5"); 451135242Sdds goto fatal; 452135242Sdds } 453232537Sdelphij if (swap_uids_back() < OK) 454232537Sdelphij err(ERROR_EXIT, "swapping uids back"); 455135242Sdds if (strcmp(orig_md5, new_md5) == 0 && !syntax_error) { 45629452Scharnier warnx("no changes made to crontab"); 4572311Sjkh goto remove; 4582311Sjkh } 45929452Scharnier warnx("installing new crontab"); 4602311Sjkh switch (replace_cmd()) { 461135165Sdds case 0: /* Success */ 4622311Sjkh break; 463135165Sdds case -1: /* Syntax error */ 4642311Sjkh for (;;) { 4652311Sjkh printf("Do you want to retry the same edit? "); 4662311Sjkh fflush(stdout); 4672311Sjkh q[0] = '\0'; 4682311Sjkh (void) fgets(q, sizeof q, stdin); 4692311Sjkh switch (islower(q[0]) ? q[0] : tolower(q[0])) { 4702311Sjkh case 'y': 471135165Sdds syntax_error = 1; 4722311Sjkh goto again; 4732311Sjkh case 'n': 4742311Sjkh goto abandon; 4752311Sjkh default: 4762311Sjkh fprintf(stderr, "Enter Y or N\n"); 4772311Sjkh } 4782311Sjkh } 4792311Sjkh /*NOTREACHED*/ 480135165Sdds case -2: /* Install error */ 4812311Sjkh abandon: 48229452Scharnier warnx("edits left in %s", Filename); 4832311Sjkh goto done; 4842311Sjkh default: 48529452Scharnier warnx("panic: bad switch() in replace_cmd()"); 4862311Sjkh goto fatal; 4872311Sjkh } 4882311Sjkh remove: 4892311Sjkh unlink(Filename); 4902311Sjkh done: 4912311Sjkh log_it(RealUser, Pid, "END EDIT", User); 4922311Sjkh} 4932311Sjkh 4948857Srgrimes 4952311Sjkh/* returns 0 on success 4962311Sjkh * -1 on syntax error 4972311Sjkh * -2 on install error 4982311Sjkh */ 4992311Sjkhstatic int 5002311Sjkhreplace_cmd() { 5012311Sjkh char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; 5022311Sjkh FILE *tmp; 5032311Sjkh int ch, eof; 5042311Sjkh entry *e; 5052311Sjkh time_t now = time(NULL); 5062311Sjkh char **envp = env_init(); 5072311Sjkh 50820573Spst if (envp == NULL) { 50929452Scharnier warnx("cannot allocate memory"); 51020573Spst return (-2); 51120573Spst } 51220573Spst 513184779Smatteo (void) snprintf(n, sizeof(n), "tmp.%d", Pid); 514185041Smatteo (void) snprintf(tn, sizeof(tn), CRON_TAB(n)); 515184780Smatteo 5162311Sjkh if (!(tmp = fopen(tn, "w+"))) { 51729452Scharnier warn("%s", tn); 5182311Sjkh return (-2); 5192311Sjkh } 5202311Sjkh 5212311Sjkh /* write a signature at the top of the file. 5222311Sjkh * 5232311Sjkh * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. 5242311Sjkh */ 5252311Sjkh fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); 5262311Sjkh fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); 5272311Sjkh fprintf(tmp, "# (Cron version -- %s)\n", rcsid); 5282311Sjkh 5292311Sjkh /* copy the crontab to the tmp 5302311Sjkh */ 53168388Sdwmalone rewind(NewCrontab); 5322311Sjkh Set_LineNum(1) 5332311Sjkh while (EOF != (ch = get_char(NewCrontab))) 5342311Sjkh putc(ch, tmp); 5352311Sjkh ftruncate(fileno(tmp), ftell(tmp)); 5362311Sjkh fflush(tmp); rewind(tmp); 5372311Sjkh 5382311Sjkh if (ferror(tmp)) { 53929452Scharnier warnx("error while writing new crontab to %s", tn); 5402311Sjkh fclose(tmp); unlink(tn); 5412311Sjkh return (-2); 5422311Sjkh } 5432311Sjkh 5442311Sjkh /* check the syntax of the file being installed. 5452311Sjkh */ 5462311Sjkh 5472311Sjkh /* BUG: was reporting errors after the EOF if there were any errors 5482311Sjkh * in the file proper -- kludged it by stopping after first error. 5492311Sjkh * vix 31mar87 5502311Sjkh */ 5512311Sjkh Set_LineNum(1 - NHEADER_LINES) 5522311Sjkh CheckErrorCount = 0; eof = FALSE; 5532311Sjkh while (!CheckErrorCount && !eof) { 5542311Sjkh switch (load_env(envstr, tmp)) { 5552311Sjkh case ERR: 5562311Sjkh eof = TRUE; 5572311Sjkh break; 5582311Sjkh case FALSE: 5592311Sjkh e = load_entry(tmp, check_error, pw, envp); 5602311Sjkh if (e) 5612311Sjkh free(e); 5622311Sjkh break; 5632311Sjkh case TRUE: 5642311Sjkh break; 5652311Sjkh } 5662311Sjkh } 5672311Sjkh 5682311Sjkh if (CheckErrorCount != 0) { 56929452Scharnier warnx("errors in crontab file, can't install"); 5702311Sjkh fclose(tmp); unlink(tn); 5712311Sjkh return (-1); 5722311Sjkh } 5732311Sjkh 5742311Sjkh#ifdef HAS_FCHOWN 5752311Sjkh if (fchown(fileno(tmp), ROOT_UID, -1) < OK) 5762311Sjkh#else 5772311Sjkh if (chown(tn, ROOT_UID, -1) < OK) 5782311Sjkh#endif 5792311Sjkh { 58029452Scharnier warn("chown"); 5812311Sjkh fclose(tmp); unlink(tn); 5822311Sjkh return (-2); 5832311Sjkh } 5842311Sjkh 5852311Sjkh#ifdef HAS_FCHMOD 5862311Sjkh if (fchmod(fileno(tmp), 0600) < OK) 5872311Sjkh#else 5882311Sjkh if (chmod(tn, 0600) < OK) 5892311Sjkh#endif 5902311Sjkh { 59129452Scharnier warn("chown"); 5922311Sjkh fclose(tmp); unlink(tn); 5932311Sjkh return (-2); 5942311Sjkh } 5952311Sjkh 5962311Sjkh if (fclose(tmp) == EOF) { 59729452Scharnier warn("fclose"); 5982311Sjkh unlink(tn); 5992311Sjkh return (-2); 6002311Sjkh } 6012311Sjkh 602184779Smatteo (void) snprintf(n, sizeof(n), CRON_TAB(User)); 6032311Sjkh if (rename(tn, n)) { 60429452Scharnier warn("error renaming %s to %s", tn, n); 6052311Sjkh unlink(tn); 6062311Sjkh return (-2); 6072311Sjkh } 608184780Smatteo 6092311Sjkh log_it(RealUser, Pid, "REPLACE", User); 6102311Sjkh 611239876Sjhb /* 612239876Sjhb * Creating the 'tn' temp file has already updated the 613239876Sjhb * modification time of the spool directory. Sleep for a 614239876Sjhb * second to ensure that poke_daemon() sets a later 615239876Sjhb * modification time. Otherwise, this can race with the cron 616239876Sjhb * daemon scanning for updated crontabs. 617239876Sjhb */ 618239876Sjhb sleep(1); 619239876Sjhb 6202311Sjkh poke_daemon(); 6212311Sjkh 6222311Sjkh return (0); 6232311Sjkh} 6242311Sjkh 6252311Sjkh 6262311Sjkhstatic void 6272311Sjkhpoke_daemon() { 6282311Sjkh#ifdef USE_UTIMES 6292311Sjkh struct timeval tvs[2]; 6302311Sjkh struct timezone tz; 6312311Sjkh 6322311Sjkh (void) gettimeofday(&tvs[0], &tz); 6332311Sjkh tvs[1] = tvs[0]; 6342311Sjkh if (utimes(SPOOL_DIR, tvs) < OK) { 63529452Scharnier warn("can't update mtime on spooldir %s", SPOOL_DIR); 6362311Sjkh return; 6372311Sjkh } 6382311Sjkh#else 6392311Sjkh if (utime(SPOOL_DIR, NULL) < OK) { 64029452Scharnier warn("can't update mtime on spooldir %s", SPOOL_DIR); 6412311Sjkh return; 6422311Sjkh } 6432311Sjkh#endif /*USE_UTIMES*/ 6442311Sjkh} 645