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