atrun.c revision 939
1/*
2 * atrun.c - run jobs queued by at; run with root privileges.
3 * Copyright (c) 1993 by Thomas Koenig
4 * All rights reserved.
5 *
6 * Redistribution and use in source and binary forms, with or without
7 * modification, are permitted provided that the following conditions
8 * are met:
9 * 1. Redistributions of source code must retain the above copyright
10 *    notice, this list of conditions and the following disclaimer.
11 * 2. The name of the author(s) may not be used to endorse or promote
12 *    products derived from this software without specific prior written
13 *    permission.
14 *
15 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
16 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
17 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
18 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
19 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
20 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
21 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
22 * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
24 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27/* System Headers */
28
29#include <sys/fcntl.h>
30#include <sys/types.h>
31#include <sys/stat.h>
32#include <sys/wait.h>
33#include <dirent.h>
34#include <errno.h>
35#include <pwd.h>
36#include <signal.h>
37#include <stddef.h>
38#include <stdio.h>
39#include <stdlib.h>
40#include <string.h>
41#include <time.h>
42#include <unistd.h>
43#include <syslog.h>
44
45#include <paths.h>
46
47/* Local headers */
48
49#define MAIN
50#include "privs.h"
51#include "pathnames.h"
52#include "atrun.h"
53
54/* File scope variables */
55
56static char *namep;
57static char rcsid[] = "$Id: atrun.c,v 1.1 1993/12/05 11:36:38 cgd Exp $";
58
59/* Local functions */
60static void
61perr(a)
62	const char *a;
63{
64	syslog(LOG_ERR, "%s: %m", a);
65	exit(EXIT_FAILURE);
66}
67
68static int
69write_string(fd, a)
70	int fd;
71	const char *a;
72{
73	return write(fd, a, strlen(a));
74}
75
76static void
77run_file(filename, uid, gid)
78	const char *filename;
79	uid_t uid;
80	gid_t gid;
81{
82	/*
83	 * Run a file by by spawning off a process which redirects I/O,
84	 * spawns a subshell, then waits for it to complete and spawns another
85	 * process to send mail to the user.
86	 */
87	pid_t pid;
88	int fd_out, fd_in;
89	int queue;
90	char mailbuf[9];
91	char *mailname = NULL;
92	FILE *stream;
93	int send_mail = 0;
94	struct stat buf;
95	off_t size;
96	struct passwd *pentry;
97	int fflags;
98
99	pid = fork();
100	if (pid == -1)
101		perr("Cannot fork");
102	else if (pid > 0)
103		return;
104
105	/*
106	 * Let's see who we mail to.  Hopefully, we can read it from the
107	 * command file; if not, send it to the owner, or, failing that, to
108	 * root.
109	 */
110
111	PRIV_START
112
113	    stream = fopen(filename, "r");
114
115	PRIV_END
116
117	if (stream == NULL)
118		perr("Cannot open input file");
119
120	if ((fd_in = dup(fileno(stream))) < 0)
121		perr("Error duplicating input file descriptor");
122
123	if ((fflags = fcntl(fd_in, F_GETFD)) < 0)
124		perr("Error in fcntl");
125
126	fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC);
127
128	if (fscanf(stream, "#! /bin/sh\n# mail %8s %d", mailbuf, &send_mail) == 2) {
129		mailname = mailbuf;
130	} else {
131		pentry = getpwuid(uid);
132		if (pentry == NULL)
133			mailname = "root";
134		else
135			mailname = pentry->pw_name;
136	}
137	fclose(stream);
138	if (chdir(_PATH_ATSPOOL) < 0)
139		perr("Cannot chdir to " _PATH_ATSPOOL);
140
141	/*
142	 * Create a file to hold the output of the job we are  about to
143	 * run. Write the mail header.
144	 */
145	if ((fd_out = open(filename,
146		    O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0)
147		perr("Cannot create output file");
148
149	write_string(fd_out, "Subject: Output from your job ");
150	write_string(fd_out, filename);
151	write_string(fd_out, "\n\n");
152	fstat(fd_out, &buf);
153	size = buf.st_size;
154
155	close(STDIN_FILENO);
156	close(STDOUT_FILENO);
157	close(STDERR_FILENO);
158
159	pid = fork();
160	if (pid < 0)
161		perr("Error in fork");
162	else if (pid == 0) {
163		char *nul = NULL;
164		char **nenvp = &nul;
165
166		/*
167		 * Set up things for the child; we want standard input from
168		 * the input file, and standard output and error sent to
169		 * our output file.
170		 */
171
172		if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0)
173			perr("Error in lseek");
174
175		if (dup(fd_in) != STDIN_FILENO)
176			perr("Error in I/O redirection");
177
178		if (dup(fd_out) != STDOUT_FILENO)
179			perr("Error in I/O redirection");
180
181		if (dup(fd_out) != STDERR_FILENO)
182			perr("Error in I/O redirection");
183
184		close(fd_in);
185		close(fd_out);
186		if (chdir(_PATH_ATJOBS) < 0)
187			perr("Cannot chdir to " _PATH_ATJOBS);
188
189		queue = *filename;
190
191		PRIV_START
192
193		    if (queue > 'b')
194			nice(queue - 'b');
195
196		if (setgid(gid) < 0)
197			perr("Cannot change group");
198
199		if (setuid(uid) < 0)
200			perr("Cannot set user id");
201
202		chdir("/");
203
204		if (execle("/bin/sh", "sh", (char *) NULL, nenvp) != 0)
205			perr("Exec failed");
206
207		PRIV_END
208	}
209	/* We're the parent.  Let's wait. */
210	close(fd_in);
211	close(fd_out);
212	waitpid(pid, (int *) NULL, 0);
213
214	stat(filename, &buf);
215	if ((buf.st_size != size) || send_mail) {
216		/* Fork off a child for sending mail */
217		pid = fork();
218		if (pid < 0)
219			perr("Fork failed");
220		else if (pid == 0) {
221			if (open(filename, O_RDONLY) != STDIN_FILENO)
222				perr("Cannot reopen output file");
223
224			execl(_PATH_SENDMAIL, _PATH_SENDMAIL, mailname,
225			    (char *) NULL);
226			perr("Exec failed");
227		}
228		waitpid(pid, (int *) NULL, 0);
229	}
230	unlink(filename);
231	exit(EXIT_SUCCESS);
232}
233
234/* Global functions */
235
236int
237main(argc, argv)
238	int argc;
239	char *argv[];
240{
241	/*
242	 * Browse through  _PATH_ATJOBS, checking all the jobfiles wether
243	 * they should be executed and or deleted. The queue is coded into
244	 * the first byte of the job filename, the date (in minutes since
245	 * Eon) as a hex number in the following eight bytes, followed by
246	 * a dot and a serial number.  A file which has not been executed
247	 * yet is denoted by its execute - bit set.  For those files which
248	 * are to be executed, run_file() is called, which forks off a
249	 * child which takes care of I/O redirection, forks off another
250	 * child for execution and yet another one, optionally, for sending
251	 * mail.  Files which already have run are removed during the
252	 * next invocation.
253	 */
254	DIR *spool;
255	struct dirent *dirent;
256	struct stat buf;
257	int older;
258	unsigned long ctm;
259	char queue;
260
261	/*
262	 * We don't need root privileges all the time; running under uid
263	 * and gid daemon is fine.
264	 */
265
266	RELINQUISH_PRIVS_ROOT(0) /* it's setuid root */
267	openlog("atrun", LOG_PID, LOG_CRON);
268
269	namep = argv[0];
270	if (chdir(_PATH_ATJOBS) != 0)
271		perr("Cannot change to " _PATH_ATJOBS);
272
273	/*
274	 * Main loop. Open spool directory for reading and look over all
275	 * the files in there. If the filename indicates that the job
276	 * should be run and the x bit is set, fork off a child which sets
277	 * its user and group id to that of the files and exec a /bin/sh
278	 * which executes the shell script. Unlink older files if they
279	 * should no longer be run.  For deletion, their r bit has to be
280	 * turned on.
281	 */
282	if ((spool = opendir(".")) == NULL)
283		perr("Cannot read " _PATH_ATJOBS);
284
285	while ((dirent = readdir(spool)) != NULL) {
286		double la;
287
288		if (stat(dirent->d_name, &buf) != 0)
289			perr("Cannot stat in " _PATH_ATJOBS);
290
291		/* We don't want directories */
292		if (!S_ISREG(buf.st_mode))
293			continue;
294
295		if (sscanf(dirent->d_name, "%c%8lx", &queue, &ctm) != 2)
296			continue;
297
298		if ((queue == 'b') && ((getloadavg(&la, 1) != 1) ||
299		    (la > ATRUN_MAXLOAD)))
300			continue;
301
302		older = (time_t) ctm *60 <= time(NULL);
303
304		/* The file is executable and old enough */
305		if (older && (S_IXUSR & buf.st_mode)) {
306			/*
307			 * Now we know we want to run the file, we can turn
308			 * off the execute bit
309			 */
310
311			PRIV_START
312
313			    if (chmod(dirent->d_name, S_IRUSR) != 0)
314				perr("Cannot change file permissions");
315
316			PRIV_END
317
318			run_file(dirent->d_name, buf.st_uid, buf.st_gid);
319		}
320		/* Delete older files */
321		if (older && !(S_IXUSR & buf.st_mode) &&
322		    (S_IRUSR & buf.st_mode))
323			unlink(dirent->d_name);
324	}
325	closelog();
326	exit(EXIT_SUCCESS);
327}
328