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