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