Annotation of src/usr.bin/at/at.c, Revision 1.1
1.1 ! deraadt 1: /* $NetBSD: at.c,v 1.4 1995/03/25 18:13:31 glass Exp $ */
! 2:
! 3: /*
! 4: * at.c : Put file into atrun queue
! 5: * Copyright (C) 1993 Thomas Koenig
! 6: *
! 7: * Atrun & Atq modifications
! 8: * Copyright (C) 1993 David Parsons
! 9: * All rights reserved.
! 10: *
! 11: * Redistribution and use in source and binary forms, with or without
! 12: * modification, are permitted provided that the following conditions
! 13: * are met:
! 14: * 1. Redistributions of source code must retain the above copyright
! 15: * notice, this list of conditions and the following disclaimer.
! 16: * 2. The name of the author(s) may not be used to endorse or promote
! 17: * products derived from this software without specific prior written
! 18: * permission.
! 19: *
! 20: * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
! 21: * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
! 22: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
! 23: * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
! 24: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
! 25: * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
! 26: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
! 27: * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
! 28: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
! 29: * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
! 30: */
! 31:
! 32: #define _USE_BSD 1
! 33:
! 34: /* System Headers */
! 35: #include <sys/types.h>
! 36: #include <sys/stat.h>
! 37: #include <sys/wait.h>
! 38: #include <ctype.h>
! 39: #include <dirent.h>
! 40: #include <errno.h>
! 41: #include <fcntl.h>
! 42: #include <pwd.h>
! 43: #include <signal.h>
! 44: #include <stddef.h>
! 45: #include <stdio.h>
! 46: #include <stdlib.h>
! 47: #include <string.h>
! 48: #include <time.h>
! 49: #include <unistd.h>
! 50:
! 51: /* Local headers */
! 52: #include "at.h"
! 53: #include "panic.h"
! 54: #include "parsetime.h"
! 55: #include "pathnames.h"
! 56: #define MAIN
! 57: #include "privs.h"
! 58:
! 59: /* Macros */
! 60: #define ALARMC 10 /* Number of seconds to wait for timeout */
! 61:
! 62: #define SIZE 255
! 63: #define TIMESIZE 50
! 64:
! 65: /* File scope variables */
! 66: #ifndef lint
! 67: static char rcsid[] = "$NetBSD: at.c,v 1.4 1995/03/25 18:13:31 glass Exp $";
! 68: #endif
! 69:
! 70: char *no_export[] =
! 71: {
! 72: "TERM", "TERMCAP", "DISPLAY", "_"
! 73: };
! 74: static send_mail = 0;
! 75:
! 76: /* External variables */
! 77: extern char **environ;
! 78: int fcreated;
! 79: char *namep;
! 80: char atfile[FILENAME_MAX];
! 81:
! 82: char *atinput = (char *) 0; /* where to get input from */
! 83: char atqueue = 0; /* which queue to examine for jobs (atq) */
! 84: char atverify = 0; /* verify time instead of queuing job */
! 85:
! 86: /* Function declarations */
! 87: static void sigc __P((int signo));
! 88: static void alarmc __P((int signo));
! 89: static char *cwdname __P((void));
! 90: static void writefile __P((time_t runtimer, char queue));
! 91: static void list_jobs __P((void));
! 92:
! 93: /* Signal catching functions */
! 94:
! 95: static void
! 96: sigc(signo)
! 97: int signo;
! 98: {
! 99: /* If the user presses ^C, remove the spool file and exit
! 100: */
! 101: if (fcreated) {
! 102: PRIV_START
! 103: unlink(atfile);
! 104: PRIV_END
! 105: }
! 106:
! 107: exit(EXIT_FAILURE);
! 108: }
! 109:
! 110: static void
! 111: alarmc(signo)
! 112: int signo;
! 113: {
! 114: /* Time out after some seconds
! 115: */
! 116: panic("File locking timed out");
! 117: }
! 118:
! 119: /* Local functions */
! 120:
! 121: static char *
! 122: cwdname()
! 123: {
! 124: /* Read in the current directory; the name will be overwritten on
! 125: * subsequent calls.
! 126: */
! 127: static char *ptr = NULL;
! 128: static size_t size = SIZE;
! 129:
! 130: if (ptr == NULL)
! 131: ptr = (char *) malloc(size);
! 132:
! 133: while (1) {
! 134: if (ptr == NULL)
! 135: panic("Out of memory");
! 136:
! 137: if (getcwd(ptr, size - 1) != NULL)
! 138: return ptr;
! 139:
! 140: if (errno != ERANGE)
! 141: perr("Cannot get directory");
! 142:
! 143: free(ptr);
! 144: size += SIZE;
! 145: ptr = (char *) malloc(size);
! 146: }
! 147: }
! 148:
! 149: static void
! 150: writefile(runtimer, queue)
! 151: time_t runtimer;
! 152: char queue;
! 153: {
! 154: /*
! 155: * This does most of the work if at or batch are invoked for
! 156: * writing a job.
! 157: */
! 158: int i;
! 159: char *ap, *ppos, *mailname;
! 160: struct passwd *pass_entry;
! 161: struct stat statbuf;
! 162: int fdes, lockdes, fd2;
! 163: FILE *fp, *fpin;
! 164: struct sigaction act;
! 165: char **atenv;
! 166: int ch;
! 167: mode_t cmask;
! 168: struct flock lock;
! 169:
! 170: /*
! 171: * Install the signal handler for SIGINT; terminate after removing the
! 172: * spool file if necessary
! 173: */
! 174: act.sa_handler = sigc;
! 175: sigemptyset(&(act.sa_mask));
! 176: act.sa_flags = 0;
! 177:
! 178: sigaction(SIGINT, &act, NULL);
! 179:
! 180: strcpy(atfile, _PATH_ATJOBS);
! 181: ppos = atfile + strlen(_PATH_ATJOBS);
! 182:
! 183: /*
! 184: * Loop over all possible file names for running something at this
! 185: * particular time, see if a file is there; the first empty slot at
! 186: * any particular time is used. Lock the file _PATH_LOCKFILE first
! 187: * to make sure we're alone when doing this.
! 188: */
! 189:
! 190: PRIV_START
! 191:
! 192: if ((lockdes = open(_PATH_LOCKFILE, O_WRONLY | O_CREAT, 0600)) < 0)
! 193: perr2("Cannot open lockfile ", _PATH_LOCKFILE);
! 194:
! 195: lock.l_type = F_WRLCK;
! 196: lock.l_whence = SEEK_SET;
! 197: lock.l_start = 0;
! 198: lock.l_len = 0;
! 199:
! 200: act.sa_handler = alarmc;
! 201: sigemptyset(&(act.sa_mask));
! 202: act.sa_flags = 0;
! 203:
! 204: /*
! 205: * Set an alarm so a timeout occurs after ALARMC seconds, in case
! 206: * something is seriously broken.
! 207: */
! 208: sigaction(SIGALRM, &act, NULL);
! 209: alarm(ALARMC);
! 210: fcntl(lockdes, F_SETLKW, &lock);
! 211: alarm(0);
! 212:
! 213: for (i = 0; i < AT_MAXJOBS; i++) {
! 214: sprintf(ppos, "%c%8lx.%3x", queue,
! 215: (unsigned long) (runtimer / 60), i);
! 216: for (ap = ppos; *ap != '\0'; ap++)
! 217: if (*ap == ' ')
! 218: *ap = '0';
! 219:
! 220: if (stat(atfile, &statbuf) != 0) {
! 221: if (errno == ENOENT)
! 222: break;
! 223: else
! 224: perr2("Cannot access ", _PATH_ATJOBS);
! 225: }
! 226: } /* for */
! 227:
! 228: if (i >= AT_MAXJOBS)
! 229: panic("Too many jobs already");
! 230:
! 231: /*
! 232: * Create the file. The x bit is only going to be set after it has
! 233: * been completely written out, to make sure it is not executed in
! 234: * the meantime. To make sure they do not get deleted, turn off
! 235: * their r bit. Yes, this is a kluge.
! 236: */
! 237: cmask = umask(S_IRUSR | S_IWUSR | S_IXUSR);
! 238: if ((fdes = creat(atfile, O_WRONLY)) == -1)
! 239: perr("Cannot create atjob file");
! 240:
! 241: if ((fd2 = dup(fdes)) < 0)
! 242: perr("Error in dup() of job file");
! 243:
! 244: if (fchown(fd2, real_uid, -1) != 0)
! 245: perr("Cannot give away file");
! 246:
! 247: PRIV_END
! 248:
! 249: /*
! 250: * We no longer need suid root; now we just need to be able to
! 251: * write to the directory, if necessary.
! 252: */
! 253:
! 254: REDUCE_PRIV(effective_uid);
! 255:
! 256: /*
! 257: * We've successfully created the file; let's set the flag so it
! 258: * gets removed in case of an interrupt or error.
! 259: */
! 260: fcreated = 1;
! 261:
! 262: /* Now we can release the lock, so other people can access it */
! 263: lock.l_type = F_UNLCK;
! 264: lock.l_whence = SEEK_SET;
! 265: lock.l_start = 0;
! 266: lock.l_len = 0;
! 267: fcntl(lockdes, F_SETLKW, &lock);
! 268: close(lockdes);
! 269:
! 270: if ((fp = fdopen(fdes, "w")) == NULL)
! 271: panic("Cannot reopen atjob file");
! 272:
! 273: /*
! 274: * Get the userid to mail to, first by trying getlogin(), which
! 275: * reads /etc/utmp, then from LOGNAME, finally from getpwuid().
! 276: */
! 277: mailname = getlogin();
! 278: if (mailname == NULL)
! 279: mailname = getenv("LOGNAME");
! 280:
! 281: if ((mailname == NULL) || (mailname[0] == '\0')
! 282: || (strlen(mailname) > 8)) {
! 283: pass_entry = getpwuid(getuid());
! 284: if (pass_entry != NULL)
! 285: mailname = pass_entry->pw_name;
! 286: }
! 287:
! 288: if (atinput != (char *) NULL) {
! 289: fpin = freopen(atinput, "r", stdin);
! 290: if (fpin == NULL)
! 291: perr("Cannot open input file");
! 292: }
! 293: fprintf(fp, "#! /bin/sh\n# mail %8s %d\n", mailname, send_mail);
! 294:
! 295: /* Write out the umask at the time of invocation */
! 296: fprintf(fp, "umask %lo\n", (unsigned long) cmask);
! 297:
! 298: /*
! 299: * Write out the environment. Anything that may look like a special
! 300: * character to the shell is quoted, except for \n, which is done
! 301: * with a pair of "'s. Dont't export the no_export list (such as
! 302: * TERM or DISPLAY) because we don't want these.
! 303: */
! 304: for (atenv = environ; *atenv != NULL; atenv++) {
! 305: int export = 1;
! 306: char *eqp;
! 307:
! 308: eqp = strchr(*atenv, '=');
! 309: if (ap == NULL)
! 310: eqp = *atenv;
! 311: else {
! 312: int i;
! 313:
! 314: for (i = 0;i < sizeof(no_export) /
! 315: sizeof(no_export[0]); i++) {
! 316: export = export
! 317: && (strncmp(*atenv, no_export[i],
! 318: (size_t) (eqp - *atenv)) != 0);
! 319: }
! 320: eqp++;
! 321: }
! 322:
! 323: if (export) {
! 324: fwrite(*atenv, sizeof(char), eqp - *atenv, fp);
! 325: for (ap = eqp; *ap != '\0'; ap++) {
! 326: if (*ap == '\n')
! 327: fprintf(fp, "\"\n\"");
! 328: else {
! 329: if (!isalnum(*ap))
! 330: fputc('\\', fp);
! 331:
! 332: fputc(*ap, fp);
! 333: }
! 334: }
! 335: fputs("; export ", fp);
! 336: fwrite(*atenv, sizeof(char), eqp - *atenv - 1, fp);
! 337: fputc('\n', fp);
! 338:
! 339: }
! 340: }
! 341: /*
! 342: * Cd to the directory at the time and write out all the commands
! 343: * the user supplies from stdin.
! 344: */
! 345: fprintf(fp, "cd %s\n", cwdname());
! 346:
! 347: while ((ch = getchar()) != EOF)
! 348: fputc(ch, fp);
! 349:
! 350: fprintf(fp, "\n");
! 351: if (ferror(fp))
! 352: panic("Output error");
! 353:
! 354: if (ferror(stdin))
! 355: panic("Input error");
! 356:
! 357: fclose(fp);
! 358:
! 359: /*
! 360: * Set the x bit so that we're ready to start executing
! 361: */
! 362: if (fchmod(fd2, S_IRUSR | S_IWUSR | S_IXUSR) < 0)
! 363: perr("Cannot give away file");
! 364:
! 365: close(fd2);
! 366: fprintf(stderr, "Job %s will be executed using /bin/sh\n", ppos);
! 367: }
! 368:
! 369: static void
! 370: list_jobs()
! 371: {
! 372: /*
! 373: * List all a user's jobs in the queue, by looping through
! 374: * _PATH_ATJOBS, or everybody's if we are root
! 375: */
! 376: struct passwd *pw;
! 377: DIR *spool;
! 378: struct dirent *dirent;
! 379: struct stat buf;
! 380: struct tm runtime;
! 381: unsigned long ctm;
! 382: char queue;
! 383: time_t runtimer;
! 384: char timestr[TIMESIZE];
! 385: int first = 1;
! 386:
! 387: PRIV_START
! 388:
! 389: if (chdir(_PATH_ATJOBS) != 0)
! 390: perr2("Cannot change to ", _PATH_ATJOBS);
! 391:
! 392: if ((spool = opendir(".")) == NULL)
! 393: perr2("Cannot open ", _PATH_ATJOBS);
! 394:
! 395: /* Loop over every file in the directory */
! 396: while ((dirent = readdir(spool)) != NULL) {
! 397: if (stat(dirent->d_name, &buf) != 0)
! 398: perr2("Cannot stat in ", _PATH_ATJOBS);
! 399:
! 400: /*
! 401: * See it's a regular file and has its x bit turned on and
! 402: * is the user's
! 403: */
! 404: if (!S_ISREG(buf.st_mode)
! 405: || ((buf.st_uid != real_uid) && !(real_uid == 0))
! 406: || !(S_IXUSR & buf.st_mode || atverify))
! 407: continue;
! 408:
! 409: if (sscanf(dirent->d_name, "%c%8lx", &queue, &ctm) != 2)
! 410: continue;
! 411:
! 412: if (atqueue && (queue != atqueue))
! 413: continue;
! 414:
! 415: runtimer = 60 * (time_t) ctm;
! 416: runtime = *localtime(&runtimer);
! 417: strftime(timestr, TIMESIZE, "%X %x", &runtime);
! 418: if (first) {
! 419: printf("Date\t\t\tOwner\tQueue\tJob#\n");
! 420: first = 0;
! 421: }
! 422: pw = getpwuid(buf.st_uid);
! 423:
! 424: printf("%s\t%s\t%c%s\t%s\n",
! 425: timestr,
! 426: pw ? pw->pw_name : "???",
! 427: queue,
! 428: (S_IXUSR & buf.st_mode) ? "" : "(done)",
! 429: dirent->d_name);
! 430: }
! 431: PRIV_END
! 432: }
! 433:
! 434: static void
! 435: delete_jobs(argc, argv)
! 436: int argc;
! 437: char **argv;
! 438: {
! 439: /* Delete every argument (job - ID) given */
! 440: int i;
! 441: struct stat buf;
! 442:
! 443: PRIV_START
! 444:
! 445: if (chdir(_PATH_ATJOBS) != 0)
! 446: perr2("Cannot change to ", _PATH_ATJOBS);
! 447:
! 448: for (i = optind; i < argc; i++) {
! 449: if (stat(argv[i], &buf) != 0)
! 450: perr(argv[i]);
! 451: if ((buf.st_uid != real_uid) && !(real_uid == 0)) {
! 452: fprintf(stderr, "%s: Not owner\n", argv[i]);
! 453: exit(EXIT_FAILURE);
! 454: }
! 455: if (unlink(argv[i]) != 0)
! 456: perr(argv[i]);
! 457: }
! 458: PRIV_END
! 459: } /* delete_jobs */
! 460:
! 461: /* Global functions */
! 462:
! 463: int
! 464: main(argc, argv)
! 465: int argc;
! 466: char **argv;
! 467: {
! 468: int c;
! 469: char queue = 'a';
! 470: char *pgm;
! 471:
! 472: enum {
! 473: ATQ, ATRM, AT, BATCH
! 474: }; /* what program we want to run */
! 475: int program = AT; /* our default program */
! 476: char *options = "q:f:mv"; /* default options for at */
! 477: time_t timer;
! 478:
! 479: RELINQUISH_PRIVS
! 480:
! 481: /* Eat any leading paths */
! 482: if ((pgm = strrchr(argv[0], '/')) == NULL)
! 483: pgm = argv[0];
! 484: else
! 485: pgm++;
! 486:
! 487: namep = pgm;
! 488:
! 489: /* find out what this program is supposed to do */
! 490: if (strcmp(pgm, "atq") == 0) {
! 491: program = ATQ;
! 492: options = "q:v";
! 493: } else if (strcmp(pgm, "atrm") == 0) {
! 494: program = ATRM;
! 495: options = "";
! 496: } else if (strcmp(pgm, "batch") == 0) {
! 497: program = BATCH;
! 498: options = "f:mv";
! 499: }
! 500:
! 501: /* process whatever options we can process */
! 502: opterr = 1;
! 503: while ((c = getopt(argc, argv, options)) != EOF)
! 504: switch (c) {
! 505: case 'v': /* verify time settings */
! 506: atverify = 1;
! 507: break;
! 508:
! 509: case 'm': /* send mail when job is complete */
! 510: send_mail = 1;
! 511: break;
! 512:
! 513: case 'f':
! 514: atinput = optarg;
! 515: break;
! 516:
! 517: case 'q': /* specify queue */
! 518: if (strlen(optarg) > 1)
! 519: usage();
! 520:
! 521: atqueue = queue = *optarg;
! 522: if ((!islower(queue)) || (queue > 'l'))
! 523: usage();
! 524: break;
! 525:
! 526: default:
! 527: usage();
! 528: break;
! 529: }
! 530: /* end of options eating */
! 531:
! 532: /* select our program */
! 533: switch (program) {
! 534: case ATQ:
! 535: list_jobs();
! 536: break;
! 537:
! 538: case ATRM:
! 539: delete_jobs(argc, argv);
! 540: break;
! 541:
! 542: case AT:
! 543: timer = parsetime(argc, argv);
! 544: if (atverify) {
! 545: struct tm *tm = localtime(&timer);
! 546:
! 547: fprintf(stderr, "%s\n", asctime(tm));
! 548: }
! 549: writefile(timer, queue);
! 550: break;
! 551:
! 552: case BATCH:
! 553: writefile(time(NULL), 'b');
! 554: break;
! 555:
! 556: default:
! 557: panic("Internal error");
! 558: break;
! 559: }
! 560: exit(EXIT_SUCCESS);
! 561: }