# 2.11BSD#431 - /usr/src/usr.sbin/cron/crontab.c

##### Functions

    main              X

##### Macros

    MAIN_PROGRAM      X
    NHEADER_LINES     X

##### enums's

    opt_t             X

       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

- in line 524

delete_cmd defined in line 248; used 1 times

- in line 101

edit_cmd defined in line 274; used 1 times

- in line 103

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

- in line 261, 569

replace_cmd defined in line 468; used 2 times

- in line 105, 429

usage defined in line 59; used 7 times

- in line 135, 154-168(4), 176, 183

#### Defined variables

CheckErrorCount defined in line 47; used 4 times

- in line 269, 517-518(2), 533

Filename defined in line 45; used 18 times

- in line 129, 181, 192, 207-208(2), 219, 270, 300-302(3), 340, 347-348(2), 385-390(2), 451, 458, 490

Option defined in line 48; used 12 times

- in line 98, 130, 153-165(6), 174, 180, 187, 219

Options defined in line 39; used 1 times

- in line 219

RealUser defined in line 44; used 7 times

- in line 94, 128, 229, 252, 285, 460, 567

User defined in line 44; used 18 times

- in line 89-92(2), 127-128(2), 150, 219, 229-233(3), 252-256(3), 285-286(2), 293, 460, 559, 567

pw defined in line 49; used 4 times

- in line 121, 127, 144, 524

sccsid defined in line 18; used 1 times

- in line 491

#### 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

- in line 318, 516

Last modified: 1999-08-10 Generated: 2016-12-26 | Generated by src2html V0.67 page hit count: 3831 |