1*3a50f0a9Sjmc /* $OpenBSD: atrun.c,v 1.54 2022/12/28 21:30:16 jmc Exp $ */
22b139a93Smillert
32b139a93Smillert /*
4bf198cc6Smillert * Copyright (c) 2002-2003 Todd C. Miller <millert@openbsd.org>
52b139a93Smillert *
6e134e629Smillert * Permission to use, copy, modify, and distribute this software for any
7e134e629Smillert * purpose with or without fee is hereby granted, provided that the above
8e134e629Smillert * copyright notice and this permission notice appear in all copies.
92b139a93Smillert *
10328f1f07Smillert * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11328f1f07Smillert * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12328f1f07Smillert * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13328f1f07Smillert * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14328f1f07Smillert * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15328f1f07Smillert * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16328f1f07Smillert * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17328f1f07Smillert *
18328f1f07Smillert * Sponsored in part by the Defense Advanced Research Projects
19328f1f07Smillert * Agency (DARPA) and Air Force Research Laboratory, Air Force
20328f1f07Smillert * Materiel Command, USAF, under agreement number F39502-99-1-0512.
212b139a93Smillert */
222b139a93Smillert
23fa575ea2Smillert #include <sys/types.h>
242b139a93Smillert #include <sys/resource.h>
25fa575ea2Smillert #include <sys/stat.h>
2695f2b4e2Smillert #include <sys/time.h>
27fa575ea2Smillert #include <sys/wait.h>
28fa575ea2Smillert
29fa575ea2Smillert #include <bitstring.h> /* for structs.h */
30fa575ea2Smillert #include <bsd_auth.h>
31fa575ea2Smillert #include <ctype.h>
32fa575ea2Smillert #include <dirent.h>
33b8c5c73dSmillert #include <err.h>
34fa575ea2Smillert #include <errno.h>
35fa575ea2Smillert #include <fcntl.h>
36fa575ea2Smillert #include <limits.h>
37fa575ea2Smillert #include <login_cap.h>
38fa575ea2Smillert #include <pwd.h>
39fa575ea2Smillert #include <signal.h>
40fa575ea2Smillert #include <stdio.h>
41fa575ea2Smillert #include <stdlib.h>
42fa575ea2Smillert #include <string.h>
43a38a3395Smillert #include <syslog.h>
44fa575ea2Smillert #include <time.h>
45fa575ea2Smillert #include <unistd.h>
46fa575ea2Smillert
47fa575ea2Smillert #include "config.h"
48fa575ea2Smillert #include "pathnames.h"
49fa575ea2Smillert #include "macros.h"
50fa575ea2Smillert #include "structs.h"
51fa575ea2Smillert #include "funcs.h"
52fa575ea2Smillert #include "globals.h"
532b139a93Smillert
54f64f78fdSmillert static void run_job(const atjob *, int, const char *);
552b139a93Smillert
562b139a93Smillert /*
572b139a93Smillert * Scan the at jobs dir and build up a list of jobs found.
582b139a93Smillert */
592b139a93Smillert int
scan_atjobs(at_db ** db,struct timespec * ts)608a6f8ff3Smillert scan_atjobs(at_db **db, struct timespec *ts)
612b139a93Smillert {
622b139a93Smillert DIR *atdir = NULL;
63a8f5ee9fSmillert int dfd, pending;
64a8f5ee9fSmillert const char *errstr;
651e5c894eSderaadt time_t run_time;
66a8f5ee9fSmillert char *queue;
678a6f8ff3Smillert at_db *new_db, *old_db = *db;
688a6f8ff3Smillert atjob *job;
692b139a93Smillert struct dirent *file;
70a642adccSmillert struct stat sb;
712b139a93Smillert
72558163bfSjca dfd = open(_PATH_AT_SPOOL, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
73558163bfSjca if (dfd == -1) {
74a38a3395Smillert syslog(LOG_ERR, "(CRON) OPEN FAILED (%s)", _PATH_AT_SPOOL);
75a642adccSmillert return (0);
76a642adccSmillert }
77a642adccSmillert if (fstat(dfd, &sb) != 0) {
78a38a3395Smillert syslog(LOG_ERR, "(CRON) FSTAT FAILED (%s)", _PATH_AT_SPOOL);
79a642adccSmillert close(dfd);
80a642adccSmillert return (0);
81a642adccSmillert }
8295f2b4e2Smillert if (old_db != NULL && timespeccmp(&old_db->mtime, &sb.st_mtim, ==)) {
83a642adccSmillert close(dfd);
842b139a93Smillert return (0);
852b139a93Smillert }
862b139a93Smillert
87a642adccSmillert if ((atdir = fdopendir(dfd)) == NULL) {
88a38a3395Smillert syslog(LOG_ERR, "(CRON) OPENDIR FAILED (%s)", _PATH_AT_SPOOL);
89a642adccSmillert close(dfd);
902b139a93Smillert return (0);
912b139a93Smillert }
922b139a93Smillert
938a6f8ff3Smillert if ((new_db = malloc(sizeof(*new_db))) == NULL) {
948a6f8ff3Smillert closedir(atdir);
958a6f8ff3Smillert return (0);
968a6f8ff3Smillert }
9795f2b4e2Smillert new_db->mtime = sb.st_mtim; /* stash at dir mtime */
988a6f8ff3Smillert TAILQ_INIT(&new_db->jobs);
992b139a93Smillert
1002b139a93Smillert pending = 0;
10120bdca44Smillert while ((file = readdir(atdir)) != NULL) {
102a642adccSmillert if (fstatat(dfd, file->d_name, &sb, AT_SYMLINK_NOFOLLOW) != 0 ||
103a642adccSmillert !S_ISREG(sb.st_mode))
1042b139a93Smillert continue;
1052b139a93Smillert
1062b139a93Smillert /*
1072b139a93Smillert * at jobs are named as RUNTIME.QUEUE
1082b139a93Smillert * RUNTIME is the time to run in seconds since the epoch
1092b139a93Smillert * QUEUE is a letter that designates the job's queue
1102b139a93Smillert */
111a8f5ee9fSmillert if ((queue = strchr(file->d_name, '.')) == NULL)
1122b139a93Smillert continue;
113a8f5ee9fSmillert *queue++ = '\0';
114a8f5ee9fSmillert run_time = strtonum(file->d_name, 0, LLONG_MAX, &errstr);
115a8f5ee9fSmillert if (errstr != NULL)
1161e5c894eSderaadt continue;
117a8f5ee9fSmillert if (!isalpha((unsigned char)*queue))
118a8f5ee9fSmillert continue;
1192b139a93Smillert
1204d4e2f5aStedu job = malloc(sizeof(*job));
1212b139a93Smillert if (job == NULL) {
1228a6f8ff3Smillert while ((job = TAILQ_FIRST(&new_db->jobs))) {
1238a6f8ff3Smillert TAILQ_REMOVE(&new_db->jobs, job, entries);
1248a6f8ff3Smillert free(job);
1252b139a93Smillert }
1268a6f8ff3Smillert free(new_db);
12720bdca44Smillert closedir(atdir);
1282b139a93Smillert return (0);
1292b139a93Smillert }
130a642adccSmillert job->uid = sb.st_uid;
131a642adccSmillert job->gid = sb.st_gid;
132a8f5ee9fSmillert job->queue = *queue;
1332b139a93Smillert job->run_time = run_time;
1348a6f8ff3Smillert TAILQ_INSERT_TAIL(&new_db->jobs, job, entries);
135f3ce7c4eSmillert if (ts != NULL && run_time <= ts->tv_sec)
1362b139a93Smillert pending = 1;
1372b139a93Smillert }
1382b139a93Smillert closedir(atdir);
1392b139a93Smillert
1408a6f8ff3Smillert /* Free up old at db and install new one */
1418a6f8ff3Smillert if (old_db != NULL) {
1428a6f8ff3Smillert while ((job = TAILQ_FIRST(&old_db->jobs))) {
1438a6f8ff3Smillert TAILQ_REMOVE(&old_db->jobs, job, entries);
1448a6f8ff3Smillert free(job);
1452b139a93Smillert }
1468a6f8ff3Smillert free(old_db);
1478a6f8ff3Smillert }
1488a6f8ff3Smillert *db = new_db;
1492b139a93Smillert
1502b139a93Smillert return (pending);
1512b139a93Smillert }
1522b139a93Smillert
1532b139a93Smillert /*
1542b139a93Smillert * Loop through the at job database and run jobs whose time have come.
1552b139a93Smillert */
1562b139a93Smillert void
atrun(at_db * db,double batch_maxload,time_t now)1571e5c894eSderaadt atrun(at_db *db, double batch_maxload, time_t now)
1582b139a93Smillert {
15988959323Smillert char atfile[PATH_MAX];
160a642adccSmillert struct stat sb;
1612b139a93Smillert double la;
162f64f78fdSmillert int dfd, len;
1638a6f8ff3Smillert atjob *job, *tjob, *batch = NULL;
1642b139a93Smillert
1658a6f8ff3Smillert if (db == NULL)
1668a6f8ff3Smillert return;
1678a6f8ff3Smillert
168558163bfSjca dfd = open(_PATH_AT_SPOOL, O_RDONLY|O_DIRECTORY|O_CLOEXEC);
169558163bfSjca if (dfd == -1) {
170f64f78fdSmillert syslog(LOG_ERR, "(CRON) OPEN FAILED (%s)", _PATH_AT_SPOOL);
171f64f78fdSmillert return;
172f64f78fdSmillert }
173f64f78fdSmillert
1748a6f8ff3Smillert TAILQ_FOREACH_SAFE(job, &db->jobs, entries, tjob) {
1752b139a93Smillert /* Skip jobs in the future */
1762b139a93Smillert if (job->run_time > now)
1772b139a93Smillert continue;
1782b139a93Smillert
179f64f78fdSmillert len = snprintf(atfile, sizeof(atfile), "%lld.%c",
1801e5c894eSderaadt (long long)job->run_time, job->queue);
181515e489cSderaadt if (len < 0 || len >= sizeof(atfile)) {
182f64f78fdSmillert TAILQ_REMOVE(&db->jobs, job, entries);
183f64f78fdSmillert free(job);
184f64f78fdSmillert continue;
185f64f78fdSmillert }
1862b139a93Smillert
187e1b228cdSmillert if (fstatat(dfd, atfile, &sb, AT_SYMLINK_NOFOLLOW) != 0) {
1888a6f8ff3Smillert TAILQ_REMOVE(&db->jobs, job, entries);
1898a6f8ff3Smillert free(job);
190*3a50f0a9Sjmc continue; /* disappeared from queue */
191e1b228cdSmillert }
192e1b228cdSmillert if (!S_ISREG(sb.st_mode)) {
193e1b228cdSmillert syslog(LOG_WARNING, "(CRON) NOT REGULAR (%s)",
194e1b228cdSmillert atfile);
195e1b228cdSmillert TAILQ_REMOVE(&db->jobs, job, entries);
196e1b228cdSmillert free(job);
197e1b228cdSmillert continue; /* was a file, no longer is */
1988a6f8ff3Smillert }
1992b139a93Smillert
2002b139a93Smillert /*
2012b139a93Smillert * Pending jobs have the user execute bit set.
2022b139a93Smillert */
203a642adccSmillert if (sb.st_mode & S_IXUSR) {
2042b139a93Smillert /* new job to run */
2052b139a93Smillert if (isupper(job->queue)) {
2062b139a93Smillert /* we run one batch job per atrun() call */
2072b139a93Smillert if (batch == NULL ||
2082b139a93Smillert job->run_time < batch->run_time)
2092b139a93Smillert batch = job;
2102b139a93Smillert } else {
2112b139a93Smillert /* normal at job */
212f64f78fdSmillert run_job(job, dfd, atfile);
2138a6f8ff3Smillert TAILQ_REMOVE(&db->jobs, job, entries);
2148a6f8ff3Smillert free(job);
2152b139a93Smillert }
2162b139a93Smillert }
2172b139a93Smillert }
2182b139a93Smillert
2192b139a93Smillert /* Run a single batch job if there is one pending. */
220e134e629Smillert if (batch != NULL
221e134e629Smillert && (batch_maxload == 0.0 ||
222e134e629Smillert ((getloadavg(&la, 1) == 1) && la <= batch_maxload))
223e134e629Smillert ) {
224f64f78fdSmillert len = snprintf(atfile, sizeof(atfile), "%lld.%c",
2251e5c894eSderaadt (long long)batch->run_time, batch->queue);
226515e489cSderaadt if (len < 0 || len >= sizeof(atfile))
227515e489cSderaadt ;
228515e489cSderaadt else
229f64f78fdSmillert run_job(batch, dfd, atfile);
2308a6f8ff3Smillert TAILQ_REMOVE(&db->jobs, batch, entries);
2318a6f8ff3Smillert free(job);
2322b139a93Smillert }
233f64f78fdSmillert
234f64f78fdSmillert close(dfd);
2352b139a93Smillert }
2362b139a93Smillert
2372b139a93Smillert /*
238a8f5ee9fSmillert * Check the at job header for sanity and extract the
239a8f5ee9fSmillert * uid, gid, mailto user and always_mail flag.
240a8f5ee9fSmillert *
241a8f5ee9fSmillert * The header should look like this:
242a8f5ee9fSmillert * #!/bin/sh
243a8f5ee9fSmillert * # atrun uid=123 gid=123
244a8f5ee9fSmillert * # mail joeuser 0
245a8f5ee9fSmillert */
246a8f5ee9fSmillert static int
parse_header(FILE * fp,uid_t * nuid,gid_t * ngid,char * mailto,int * always_mail)247a8f5ee9fSmillert parse_header(FILE *fp, uid_t *nuid, gid_t *ngid, char *mailto, int *always_mail)
248a8f5ee9fSmillert {
249a8f5ee9fSmillert char *cp, *ep, *line = NULL;
250a8f5ee9fSmillert const char *errstr;
251a8f5ee9fSmillert size_t size = 0;
252a8f5ee9fSmillert int lineno = 0;
253a8f5ee9fSmillert ssize_t len;
254a8f5ee9fSmillert int ret = -1;
255a8f5ee9fSmillert
256a8f5ee9fSmillert for (lineno = 1; (len = getline(&line, &size, fp)) != -1; lineno++) {
257a8f5ee9fSmillert if (line[--len] != '\n')
258a8f5ee9fSmillert break;
259a8f5ee9fSmillert line[len] = '\0';
260a8f5ee9fSmillert
261a8f5ee9fSmillert switch (lineno) {
262a8f5ee9fSmillert case 1:
263a8f5ee9fSmillert if (strcmp(line, "#!/bin/sh") != 0)
264a8f5ee9fSmillert goto done;
265a8f5ee9fSmillert break;
266a8f5ee9fSmillert case 2:
267a8f5ee9fSmillert if (strncmp(line, "# atrun uid=", 12) != 0)
268a8f5ee9fSmillert goto done;
269a8f5ee9fSmillert
270a8f5ee9fSmillert /* Pull out uid */
271a8f5ee9fSmillert cp = line + 12;
272a8f5ee9fSmillert if ((ep = strchr(cp, ' ')) == NULL)
273a8f5ee9fSmillert goto done;
274a8f5ee9fSmillert *ep++ = '\0';
275a8f5ee9fSmillert *nuid = strtonum(cp, 0, UID_MAX - 1, &errstr);
276a8f5ee9fSmillert if (errstr != NULL)
277a8f5ee9fSmillert goto done;
278a8f5ee9fSmillert
279a8f5ee9fSmillert /* Pull out gid */
280a8f5ee9fSmillert if (strncmp(ep, "gid=", 4) != 0)
281a8f5ee9fSmillert goto done;
282a8f5ee9fSmillert cp = ep + 4;
283a8f5ee9fSmillert *ngid = strtonum(cp, 0, GID_MAX - 1, &errstr);
284a8f5ee9fSmillert if (errstr != NULL)
285a8f5ee9fSmillert goto done;
286a8f5ee9fSmillert break;
287a8f5ee9fSmillert case 3:
288a8f5ee9fSmillert /* Pull out mailto user (and always_mail flag) */
289a8f5ee9fSmillert if (strncmp(line, "# mail ", 7) != 0)
290a8f5ee9fSmillert goto done;
291a8f5ee9fSmillert for (cp = line + 7; *cp == ' '; cp++)
292a8f5ee9fSmillert continue;
293a8f5ee9fSmillert if (*cp == '\0')
294a8f5ee9fSmillert goto done;
295a8f5ee9fSmillert for (ep = cp; *ep != ' ' && *ep != '\0'; ep++)
296a8f5ee9fSmillert continue;
297a8f5ee9fSmillert if (*ep != ' ')
298a8f5ee9fSmillert goto done;
299a8f5ee9fSmillert *ep++ = '\0';
300a8f5ee9fSmillert if (strlcpy(mailto, cp, MAX_UNAME) >= MAX_UNAME)
301a8f5ee9fSmillert goto done;
302a8f5ee9fSmillert *always_mail = *ep == '1';
303a8f5ee9fSmillert
304a8f5ee9fSmillert /* success */
305a8f5ee9fSmillert ret = 0;
306a8f5ee9fSmillert goto done;
307a8f5ee9fSmillert default:
308a8f5ee9fSmillert /* can't happen */
309a8f5ee9fSmillert goto done;
310a8f5ee9fSmillert }
311a8f5ee9fSmillert }
312a8f5ee9fSmillert done:
313a8f5ee9fSmillert free(line);
314a8f5ee9fSmillert return ret;
315a8f5ee9fSmillert }
316a8f5ee9fSmillert
317a8f5ee9fSmillert /*
3182b139a93Smillert * Run the specified job contained in atfile.
3192b139a93Smillert */
3202b139a93Smillert static void
run_job(const atjob * job,int dfd,const char * atfile)321f64f78fdSmillert run_job(const atjob *job, int dfd, const char *atfile)
3222b139a93Smillert {
323a642adccSmillert struct stat sb;
3242b139a93Smillert struct passwd *pw;
325b8c5c73dSmillert login_cap_t *lc;
326b8c5c73dSmillert auth_session_t *as;
3272b139a93Smillert pid_t pid;
328a8f5ee9fSmillert uid_t nuid;
329a8f5ee9fSmillert gid_t ngid;
3302b139a93Smillert FILE *fp;
331afc409ecSmillert int waiter;
33272574193Smillert size_t nread;
333a8f5ee9fSmillert char mailto[MAX_UNAME], buf[BUFSIZ];
3342b139a93Smillert int fd, always_mail;
3352b139a93Smillert int output_pipe[2];
3362b139a93Smillert char *nargv[2], *nenvp[1];
3372b139a93Smillert
3382b139a93Smillert /* Open the file and unlink it so we don't try running it again. */
339b7041c07Sderaadt if ((fd = openat(dfd, atfile, O_RDONLY|O_NONBLOCK|O_NOFOLLOW)) == -1) {
340a38a3395Smillert syslog(LOG_ERR, "(CRON) CAN'T OPEN (%s)", atfile);
3412b139a93Smillert return;
3422b139a93Smillert }
343f64f78fdSmillert unlinkat(dfd, atfile, 0);
3440aec96c6Smillert
3452b139a93Smillert /* Fork so other pending jobs don't have to wait for us to finish. */
3462b139a93Smillert switch (fork()) {
3472b139a93Smillert case 0:
3482b139a93Smillert /* child */
3492b139a93Smillert break;
3502b139a93Smillert case -1:
3512b139a93Smillert /* error */
352a38a3395Smillert syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)");
3532b139a93Smillert /* FALLTHROUGH */
3542b139a93Smillert default:
3552b139a93Smillert /* parent */
3562b139a93Smillert close(fd);
3572b139a93Smillert return;
3582b139a93Smillert }
3592b139a93Smillert
3600a66a1feSjca /* Close fds opened by the parent. */
3610a66a1feSjca close(cronSock);
3620a66a1feSjca close(dfd);
3630a66a1feSjca
3642b139a93Smillert /*
3652b139a93Smillert * We don't want the main cron daemon to wait for our children--
3662b139a93Smillert * we will do it ourselves via waitpid().
3672b139a93Smillert */
3682b139a93Smillert (void) signal(SIGCHLD, SIG_DFL);
3692b139a93Smillert
3702b139a93Smillert /*
3712b139a93Smillert * Verify the user still exists and their account has not expired.
3722b139a93Smillert */
3732b139a93Smillert pw = getpwuid(job->uid);
3742b139a93Smillert if (pw == NULL) {
375a38a3395Smillert syslog(LOG_WARNING, "(CRON) ORPHANED JOB (%s)", atfile);
37610ee6820Smillert _exit(EXIT_FAILURE);
3772b139a93Smillert }
3782b139a93Smillert if (pw->pw_expire && time(NULL) >= pw->pw_expire) {
379a38a3395Smillert syslog(LOG_NOTICE, "(%s) ACCOUNT EXPIRED, JOB ABORTED (%s)",
380a38a3395Smillert pw->pw_name, atfile);
38110ee6820Smillert _exit(EXIT_FAILURE);
3822b139a93Smillert }
3832b139a93Smillert
3842b139a93Smillert /* Sanity checks */
385df69c215Sderaadt if (fstat(fd, &sb) == -1) {
386a38a3395Smillert syslog(LOG_ERR, "(%s) FSTAT FAILED (%s)", pw->pw_name, atfile);
38710ee6820Smillert _exit(EXIT_FAILURE);
3882b139a93Smillert }
389a642adccSmillert if (!S_ISREG(sb.st_mode)) {
390a38a3395Smillert syslog(LOG_WARNING, "(%s) NOT REGULAR (%s)", pw->pw_name,
391a38a3395Smillert atfile);
39210ee6820Smillert _exit(EXIT_FAILURE);
3932b139a93Smillert }
394a642adccSmillert if ((sb.st_mode & ALLPERMS) != (S_IRUSR | S_IWUSR | S_IXUSR)) {
395a38a3395Smillert syslog(LOG_WARNING, "(%s) BAD FILE MODE (%s)", pw->pw_name,
396a38a3395Smillert atfile);
39710ee6820Smillert _exit(EXIT_FAILURE);
3982b139a93Smillert }
399a642adccSmillert if (sb.st_uid != 0 && sb.st_uid != job->uid) {
400a38a3395Smillert syslog(LOG_WARNING, "(%s) WRONG FILE OWNER (%s)", pw->pw_name,
401a38a3395Smillert atfile);
40210ee6820Smillert _exit(EXIT_FAILURE);
4032b139a93Smillert }
404a9e807deSmillert if (sb.st_gid != cron_gid) {
405a9e807deSmillert syslog(LOG_WARNING, "(%s) WRONG FILE GROUP (%s)", pw->pw_name,
406a9e807deSmillert atfile);
407a9e807deSmillert _exit(EXIT_FAILURE);
408a9e807deSmillert }
409a642adccSmillert if (sb.st_nlink > 1) {
410a38a3395Smillert syslog(LOG_WARNING, "(%s) BAD LINK COUNT (%s)", pw->pw_name,
411a38a3395Smillert atfile);
41210ee6820Smillert _exit(EXIT_FAILURE);
4132b139a93Smillert }
4142b139a93Smillert if ((fp = fdopen(dup(fd), "r")) == NULL) {
415a38a3395Smillert syslog(LOG_ERR, "(CRON) DUP FAILED (%m)");
41610ee6820Smillert _exit(EXIT_FAILURE);
4172b139a93Smillert }
418a8f5ee9fSmillert if (parse_header(fp, &nuid, &ngid, mailto, &always_mail) == -1) {
419a8f5ee9fSmillert syslog(LOG_ERR, "(%s) BAD FILE FORMAT (%s)", pw->pw_name,
420a8f5ee9fSmillert atfile);
421a8f5ee9fSmillert _exit(EXIT_FAILURE);
422a8f5ee9fSmillert }
4232b139a93Smillert (void)fclose(fp);
4242b139a93Smillert if (!safe_p(pw->pw_name, mailto))
42510ee6820Smillert _exit(EXIT_FAILURE);
4262b139a93Smillert if ((uid_t)nuid != job->uid) {
427a38a3395Smillert syslog(LOG_WARNING, "(%s) UID MISMATCH (%s)", pw->pw_name,
428a38a3395Smillert atfile);
42910ee6820Smillert _exit(EXIT_FAILURE);
4302b139a93Smillert }
4312b139a93Smillert if ((gid_t)ngid != job->gid) {
432a38a3395Smillert syslog(LOG_WARNING, "(%s) GID MISMATCH (%s)", pw->pw_name,
433a38a3395Smillert atfile);
43410ee6820Smillert _exit(EXIT_FAILURE);
4352b139a93Smillert }
4362b139a93Smillert
4370aec96c6Smillert /* mark ourselves as different to PS command watchers */
4380aec96c6Smillert setproctitle("atrun %s", atfile);
4392b139a93Smillert
4403b9f8275Smillert if (pipe(output_pipe) != 0) { /* child's stdout/stderr */
4413b9f8275Smillert syslog(LOG_ERR, "(CRON) PIPE (%m)");
4423b9f8275Smillert _exit(EXIT_FAILURE);
4433b9f8275Smillert }
4442b139a93Smillert
4452b139a93Smillert /* Fork again, child will run the job, parent will catch output. */
4462b139a93Smillert switch ((pid = fork())) {
4472b139a93Smillert case -1:
448a38a3395Smillert syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)");
44910ee6820Smillert _exit(EXIT_FAILURE);
4502b139a93Smillert /*NOTREACHED*/
4512b139a93Smillert case 0:
4522b139a93Smillert /* Write log message now that we have our real pid. */
453a38a3395Smillert syslog(LOG_INFO, "(%s) ATJOB (%s)", pw->pw_name, atfile);
4542b139a93Smillert
4552b139a93Smillert /* Connect grandchild's stdin to the at job file. */
456df69c215Sderaadt if (lseek(fd, 0, SEEK_SET) == -1) {
457b8c5c73dSmillert syslog(LOG_ERR, "(CRON) LSEEK (%m)");
45810ee6820Smillert _exit(EXIT_FAILURE);
4592b139a93Smillert }
46010ee6820Smillert if (fd != STDIN_FILENO) {
46110ee6820Smillert dup2(fd, STDIN_FILENO);
4622b139a93Smillert close(fd);
4632b139a93Smillert }
4642b139a93Smillert
4652b139a93Smillert /* Connect stdout/stderr to the pipe from our parent. */
46610ee6820Smillert if (output_pipe[WRITE_PIPE] != STDOUT_FILENO) {
46710ee6820Smillert dup2(output_pipe[WRITE_PIPE], STDOUT_FILENO);
4682b139a93Smillert close(output_pipe[WRITE_PIPE]);
4692b139a93Smillert }
47010ee6820Smillert dup2(STDOUT_FILENO, STDERR_FILENO);
4712b139a93Smillert close(output_pipe[READ_PIPE]);
4722b139a93Smillert
4732b139a93Smillert (void) setsid();
4742b139a93Smillert
475b8c5c73dSmillert /*
476b8c5c73dSmillert * From this point on, anything written to stderr will be
477b8c5c73dSmillert * mailed to the user as output.
478b8c5c73dSmillert */
479b8c5c73dSmillert
480b8c5c73dSmillert /* Setup execution environment as per login.conf */
4812b139a93Smillert if ((lc = login_getclass(pw->pw_class)) == NULL) {
482b8c5c73dSmillert warnx("unable to get login class for %s",
483b8c5c73dSmillert pw->pw_name);
484b8c5c73dSmillert syslog(LOG_ERR, "(CRON) CAN'T GET LOGIN CLASS (%s)",
4852b139a93Smillert pw->pw_name);
48610ee6820Smillert _exit(EXIT_FAILURE);
4872b139a93Smillert
4882b139a93Smillert }
4892b139a93Smillert if (setusercontext(lc, pw, pw->pw_uid, LOGIN_SETALL)) {
490b8c5c73dSmillert warn("setusercontext failed for %s", pw->pw_name);
491b8c5c73dSmillert syslog(LOG_ERR, "(%s) SETUSERCONTEXT FAILED (%m)",
4922b139a93Smillert pw->pw_name);
49310ee6820Smillert _exit(EXIT_FAILURE);
4942b139a93Smillert }
495b8c5c73dSmillert
496b8c5c73dSmillert /* Run any approval scripts. */
4972b139a93Smillert as = auth_open();
4982b139a93Smillert if (as == NULL || auth_setpwd(as, pw) != 0) {
499b8c5c73dSmillert warn("auth_setpwd");
500b8c5c73dSmillert syslog(LOG_ERR, "(%s) AUTH_SETPWD FAILED (%m)",
501b8c5c73dSmillert pw->pw_name);
50210ee6820Smillert _exit(EXIT_FAILURE);
5032b139a93Smillert }
5042b139a93Smillert if (auth_approval(as, lc, pw->pw_name, "cron") <= 0) {
505b8c5c73dSmillert warnx("approval failed for %s", pw->pw_name);
506b8c5c73dSmillert syslog(LOG_ERR, "(%s) APPROVAL FAILED (cron)",
5072b139a93Smillert pw->pw_name);
50810ee6820Smillert _exit(EXIT_FAILURE);
5092b139a93Smillert }
5102b139a93Smillert auth_close(as);
51163705530Smillert login_close(lc);
5122b139a93Smillert
5132b139a93Smillert /* If this is a low priority job, nice ourself. */
514b8c5c73dSmillert if (job->queue > 'b') {
515b8c5c73dSmillert if (setpriority(PRIO_PROCESS, 0, job->queue - 'b') != 0)
516b8c5c73dSmillert syslog(LOG_ERR, "(%s) CAN'T NICE (%m)",
517b8c5c73dSmillert pw->pw_name);
518b8c5c73dSmillert }
5192b139a93Smillert
5208b18fb30Smillert (void) signal(SIGPIPE, SIG_DFL);
5218b18fb30Smillert
5222b139a93Smillert /*
5232b139a93Smillert * Exec /bin/sh with stdin connected to the at job file
5242b139a93Smillert * and stdout/stderr hooked up to our parent.
5252b139a93Smillert * The at file will set the environment up for us.
5262b139a93Smillert */
5272b139a93Smillert nargv[0] = "sh";
5282b139a93Smillert nargv[1] = NULL;
5292b139a93Smillert nenvp[0] = NULL;
5302b139a93Smillert if (execve(_PATH_BSHELL, nargv, nenvp) != 0) {
531b8c5c73dSmillert warn("unable to execute %s", _PATH_BSHELL);
532b8c5c73dSmillert syslog(LOG_ERR, "(%s) CAN'T EXEC (%s: %m)", pw->pw_name,
533b8c5c73dSmillert _PATH_BSHELL);
53410ee6820Smillert _exit(EXIT_FAILURE);
5352b139a93Smillert }
5362b139a93Smillert break;
5372b139a93Smillert default:
5382b139a93Smillert /* parent */
5392b139a93Smillert break;
5402b139a93Smillert }
5412b139a93Smillert
5422b139a93Smillert /* Close the atfile's fd and the end of the pipe we don't use. */
5432b139a93Smillert close(fd);
5442b139a93Smillert close(output_pipe[WRITE_PIPE]);
5452b139a93Smillert
5462b139a93Smillert /* Read piped output (if any) from the at job. */
54772574193Smillert if ((fp = fdopen(output_pipe[READ_PIPE], "r")) == NULL) {
548b8c5c73dSmillert syslog(LOG_ERR, "(%s) FDOPEN (%m)", pw->pw_name);
54910ee6820Smillert (void) _exit(EXIT_FAILURE);
55072574193Smillert }
55172574193Smillert nread = fread(buf, 1, sizeof(buf), fp);
55272574193Smillert if (nread != 0 || always_mail) {
5532b139a93Smillert FILE *mail;
55494b4a649Stedu pid_t mailpid;
55572574193Smillert size_t bytes = 0;
5562b139a93Smillert int status = 0;
5572b139a93Smillert char mailcmd[MAX_COMMAND];
55875e8a723Smillert char hostname[HOST_NAME_MAX + 1];
5592b139a93Smillert
5602b139a93Smillert if (gethostname(hostname, sizeof(hostname)) != 0)
561f56315c7Smillert strlcpy(hostname, "unknown", sizeof(hostname));
5622b139a93Smillert if (snprintf(mailcmd, sizeof mailcmd, MAILFMT,
5632b139a93Smillert MAILARG) >= sizeof mailcmd) {
564b8c5c73dSmillert syslog(LOG_ERR, "(%s) ERROR (mailcmd too long)",
565b8c5c73dSmillert pw->pw_name);
56610ee6820Smillert (void) _exit(EXIT_FAILURE);
5672b139a93Smillert }
56894b4a649Stedu if (!(mail = cron_popen(mailcmd, "w", pw, &mailpid))) {
569b8c5c73dSmillert syslog(LOG_ERR, "(%s) POPEN (%s)", pw->pw_name, mailcmd);
57010ee6820Smillert (void) _exit(EXIT_FAILURE);
5712b139a93Smillert }
5722b139a93Smillert fprintf(mail, "From: %s (Atrun Service)\n", pw->pw_name);
5732b139a93Smillert fprintf(mail, "To: %s\n", mailto);
5742b139a93Smillert fprintf(mail, "Subject: Output from \"at\" job\n");
57513c10301Smillert fprintf(mail, "Auto-Submitted: auto-generated\n");
57688959323Smillert fprintf(mail, "\nYour \"at\" job on %s\n\"%s/%s\"\n",
57788959323Smillert hostname, _PATH_AT_SPOOL, atfile);
5782b139a93Smillert fprintf(mail, "\nproduced the following output:\n\n");
5792b139a93Smillert
5802b139a93Smillert /* Pipe the job's output to sendmail. */
58172574193Smillert do {
5822b139a93Smillert bytes += nread;
5832b139a93Smillert fwrite(buf, nread, 1, mail);
58472574193Smillert } while ((nread = fread(buf, 1, sizeof(buf), fp)) != 0);
5852b139a93Smillert
5862b139a93Smillert /*
5872b139a93Smillert * If the mailer exits with non-zero exit status, log
5882b139a93Smillert * this fact so the problem can (hopefully) be debugged.
5892b139a93Smillert */
59094b4a649Stedu if ((status = cron_pclose(mail, mailpid)) != 0) {
591a38a3395Smillert syslog(LOG_NOTICE, "(%s) MAIL (mailed %zu byte%s of "
592a38a3395Smillert "output but got status 0x%04x)", pw->pw_name,
593a38a3395Smillert bytes, (bytes == 1) ? "" : "s", status);
5942b139a93Smillert }
5952b139a93Smillert }
5962b139a93Smillert
5972b139a93Smillert fclose(fp); /* also closes output_pipe[READ_PIPE] */
5982b139a93Smillert
5992b139a93Smillert /* Wait for grandchild to die. */
6002b139a93Smillert for (;;) {
6012b139a93Smillert if (waitpid(pid, &waiter, 0) == -1) {
6022b139a93Smillert if (errno == EINTR)
6032b139a93Smillert continue;
6042b139a93Smillert break;
6052b139a93Smillert } else {
606585d9be6Stedu /*
6072b139a93Smillert if (WIFSIGNALED(waiter) && WCOREDUMP(waiter))
6082b139a93Smillert Debug(DPROC, (", dumped core"))
609585d9be6Stedu */
6102b139a93Smillert break;
6112b139a93Smillert }
6122b139a93Smillert }
61310ee6820Smillert _exit(EXIT_SUCCESS);
6142b139a93Smillert }
615