static void list_cmd(void) { char n[MAX_FNAME]; FILE *f; int ch; log_it(RealUser, Pid, "LIST", User); if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { errx(ERROR_EXIT, "path too long"); } if (!(f = fopen(n, "r"))) { if (errno == ENOENT) errx(ERROR_EXIT, "no crontab for `%s'", User); else err(ERROR_EXIT, "Cannot open `%s'", n); } /* file is open. copy to stdout, close. */ Set_LineNum(1); skip_header(&ch, f); for (; EOF != ch; ch = get_char(f)) (void)putchar(ch); (void)fclose(f); }
static void list_cmd (void) { char n[MAX_FNAME]; FILE *f; int ch; log_it (RealUser, Pid, "LIST", User); (void) snprintf (n, MAX_FNAME, CRON_TAB (User)); if (!(f = fopen (n, "r"))) { if (errno == ENOENT) fprintf (stderr, "no crontab for %s\n", User); else perror (n); exit (ERROR_EXIT); } /* file is open. copy to stdout, close. */ Set_LineNum (1); while (EOF != (ch = get_char (f))) putchar (ch); fclose (f); }
/* unget_char(ch, file) : like ungetc but do LineNumber processing */ void unget_char(int ch, FILE *file) { ungetc(ch, file); if (ch == '\n') { Set_LineNum(LineNumber - 1); } }
/* get_char(file) : like getc() but increment LineNumber on newlines */ int get_char(FILE *file) { int ch; ch = getc(file); if (ch == '\n') { Set_LineNum(LineNumber + 1); } return ch; }
/* returns 0 on success * -1 on syntax error * -2 on install error */ static int replace_cmd (void) { char n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME]; FILE *tmp; int ch, eof; entry *e; char **envp = env_init (); int n_header_lines; /* Number of #CRONTAB header lines we write */ (void) snprintf (n, MAX_FNAME, "tmp.%d", Pid); (void) snprintf (tn, MAX_FNAME, CRON_TAB (n)); if (!(tmp = fopen (tn, "w+"))) { fprintf (stderr, "Unable to create new crontab file '%s'.\n" "fopen() failed with errno=%s (%d)\n", tn, strerror (errno), errno); if (errno == EACCES) fprintf (stderr, "In order to have permission to create such files,\n" "Crontab normally is installed setuid either superuser or \n" "a user or group that owns the crontab files and " "directories.\n"); return (-2); } write_header_if_wanted (tmp, &n_header_lines); /* copy the crontab to the tmp */ rewind (NewCrontab); Set_LineNum (1); while (EOF != (ch = get_char (NewCrontab))) putc (ch, tmp); ftruncate (fileno (tmp), ftell (tmp)); fflush (tmp); rewind (tmp); if (ferror (tmp)) { fprintf (stderr, "%s: error while writing new crontab to %s\n", ProgramName, tn); fclose (tmp); unlink (tn); return (-2); } /* check the syntax of the file being installed. */ /* BUG: was reporting errors after the EOF if there were any errors * in the file proper -- kludged it by stopping after first error. * vix 31mar87 */ Set_LineNum (1 - n_header_lines); CheckErrorCount = 0; eof = FALSE; while (!CheckErrorCount && !eof) { switch (load_env (envstr, tmp)) { case ERR: eof = TRUE; break; case FALSE: e = load_entry (tmp, check_error, pw, envp); if (e) free (e); break; case TRUE: break; } } if (CheckErrorCount != 0) { fprintf (stderr, "errors in crontab file, can't install.\n"); fclose (tmp); unlink (tn); return (-1); } /* Why are we doing this? We just created this file, so if we're privileged to do a chown(), the file's owner is already root. (Maybe on some systems the file doesn't automatically get the creator's effective uid?) */ #ifdef HAVE_FCHOWN if (fchown (fileno (tmp), ROOT_UID, -1) < OK) #else if (chown (tn, ROOT_UID, -1) < OK) #endif { if (errno == EPERM) { /* This just means we aren't running as superuser. We already passed the test of having enough privilege to create the crontab file in the crontab directory, so the fact that we aren't superuser just means the system administrator wants the crontabs owned by the owner of Crontab, not root. So let it be. */ } else { perror ("chown"); fclose (tmp); unlink (tn); return (-2); } } #ifdef HAVE_FCHMOD if (fchmod (fileno (tmp), 0600) < OK) #else if (chmod (tn, 0600) < OK) #endif { perror ("chmod"); fclose (tmp); unlink (tn); return (-2); } if (fclose (tmp) == EOF) { perror ("fclose"); unlink (tn); return (-2); } (void) snprintf (n, sizeof (n), CRON_TAB (User)); if (rename (tn, n)) { fprintf (stderr, "%s: error renaming %s to %s\n", ProgramName, tn, n); perror ("rename"); unlink (tn); return (-2); } log_it (RealUser, Pid, "REPLACE", User); poke_daemon (); return (0); }
/* return ERR = end of file * FALSE = not an env setting (file was repositioned) * TRUE = was an env setting */ int load_env(char *envstr, FILE *f) { long filepos; int fileline; enum env_state state; char name[MAX_ENVSTR], val[MAX_ENVSTR]; char quotechar, *c, *str; filepos = ftell(f); fileline = LineNumber; skip_comments(f); if (EOF == get_string(envstr, MAX_ENVSTR, f, "\n")) return (ERR); Debug(DPARS, ("load_env, read <%s>\n", envstr)); (void)memset(name, 0, sizeof name); (void)memset(val, 0, sizeof val); str = name; state = NAMEI; quotechar = '\0'; c = envstr; while (state != ERROR && *c) { switch (state) { case NAMEI: case VALUEI: if (*c == '\'' || *c == '"') quotechar = *c++; state++; /* FALLTHROUGH */ case NAME: case VALUE: if (quotechar) { if (*c == quotechar) { state++; c++; break; } if (state == NAME && *c == '=') { state = ERROR; break; } } else { if (state == NAME) { if (isspace((unsigned char)*c)) { c++; state++; break; } if (*c == '=') { state++; break; } } } *str++ = *c++; break; case EQ1: if (*c == '=') { state++; str = val; quotechar = '\0'; } else { if (!isspace((unsigned char)*c)) state = ERROR; } c++; break; case EQ2: case FINI: if (isspace((unsigned char)*c)) c++; else state++; break; default: abort(); } } if (state != FINI && !(state == VALUE && !quotechar)) { Debug(DPARS, ("load_env, not an env var, state = %d\n", state)); (void)fseek(f, filepos, 0); Set_LineNum(fileline); return (FALSE); } if (state == VALUE) { /* End of unquoted value: trim trailing whitespace */ c = val + strlen(val); while (c > val && isspace((unsigned char)c[-1])) *(--c) = '\0'; } /* 2 fields from parser; looks like an env setting */ /* * This can't overflow because get_string() limited the size of the * name and val fields. Still, it doesn't hurt to be careful... */ if (!glue_strings(envstr, MAX_ENVSTR, name, val, '=')) return (FALSE); Debug(DPARS, ("load_env, <%s> <%s> -> <%s>\n", name, val, envstr)); return (TRUE); }
/* returns 0 on success * -1 on syntax error * -2 on install error */ static int replace_cmd(void) { char n[MAX_FNAME], n2[MAX_FNAME], envstr[MAX_ENVSTR], lastch; FILE *tmp, *fmaxtabsize; int ch, eof, fd; int error = 0; entry *e; sig_t oint, oabrt, oquit, ohup; uid_t file_owner; time_t now = time(NULL); char **envp = env_init(); size_t maxtabsize; struct stat statbuf; if (envp == NULL) { warn("Cannot allocate memory."); return (-2); } if (!glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR, "tmp.XXXXXXXXXX", '/')) { TempFilename[0] = '\0'; warnx("path too long"); return (-2); } if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) { warn("cannot create `%s'", TempFilename); if (fd != -1) { (void)close(fd); (void)unlink(TempFilename); } TempFilename[0] = '\0'; return (-2); } ohup = signal(SIGHUP, SIG_IGN); oint = signal(SIGINT, SIG_IGN); oquit = signal(SIGQUIT, SIG_IGN); oabrt = signal(SIGABRT, SIG_IGN); /* Make sure that the crontab is not an unreasonable size. * * XXX This is subject to a race condition--the user could * add stuff to the file after we've checked the size but * before we slurp it in and write it out. We can't just move * the test to test the temp file we later create, because by * that time we've already filled up the crontab disk. Probably * the right thing to do is to do a bytecount in the copy loop * rather than stating the file we're about to read. */ (void)snprintf(n2, sizeof(n2), "%s/%s", CRONDIR, MAXTABSIZE_FILE); if ((fmaxtabsize = fopen(n2, "r")) != NULL) { if (fgets(n2, (int)sizeof(n2), fmaxtabsize) == NULL) { maxtabsize = 0; } else { maxtabsize = atoi(n2); } (void)fclose(fmaxtabsize); } else { maxtabsize = MAXTABSIZE_DEFAULT; } if (fstat(fileno(NewCrontab), &statbuf)) { warn("error stat'ing crontab input"); error = -2; goto done; } if ((uintmax_t)statbuf.st_size > (uintmax_t)maxtabsize) { warnx("%ld bytes is larger than the maximum size of %ld bytes", (long) statbuf.st_size, (long) maxtabsize); error = -2; goto done; } /* write a signature at the top of the file. * * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code. */ (void)fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n"); (void)fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now)); (void)fprintf(tmp, "# (Cron version %s -- %s)\n", CRON_VERSION, "$NetBSD: crontab.c,v 1.3.8.1 2012/03/07 23:41:17 riz Exp $"); /* copy the crontab to the tmp */ (void)rewind(NewCrontab); Set_LineNum(1); lastch = EOF; while (EOF != (ch = get_char(NewCrontab))) (void)putc(lastch = ch, tmp); if (lastch != (char)EOF && lastch != '\n') { warnx("missing trailing newline in `%s'", Filename); error = -1; goto done; } if (ferror(NewCrontab)) { warn("error while reading `%s'", Filename); error = -2; goto done; } (void)ftruncate(fileno(tmp), ftell(tmp)); /* XXX this should be a NOOP - is */ (void)fflush(tmp); if (ferror(tmp)) { (void)fclose(tmp); warn("error while writing new crontab to `%s'", TempFilename); error = -2; goto done; } /* check the syntax of the file being installed. */ /* BUG: was reporting errors after the EOF if there were any errors * in the file proper -- kludged it by stopping after first error. * vix 31mar87 */ Set_LineNum(1 - NHEADER_LINES); CheckErrorCount = 0; eof = FALSE; while (!CheckErrorCount && !eof) { switch (load_env(envstr, tmp)) { case ERR: /* check for data before the EOF */ if (envstr[0] != '\0') { Set_LineNum(LineNumber + 1); check_error("premature EOF"); } eof = TRUE; break; case FALSE: e = load_entry(tmp, check_error, pw, envp); if (e) free(e); break; case TRUE: break; } } if (CheckErrorCount != 0) { warnx("errors in crontab file, can't install."); (void)fclose(tmp); error = -1; goto done; } file_owner = (getgid() == getegid()) ? ROOT_UID : pw->pw_uid; #ifdef HAVE_FCHOWN error = fchown(fileno(tmp), file_owner, (uid_t)-1); #else error = chown(TempFilename, file_owner, (gid_t)-1); #endif if (error < OK) { warn("cannot chown `%s'", TempFilename); (void)fclose(tmp); error = -2; goto done; } if (fclose(tmp) == EOF) { warn("error closing file"); error = -2; goto done; } if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { warnx("path too long"); error = -2; goto done; } if (rename(TempFilename, n)) { warn("error renaming `%s' to `%s'", TempFilename, n); error = -2; goto done; } TempFilename[0] = '\0'; log_it(RealUser, Pid, "REPLACE", User); poke_daemon(); done: (void)signal(SIGHUP, ohup); (void)signal(SIGINT, oint); (void)signal(SIGQUIT, oquit); (void)signal(SIGABRT, oabrt); if (TempFilename[0]) { (void) unlink(TempFilename); TempFilename[0] = '\0'; } return (error); }
static void edit_cmd(void) { char n[MAX_FNAME], q[MAX_TEMPSTR]; const char *editor; FILE *f; int ch, t, x; sig_t oint, oabrt, oquit, ohup; struct stat statbuf; struct utimbuf utimebuf; long mtimensec; WAIT_T waiter; PID_T pid, xpid; log_it(RealUser, Pid, "BEGIN EDIT", User); if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { errx(ERROR_EXIT, "path too long"); } if (!(f = fopen(n, "r"))) { if (errno != ENOENT) { err(ERROR_EXIT, "cannot open `%s'", n); } warnx("no crontab for `%s' - using an empty one", User); if (!(f = fopen(_PATH_DEVNULL, "r"))) { err(ERROR_EXIT, "cannot open `%s'", _PATH_DEVNULL); } } if (fstat(fileno(f), &statbuf) < 0) { warn("cannot stat crontab file"); goto fatal; } utimebuf.actime = statbuf.st_atime; utimebuf.modtime = statbuf.st_mtime; mtimensec = statbuf.st_mtimensec; /* Turn off signals. */ ohup = signal(SIGHUP, SIG_IGN); oint = signal(SIGINT, SIG_IGN); oquit = signal(SIGQUIT, SIG_IGN); oabrt = signal(SIGABRT, SIG_IGN); if (!glue_strings(Filename, sizeof Filename, _PATH_TMP, "crontab.XXXXXXXXXX", '/')) { warnx("path too long"); goto fatal; } if (-1 == (t = mkstemp(Filename))) { warn("cannot create `%s'", Filename); goto fatal; } #ifdef HAS_FCHOWN x = fchown(t, MY_UID(pw), MY_GID(pw)); #else x = chown(Filename, MY_UID(pw), MY_GID(pw)); #endif if (x < 0) { warn("cannot chown `%s'", Filename); goto fatal; } if (!(NewCrontab = fdopen(t, "r+"))) { warn("cannot open fd"); goto fatal; } Set_LineNum(1); skip_header(&ch, f); /* copy the rest of the crontab (if any) to the temp file. */ for (; EOF != ch; ch = get_char(f)) (void)putc(ch, NewCrontab); (void)fclose(f); if (fflush(NewCrontab) < OK) { err(ERROR_EXIT, "cannot flush output for `%s'", Filename); } (void)utime(Filename, &utimebuf); again: rewind(NewCrontab); if (ferror(NewCrontab)) { warn("error while writing new crontab to `%s'", Filename); fatal: (void)unlink(Filename); exit(ERROR_EXIT); } if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') && ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) { editor = EDITOR; } /* we still have the file open. editors will generally rewrite the * original file rather than renaming/unlinking it and starting a * new one; even backup files are supposed to be made by copying * rather than by renaming. if some editor does not support this, * then don't use it. the security problems are more severe if we * close and reopen the file around the edit. */ switch (pid = fork()) { case -1: warn("cannot fork"); goto fatal; case 0: /* child */ if (setgid(MY_GID(pw)) < 0) { err(ERROR_EXIT, "cannot setgid(getgid())"); } if (setuid(MY_UID(pw)) < 0) { err(ERROR_EXIT, "cannot setuid(getuid())"); } if (chdir(_PATH_TMP) < 0) { err(ERROR_EXIT, "cannot chdir to `%s'", _PATH_TMP); } if (!glue_strings(q, sizeof q, editor, Filename, ' ')) { errx(ERROR_EXIT, "editor command line too long"); } (void)execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *)0); err(ERROR_EXIT, "cannot start `%s'", editor); /*NOTREACHED*/ default: /* parent */ break; } /* parent */ for (;;) { xpid = waitpid(pid, &waiter, WUNTRACED); if (xpid == -1) { if (errno != EINTR) warn("waitpid() failed waiting for PID %ld " "from `%s'", (long)pid, editor); } else if (xpid != pid) { warnx("wrong PID (%ld != %ld) from `%s'", (long)xpid, (long)pid, editor); goto fatal; } else if (WIFSTOPPED(waiter)) { (void)kill(getpid(), WSTOPSIG(waiter)); } else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { warnx("`%s' exited with status %d\n", editor, WEXITSTATUS(waiter)); goto fatal; } else if (WIFSIGNALED(waiter)) { warnx("`%s' killed; signal %d (%score dumped)", editor, WTERMSIG(waiter), WCOREDUMP(waiter) ? "" : "no "); goto fatal; } else break; } (void)signal(SIGHUP, ohup); (void)signal(SIGINT, oint); (void)signal(SIGQUIT, oquit); (void)signal(SIGABRT, oabrt); if (fstat(t, &statbuf) < 0) { warn("cannot stat `%s'", Filename); goto fatal; } if (utimebuf.modtime == statbuf.st_mtime && mtimensec == statbuf.st_mtimensec) { warnx("no changes made to crontab"); goto remove; } warnx("installing new crontab"); switch (replace_cmd()) { case 0: break; case -1: for (;;) { (void)fpurge(stdin); (void)printf("Do you want to retry the same edit? "); (void)fflush(stdout); q[0] = '\0'; (void) fgets(q, (int)sizeof(q), stdin); switch (q[0]) { case 'y': case 'Y': goto again; case 'n': case 'N': goto abandon; default: (void)printf("Enter Y or N\n"); } } /*NOTREACHED*/ case -2: abandon: warnx("edits left in `%s'", Filename); goto done; default: warnx("panic: bad switch() in replace_cmd()"); goto fatal; } remove: (void)unlink(Filename); done: log_it(RealUser, Pid, "END EDIT", User); }