1: /* Copyright 1988,1990,1993,1994 by Paul Vixie
   2:  * All rights reserved
   3:  *
   4:  * Distribute freely, except: don't remove my name from the source or
   5:  * documentation (don't take credit for my work), mark your changes (don't
   6:  * get me blamed for your possible bugs), don't alter or remove this
   7:  * notice.  May be sold if buildable source is provided to buyer.  No
   8:  * warrantee of any kind, express or implied, is included with this
   9:  * software; use at your own risk, responsibility for damages (if any) to
  10:  * anyone resulting from the use of this software rests entirely with the
  11:  * user.
  12:  *
  13:  * Send bug reports, bug fixes, enhancements, requests, flames, etc., and
  14:  * I'll try to keep a version up to date.  I can be reached as follows:
  15:  * Paul Vixie          <paul@vix.com>          uunet!decwrl!vixie!paul
  16:  */
  17: 
  18: static char sccsid[] = "@(#) crontab.c 2.13.1 (2.11BSD) 1999/8/9";
  19: 
  20: /* crontab - install and manage per-user crontab files
  21:  * vix 02may87 [RCS has the rest of the log]
  22:  * vix 26jan87 [original]
  23:  */
  24: 
  25: #define MAIN_PROGRAM
  26: 
  27: #include "cron.h"
  28: #include <errno.h>
  29: #include <fcntl.h>
  30: #include <sys/file.h>
  31: #include <sys/stat.h>
  32: #include <sys/time.h>
  33: 
  34: #define NHEADER_LINES 3
  35: 
  36: enum opt_t  { opt_unknown, opt_list, opt_delete, opt_edit, opt_replace };
  37: 
  38: #if DEBUGGING
  39: static char *Options[] = { "???", "list", "delete", "edit", "replace" };
  40: #endif
  41: 
  42: 
  43: static  PID_T       Pid;
  44: static  char        User[MAX_UNAME], RealUser[MAX_UNAME];
  45: static  char        Filename[MAX_FNAME];
  46: static  FILE        *NewCrontab;
  47: static  int     CheckErrorCount;
  48: static  enum opt_t  Option;
  49: static  struct passwd   *pw;
  50: static  void        list_cmd __P((void)),
  51:             delete_cmd __P((void)),
  52:             edit_cmd __P((void)),
  53:             poke_daemon __P((void)),
  54:             check_error __P((char *)),
  55:             parse_args __P((int c, char *v[]));
  56: static  int     replace_cmd __P((void));
  57: 
  58: 
  59: static void
  60: usage(msg)
  61:     char *msg;
  62: {
  63:     fprintf(stderr, "%s: usage error: %s\n", ProgramName, msg);
  64:     fprintf(stderr, "usage:\t%s [-u user] file\n", ProgramName);
  65:     fprintf(stderr, "\t%s [-u user] { -e | -l | -r }\n", ProgramName);
  66:     fprintf(stderr, "\t\t(default operation is replace, per 1003.2)\n");
  67:     fprintf(stderr, "\t-e\t(edit user's crontab)\n");
  68:     fprintf(stderr, "\t-l\t(list user's crontab)\n");
  69:     fprintf(stderr, "\t-r\t(delete user's crontab)\n");
  70:     exit(ERROR_EXIT);
  71: }
  72: 
  73: 
  74: int
  75: main(argc, argv)
  76:     int argc;
  77:     char    *argv[];
  78: {
  79:     int exitstatus;
  80: 
  81:     Pid = getpid();
  82:     ProgramName = argv[0];
  83: 
  84:     setlinebuf(stderr);
  85: 
  86:     parse_args(argc, argv);     /* sets many globals, opens a file */
  87:     set_cron_uid();
  88:     set_cron_cwd();
  89:     if (!allowed(User)) {
  90:         fprintf(stderr,
  91:             "You (%s) are not allowed to use this program (%s)\n",
  92:             User, ProgramName);
  93:         fprintf(stderr, "See crontab(1) for more information\n");
  94:         log_it(RealUser, Pid, "AUTH", "crontab command not allowed");
  95:         exit(ERROR_EXIT);
  96:     }
  97:     exitstatus = OK_EXIT;
  98:     switch (Option) {
  99:     case opt_list:      list_cmd();
 100:                 break;
 101:     case opt_delete:    delete_cmd();
 102:                 break;
 103:     case opt_edit:      edit_cmd();
 104:                 break;
 105:     case opt_replace:   if (replace_cmd() < 0)
 106:                     exitstatus = ERROR_EXIT;
 107:                 break;
 108:     }
 109:     exit(0);
 110:     /*NOTREACHED*/
 111: }
 112: 
 113: 
 114: static void
 115: parse_args(argc, argv)
 116:     int argc;
 117:     char    *argv[];
 118: {
 119:     int     argch;
 120: 
 121:     if (!(pw = getpwuid(getuid()))) {
 122:         fprintf(stderr, "%s: your UID isn't in the passwd file.\n",
 123:             ProgramName);
 124:         fprintf(stderr, "bailing out.\n");
 125:         exit(ERROR_EXIT);
 126:     }
 127:     strcpy(User, pw->pw_name);
 128:     strcpy(RealUser, User);
 129:     Filename[0] = '\0';
 130:     Option = opt_unknown;
 131:     while (EOF != (argch = getopt(argc, argv, "u:lerx:"))) {
 132:         switch (argch) {
 133:         case 'x':
 134:             if (!set_debug_flags(optarg))
 135:                 usage("bad debug option");
 136:             break;
 137:         case 'u':
 138:             if (getuid() != ROOT_UID)
 139:             {
 140:                 fprintf(stderr,
 141:                     "must be privileged to use -u\n");
 142:                 exit(ERROR_EXIT);
 143:             }
 144:             if (!(pw = getpwnam(optarg)))
 145:             {
 146:                 fprintf(stderr, "%s:  user `%s' unknown\n",
 147:                     ProgramName, optarg);
 148:                 exit(ERROR_EXIT);
 149:             }
 150:             (void) strcpy(User, optarg);
 151:             break;
 152:         case 'l':
 153:             if (Option != opt_unknown)
 154:                 usage("only one operation permitted");
 155:             Option = opt_list;
 156:             break;
 157:         case 'r':
 158:             if (Option != opt_unknown)
 159:                 usage("only one operation permitted");
 160:             Option = opt_delete;
 161:             break;
 162:         case 'e':
 163:             if (Option != opt_unknown)
 164:                 usage("only one operation permitted");
 165:             Option = opt_edit;
 166:             break;
 167:         default:
 168:             usage("unrecognized option");
 169:         }
 170:     }
 171: 
 172:     endpwent();
 173: 
 174:     if (Option != opt_unknown) {
 175:         if (argv[optind] != NULL) {
 176:             usage("no arguments permitted after this option");
 177:         }
 178:     } else {
 179:         if (argv[optind] != NULL) {
 180:             Option = opt_replace;
 181:             (void) strcpy (Filename, argv[optind]);
 182:         } else {
 183:             usage("file name must be specified for replace");
 184:         }
 185:     }
 186: 
 187:     if (Option == opt_replace) {
 188:         /* we have to open the file here because we're going to
 189: 		 * chdir(2) into /var/cron before we get around to
 190: 		 * reading the file.
 191: 		 */
 192:         if (!strcmp(Filename, "-")) {
 193:             NewCrontab = stdin;
 194:         } else {
 195:             /* relinquish the setuid status of the binary during
 196: 			 * the open, lest nonroot users read files they should
 197: 			 * not be able to read.  we can't use access() here
 198: 			 * since there's a race condition.  thanks go out to
 199: 			 * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
 200: 			 * the race.
 201: 			 */
 202: 
 203:             if (swap_uids() < OK) {
 204:                 perror("swapping uids");
 205:                 exit(ERROR_EXIT);
 206:             }
 207:             if (!(NewCrontab = fopen(Filename, "r"))) {
 208:                 perror(Filename);
 209:                 exit(ERROR_EXIT);
 210:             }
 211:             if (swap_uids_back() < OK) {
 212:                 perror("swapping uids back");
 213:                 exit(ERROR_EXIT);
 214:             }
 215:         }
 216:     }
 217: 
 218:     Debug(DMISC, ("user=%s, file=%s, option=%s\n",
 219:               User, Filename, Options[(int)Option]))
 220: }
 221: 
 222: 
 223: static void
 224: list_cmd() {
 225:     char    n[MAX_FNAME];
 226:     register FILE   *f;
 227:     int ch;
 228: 
 229:     log_it(RealUser, Pid, "LIST", User);
 230:     (void) sprintf(n, CRON_TAB(User));
 231:     if (!(f = fopen(n, "r"))) {
 232:         if (errno == ENOENT)
 233:             fprintf(stderr, "no crontab for %s\n", User);
 234:         else
 235:             perror(n);
 236:         exit(ERROR_EXIT);
 237:     }
 238: 
 239:     /* file is open. copy to stdout, close.
 240: 	 */
 241:     Set_LineNum(1)
 242:     while (EOF != (ch = get_char(f)))
 243:         putchar(ch);
 244:     fclose(f);
 245: }
 246: 
 247: 
 248: static void
 249: delete_cmd() {
 250:     char    n[MAX_FNAME];
 251: 
 252:     log_it(RealUser, Pid, "DELETE", User);
 253:     (void) sprintf(n, CRON_TAB(User));
 254:     if (unlink(n)) {
 255:         if (errno == ENOENT)
 256:             fprintf(stderr, "no crontab for %s\n", User);
 257:         else
 258:             perror(n);
 259:         exit(ERROR_EXIT);
 260:     }
 261:     poke_daemon();
 262: }
 263: 
 264: 
 265: static void
 266: check_error(msg)
 267:     char    *msg;
 268: {
 269:     CheckErrorCount++;
 270:     fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber-1, msg);
 271: }
 272: 
 273: 
 274: static void
 275: edit_cmd() {
 276:     char        n[MAX_FNAME], q[MAX_TEMPSTR], *editor;
 277:     register FILE   *f;
 278:     register int    ch;
 279:     int     t, x;
 280:     struct stat statbuf;
 281:     time_t      mtime;
 282:     WAIT_T      waiter;
 283:     PID_T       pid, xpid;
 284: 
 285:     log_it(RealUser, Pid, "BEGIN EDIT", User);
 286:     (void) sprintf(n, CRON_TAB(User));
 287:     if (!(f = fopen(n, "r"))) {
 288:         if (errno != ENOENT) {
 289:             perror(n);
 290:             exit(ERROR_EXIT);
 291:         }
 292:         fprintf(stderr, "no crontab for %s - using an empty one\n",
 293:             User);
 294:         if (!(f = fopen("/dev/null", "r"))) {
 295:             perror("/dev/null");
 296:             exit(ERROR_EXIT);
 297:         }
 298:     }
 299: 
 300:     (void) sprintf(Filename, "/tmp/crontab.%d", Pid);
 301:     if (-1 == (t = open(Filename, O_CREAT|O_EXCL|O_RDWR, 0600))) {
 302:         perror(Filename);
 303:         goto fatal;
 304:     }
 305:     if (fchown(t, getuid(), getgid()) < 0) {
 306:         perror("fchown");
 307:         goto fatal;
 308:     }
 309:     if (!(NewCrontab = fdopen(t, "r+"))) {
 310:         perror("fdopen");
 311:         goto fatal;
 312:     }
 313: 
 314:     Set_LineNum(1)
 315: 
 316:     /* ignore the top few comments since we probably put them there.
 317: 	 */
 318:     for (x = 0;  x < NHEADER_LINES;  x++) {
 319:         ch = get_char(f);
 320:         if (EOF == ch)
 321:             break;
 322:         if ('#' != ch) {
 323:             putc(ch, NewCrontab);
 324:             break;
 325:         }
 326:         while (EOF != (ch = get_char(f)))
 327:             if (ch == '\n')
 328:                 break;
 329:         if (EOF == ch)
 330:             break;
 331:     }
 332: 
 333:     /* copy the rest of the crontab (if any) to the temp file.
 334: 	 */
 335:     if (EOF != ch)
 336:         while (EOF != (ch = get_char(f)))
 337:             putc(ch, NewCrontab);
 338:     fclose(f);
 339:     if (fflush(NewCrontab) < OK) {
 340:         perror(Filename);
 341:         exit(ERROR_EXIT);
 342:     }
 343:  again:
 344:     rewind(NewCrontab);
 345:     if (ferror(NewCrontab)) {
 346:         fprintf(stderr, "%s: error while writing new crontab to %s\n",
 347:             ProgramName, Filename);
 348:  fatal:     unlink(Filename);
 349:         exit(ERROR_EXIT);
 350:     }
 351:     if (fstat(t, &statbuf) < 0) {
 352:         perror("fstat");
 353:         goto fatal;
 354:     }
 355:     mtime = statbuf.st_mtime;
 356: 
 357:     if ((!(editor = getenv("VISUAL")))
 358:      && (!(editor = getenv("EDITOR")))
 359:         ) {
 360:         editor = EDITOR;
 361:     }
 362: 
 363:     /* we still have the file open.  editors will generally rewrite the
 364: 	 * original file rather than renaming/unlinking it and starting a
 365: 	 * new one; even backup files are supposed to be made by copying
 366: 	 * rather than by renaming.  if some editor does not support this,
 367: 	 * then don't use it.  the security problems are more severe if we
 368: 	 * close and reopen the file around the edit.
 369: 	 */
 370: 
 371:     switch (pid = fork()) {
 372:     case -1:
 373:         perror("fork");
 374:         goto fatal;
 375:     case 0:
 376:         /* child */
 377:         if (setuid(getuid()) < 0) {
 378:             perror("setuid(getuid())");
 379:             exit(ERROR_EXIT);
 380:         }
 381:         if (chdir("/tmp") < 0) {
 382:             perror("chdir(/tmp)");
 383:             exit(ERROR_EXIT);
 384:         }
 385:         if (strlen(editor) + strlen(Filename) + 2 >= MAX_TEMPSTR) {
 386:             fprintf(stderr, "%s: editor or filename too long\n",
 387:                 ProgramName);
 388:             exit(ERROR_EXIT);
 389:         }
 390:         sprintf(q, "%s %s", editor, Filename);
 391:         execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, NULL);
 392:         perror(editor);
 393:         exit(ERROR_EXIT);
 394:         /*NOTREACHED*/
 395:     default:
 396:         /* parent */
 397:         break;
 398:     }
 399: 
 400:     /* parent */
 401:     xpid = wait(&waiter);
 402:     if (xpid != pid) {
 403:         fprintf(stderr, "%s: wrong PID (%d != %d) from \"%s\"\n",
 404:             ProgramName, xpid, pid, editor);
 405:         goto fatal;
 406:     }
 407:     if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
 408:         fprintf(stderr, "%s: \"%s\" exited with status %d\n",
 409:             ProgramName, editor, WEXITSTATUS(waiter));
 410:         goto fatal;
 411:     }
 412:     if (WIFSIGNALED(waiter)) {
 413:         fprintf(stderr,
 414:             "%s: \"%s\" killed; signal %d (%score dumped)\n",
 415:             ProgramName, editor, WTERMSIG(waiter),
 416:             WCOREDUMP(waiter) ?"" :"no ");
 417:         goto fatal;
 418:     }
 419:     if (fstat(t, &statbuf) < 0) {
 420:         perror("fstat");
 421:         goto fatal;
 422:     }
 423:     if (mtime == statbuf.st_mtime) {
 424:         fprintf(stderr, "%s: no changes made to crontab\n",
 425:             ProgramName);
 426:         goto remove;
 427:     }
 428:     fprintf(stderr, "%s: installing new crontab\n", ProgramName);
 429:     switch (replace_cmd()) {
 430:     case 0:
 431:         break;
 432:     case -1:
 433:         for (;;) {
 434:             printf("Do you want to retry the same edit? ");
 435:             fflush(stdout);
 436:             q[0] = '\0';
 437:             (void) fgets(q, sizeof q, stdin);
 438:             switch (islower(q[0]) ? q[0] : tolower(q[0])) {
 439:             case 'y':
 440:                 goto again;
 441:             case 'n':
 442:                 goto abandon;
 443:             default:
 444:                 fprintf(stderr, "Enter Y or N\n");
 445:             }
 446:         }
 447:         /*NOTREACHED*/
 448:     case -2:
 449:     abandon:
 450:         fprintf(stderr, "%s: edits left in %s\n",
 451:             ProgramName, Filename);
 452:         goto done;
 453:     default:
 454:         fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n");
 455:         goto fatal;
 456:     }
 457:  remove:
 458:     unlink(Filename);
 459:  done:
 460:     log_it(RealUser, Pid, "END EDIT", User);
 461: }
 462: 
 463: 
 464: /* returns	0	on success
 465:  *		-1	on syntax error
 466:  *		-2	on install error
 467:  */
 468: static int
 469: replace_cmd() {
 470:     char    n[MAX_FNAME], envstr[MAX_ENVSTR], tn[MAX_FNAME];
 471:     register FILE   *tmp;
 472:     register int    ch;
 473:     int eof;
 474:     entry   *e;
 475:     time_t  now = time(NULL);
 476:     char    **envp = env_init();
 477: 
 478:     (void) sprintf(n, "tmp.%d", Pid);
 479:     (void) sprintf(tn, CRON_TAB(n));
 480:     if (!(tmp = fopen(tn, "w+"))) {
 481:         perror(tn);
 482:         return (-2);
 483:     }
 484: 
 485:     /* write a signature at the top of the file.
 486: 	 *
 487: 	 * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
 488: 	 */
 489:     fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
 490:     fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
 491:     fprintf(tmp, "# (Cron version -- %s)\n", sccsid);
 492: 
 493:     /* copy the crontab to the tmp
 494: 	 */
 495:     rewind(NewCrontab);
 496:     Set_LineNum(1)
 497:     while (EOF != (ch = get_char(NewCrontab)))
 498:         putc(ch, tmp);
 499:     ftruncate(fileno(tmp), ftell(tmp));
 500:     fflush(tmp);  rewind(tmp);
 501: 
 502:     if (ferror(tmp)) {
 503:         fprintf(stderr, "%s: error while writing new crontab to %s\n",
 504:             ProgramName, tn);
 505:         fclose(tmp);  unlink(tn);
 506:         return (-2);
 507:     }
 508: 
 509:     /* check the syntax of the file being installed.
 510: 	 */
 511: 
 512:     /* BUG: was reporting errors after the EOF if there were any errors
 513: 	 * in the file proper -- kludged it by stopping after first error.
 514: 	 *		vix 31mar87
 515: 	 */
 516:     Set_LineNum(1 - NHEADER_LINES)
 517:     CheckErrorCount = 0;  eof = FALSE;
 518:     while (!CheckErrorCount && !eof) {
 519:         switch (load_env(envstr, tmp)) {
 520:         case ERR:
 521:             eof = TRUE;
 522:             break;
 523:         case FALSE:
 524:             e = load_entry(tmp, check_error, pw, envp);
 525:             if (e)
 526:                 free(e);
 527:             break;
 528:         case TRUE:
 529:             break;
 530:         }
 531:     }
 532: 
 533:     if (CheckErrorCount != 0) {
 534:         fprintf(stderr, "errors in crontab file, can't install.\n");
 535:         fclose(tmp);  unlink(tn);
 536:         return (-1);
 537:     }
 538: 
 539:     if (fchown(fileno(tmp), ROOT_UID, -1) < OK)
 540:         {
 541:         perror("chown");
 542:         fclose(tmp);  unlink(tn);
 543:         return (-2);
 544:         }
 545: 
 546:     if (fchmod(fileno(tmp), 0600) < OK)
 547:         {
 548:         perror("chown");
 549:         fclose(tmp);  unlink(tn);
 550:         return (-2);
 551:         }
 552: 
 553:     if (fclose(tmp) == EOF) {
 554:         perror("fclose");
 555:         unlink(tn);
 556:         return (-2);
 557:     }
 558: 
 559:     (void) sprintf(n, CRON_TAB(User));
 560:     if (rename(tn, n)) {
 561:         fprintf(stderr, "%s: error renaming %s to %s\n",
 562:             ProgramName, tn, n);
 563:         perror("rename");
 564:         unlink(tn);
 565:         return (-2);
 566:     }
 567:     log_it(RealUser, Pid, "REPLACE", User);
 568: 
 569:     poke_daemon();
 570: 
 571:     return (0);
 572: }
 573: 
 574: 
 575: static void
 576: poke_daemon() {
 577:     struct timeval tvs[2];
 578: 
 579:     (void) gettimeofday(&tvs[0], NULL);
 580:     tvs[1] = tvs[0];
 581:     if (utimes(SPOOL_DIR, tvs) < OK) {
 582:         fprintf(stderr, "crontab: can't update mtime on spooldir\n");
 583:         perror(SPOOL_DIR);
 584:         return;
 585:     }
 586: }

Defined functions

check_error defined in line 265; used 1 times
delete_cmd defined in line 248; used 1 times
edit_cmd defined in line 274; used 1 times
list_cmd defined in line 223; used 1 times
  • in line 99
main defined in line 74; never used
parse_args defined in line 114; used 1 times
  • in line 86
poke_daemon defined in line 575; used 2 times
replace_cmd defined in line 468; used 2 times
usage defined in line 59; used 7 times

Defined variables

CheckErrorCount defined in line 47; used 4 times
Filename defined in line 45; used 18 times
Option defined in line 48; used 12 times
Options defined in line 39; used 1 times
RealUser defined in line 44; used 7 times
User defined in line 44; used 18 times
pw defined in line 49; used 4 times
sccsid defined in line 18; used 1 times

Defined enum's

opt_t defined in line 36; used 2 times
  • in line 48(2)

Defined macros

MAIN_PROGRAM defined in line 25; never used
NHEADER_LINES defined in line 34; used 2 times
Last modified: 1999-08-10
Generated: 2016-12-26
Generated by src2html V0.67
page hit count: 1034
Valid CSS Valid XHTML 1.0 Strict