]> git.saurik.com Git - apple/system_cmds.git/blob - at.tproj/at.c
system_cmds-175.tar.gz
[apple/system_cmds.git] / at.tproj / at.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 * at.c : Put file into atrun queue
26 * Copyright (C) 1993 Thomas Koenig
27 *
28 * Atrun & Atq modifications
29 * Copyright (C) 1993 David Parsons
30 * All rights reserved.
31 *
32 * Redistribution and use in source and binary forms, with or without
33 * modification, are permitted provided that the following conditions
34 * are met:
35 * 1. Redistributions of source code must retain the above copyright
36 * notice, this list of conditions and the following disclaimer.
37 * 2. The name of the author(s) may not be used to endorse or promote
38 * products derived from this software without specific prior written
39 * permission.
40 *
41 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
42 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
43 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
44 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
45 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
46 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
47 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
48 * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
49 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
50 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
51 */
52
53 #define _USE_BSD 1
54
55 /* System Headers */
56 #include <sys/types.h>
57 #include <sys/stat.h>
58 #include <sys/wait.h>
59 #include <ctype.h>
60 #include <dirent.h>
61 #include <errno.h>
62 #include <fcntl.h>
63 #include <pwd.h>
64 #include <signal.h>
65 #include <stddef.h>
66 #include <stdio.h>
67 #include <stdlib.h>
68 #include <string.h>
69 #include <time.h>
70 #include <unistd.h>
71
72 /* Local headers */
73 #include "at.h"
74 #include "panic.h"
75 #include "parsetime.h"
76 #include "pathnames.h"
77 #define MAIN
78 #include "privs.h"
79 #include "perm.h"
80
81 /* Macros */
82 #define ALARMC 10 /* Number of seconds to wait for timeout */
83
84 #define SIZE 255
85 #define TIMESIZE 50
86
87 /* File scope variables */
88 static char rcsid[] = "$Id: at.c,v 1.1.1.2 2000/01/11 02:10:04 wsanchez Exp $";
89 char *no_export[] =
90 {
91 "TERM", "TERMCAP", "DISPLAY", "_"
92 };
93 static send_mail = 0;
94
95 /* External variables */
96 extern char **environ;
97 int fcreated;
98 char *namep;
99 char atfile[FILENAME_MAX];
100
101 char *atinput = (char *) 0; /* where to get input from */
102 char atqueue = 0; /* which queue to examine for jobs (atq) */
103 char atverify = 0; /* verify time instead of queuing job */
104
105 /* Function declarations */
106 static void sigc __P((int signo));
107 static void alarmc __P((int signo));
108 static char *cwdname __P((void));
109 static void writefile __P((time_t runtimer, char queue));
110 static void list_jobs __P((void));
111
112 /* Signal catching functions */
113
114 static void
115 sigc(signo)
116 int signo;
117 {
118 /* If the user presses ^C, remove the spool file and exit
119 */
120 if (fcreated) {
121 PRIV_START
122 unlink(atfile);
123 PRIV_END
124 }
125
126 exit(EXIT_FAILURE);
127 }
128
129 static void
130 alarmc(signo)
131 int signo;
132 {
133 /* Time out after some seconds
134 */
135 panic("File locking timed out");
136 }
137
138 /* Local functions */
139
140 static char *
141 cwdname()
142 {
143 /* Read in the current directory; the name will be overwritten on
144 * subsequent calls.
145 */
146 static char *ptr = NULL;
147 static size_t size = SIZE;
148
149 if (ptr == NULL)
150 ptr = (char *) malloc(size);
151
152 while (1) {
153 if (ptr == NULL)
154 panic("Out of memory");
155
156 if (getcwd(ptr, size - 1) != NULL)
157 return ptr;
158
159 if (errno != ERANGE)
160 perr("Cannot get directory");
161
162 free(ptr);
163 size += SIZE;
164 ptr = (char *) malloc(size);
165 }
166 }
167
168 static void
169 writefile(runtimer, queue)
170 time_t runtimer;
171 char queue;
172 {
173 /*
174 * This does most of the work if at or batch are invoked for
175 * writing a job.
176 */
177 int i;
178 char *ap, *ppos, *mailname;
179 struct passwd *pass_entry;
180 struct stat statbuf;
181 int fdes, lockdes, fd2;
182 FILE *fp, *fpin;
183 struct sigaction act;
184 char **atenv;
185 int ch;
186 mode_t cmask;
187 struct flock lock;
188
189 /*
190 * Install the signal handler for SIGINT; terminate after removing the
191 * spool file if necessary
192 */
193 act.sa_handler = sigc;
194 sigemptyset(&(act.sa_mask));
195 act.sa_flags = 0;
196
197 sigaction(SIGINT, &act, NULL);
198
199 strcpy(atfile, _PATH_ATJOBS);
200 ppos = atfile + strlen(_PATH_ATJOBS);
201
202 /*
203 * Loop over all possible file names for running something at this
204 * particular time, see if a file is there; the first empty slot at
205 * any particular time is used. Lock the file _PATH_LOCKFILE first
206 * to make sure we're alone when doing this.
207 */
208
209 PRIV_START
210
211 if ((lockdes = open(_PATH_LOCKFILE, O_WRONLY | O_CREAT, 0600)) < 0)
212 perr2("Cannot open lockfile ", _PATH_LOCKFILE);
213
214 lock.l_type = F_WRLCK;
215 lock.l_whence = SEEK_SET;
216 lock.l_start = 0;
217 lock.l_len = 0;
218
219 act.sa_handler = alarmc;
220 sigemptyset(&(act.sa_mask));
221 act.sa_flags = 0;
222
223 /*
224 * Set an alarm so a timeout occurs after ALARMC seconds, in case
225 * something is seriously broken.
226 */
227 sigaction(SIGALRM, &act, NULL);
228 alarm(ALARMC);
229 fcntl(lockdes, F_SETLKW, &lock);
230 alarm(0);
231
232 for (i = 0; i < AT_MAXJOBS; i++) {
233 sprintf(ppos, "%c%8lx.%3x", queue,
234 (unsigned long) (runtimer / 60), i);
235 for (ap = ppos; *ap != '\0'; ap++)
236 if (*ap == ' ')
237 *ap = '0';
238
239 if (stat(atfile, &statbuf) != 0) {
240 if (errno == ENOENT)
241 break;
242 else
243 perr2("Cannot access ", _PATH_ATJOBS);
244 }
245 } /* for */
246
247 if (i >= AT_MAXJOBS)
248 panic("Too many jobs already");
249
250 /*
251 * Create the file. The x bit is only going to be set after it has
252 * been completely written out, to make sure it is not executed in
253 * the meantime. To make sure they do not get deleted, turn off
254 * their r bit. Yes, this is a kluge.
255 */
256 cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR);
257 if ((fdes = creat(atfile, O_WRONLY)) == -1)
258 perr("Cannot create atjob file");
259
260 if ((fd2 = dup(fdes)) < 0)
261 perr("Error in dup() of job file");
262
263 if (fchown(fd2, real_uid, -1) != 0)
264 perr("Cannot give away file");
265
266 PRIV_END
267
268 /*
269 * We no longer need suid root; now we just need to be able to
270 * write to the directory, if necessary.
271 */
272
273 REDUCE_PRIV(0);
274
275 /*
276 * We've successfully created the file; let's set the flag so it
277 * gets removed in case of an interrupt or error.
278 */
279 fcreated = 1;
280
281 /* Now we can release the lock, so other people can access it */
282 lock.l_type = F_UNLCK;
283 lock.l_whence = SEEK_SET;
284 lock.l_start = 0;
285 lock.l_len = 0;
286 fcntl(lockdes, F_SETLKW, &lock);
287 close(lockdes);
288
289 if ((fp = fdopen(fdes, "w")) == NULL)
290 panic("Cannot reopen atjob file");
291
292 /*
293 * Get the userid to mail to, first by trying getlogin(), which
294 * reads /etc/utmp, then from LOGNAME, finally from getpwuid().
295 */
296 mailname = getlogin();
297 if (mailname == NULL)
298 mailname = getenv("LOGNAME");
299
300 if ((mailname == NULL) || (mailname[0] == '\0')
301 || (strlen(mailname) > 8)) {
302 pass_entry = getpwuid(getuid());
303 if (pass_entry != NULL)
304 mailname = pass_entry->pw_name;
305 }
306
307 if (atinput != (char *) NULL) {
308 fpin = freopen(atinput, "r", stdin);
309 if (fpin == NULL)
310 perr("Cannot open input file");
311 }
312 fprintf(fp, "#! /bin/sh\n# mail %8s %d\n", mailname, send_mail);
313
314 /* Write out the umask at the time of invocation */
315 fprintf(fp, "umask %lo\n", (unsigned long) cmask);
316
317 /*
318 * Write out the environment. Anything that may look like a special
319 * character to the shell is quoted, except for \n, which is done
320 * with a pair of "'s. Dont't export the no_export list (such as
321 * TERM or DISPLAY) because we don't want these.
322 */
323 for (atenv = environ; *atenv != NULL; atenv++) {
324 int export = 1;
325 char *eqp;
326
327 eqp = strchr(*atenv, '=');
328 if (ap == NULL)
329 eqp = *atenv;
330 else {
331 int i;
332
333 for (i = 0;i < sizeof(no_export) /
334 sizeof(no_export[0]); i++) {
335 export = export
336 && (strncmp(*atenv, no_export[i],
337 (size_t) (eqp - *atenv)) != 0);
338 }
339 eqp++;
340 }
341
342 if (export) {
343 fwrite(*atenv, sizeof(char), eqp - *atenv, fp);
344 for (ap = eqp; *ap != '\0'; ap++) {
345 if (*ap == '\n')
346 fprintf(fp, "\"\n\"");
347 else {
348 if (!isalnum(*ap))
349 fputc('\\', fp);
350
351 fputc(*ap, fp);
352 }
353 }
354 fputs("; export ", fp);
355 fwrite(*atenv, sizeof(char), eqp - *atenv - 1, fp);
356 fputc('\n', fp);
357
358 }
359 }
360 /*
361 * Cd to the directory at the time and write out all the commands
362 * the user supplies from stdin.
363 */
364 fprintf(fp, "cd %s\n", cwdname());
365
366 while ((ch = getchar()) != EOF)
367 fputc(ch, fp);
368
369 fprintf(fp, "\n");
370 if (ferror(fp))
371 panic("Output error");
372
373 if (ferror(stdin))
374 panic("Input error");
375
376 fclose(fp);
377
378 /*
379 * Set the x bit so that we're ready to start executing
380 */
381 if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
382 perr("Cannot give away file");
383
384 close(fd2);
385 fprintf(stderr, "Job %s will be executed using /bin/sh\n", ppos);
386 }
387
388 static void
389 list_jobs()
390 {
391 /*
392 * List all a user's jobs in the queue, by looping through
393 * _PATH_ATJOBS, or everybody's if we are root
394 */
395 struct passwd *pw;
396 DIR *spool;
397 struct dirent *dirent;
398 struct stat buf;
399 struct tm runtime;
400 unsigned long ctm;
401 char queue;
402 time_t runtimer;
403 char timestr[TIMESIZE];
404 int first = 1;
405
406 PRIV_START
407
408 if (chdir(_PATH_ATJOBS) != 0)
409 perr2("Cannot change to ", _PATH_ATJOBS);
410
411 if ((spool = opendir(".")) == NULL)
412 perr2("Cannot open ", _PATH_ATJOBS);
413
414 /* Loop over every file in the directory */
415 while ((dirent = readdir(spool)) != NULL) {
416 if (stat(dirent->d_name, &buf) != 0)
417 perr2("Cannot stat in ", _PATH_ATJOBS);
418
419 /*
420 * See it's a regular file and has its x bit turned on and
421 * is the user's
422 */
423 if (!S_ISREG(buf.st_mode)
424 || ((buf.st_uid != real_uid) && !(real_uid == 0))
425 || !(S_IXUSR & buf.st_mode || atverify))
426 continue;
427
428 if (sscanf(dirent->d_name, "%c%8lx", &queue, &ctm) != 2)
429 continue;
430
431 if (atqueue && (queue != atqueue))
432 continue;
433
434 runtimer = 60 * (time_t) ctm;
435 runtime = *localtime(&runtimer);
436 strftime(timestr, TIMESIZE, "%X %x", &runtime);
437 if (first) {
438 printf("Date\t\t\tOwner\tQueue\tJob#\n");
439 first = 0;
440 }
441 pw = getpwuid(buf.st_uid);
442
443 printf("%s\t%s\t%c%s\t%s\n",
444 timestr,
445 pw ? pw->pw_name : "???",
446 queue,
447 (S_IXUSR & buf.st_mode) ? "" : "(done)",
448 dirent->d_name);
449 }
450 PRIV_END
451 }
452
453 static void
454 delete_jobs(argc, argv)
455 int argc;
456 char **argv;
457 {
458 /* Delete every argument (job - ID) given */
459 int i;
460 struct stat buf;
461
462 PRIV_START
463
464 if (chdir(_PATH_ATJOBS) != 0)
465 perr2("Cannot change to ", _PATH_ATJOBS);
466
467 for (i = optind; i < argc; i++) {
468 if (stat(argv[i], &buf) != 0)
469 perr(argv[i]);
470 if ((buf.st_uid != real_uid) && !(real_uid == 0)) {
471 fprintf(stderr, "%s: Not owner\n", argv[i]);
472 exit(EXIT_FAILURE);
473 }
474 if (unlink(argv[i]) != 0)
475 perr(argv[i]);
476 }
477 PRIV_END
478 } /* delete_jobs */
479
480 /* Global functions */
481
482 int
483 main(argc, argv)
484 int argc;
485 char **argv;
486 {
487 int c;
488 char queue = 'a';
489 char *pgm;
490
491 enum {
492 ATQ, ATRM, AT, BATCH
493 }; /* what program we want to run */
494 int program = AT; /* our default program */
495 char *options = "q:f:mv"; /* default options for at */
496 time_t timer;
497
498 RELINQUISH_PRIVS
499
500 /* Eat any leading paths */
501 if ((pgm = strrchr(argv[0], '/')) == NULL)
502 pgm = argv[0];
503 else
504 pgm++;
505
506 namep = pgm;
507
508 /* find out what this program is supposed to do */
509 if (strcmp(pgm, "atq") == 0) {
510 program = ATQ;
511 options = "q:v";
512 } else if (strcmp(pgm, "atrm") == 0) {
513 program = ATRM;
514 options = "";
515 } else if (strcmp(pgm, "batch") == 0) {
516 program = BATCH;
517 options = "f:mv";
518 }
519
520 /* process whatever options we can process */
521 opterr = 1;
522 while ((c = getopt(argc, argv, options)) != EOF)
523 switch (c) {
524 case 'v': /* verify time settings */
525 atverify = 1;
526 break;
527
528 case 'm': /* send mail when job is complete */
529 send_mail = 1;
530 break;
531
532 case 'f':
533 atinput = optarg;
534 break;
535
536 case 'q': /* specify queue */
537 if (strlen(optarg) > 1)
538 usage();
539
540 atqueue = queue = *optarg;
541 if ((!islower(queue)) || (queue > 'l'))
542 usage();
543 break;
544
545 default:
546 usage();
547 break;
548 }
549 /* end of options eating */
550
551 /* select our program */
552 if (!check_permission())
553 {
554 fprintf(stderr, "You do not have permission to use %s.\n",namep);
555 exit(EXIT_FAILURE);
556 }
557 switch (program) {
558 case ATQ:
559
560 REDUCE_PRIV(0);
561
562 list_jobs();
563 break;
564
565 case ATRM:
566
567 REDUCE_PRIV(0);
568
569 delete_jobs(argc, argv);
570 break;
571
572 case AT:
573 timer = parsetime(argc, argv);
574 if (atverify) {
575 struct tm *tm = localtime(&timer);
576
577 fprintf(stderr, "%s\n", asctime(tm));
578 }
579 writefile(timer, queue);
580 break;
581
582 case BATCH:
583 writefile(time(NULL), 'b');
584 break;
585
586 default:
587 panic("Internal error");
588 break;
589 }
590 exit(EXIT_SUCCESS);
591 }