root/src/crontab.c

/* [previous][next][first][last][top][bottom][index][help]  */

DEFINITIONS

This source file includes following definitions.
  1. die
  2. main
  3. parse_args
  4. list_cmd
  5. delete_cmd
  6. check_error
  7. tmp_path
  8. host_specific_filename
  9. edit_cmd
  10. replace_cmd
  11. hostset_cmd
  12. hostget_cmd
  13. poke_daemon
  14. die

   1 /* Copyright 1988,1990,1993,1994 by Paul Vixie
   2  * All rights reserved
   3  */
   4 
   5 /*
   6  * Copyright (c) 2004 by Internet Systems Consortium, Inc. ("ISC")
   7  * Copyright (c) 1997,2000 by Internet Software Consortium, Inc.
   8  *
   9  * Permission to use, copy, modify, and distribute this software for any
  10  * purpose with or without fee is hereby granted, provided that the above
  11  * copyright notice and this permission notice appear in all copies.
  12  *
  13  * THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES
  14  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  15  * MERCHANTABILITY AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR
  16  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
  17  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
  18  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT
  19  * OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  20  */
  21 
  22 /* crontab - install and manage per-user crontab files
  23  * vix 02may87 [RCS has the rest of the log]
  24  * vix 26jan87 [original]
  25  */
  26 
  27 /*
  28  * Modified 2010/09/10 by Colin Dean, Durham University IT Service,
  29  * to add clustering support.
  30  */
  31 
  32 #include "config.h"
  33 
  34 #define MAIN_PROGRAM
  35 
  36 #include <errno.h>
  37 #include <locale.h>
  38 #include <pwd.h>
  39 #include <signal.h>
  40 #include <stdio.h>
  41 #include <stdlib.h>
  42 #include <string.h>
  43 #include <sys/stat.h>
  44 #include <sys/types.h>
  45 #include <sys/wait.h>
  46 #include <unistd.h>
  47 #include <utime.h>
  48 
  49 #ifdef WITH_PAM
  50 # include <security/pam_appl.h>
  51 #endif
  52 
  53 #ifdef WITH_SELINUX
  54 # include <selinux/selinux.h>
  55 # include <selinux/context.h>
  56 #endif
  57 
  58 #include "cronie_common.h"
  59 #include "bitstring.h"
  60 #include "externs.h"
  61 #include "funcs.h"
  62 #include "globals.h"
  63 #include "macros.h"
  64 #include "pathnames.h"
  65 #include "structs.h"
  66 
  67 #define NHEADER_LINES 0
  68 
  69 enum opt_t {opt_unknown, opt_list, opt_delete, opt_edit, opt_replace, opt_hostset, opt_hostget};
  70 
  71 #if DEBUGGING
  72 static const char *Options[] = {"???", "list", "delete", "edit", "replace", "hostset", "hostget"};
  73 
  74 # ifdef WITH_SELINUX
  75 static const char *getoptargs = "u:lerisncx:V";
  76 # else
  77 static const char *getoptargs = "u:lerincx:V";
  78 # endif
  79 #else
  80 # ifdef WITH_SELINUX
  81 static const char *getoptargs = "u:lerisncV";
  82 # else
  83 static const char *getoptargs = "u:lerincV";
  84 # endif
  85 #endif
  86 #ifdef WITH_SELINUX
  87 static char *selinux_context = 0;
  88 #endif
  89 
  90 static PID_T Pid;
  91 static char User[MAX_UNAME], RealUser[MAX_UNAME];
  92 static char Filename[MAX_FNAME], TempFilename[MAX_FNAME];
  93 static char Host[MAXHOSTNAMELEN];
  94 static FILE *NewCrontab;
  95 static int CheckErrorCount;
  96 static int PromptOnDelete;
  97 static int HostSpecified;
  98 static enum opt_t Option;
  99 static struct passwd *pw;
 100 static void list_cmd(void),
 101 delete_cmd(void),
 102 edit_cmd(void),
 103 poke_daemon(void),
 104 check_error(const char *), parse_args(int c, char *v[]), die(int) ATTRIBUTE_NORETURN;
     /* [previous][next][first][last][top][bottom][index][help]  */
 105 static int replace_cmd(void), hostset_cmd(void), hostget_cmd(void);
 106 static char *host_specific_filename(const char *prefix, const char *suffix);
 107 static const char *tmp_path(void);
 108 
 109 static void usage(const char *msg) ATTRIBUTE_NORETURN;
 110 static void usage(const char *msg) {
 111         fprintf(stderr, "%s: usage error: %s\n", ProgramName, msg);
 112         fprintf(stderr, "Usage:\n");
 113         fprintf(stderr, " %s [options] file\n", ProgramName);
 114         fprintf(stderr, " %s [options]\n", ProgramName);
 115         fprintf(stderr, " %s -n [hostname]\n", ProgramName);
 116         fprintf(stderr, "\n");
 117         fprintf(stderr, "Options:\n");
 118         fprintf(stderr, " -u <user>  define user\n");
 119         fprintf(stderr, " -e         edit user's crontab\n");
 120         fprintf(stderr, " -l         list user's crontab\n");
 121         fprintf(stderr, " -r         delete user's crontab\n");
 122         fprintf(stderr, " -i         prompt before deleting\n");
 123         fprintf(stderr, " -n <host>  set host in cluster to run users' crontabs\n");
 124         fprintf(stderr, " -c         get host in cluster to run users' crontabs\n");
 125 #ifdef WITH_SELINUX
 126         fprintf(stderr, " -s         selinux context\n");
 127 #endif
 128         fprintf(stderr, " -V         print version and exit\n");
 129 #ifdef DEBUGGING
 130         fprintf(stderr, " -x <mask>  enable debugging\n");
 131 #endif
 132         fprintf(stderr, "\nDefault operation is replace, per 1003.2\n");
 133         exit(ERROR_EXIT);
 134 }
 135 
 136 int main(int argc, char *argv[]) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 137         int exitstatus;
 138         char n[] = "-"; /*set the n string to - so we have a valid string to use */
 139         char *nargv[] = { argv[0], n, NULL };
 140 
 141         if ((ProgramName=strrchr(argv[0], '/')) == NULL) {
 142                 ProgramName = argv[0];
 143         }
 144         else {
 145                 ++ProgramName;
 146         }
 147 
 148         Pid = getpid();
 149         MailCmd[0] = '\0';
 150         cron_default_mail_charset[0] = '\0';
 151 
 152         setlocale(LC_ALL, "");
 153 
 154 #if defined(BSD)
 155         setlinebuf(stderr);
 156 #endif
 157         /*should we desire to make changes to behavior later. */
 158         if (argv[1] == NULL) {  /* change behavior to allow crontab to take stdin with no '-' */
 159                 argv = nargv;
 160         }
 161         parse_args(argc, argv); /* sets many globals, opens a file */
 162         check_spool_dir();
 163         if (!allowed(RealUser, CRON_ALLOW, CRON_DENY)) {
 164                 fprintf(stderr,
 165                         "You (%s) are not allowed to use this program (%s)\n",
 166                         User, ProgramName);
 167                 fprintf(stderr, "See crontab(1) for more information\n");
 168                 log_it(RealUser, Pid, "AUTH", "crontab command not allowed", 0);
 169                 exit(ERROR_EXIT);
 170         }
 171 
 172 #if defined(WITH_PAM)
 173         if (getuid() != 0 && cron_start_pam(pw) != PAM_SUCCESS) {
 174                 fprintf(stderr,
 175                         "You (%s) are not allowed to access to (%s) because of pam configuration.\n",
 176                         User, ProgramName);
 177                 exit(ERROR_EXIT);
 178         };
 179 #endif
 180 
 181         exitstatus = OK_EXIT;
 182         switch (Option) {
 183         case opt_unknown:
 184                 exitstatus = ERROR_EXIT;
 185                 break;
 186         case opt_list:
 187                 list_cmd();
 188                 break;
 189         case opt_delete:
 190                 delete_cmd();
 191                 break;
 192         case opt_edit:
 193                 edit_cmd();
 194                 break;
 195         case opt_replace:
 196                 if (replace_cmd() < 0)
 197                         exitstatus = ERROR_EXIT;
 198                 break;
 199         case opt_hostset:
 200                 if (hostset_cmd() < 0)
 201                         exitstatus = ERROR_EXIT;
 202                 break;
 203         case opt_hostget:
 204                 if (hostget_cmd() < 0)
 205                         exitstatus = ERROR_EXIT;
 206                 break;
 207         default:
 208                 abort();
 209         }
 210 #ifdef WITH_PAM
 211         cron_close_pam();
 212 #endif
 213         exit(exitstatus);
 214  /*NOTREACHED*/}
 215 
 216 static void parse_args(int argc, char *argv[]) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 217         int argch;
 218 
 219         if (!(pw = getpwuid(getuid()))) {
 220                 fprintf(stderr, "%s: your UID isn't in the passwd file.\n",
 221                         ProgramName);
 222                 fprintf(stderr, "bailing out.\n");
 223                 exit(ERROR_EXIT);
 224         }
 225         if (strlen(pw->pw_name) >= sizeof User) {
 226                 fprintf(stderr, "username too long\n");
 227                 exit(ERROR_EXIT);
 228         }
 229         strcpy(User, pw->pw_name);
 230         strcpy(RealUser, User);
 231         Filename[0] = '\0';
 232         Option = opt_unknown;
 233         PromptOnDelete = 0;
 234         HostSpecified = 0;
 235         while (-1 != (argch = getopt(argc, argv, getoptargs))) {
 236                 switch (argch) {
 237 #if DEBUGGING
 238                 case 'x':
 239                         if (!set_debug_flags(optarg))
 240                                 usage("bad debug option");
 241                         break;
 242 #endif
 243                 case 'u':
 244                         if (MY_UID(pw) != ROOT_UID) {
 245                                 fprintf(stderr, "must be privileged to use -u\n");
 246                                 exit(ERROR_EXIT);
 247                         }
 248 #ifdef WITH_SELINUX
 249                         if (crontab_security_access() != 0) {
 250                                 fprintf(stderr,
 251                                         "Access denied by SELinux, must be privileged to use -u\n");
 252                                 exit(ERROR_EXIT);
 253                         }
 254 #endif
 255                         if (Option == opt_hostset || Option == opt_hostget) {
 256                                 fprintf(stderr,
 257                                         "cannot use -u with -n or -c\n");
 258                                 exit(ERROR_EXIT);
 259                         }
 260 
 261                         if (!(pw = getpwnam(optarg))) {
 262                                 fprintf(stderr, "%s:  user `%s' unknown\n",
 263                                         ProgramName, optarg);
 264                                 exit(ERROR_EXIT);
 265                         }
 266                         if (strlen(optarg) >= sizeof User)
 267                                 usage("username too long");
 268                         (void) strcpy(User, optarg);
 269                         break;
 270                 case 'l':
 271                         if (Option != opt_unknown)
 272                                 usage("only one operation permitted");
 273                         Option = opt_list;
 274                         break;
 275                 case 'r':
 276                         if (Option != opt_unknown)
 277                                 usage("only one operation permitted");
 278                         Option = opt_delete;
 279                         break;
 280                 case 'e':
 281                         if (Option != opt_unknown)
 282                                 usage("only one operation permitted");
 283                         Option = opt_edit;
 284                         break;
 285                 case 'i':
 286                         PromptOnDelete = 1;
 287                         break;
 288 #ifdef WITH_SELINUX
 289                 case 's':
 290                         if (getprevcon((security_context_t *) & (selinux_context))) {
 291                                 fprintf(stderr, "Cannot obtain SELinux process context\n");
 292                                 exit(ERROR_EXIT);
 293                         }
 294                         break;
 295 #endif
 296                 case 'n':
 297                         if (MY_UID(pw) != ROOT_UID) {
 298                                 fprintf(stderr,
 299                                         "must be privileged to set host with -n\n");
 300                                 exit(ERROR_EXIT);
 301                         }
 302                         if (Option != opt_unknown)
 303                                 usage("only one operation permitted");
 304                         if (strcmp(User, RealUser) != 0) {
 305                                 fprintf(stderr,
 306                                         "cannot use -u with -n or -c\n");
 307                                 exit(ERROR_EXIT);
 308                         }
 309                         Option = opt_hostset;
 310                         break;
 311                 case 'c':
 312                         if (Option != opt_unknown)
 313                                 usage("only one operation permitted");
 314                         if (strcmp(User, RealUser) != 0) {
 315                                 fprintf(stderr,
 316                                         "cannot use -u with -n or -c\n");
 317                                 exit(ERROR_EXIT);
 318                         }
 319                         Option = opt_hostget;
 320                         break;
 321                 case 'V':
 322                         puts(PACKAGE_STRING);
 323                         exit(EXIT_SUCCESS);
 324                 default:
 325                         usage("unrecognized option");
 326                 }
 327         }
 328 
 329         endpwent();
 330 
 331         if (Option == opt_hostset && argv[optind] != NULL) {            
 332                 HostSpecified = 1;
 333                 if (strlen(argv[optind]) >= sizeof Host)
 334                         usage("hostname too long");
 335                 (void) strcpy(Host, argv[optind]);
 336                 optind++;
 337         }
 338 
 339         if (Option != opt_unknown) {
 340                 if (argv[optind] != NULL)
 341                         usage("no arguments permitted after this option");
 342         }
 343         else {
 344                 if (argv[optind] != NULL) {
 345                         Option = opt_replace;
 346                         if (strlen(argv[optind]) >= sizeof Filename)
 347                                 usage("filename too long");
 348                         (void) strcpy(Filename, argv[optind]);
 349                 }
 350                 else
 351                         usage("file name must be specified for replace");
 352         }
 353 
 354         if (Option == opt_replace) {
 355                 if (!strcmp(Filename, "-"))
 356                         NewCrontab = stdin;
 357                 else {
 358                         /* relinquish the setuid status of the binary during
 359                          * the open, lest nonroot users read files they should
 360                          * not be able to read.  we can't use access() here
 361                          * since there's a race condition.  thanks go out to
 362                          * Arnt Gulbrandsen <agulbra@pvv.unit.no> for spotting
 363                          * the race.
 364                          */
 365                         struct stat sb;
 366 
 367                         if (swap_uids() < OK) {
 368                                 perror("swapping uids");
 369                                 exit(ERROR_EXIT);
 370                         }
 371                         if (!(NewCrontab = fopen(Filename, "r"))) {
 372                                 perror(Filename);
 373                                 exit(ERROR_EXIT);
 374                         }
 375                         if (fstat(fileno(NewCrontab), &sb) < 0) {
 376                                 perror(Filename);
 377                                 exit(ERROR_EXIT);
 378                         }
 379                         if ((sb.st_mode & S_IFMT) == S_IFDIR) {
 380                                 fprintf(stderr,
 381                                         "cannot replace crontab with a directory: %s\n",
 382                                         Filename);
 383                                 fclose(NewCrontab);
 384                                 exit(ERROR_EXIT);
 385                         }
 386                         if (swap_uids_back() < OK) {
 387                                 perror("swapping uids back");
 388                                 exit(ERROR_EXIT);
 389                         }
 390                 }
 391         }
 392 
 393         Debug(DMISC, ("user=%s, file=%s, option=%s\n",
 394                         User, Filename, Options[(int) Option]));
 395 }
 396 
 397 static void list_cmd(void) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 398         char n[MAX_FNAME];
 399         FILE *f;
 400         int ch;
 401 
 402         log_it(RealUser, Pid, "LIST", User, 0);
 403         if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
 404                 fprintf(stderr, "path too long\n");
 405                 exit(ERROR_EXIT);
 406         }
 407         if (!(f = fopen(n, "r"))) {
 408                 if (errno == ENOENT)
 409                         fprintf(stderr, "no crontab for %s\n", User);
 410                 else
 411                         perror(n);
 412                 exit(ERROR_EXIT);
 413         }
 414 
 415         /* file is open. copy to stdout, close.
 416          */
 417         Set_LineNum(1)
 418                 while (EOF != (ch = get_char(f)))
 419                 putchar(ch);
 420         fclose(f);
 421 }
 422 
 423 static void delete_cmd(void) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 424         char n[MAX_FNAME] = "";
 425         if (PromptOnDelete == 1) {
 426                 printf("crontab: really delete %s's crontab? ", User);
 427                 fflush(stdout);
 428                 if ((fgets(n, MAX_FNAME - 1, stdin) == 0L)
 429                         || ((n[0] != 'Y') && (n[0] != 'y'))
 430                         )
 431                         exit(0);
 432         }
 433 
 434         log_it(RealUser, Pid, "DELETE", User, 0);
 435         if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
 436                 fprintf(stderr, "path too long\n");
 437                 exit(ERROR_EXIT);
 438         }
 439         if (unlink(n) != 0) {
 440                 if (errno == ENOENT)
 441                         fprintf(stderr, "no crontab for %s\n", User);
 442                 else
 443                         perror(n);
 444                 exit(ERROR_EXIT);
 445         }
 446         poke_daemon();
 447 }
 448 
 449 static void check_error(const char *msg) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 450         CheckErrorCount++;
 451         fprintf(stderr, "\"%s\":%d: %s\n", Filename, LineNumber - 1, msg);
 452 }
 453 
 454 static const char *tmp_path(void) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 455         const char *tmpdir = NULL;
 456 
 457         if ((getuid() == geteuid()) && (getgid() == getegid())) {
 458                 tmpdir = getenv("TMPDIR");
 459         }
 460         return tmpdir ? tmpdir : "/tmp";
 461 }
 462 
 463 static char *host_specific_filename(const char *prefix, const char *suffix)
     /* [previous][next][first][last][top][bottom][index][help]  */
 464 {
 465         /*
 466          * For cluster-wide use, where there is otherwise risk of the same
 467          * name being generated on more than one host at once, insert hostname
 468          * separated with dots, and return static buffer or NULL on failure.
 469          */
 470 
 471         static char safename[MAX_FNAME];
 472         char hostname[MAX_FNAME];
 473 
 474         if (gethostname(hostname, sizeof hostname) != 0)
 475                 return NULL;
 476 
 477         if (prefix) {
 478                 if (!glue_strings(safename, sizeof safename, prefix, hostname, '.'))
 479                         return NULL;
 480                 strcpy(hostname, safename);
 481         }
 482         if (suffix) {
 483                 if (!glue_strings(safename, sizeof safename, hostname, suffix, '.'))
 484                         return NULL;
 485         }
 486 
 487         return safename;
 488 }
 489 
 490 static void edit_cmd(void) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 491         char n[MAX_FNAME], q[MAX_TEMPSTR];
 492         const char *editor;
 493         FILE *f;
 494         int ch = '\0', t;
 495         struct stat statbuf;
 496         struct utimbuf utimebuf;
 497         WAIT_T waiter;
 498         PID_T pid, xpid;
 499 
 500         log_it(RealUser, Pid, "BEGIN EDIT", User, 0);
 501         if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
 502                 fprintf(stderr, "path too long\n");
 503                 exit(ERROR_EXIT);
 504         }
 505         if (!(f = fopen(n, "r"))) {
 506                 if (errno != ENOENT) {
 507                         perror(n);
 508                         exit(ERROR_EXIT);
 509                 }
 510                 fprintf(stderr, "no crontab for %s - using an empty one\n", User);
 511                 if (!(f = fopen(_PATH_DEVNULL, "r"))) {
 512                         perror(_PATH_DEVNULL);
 513                         exit(ERROR_EXIT);
 514                 }
 515         }
 516 
 517         /* Turn off signals. */
 518         (void) signal(SIGHUP, SIG_IGN);
 519         (void) signal(SIGINT, SIG_IGN);
 520         (void) signal(SIGQUIT, SIG_IGN);
 521 
 522         if (!glue_strings(Filename, sizeof Filename, tmp_path(),
 523                         "crontab.XXXXXX", '/')) {
 524                 fprintf(stderr, "path too long\n");
 525                 exit(ERROR_EXIT);
 526         }
 527         if (swap_uids() == -1) {
 528                 perror("swapping uids");
 529                 exit(ERROR_EXIT);
 530         }
 531         if (-1 == (t = mkstemp(Filename))) {
 532                 perror(Filename);
 533                 goto fatal;
 534         }
 535 
 536         if (swap_uids_back() == -1) {
 537                 perror("swapping uids back");
 538                 goto fatal;
 539         }
 540         if (!(NewCrontab = fdopen(t, "r+"))) {
 541                 perror("fdopen");
 542                 goto fatal;
 543         }
 544 
 545         Set_LineNum(1)
 546                 /* 
 547                  * NHEADER_LINES processing removed for clarity
 548                  * (NHEADER_LINES == 0 in all Red Hat crontabs)
 549                  */
 550                 /* copy the rest of the crontab (if any) to the temp file.
 551                  */
 552                 if (EOF != ch)
 553                 while (EOF != (ch = get_char(f)))
 554                         putc(ch, NewCrontab);
 555 
 556 #ifdef WITH_SELINUX
 557         if (selinux_context) {
 558                 context_t ccon = NULL;
 559                 const char *level = NULL;
 560 
 561                 if (!(ccon = context_new(selinux_context))) {
 562                         fprintf(stderr, "context_new failed\n");
 563                         goto fatal;
 564                 }
 565 
 566                 if (!(level = context_range_get(ccon))) {
 567                         fprintf(stderr, "context_range failed\n");
 568                         goto fatal;
 569                 }
 570 
 571                 fprintf(NewCrontab, "MLS_LEVEL=%s\n", level);
 572                 context_free(ccon);
 573                 freecon(selinux_context);
 574                 selinux_context = NULL;
 575         }
 576 #endif
 577 
 578         fclose(f);
 579         if (fflush(NewCrontab) < OK) {
 580                 perror(Filename);
 581                 exit(ERROR_EXIT);
 582         }
 583         if (swap_uids() == -1) {
 584                 perror("swapping uids");
 585                 exit(ERROR_EXIT);
 586         }
 587         /* Set it to 1970 */
 588         utimebuf.actime = 0;
 589         utimebuf.modtime = 0;
 590         utime(Filename, &utimebuf);
 591         if (swap_uids_back() == -1) {
 592                 perror("swapping uids");
 593                 exit(ERROR_EXIT);
 594         }
 595   again:
 596         rewind(NewCrontab);
 597         if (ferror(NewCrontab)) {
 598                 fprintf(stderr, "%s: error while writing new crontab to %s\n",
 599                         ProgramName, Filename);
 600           fatal:
 601                 unlink(Filename);
 602                 exit(ERROR_EXIT);
 603         }
 604 
 605         if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') &&
 606                 ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) {
 607                 editor = EDITOR;
 608         }
 609 
 610         /* we still have the file open.  editors will generally rewrite the
 611          * original file rather than renaming/unlinking it and starting a
 612          * new one; even backup files are supposed to be made by copying
 613          * rather than by renaming.  if some editor does not support this,
 614          * then don't use it.  the security problems are more severe if we
 615          * close and reopen the file around the edit.
 616          */
 617 
 618         switch (pid = fork()) {
 619         case -1:
 620                 perror("fork");
 621                 goto fatal;
 622         case 0:
 623                 /* child */
 624                 if (setgid(MY_GID(pw)) < 0) {
 625                         perror("setgid(getgid())");
 626                         exit(ERROR_EXIT);
 627                 }
 628                 if (setuid(MY_UID(pw)) < 0) {
 629                         perror("setuid(getuid())");
 630                         exit(ERROR_EXIT);
 631                 }
 632                 if (!glue_strings(q, sizeof q, editor, Filename, ' ')) {
 633                         fprintf(stderr, "%s: editor command line too long\n", ProgramName);
 634                         exit(ERROR_EXIT);
 635                 }
 636                 execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *) 0);
 637                 perror(editor);
 638                 exit(ERROR_EXIT);
 639          /*NOTREACHED*/ default:
 640                 /* parent */
 641                 break;
 642         }
 643 
 644         /* parent */
 645         for (;;) {
 646                 xpid = waitpid(pid, &waiter, 0);
 647                 if (xpid == -1) {
 648                         if (errno != EINTR)
 649                                 fprintf(stderr,
 650                                         "%s: waitpid() failed waiting for PID %ld from \"%s\": %s\n",
 651                                         ProgramName, (long) pid, editor, strerror(errno));
 652                 }
 653                 else if (xpid != pid) {
 654                         fprintf(stderr, "%s: wrong PID (%ld != %ld) from \"%s\"\n",
 655                                 ProgramName, (long) xpid, (long) pid, editor);
 656                         goto fatal;
 657                 }
 658                 else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) {
 659                         fprintf(stderr, "%s: \"%s\" exited with status %d\n",
 660                                 ProgramName, editor, WEXITSTATUS(waiter));
 661                         goto fatal;
 662                 }
 663                 else if (WIFSIGNALED(waiter)) {
 664                         fprintf(stderr,
 665                                 "%s: \"%s\" killed; signal %d (%score dumped)\n",
 666                                 ProgramName, editor, WTERMSIG(waiter),
 667                                 WCOREDUMP(waiter) ? "" : "no ");
 668                         goto fatal;
 669                 }
 670                 else
 671                         break;
 672         }
 673         (void) signal(SIGHUP, SIG_DFL);
 674         (void) signal(SIGINT, SIG_DFL);
 675         (void) signal(SIGQUIT, SIG_DFL);
 676 
 677         /* lstat doesn't make any harm, because 
 678          * the file is stat'ed only when crontab is touched
 679          */
 680         if (lstat(Filename, &statbuf) < 0) {
 681                 perror("lstat");
 682                 goto fatal;
 683         }
 684 
 685         if (!S_ISREG(statbuf.st_mode)) {
 686                 fprintf(stderr, "%s: illegal crontab\n", ProgramName);
 687                 goto remove;
 688         }
 689 
 690         if (statbuf.st_mtime == 0) {
 691                 fprintf(stderr, "%s: no changes made to crontab\n", ProgramName);
 692                 goto remove;
 693         }
 694 
 695         fprintf(stderr, "%s: installing new crontab\n", ProgramName);
 696         fclose(NewCrontab);
 697         if (swap_uids() < OK) {
 698                 perror("swapping uids");
 699                 goto remove;
 700         }
 701         if (!(NewCrontab = fopen(Filename, "r+"))) {
 702                 perror("cannot read new crontab");
 703                 goto remove;
 704         }
 705         if (swap_uids_back() < OK) {
 706                 perror("swapping uids back");
 707                 exit(ERROR_EXIT);
 708         }
 709         if (NewCrontab == 0L) {
 710                 perror("fopen");
 711                 goto fatal;
 712         }
 713         switch (replace_cmd()) {
 714         case 0:
 715                 break;
 716         case -1:
 717                 for (;;) {
 718                         printf("Do you want to retry the same edit? ");
 719                         fflush(stdout);
 720                         q[0] = '\0';
 721                         if (fgets(q, sizeof q, stdin) == 0L)
 722                                 continue;
 723                         switch (q[0]) {
 724                         case 'y':
 725                         case 'Y':
 726                                 goto again;
 727                         case 'n':
 728                         case 'N':
 729                                 goto abandon;
 730                         default:
 731                                 fprintf(stderr, "Enter Y or N\n");
 732                         }
 733                 }
 734          /*NOTREACHED*/ case -2:
 735           abandon:
 736                 fprintf(stderr, "%s: edits left in %s\n", ProgramName, Filename);
 737                 goto done;
 738         default:
 739                 fprintf(stderr, "%s: panic: bad switch() in replace_cmd()\n",
 740                         ProgramName);
 741                 goto fatal;
 742         }
 743   remove:
 744         unlink(Filename);
 745   done:
 746         log_it(RealUser, Pid, "END EDIT", User, 0);
 747 }
 748 
 749 /* returns      0       on success
 750  *              -1      on syntax error
 751  *              -2      on install error
 752  */
 753 static int replace_cmd(void) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 754         char n[MAX_FNAME], envstr[MAX_ENVSTR];
 755         FILE *tmp;
 756         int ch, eof, fd;
 757         int error = 0;
 758         entry *e;
 759         uid_t file_owner;
 760         char **envp;
 761         char *safename;
 762 
 763 
 764         safename = host_specific_filename("#tmp", "XXXXXXXXXX");
 765         if (!safename || !glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR,
 766                         safename, '/')) {
 767                 TempFilename[0] = '\0';
 768                 fprintf(stderr, "path too long\n");
 769                 return (-2);
 770         }
 771         if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w+"))) {
 772                 perror(TempFilename);
 773                 if (fd != -1) {
 774                         close(fd);
 775                         unlink(TempFilename);
 776                 }
 777                 TempFilename[0] = '\0';
 778                 return (-2);
 779         }
 780 
 781         (void) signal(SIGHUP, die);
 782         (void) signal(SIGINT, die);
 783         (void) signal(SIGQUIT, die);
 784 
 785         /* write a signature at the top of the file.
 786          *
 787          * VERY IMPORTANT: make sure NHEADER_LINES agrees with this code.
 788          */
 789         /*fprintf(tmp, "# DO NOT EDIT THIS FILE - edit the master and reinstall.\n");
 790          *fprintf(tmp, "# (%s installed on %-24.24s)\n", Filename, ctime(&now));
 791          *fprintf(tmp, "# (Cron version %s)\n", CRON_VERSION);
 792          */
 793 #ifdef WITH_SELINUX
 794         if (selinux_context)
 795                 fprintf(tmp, "SELINUX_ROLE_TYPE=%s\n", selinux_context);
 796 #endif
 797 
 798         /* copy the crontab to the tmp
 799          */
 800         rewind(NewCrontab);
 801         Set_LineNum(1)
 802                 while (EOF != (ch = get_char(NewCrontab)))
 803                 putc(ch, tmp);
 804         if (ftruncate(fileno(tmp), ftell(tmp)) == -1) {
 805                 fprintf(stderr, "%s: error while writing new crontab to %s\n",
 806                         ProgramName, TempFilename);
 807                 fclose(tmp);
 808                 error = -2;
 809                 goto done;
 810         }
 811         fflush(tmp);
 812         rewind(tmp);
 813         if (ferror(tmp)) {
 814                 fprintf(stderr, "%s: error while writing new crontab to %s\n",
 815                         ProgramName, TempFilename);
 816                 fclose(tmp);
 817                 error = -2;
 818                 goto done;
 819         }
 820 
 821         /* check the syntax of the file being installed.
 822          */
 823 
 824         /* BUG: was reporting errors after the EOF if there were any errors
 825          * in the file proper -- kludged it by stopping after first error.
 826          *      vix 31mar87
 827          */
 828         Set_LineNum(1 - NHEADER_LINES)
 829                 CheckErrorCount = 0;
 830         eof = FALSE;
 831 
 832         envp = env_init();
 833         if (envp == NULL) {
 834                 fprintf(stderr, "%s: Cannot allocate memory.\n", ProgramName);
 835                 fclose(tmp);
 836                 error = -2;
 837                 goto done;
 838         }
 839 
 840         while (!CheckErrorCount && !eof) {
 841                 switch (load_env(envstr, tmp)) {
 842                 case ERR:
 843                         /* check for data before the EOF */
 844                         if (envstr[0] != '\0') {
 845                                 Set_LineNum(LineNumber + 1);
 846                                 check_error("premature EOF");
 847                         }
 848                         eof = TRUE;
 849                         break;
 850                 case FALSE:
 851                         e = load_entry(tmp, check_error, pw, envp);
 852                         if (e)
 853                                 free_entry(e);
 854                         break;
 855                 case TRUE:
 856                         break;
 857                 }
 858         }
 859         env_free(envp);
 860 
 861         if (CheckErrorCount != 0) {
 862                 fprintf(stderr, "errors in crontab file, can't install.\n");
 863                 fclose(tmp);
 864                 error = -1;
 865                 goto done;
 866         }
 867 
 868         file_owner = (getgid() == geteuid() && getgid() == getegid()) ? ROOT_UID : pw->pw_uid;
 869 
 870 #ifdef HAVE_FCHOWN
 871         if (fchown(fileno(tmp), file_owner, (gid_t)-1) < OK) {
 872                 perror("fchown");
 873                 fclose(tmp);
 874                 error = -2;
 875                 goto done;
 876         }
 877 #else
 878         if (chown(TempFilename, file_owner, (gid_t)-1) < OK) {
 879                 perror("chown");
 880                 fclose(tmp);
 881                 error = -2;
 882                 goto done;
 883         }
 884 #endif
 885 
 886         if (fclose(tmp) == EOF) {
 887                 perror("fclose");
 888                 error = -2;
 889                 goto done;
 890         }
 891 
 892         if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) {
 893                 fprintf(stderr, "path too long\n");
 894                 error = -2;
 895                 goto done;
 896         }
 897         if (rename(TempFilename, n)) {
 898                 fprintf(stderr, "%s: error renaming %s to %s\n",
 899                         ProgramName, TempFilename, n);
 900                 perror("rename");
 901                 error = -2;
 902                 goto done;
 903         }
 904         TempFilename[0] = '\0';
 905         log_it(RealUser, Pid, "REPLACE", User, 0);
 906 
 907         poke_daemon();
 908 
 909   done:
 910         (void) signal(SIGHUP, SIG_DFL);
 911         (void) signal(SIGINT, SIG_DFL);
 912         (void) signal(SIGQUIT, SIG_DFL);
 913         if (TempFilename[0]) {
 914                 (void) unlink(TempFilename);
 915                 TempFilename[0] = '\0';
 916         }
 917         return (error);
 918 }
 919 
 920 static int hostset_cmd(void) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 921         char n[MAX_FNAME];
 922         FILE *tmp;
 923         int fd;
 924         int error = 0;
 925         char *safename;
 926         
 927         if (!HostSpecified)
 928                 gethostname(Host, sizeof Host);
 929         
 930         safename = host_specific_filename("#tmp", "XXXXXXXXXX");
 931         if (!safename || !glue_strings(TempFilename, sizeof TempFilename, SPOOL_DIR,
 932                         safename, '/')) {
 933                 TempFilename[0] = '\0';
 934                 fprintf(stderr, "path too long\n");
 935                 return (-2);
 936         }
 937         if ((fd = mkstemp(TempFilename)) == -1 || !(tmp = fdopen(fd, "w"))) {
 938                 perror(TempFilename);
 939                 if (fd != -1) {
 940                         close(fd);
 941                         unlink(TempFilename);
 942                 }
 943                 TempFilename[0] = '\0';
 944                 return (-2);
 945         }
 946 
 947         (void) signal(SIGHUP, die);
 948         (void) signal(SIGINT, die);
 949         (void) signal(SIGQUIT, die);
 950 
 951         (void) fchmod(fd, 0600); /* not all mkstemp() implementations do this */
 952 
 953         if (fprintf(tmp, "%s\n", Host) < 0 || fclose(tmp) == EOF) {
 954                 fprintf(stderr, "%s: error while writing to %s\n",
 955                         ProgramName, TempFilename);
 956                 error = -2;
 957                 goto done;
 958         }
 959 
 960         if (!glue_strings(n, sizeof n, SPOOL_DIR, CRON_HOSTNAME, '/')) {
 961                 fprintf(stderr, "path too long\n");
 962                 error = -2;
 963                 goto done;
 964         }
 965 
 966         if (rename(TempFilename, n)) {
 967                 fprintf(stderr, "%s: error renaming %s to %s\n",
 968                         ProgramName, TempFilename, n);
 969                 perror("rename");
 970                 error = -2;
 971                 goto done;
 972         }
 973         TempFilename[0] = '\0';
 974         log_it(RealUser, Pid, "SET HOST", Host, 0);
 975 
 976         poke_daemon();
 977 
 978   done:
 979         (void) signal(SIGHUP, SIG_DFL);
 980         (void) signal(SIGINT, SIG_DFL);
 981         (void) signal(SIGQUIT, SIG_DFL);
 982         if (TempFilename[0]) {
 983                 (void) unlink(TempFilename);
 984                 TempFilename[0] = '\0';
 985         }
 986         return (error);
 987 }
 988 
 989 static int hostget_cmd(void) {
     /* [previous][next][first][last][top][bottom][index][help]  */
 990         char n[MAX_FNAME];
 991         FILE *f;
 992 
 993         if (!glue_strings(n, sizeof n, SPOOL_DIR, CRON_HOSTNAME, '/')) {
 994                 fprintf(stderr, "path too long\n");
 995                 return (-2);
 996         }
 997 
 998         if (!(f = fopen(n, "r"))) {
 999                 if (errno == ENOENT)
1000                         fprintf(stderr, "File %s not found\n", n);
1001                 else
1002                         perror(n);
1003                 return (-2);
1004         }
1005 
1006         if (get_string(Host, sizeof Host, f, "\n") == EOF) {
1007                 fprintf(stderr, "Error reading from %s\n", n);
1008                 fclose(f);
1009                 return (-2);
1010         }
1011 
1012         fclose(f);
1013 
1014         printf("%s\n", Host);
1015         fflush(stdout);
1016 
1017         log_it(RealUser, Pid, "GET HOST", Host, 0);
1018         return (0);
1019 }
1020 
1021 static void poke_daemon(void) {
     /* [previous][next][first][last][top][bottom][index][help]  */
1022         if (utime(SPOOL_DIR, NULL) < OK) {
1023                 fprintf(stderr, "crontab: can't update mtime on spooldir\n");
1024                 perror(SPOOL_DIR);
1025                 return;
1026         }
1027 }
1028 
1029 static void die(int x) {
     /* [previous][next][first][last][top][bottom][index][help]  */
1030         if (TempFilename[0])
1031                 (void) unlink(TempFilename);
1032         _exit(ERROR_EXIT);
1033 }

/* [previous][next][first][last][top][bottom][index][help]  */