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