1 |
|
|
/* $OpenBSD: at.c,v 1.81 2017/06/15 19:37:10 tb Exp $ */ |
2 |
|
|
|
3 |
|
|
/* |
4 |
|
|
* at.c : Put file into atrun queue |
5 |
|
|
* Copyright (C) 1993, 1994 Thomas Koenig |
6 |
|
|
* |
7 |
|
|
* Atrun & Atq modifications |
8 |
|
|
* Copyright (C) 1993 David Parsons |
9 |
|
|
* |
10 |
|
|
* Traditional BSD behavior and other significant modifications |
11 |
|
|
* Copyright (C) 2002-2003 Todd C. Miller |
12 |
|
|
* |
13 |
|
|
* Redistribution and use in source and binary forms, with or without |
14 |
|
|
* modification, are permitted provided that the following conditions |
15 |
|
|
* are met: |
16 |
|
|
* 1. Redistributions of source code must retain the above copyright |
17 |
|
|
* notice, this list of conditions and the following disclaimer. |
18 |
|
|
* 2. The name of the author(s) may not be used to endorse or promote |
19 |
|
|
* products derived from this software without specific prior written |
20 |
|
|
* permission. |
21 |
|
|
* |
22 |
|
|
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR |
23 |
|
|
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
24 |
|
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
25 |
|
|
* IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, |
26 |
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
27 |
|
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
28 |
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
29 |
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
30 |
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
31 |
|
|
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
32 |
|
|
*/ |
33 |
|
|
|
34 |
|
|
#include <sys/types.h> |
35 |
|
|
#include <sys/stat.h> |
36 |
|
|
|
37 |
|
|
#include <bitstring.h> /* for structs.h */ |
38 |
|
|
#include <ctype.h> |
39 |
|
|
#include <dirent.h> |
40 |
|
|
#include <err.h> |
41 |
|
|
#include <errno.h> |
42 |
|
|
#include <fcntl.h> |
43 |
|
|
#include <limits.h> |
44 |
|
|
#include <pwd.h> |
45 |
|
|
#include <signal.h> |
46 |
|
|
#include <stdarg.h> |
47 |
|
|
#include <stdio.h> |
48 |
|
|
#include <stdlib.h> |
49 |
|
|
#include <string.h> |
50 |
|
|
#include <syslog.h> |
51 |
|
|
#include <time.h> |
52 |
|
|
#include <unistd.h> |
53 |
|
|
|
54 |
|
|
#include "pathnames.h" |
55 |
|
|
#include "macros.h" |
56 |
|
|
#include "structs.h" |
57 |
|
|
#include "funcs.h" |
58 |
|
|
#include "globals.h" |
59 |
|
|
|
60 |
|
|
#include "at.h" |
61 |
|
|
|
62 |
|
|
#define ALARMC 10 /* Number of seconds to wait for timeout */ |
63 |
|
|
#define TIMESIZE 50 /* Size of buffer passed to strftime() */ |
64 |
|
|
|
65 |
|
|
/* Variables to remove from the job's environment. */ |
66 |
|
|
char *no_export[] = |
67 |
|
|
{ |
68 |
|
|
"TERM", "TERMCAP", "DISPLAY", "_", "SHELLOPTS", "BASH_VERSINFO", |
69 |
|
|
"EUID", "GROUPS", "PPID", "UID", "SSH_AUTH_SOCK", "SSH_AGENT_PID", |
70 |
|
|
}; |
71 |
|
|
|
72 |
|
|
static int program = AT; /* default program mode */ |
73 |
|
|
static char atfile[PATH_MAX]; /* path to the at spool file */ |
74 |
|
|
static char user_name[MAX_UNAME];/* invoking user name */ |
75 |
|
|
static int fcreated; /* whether or not we created the file yet */ |
76 |
|
|
static char atqueue = 0; /* which queue to examine for jobs (atq) */ |
77 |
|
|
static char vflag = 0; /* show completed but unremoved jobs (atq) */ |
78 |
|
|
static char force = 0; /* suppress errors (atrm) */ |
79 |
|
|
static char interactive = 0; /* interactive mode (atrm) */ |
80 |
|
|
static int send_mail = 0; /* whether we are sending mail */ |
81 |
|
|
static uid_t user_uid; /* user's real uid */ |
82 |
|
|
static gid_t user_gid; /* user's real gid */ |
83 |
|
|
static gid_t spool_gid; /* gid for writing to at spool */ |
84 |
|
|
|
85 |
|
|
static void sigc(int); |
86 |
|
|
static void writefile(const char *, time_t, char); |
87 |
|
|
static void list_jobs(int, char **, int, int); |
88 |
|
|
static time_t ttime(char *); |
89 |
|
|
static __dead void fatal(const char *, ...) |
90 |
|
|
__attribute__((__format__ (printf, 1, 2))); |
91 |
|
|
static __dead void fatalx(const char *, ...) |
92 |
|
|
__attribute__((__format__ (printf, 1, 2))); |
93 |
|
|
static __dead void usage(void); |
94 |
|
|
static int rmok(long long); |
95 |
|
|
time_t parsetime(int, char **); |
96 |
|
|
|
97 |
|
|
/* |
98 |
|
|
* Something fatal has happened, print error message and exit. |
99 |
|
|
*/ |
100 |
|
|
static __dead void |
101 |
|
|
fatal(const char *fmt, ...) |
102 |
|
|
{ |
103 |
|
|
va_list ap; |
104 |
|
|
|
105 |
|
|
va_start(ap, fmt); |
106 |
|
|
vwarn(fmt, ap); |
107 |
|
|
va_end(ap); |
108 |
|
|
|
109 |
|
|
if (fcreated) |
110 |
|
|
unlink(atfile); |
111 |
|
|
|
112 |
|
|
exit(EXIT_FAILURE); |
113 |
|
|
} |
114 |
|
|
|
115 |
|
|
/* |
116 |
|
|
* Something fatal has happened, print error message and exit. |
117 |
|
|
*/ |
118 |
|
|
static __dead void |
119 |
|
|
fatalx(const char *fmt, ...) |
120 |
|
|
{ |
121 |
|
|
va_list ap; |
122 |
|
|
|
123 |
|
|
va_start(ap, fmt); |
124 |
|
|
vwarnx(fmt, ap); |
125 |
|
|
va_end(ap); |
126 |
|
|
|
127 |
|
|
if (fcreated) |
128 |
|
|
unlink(atfile); |
129 |
|
|
|
130 |
|
|
exit(EXIT_FAILURE); |
131 |
|
|
} |
132 |
|
|
|
133 |
|
|
/* ARGSUSED */ |
134 |
|
|
static void |
135 |
|
|
sigc(int signo) |
136 |
|
|
{ |
137 |
|
|
/* If the user presses ^C, remove the spool file and exit. */ |
138 |
|
|
if (fcreated) |
139 |
|
|
(void)unlink(atfile); |
140 |
|
|
|
141 |
|
|
_exit(EXIT_FAILURE); |
142 |
|
|
} |
143 |
|
|
|
144 |
|
|
static int |
145 |
|
|
strtot(const char *nptr, char **endptr, time_t *tp) |
146 |
|
|
{ |
147 |
|
|
long long ll; |
148 |
|
|
|
149 |
|
|
errno = 0; |
150 |
|
|
ll = strtoll(nptr, endptr, 10); |
151 |
|
|
if (*endptr == nptr) |
152 |
|
|
return (-1); |
153 |
|
|
if (ll < 0 || (errno == ERANGE && ll == LLONG_MAX) || (time_t)ll != ll) |
154 |
|
|
return (-1); |
155 |
|
|
*tp = (time_t)ll; |
156 |
|
|
return (0); |
157 |
|
|
} |
158 |
|
|
|
159 |
|
|
static int |
160 |
|
|
newjob(time_t runtimer, int queue) |
161 |
|
|
{ |
162 |
|
|
int fd, i; |
163 |
|
|
|
164 |
|
|
/* |
165 |
|
|
* If we have a collision, try shifting the time by up to |
166 |
|
|
* two minutes. Perhaps it would be better to try different |
167 |
|
|
* queues instead... |
168 |
|
|
*/ |
169 |
|
|
for (i = 0; i < 120; i++) { |
170 |
|
|
snprintf(atfile, sizeof(atfile), "%s/%lld.%c", _PATH_AT_SPOOL, |
171 |
|
|
(long long)runtimer, queue); |
172 |
|
|
fd = open(atfile, O_WRONLY | O_CREAT | O_EXCL, S_IRUSR); |
173 |
|
|
if (fd >= 0) |
174 |
|
|
return (fd); |
175 |
|
|
runtimer++; |
176 |
|
|
} |
177 |
|
|
return (-1); |
178 |
|
|
} |
179 |
|
|
|
180 |
|
|
/* |
181 |
|
|
* This does most of the work if at or batch are invoked for |
182 |
|
|
* writing a job. |
183 |
|
|
*/ |
184 |
|
|
static void |
185 |
|
|
writefile(const char *cwd, time_t runtimer, char queue) |
186 |
|
|
{ |
187 |
|
|
const char *ap; |
188 |
|
|
char *mailname, *shell; |
189 |
|
|
char timestr[TIMESIZE]; |
190 |
|
|
struct passwd *pass_entry; |
191 |
|
|
struct tm runtime; |
192 |
|
|
int fd; |
193 |
|
|
FILE *fp; |
194 |
|
|
struct sigaction act; |
195 |
|
|
char **atenv; |
196 |
|
|
int ch; |
197 |
|
|
mode_t cmask; |
198 |
|
|
extern char **environ; |
199 |
|
|
|
200 |
|
|
/* |
201 |
|
|
* Install the signal handler for SIGINT; terminate after removing the |
202 |
|
|
* spool file if necessary |
203 |
|
|
*/ |
204 |
|
|
bzero(&act, sizeof act); |
205 |
|
|
act.sa_handler = sigc; |
206 |
|
|
sigemptyset(&act.sa_mask); |
207 |
|
|
act.sa_flags = 0; |
208 |
|
|
sigaction(SIGINT, &act, NULL); |
209 |
|
|
|
210 |
|
|
/* |
211 |
|
|
* Create the file. The x bit is only going to be set after it has |
212 |
|
|
* been completely written out, to make sure it is not executed in |
213 |
|
|
* the meantime. To make sure they do not get deleted, turn off |
214 |
|
|
* their r bit. Yes, this is a kluge. |
215 |
|
|
*/ |
216 |
|
|
cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR); |
217 |
|
|
if ((fd = newjob(runtimer, queue)) == -1) |
218 |
|
|
fatal("unable to create atjob file"); |
219 |
|
|
|
220 |
|
|
/* |
221 |
|
|
* We've successfully created the file; let's set the flag so it |
222 |
|
|
* gets removed in case of an interrupt or error. |
223 |
|
|
*/ |
224 |
|
|
fcreated = 1; |
225 |
|
|
|
226 |
|
|
if ((fp = fdopen(fd, "w")) == NULL) |
227 |
|
|
fatal("unable to reopen atjob file"); |
228 |
|
|
|
229 |
|
|
/* |
230 |
|
|
* Get the userid to mail to, first by trying getlogin(), which asks |
231 |
|
|
* the kernel, then from $LOGNAME or $USER, finally from getpwuid(). |
232 |
|
|
*/ |
233 |
|
|
mailname = getlogin(); |
234 |
|
|
if (mailname == NULL && (mailname = getenv("LOGNAME")) == NULL) |
235 |
|
|
mailname = getenv("USER"); |
236 |
|
|
|
237 |
|
|
if ((mailname == NULL) || (mailname[0] == '\0') || |
238 |
|
|
(strlen(mailname) > MAX_UNAME) || (getpwnam(mailname) == NULL)) { |
239 |
|
|
mailname = user_name; |
240 |
|
|
} |
241 |
|
|
|
242 |
|
|
/* |
243 |
|
|
* Get the shell to run the job under. First check $SHELL, falling |
244 |
|
|
* back to the user's shell in the password database or, failing |
245 |
|
|
* that, /bin/sh. |
246 |
|
|
*/ |
247 |
|
|
if ((shell = getenv("SHELL")) == NULL || *shell == '\0') { |
248 |
|
|
pass_entry = getpwuid(user_uid); |
249 |
|
|
if (pass_entry != NULL && *pass_entry->pw_shell != '\0') |
250 |
|
|
shell = pass_entry->pw_shell; |
251 |
|
|
else |
252 |
|
|
shell = _PATH_BSHELL; |
253 |
|
|
} |
254 |
|
|
|
255 |
|
|
(void)fprintf(fp, "#!/bin/sh\n# atrun uid=%lu gid=%lu\n# mail %*s %d\n", |
256 |
|
|
(unsigned long)user_uid, (unsigned long)spool_gid, |
257 |
|
|
MAX_UNAME, mailname, send_mail); |
258 |
|
|
|
259 |
|
|
/* Write out the umask at the time of invocation */ |
260 |
|
|
(void)fprintf(fp, "umask %o\n", cmask); |
261 |
|
|
|
262 |
|
|
/* |
263 |
|
|
* Write out the environment. Anything that may look like a special |
264 |
|
|
* character to the shell is quoted, except for \n, which is done |
265 |
|
|
* with a pair of "'s. Don't export the no_export list (such as |
266 |
|
|
* TERM or DISPLAY) because we don't want these. |
267 |
|
|
*/ |
268 |
|
|
for (atenv = environ; *atenv != NULL; atenv++) { |
269 |
|
|
int export = 1; |
270 |
|
|
char *eqp; |
271 |
|
|
|
272 |
|
|
eqp = strchr(*atenv, '='); |
273 |
|
|
if (eqp == NULL) |
274 |
|
|
eqp = *atenv; |
275 |
|
|
else { |
276 |
|
|
int i; |
277 |
|
|
|
278 |
|
|
for (i = 0;i < sizeof(no_export) / |
279 |
|
|
sizeof(no_export[0]); i++) { |
280 |
|
|
export = export |
281 |
|
|
&& (strncmp(*atenv, no_export[i], |
282 |
|
|
(size_t) (eqp - *atenv)) != 0); |
283 |
|
|
} |
284 |
|
|
eqp++; |
285 |
|
|
} |
286 |
|
|
|
287 |
|
|
if (export) { |
288 |
|
|
(void)fputs("export ", fp); |
289 |
|
|
(void)fwrite(*atenv, sizeof(char), eqp - *atenv, fp); |
290 |
|
|
for (ap = eqp; *ap != '\0'; ap++) { |
291 |
|
|
if (*ap == '\n') |
292 |
|
|
(void)fprintf(fp, "\"\n\""); |
293 |
|
|
else { |
294 |
|
|
if (!isalnum((unsigned char)*ap)) { |
295 |
|
|
switch (*ap) { |
296 |
|
|
case '%': case '/': case '{': |
297 |
|
|
case '[': case ']': case '=': |
298 |
|
|
case '}': case '@': case '+': |
299 |
|
|
case '#': case ',': case '.': |
300 |
|
|
case ':': case '-': case '_': |
301 |
|
|
break; |
302 |
|
|
default: |
303 |
|
|
(void)fputc('\\', fp); |
304 |
|
|
break; |
305 |
|
|
} |
306 |
|
|
} |
307 |
|
|
(void)fputc(*ap, fp); |
308 |
|
|
} |
309 |
|
|
} |
310 |
|
|
(void)fputc('\n', fp); |
311 |
|
|
} |
312 |
|
|
} |
313 |
|
|
/* |
314 |
|
|
* Cd to the directory at the time and write out all the |
315 |
|
|
* commands the user supplies from stdin. |
316 |
|
|
*/ |
317 |
|
|
(void)fputs("cd ", fp); |
318 |
|
|
for (ap = cwd; *ap != '\0'; ap++) { |
319 |
|
|
if (*ap == '\n') |
320 |
|
|
fprintf(fp, "\"\n\""); |
321 |
|
|
else { |
322 |
|
|
if (*ap != '/' && !isalnum((unsigned char)*ap)) |
323 |
|
|
(void)fputc('\\', fp); |
324 |
|
|
|
325 |
|
|
(void)fputc(*ap, fp); |
326 |
|
|
} |
327 |
|
|
} |
328 |
|
|
/* |
329 |
|
|
* Test cd's exit status: die if the original directory has been |
330 |
|
|
* removed, become unreadable or whatever. |
331 |
|
|
*/ |
332 |
|
|
(void)fprintf(fp, " || {\n\t echo 'Execution directory inaccessible'" |
333 |
|
|
" >&2\n\t exit 1\n}\n"); |
334 |
|
|
|
335 |
|
|
if ((ch = getchar()) == EOF) |
336 |
|
|
fatalx("unexpected EOF"); |
337 |
|
|
|
338 |
|
|
/* We want the job to run under the user's shell. */ |
339 |
|
|
fprintf(fp, "%s << '_END_OF_AT_JOB'\n", shell); |
340 |
|
|
|
341 |
|
|
do { |
342 |
|
|
(void)fputc(ch, fp); |
343 |
|
|
} while ((ch = getchar()) != EOF); |
344 |
|
|
|
345 |
|
|
(void)fprintf(fp, "\n_END_OF_AT_JOB\n"); |
346 |
|
|
(void)fflush(fp); |
347 |
|
|
if (ferror(fp)) |
348 |
|
|
fatalx("write error"); |
349 |
|
|
|
350 |
|
|
if (ferror(stdin)) |
351 |
|
|
fatalx("read error"); |
352 |
|
|
|
353 |
|
|
/* |
354 |
|
|
* Set the x bit so that we're ready to start executing |
355 |
|
|
*/ |
356 |
|
|
if (fchmod(fileno(fp), S_IRUSR | S_IWUSR | S_IXUSR) < 0) |
357 |
|
|
fatal("fchmod"); |
358 |
|
|
|
359 |
|
|
(void)fclose(fp); |
360 |
|
|
|
361 |
|
|
/* Poke cron so it knows to reload the at spool. */ |
362 |
|
|
poke_daemon(RELOAD_AT); |
363 |
|
|
|
364 |
|
|
runtime = *localtime(&runtimer); |
365 |
|
|
strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); |
366 |
|
|
(void)fprintf(stderr, "commands will be executed using %s\n", shell); |
367 |
|
|
(void)fprintf(stderr, "job %s at %s\n", &atfile[sizeof(_PATH_AT_SPOOL)], |
368 |
|
|
timestr); |
369 |
|
|
|
370 |
|
|
syslog(LOG_INFO, "(%s) CREATE (%s)", user_name, |
371 |
|
|
&atfile[sizeof(_PATH_AT_SPOOL)]); |
372 |
|
|
} |
373 |
|
|
|
374 |
|
|
/* Sort by creation time. */ |
375 |
|
|
static int |
376 |
|
|
byctime(const void *v1, const void *v2) |
377 |
|
|
{ |
378 |
|
|
const struct atjob *j1 = *(const struct atjob **)v1; |
379 |
|
|
const struct atjob *j2 = *(const struct atjob **)v2; |
380 |
|
|
|
381 |
|
|
return (j1->ctime - j2->ctime); |
382 |
|
|
} |
383 |
|
|
|
384 |
|
|
/* Sort by job number (and thus execution time). */ |
385 |
|
|
static int |
386 |
|
|
byjobno(const void *v1, const void *v2) |
387 |
|
|
{ |
388 |
|
|
const struct atjob *j1 = *(struct atjob **)v1; |
389 |
|
|
const struct atjob *j2 = *(struct atjob **)v2; |
390 |
|
|
|
391 |
|
|
if (j1->runtimer == j2->runtimer) |
392 |
|
|
return (j1->queue - j2->queue); |
393 |
|
|
return (j1->runtimer - j2->runtimer); |
394 |
|
|
} |
395 |
|
|
|
396 |
|
|
static void |
397 |
|
|
print_job(struct atjob *job, int n, int shortformat) |
398 |
|
|
{ |
399 |
|
|
struct passwd *pw; |
400 |
|
|
struct tm runtime; |
401 |
|
|
char timestr[TIMESIZE]; |
402 |
|
|
static char *ranks[] = { |
403 |
|
|
"th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" |
404 |
|
|
}; |
405 |
|
|
|
406 |
|
|
runtime = *localtime(&job->runtimer); |
407 |
|
|
if (shortformat) { |
408 |
|
|
strftime(timestr, TIMESIZE, "%a %b %e %T %Y", &runtime); |
409 |
|
|
(void)printf("%lld.%c\t%s\n", (long long)job->runtimer, |
410 |
|
|
job->queue, timestr); |
411 |
|
|
} else { |
412 |
|
|
pw = getpwuid(job->uid); |
413 |
|
|
/* Rank hack shamelessly stolen from lpq */ |
414 |
|
|
if (n / 10 == 1) |
415 |
|
|
printf("%3d%-5s", n,"th"); |
416 |
|
|
else |
417 |
|
|
printf("%3d%-5s", n, ranks[n % 10]); |
418 |
|
|
strftime(timestr, TIMESIZE, "%b %e, %Y %R", &runtime); |
419 |
|
|
(void)printf("%-21.18s%-11.8s%10lld.%c %c%s\n", |
420 |
|
|
timestr, pw ? pw->pw_name : "???", |
421 |
|
|
(long long)job->runtimer, job->queue, job->queue, |
422 |
|
|
(S_IXUSR & job->mode) ? "" : " (done)"); |
423 |
|
|
} |
424 |
|
|
} |
425 |
|
|
|
426 |
|
|
/* |
427 |
|
|
* List all of a user's jobs in the queue, by looping through |
428 |
|
|
* _PATH_AT_SPOOL, or all jobs if we are root. If argc is > 0, argv |
429 |
|
|
* contains the list of users whose jobs shall be displayed. By |
430 |
|
|
* default, the list is sorted by execution date and queue. If |
431 |
|
|
* csort is non-zero jobs will be sorted by creation/submission date. |
432 |
|
|
*/ |
433 |
|
|
static void |
434 |
|
|
list_jobs(int argc, char **argv, int count_only, int csort) |
435 |
|
|
{ |
436 |
|
|
struct passwd *pw; |
437 |
|
|
struct dirent *dirent; |
438 |
|
|
struct atjob **atjobs, **newatjobs, *job; |
439 |
|
|
struct stat stbuf; |
440 |
|
|
time_t runtimer; |
441 |
|
|
char **jobs; |
442 |
|
|
uid_t *uids; |
443 |
|
|
char queue, *ep; |
444 |
|
|
DIR *spool; |
445 |
|
|
int job_matches, jobs_len, uids_len; |
446 |
|
|
int dfd, i, shortformat; |
447 |
|
|
size_t numjobs, maxjobs; |
448 |
|
|
|
449 |
|
|
syslog(LOG_INFO, "(%s) LIST (%s)", user_name, |
450 |
|
|
user_uid ? user_name : "ALL"); |
451 |
|
|
|
452 |
|
|
/* Convert argv into a list of jobs and uids. */ |
453 |
|
|
jobs = NULL; |
454 |
|
|
uids = NULL; |
455 |
|
|
jobs_len = uids_len = 0; |
456 |
|
|
|
457 |
|
|
if (argc) { |
458 |
|
|
if ((jobs = reallocarray(NULL, argc, sizeof(char *))) == NULL || |
459 |
|
|
(uids = reallocarray(NULL, argc, sizeof(uid_t))) == NULL) |
460 |
|
|
fatal(NULL); |
461 |
|
|
|
462 |
|
|
for (i = 0; i < argc; i++) { |
463 |
|
|
if (strtot(argv[i], &ep, &runtimer) == 0 && |
464 |
|
|
*ep == '.' && isalpha((unsigned char)*(ep + 1)) && |
465 |
|
|
*(ep + 2) == '\0') |
466 |
|
|
jobs[jobs_len++] = argv[i]; |
467 |
|
|
else if ((pw = getpwnam(argv[i])) != NULL) { |
468 |
|
|
if (pw->pw_uid != user_uid && user_uid != 0) |
469 |
|
|
fatalx("only the superuser may " |
470 |
|
|
"display other users' jobs"); |
471 |
|
|
uids[uids_len++] = pw->pw_uid; |
472 |
|
|
} else |
473 |
|
|
fatalx("unknown user %s", argv[i]); |
474 |
|
|
} |
475 |
|
|
} |
476 |
|
|
|
477 |
|
|
shortformat = strcmp(__progname, "at") == 0; |
478 |
|
|
|
479 |
|
|
if ((dfd = open(_PATH_AT_SPOOL, O_RDONLY|O_DIRECTORY)) == -1 || |
480 |
|
|
(spool = fdopendir(dfd)) == NULL) |
481 |
|
|
fatal(_PATH_AT_SPOOL); |
482 |
|
|
|
483 |
|
|
if (fstat(dfd, &stbuf) != 0) |
484 |
|
|
fatal(_PATH_AT_SPOOL); |
485 |
|
|
|
486 |
|
|
/* |
487 |
|
|
* The directory's link count should give us a good idea |
488 |
|
|
* of how many files are in it. Fudge things a little just |
489 |
|
|
* in case someone adds a job or two. |
490 |
|
|
*/ |
491 |
|
|
numjobs = 0; |
492 |
|
|
maxjobs = stbuf.st_nlink + 4; |
493 |
|
|
atjobs = reallocarray(NULL, maxjobs, sizeof(struct atjob *)); |
494 |
|
|
if (atjobs == NULL) |
495 |
|
|
fatal(NULL); |
496 |
|
|
|
497 |
|
|
/* Loop over every file in the directory. */ |
498 |
|
|
while ((dirent = readdir(spool)) != NULL) { |
499 |
|
|
if (fstatat(dfd, dirent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) != 0) |
500 |
|
|
fatal("%s", dirent->d_name); |
501 |
|
|
|
502 |
|
|
/* |
503 |
|
|
* See it's a regular file and has its x bit turned on and |
504 |
|
|
* is the user's |
505 |
|
|
*/ |
506 |
|
|
if (!S_ISREG(stbuf.st_mode) |
507 |
|
|
|| ((stbuf.st_uid != user_uid) && !(user_uid == 0)) |
508 |
|
|
|| !(S_IXUSR & stbuf.st_mode || vflag)) |
509 |
|
|
continue; |
510 |
|
|
|
511 |
|
|
if (strtot(dirent->d_name, &ep, &runtimer) == -1) |
512 |
|
|
continue; |
513 |
|
|
if (*ep != '.' || !isalpha((unsigned char)*(ep + 1)) || |
514 |
|
|
*(ep + 2) != '\0') |
515 |
|
|
continue; |
516 |
|
|
queue = *(ep + 1); |
517 |
|
|
|
518 |
|
|
if (atqueue && (queue != atqueue)) |
519 |
|
|
continue; |
520 |
|
|
|
521 |
|
|
/* Check against specified jobs and/or user(s). */ |
522 |
|
|
job_matches = (argc == 0) ? 1 : 0; |
523 |
|
|
if (!job_matches) { |
524 |
|
|
for (i = 0; i < jobs_len; i++) { |
525 |
|
|
if (strcmp(dirent->d_name, jobs[i]) == 0) { |
526 |
|
|
job_matches = 1; |
527 |
|
|
break; |
528 |
|
|
} |
529 |
|
|
} |
530 |
|
|
} |
531 |
|
|
if (!job_matches) { |
532 |
|
|
for (i = 0; i < uids_len; i++) { |
533 |
|
|
if (uids[i] == stbuf.st_uid) { |
534 |
|
|
job_matches = 1; |
535 |
|
|
break; |
536 |
|
|
} |
537 |
|
|
} |
538 |
|
|
} |
539 |
|
|
if (!job_matches) |
540 |
|
|
continue; |
541 |
|
|
|
542 |
|
|
if (count_only) { |
543 |
|
|
numjobs++; |
544 |
|
|
continue; |
545 |
|
|
} |
546 |
|
|
|
547 |
|
|
job = malloc(sizeof(struct atjob)); |
548 |
|
|
if (job == NULL) |
549 |
|
|
fatal(NULL); |
550 |
|
|
job->runtimer = runtimer; |
551 |
|
|
job->ctime = stbuf.st_ctime; |
552 |
|
|
job->uid = stbuf.st_uid; |
553 |
|
|
job->mode = stbuf.st_mode; |
554 |
|
|
job->queue = queue; |
555 |
|
|
if (numjobs == maxjobs) { |
556 |
|
|
size_t newjobs = maxjobs * 2; |
557 |
|
|
newatjobs = recallocarray(atjobs, maxjobs, |
558 |
|
|
newjobs, sizeof(job)); |
559 |
|
|
if (newatjobs == NULL) |
560 |
|
|
fatal(NULL); |
561 |
|
|
atjobs = newatjobs; |
562 |
|
|
maxjobs = newjobs; |
563 |
|
|
} |
564 |
|
|
atjobs[numjobs++] = job; |
565 |
|
|
} |
566 |
|
|
free(uids); |
567 |
|
|
closedir(spool); |
568 |
|
|
|
569 |
|
|
if (count_only || numjobs == 0) { |
570 |
|
|
if (numjobs == 0 && !shortformat) |
571 |
|
|
warnx("no files in queue"); |
572 |
|
|
else if (count_only) |
573 |
|
|
printf("%zu\n", numjobs); |
574 |
|
|
free(atjobs); |
575 |
|
|
return; |
576 |
|
|
} |
577 |
|
|
|
578 |
|
|
/* Sort by job run time or by job creation time. */ |
579 |
|
|
qsort(atjobs, numjobs, sizeof(struct atjob *), |
580 |
|
|
csort ? byctime : byjobno); |
581 |
|
|
|
582 |
|
|
if (!shortformat) |
583 |
|
|
(void)puts(" Rank Execution Date Owner " |
584 |
|
|
"Job Queue"); |
585 |
|
|
|
586 |
|
|
for (i = 0; i < numjobs; i++) { |
587 |
|
|
print_job(atjobs[i], i + 1, shortformat); |
588 |
|
|
free(atjobs[i]); |
589 |
|
|
} |
590 |
|
|
free(atjobs); |
591 |
|
|
} |
592 |
|
|
|
593 |
|
|
static int |
594 |
|
|
rmok(long long job) |
595 |
|
|
{ |
596 |
|
|
int ch, junk; |
597 |
|
|
|
598 |
|
|
printf("%lld: remove it? ", job); |
599 |
|
|
ch = getchar(); |
600 |
|
|
while ((junk = getchar()) != EOF && junk != '\n') |
601 |
|
|
; |
602 |
|
|
return (ch == 'y' || ch == 'Y'); |
603 |
|
|
} |
604 |
|
|
|
605 |
|
|
/* |
606 |
|
|
* Loop through all jobs in _PATH_AT_SPOOL and display or delete ones |
607 |
|
|
* that match argv (may be job or username), or all if argc == 0. |
608 |
|
|
* Only the superuser may display/delete other people's jobs. |
609 |
|
|
*/ |
610 |
|
|
static int |
611 |
|
|
process_jobs(int argc, char **argv, int what) |
612 |
|
|
{ |
613 |
|
|
struct stat stbuf; |
614 |
|
|
struct dirent *dirent; |
615 |
|
|
struct passwd *pw; |
616 |
|
|
time_t runtimer; |
617 |
|
|
uid_t *uids; |
618 |
|
|
char **jobs, *ep; |
619 |
|
|
FILE *fp; |
620 |
|
|
DIR *spool; |
621 |
|
|
int job_matches, jobs_len, uids_len; |
622 |
|
|
int error, i, ch, changed, dfd; |
623 |
|
|
|
624 |
|
|
if ((dfd = open(_PATH_AT_SPOOL, O_RDONLY|O_DIRECTORY)) == -1 || |
625 |
|
|
(spool = fdopendir(dfd)) == NULL) |
626 |
|
|
fatal(_PATH_AT_SPOOL); |
627 |
|
|
|
628 |
|
|
/* Convert argv into a list of jobs and uids. */ |
629 |
|
|
jobs = NULL; |
630 |
|
|
uids = NULL; |
631 |
|
|
jobs_len = uids_len = 0; |
632 |
|
|
if (argc > 0) { |
633 |
|
|
if ((jobs = reallocarray(NULL, argc, sizeof(char *))) == NULL || |
634 |
|
|
(uids = reallocarray(NULL, argc, sizeof(uid_t))) == NULL) |
635 |
|
|
fatal(NULL); |
636 |
|
|
|
637 |
|
|
for (i = 0; i < argc; i++) { |
638 |
|
|
if (strtot(argv[i], &ep, &runtimer) == 0 && |
639 |
|
|
*ep == '.' && isalpha((unsigned char)*(ep + 1)) && |
640 |
|
|
*(ep + 2) == '\0') |
641 |
|
|
jobs[jobs_len++] = argv[i]; |
642 |
|
|
else if ((pw = getpwnam(argv[i])) != NULL) { |
643 |
|
|
if (user_uid != pw->pw_uid && user_uid != 0) { |
644 |
|
|
fatalx("only the superuser may %s " |
645 |
|
|
"other users' jobs", |
646 |
|
|
what == ATRM ? "remove" : "view"); |
647 |
|
|
} |
648 |
|
|
uids[uids_len++] = pw->pw_uid; |
649 |
|
|
} else |
650 |
|
|
fatalx("unknown user %s", argv[i]); |
651 |
|
|
} |
652 |
|
|
} |
653 |
|
|
|
654 |
|
|
/* Loop over every file in the directory */ |
655 |
|
|
changed = 0; |
656 |
|
|
while ((dirent = readdir(spool)) != NULL) { |
657 |
|
|
if (fstatat(dfd, dirent->d_name, &stbuf, AT_SYMLINK_NOFOLLOW) != 0) |
658 |
|
|
fatal("%s", dirent->d_name); |
659 |
|
|
|
660 |
|
|
if (stbuf.st_uid != user_uid && user_uid != 0) |
661 |
|
|
continue; |
662 |
|
|
|
663 |
|
|
if (strtot(dirent->d_name, &ep, &runtimer) == -1) |
664 |
|
|
continue; |
665 |
|
|
if (*ep != '.' || !isalpha((unsigned char)*(ep + 1)) || |
666 |
|
|
*(ep + 2) != '\0') |
667 |
|
|
continue; |
668 |
|
|
|
669 |
|
|
/* Check runtimer against argv; argc==0 means do all. */ |
670 |
|
|
job_matches = (argc == 0) ? 1 : 0; |
671 |
|
|
if (!job_matches) { |
672 |
|
|
for (i = 0; i < jobs_len; i++) { |
673 |
|
|
if (jobs[i] != NULL && |
674 |
|
|
strcmp(dirent->d_name, jobs[i]) == 0) { |
675 |
|
|
jobs[i] = NULL; |
676 |
|
|
job_matches = 1; |
677 |
|
|
break; |
678 |
|
|
} |
679 |
|
|
} |
680 |
|
|
} |
681 |
|
|
if (!job_matches) { |
682 |
|
|
for (i = 0; i < uids_len; i++) { |
683 |
|
|
if (uids[i] == stbuf.st_uid) { |
684 |
|
|
job_matches = 1; |
685 |
|
|
break; |
686 |
|
|
} |
687 |
|
|
} |
688 |
|
|
} |
689 |
|
|
|
690 |
|
|
if (job_matches) { |
691 |
|
|
switch (what) { |
692 |
|
|
case ATRM: |
693 |
|
|
if (!interactive || |
694 |
|
|
(interactive && rmok(runtimer))) { |
695 |
|
|
if (unlinkat(dfd, dirent->d_name, 0) == 0) { |
696 |
|
|
syslog(LOG_INFO, |
697 |
|
|
"(%s) DELETE (%s)", |
698 |
|
|
user_name, dirent->d_name); |
699 |
|
|
changed = 1; |
700 |
|
|
} else if (!force) |
701 |
|
|
fatal("%s", dirent->d_name); |
702 |
|
|
if (!force && !interactive) |
703 |
|
|
warnx("%s removed", |
704 |
|
|
dirent->d_name); |
705 |
|
|
} |
706 |
|
|
break; |
707 |
|
|
|
708 |
|
|
case CAT: |
709 |
|
|
i = openat(dfd, dirent->d_name, |
710 |
|
|
O_RDONLY|O_NOFOLLOW); |
711 |
|
|
if (i == -1 || (fp = fdopen(i, "r")) == NULL) |
712 |
|
|
fatal("%s", dirent->d_name); |
713 |
|
|
syslog(LOG_INFO, "(%s) CAT (%s)", |
714 |
|
|
user_name, dirent->d_name); |
715 |
|
|
|
716 |
|
|
while ((ch = getc(fp)) != EOF) |
717 |
|
|
putchar(ch); |
718 |
|
|
|
719 |
|
|
fclose(fp); |
720 |
|
|
break; |
721 |
|
|
|
722 |
|
|
default: |
723 |
|
|
fatalx("internal error"); |
724 |
|
|
break; |
725 |
|
|
} |
726 |
|
|
} |
727 |
|
|
} |
728 |
|
|
closedir(spool); |
729 |
|
|
|
730 |
|
|
for (error = 0, i = 0; i < jobs_len; i++) { |
731 |
|
|
if (jobs[i] != NULL) { |
732 |
|
|
if (!force) |
733 |
|
|
warnx("%s: no such job", jobs[i]); |
734 |
|
|
error++; |
735 |
|
|
} |
736 |
|
|
} |
737 |
|
|
free(jobs); |
738 |
|
|
free(uids); |
739 |
|
|
|
740 |
|
|
/* If we modied the spool, poke cron so it knows to reload. */ |
741 |
|
|
if (changed) |
742 |
|
|
poke_daemon(RELOAD_AT); |
743 |
|
|
|
744 |
|
|
return (error); |
745 |
|
|
} |
746 |
|
|
|
747 |
|
|
#define ATOI2(s) ((s) += 2, ((s)[-2] - '0') * 10 + ((s)[-1] - '0')) |
748 |
|
|
|
749 |
|
|
/* |
750 |
|
|
* Adapted from date(1) |
751 |
|
|
*/ |
752 |
|
|
static time_t |
753 |
|
|
ttime(char *arg) |
754 |
|
|
{ |
755 |
|
|
time_t now, then; |
756 |
|
|
struct tm *lt; |
757 |
|
|
int yearset; |
758 |
|
|
char *dot, *p; |
759 |
|
|
|
760 |
|
|
if (time(&now) == (time_t)-1 || (lt = localtime(&now)) == NULL) |
761 |
|
|
fatal("unable to get current time"); |
762 |
|
|
|
763 |
|
|
/* Valid date format is [[CC]YY]MMDDhhmm[.SS] */ |
764 |
|
|
for (p = arg, dot = NULL; *p != '\0'; p++) { |
765 |
|
|
if (*p == '.' && dot == NULL) |
766 |
|
|
dot = p; |
767 |
|
|
else if (!isdigit((unsigned char)*p)) |
768 |
|
|
goto terr; |
769 |
|
|
} |
770 |
|
|
if (dot == NULL) |
771 |
|
|
lt->tm_sec = 0; |
772 |
|
|
else { |
773 |
|
|
*dot++ = '\0'; |
774 |
|
|
if (strlen(dot) != 2) |
775 |
|
|
goto terr; |
776 |
|
|
lt->tm_sec = ATOI2(dot); |
777 |
|
|
if (lt->tm_sec > 61) /* could be leap second */ |
778 |
|
|
goto terr; |
779 |
|
|
} |
780 |
|
|
|
781 |
|
|
yearset = 0; |
782 |
|
|
switch(strlen(arg)) { |
783 |
|
|
case 12: /* CCYYMMDDhhmm */ |
784 |
|
|
lt->tm_year = ATOI2(arg) * 100; |
785 |
|
|
lt->tm_year -= 1900; /* Convert to Unix time */ |
786 |
|
|
yearset = 1; |
787 |
|
|
/* FALLTHROUGH */ |
788 |
|
|
case 10: /* YYMMDDhhmm */ |
789 |
|
|
if (yearset) { |
790 |
|
|
yearset = ATOI2(arg); |
791 |
|
|
lt->tm_year += yearset; |
792 |
|
|
} else { |
793 |
|
|
yearset = ATOI2(arg); |
794 |
|
|
/* POSIX logic: [00,68]=>20xx, [69,99]=>19xx */ |
795 |
|
|
lt->tm_year = yearset; |
796 |
|
|
if (yearset < 69) |
797 |
|
|
lt->tm_year += 100; |
798 |
|
|
} |
799 |
|
|
/* FALLTHROUGH */ |
800 |
|
|
case 8: /* MMDDhhmm */ |
801 |
|
|
lt->tm_mon = ATOI2(arg); |
802 |
|
|
if (lt->tm_mon > 12 || lt->tm_mon == 0) |
803 |
|
|
goto terr; |
804 |
|
|
--lt->tm_mon; /* Convert from 01-12 to 00-11 */ |
805 |
|
|
lt->tm_mday = ATOI2(arg); |
806 |
|
|
if (lt->tm_mday > 31 || lt->tm_mday == 0) |
807 |
|
|
goto terr; |
808 |
|
|
lt->tm_hour = ATOI2(arg); |
809 |
|
|
if (lt->tm_hour > 23) |
810 |
|
|
goto terr; |
811 |
|
|
lt->tm_min = ATOI2(arg); |
812 |
|
|
if (lt->tm_min > 59) |
813 |
|
|
goto terr; |
814 |
|
|
break; |
815 |
|
|
default: |
816 |
|
|
goto terr; |
817 |
|
|
} |
818 |
|
|
|
819 |
|
|
lt->tm_isdst = -1; /* mktime will deduce DST. */ |
820 |
|
|
then = mktime(lt); |
821 |
|
|
if (then == (time_t)-1) { |
822 |
|
|
terr: |
823 |
|
|
fatalx("illegal time specification: [[CC]YY]MMDDhhmm[.SS]"); |
824 |
|
|
} |
825 |
|
|
if (then < now) |
826 |
|
|
fatalx("cannot schedule jobs in the past"); |
827 |
|
|
return (then); |
828 |
|
|
} |
829 |
|
|
|
830 |
|
|
static __dead void |
831 |
|
|
usage(void) |
832 |
|
|
{ |
833 |
|
|
/* Print usage and exit. */ |
834 |
|
|
switch (program) { |
835 |
|
|
case AT: |
836 |
|
|
case CAT: |
837 |
|
|
(void)fprintf(stderr, |
838 |
|
|
"usage: at [-bm] [-f file] [-l [job ...]] [-q queue] " |
839 |
|
|
"-t time_arg | timespec\n" |
840 |
|
|
" at -c | -r job ...\n"); |
841 |
|
|
break; |
842 |
|
|
case ATQ: |
843 |
|
|
(void)fprintf(stderr, |
844 |
|
|
"usage: atq [-cnv] [-q queue] [name ...]\n"); |
845 |
|
|
break; |
846 |
|
|
case ATRM: |
847 |
|
|
(void)fprintf(stderr, |
848 |
|
|
"usage: atrm [-afi] [[job] [name] ...]\n"); |
849 |
|
|
break; |
850 |
|
|
case BATCH: |
851 |
|
|
(void)fprintf(stderr, |
852 |
|
|
"usage: batch [-m] [-f file] [-q queue] [timespec]\n"); |
853 |
|
|
break; |
854 |
|
|
} |
855 |
|
|
exit(EXIT_FAILURE); |
856 |
|
|
} |
857 |
|
|
|
858 |
|
|
int |
859 |
|
|
main(int argc, char **argv) |
860 |
|
|
{ |
861 |
|
|
time_t timer = -1; |
862 |
|
|
char *atinput = NULL; /* where to get input from */ |
863 |
|
|
char queue = DEFAULT_AT_QUEUE; |
864 |
|
|
char queue_set = 0; |
865 |
|
|
char *options = "q:f:t:bcdlmrv"; /* default options for at */ |
866 |
|
|
char cwd[PATH_MAX]; |
867 |
|
|
struct passwd *pw; |
868 |
|
|
int ch; |
869 |
|
|
int aflag = 0; |
870 |
|
|
int cflag = 0; |
871 |
|
|
int nflag = 0; |
872 |
|
|
|
873 |
|
|
if (pledge("stdio rpath wpath cpath fattr getpw unix id flock", NULL) == -1) |
874 |
|
|
fatal("pledge"); |
875 |
|
|
|
876 |
|
|
openlog(__progname, LOG_PID, LOG_CRON); |
877 |
|
|
|
878 |
|
|
if (argc < 1) |
879 |
|
|
usage(); |
880 |
|
|
|
881 |
|
|
user_uid = getuid(); |
882 |
|
|
user_gid = getgid(); |
883 |
|
|
spool_gid = getegid(); |
884 |
|
|
|
885 |
|
|
/* find out what this program is supposed to do */ |
886 |
|
|
if (strcmp(__progname, "atq") == 0) { |
887 |
|
|
program = ATQ; |
888 |
|
|
options = "cnvq:"; |
889 |
|
|
} else if (strcmp(__progname, "atrm") == 0) { |
890 |
|
|
program = ATRM; |
891 |
|
|
options = "afi"; |
892 |
|
|
} else if (strcmp(__progname, "batch") == 0) { |
893 |
|
|
program = BATCH; |
894 |
|
|
options = "f:q:mv"; |
895 |
|
|
} |
896 |
|
|
|
897 |
|
|
/* process whatever options we can process */ |
898 |
|
|
while ((ch = getopt(argc, argv, options)) != -1) { |
899 |
|
|
switch (ch) { |
900 |
|
|
case 'a': |
901 |
|
|
aflag = 1; |
902 |
|
|
break; |
903 |
|
|
|
904 |
|
|
case 'i': |
905 |
|
|
interactive = 1; |
906 |
|
|
force = 0; |
907 |
|
|
break; |
908 |
|
|
|
909 |
|
|
case 'v': /* show completed but unremoved jobs */ |
910 |
|
|
/* |
911 |
|
|
* This option is only useful when we are invoked |
912 |
|
|
* as atq but we accept (and ignore) this flag in |
913 |
|
|
* the other programs for backwards compatibility. |
914 |
|
|
*/ |
915 |
|
|
vflag = 1; |
916 |
|
|
break; |
917 |
|
|
|
918 |
|
|
case 'm': /* send mail when job is complete */ |
919 |
|
|
send_mail = 1; |
920 |
|
|
break; |
921 |
|
|
|
922 |
|
|
case 'f': |
923 |
|
|
if (program == ATRM) { |
924 |
|
|
force = 1; |
925 |
|
|
interactive = 0; |
926 |
|
|
} else |
927 |
|
|
atinput = optarg; |
928 |
|
|
break; |
929 |
|
|
|
930 |
|
|
case 'q': /* specify queue */ |
931 |
|
|
if (strlen(optarg) > 1) |
932 |
|
|
usage(); |
933 |
|
|
|
934 |
|
|
atqueue = queue = *optarg; |
935 |
|
|
if (!(islower((unsigned char)queue) || |
936 |
|
|
isupper((unsigned char)queue))) |
937 |
|
|
usage(); |
938 |
|
|
|
939 |
|
|
queue_set = 1; |
940 |
|
|
break; |
941 |
|
|
|
942 |
|
|
case 'd': /* for backwards compatibility */ |
943 |
|
|
case 'r': |
944 |
|
|
program = ATRM; |
945 |
|
|
options = ""; |
946 |
|
|
break; |
947 |
|
|
|
948 |
|
|
case 't': |
949 |
|
|
timer = ttime(optarg); |
950 |
|
|
break; |
951 |
|
|
|
952 |
|
|
case 'l': |
953 |
|
|
program = ATQ; |
954 |
|
|
options = "cnvq:"; |
955 |
|
|
break; |
956 |
|
|
|
957 |
|
|
case 'b': |
958 |
|
|
program = BATCH; |
959 |
|
|
options = "f:q:mv"; |
960 |
|
|
break; |
961 |
|
|
|
962 |
|
|
case 'c': |
963 |
|
|
if (program == ATQ) { |
964 |
|
|
cflag = 1; |
965 |
|
|
} else { |
966 |
|
|
program = CAT; |
967 |
|
|
options = ""; |
968 |
|
|
} |
969 |
|
|
break; |
970 |
|
|
|
971 |
|
|
case 'n': |
972 |
|
|
nflag = 1; |
973 |
|
|
break; |
974 |
|
|
|
975 |
|
|
default: |
976 |
|
|
usage(); |
977 |
|
|
break; |
978 |
|
|
} |
979 |
|
|
} |
980 |
|
|
argc -= optind; |
981 |
|
|
argv += optind; |
982 |
|
|
|
983 |
|
|
switch (program) { |
984 |
|
|
case AT: |
985 |
|
|
case BATCH: |
986 |
|
|
if (atinput != NULL) { |
987 |
|
|
if (setegid(user_gid) != 0) |
988 |
|
|
fatal("setegid(user_gid)"); |
989 |
|
|
if (freopen(atinput, "r", stdin) == NULL) |
990 |
|
|
fatal("%s", atinput); |
991 |
|
|
if (setegid(spool_gid) != 0) |
992 |
|
|
fatal("setegid(spool_gid)"); |
993 |
|
|
} |
994 |
|
|
|
995 |
|
|
if (pledge("stdio rpath wpath cpath fattr getpw unix flock", NULL) |
996 |
|
|
== -1) |
997 |
|
|
fatal("pledge"); |
998 |
|
|
break; |
999 |
|
|
|
1000 |
|
|
case ATQ: |
1001 |
|
|
case CAT: |
1002 |
|
|
if (pledge("stdio rpath getpw flock cpath wpath", NULL) == -1) |
1003 |
|
|
fatal("pledge"); |
1004 |
|
|
break; |
1005 |
|
|
|
1006 |
|
|
case ATRM: |
1007 |
|
|
if (pledge("stdio rpath cpath getpw unix flock wpath", NULL) == -1) |
1008 |
|
|
fatal("pledge"); |
1009 |
|
|
break; |
1010 |
|
|
|
1011 |
|
|
default: |
1012 |
|
|
fatalx("internal error"); |
1013 |
|
|
break; |
1014 |
|
|
} |
1015 |
|
|
|
1016 |
|
|
if ((pw = getpwuid(user_uid)) == NULL) |
1017 |
|
|
fatalx("unknown uid %u", user_uid); |
1018 |
|
|
if (strlcpy(user_name, pw->pw_name, sizeof(user_name)) >= sizeof(user_name)) |
1019 |
|
|
fatalx("username too long"); |
1020 |
|
|
|
1021 |
|
|
if (getcwd(cwd, sizeof(cwd)) == NULL) |
1022 |
|
|
fatal("unable to get current working directory"); |
1023 |
|
|
|
1024 |
|
|
if (!allowed(pw->pw_name, _PATH_AT_ALLOW, _PATH_AT_DENY)) { |
1025 |
|
|
syslog(LOG_WARNING, "(%s) AUTH (at command not allowed)", |
1026 |
|
|
pw->pw_name); |
1027 |
|
|
fatalx("you do not have permission to use at."); |
1028 |
|
|
} |
1029 |
|
|
|
1030 |
|
|
/* select our program */ |
1031 |
|
|
switch (program) { |
1032 |
|
|
case ATQ: |
1033 |
|
|
list_jobs(argc, argv, nflag, cflag); |
1034 |
|
|
break; |
1035 |
|
|
|
1036 |
|
|
case ATRM: |
1037 |
|
|
case CAT: |
1038 |
|
|
if ((aflag && argc) || (!aflag && !argc)) |
1039 |
|
|
usage(); |
1040 |
|
|
return process_jobs(argc, argv, program); |
1041 |
|
|
break; |
1042 |
|
|
|
1043 |
|
|
case AT: |
1044 |
|
|
/* Time may have been specified via the -t flag. */ |
1045 |
|
|
if (timer == -1) { |
1046 |
|
|
if (argc == 0) |
1047 |
|
|
usage(); |
1048 |
|
|
else if ((timer = parsetime(argc, argv)) == -1) |
1049 |
|
|
return EXIT_FAILURE; |
1050 |
|
|
} |
1051 |
|
|
writefile(cwd, timer, queue); |
1052 |
|
|
break; |
1053 |
|
|
|
1054 |
|
|
case BATCH: |
1055 |
|
|
if (queue_set) |
1056 |
|
|
queue = toupper((unsigned char)queue); |
1057 |
|
|
else |
1058 |
|
|
queue = DEFAULT_BATCH_QUEUE; |
1059 |
|
|
|
1060 |
|
|
if (argc == 0) |
1061 |
|
|
timer = time(NULL); |
1062 |
|
|
else if ((timer = parsetime(argc, argv)) == -1) |
1063 |
|
|
return EXIT_FAILURE; |
1064 |
|
|
|
1065 |
|
|
writefile(cwd, timer, queue); |
1066 |
|
|
break; |
1067 |
|
|
|
1068 |
|
|
default: |
1069 |
|
|
fatalx("internal error"); |
1070 |
|
|
break; |
1071 |
|
|
} |
1072 |
|
|
return EXIT_SUCCESS; |
1073 |
|
|
} |