Commit | Line | Data |
---|---|---|
984263bc MD |
1 | /* |
2 | * at.c : Put file into atrun queue | |
3 | * Copyright (C) 1993, 1994 Thomas Koenig | |
4 | * | |
5 | * Atrun & Atq modifications | |
6 | * Copyright (C) 1993 David Parsons | |
7 | * | |
8 | * Redistribution and use in source and binary forms, with or without | |
9 | * modification, are permitted provided that the following conditions | |
10 | * are met: | |
11 | * 1. Redistributions of source code must retain the above copyright | |
12 | * notice, this list of conditions and the following disclaimer. | |
13 | * 2. The name of the author(s) may not be used to endorse or promote | |
14 | * products derived from this software without specific prior written | |
15 | * permission. | |
16 | * | |
17 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR | |
18 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
19 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
20 | * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, | |
21 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
22 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
23 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
24 | * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
1de703da | 27 | * |
b8f119b3 | 28 | * $FreeBSD: src/usr.bin/at/at.c,v 1.30 2007/09/21 01:55:11 kevlo Exp $ |
984263bc MD |
29 | */ |
30 | ||
984263bc MD |
31 | #define _USE_BSD 1 |
32 | ||
33 | /* System Headers */ | |
34 | ||
e3a4c51f | 35 | #include <sys/param.h> |
984263bc | 36 | #include <sys/stat.h> |
e3a4c51f | 37 | #include <sys/time.h> |
984263bc | 38 | #include <sys/wait.h> |
984263bc MD |
39 | #include <ctype.h> |
40 | #include <dirent.h> | |
41 | #include <err.h> | |
42 | #include <errno.h> | |
43 | #include <fcntl.h> | |
e3a4c51f PA |
44 | #include <langinfo.h> |
45 | #include <locale.h> | |
984263bc MD |
46 | #include <pwd.h> |
47 | #include <signal.h> | |
48 | #include <stddef.h> | |
49 | #include <stdio.h> | |
50 | #include <stdlib.h> | |
51 | #include <string.h> | |
52 | #include <time.h> | |
53 | #include <unistd.h> | |
984263bc MD |
54 | |
55 | /* Local headers */ | |
56 | ||
57 | #include "at.h" | |
58 | #include "panic.h" | |
59 | #include "parsetime.h" | |
60 | #include "perm.h" | |
61 | ||
62 | #define MAIN | |
63 | #include "privs.h" | |
64 | ||
65 | /* Macros */ | |
66 | ||
67 | #ifndef ATJOB_DIR | |
68 | #define ATJOB_DIR "/usr/spool/atjobs/" | |
69 | #endif | |
70 | ||
71 | #ifndef LFILE | |
72 | #define LFILE ATJOB_DIR ".lockfile" | |
73 | #endif | |
74 | ||
75 | #ifndef ATJOB_MX | |
76 | #define ATJOB_MX 255 | |
77 | #endif | |
78 | ||
79 | #define ALARMC 10 /* Number of seconds to wait for timeout */ | |
80 | ||
81 | #define SIZE 255 | |
82 | #define TIMESIZE 50 | |
83 | ||
84 | enum { ATQ, ATRM, AT, BATCH, CAT }; /* what program we want to run */ | |
85 | ||
86 | /* File scope variables */ | |
87 | ||
d50f9ae3 | 88 | static const char *no_export[] = |
984263bc MD |
89 | { |
90 | "TERM", "TERMCAP", "DISPLAY", "_" | |
d50f9ae3 | 91 | }; |
984263bc MD |
92 | static int send_mail = 0; |
93 | ||
94 | /* External variables */ | |
eed06283 JS |
95 | uid_t real_uid, effective_uid; |
96 | gid_t real_gid, effective_gid; | |
984263bc MD |
97 | |
98 | extern char **environ; | |
99 | int fcreated; | |
eed06283 | 100 | char atfile[sizeof(ATJOB_DIR) + 14] = ATJOB_DIR; |
984263bc | 101 | |
d50f9ae3 SW |
102 | static char *atinput = NULL; /* where to get input from */ |
103 | static char atqueue = 0; /* which queue to examine for jobs (atq) */ | |
984263bc | 104 | char atverify = 0; /* verify time instead of queuing job */ |
e3a4c51f | 105 | char *namep; |
984263bc MD |
106 | |
107 | /* Function declarations */ | |
108 | ||
109 | static void sigc(int signo); | |
110 | static void alarmc(int signo); | |
111 | static char *cwdname(void); | |
112 | static void writefile(time_t runtimer, char queue); | |
e3a4c51f PA |
113 | static void list_jobs(long *, int); |
114 | static long nextjob(void); | |
115 | static time_t ttime(const char *arg); | |
ae2e8c20 | 116 | static char * timer2str(time_t runtimer); |
e3a4c51f PA |
117 | static int in_job_list(long, long *, int); |
118 | static long *get_job_list(int, char *[], int *); | |
984263bc MD |
119 | |
120 | /* Signal catching functions */ | |
121 | ||
6b742d99 PA |
122 | static void |
123 | sigc(int signo __unused) | |
984263bc MD |
124 | { |
125 | /* If the user presses ^C, remove the spool file and exit | |
126 | */ | |
127 | if (fcreated) | |
128 | { | |
129 | PRIV_START | |
130 | unlink(atfile); | |
131 | PRIV_END | |
132 | } | |
133 | ||
e3a4c51f | 134 | _exit(EXIT_FAILURE); |
984263bc MD |
135 | } |
136 | ||
6b742d99 PA |
137 | static void |
138 | alarmc(int signo __unused) | |
984263bc | 139 | { |
e3a4c51f PA |
140 | char buf[1024]; |
141 | ||
142 | /* Time out after some seconds. */ | |
143 | strlcpy(buf, namep, sizeof(buf)); | |
144 | strlcat(buf, ": file locking timed out\n", sizeof(buf)); | |
145 | write(STDERR_FILENO, buf, strlen(buf)); | |
146 | sigc(0); | |
984263bc MD |
147 | } |
148 | ||
149 | /* Local functions */ | |
150 | ||
6b742d99 PA |
151 | static char * |
152 | cwdname(void) | |
984263bc MD |
153 | { |
154 | /* Read in the current directory; the name will be overwritten on | |
155 | * subsequent calls. | |
156 | */ | |
157 | static char *ptr = NULL; | |
158 | static size_t size = SIZE; | |
159 | ||
160 | if (ptr == NULL) | |
161 | if ((ptr = malloc(size)) == NULL) | |
162 | errx(EXIT_FAILURE, "virtual memory exhausted"); | |
163 | ||
164 | while (1) | |
165 | { | |
166 | if (ptr == NULL) | |
167 | panic("out of memory"); | |
168 | ||
169 | if (getcwd(ptr, size-1) != NULL) | |
170 | return ptr; | |
171 | ||
172 | if (errno != ERANGE) | |
173 | perr("cannot get directory"); | |
174 | ||
175 | free (ptr); | |
176 | size += SIZE; | |
177 | if ((ptr = malloc(size)) == NULL) | |
178 | errx(EXIT_FAILURE, "virtual memory exhausted"); | |
179 | } | |
180 | } | |
181 | ||
182 | static long | |
eed06283 | 183 | nextjob(void) |
984263bc MD |
184 | { |
185 | long jobno; | |
186 | FILE *fid; | |
187 | ||
b8f119b3 | 188 | if ((fid = fopen(ATJOB_DIR ".SEQ", "r+")) != NULL) { |
984263bc MD |
189 | if (fscanf(fid, "%5lx", &jobno) == 1) { |
190 | rewind(fid); | |
191 | jobno = (1+jobno) % 0xfffff; /* 2^20 jobs enough? */ | |
192 | fprintf(fid, "%05lx\n", jobno); | |
193 | } | |
194 | else | |
195 | jobno = EOF; | |
196 | fclose(fid); | |
197 | return jobno; | |
198 | } | |
b8f119b3 | 199 | else if ((fid = fopen(ATJOB_DIR ".SEQ", "w")) != NULL) { |
984263bc MD |
200 | fprintf(fid, "%05lx\n", jobno = 1); |
201 | fclose(fid); | |
202 | return 1; | |
203 | } | |
204 | return EOF; | |
205 | } | |
206 | ||
207 | static void | |
208 | writefile(time_t runtimer, char queue) | |
209 | { | |
210 | /* This does most of the work if at or batch are invoked for writing a job. | |
211 | */ | |
212 | long jobno; | |
213 | char *ap, *ppos, *mailname; | |
214 | struct passwd *pass_entry; | |
215 | struct stat statbuf; | |
216 | int fdes, lockdes, fd2; | |
217 | FILE *fp, *fpin; | |
218 | struct sigaction act; | |
219 | char **atenv; | |
220 | int ch; | |
221 | mode_t cmask; | |
222 | struct flock lock; | |
223 | ||
eed06283 | 224 | setlocale(LC_TIME, ""); |
984263bc MD |
225 | |
226 | /* Install the signal handler for SIGINT; terminate after removing the | |
227 | * spool file if necessary | |
228 | */ | |
229 | act.sa_handler = sigc; | |
230 | sigemptyset(&(act.sa_mask)); | |
231 | act.sa_flags = 0; | |
232 | ||
233 | sigaction(SIGINT, &act, NULL); | |
234 | ||
984263bc MD |
235 | /* Loop over all possible file names for running something at this |
236 | * particular time, see if a file is there; the first empty slot at any | |
237 | * particular time is used. Lock the file LFILE first to make sure | |
238 | * we're alone when doing this. | |
239 | */ | |
240 | ||
241 | PRIV_START | |
242 | ||
243 | if ((lockdes = open(LFILE, O_WRONLY | O_CREAT, S_IWUSR | S_IRUSR)) < 0) | |
244 | perr("cannot open lockfile " LFILE); | |
245 | ||
246 | lock.l_type = F_WRLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; | |
247 | lock.l_len = 0; | |
248 | ||
249 | act.sa_handler = alarmc; | |
250 | sigemptyset(&(act.sa_mask)); | |
251 | act.sa_flags = 0; | |
252 | ||
253 | /* Set an alarm so a timeout occurs after ALARMC seconds, in case | |
254 | * something is seriously broken. | |
255 | */ | |
256 | sigaction(SIGALRM, &act, NULL); | |
257 | alarm(ALARMC); | |
258 | fcntl(lockdes, F_SETLKW, &lock); | |
259 | alarm(0); | |
260 | ||
261 | if ((jobno = nextjob()) == EOF) | |
262 | perr("cannot generate job number"); | |
263 | ||
eed06283 JS |
264 | ppos = atfile + strlen(atfile); |
265 | snprintf(ppos, sizeof(atfile) - strlen(atfile), "%c%5lx%8lx", queue, | |
266 | jobno, (unsigned long) (runtimer/60)); | |
984263bc MD |
267 | |
268 | for(ap=ppos; *ap != '\0'; ap ++) | |
269 | if (*ap == ' ') | |
270 | *ap = '0'; | |
271 | ||
272 | if (stat(atfile, &statbuf) != 0) | |
273 | if (errno != ENOENT) | |
274 | perr("cannot access " ATJOB_DIR); | |
275 | ||
276 | /* Create the file. The x bit is only going to be set after it has | |
277 | * been completely written out, to make sure it is not executed in the | |
278 | * meantime. To make sure they do not get deleted, turn off their r | |
279 | * bit. Yes, this is a kluge. | |
280 | */ | |
281 | cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); | |
282 | if ((fdes = creat(atfile, O_WRONLY)) == -1) | |
283 | perr("cannot create atjob file"); | |
284 | ||
285 | if ((fd2 = dup(fdes)) <0) | |
286 | perr("error in dup() of job file"); | |
287 | ||
288 | if(fchown(fd2, real_uid, real_gid) != 0) | |
289 | perr("cannot give away file"); | |
290 | ||
291 | PRIV_END | |
292 | ||
293 | /* We no longer need suid root; now we just need to be able to write | |
294 | * to the directory, if necessary. | |
295 | */ | |
296 | ||
297 | REDUCE_PRIV(DAEMON_UID, DAEMON_GID) | |
298 | ||
299 | /* We've successfully created the file; let's set the flag so it | |
300 | * gets removed in case of an interrupt or error. | |
301 | */ | |
302 | fcreated = 1; | |
303 | ||
304 | /* Now we can release the lock, so other people can access it | |
305 | */ | |
306 | lock.l_type = F_UNLCK; lock.l_whence = SEEK_SET; lock.l_start = 0; | |
307 | lock.l_len = 0; | |
308 | fcntl(lockdes, F_SETLKW, &lock); | |
309 | close(lockdes); | |
310 | ||
311 | if((fp = fdopen(fdes, "w")) == NULL) | |
312 | panic("cannot reopen atjob file"); | |
313 | ||
e3a4c51f PA |
314 | /* Get the userid to mail to, first by trying getlogin(), |
315 | * then from LOGNAME, finally from getpwuid(). | |
984263bc MD |
316 | */ |
317 | mailname = getlogin(); | |
318 | if (mailname == NULL) | |
319 | mailname = getenv("LOGNAME"); | |
320 | ||
321 | if ((mailname == NULL) || (mailname[0] == '\0') | |
e3a4c51f | 322 | || (strlen(mailname) >= MAXLOGNAME) || (getpwnam(mailname)==NULL)) |
984263bc MD |
323 | { |
324 | pass_entry = getpwuid(real_uid); | |
325 | if (pass_entry != NULL) | |
326 | mailname = pass_entry->pw_name; | |
327 | } | |
328 | ||
2038fb68 | 329 | if (atinput != NULL) |
984263bc MD |
330 | { |
331 | fpin = freopen(atinput, "r", stdin); | |
332 | if (fpin == NULL) | |
333 | perr("cannot open input file"); | |
334 | } | |
335 | fprintf(fp, "#!/bin/sh\n# atrun uid=%ld gid=%ld\n# mail %*s %d\n", | |
e3a4c51f PA |
336 | (long) real_uid, (long) real_gid, MAXLOGNAME - 1, mailname, |
337 | send_mail); | |
984263bc MD |
338 | |
339 | /* Write out the umask at the time of invocation | |
340 | */ | |
341 | fprintf(fp, "umask %lo\n", (unsigned long) cmask); | |
342 | ||
343 | /* Write out the environment. Anything that may look like a | |
344 | * special character to the shell is quoted, except for \n, which is | |
345 | * done with a pair of "'s. Don't export the no_export list (such | |
346 | * as TERM or DISPLAY) because we don't want these. | |
347 | */ | |
348 | for (atenv= environ; *atenv != NULL; atenv++) | |
349 | { | |
350 | int export = 1; | |
351 | char *eqp; | |
352 | ||
353 | eqp = strchr(*atenv, '='); | |
354 | if (ap == NULL) | |
355 | eqp = *atenv; | |
356 | else | |
357 | { | |
e3a4c51f | 358 | size_t i; |
e62ef63c | 359 | for (i=0; i<NELEM(no_export); i++) |
984263bc MD |
360 | { |
361 | export = export | |
362 | && (strncmp(*atenv, no_export[i], | |
363 | (size_t) (eqp-*atenv)) != 0); | |
364 | } | |
365 | eqp++; | |
366 | } | |
367 | ||
368 | if (export) | |
369 | { | |
370 | fwrite(*atenv, sizeof(char), eqp-*atenv, fp); | |
371 | for(ap = eqp;*ap != '\0'; ap++) | |
372 | { | |
373 | if (*ap == '\n') | |
374 | fprintf(fp, "\"\n\""); | |
375 | else | |
376 | { | |
377 | if (!isalnum(*ap)) { | |
378 | switch (*ap) { | |
379 | case '%': case '/': case '{': case '[': | |
380 | case ']': case '=': case '}': case '@': | |
381 | case '+': case '#': case ',': case '.': | |
382 | case ':': case '-': case '_': | |
383 | break; | |
384 | default: | |
385 | fputc('\\', fp); | |
386 | break; | |
387 | } | |
388 | } | |
389 | fputc(*ap, fp); | |
390 | } | |
391 | } | |
392 | fputs("; export ", fp); | |
393 | fwrite(*atenv, sizeof(char), eqp-*atenv -1, fp); | |
394 | fputc('\n', fp); | |
395 | ||
396 | } | |
397 | } | |
398 | /* Cd to the directory at the time and write out all the | |
399 | * commands the user supplies from stdin. | |
400 | */ | |
401 | fprintf(fp, "cd "); | |
402 | for (ap = cwdname(); *ap != '\0'; ap++) | |
403 | { | |
404 | if (*ap == '\n') | |
405 | fprintf(fp, "\"\n\""); | |
406 | else | |
407 | { | |
408 | if (*ap != '/' && !isalnum(*ap)) | |
409 | fputc('\\', fp); | |
410 | ||
411 | fputc(*ap, fp); | |
412 | } | |
413 | } | |
414 | /* Test cd's exit status: die if the original directory has been | |
415 | * removed, become unreadable or whatever | |
416 | */ | |
417 | fprintf(fp, " || {\n\t echo 'Execution directory " | |
418 | "inaccessible' >&2\n\t exit 1\n}\n"); | |
419 | ||
420 | while((ch = getchar()) != EOF) | |
421 | fputc(ch, fp); | |
422 | ||
423 | fprintf(fp, "\n"); | |
424 | if (ferror(fp)) | |
425 | panic("output error"); | |
426 | ||
427 | if (ferror(stdin)) | |
428 | panic("input error"); | |
429 | ||
430 | fclose(fp); | |
431 | ||
432 | /* Set the x bit so that we're ready to start executing | |
433 | */ | |
434 | ||
435 | if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0) | |
436 | perr("cannot give away file"); | |
437 | ||
438 | close(fd2); | |
ae2e8c20 SK |
439 | |
440 | /* Print output. */ | |
441 | char *timestr = timer2str(runtimer); | |
442 | ||
984263bc | 443 | fprintf(stderr, "Job %ld will be executed using /bin/sh\n", jobno); |
ae2e8c20 SK |
444 | fprintf(stderr, "Job %ld at %s\n", jobno, timestr); |
445 | free(timestr); | |
984263bc MD |
446 | } |
447 | ||
e3a4c51f PA |
448 | static int |
449 | in_job_list(long job, long *joblist, int len) | |
450 | { | |
451 | int i; | |
452 | ||
453 | for (i = 0; i < len; i++) | |
454 | if (job == joblist[i]) | |
455 | return 1; | |
456 | ||
457 | return 0; | |
458 | } | |
459 | ||
984263bc | 460 | static void |
e3a4c51f | 461 | list_jobs(long *joblist, int len) |
984263bc MD |
462 | { |
463 | /* List all a user's jobs in the queue, by looping through ATJOB_DIR, | |
464 | * or everybody's if we are root | |
465 | */ | |
466 | struct passwd *pw; | |
467 | DIR *spool; | |
468 | struct dirent *dirent; | |
469 | struct stat buf; | |
984263bc MD |
470 | unsigned long ctm; |
471 | char queue; | |
472 | long jobno; | |
473 | time_t runtimer; | |
ae2e8c20 | 474 | char *timestr; |
984263bc MD |
475 | int first=1; |
476 | ||
eed06283 | 477 | setlocale(LC_TIME, ""); |
984263bc MD |
478 | |
479 | PRIV_START | |
480 | ||
481 | if (chdir(ATJOB_DIR) != 0) | |
482 | perr("cannot change to " ATJOB_DIR); | |
483 | ||
484 | if ((spool = opendir(".")) == NULL) | |
485 | perr("cannot open " ATJOB_DIR); | |
486 | ||
487 | /* Loop over every file in the directory | |
488 | */ | |
489 | while((dirent = readdir(spool)) != NULL) { | |
490 | if (stat(dirent->d_name, &buf) != 0) | |
491 | perr("cannot stat in " ATJOB_DIR); | |
492 | ||
493 | /* See it's a regular file and has its x bit turned on and | |
494 | * is the user's | |
495 | */ | |
496 | if (!S_ISREG(buf.st_mode) | |
497 | || ((buf.st_uid != real_uid) && ! (real_uid == 0)) | |
498 | || !(S_IXUSR & buf.st_mode || atverify)) | |
499 | continue; | |
500 | ||
501 | if(sscanf(dirent->d_name, "%c%5lx%8lx", &queue, &jobno, &ctm)!=3) | |
502 | continue; | |
503 | ||
e3a4c51f PA |
504 | /* If jobs are given, only list those jobs */ |
505 | if (joblist && !in_job_list(jobno, joblist, len)) | |
506 | continue; | |
507 | ||
984263bc MD |
508 | if (atqueue && (queue != atqueue)) |
509 | continue; | |
510 | ||
ae2e8c20 SK |
511 | /* Get datetime */ |
512 | runtimer = 60*(time_t) ctm; | |
513 | timestr = timer2str(runtimer); | |
514 | ||
515 | /* Look up owner's uid in password database file */ | |
984263bc MD |
516 | pw = getpwuid(buf.st_uid); |
517 | ||
ae2e8c20 SK |
518 | if (first) { |
519 | printf("Date\t\t\t\tOwner\t\tQueue\tJob#\n"); | |
520 | first=0; | |
521 | } | |
e3a4c51f | 522 | printf("%s\t%-16s%c%s\t%ld\n", |
984263bc MD |
523 | timestr, |
524 | pw ? pw->pw_name : "???", | |
525 | queue, | |
526 | (S_IXUSR & buf.st_mode) ? "":"(done)", | |
527 | jobno); | |
ae2e8c20 | 528 | free(timestr); |
984263bc | 529 | } |
d8335585 SW |
530 | closedir(spool); |
531 | ||
984263bc MD |
532 | PRIV_END |
533 | } | |
534 | ||
535 | static void | |
536 | process_jobs(int argc, char **argv, int what) | |
537 | { | |
538 | /* Delete every argument (job - ID) given | |
539 | */ | |
540 | int i; | |
541 | struct stat buf; | |
542 | DIR *spool; | |
543 | struct dirent *dirent; | |
544 | unsigned long ctm; | |
545 | char queue; | |
546 | long jobno; | |
547 | ||
548 | PRIV_START | |
549 | ||
550 | if (chdir(ATJOB_DIR) != 0) | |
551 | perr("cannot change to " ATJOB_DIR); | |
552 | ||
553 | if ((spool = opendir(".")) == NULL) | |
554 | perr("cannot open " ATJOB_DIR); | |
555 | ||
556 | PRIV_END | |
557 | ||
558 | /* Loop over every file in the directory | |
559 | */ | |
560 | while((dirent = readdir(spool)) != NULL) { | |
561 | ||
562 | PRIV_START | |
563 | if (stat(dirent->d_name, &buf) != 0) | |
564 | perr("cannot stat in " ATJOB_DIR); | |
565 | PRIV_END | |
566 | ||
567 | if(sscanf(dirent->d_name, "%c%5lx%8lx", &queue, &jobno, &ctm)!=3) | |
568 | continue; | |
569 | ||
570 | for (i=optind; i < argc; i++) { | |
571 | if (atoi(argv[i]) == jobno) { | |
572 | if ((buf.st_uid != real_uid) && !(real_uid == 0)) | |
573 | errx(EXIT_FAILURE, "%s: not owner", argv[i]); | |
574 | switch (what) { | |
575 | case ATRM: | |
576 | ||
577 | PRIV_START | |
578 | ||
579 | if (unlink(dirent->d_name) != 0) | |
580 | perr(dirent->d_name); | |
581 | ||
582 | PRIV_END | |
583 | ||
584 | break; | |
585 | ||
586 | case CAT: | |
587 | { | |
588 | FILE *fp; | |
589 | int ch; | |
590 | ||
591 | PRIV_START | |
592 | ||
593 | fp = fopen(dirent->d_name,"r"); | |
594 | ||
595 | PRIV_END | |
596 | ||
597 | if (!fp) { | |
598 | perr("cannot open file"); | |
599 | } | |
600 | while((ch = getc(fp)) != EOF) { | |
601 | putchar(ch); | |
602 | } | |
d8335585 | 603 | fclose(fp); |
984263bc MD |
604 | } |
605 | break; | |
606 | ||
607 | default: | |
608 | errx(EXIT_FAILURE, "internal error, process_jobs = %d", | |
609 | what); | |
610 | } | |
611 | } | |
612 | } | |
613 | } | |
d8335585 | 614 | closedir(spool); |
984263bc MD |
615 | } /* delete_jobs */ |
616 | ||
e3a4c51f PA |
617 | #define ATOI2(ar) ((ar)[0] - '0') * 10 + ((ar)[1] - '0'); (ar) += 2; |
618 | ||
619 | static time_t | |
620 | ttime(const char *arg) | |
621 | { | |
622 | /* | |
623 | * This is pretty much a copy of stime_arg1() from touch.c. I changed | |
624 | * the return value and the argument list because it's more convenient | |
625 | * (IMO) to do everything in one place. - Joe Halpin | |
626 | */ | |
627 | struct timeval tv[2]; | |
628 | time_t now; | |
629 | struct tm *t; | |
630 | int yearset; | |
631 | char *p; | |
632 | ||
633 | if (gettimeofday(&tv[0], NULL)) | |
634 | panic("Cannot get current time"); | |
635 | ||
636 | /* Start with the current time. */ | |
637 | now = tv[0].tv_sec; | |
638 | if ((t = localtime(&now)) == NULL) | |
639 | panic("localtime"); | |
640 | /* [[CC]YY]MMDDhhmm[.SS] */ | |
641 | if ((p = strchr(arg, '.')) == NULL) | |
642 | t->tm_sec = 0; /* Seconds defaults to 0. */ | |
643 | else { | |
644 | if (strlen(p + 1) != 2) | |
645 | goto terr; | |
646 | *p++ = '\0'; | |
647 | t->tm_sec = ATOI2(p); | |
648 | } | |
649 | ||
650 | yearset = 0; | |
651 | switch(strlen(arg)) { | |
652 | case 12: /* CCYYMMDDhhmm */ | |
653 | t->tm_year = ATOI2(arg); | |
654 | t->tm_year *= 100; | |
655 | yearset = 1; | |
656 | /* FALLTHROUGH */ | |
657 | case 10: /* YYMMDDhhmm */ | |
658 | if (yearset) { | |
659 | yearset = ATOI2(arg); | |
660 | t->tm_year += yearset; | |
661 | } else { | |
662 | yearset = ATOI2(arg); | |
663 | t->tm_year = yearset + 2000; | |
664 | } | |
665 | t->tm_year -= 1900; /* Convert to UNIX time. */ | |
666 | /* FALLTHROUGH */ | |
667 | case 8: /* MMDDhhmm */ | |
668 | t->tm_mon = ATOI2(arg); | |
669 | --t->tm_mon; /* Convert from 01-12 to 00-11 */ | |
670 | t->tm_mday = ATOI2(arg); | |
671 | t->tm_hour = ATOI2(arg); | |
672 | t->tm_min = ATOI2(arg); | |
673 | break; | |
674 | default: | |
675 | goto terr; | |
676 | } | |
677 | ||
678 | t->tm_isdst = -1; /* Figure out DST. */ | |
679 | tv[0].tv_sec = tv[1].tv_sec = mktime(t); | |
680 | if (tv[0].tv_sec != -1) | |
681 | return tv[0].tv_sec; | |
682 | else | |
683 | terr: | |
684 | panic( | |
685 | "out of range or illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); | |
686 | } | |
687 | ||
ae2e8c20 SK |
688 | /* The caller must free up the memory. */ |
689 | static char * | |
690 | timer2str(time_t runtimer) | |
691 | { | |
692 | struct tm runtime; | |
693 | char *timestr; | |
694 | size_t rv; | |
695 | ||
696 | timestr = malloc(TIMESIZE); | |
697 | if (timestr == NULL) | |
698 | panic("out of memory"); | |
699 | ||
700 | runtime = *localtime(&runtimer); | |
701 | rv = strftime(timestr, TIMESIZE, nl_langinfo(D_T_FMT), &runtime); | |
702 | if (rv == 0) | |
703 | panic("TIMESIZE too low"); | |
704 | ||
705 | return (timestr); | |
706 | } | |
707 | ||
e3a4c51f PA |
708 | static long * |
709 | get_job_list(int argc, char *argv[], int *joblen) | |
710 | { | |
711 | int i, len; | |
712 | long *joblist; | |
713 | char *ep; | |
714 | ||
715 | joblist = NULL; | |
716 | len = argc; | |
717 | if (len > 0) { | |
718 | if ((joblist = malloc(len * sizeof(*joblist))) == NULL) | |
719 | panic("out of memory"); | |
720 | ||
721 | for (i = 0; i < argc; i++) { | |
722 | errno = 0; | |
723 | if ((joblist[i] = strtol(argv[i], &ep, 10)) < 0 || | |
724 | ep == argv[i] || *ep != '\0' || errno) | |
725 | panic("invalid job number"); | |
726 | } | |
727 | } | |
728 | ||
729 | *joblen = len; | |
730 | return joblist; | |
731 | } | |
732 | ||
984263bc MD |
733 | int |
734 | main(int argc, char **argv) | |
735 | { | |
736 | int c; | |
737 | char queue = DEFAULT_AT_QUEUE; | |
738 | char queue_set = 0; | |
739 | char *pgm; | |
740 | ||
984263bc | 741 | int program = AT; /* our default program */ |
e3a4c51f | 742 | const char *options = "q:f:t:rmvldbc"; /* default options for at */ |
984263bc | 743 | time_t timer; |
e3a4c51f PA |
744 | long *joblist; |
745 | int joblen; | |
984263bc | 746 | |
e3a4c51f PA |
747 | joblist = NULL; |
748 | joblen = 0; | |
749 | timer = -1; | |
984263bc MD |
750 | RELINQUISH_PRIVS |
751 | ||
752 | /* Eat any leading paths | |
753 | */ | |
754 | if ((pgm = strrchr(argv[0], '/')) == NULL) | |
755 | pgm = argv[0]; | |
756 | else | |
757 | pgm++; | |
758 | ||
e3a4c51f PA |
759 | namep = pgm; |
760 | ||
984263bc MD |
761 | /* find out what this program is supposed to do |
762 | */ | |
763 | if (strcmp(pgm, "atq") == 0) { | |
764 | program = ATQ; | |
e3a4c51f | 765 | options = "q:v"; |
984263bc MD |
766 | } |
767 | else if (strcmp(pgm, "atrm") == 0) { | |
768 | program = ATRM; | |
e3a4c51f | 769 | options = ""; |
984263bc MD |
770 | } |
771 | else if (strcmp(pgm, "batch") == 0) { | |
772 | program = BATCH; | |
e3a4c51f | 773 | options = "f:q:mv"; |
984263bc MD |
774 | } |
775 | ||
776 | /* process whatever options we can process | |
777 | */ | |
778 | opterr=1; | |
779 | while ((c=getopt(argc, argv, options)) != -1) | |
780 | switch (c) { | |
781 | case 'v': /* verify time settings */ | |
782 | atverify = 1; | |
783 | break; | |
784 | ||
785 | case 'm': /* send mail when job is complete */ | |
786 | send_mail = 1; | |
787 | break; | |
788 | ||
789 | case 'f': | |
790 | atinput = optarg; | |
791 | break; | |
792 | ||
793 | case 'q': /* specify queue */ | |
794 | if (strlen(optarg) > 1) | |
795 | usage(); | |
796 | ||
797 | atqueue = queue = *optarg; | |
798 | if (!(islower(queue)||isupper(queue))) | |
799 | usage(); | |
800 | ||
801 | queue_set = 1; | |
802 | break; | |
803 | ||
804 | case 'd': | |
e3a4c51f | 805 | warnx("-d is deprecated; use -r instead"); |
d2d9ec23 | 806 | /* FALLTHROUGH */ |
e3a4c51f PA |
807 | |
808 | case 'r': | |
984263bc MD |
809 | if (program != AT) |
810 | usage(); | |
811 | ||
812 | program = ATRM; | |
e3a4c51f PA |
813 | options = ""; |
814 | break; | |
815 | ||
816 | case 't': | |
817 | if (program != AT) | |
818 | usage(); | |
819 | timer = ttime(optarg); | |
984263bc MD |
820 | break; |
821 | ||
822 | case 'l': | |
823 | if (program != AT) | |
824 | usage(); | |
825 | ||
826 | program = ATQ; | |
e3a4c51f | 827 | options = "q:"; |
984263bc MD |
828 | break; |
829 | ||
830 | case 'b': | |
831 | if (program != AT) | |
832 | usage(); | |
833 | ||
834 | program = BATCH; | |
e3a4c51f | 835 | options = "f:q:mv"; |
984263bc MD |
836 | break; |
837 | ||
838 | case 'c': | |
839 | program = CAT; | |
840 | options = ""; | |
841 | break; | |
842 | ||
843 | default: | |
844 | usage(); | |
845 | break; | |
846 | } | |
847 | /* end of options eating | |
848 | */ | |
849 | ||
984263bc MD |
850 | /* select our program |
851 | */ | |
852 | if(!check_permission()) | |
853 | errx(EXIT_FAILURE, "you do not have permission to use this program"); | |
854 | switch (program) { | |
855 | case ATQ: | |
856 | ||
857 | REDUCE_PRIV(DAEMON_UID, DAEMON_GID) | |
858 | ||
e3a4c51f PA |
859 | if (queue_set == 0) |
860 | joblist = get_job_list(argc - optind, argv + optind, &joblen); | |
861 | list_jobs(joblist, joblen); | |
984263bc MD |
862 | break; |
863 | ||
864 | case ATRM: | |
865 | ||
866 | REDUCE_PRIV(DAEMON_UID, DAEMON_GID) | |
867 | ||
868 | process_jobs(argc, argv, ATRM); | |
869 | break; | |
870 | ||
871 | case CAT: | |
872 | ||
873 | process_jobs(argc, argv, CAT); | |
874 | break; | |
875 | ||
876 | case AT: | |
e3a4c51f PA |
877 | /* |
878 | * If timer is > -1, then the user gave the time with -t. In that | |
879 | * case, it's already been set. If not, set it now. | |
880 | */ | |
881 | if (timer == -1) | |
882 | timer = parsetime(argc, argv); | |
883 | ||
984263bc MD |
884 | if (atverify) |
885 | { | |
886 | struct tm *tm = localtime(&timer); | |
887 | fprintf(stderr, "%s\n", asctime(tm)); | |
888 | } | |
889 | writefile(timer, queue); | |
890 | break; | |
891 | ||
892 | case BATCH: | |
893 | if (queue_set) | |
894 | queue = toupper(queue); | |
895 | else | |
896 | queue = DEFAULT_BATCH_QUEUE; | |
897 | ||
898 | if (argc > optind) | |
899 | timer = parsetime(argc, argv); | |
900 | else | |
901 | timer = time(NULL); | |
902 | ||
903 | if (atverify) | |
904 | { | |
905 | struct tm *tm = localtime(&timer); | |
906 | fprintf(stderr, "%s\n", asctime(tm)); | |
907 | } | |
908 | ||
909 | writefile(timer, queue); | |
910 | break; | |
911 | ||
912 | default: | |
913 | panic("internal error"); | |
914 | break; | |
915 | } | |
916 | exit(EXIT_SUCCESS); | |
917 | } |