crontab.c revision 8998:77d724ac3209
1296417Sdim/* 2254721Semaste * CDDL HEADER START 3254721Semaste * 4254721Semaste * The contents of this file are subject to the terms of the 5254721Semaste * Common Development and Distribution License (the "License"). 6254721Semaste * You may not use this file except in compliance with the License. 7254721Semaste * 8254721Semaste * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9254721Semaste * or http://www.opensolaris.org/os/licensing. 10254721Semaste * See the License for the specific language governing permissions 11254721Semaste * and limitations under the License. 12254721Semaste * 13296417Sdim * When distributing Covered Code, include this CDDL HEADER in each 14296417Sdim * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15254721Semaste * If applicable, add the following below this CDDL HEADER, with the 16254721Semaste * fields enclosed by brackets "[]" replaced with your own identifying 17296417Sdim * information: Portions Copyright [yyyy] [name of copyright owner] 18296417Sdim * 19254721Semaste * CDDL HEADER END 20254721Semaste */ 21254721Semaste/* 22254721Semaste * Copyright 2009 Sun Microsystems, Inc. All rights reserved. 23254721Semaste * Use is subject to license terms. 24254721Semaste */ 25254721Semaste/* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 26254721Semaste/* All Rights Reserved */ 27254721Semaste 28254721Semaste 29254721Semaste#include <sys/types.h> 30254721Semaste#include <sys/stat.h> 31254721Semaste#include <sys/types.h> 32254721Semaste#include <sys/wait.h> 33254721Semaste#include <errno.h> 34254721Semaste#include <signal.h> 35254721Semaste#include <stdio.h> 36258054Semaste#include <stdlib.h> 37258054Semaste#include <string.h> 38258054Semaste#include <fcntl.h> 39258054Semaste#include <ctype.h> 40254721Semaste#include <pwd.h> 41254721Semaste#include <unistd.h> 42254721Semaste#include <locale.h> 43254721Semaste#include <nl_types.h> 44254721Semaste#include <langinfo.h> 45254721Semaste#include <libintl.h> 46254721Semaste#include <spawn.h> 47276479Sdim#include <security/pam_appl.h> 48254721Semaste#include <limits.h> 49254721Semaste#include <libzoneinfo.h> 50254721Semaste#include "cron.h" 51254721Semaste#include "getresponse.h" 52254721Semaste 53254721Semaste#if defined(XPG4) 54254721Semaste#define VIPATH "/usr/xpg4/bin/vi" 55254721Semaste#elif defined(XPG6) 56254721Semaste#define VIPATH "/usr/xpg6/bin/vi" 57254721Semaste#else 58254721Semaste#define _XPG_NOTDEFINED 59254721Semaste#define VIPATH "vi" 60254721Semaste#endif 61254721Semaste 62254721Semaste#define TMPFILE "_cron" /* prefix for tmp file */ 63254721Semaste#define CRMODE 0600 /* mode for creating crontabs */ 64254721Semaste 65254721Semaste#define BADCREATE \ 66254721Semaste "can't create your crontab file in the crontab directory." 67254721Semaste#define BADOPEN "can't open your crontab file." 68254721Semaste#define BADSHELL \ 69254721Semaste "because your login shell isn't /usr/bin/sh, you can't use cron." 70254721Semaste#define WARNSHELL "warning: commands will be executed using /usr/bin/sh\n" 71254721Semaste#define BADUSAGE \ 72254721Semaste "usage:\n" \ 73254721Semaste "\tcrontab [file]\n" \ 74254721Semaste "\tcrontab -e [username]\n" \ 75254721Semaste "\tcrontab -l [username]\n" \ 76254721Semaste "\tcrontab -r [username]" 77254721Semaste#define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)." 78254721Semaste#define NOTALLOWED "you are not authorized to use cron. Sorry." 79254721Semaste#define NOTROOT \ 80254721Semaste "you must be super-user to access another user's crontab file" 81254721Semaste#define AUDITREJECT "The audit context for your shell has not been set." 82254721Semaste#define EOLN "unexpected end of line." 83254721Semaste#define UNEXPECT "unexpected character found in line." 84254721Semaste#define OUTOFBOUND "number out of bounds." 85254721Semaste#define ERRSFND "errors detected in input, no crontab file generated." 86254721Semaste#define ED_ERROR \ 87254721Semaste " The editor indicates that an error occurred while you were\n"\ 88254721Semaste " editing the crontab data - usually a minor typing error.\n\n" 89254721Semaste#define BADREAD "error reading your crontab file" 90254721Semaste#define ED_PROMPT \ 91254721Semaste " Edit again, to ensure crontab information is intact (%s/%s)?\n"\ 92254721Semaste " ('%s' will discard edits.)" 93254721Semaste#define NAMETOOLONG "login name too long" 94254721Semaste#define BAD_TZ "Timezone unrecognized in: %s" 95254721Semaste#define BAD_SHELL "Invalid shell specified: %s" 96254721Semaste#define BAD_HOME "Unable to access directory: %s\t%s\n" 97254721Semaste 98254721Semasteextern int per_errno; 99254721Semasteextern char **environ; 100254721Semaste 101254721Semasteextern int audit_crontab_modify(char *, char *, int); 102254721Semasteextern int audit_crontab_delete(char *, int); 103254721Semasteextern int audit_crontab_not_allowed(uid_t, char *); 104254721Semaste 105254721Semasteint err; 106254721Semasteint cursor; 107254721Semastechar *cf; 108254721Semastechar *tnam; 109254721Semastechar edtemp[5+13+1]; 110254721Semastechar line[CTLINESIZE]; 111254721Semastestatic char login[UNAMESIZE]; 112254721Semaste 113254721Semastestatic int next_field(int, int); 114254721Semastestatic void catch(int); 115254721Semastestatic void crabort(char *); 116254721Semastestatic void cerror(char *); 117288943Sdimstatic void copycron(FILE *); 118296417Sdim 119254721Semasteint 120254721Semastemain(int argc, char **argv) 121254721Semaste{ 122254721Semaste int c, r; 123254721Semaste int rflag = 0; 124254721Semaste int lflag = 0; 125254721Semaste int eflag = 0; 126296417Sdim int errflg = 0; 127254721Semaste char *pp; 128296417Sdim FILE *fp, *tmpfp; 129254721Semaste struct stat stbuf; 130296417Sdim struct passwd *pwp; 131296417Sdim time_t omodtime; 132254721Semaste char *editor; 133296417Sdim uid_t ruid; 134296417Sdim pid_t pid; 135254721Semaste int stat_loc; 136296417Sdim int ret; 137296417Sdim char real_login[UNAMESIZE]; 138254721Semaste int tmpfd = -1; 139296417Sdim pam_handle_t *pamh; 140296417Sdim int pam_error; 141254721Semaste pid_t cpid; 142254721Semaste int cstatus; 143254721Semaste char *argvec[3]; 144254721Semaste 145254721Semaste (void) setlocale(LC_ALL, ""); 146254721Semaste#if !defined(TEXT_DOMAIN) /* Should be defined by cc -D */ 147254721Semaste#define TEXT_DOMAIN "SYS_TEST" /* Use this only if it weren't */ 148296417Sdim#endif 149296417Sdim (void) textdomain(TEXT_DOMAIN); 150254721Semaste 151254721Semaste if (init_yes() < 0) { 152254721Semaste (void) fprintf(stderr, gettext(ERR_MSG_INIT_YES), 153254721Semaste strerror(errno)); 154254721Semaste exit(1); 155254721Semaste } 156254721Semaste 157296417Sdim while ((c = getopt(argc, argv, "elr")) != EOF) 158254721Semaste switch (c) { 159254721Semaste case 'e': 160254721Semaste eflag++; 161254721Semaste break; 162254721Semaste case 'l': 163254721Semaste lflag++; 164254721Semaste break; 165254721Semaste case 'r': 166254721Semaste rflag++; 167254721Semaste break; 168254721Semaste case '?': 169254721Semaste errflg++; 170254721Semaste break; 171254721Semaste } 172254721Semaste 173254721Semaste if (eflag + lflag + rflag > 1) 174254721Semaste errflg++; 175254721Semaste 176288943Sdim argc -= optind; 177254721Semaste argv += optind; 178296417Sdim if (errflg || argc > 1) 179254721Semaste crabort(BADUSAGE); 180254721Semaste 181296417Sdim ruid = getuid(); 182 if ((pwp = getpwuid(ruid)) == NULL) 183 crabort(INVALIDUSER); 184 185 if (strlcpy(real_login, pwp->pw_name, sizeof (real_login)) 186 >= sizeof (real_login)) 187 crabort(NAMETOOLONG); 188 189 if ((eflag || lflag || rflag) && argc == 1) { 190 if ((pwp = getpwnam(*argv)) == NULL) 191 crabort(INVALIDUSER); 192 193 if (!chkauthattr(CRONADMIN_AUTH, real_login)) { 194 if (pwp->pw_uid != ruid) 195 crabort(NOTROOT); 196 else 197 pp = getuser(ruid); 198 } else 199 pp = *argv++; 200 } else { 201 pp = getuser(ruid); 202 } 203 204 if (pp == NULL) { 205 if (per_errno == 2) 206 crabort(BADSHELL); 207 else 208 crabort(INVALIDUSER); 209 } 210 if (strlcpy(login, pp, sizeof (login)) >= sizeof (login)) 211 crabort(NAMETOOLONG); 212 if (!allowed(login, CRONALLOW, CRONDENY)) 213 crabort(NOTALLOWED); 214 215 /* Do account validation check */ 216 pam_error = pam_start("cron", pp, NULL, &pamh); 217 if (pam_error != PAM_SUCCESS) { 218 crabort((char *)pam_strerror(pamh, pam_error)); 219 } 220 pam_error = pam_acct_mgmt(pamh, PAM_SILENT); 221 if (pam_error != PAM_SUCCESS) { 222 (void) fprintf(stderr, gettext("Warning - Invalid account: " 223 "'%s' not allowed to execute cronjobs\n"), pp); 224 } 225 (void) pam_end(pamh, PAM_SUCCESS); 226 227 228 /* check for unaudited shell */ 229 if (audit_crontab_not_allowed(ruid, pp)) 230 crabort(AUDITREJECT); 231 232 cf = xmalloc(strlen(CRONDIR)+strlen(login)+2); 233 strcat(strcat(strcpy(cf, CRONDIR), "/"), login); 234 235 if (rflag) { 236 r = unlink(cf); 237 cron_sendmsg(DELETE, login, login, CRON); 238 audit_crontab_delete(cf, r); 239 exit(0); 240 } 241 if (lflag) { 242 if ((fp = fopen(cf, "r")) == NULL) 243 crabort(BADOPEN); 244 while (fgets(line, CTLINESIZE, fp) != NULL) 245 fputs(line, stdout); 246 fclose(fp); 247 exit(0); 248 } 249 if (eflag) { 250 if ((fp = fopen(cf, "r")) == NULL) { 251 if (errno != ENOENT) 252 crabort(BADOPEN); 253 } 254 (void) strcpy(edtemp, "/tmp/crontabXXXXXX"); 255 tmpfd = mkstemp(edtemp); 256 if (fchown(tmpfd, ruid, -1) == -1) { 257 (void) close(tmpfd); 258 crabort("fchown of temporary file failed"); 259 } 260 (void) close(tmpfd); 261 /* 262 * Fork off a child with user's permissions, 263 * to edit the crontab file 264 */ 265 if ((pid = fork()) == (pid_t)-1) 266 crabort("fork failed"); 267 if (pid == 0) { /* child process */ 268 /* give up super-user privileges. */ 269 setuid(ruid); 270 if ((tmpfp = fopen(edtemp, "w")) == NULL) 271 crabort("can't create temporary file"); 272 if (fp != NULL) { 273 /* 274 * Copy user's crontab file to temporary file. 275 */ 276 while (fgets(line, CTLINESIZE, fp) != NULL) { 277 fputs(line, tmpfp); 278 if (ferror(tmpfp)) { 279 fclose(fp); 280 fclose(tmpfp); 281 crabort("write error on" 282 "temporary file"); 283 } 284 } 285 if (ferror(fp)) { 286 fclose(fp); 287 fclose(tmpfp); 288 crabort(BADREAD); 289 } 290 fclose(fp); 291 } 292 if (fclose(tmpfp) == EOF) 293 crabort("write error on temporary file"); 294 if (stat(edtemp, &stbuf) < 0) 295 crabort("can't stat temporary file"); 296 omodtime = stbuf.st_mtime; 297#ifdef _XPG_NOTDEFINED 298 editor = getenv("VISUAL"); 299 if (editor == NULL) { 300#endif 301 editor = getenv("EDITOR"); 302 if (editor == NULL) 303 editor = VIPATH; 304#ifdef _XPG_NOTDEFINED 305 } 306#endif 307 argvec[0] = strdup(editor); 308 argvec[1] = strdup(edtemp); 309 argvec[2] = NULL; 310 311 if (argvec[0] == NULL || argvec[1] == NULL) 312 crabort("Insufficient memory"); 313 314 sleep(1); 315 316 while (1) { 317 /* 318 * posix_spawnp() allows the file pointed to 319 * by the 'EDITOR' variable to be searched in 320 * the PATH environment variable 321 */ 322 323 ret = posix_spawnp(&cpid, editor, NULL, NULL, 324 (char *const *)argvec, 325 (char *const *)environ); 326 if (ret) { 327 (void) fprintf(stderr, 328 gettext("crontab: %s: %s\n"), 329 editor, strerror(errno)); 330 cstatus = -1; 331 } else { 332 pid_t wpid = 0; 333 while ((wpid = waitpid(cpid, &cstatus, 334 0)) == -1 && errno == EINTR) 335 ; 336 if (wpid == -1) 337 cstatus = -1; 338 } 339 340 /* sanity checks */ 341 if ((tmpfp = fopen(edtemp, "r")) == NULL) 342 crabort("can't open temporary file"); 343 if (fstat(fileno(tmpfp), &stbuf) < 0) 344 crabort("can't stat temporary file"); 345 if (stbuf.st_size == 0) 346 crabort("temporary file empty"); 347 if (omodtime == stbuf.st_mtime) { 348 (void) unlink(edtemp); 349 fprintf(stderr, gettext( 350 "The crontab file was not" 351 " changed.\n")); 352 exit(1); 353 } 354 if ((cstatus) && (errno != EINTR)) { 355 /* 356 * Some editors (like 'vi') can return 357 * a non-zero exit status even though 358 * everything is okay. Need to check. 359 */ 360 fprintf(stderr, gettext(ED_ERROR)); 361 fflush(stderr); 362 if (isatty(fileno(stdin))) { 363 /* Interactive */ 364 fprintf(stdout, 365 gettext(ED_PROMPT), 366 yesstr, nostr, nostr); 367 fflush(stdout); 368 369 if (yes()) { 370 /* Edit again */ 371 continue; 372 } else { 373 /* Dump changes */ 374 (void) unlink(edtemp); 375 exit(1); 376 } 377 } else { 378 /* 379 * Non-interactive, dump changes 380 */ 381 (void) unlink(edtemp); 382 exit(1); 383 } 384 } 385 exit(0); 386 } /* while (1) */ 387 } 388 389 /* fix for 1125555 - ignore common signals while waiting */ 390 (void) signal(SIGINT, SIG_IGN); 391 (void) signal(SIGHUP, SIG_IGN); 392 (void) signal(SIGQUIT, SIG_IGN); 393 (void) signal(SIGTERM, SIG_IGN); 394 wait(&stat_loc); 395 if ((stat_loc & 0xFF00) != 0) 396 exit(1); 397 398 /* 399 * unlink edtemp as 'ruid'. The file contents will be held 400 * since we open the file descriptor 'tmpfp' before calling 401 * unlink. 402 */ 403 if (((ret = seteuid(ruid)) < 0) || 404 ((tmpfp = fopen(edtemp, "r")) == NULL) || 405 (unlink(edtemp) == -1)) { 406 fprintf(stderr, "crontab: %s: %s\n", 407 edtemp, errmsg(errno)); 408 if ((ret < 0) || (tmpfp == NULL)) 409 (void) unlink(edtemp); 410 exit(1); 411 } else 412 seteuid(0); 413 414 copycron(tmpfp); 415 } else { 416 if (argc == 0) 417 copycron(stdin); 418 else if (seteuid(getuid()) != 0 || (fp = fopen(argv[0], "r")) 419 == NULL) 420 crabort(BADOPEN); 421 else { 422 seteuid(0); 423 copycron(fp); 424 } 425 } 426 cron_sendmsg(ADD, login, login, CRON); 427/* 428 * if (per_errno == 2) 429 * fprintf(stderr, gettext(WARNSHELL)); 430 */ 431 return (0); 432} 433 434static void 435copycron(fp) 436FILE *fp; 437{ 438 FILE *tfp; 439 char pid[6], *tnam_end; 440 int t; 441 char buf[LINE_MAX]; 442 443 sprintf(pid, "%-5d", getpid()); 444 tnam = xmalloc(strlen(CRONDIR)+strlen(TMPFILE)+7); 445 strcat(strcat(strcat(strcpy(tnam, CRONDIR), "/"), TMPFILE), pid); 446 /* cut trailing blanks */ 447 tnam_end = strchr(tnam, ' '); 448 if (tnam_end != NULL) 449 *tnam_end = 0; 450 /* catch SIGINT, SIGHUP, SIGQUIT signals */ 451 if (signal(SIGINT, catch) == SIG_IGN) 452 signal(SIGINT, SIG_IGN); 453 if (signal(SIGHUP, catch) == SIG_IGN) signal(SIGHUP, SIG_IGN); 454 if (signal(SIGQUIT, catch) == SIG_IGN) signal(SIGQUIT, SIG_IGN); 455 if (signal(SIGTERM, catch) == SIG_IGN) signal(SIGTERM, SIG_IGN); 456 if ((t = creat(tnam, CRMODE)) == -1) crabort(BADCREATE); 457 if ((tfp = fdopen(t, "w")) == NULL) { 458 unlink(tnam); 459 crabort(BADCREATE); 460 } 461 err = 0; /* if errors found, err set to 1 */ 462 while (fgets(line, CTLINESIZE, fp) != NULL) { 463 cursor = 0; 464 while (line[cursor] == ' ' || line[cursor] == '\t') 465 cursor++; 466 /* fix for 1039689 - treat blank line like a comment */ 467 if (line[cursor] == '#' || line[cursor] == '\n') 468 goto cont; 469 470 if (strncmp(&line[cursor], ENV_TZ, strlen(ENV_TZ)) == 0) { 471 char *x; 472 473 strncpy(buf, &line[cursor + strlen(ENV_TZ)], 474 sizeof (buf)); 475 if ((x = strchr(buf, '\n')) != NULL) 476 *x = NULL; 477 478 if (isvalid_tz(buf, NULL, _VTZ_ALL)) { 479 goto cont; 480 } else { 481 err = 1; 482 fprintf(stderr, BAD_TZ, &line[cursor]); 483 continue; 484 } 485 } else if (strncmp(&line[cursor], ENV_SHELL, 486 strlen(ENV_SHELL)) == 0) { 487 char *x; 488 489 strncpy(buf, &line[cursor + strlen(ENV_SHELL)], 490 sizeof (buf)); 491 if ((x = strchr(buf, '\n')) != NULL) 492 *x = NULL; 493 494 if (isvalid_shell(buf)) { 495 goto cont; 496 } else { 497 err = 1; 498 fprintf(stderr, BAD_SHELL, &line[cursor]); 499 continue; 500 } 501 } else if (strncmp(&line[cursor], ENV_HOME, 502 strlen(ENV_HOME)) == 0) { 503 char *x; 504 505 strncpy(buf, &line[cursor + strlen(ENV_HOME)], 506 sizeof (buf)); 507 if ((x = strchr(buf, '\n')) != NULL) 508 *x = NULL; 509 if (chdir(buf) == 0) { 510 goto cont; 511 } else { 512 err = 1; 513 fprintf(stderr, BAD_HOME, &line[cursor], 514 strerror(errno)); 515 continue; 516 } 517 } 518 519 if (next_field(0, 59)) continue; 520 if (next_field(0, 23)) continue; 521 if (next_field(1, 31)) continue; 522 if (next_field(1, 12)) continue; 523 if (next_field(0, 06)) continue; 524 if (line[++cursor] == '\0') { 525 cerror(EOLN); 526 continue; 527 } 528cont: 529 if (fputs(line, tfp) == EOF) { 530 unlink(tnam); 531 crabort(BADCREATE); 532 } 533 } 534 fclose(fp); 535 fclose(tfp); 536 537 /* audit differences between old and new crontabs */ 538 audit_crontab_modify(cf, tnam, err); 539 540 if (!err) { 541 /* make file tfp the new crontab */ 542 unlink(cf); 543 if (link(tnam, cf) == -1) { 544 unlink(tnam); 545 crabort(BADCREATE); 546 } 547 } else { 548 crabort(ERRSFND); 549 } 550 unlink(tnam); 551} 552 553static int 554next_field(lower, upper) 555int lower, upper; 556{ 557 int num, num2; 558 559 while ((line[cursor] == ' ') || (line[cursor] == '\t')) cursor++; 560 if (line[cursor] == '\0') { 561 cerror(EOLN); 562 return (1); 563 } 564 if (line[cursor] == '*') { 565 cursor++; 566 if ((line[cursor] != ' ') && (line[cursor] != '\t')) { 567 cerror(UNEXPECT); 568 return (1); 569 } 570 return (0); 571 } 572 while (TRUE) { 573 if (!isdigit(line[cursor])) { 574 cerror(UNEXPECT); 575 return (1); 576 } 577 num = 0; 578 do { 579 num = num*10 + (line[cursor]-'0'); 580 } while (isdigit(line[++cursor])); 581 if ((num < lower) || (num > upper)) { 582 cerror(OUTOFBOUND); 583 return (1); 584 } 585 if (line[cursor] == '-') { 586 if (!isdigit(line[++cursor])) { 587 cerror(UNEXPECT); 588 return (1); 589 } 590 num2 = 0; 591 do { 592 num2 = num2*10 + (line[cursor]-'0'); 593 } while (isdigit(line[++cursor])); 594 if ((num2 < lower) || (num2 > upper)) { 595 cerror(OUTOFBOUND); 596 return (1); 597 } 598 } 599 if ((line[cursor] == ' ') || (line[cursor] == '\t')) break; 600 if (line[cursor] == '\0') { 601 cerror(EOLN); 602 return (1); 603 } 604 if (line[cursor++] != ',') { 605 cerror(UNEXPECT); 606 return (1); 607 } 608 } 609 return (0); 610} 611 612static void 613cerror(msg) 614char *msg; 615{ 616 fprintf(stderr, gettext("%scrontab: error on previous line; %s\n"), 617 line, msg); 618 err = 1; 619} 620 621 622static void 623catch(int x) 624{ 625 unlink(tnam); 626 exit(1); 627} 628 629static void 630crabort(msg) 631char *msg; 632{ 633 int sverrno; 634 635 if (strcmp(edtemp, "") != 0) { 636 sverrno = errno; 637 (void) unlink(edtemp); 638 errno = sverrno; 639 } 640 if (tnam != NULL) { 641 sverrno = errno; 642 (void) unlink(tnam); 643 errno = sverrno; 644 } 645 fprintf(stderr, "crontab: %s\n", gettext(msg)); 646 exit(1); 647} 648