void tab_parse(char *file, char *user) /* Parse a crontab file and add its data to the tables. Handle errors by * yourself. Table is owned by 'user' if non-null. */ { crontab_t **atab, *tab; cronjob_t **ajob, *job; int fd; struct stat st; char *p, *q; size_t n; ssize_t r; int ok, wc; for (atab= &crontabs; (tab= *atab) != nil; atab= &tab->next) { if (strcmp(file, tab->file) == 0) break; } /* Try to open the file. */ if ((fd= open(file, O_RDONLY)) < 0 || fstat(fd, &st) < 0) { if (errno != ENOENT) { log(LOG_ERR, "%s: %s\n", file, strerror(errno)); } if (fd != -1) close(fd); return; } /* Forget it if the file is awfully big. */ if (st.st_size > TAB_MAX) { log(LOG_ERR, "%s: %lu bytes is bigger than my %lu limit\n", file, (unsigned long) st.st_size, (unsigned long) TAB_MAX); return; } /* If the file is the same as before then don't bother. */ if (tab != nil && st.st_mtime == tab->mtime) { close(fd); tab->current= 1; return; } /* Create a new table structure. */ tab= allocate(sizeof(*tab)); tab->file= allocate((strlen(file) + 1) * sizeof(tab->file[0])); strcpy(tab->file, file); tab->user= nil; if (user != nil) { tab->user= allocate((strlen(user) + 1) * sizeof(tab->user[0])); strcpy(tab->user, user); } tab->data= allocate((st.st_size + 1) * sizeof(tab->data[0])); tab->jobs= nil; tab->mtime= st.st_mtime; tab->current= 0; tab->next= *atab; *atab= tab; /* Pull a new table in core. */ n= 0; while (n < st.st_size) { if ((r = read(fd, tab->data + n, st.st_size - n)) < 0) { log(LOG_CRIT, "%s: %s", file, strerror(errno)); close(fd); return; } if (r == 0) break; n+= r; } close(fd); tab->data[n]= 0; if (strlen(tab->data) < n) { log(LOG_ERR, "%s contains a null character\n", file); return; } /* Parse the file. */ ajob= &tab->jobs; p= tab->data; ok= 1; while (ok && *p != 0) { q= get_token(&p); if (*q == '#' || q == p) { /* Comment or empty. */ while (*p != 0 && *p++ != '\n') {} continue; } /* One new job coming up. */ *ajob= job= allocate(sizeof(*job)); *(ajob= &job->next)= nil; job->tab= tab; if (!range_parse(file, q, job->min, 0, 59, &wc)) { ok= 0; break; } q= get_token(&p); if (!range_parse(file, q, job->hour, 0, 23, &wc)) { ok= 0; break; } q= get_token(&p); if (!range_parse(file, q, job->mday, 1, 31, &wc)) { ok= 0; break; } job->do_mday= !wc; q= get_token(&p); if (!range_parse(file, q, job->mon, 1, 12, &wc)) { ok= 0; break; } job->do_mday |= !wc; q= get_token(&p); if (!range_parse(file, q, job->wday, 0, 7, &wc)) { ok= 0; break; } job->do_wday= !wc; /* 7 is Sunday, but 0 is a common mistake because it is in the * tm_wday range. We allow and even prefer it internally. */ if (bit_isset(job->wday, 7)) { bit_clr(job->wday, 7); bit_set(job->wday, 0); } /* The month range is 1-12, but tm_mon likes 0-11. */ job->mon[0] >>= 1; if (bit_isset(job->mon, 8)) bit_set(job->mon, 7); job->mon[1] >>= 1; /* Scan for options. */ job->user= nil; while (q= get_token(&p), *q == '-') { q++; if (q[0] == '-' && q+1 == p) { /* -- */ q= get_token(&p); break; } while (q < p) switch (*q++) { case 'u': if (q == p) q= get_token(&p); if (q == p) goto usage; memmove(q-1, q, p-q); /* gross... */ p[-1]= 0; job->user= q-1; q= p; break; default: usage: log(LOG_ERR, "%s: bad option -%c, good options are: -u username\n", file, q[-1]); ok= 0; goto endtab; } } /* A crontab owned by a user can only do things as that user. */ if (tab->user != nil) job->user= tab->user; /* Inspect the first character of the command. */ job->cmd= q; if (q == p || *q == '#') { /* Rest of the line is empty, i.e. the commands are on * the following lines indented by one tab. */ while (*p != 0 && *p++ != '\n') {} if (*p++ != '\t') { log(LOG_ERR, "%s: contains an empty command\n", file); ok= 0; goto endtab; } while (*p != 0) { if ((*q = *p++) == '\n') { if (*p != '\t') break; p++; } q++; } } else { /* The command is on this line. Alas we must now be * backwards compatible and change %'s to newlines. */ p= q; while (*p != 0) { if ((*q = *p++) == '\n') break; if (*q == '%') *q= '\n'; q++; } } *q= 0; job->rtime= now; job->late= 0; /* It is on time. */ job->atjob= 0; /* True cron job. */ job->pid= IDLE_PID; /* Not running yet. */ tab_reschedule(job); /* Compute next time to run. */ } endtab: if (ok) tab->current= 1; }
static void run_job(cronjob_t *job) /* Execute a cron job. Register its pid in the job structure. If a job's * crontab has an owner then its output is mailed to that owner, otherwise * no special provisions are made, so the output will go where cron's output * goes. This keeps root's mailbox from filling up. */ { pid_t pid; int need_mailer; int mailfd[2], errfd[2]; struct passwd *pw; crontab_t *tab= job->tab; need_mailer= (tab->user != nil); if (job->atjob) { struct stat st; need_mailer= 1; if (rename(tab->file, tab->data) < 0) { if (errno == ENOENT) { /* Normal error, job deleted. */ need_reload= 1; } else { /* Bad error, halt processing AT jobs. */ log(LOG_CRIT, "Can't rename %s: %s\n", tab->file, strerror(errno)); tab_reschedule(job); } return; } /* Will need to determine the next AT job. */ need_reload= 1; if (stat(tab->data, &st) < 0) { log(LOG_ERR, "Can't stat %s: %s\n", tab->data, strerror(errno)); tab_reschedule(job); return; } if ((pw= getpwuid(st.st_uid)) == nil) { log(LOG_ERR, "Unknown owner for uid %lu of AT job %s\n", (unsigned long) st.st_uid, job->cmd); tab_reschedule(job); return; } } else { pw= nil; if (job->user != nil && (pw= getpwnam(job->user)) == nil) { log(LOG_ERR, "%s: Unknown user\n", job->user); tab_reschedule(job); return; } } if (need_mailer) { errfd[0]= -1; if (pipe(errfd) < 0 || pipe(mailfd) < 0) { log(LOG_ERR, "pipe() call failed: %s\n", strerror(errno)); if (errfd[0] != -1) { close(errfd[0]); close(errfd[1]); } tab_reschedule(job); return; } (void) fcntl(errfd[1], F_SETFD, fcntl(errfd[1], F_GETFD) | FD_CLOEXEC); if ((pid= fork()) == -1) { log(LOG_ERR, "fork() call failed: %s\n", strerror(errno)); close(errfd[0]); close(errfd[1]); close(mailfd[0]); close(mailfd[1]); tab_reschedule(job); return; } if (pid == 0) { /* Child that is to be the mailer. */ char subject[70+20], *ps; close(errfd[0]); close(mailfd[1]); if (mailfd[0] != 0) { dup2(mailfd[0], 0); close(mailfd[0]); } memset(subject, 0, sizeof(subject)); sprintf(subject, "Output from your %s job: %.50s", job->atjob ? "AT" : "cron", job->cmd); if (subject[70] != 0) { strcpy(subject+70-3, "..."); } for (ps= subject; *ps != 0; ps++) { if (*ps == '\n') *ps= '%'; } execl("/usr/bin/mail", "mail", "-s", subject, pw->pw_name, (char *) nil); write(errfd[1], &errno, sizeof(errno)); _exit(1); } close(mailfd[0]); close(errfd[1]); if (read(errfd[0], &errno, sizeof(errno)) > 0) { log(LOG_ERR, "can't execute /usr/bin/mail: %s\n", strerror(errno)); close(errfd[0]); close(mailfd[1]); tab_reschedule(job); return; } close(errfd[0]); } if (pipe(errfd) < 0) { log(LOG_ERR, "pipe() call failed: %s\n", strerror(errno)); if (need_mailer) close(mailfd[1]); tab_reschedule(job); return; } (void) fcntl(errfd[1], F_SETFD, fcntl(errfd[1], F_GETFD) | FD_CLOEXEC); if ((pid= fork()) == -1) { log(LOG_ERR, "fork() call failed: %s\n", strerror(errno)); close(errfd[0]); close(errfd[1]); if (need_mailer) close(mailfd[1]); tab_reschedule(job); return; } if (pid == 0) { /* Child that is to be the cron job. */ close(errfd[0]); if (need_mailer) { if (mailfd[1] != 1) { dup2(mailfd[1], 1); close(mailfd[1]); } dup2(1, 2); } if (pw != nil) { /* Change id to the owner of the job. */ initgroups(pw->pw_name, pw->pw_gid); setgid(pw->pw_gid); setuid(pw->pw_uid); chdir(pw->pw_dir); if (setenv("USER", pw->pw_name, 1) < 0) goto bad; if (setenv("LOGNAME", pw->pw_name, 1) < 0) goto bad; if (setenv("HOME", pw->pw_dir, 1) < 0) goto bad; if (setenv("SHELL", pw->pw_shell[0] == 0 ? "/bin/sh" : pw->pw_shell, 1) < 0) goto bad; } if (job->atjob) { execl("/bin/sh", "sh", tab->data, (char *) nil); } else { execl("/bin/sh", "sh", "-c", job->cmd, (char *) nil); } bad: write(errfd[1], &errno, sizeof(errno)); _exit(1); } if (need_mailer) close(mailfd[1]); close(errfd[1]); if (read(errfd[0], &errno, sizeof(errno)) > 0) { log(LOG_ERR, "can't execute /bin/sh: %s\n", strerror(errno)); close(errfd[0]); tab_reschedule(job); return; } close(errfd[0]); job->pid= pid; if (debug >= 1) fprintf(stderr, "executing >%s<, pid = %ld\n", job->cmd, (long) job->pid); }