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