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