1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2008 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 /* Copyright (c) 1984, 1986, 1987, 1988, 1989 AT&T */ 27 /* All Rights Reserved */ 28 29 /* Copyright (c) 1987, 1988 Microsoft Corporation */ 30 /* All Rights Reserved */ 31 32 #pragma ident "%Z%%M% %I% %E% SMI" 33 34 #ifdef lint 35 /* make lint happy */ 36 #define __EXTENSIONS__ 37 #endif 38 39 #include <sys/contract/process.h> 40 #include <sys/ctfs.h> 41 #include <sys/param.h> 42 #include <sys/resource.h> 43 #include <sys/stat.h> 44 #include <sys/task.h> 45 #include <sys/time.h> 46 #include <sys/types.h> 47 #include <sys/utsname.h> 48 #include <sys/wait.h> 49 50 #include <security/pam_appl.h> 51 52 #include <alloca.h> 53 #include <ctype.h> 54 #include <deflt.h> 55 #include <dirent.h> 56 #include <errno.h> 57 #include <fcntl.h> 58 #include <grp.h> 59 #include <libcontract.h> 60 #include <libcontract_priv.h> 61 #include <limits.h> 62 #include <locale.h> 63 #include <poll.h> 64 #include <project.h> 65 #include <pwd.h> 66 #include <signal.h> 67 #include <stdarg.h> 68 #include <stdio.h> 69 #include <stdlib.h> 70 #include <string.h> 71 #include <stropts.h> 72 #include <time.h> 73 #include <unistd.h> 74 75 #include "cron.h" 76 77 /* 78 * #define DEBUG 79 */ 80 81 #define MAIL "/usr/bin/mail" /* mail program to use */ 82 #define CONSOLE "/dev/console" /* where messages go when cron dies */ 83 84 #define TMPINFILE "/tmp/crinXXXXXX" /* file to put stdin in for cmd */ 85 #define TMPDIR "/tmp" 86 #define PFX "crout" 87 #define TMPOUTFILE "/tmp/croutXXXXXX" /* file to place stdout, stderr */ 88 89 #define INMODE 00400 /* mode for stdin file */ 90 #define OUTMODE 00600 /* mode for stdout file */ 91 #define ISUID S_ISUID /* mode for verifing at jobs */ 92 93 #define INFINITY 2147483647L /* upper bound on time */ 94 #define CUSHION 180L 95 #define ZOMB 100 /* proc slot used for mailing output */ 96 97 #define JOBF 'j' 98 #define NICEF 'n' 99 #define USERF 'u' 100 #define WAITF 'w' 101 102 #define BCHAR '>' 103 #define ECHAR '<' 104 105 #define DEFAULT 0 106 #define LOAD 1 107 #define QBUFSIZ 80 108 109 /* Defined actions for crabort() routine */ 110 #define NO_ACTION 000 111 #define REMOVE_FIFO 001 112 #define CONSOLE_MSG 002 113 114 #define BADCD "can't change directory to the crontab directory." 115 #define NOREADDIR "can't read the crontab directory." 116 117 #define BADJOBOPEN "unable to read your at job." 118 #define BADSHELL "because your login shell \ 119 isn't /usr/bin/sh, you can't use cron." 120 121 #define BADSTAT "can't access your crontab or at-job file. Resubmit it." 122 #define BADPROJID "can't set project id for your job." 123 #define CANTCDHOME "can't change directory to your home directory.\ 124 \nYour commands will not be executed." 125 #define CANTEXECSH "unable to exec the shell for one of your commands." 126 #define NOREAD "can't read your crontab file. Resubmit it." 127 #define BADTYPE "crontab or at-job file is not a regular file.\n" 128 #define NOSTDIN "unable to create a standard input file for \ 129 one of your crontab commands. \ 130 \nThat command was not executed." 131 132 #define NOTALLOWED "you are not authorized to use cron. Sorry." 133 #define STDERRMSG "\n\n********************************************\ 134 *****\nCron: The previous message is the \ 135 standard output and standard error \ 136 \nof one of your cron commands.\n" 137 138 #define STDOUTERR "one of your commands generated output or errors, \ 139 but cron was unable to mail you this output.\ 140 \nRemember to redirect standard output and standard \ 141 error for each of your commands." 142 143 #define CLOCK_DRIFT "clock time drifted backwards after event!\n" 144 #define PIDERR "unexpected pid returned %d (ignored)" 145 #define CRONTABERR "Subject: Your crontab file has an error in it\n\n" 146 #define CRONOUT "Subject: Output from \"cron\" command\n\n" 147 #define MALLOCERR "out of space, cannot create new string\n" 148 149 #define DIDFORK didfork 150 #define NOFORK !didfork 151 152 #define MAILBUFLEN (8*1024) 153 #define LINELIMIT 80 154 #define MAILBINITFREE (MAILBUFLEN - (sizeof (cte_intro) - 1) \ 155 - (sizeof (cte_trail1) - 1) - (sizeof (cte_trail2) - 1) - 1) 156 157 #define ERR_CRONTABENT 0 /* error in crontab file entry */ 158 #define ERR_UNIXERR 1 /* error in some system call */ 159 #define ERR_CANTEXECCRON 2 /* error setting up "cron" job environment */ 160 #define ERR_CANTEXECAT 3 /* error setting up "at" job environment */ 161 #define ERR_NOTREG 4 /* error not a regular file */ 162 163 #define PROJECT "project=" 164 165 #define MAX_LOST_CONTRACTS 2048 /* reset if this many failed abandons */ 166 167 #define FORMAT "%a %b %e %H:%M:%S %Y" 168 static char timebuf[80]; 169 170 struct event { 171 time_t time; /* time of the event */ 172 short etype; /* what type of event; 0=cron, 1=at */ 173 char *cmd; /* command for cron, job name for at */ 174 struct usr *u; /* ptr to the owner (usr) of this event */ 175 struct event *link; /* ptr to another event for this user */ 176 union { 177 struct { /* for crontab events */ 178 char *minute; /* (these */ 179 char *hour; /* fields */ 180 char *daymon; /* are */ 181 char *month; /* from */ 182 char *dayweek; /* crontab) */ 183 char *input; /* ptr to stdin */ 184 } ct; 185 struct { /* for at events */ 186 short exists; /* for revising at events */ 187 int eventid; /* for el_remove-ing at events */ 188 } at; 189 } of; 190 }; 191 192 struct usr { 193 char *name; /* name of user (e.g. "root") */ 194 char *home; /* home directory for user */ 195 uid_t uid; /* user id */ 196 gid_t gid; /* group id */ 197 int aruncnt; /* counter for running jobs per uid */ 198 int cruncnt; /* counter for running cron jobs per uid */ 199 int ctid; /* for el_remove-ing crontab events */ 200 short ctexists; /* for revising crontab events */ 201 struct event *ctevents; /* list of this usr's crontab events */ 202 struct event *atevents; /* list of this usr's at events */ 203 struct usr *nextusr; 204 }; /* ptr to next user */ 205 206 static struct queue 207 { 208 int njob; /* limit */ 209 int nice; /* nice for execution */ 210 int nwait; /* wait time to next execution attempt */ 211 int nrun; /* number running */ 212 } 213 qd = {100, 2, 60}, /* default values for queue defs */ 214 qt[NQUEUE]; 215 static struct queue qq; 216 217 static struct runinfo 218 { 219 pid_t pid; 220 short que; 221 struct usr *rusr; /* pointer to usr struct */ 222 char *outfile; /* file where stdout & stderr are trapped */ 223 short jobtype; /* what type of event: 0=cron, 1=at */ 224 char *jobname; /* command for "cron", jobname for "at" */ 225 int mailwhendone; /* 1 = send mail even if no ouptut */ 226 struct runinfo *next; 227 } *rthead; 228 229 static struct miscpid { 230 pid_t pid; 231 struct miscpid *next; 232 } *miscpid_head; 233 234 static pid_t cron_pid; /* own pid */ 235 static char didfork = 0; /* flag to see if I'm process group leader */ 236 static int msgfd; /* file descriptor for fifo queue */ 237 static int ecid = 1; /* event class id for el_remove(); MUST be set to 1 */ 238 static int delayed; /* is job being rescheduled or did it run first time */ 239 static int cwd; /* current working directory */ 240 static struct event *next_event; /* the next event to execute */ 241 static struct usr *uhead; /* ptr to the list of users */ 242 243 /* Variables for error handling at reading crontabs. */ 244 static char cte_intro[] = "Line(s) with errors:\n\n"; 245 static char cte_trail1[] = "\nMax number of errors encountered."; 246 static char cte_trail2[] = " Evaluation of crontab aborted.\n"; 247 static int cte_free = MAILBINITFREE; /* Free buffer space */ 248 static char *cte_text = NULL; /* Text buffer pointer */ 249 static char *cte_lp; /* Next free line in cte_text */ 250 static int cte_nvalid; /* Valid lines found */ 251 252 /* user's default environment for the shell */ 253 #define ROOTPATH "PATH=/usr/sbin:/usr/bin" 254 #define NONROOTPATH "PATH=/usr/bin:" 255 256 static char *Def_supath = NULL; 257 static char *Def_path = NULL; 258 static char path[LINE_MAX] = "PATH="; 259 static char supath[LINE_MAX] = "PATH="; 260 static char homedir[LINE_MAX] = "HOME="; 261 static char logname[LINE_MAX] = "LOGNAME="; 262 static char tzone[LINE_MAX] = "TZ="; 263 static char *envinit[] = { 264 homedir, 265 logname, 266 ROOTPATH, 267 "SHELL=/usr/bin/sh", 268 tzone, 269 NULL 270 }; 271 272 extern char **environ; 273 274 #define DEFTZ "GMT" 275 static int log = 0; 276 static char hzname[10]; 277 278 static void cronend(int); 279 static void thaw_handler(int); 280 static void child_handler(int); 281 static void child_sigreset(void); 282 283 static void mod_ctab(char *, time_t); 284 static void mod_atjob(char *, time_t); 285 static void add_atevent(struct usr *, char *, time_t, int); 286 static void rm_ctevents(struct usr *); 287 static void cleanup(struct runinfo *rn, int r); 288 static void crabort(char *, int); 289 static void msg(char *fmt, ...); 290 static void logit(int, struct runinfo *, int); 291 static void parsqdef(char *); 292 static void defaults(); 293 static void initialize(int); 294 static void quedefs(int); 295 static int idle(long); 296 static struct usr *find_usr(char *); 297 static int ex(struct event *e); 298 static void read_dirs(int); 299 static void mail(char *, char *, int); 300 static char *next_field(int, int); 301 static void readcron(struct usr *, time_t); 302 static int next_ge(int, char *); 303 static void free_if_unused(struct usr *); 304 static void del_atjob(char *, char *); 305 static void del_ctab(char *); 306 static void resched(int); 307 static int msg_wait(long); 308 static struct runinfo *rinfo_get(pid_t); 309 static void rinfo_free(struct runinfo *rp); 310 static void mail_result(struct usr *p, struct runinfo *pr, size_t filesize); 311 static time_t next_time(struct event *, time_t); 312 static time_t get_switching_time(int, time_t); 313 static time_t xmktime(struct tm *); 314 static void process_msg(struct message *, time_t); 315 static void reap_child(void); 316 static void miscpid_insert(pid_t); 317 static int miscpid_delete(pid_t); 318 static void contract_set_template(void); 319 static void contract_clear_template(void); 320 static void contract_abandon_latest(pid_t); 321 322 static void cte_init(void); 323 static void cte_add(int, char *); 324 static void cte_valid(void); 325 static int cte_istoomany(void); 326 static void cte_sendmail(char *); 327 328 static int set_user_cred(const struct usr *, struct project *); 329 330 /* 331 * last_time is set immediately prior to exection of an event (via ex()) 332 * to indicate the last time an event was executed. This was (surely) 333 * it's original intended use. 334 */ 335 static time_t last_time, init_time, t_old; 336 337 static int accept_sigcld, notifypipe[2]; 338 static sigset_t defmask, childmask; 339 340 /* 341 * BSM hooks 342 */ 343 extern int audit_cron_session(char *, char *, uid_t, gid_t, char *); 344 extern void audit_cron_new_job(char *, int, void *); 345 extern void audit_cron_bad_user(char *); 346 extern void audit_cron_user_acct_expired(char *); 347 extern int audit_cron_create_anc_file(char *, char *, char *, uid_t); 348 extern int audit_cron_delete_anc_file(char *, char *); 349 extern int audit_cron_is_anc_name(char *); 350 extern int audit_cron_mode(); 351 352 static int cron_conv(int, struct pam_message **, 353 struct pam_response **, void *); 354 355 static struct pam_conv pam_conv = {cron_conv, NULL}; 356 static pam_handle_t *pamh; /* Authentication handle */ 357 358 /* 359 * Function to help check a user's credentials. 360 */ 361 362 static int verify_user_cred(struct usr *u); 363 364 /* 365 * Values returned by verify_user_cred and set_user_cred: 366 */ 367 368 #define VUC_OK 0 369 #define VUC_BADUSER 1 370 #define VUC_NOTINGROUP 2 371 #define VUC_EXPIRED 3 372 #define VUC_NEW_AUTH 4 373 374 /* 375 * Modes of process_anc_files function 376 */ 377 #define CRON_ANC_DELETE 1 378 #define CRON_ANC_CREATE 0 379 380 /* 381 * Functions to remove a user or job completely from the running database. 382 */ 383 static void clean_out_atjobs(struct usr *u); 384 static void clean_out_ctab(struct usr *u); 385 static void clean_out_user(struct usr *u); 386 static void cron_unlink(char *name); 387 static void process_anc_files(int); 388 389 /* 390 * functions in elm.c 391 */ 392 extern void el_init(int, time_t, time_t, int); 393 extern void el_add(void *, time_t, int); 394 extern void el_remove(int, int); 395 extern int el_empty(void); 396 extern void *el_first(void); 397 extern void el_delete(void); 398 399 static int valid_entry(char *, int); 400 static struct usr *create_ulist(char *, int); 401 static void init_cronevent(char *, int); 402 static void init_atevent(char *, time_t, int, int); 403 static void update_atevent(struct usr *, char *, time_t, int); 404 405 int 406 main(int argc, char *argv[]) 407 { 408 time_t t; 409 time_t ne_time; /* amt of time until next event execution */ 410 time_t newtime, lastmtime = 0L; 411 struct usr *u; 412 struct event *e, *e2, *eprev; 413 struct stat buf; 414 pid_t rfork; 415 struct sigaction act; 416 417 /* 418 * reset is set to 1 via the return from ex() should ex() find 419 * that the event to be executed is being run at the wrong time. 420 * We immediately return to the top of the while (TRUE) loop in 421 * main() where the event list is cleared and rebuilt, and reset 422 * is set back to 0. 423 */ 424 int reset = 0; 425 426 /* 427 * Only the privileged user can run this command. 428 */ 429 if (getuid() != 0) 430 crabort(NOTALLOWED, 0); 431 432 begin: 433 (void) setlocale(LC_ALL, ""); 434 /* fork unless 'nofork' is specified */ 435 if ((argc <= 1) || (strcmp(argv[1], "nofork"))) { 436 if (rfork = fork()) { 437 if (rfork == (pid_t)-1) { 438 (void) sleep(30); 439 goto begin; 440 } 441 return (0); 442 } 443 didfork++; 444 (void) setpgrp(); /* detach cron from console */ 445 } 446 447 (void) umask(022); 448 (void) signal(SIGHUP, SIG_IGN); 449 (void) signal(SIGINT, SIG_IGN); 450 (void) signal(SIGQUIT, SIG_IGN); 451 (void) signal(SIGTERM, cronend); 452 453 defaults(); 454 initialize(1); 455 quedefs(DEFAULT); /* load default queue definitions */ 456 cron_pid = getpid(); 457 msg("*** cron started *** pid = %d", cron_pid); 458 (void) sigset(SIGTHAW, thaw_handler); 459 /* 460 * setup SIGCLD handler/mask 461 */ 462 act.sa_handler = child_handler; 463 act.sa_flags = 0; 464 (void) sigemptyset(&act.sa_mask); 465 (void) sigaddset(&act.sa_mask, SIGCLD); 466 (void) sigaction(SIGCLD, &act, NULL); 467 (void) sigemptyset(&childmask); 468 (void) sigaddset(&childmask, SIGCLD); 469 (void) sigprocmask(SIG_BLOCK, &childmask, &defmask); 470 471 if (pipe(notifypipe) != 0) { 472 crabort("cannot create pipe", REMOVE_FIFO|CONSOLE_MSG); 473 } 474 /* 475 * will set O_NONBLOCK, so that the write() from child_handler 476 * never be blocked. 477 */ 478 (void) fcntl(notifypipe[0], F_SETFL, O_WRONLY|O_NONBLOCK); 479 480 t_old = time(NULL); 481 last_time = t_old; 482 for (;;) { /* MAIN LOOP */ 483 t = time(NULL); 484 if ((t_old > t) || (t-last_time > CUSHION) || reset) { 485 reset = 0; 486 /* the time was set backwards or forward */ 487 el_delete(); 488 u = uhead; 489 while (u != NULL) { 490 rm_ctevents(u); 491 e = u->atevents; 492 while (e != NULL) { 493 free(e->cmd); 494 e2 = e->link; 495 free(e); 496 e = e2; 497 } 498 u->atevents = NULL; 499 u = u->nextusr; 500 } 501 (void) close(msgfd); 502 initialize(0); 503 t = time(NULL); 504 last_time = t; 505 } 506 t_old = t; 507 508 if (next_event == NULL && !el_empty()) { 509 next_event = (struct event *)el_first(); 510 } 511 if (next_event == NULL) { 512 ne_time = INFINITY; 513 } else { 514 ne_time = next_event->time - t; 515 #ifdef DEBUG 516 cftime(timebuf, "%C", &next_event->time); 517 (void) fprintf(stderr, "next_time=%ld %s\n", 518 next_event->time, timebuf); 519 #endif 520 } 521 if (ne_time > 0) { 522 if ((reset = idle(ne_time)) != 0) 523 continue; 524 } 525 526 if (stat(QUEDEFS, &buf)) { 527 msg("cannot stat QUEDEFS file"); 528 } else if (lastmtime != buf.st_mtime) { 529 quedefs(LOAD); 530 lastmtime = buf.st_mtime; 531 } 532 533 last_time = next_event->time; /* save execution time */ 534 535 if (reset = ex(next_event)) 536 continue; 537 538 switch (next_event->etype) { 539 case CRONEVENT: 540 /* add cronevent back into the main event list */ 541 if (delayed) { 542 delayed = 0; 543 break; 544 } 545 546 /* 547 * check if time(0)< last_time. if so, then the 548 * system clock has gone backwards. to prevent this 549 * job from being started twice, we reschedule this 550 * job for the >>next time after last_time<<, and 551 * then set next_event->time to this. note that 552 * crontab's resolution is 1 minute. 553 */ 554 555 if (last_time > time(NULL)) { 556 msg(CLOCK_DRIFT); 557 /* 558 * bump up to next 30 second 559 * increment 560 * 1 <= newtime <= 30 561 */ 562 newtime = 30 - (last_time % 30); 563 newtime += last_time; 564 565 /* 566 * get the next scheduled event, 567 * not the one that we just 568 * kicked off! 569 */ 570 next_event->time = 571 next_time(next_event, newtime); 572 t_old = time(NULL); 573 } else { 574 next_event->time = 575 next_time(next_event, (time_t)0); 576 } 577 #ifdef DEBUG 578 cftime(timebuf, "%C", &next_event->time); 579 (void) fprintf(stderr, 580 "pushing back cron event %s at %ld (%s)\n", 581 next_event->cmd, next_event->time, timebuf); 582 #endif 583 584 el_add(next_event, next_event->time, 585 (next_event->u)->ctid); 586 break; 587 default: 588 /* remove at or batch job from system */ 589 if (delayed) { 590 delayed = 0; 591 break; 592 } 593 eprev = NULL; 594 e = (next_event->u)->atevents; 595 while (e != NULL) { 596 if (e == next_event) { 597 if (eprev == NULL) 598 (e->u)->atevents = e->link; 599 else 600 eprev->link = e->link; 601 free(e->cmd); 602 free(e); 603 break; 604 } else { 605 eprev = e; 606 e = e->link; 607 } 608 } 609 break; 610 } 611 next_event = NULL; 612 } 613 614 /*NOTREACHED*/ 615 } 616 617 static void 618 initialize(int firstpass) 619 { 620 #ifdef DEBUG 621 (void) fprintf(stderr, "in initialize\n"); 622 #endif 623 init_time = time(NULL); 624 el_init(8, init_time, (time_t)(60*60*24), 10); 625 if (firstpass) { 626 /* for mail(1), make sure messages come from root */ 627 if (putenv("LOGNAME=root") != 0) { 628 crabort("cannot expand env variable", 629 REMOVE_FIFO|CONSOLE_MSG); 630 } 631 if (access(FIFO, R_OK) == -1) { 632 if (errno == ENOENT) { 633 if (mknod(FIFO, S_IFIFO|0600, 0) != 0) 634 crabort("cannot create fifo queue", 635 REMOVE_FIFO|CONSOLE_MSG); 636 } else { 637 if (NOFORK) { 638 /* didn't fork... init(1M) is waiting */ 639 (void) sleep(60); 640 } 641 perror("FIFO"); 642 crabort("cannot access fifo queue", 643 REMOVE_FIFO|CONSOLE_MSG); 644 } 645 } else { 646 if (NOFORK) { 647 /* didn't fork... init(1M) is waiting */ 648 (void) sleep(60); 649 /* 650 * the wait is painful, but we don't want 651 * init respawning this quickly 652 */ 653 } 654 crabort("cannot start cron; FIFO exists", CONSOLE_MSG); 655 } 656 } 657 658 if ((msgfd = open(FIFO, O_RDWR)) < 0) { 659 perror("! open"); 660 crabort("cannot open fifo queue", REMOVE_FIFO|CONSOLE_MSG); 661 } 662 663 /* 664 * read directories, create users list, and add events to the 665 * main event list. Only zero user list on firstpass. 666 */ 667 if (firstpass) 668 uhead = NULL; 669 read_dirs(firstpass); 670 next_event = NULL; 671 672 if (!firstpass) 673 return; 674 675 /* stdout is log file */ 676 if (freopen(ACCTFILE, "a", stdout) == NULL) 677 (void) fprintf(stderr, "cannot open %s\n", ACCTFILE); 678 679 /* log should be root-only */ 680 (void) fchmod(1, S_IRUSR|S_IWUSR); 681 682 /* stderr also goes to ACCTFILE */ 683 (void) close(fileno(stderr)); 684 (void) dup(1); 685 686 /* null for stdin */ 687 (void) freopen("/dev/null", "r", stdin); 688 689 contract_set_template(); 690 } 691 692 static void 693 read_dirs(int first) 694 { 695 DIR *dir; 696 struct dirent *dp; 697 char *ptr; 698 int jobtype; 699 time_t tim; 700 701 702 if (chdir(CRONDIR) == -1) 703 crabort(BADCD, REMOVE_FIFO|CONSOLE_MSG); 704 cwd = CRON; 705 if ((dir = opendir(".")) == NULL) 706 crabort(NOREADDIR, REMOVE_FIFO|CONSOLE_MSG); 707 while ((dp = readdir(dir)) != NULL) { 708 if (!valid_entry(dp->d_name, CRONEVENT)) 709 continue; 710 init_cronevent(dp->d_name, first); 711 } 712 (void) closedir(dir); 713 714 if (chdir(ATDIR) == -1) { 715 msg("cannot chdir to at directory"); 716 return; 717 } 718 if ((dir = opendir(".")) == NULL) { 719 msg("cannot read at at directory"); 720 return; 721 } 722 cwd = AT; 723 while ((dp = readdir(dir)) != NULL) { 724 if (!valid_entry(dp->d_name, ATEVENT)) 725 continue; 726 ptr = dp->d_name; 727 if (((tim = num(&ptr)) == 0) || (*ptr != '.')) 728 continue; 729 ptr++; 730 if (!isalpha(*ptr)) 731 continue; 732 jobtype = *ptr - 'a'; 733 if (jobtype >= NQUEUE) { 734 cron_unlink(dp->d_name); 735 continue; 736 } 737 init_atevent(dp->d_name, tim, jobtype, first); 738 } 739 (void) closedir(dir); 740 } 741 742 static int 743 valid_entry(char *name, int type) 744 { 745 struct stat buf; 746 747 if (strcmp(name, ".") == 0 || 748 strcmp(name, "..") == 0) 749 return (0); 750 751 /* skip over ancillary file names */ 752 if (audit_cron_is_anc_name(name)) 753 return (0); 754 755 if (stat(name, &buf)) { 756 mail(name, BADSTAT, ERR_UNIXERR); 757 cron_unlink(name); 758 return (0); 759 } 760 if (!S_ISREG(buf.st_mode)) { 761 mail(name, BADTYPE, ERR_NOTREG); 762 cron_unlink(name); 763 return (0); 764 } 765 if (type == ATEVENT) { 766 if (!(buf.st_mode & ISUID)) { 767 cron_unlink(name); 768 return (0); 769 } 770 } 771 return (1); 772 } 773 774 struct usr * 775 create_ulist(char *name, int type) 776 { 777 struct usr *u; 778 779 u = xmalloc(sizeof (struct usr)); 780 u->name = xmalloc(strlen(name) + 1); 781 (void) strcpy(u->name, name); 782 u->home = NULL; 783 if (type == CRONEVENT) { 784 u->ctexists = TRUE; 785 u->ctid = ecid++; 786 } else { 787 u->ctexists = FALSE; 788 u->ctid = 0; 789 } 790 u->ctevents = NULL; 791 u->atevents = NULL; 792 u->aruncnt = 0; 793 u->cruncnt = 0; 794 u->nextusr = uhead; 795 uhead = u; 796 return (u); 797 } 798 799 void 800 init_cronevent(char *name, int first) 801 { 802 struct usr *u; 803 804 if (first) { 805 u = create_ulist(name, CRONEVENT); 806 readcron(u, 0); 807 } else { 808 if ((u = find_usr(name)) == NULL) { 809 u = create_ulist(name, CRONEVENT); 810 readcron(u, 0); 811 } else { 812 u->ctexists = TRUE; 813 rm_ctevents(u); 814 el_remove(u->ctid, 0); 815 readcron(u, 0); 816 } 817 } 818 } 819 820 void 821 init_atevent(char *name, time_t tim, int jobtype, int first) 822 { 823 struct usr *u; 824 825 if (first) { 826 u = create_ulist(name, ATEVENT); 827 add_atevent(u, name, tim, jobtype); 828 } else { 829 if ((u = find_usr(name)) == NULL) { 830 u = create_ulist(name, ATEVENT); 831 add_atevent(u, name, tim, jobtype); 832 } else { 833 update_atevent(u, name, tim, jobtype); 834 } 835 } 836 } 837 838 static void 839 mod_ctab(char *name, time_t reftime) 840 { 841 struct passwd *pw; 842 struct stat buf; 843 struct usr *u; 844 char namebuf[PATH_MAX]; 845 char *pname; 846 847 /* skip over ancillary file names */ 848 if (audit_cron_is_anc_name(name)) 849 return; 850 851 if ((pw = getpwnam(name)) == NULL) { 852 msg("No such user as %s - cron entries not created", name); 853 return; 854 } 855 if (cwd != CRON) { 856 if (snprintf(namebuf, sizeof (namebuf), "%s/%s", 857 CRONDIR, name) >= sizeof (namebuf)) { 858 msg("Too long path name %s - cron entries not created", 859 namebuf); 860 return; 861 } 862 pname = namebuf; 863 } else { 864 pname = name; 865 } 866 /* 867 * a warning message is given by the crontab command so there is 868 * no need to give one here...... use this code if you only want 869 * users with a login shell of /usr/bin/sh to use cron 870 */ 871 #ifdef BOURNESHELLONLY 872 if ((strcmp(pw->pw_shell, "") != 0) && 873 (strcmp(pw->pw_shell, SHELL) != 0)) { 874 mail(name, BADSHELL, ERR_CANTEXECCRON); 875 cron_unlink(pname); 876 return; 877 } 878 #endif 879 if (stat(pname, &buf)) { 880 mail(name, BADSTAT, ERR_UNIXERR); 881 cron_unlink(pname); 882 return; 883 } 884 if (!S_ISREG(buf.st_mode)) { 885 mail(name, BADTYPE, ERR_CRONTABENT); 886 return; 887 } 888 if ((u = find_usr(name)) == NULL) { 889 #ifdef DEBUG 890 (void) fprintf(stderr, "new user (%s) with a crontab\n", name); 891 #endif 892 u = create_ulist(name, CRONEVENT); 893 u->home = xmalloc(strlen(pw->pw_dir) + 1); 894 (void) strcpy(u->home, pw->pw_dir); 895 u->uid = pw->pw_uid; 896 u->gid = pw->pw_gid; 897 readcron(u, reftime); 898 } else { 899 u->uid = pw->pw_uid; 900 u->gid = pw->pw_gid; 901 if (u->home != NULL) { 902 if (strcmp(u->home, pw->pw_dir) != 0) { 903 free(u->home); 904 u->home = xmalloc(strlen(pw->pw_dir) + 1); 905 (void) strcpy(u->home, pw->pw_dir); 906 } 907 } else { 908 u->home = xmalloc(strlen(pw->pw_dir) + 1); 909 (void) strcpy(u->home, pw->pw_dir); 910 } 911 u->ctexists = TRUE; 912 if (u->ctid == 0) { 913 #ifdef DEBUG 914 (void) fprintf(stderr, "%s now has a crontab\n", 915 u->name); 916 #endif 917 /* user didnt have a crontab last time */ 918 u->ctid = ecid++; 919 u->ctevents = NULL; 920 readcron(u, reftime); 921 return; 922 } 923 #ifdef DEBUG 924 (void) fprintf(stderr, "%s has revised his crontab\n", u->name); 925 #endif 926 rm_ctevents(u); 927 el_remove(u->ctid, 0); 928 readcron(u, reftime); 929 } 930 } 931 932 /* ARGSUSED */ 933 static void 934 mod_atjob(char *name, time_t reftime) 935 { 936 char *ptr; 937 time_t tim; 938 struct passwd *pw; 939 struct stat buf; 940 struct usr *u; 941 char namebuf[PATH_MAX]; 942 char *pname; 943 int jobtype; 944 945 ptr = name; 946 if (((tim = num(&ptr)) == 0) || (*ptr != '.')) 947 return; 948 ptr++; 949 if (!isalpha(*ptr)) 950 return; 951 jobtype = *ptr - 'a'; 952 953 /* check for audit ancillary file */ 954 if (audit_cron_is_anc_name(name)) 955 return; 956 957 if (cwd != AT) { 958 if (snprintf(namebuf, sizeof (namebuf), "%s/%s", ATDIR, name) 959 >= sizeof (namebuf)) { 960 return; 961 } 962 pname = namebuf; 963 } else { 964 pname = name; 965 } 966 if (stat(pname, &buf) || jobtype >= NQUEUE) { 967 cron_unlink(pname); 968 return; 969 } 970 if (!(buf.st_mode & ISUID) || !S_ISREG(buf.st_mode)) { 971 cron_unlink(pname); 972 return; 973 } 974 if ((pw = getpwuid(buf.st_uid)) == NULL) { 975 cron_unlink(pname); 976 return; 977 } 978 /* 979 * a warning message is given by the at command so there is no 980 * need to give one here......use this code if you only want 981 * users with a login shell of /usr/bin/sh to use cron 982 */ 983 #ifdef BOURNESHELLONLY 984 if ((strcmp(pw->pw_shell, "") != 0) && 985 (strcmp(pw->pw_shell, SHELL) != 0)) { 986 mail(pw->pw_name, BADSHELL, ERR_CANTEXECAT); 987 cron_unlink(pname); 988 return; 989 } 990 #endif 991 if ((u = find_usr(pw->pw_name)) == NULL) { 992 #ifdef DEBUG 993 (void) fprintf(stderr, "new user (%s) with an at job = %s\n", 994 pw->pw_name, name); 995 #endif 996 u = create_ulist(pw->pw_name, ATEVENT); 997 u->home = xmalloc(strlen(pw->pw_dir) + 1); 998 (void) strcpy(u->home, pw->pw_dir); 999 u->uid = pw->pw_uid; 1000 u->gid = pw->pw_gid; 1001 add_atevent(u, name, tim, jobtype); 1002 } else { 1003 u->uid = pw->pw_uid; 1004 u->gid = pw->pw_gid; 1005 free(u->home); 1006 u->home = xmalloc(strlen(pw->pw_dir) + 1); 1007 (void) strcpy(u->home, pw->pw_dir); 1008 update_atevent(u, name, tim, jobtype); 1009 } 1010 } 1011 1012 static void 1013 add_atevent(struct usr *u, char *job, time_t tim, int jobtype) 1014 { 1015 struct event *e; 1016 1017 e = xmalloc(sizeof (struct event)); 1018 e->etype = jobtype; 1019 e->cmd = xmalloc(strlen(job) + 1); 1020 (void) strcpy(e->cmd, job); 1021 e->u = u; 1022 e->link = u->atevents; 1023 u->atevents = e; 1024 e->of.at.exists = TRUE; 1025 e->of.at.eventid = ecid++; 1026 if (tim < init_time) /* old job */ 1027 e->time = init_time; 1028 else 1029 e->time = tim; 1030 #ifdef DEBUG 1031 (void) fprintf(stderr, "add_atevent: user=%s, job=%s, time=%ld\n", 1032 u->name, e->cmd, e->time); 1033 #endif 1034 el_add(e, e->time, e->of.at.eventid); 1035 } 1036 1037 void 1038 update_atevent(struct usr *u, char *name, time_t tim, int jobtype) 1039 { 1040 struct event *e; 1041 1042 e = u->atevents; 1043 while (e != NULL) { 1044 if (strcmp(e->cmd, name) == 0) { 1045 e->of.at.exists = TRUE; 1046 break; 1047 } else { 1048 e = e->link; 1049 } 1050 } 1051 if (e == NULL) { 1052 #ifdef DEBUG 1053 (void) fprintf(stderr, "%s has a new at job = %s\n", 1054 u->name, name); 1055 #endif 1056 add_atevent(u, name, tim, jobtype); 1057 } 1058 } 1059 1060 static char line[CTLINESIZE]; /* holds a line from a crontab file */ 1061 static int cursor; /* cursor for the above line */ 1062 1063 static void 1064 readcron(struct usr *u, time_t reftime) 1065 { 1066 /* 1067 * readcron reads in a crontab file for a user (u). The list of 1068 * events for user u is built, and u->events is made to point to 1069 * this list. Each event is also entered into the main event 1070 * list. 1071 */ 1072 FILE *cf; /* cf will be a user's crontab file */ 1073 struct event *e; 1074 int start; 1075 unsigned int i; 1076 char namebuf[PATH_MAX]; 1077 char *pname; 1078 int lineno = 0; 1079 1080 /* read the crontab file */ 1081 cte_init(); /* Init error handling */ 1082 if (cwd != CRON) { 1083 if (snprintf(namebuf, sizeof (namebuf), "%s/%s", 1084 CRONDIR, u->name) >= sizeof (namebuf)) { 1085 return; 1086 } 1087 pname = namebuf; 1088 } else { 1089 pname = u->name; 1090 } 1091 if ((cf = fopen(pname, "r")) == NULL) { 1092 mail(u->name, NOREAD, ERR_UNIXERR); 1093 return; 1094 } 1095 while (fgets(line, CTLINESIZE, cf) != NULL) { 1096 /* process a line of a crontab file */ 1097 lineno++; 1098 if (cte_istoomany()) 1099 break; 1100 cursor = 0; 1101 while (line[cursor] == ' ' || line[cursor] == '\t') 1102 cursor++; 1103 if (line[cursor] == '#' || line[cursor] == '\n') 1104 continue; 1105 e = xmalloc(sizeof (struct event)); 1106 e->etype = CRONEVENT; 1107 if (!(((e->of.ct.minute = next_field(0, 59)) != NULL) && 1108 ((e->of.ct.hour = next_field(0, 23)) != NULL) && 1109 ((e->of.ct.daymon = next_field(1, 31)) != NULL) && 1110 ((e->of.ct.month = next_field(1, 12)) != NULL) && 1111 ((e->of.ct.dayweek = next_field(0, 6)) != NULL))) { 1112 free(e); 1113 cte_add(lineno, line); 1114 continue; 1115 } 1116 while (line[cursor] == ' ' || line[cursor] == '\t') 1117 cursor++; 1118 if (line[cursor] == '\n' || line[cursor] == '\0') 1119 continue; 1120 /* get the command to execute */ 1121 start = cursor; 1122 again: 1123 while ((line[cursor] != '%') && 1124 (line[cursor] != '\n') && 1125 (line[cursor] != '\0') && 1126 (line[cursor] != '\\')) 1127 cursor++; 1128 if (line[cursor] == '\\') { 1129 cursor += 2; 1130 goto again; 1131 } 1132 e->cmd = xmalloc(cursor-start + 1); 1133 (void) strncpy(e->cmd, line + start, cursor-start); 1134 e->cmd[cursor-start] = '\0'; 1135 /* see if there is any standard input */ 1136 if (line[cursor] == '%') { 1137 e->of.ct.input = xmalloc(strlen(line)-cursor + 1); 1138 (void) strcpy(e->of.ct.input, line + cursor + 1); 1139 for (i = 0; i < strlen(e->of.ct.input); i++) { 1140 if (e->of.ct.input[i] == '%') 1141 e->of.ct.input[i] = '\n'; 1142 } 1143 } else { 1144 e->of.ct.input = NULL; 1145 } 1146 /* have the event point to it's owner */ 1147 e->u = u; 1148 /* insert this event at the front of this user's event list */ 1149 e->link = u->ctevents; 1150 u->ctevents = e; 1151 /* set the time for the first occurance of this event */ 1152 e->time = next_time(e, reftime); 1153 /* finally, add this event to the main event list */ 1154 el_add(e, e->time, u->ctid); 1155 cte_valid(); 1156 #ifdef DEBUG 1157 cftime(timebuf, "%C", &e->time); 1158 (void) fprintf(stderr, "inserting cron event %s at %ld (%s)\n", 1159 e->cmd, e->time, timebuf); 1160 #endif 1161 } 1162 cte_sendmail(u->name); /* mail errors if any to user */ 1163 (void) fclose(cf); 1164 } 1165 1166 /* 1167 * Below are the functions for handling of errors in crontabs. Concept is to 1168 * collect faulty lines and send one email at the end of the crontab 1169 * evaluation. If there are erroneous lines only ((cte_nvalid == 0), evaluation 1170 * of crontab is aborted. Otherwise reading of crontab is continued to the end 1171 * of the file but no further error logging appears. 1172 */ 1173 static void 1174 cte_init() 1175 { 1176 if (cte_text == NULL) 1177 cte_text = xmalloc(MAILBUFLEN); 1178 (void) strlcpy(cte_text, cte_intro, MAILBUFLEN); 1179 cte_lp = cte_text + sizeof (cte_intro) - 1; 1180 cte_free = MAILBINITFREE; 1181 cte_nvalid = 0; 1182 } 1183 1184 static void 1185 cte_add(int lineno, char *ctline) 1186 { 1187 int len; 1188 char *p; 1189 1190 if (cte_free >= LINELIMIT) { 1191 (void) sprintf(cte_lp, "%4d: ", lineno); 1192 (void) strlcat(cte_lp, ctline, LINELIMIT - 1); 1193 len = strlen(cte_lp); 1194 if (cte_lp[len - 1] != '\n') { 1195 cte_lp[len++] = '\n'; 1196 cte_lp[len] = '\0'; 1197 } 1198 for (p = cte_lp; *p; p++) { 1199 if (isprint(*p) || *p == '\n' || *p == '\t') 1200 continue; 1201 *p = '.'; 1202 } 1203 cte_lp += len; 1204 cte_free -= len; 1205 if (cte_free < LINELIMIT) { 1206 size_t buflen = MAILBUFLEN - (cte_lp - cte_text); 1207 (void) strlcpy(cte_lp, cte_trail1, buflen); 1208 if (cte_nvalid == 0) 1209 (void) strlcat(cte_lp, cte_trail2, buflen); 1210 } 1211 } 1212 } 1213 1214 static void 1215 cte_valid() 1216 { 1217 cte_nvalid++; 1218 } 1219 1220 static int 1221 cte_istoomany() 1222 { 1223 /* 1224 * Return TRUE only if all lines are faulty. So evaluation of 1225 * a crontab is not aborted if at least one valid line was found. 1226 */ 1227 return (cte_nvalid == 0 && cte_free < LINELIMIT); 1228 } 1229 1230 static void 1231 cte_sendmail(char *username) 1232 { 1233 if (cte_free < MAILBINITFREE) 1234 mail(username, cte_text, ERR_CRONTABENT); 1235 } 1236 1237 /* 1238 * Send mail with error message to a user 1239 */ 1240 static void 1241 mail(char *usrname, char *mesg, int format) 1242 { 1243 /* mail mails a user a message. */ 1244 FILE *pipe; 1245 char *temp; 1246 struct passwd *ruser_ids; 1247 pid_t fork_val; 1248 int saveerrno = errno; 1249 struct utsname name; 1250 1251 #ifdef TESTING 1252 return; 1253 #endif 1254 (void) uname(&name); 1255 if ((fork_val = fork()) == (pid_t)-1) { 1256 msg("cron cannot fork\n"); 1257 return; 1258 } 1259 if (fork_val == 0) { 1260 child_sigreset(); 1261 contract_clear_template(); 1262 if ((ruser_ids = getpwnam(usrname)) == NULL) 1263 exit(0); 1264 (void) setuid(ruser_ids->pw_uid); 1265 temp = xmalloc(strlen(MAIL) + strlen(usrname) + 2); 1266 (void) sprintf(temp, "%s %s", MAIL, usrname); 1267 pipe = popen(temp, "w"); 1268 if (pipe != NULL) { 1269 (void) fprintf(pipe, "To: %s\n", usrname); 1270 switch (format) { 1271 case ERR_CRONTABENT: 1272 (void) fprintf(pipe, CRONTABERR); 1273 (void) fprintf(pipe, "Your \"crontab\" on %s\n", 1274 name.nodename); 1275 (void) fprintf(pipe, mesg); 1276 (void) fprintf(pipe, 1277 "\nEntries or crontab have been ignored\n"); 1278 break; 1279 case ERR_UNIXERR: 1280 (void) fprintf(pipe, "Subject: %s\n\n", mesg); 1281 (void) fprintf(pipe, 1282 "The error on %s was \"%s\"\n", 1283 name.nodename, errmsg(saveerrno)); 1284 break; 1285 1286 case ERR_CANTEXECCRON: 1287 (void) fprintf(pipe, 1288 "Subject: Couldn't run your \"cron\" job\n\n"); 1289 (void) fprintf(pipe, 1290 "Your \"cron\" job on %s ", name.nodename); 1291 (void) fprintf(pipe, "couldn't be run\n"); 1292 (void) fprintf(pipe, "%s\n", mesg); 1293 (void) fprintf(pipe, 1294 "The error was \"%s\"\n", errmsg(saveerrno)); 1295 break; 1296 1297 case ERR_CANTEXECAT: 1298 (void) fprintf(pipe, 1299 "Subject: Couldn't run your \"at\" job\n\n"); 1300 (void) fprintf(pipe, "Your \"at\" job on %s ", 1301 name.nodename); 1302 (void) fprintf(pipe, "couldn't be run\n"); 1303 (void) fprintf(pipe, "%s\n", mesg); 1304 (void) fprintf(pipe, 1305 "The error was \"%s\"\n", errmsg(saveerrno)); 1306 break; 1307 1308 default: 1309 break; 1310 } 1311 (void) pclose(pipe); 1312 } 1313 free(temp); 1314 exit(0); 1315 } 1316 1317 contract_abandon_latest(fork_val); 1318 1319 if (cron_pid == getpid()) { 1320 miscpid_insert(fork_val); 1321 } 1322 } 1323 1324 static char * 1325 next_field(int lower, int upper) 1326 { 1327 /* 1328 * next_field returns a pointer to a string which holds the next 1329 * field of a line of a crontab file. 1330 * if (numbers in this field are out of range (lower..upper), 1331 * or there is a syntax error) then 1332 * NULL is returned, and a mail message is sent to the 1333 * user telling him which line the error was in. 1334 */ 1335 1336 char *s; 1337 int num, num2, start; 1338 1339 while ((line[cursor] == ' ') || (line[cursor] == '\t')) 1340 cursor++; 1341 start = cursor; 1342 if (line[cursor] == '\0') { 1343 return (NULL); 1344 } 1345 if (line[cursor] == '*') { 1346 cursor++; 1347 if ((line[cursor] != ' ') && (line[cursor] != '\t')) 1348 return (NULL); 1349 s = xmalloc(2); 1350 (void) strcpy(s, "*"); 1351 return (s); 1352 } 1353 for (;;) { 1354 if (!isdigit(line[cursor])) 1355 return (NULL); 1356 num = 0; 1357 do { 1358 num = num*10 + (line[cursor]-'0'); 1359 } while (isdigit(line[++cursor])); 1360 if ((num < lower) || (num > upper)) 1361 return (NULL); 1362 if (line[cursor] == '-') { 1363 if (!isdigit(line[++cursor])) 1364 return (NULL); 1365 num2 = 0; 1366 do { 1367 num2 = num2*10 + (line[cursor]-'0'); 1368 } while (isdigit(line[++cursor])); 1369 if ((num2 < lower) || (num2 > upper)) 1370 return (NULL); 1371 } 1372 if ((line[cursor] == ' ') || (line[cursor] == '\t')) 1373 break; 1374 if (line[cursor] == '\0') 1375 return (NULL); 1376 if (line[cursor++] != ',') 1377 return (NULL); 1378 } 1379 s = xmalloc(cursor-start + 1); 1380 (void) strncpy(s, line + start, cursor-start); 1381 s[cursor-start] = '\0'; 1382 return (s); 1383 } 1384 1385 #define tm_cmp(t1, t2) (\ 1386 (t1)->tm_year == (t2)->tm_year && \ 1387 (t1)->tm_mon == (t2)->tm_mon && \ 1388 (t1)->tm_mday == (t2)->tm_mday && \ 1389 (t1)->tm_hour == (t2)->tm_hour && \ 1390 (t1)->tm_min == (t2)->tm_min) 1391 1392 #define tm_setup(tp, yr, mon, dy, hr, min, dst) \ 1393 (tp)->tm_year = yr; \ 1394 (tp)->tm_mon = mon; \ 1395 (tp)->tm_mday = dy; \ 1396 (tp)->tm_hour = hr; \ 1397 (tp)->tm_min = min; \ 1398 (tp)->tm_isdst = dst; \ 1399 (tp)->tm_sec = 0; \ 1400 (tp)->tm_wday = 0; \ 1401 (tp)->tm_yday = 0; 1402 1403 /* 1404 * modification for bugid 1104537. the second argument to next_time is 1405 * now the value of time(2) to be used. if this is 0, then use the 1406 * current time. otherwise, the second argument is the time from which to 1407 * calculate things. this is useful to correct situations where you've 1408 * gone backwards in time (I.e. the system's internal clock is correcting 1409 * itself backwards). 1410 */ 1411 1412 static time_t 1413 next_time(struct event *e, time_t tflag) 1414 { 1415 /* 1416 * returns the integer time for the next occurance of event e. 1417 * the following fields have ranges as indicated: 1418 * PRGM | min hour day of month mon day of week 1419 * ------|------------------------------------------------------- 1420 * cron | 0-59 0-23 1-31 1-12 0-6 (0=sunday) 1421 * time | 0-59 0-23 1-31 0-11 0-6 (0=sunday) 1422 * NOTE: this routine is hard to understand. 1423 */ 1424 1425 struct tm *tm, ref_tm, tmp, tmp1, tmp2; 1426 int tm_mon, tm_mday, tm_wday, wday, m, min, h, hr, carry, day, days, 1427 d1, day1, carry1, d2, day2, carry2, daysahead, mon, yr, db, wd, 1428 today; 1429 1430 time_t t, ref_t, t1, t2, zone_start; 1431 int fallback; 1432 extern int days_btwn(int, int, int, int, int, int); 1433 1434 if (tflag == 0) { 1435 t = time(NULL); /* original way of doing things */ 1436 } else { 1437 t = tflag; 1438 } 1439 1440 tm = &ref_tm; /* use a local variable and call localtime_r() */ 1441 ref_t = t; /* keep a copy of the reference time */ 1442 1443 recalc: 1444 fallback = 0; 1445 1446 (void) localtime_r(&t, tm); 1447 1448 if (daylight) { 1449 tmp = *tm; 1450 tmp.tm_isdst = (tm->tm_isdst > 0 ? 0 : 1); 1451 t1 = xmktime(&tmp); 1452 /* 1453 * see if we will have timezone switch over, and clock will 1454 * fall back. zone_start will hold the time when it happens 1455 * (ie time of PST -> PDT switch over). 1456 */ 1457 if (tm->tm_isdst != tmp.tm_isdst && 1458 (t1 - t) == (timezone - altzone) && 1459 tm_cmp(tm, &tmp)) { 1460 zone_start = get_switching_time(tmp.tm_isdst, t); 1461 fallback = 1; 1462 } 1463 } 1464 1465 tm_mon = next_ge(tm->tm_mon + 1, e->of.ct.month) - 1; /* 0-11 */ 1466 tm_mday = next_ge(tm->tm_mday, e->of.ct.daymon); /* 1-31 */ 1467 tm_wday = next_ge(tm->tm_wday, e->of.ct.dayweek); /* 0-6 */ 1468 today = TRUE; 1469 if ((strcmp(e->of.ct.daymon, "*") == 0 && tm->tm_wday != tm_wday) || 1470 (strcmp(e->of.ct.dayweek, "*") == 0 && tm->tm_mday != tm_mday) || 1471 (tm->tm_mday != tm_mday && tm->tm_wday != tm_wday) || 1472 (tm->tm_mon != tm_mon)) { 1473 today = FALSE; 1474 } 1475 m = tm->tm_min + (t == ref_t ? 1 : 0); 1476 if ((tm->tm_hour + 1) <= next_ge(tm->tm_hour, e->of.ct.hour)) { 1477 m = 0; 1478 } 1479 min = next_ge(m%60, e->of.ct.minute); 1480 carry = (min < m) ? 1 : 0; 1481 h = tm->tm_hour + carry; 1482 hr = next_ge(h%24, e->of.ct.hour); 1483 carry = (hr < h) ? 1 : 0; 1484 1485 if (carry == 0 && today) { 1486 /* this event must occur today */ 1487 tm_setup(&tmp, tm->tm_year, tm->tm_mon, tm->tm_mday, 1488 hr, min, tm->tm_isdst); 1489 tmp1 = tmp; 1490 if ((t1 = xmktime(&tmp1)) == (time_t)-1) { 1491 return (0); 1492 } 1493 if (daylight && tmp.tm_isdst != tmp1.tm_isdst) { 1494 /* In case we are falling back */ 1495 if (fallback) { 1496 /* we may need to run the job once more. */ 1497 t = zone_start; 1498 goto recalc; 1499 } 1500 1501 /* 1502 * In case we are not in falling back period, 1503 * calculate the time assuming the DST. If the 1504 * date/time is not altered by mktime, it is the 1505 * time to execute the job. 1506 */ 1507 tmp2 = tmp; 1508 tmp2.tm_isdst = tmp1.tm_isdst; 1509 if ((t1 = xmktime(&tmp2)) == (time_t)-1) { 1510 return (0); 1511 } 1512 if (tmp1.tm_isdst == tmp2.tm_isdst && 1513 tm_cmp(&tmp, &tmp2)) { 1514 /* 1515 * We got a valid time. 1516 */ 1517 return (t1); 1518 } else { 1519 /* 1520 * If the date does not match even if 1521 * we assume the alternate timezone, then 1522 * it must be the invalid time. eg 1523 * 2am while switching 1:59am to 3am. 1524 * t1 should point the time before the 1525 * switching over as we've calculate the 1526 * time with assuming alternate zone. 1527 */ 1528 if (tmp1.tm_isdst != tmp2.tm_isdst) { 1529 t = get_switching_time(tmp1.tm_isdst, 1530 t1); 1531 } else { 1532 /* does this really happen? */ 1533 t = get_switching_time(tmp1.tm_isdst, 1534 t1 - abs(timezone - altzone)); 1535 } 1536 if (t == (time_t)-1) 1537 return (0); 1538 } 1539 goto recalc; 1540 } 1541 if (tm_cmp(&tmp, &tmp1)) { 1542 /* got valid time */ 1543 return (t1); 1544 } else { 1545 /* 1546 * This should never happen, but just in 1547 * case, we fall back to the old code. 1548 */ 1549 if (tm->tm_min > min) { 1550 t += (time_t)(hr-tm->tm_hour-1) * HOUR + 1551 (time_t)(60-tm->tm_min + min) * MINUTE; 1552 } else { 1553 t += (time_t)(hr-tm->tm_hour) * HOUR + 1554 (time_t)(min-tm->tm_min) * MINUTE; 1555 } 1556 t1 = t; 1557 t -= (time_t)tm->tm_sec; 1558 (void) localtime_r(&t, &tmp); 1559 if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0)) 1560 t -= (timezone - altzone); 1561 return ((t <= ref_t) ? t1 : t); 1562 } 1563 } 1564 1565 /* 1566 * Job won't run today, however if we have a switch over within 1567 * one hour and we will have one hour time drifting back in this 1568 * period, we may need to run the job one more time if the job was 1569 * set to run on this hour of clock. 1570 */ 1571 if (fallback) { 1572 t = zone_start; 1573 goto recalc; 1574 } 1575 1576 min = next_ge(0, e->of.ct.minute); 1577 hr = next_ge(0, e->of.ct.hour); 1578 1579 /* 1580 * calculate the date of the next occurance of this event, which 1581 * will be on a different day than the current 1582 */ 1583 1584 /* check monthly day specification */ 1585 d1 = tm->tm_mday + 1; 1586 day1 = next_ge((d1-1)%days_in_mon(tm->tm_mon, tm->tm_year) + 1, 1587 e->of.ct.daymon); 1588 carry1 = (day1 < d1) ? 1 : 0; 1589 1590 /* check weekly day specification */ 1591 d2 = tm->tm_wday + 1; 1592 wday = next_ge(d2%7, e->of.ct.dayweek); 1593 if (wday < d2) 1594 daysahead = 7 - d2 + wday; 1595 else 1596 daysahead = wday - d2; 1597 day2 = (d1 + daysahead-1)%days_in_mon(tm->tm_mon, tm->tm_year) + 1; 1598 carry2 = (day2 < d1) ? 1 : 0; 1599 1600 /* 1601 * based on their respective specifications, day1, and day2 give 1602 * the day of the month for the next occurance of this event. 1603 */ 1604 if ((strcmp(e->of.ct.daymon, "*") == 0) && 1605 (strcmp(e->of.ct.dayweek, "*") != 0)) { 1606 day1 = day2; 1607 carry1 = carry2; 1608 } 1609 if ((strcmp(e->of.ct.daymon, "*") != 0) && 1610 (strcmp(e->of.ct.dayweek, "*") == 0)) { 1611 day2 = day1; 1612 carry2 = carry1; 1613 } 1614 1615 yr = tm->tm_year; 1616 if ((carry1 && carry2) || (tm->tm_mon != tm_mon)) { 1617 /* event does not occur in this month */ 1618 m = tm->tm_mon + 1; 1619 mon = next_ge(m%12 + 1, e->of.ct.month) - 1; /* 0..11 */ 1620 carry = (mon < m) ? 1 : 0; 1621 yr += carry; 1622 /* recompute day1 and day2 */ 1623 day1 = next_ge(1, e->of.ct.daymon); 1624 db = days_btwn(tm->tm_mon, tm->tm_mday, tm->tm_year, mon, 1625 1, yr) + 1; 1626 wd = (tm->tm_wday + db)%7; 1627 /* wd is the day of the week of the first of month mon */ 1628 wday = next_ge(wd, e->of.ct.dayweek); 1629 if (wday < wd) 1630 day2 = 1 + 7 - wd + wday; 1631 else 1632 day2 = 1 + wday - wd; 1633 if ((strcmp(e->of.ct.daymon, "*") != 0) && 1634 (strcmp(e->of.ct.dayweek, "*") == 0)) 1635 day2 = day1; 1636 if ((strcmp(e->of.ct.daymon, "*") == 0) && 1637 (strcmp(e->of.ct.dayweek, "*") != 0)) 1638 day1 = day2; 1639 day = (day1 < day2) ? day1 : day2; 1640 } else { /* event occurs in this month */ 1641 mon = tm->tm_mon; 1642 if (!carry1 && !carry2) 1643 day = (day1 < day2) ? day1 : day2; 1644 else if (!carry1) 1645 day = day1; 1646 else 1647 day = day2; 1648 } 1649 1650 /* 1651 * now that we have the min, hr, day, mon, yr of the next event, 1652 * figure out what time that turns out to be. 1653 */ 1654 tm_setup(&tmp, yr, mon, day, hr, min, -1); 1655 tmp2 = tmp; 1656 if ((t1 = xmktime(&tmp2)) == (time_t)-1) { 1657 return (0); 1658 } 1659 if (tm_cmp(&tmp, &tmp2)) { 1660 /* 1661 * mktime returns clock for the current time zone. If the 1662 * target date was in fallback period, it needs to be adjusted 1663 * to the time comes first. 1664 * Suppose, we are at Jan and scheduling job at 1:30am10/26/03. 1665 * mktime returns the time in PST, but 1:30am in PDT comes 1666 * first. So reverse the tm_isdst, and see if we have such 1667 * time/date. 1668 */ 1669 if (daylight) { 1670 int dst = tmp2.tm_isdst; 1671 1672 tmp2 = tmp; 1673 tmp2.tm_isdst = (dst > 0 ? 0 : 1); 1674 if ((t2 = xmktime(&tmp2)) == (time_t)-1) { 1675 return (0); 1676 } 1677 if (tm_cmp(&tmp, &tmp2)) { 1678 /* 1679 * same time/date found in the opposite zone. 1680 * check the clock to see which comes early. 1681 */ 1682 if (t2 > ref_t && t2 < t1) { 1683 t1 = t2; 1684 } 1685 } 1686 } 1687 return (t1); 1688 } else { 1689 /* 1690 * mktime has set different time/date for the given date. 1691 * This means that the next job is scheduled to be run on the 1692 * invalid time. There are three possible invalid date/time. 1693 * 1. Non existing day of the month. such as April 31th. 1694 * 2. Feb 29th in the non-leap year. 1695 * 3. Time gap during the DST switch over. 1696 */ 1697 d1 = days_in_mon(mon, yr); 1698 if ((mon != 1 && day > d1) || (mon == 1 && day > 29)) { 1699 /* 1700 * see if we have got a specific date which 1701 * is invalid. 1702 */ 1703 if (strcmp(e->of.ct.dayweek, "*") == 0 && 1704 mon == (next_ge((mon + 1)%12 + 1, 1705 e->of.ct.month) - 1) && 1706 day <= next_ge(1, e->of.ct.daymon)) { 1707 /* job never run */ 1708 return (0); 1709 } 1710 /* 1711 * Since the day has gone invalid, we need to go to 1712 * next month, and recalcuate the first occurrence. 1713 * eg the cron tab such as: 1714 * 0 0 1,15,31 1,2,3,4,5 * /usr/bin.... 1715 * 2/31 is invalid, so the next job is 3/1. 1716 */ 1717 tmp2 = tmp; 1718 tmp2.tm_min = 0; 1719 tmp2.tm_hour = 0; 1720 tmp2.tm_mday = 1; /* 1st day of the month */ 1721 if (mon == 11) { 1722 tmp2.tm_mon = 0; 1723 tmp2.tm_year = yr + 1; 1724 } else { 1725 tmp2.tm_mon = mon + 1; 1726 } 1727 if ((t = xmktime(&tmp2)) == (time_t)-1) { 1728 return (0); 1729 } 1730 } else if (mon == 1 && day > d1) { 1731 /* 1732 * ie 29th in the non-leap year. Forwarding the 1733 * clock to Feb 29th 00:00 (March 1st), and recalculate 1734 * the next time. 1735 */ 1736 tmp2 = tmp; 1737 tmp2.tm_min = 0; 1738 tmp2.tm_hour = 0; 1739 if ((t = xmktime(&tmp2)) == (time_t)-1) { 1740 return (0); 1741 } 1742 } else if (daylight) { 1743 /* 1744 * Non existing time, eg 2am PST during summer time 1745 * switch. 1746 * We need to get the correct isdst which we are 1747 * swithing to, by adding time difference to make sure 1748 * that t2 is in the zone being switched. 1749 */ 1750 t2 = t1; 1751 t2 += abs(timezone - altzone); 1752 (void) localtime_r(&t2, &tmp2); 1753 zone_start = get_switching_time(tmp2.tm_isdst, 1754 t1 - abs(timezone - altzone)); 1755 if (zone_start == (time_t)-1) { 1756 return (0); 1757 } 1758 t = zone_start; 1759 } else { 1760 /* 1761 * This should never happen, but fall back to the 1762 * old code. 1763 */ 1764 days = days_btwn(tm->tm_mon, 1765 tm->tm_mday, tm->tm_year, mon, day, yr); 1766 t += (time_t)(23-tm->tm_hour)*HOUR 1767 + (time_t)(60-tm->tm_min)*MINUTE 1768 + (time_t)hr*HOUR + (time_t)min*MINUTE 1769 + (time_t)days*DAY; 1770 t1 = t; 1771 t -= (time_t)tm->tm_sec; 1772 (void) localtime_r(&t, &tmp); 1773 if ((tm->tm_isdst == 0) && (tmp.tm_isdst > 0)) 1774 t -= (timezone - altzone); 1775 return (t <= ref_t ? t1 : t); 1776 } 1777 goto recalc; 1778 } 1779 /*NOTREACHED*/ 1780 } 1781 1782 /* 1783 * This returns TOD in time_t that zone switch will happen, and this 1784 * will be called when clock fallback is about to happen. 1785 * (ie 30minutes before the time of PST -> PDT switch. 2:00 AM PST 1786 * will fall back to 1:00 PDT. So this function will be called only 1787 * for the time between 1:00 AM PST and 2:00 PST(1:00 PST)). 1788 * First goes through the common time differences to see if zone 1789 * switch happens at those minutes later. If not, check every minutes 1790 * until 6 hours ahead see if it happens(We might have 45minutes 1791 * fallback). 1792 */ 1793 static time_t 1794 get_switching_time(int to_dst, time_t t_ref) 1795 { 1796 time_t t, t1; 1797 struct tm tmp, tmp1; 1798 int hints[] = { 60, 120, 30, 90, 0}; /* minutes */ 1799 int i; 1800 1801 (void) localtime_r(&t_ref, &tmp); 1802 tmp1 = tmp; 1803 tmp1.tm_sec = 0; 1804 tmp1.tm_min = 0; 1805 if ((t = xmktime(&tmp1)) == (time_t)-1) 1806 return ((time_t)-1); 1807 1808 /* fast path */ 1809 for (i = 0; hints[i] != 0; i++) { 1810 t1 = t + hints[i] * 60; 1811 (void) localtime_r(&t1, &tmp1); 1812 if (tmp1.tm_isdst == to_dst) { 1813 t1--; 1814 (void) localtime_r(&t1, &tmp1); 1815 if (tmp1.tm_isdst != to_dst) { 1816 return (t1 + 1); 1817 } 1818 } 1819 } 1820 1821 /* ugly, but don't know other than this. */ 1822 tmp1 = tmp; 1823 tmp1.tm_sec = 0; 1824 if ((t = xmktime(&tmp1)) == (time_t)-1) 1825 return ((time_t)-1); 1826 while (t < (t_ref + 6*60*60)) { /* 6 hours should be enough */ 1827 t += 60; /* at least one minute, I assume */ 1828 (void) localtime_r(&t, &tmp); 1829 if (tmp.tm_isdst == to_dst) 1830 return (t); 1831 } 1832 return ((time_t)-1); 1833 } 1834 1835 static time_t 1836 xmktime(struct tm *tmp) 1837 { 1838 time_t ret; 1839 1840 if ((ret = mktime(tmp)) == (time_t)-1) { 1841 if (errno == EOVERFLOW) { 1842 return ((time_t)-1); 1843 } 1844 crabort("internal error: mktime failed", 1845 REMOVE_FIFO|CONSOLE_MSG); 1846 } 1847 return (ret); 1848 } 1849 1850 #define DUMMY 100 1851 1852 static int 1853 next_ge(int current, char *list) 1854 { 1855 /* 1856 * list is a character field as in a crontab file; 1857 * for example: "40, 20, 50-10" 1858 * next_ge returns the next number in the list that is 1859 * greater than or equal to current. if no numbers of list 1860 * are >= current, the smallest element of list is returned. 1861 * NOTE: current must be in the appropriate range. 1862 */ 1863 1864 char *ptr; 1865 int n, n2, min, min_gt; 1866 1867 if (strcmp(list, "*") == 0) 1868 return (current); 1869 ptr = list; 1870 min = DUMMY; 1871 min_gt = DUMMY; 1872 for (;;) { 1873 if ((n = (int)num(&ptr)) == current) 1874 return (current); 1875 if (n < min) 1876 min = n; 1877 if ((n > current) && (n < min_gt)) 1878 min_gt = n; 1879 if (*ptr == '-') { 1880 ptr++; 1881 if ((n2 = (int)num(&ptr)) > n) { 1882 if ((current > n) && (current <= n2)) 1883 return (current); 1884 } else { /* range that wraps around */ 1885 if (current > n) 1886 return (current); 1887 if (current <= n2) 1888 return (current); 1889 } 1890 } 1891 if (*ptr == '\0') 1892 break; 1893 ptr += 1; 1894 } 1895 if (min_gt != DUMMY) 1896 return (min_gt); 1897 else 1898 return (min); 1899 } 1900 1901 static void 1902 free_if_unused(struct usr *u) 1903 { 1904 struct usr *cur, *prev; 1905 /* 1906 * To make sure a usr structure is idle we must check that 1907 * there are no at jobs queued for the user; the user does 1908 * not have a crontab, and also that there are no running at 1909 * or cron jobs (since the runinfo structure also has a 1910 * pointer to the usr structure). 1911 */ 1912 if (!u->ctexists && u->atevents == NULL && 1913 u->cruncnt == 0 && u->aruncnt == 0) { 1914 #ifdef DEBUG 1915 (void) fprintf(stderr, "%s removed from usr list\n", u->name); 1916 #endif 1917 for (cur = uhead, prev = NULL; 1918 cur != u; 1919 prev = cur, cur = cur->nextusr) { 1920 if (cur == NULL) { 1921 return; 1922 } 1923 } 1924 1925 if (prev == NULL) 1926 uhead = u->nextusr; 1927 else 1928 prev->nextusr = u->nextusr; 1929 free(u->name); 1930 free(u->home); 1931 free(u); 1932 } 1933 } 1934 1935 static void 1936 del_atjob(char *name, char *usrname) 1937 { 1938 1939 struct event *e, *eprev; 1940 struct usr *u; 1941 1942 if ((u = find_usr(usrname)) == NULL) 1943 return; 1944 e = u->atevents; 1945 eprev = NULL; 1946 while (e != NULL) { 1947 if (strcmp(name, e->cmd) == 0) { 1948 if (next_event == e) 1949 next_event = NULL; 1950 if (eprev == NULL) 1951 u->atevents = e->link; 1952 else 1953 eprev->link = e->link; 1954 el_remove(e->of.at.eventid, 1); 1955 free(e->cmd); 1956 free(e); 1957 break; 1958 } else { 1959 eprev = e; 1960 e = e->link; 1961 } 1962 } 1963 1964 free_if_unused(u); 1965 } 1966 1967 static void 1968 del_ctab(char *name) 1969 { 1970 1971 struct usr *u; 1972 1973 if ((u = find_usr(name)) == NULL) 1974 return; 1975 rm_ctevents(u); 1976 el_remove(u->ctid, 0); 1977 u->ctid = 0; 1978 u->ctexists = 0; 1979 1980 free_if_unused(u); 1981 } 1982 1983 static void 1984 rm_ctevents(struct usr *u) 1985 { 1986 struct event *e2, *e3; 1987 1988 /* 1989 * see if the next event (to be run by cron) is a cronevent 1990 * owned by this user. 1991 */ 1992 1993 if ((next_event != NULL) && 1994 (next_event->etype == CRONEVENT) && 1995 (next_event->u == u)) { 1996 next_event = NULL; 1997 } 1998 e2 = u->ctevents; 1999 while (e2 != NULL) { 2000 free(e2->cmd); 2001 free(e2->of.ct.minute); 2002 free(e2->of.ct.hour); 2003 free(e2->of.ct.daymon); 2004 free(e2->of.ct.month); 2005 free(e2->of.ct.dayweek); 2006 if (e2->of.ct.input != NULL) 2007 free(e2->of.ct.input); 2008 e3 = e2->link; 2009 free(e2); 2010 e2 = e3; 2011 } 2012 u->ctevents = NULL; 2013 } 2014 2015 2016 static struct usr * 2017 find_usr(char *uname) 2018 { 2019 struct usr *u; 2020 2021 u = uhead; 2022 while (u != NULL) { 2023 if (strcmp(u->name, uname) == 0) 2024 return (u); 2025 u = u->nextusr; 2026 } 2027 return (NULL); 2028 } 2029 2030 /* 2031 * Execute cron command or at/batch job. 2032 * If ever a premature return is added to this function pay attention to 2033 * free at_cmdfile and outfile plus jobname buffers of the runinfo structure. 2034 */ 2035 static int 2036 ex(struct event *e) 2037 { 2038 int r; 2039 int fd; 2040 pid_t rfork; 2041 FILE *atcmdfp; 2042 char mailvar[4]; 2043 char *at_cmdfile = NULL; 2044 struct stat buf; 2045 struct queue *qp; 2046 struct runinfo *rp; 2047 struct project proj, *pproj = NULL; 2048 char mybuf[PROJECT_BUFSZ]; 2049 char mybuf2[PROJECT_BUFSZ]; 2050 char *tmpfile; 2051 FILE *fptr; 2052 time_t dhltime; 2053 projid_t projid; 2054 int projflag = 0; 2055 2056 qp = &qt[e->etype]; /* set pointer to queue defs */ 2057 if (qp->nrun >= qp->njob) { 2058 msg("%c queue max run limit reached", e->etype + 'a'); 2059 resched(qp->nwait); 2060 return (0); 2061 } 2062 rp = rinfo_get(0); /* allocating a new runinfo struct */ 2063 2064 2065 /* 2066 * the tempnam() function uses malloc(3C) to allocate space for the 2067 * constructed file name, and returns a pointer to this area, which 2068 * is assigned to rp->outfile. Here rp->outfile is not overwritten. 2069 */ 2070 2071 rp->outfile = tempnam(TMPDIR, PFX); 2072 rp->jobtype = e->etype; 2073 if (e->etype == CRONEVENT) { 2074 rp->jobname = xmalloc(strlen(e->cmd) + 1); 2075 (void) strcpy(rp->jobname, e->cmd); 2076 /* "cron" jobs only produce mail if there's output */ 2077 rp->mailwhendone = 0; 2078 } else { 2079 at_cmdfile = xmalloc(strlen(ATDIR) + strlen(e->cmd) + 2); 2080 (void) sprintf(at_cmdfile, "%s/%s", ATDIR, e->cmd); 2081 if ((atcmdfp = fopen(at_cmdfile, "r")) == NULL) { 2082 if (errno == ENAMETOOLONG) { 2083 if (chdir(ATDIR) == 0) 2084 cron_unlink(e->cmd); 2085 } else { 2086 cron_unlink(at_cmdfile); 2087 } 2088 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECAT); 2089 free(at_cmdfile); 2090 rinfo_free(rp); 2091 return (0); 2092 } 2093 rp->jobname = xmalloc(strlen(at_cmdfile) + 1); 2094 (void) strcpy(rp->jobname, at_cmdfile); 2095 2096 /* 2097 * Skip over the first two lines. 2098 */ 2099 (void) fscanf(atcmdfp, "%*[^\n]\n"); 2100 (void) fscanf(atcmdfp, "%*[^\n]\n"); 2101 if (fscanf(atcmdfp, ": notify by mail: %3s%*[^\n]\n", 2102 mailvar) == 1) { 2103 /* 2104 * Check to see if we should always send mail 2105 * to the owner. 2106 */ 2107 rp->mailwhendone = (strcmp(mailvar, "yes") == 0); 2108 } else { 2109 rp->mailwhendone = 0; 2110 } 2111 2112 if (fscanf(atcmdfp, "\n: project: %d\n", &projid) == 1) { 2113 projflag = 1; 2114 } 2115 (void) fclose(atcmdfp); 2116 } 2117 2118 /* 2119 * we make sure that the system time 2120 * hasn't drifted backwards. if it has, el_add() is now 2121 * called, to make sure that the event queue is back in order, 2122 * and we set the delayed flag. cron will pick up the request 2123 * later on at the proper time. 2124 */ 2125 dhltime = time(NULL); 2126 if ((dhltime - e->time) < 0) { 2127 msg("clock time drifted backwards!\n"); 2128 if (next_event->etype == CRONEVENT) { 2129 msg("correcting cron event\n"); 2130 next_event->time = next_time(next_event, dhltime); 2131 el_add(next_event, next_event->time, 2132 (next_event->u)->ctid); 2133 } else { /* etype == ATEVENT */ 2134 msg("correcting batch event\n"); 2135 el_add(next_event, next_event->time, 2136 next_event->of.at.eventid); 2137 } 2138 delayed++; 2139 t_old = time(NULL); 2140 free(at_cmdfile); 2141 rinfo_free(rp); 2142 return (0); 2143 } 2144 2145 if ((rfork = fork()) == (pid_t)-1) { 2146 reap_child(); 2147 if ((rfork = fork()) == (pid_t)-1) { 2148 msg("cannot fork"); 2149 free(at_cmdfile); 2150 rinfo_free(rp); 2151 resched(60); 2152 (void) sleep(30); 2153 return (0); 2154 } 2155 } 2156 if (rfork) { /* parent process */ 2157 contract_abandon_latest(rfork); 2158 2159 ++qp->nrun; 2160 rp->pid = rfork; 2161 rp->que = e->etype; 2162 if (e->etype != CRONEVENT) 2163 (e->u)->aruncnt++; 2164 else 2165 (e->u)->cruncnt++; 2166 rp->rusr = (e->u); 2167 logit(BCHAR, rp, 0); 2168 free(at_cmdfile); 2169 2170 return (0); 2171 } 2172 2173 child_sigreset(); 2174 contract_clear_template(); 2175 2176 if (e->etype != CRONEVENT) { 2177 /* open jobfile as stdin to shell */ 2178 if (stat(at_cmdfile, &buf)) { 2179 if (errno == ENAMETOOLONG) { 2180 if (chdir(ATDIR) == 0) 2181 cron_unlink(e->cmd); 2182 } else 2183 cron_unlink(at_cmdfile); 2184 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON); 2185 exit(1); 2186 } 2187 if (!(buf.st_mode&ISUID)) { 2188 /* 2189 * if setuid bit off, original owner has 2190 * given this file to someone else 2191 */ 2192 cron_unlink(at_cmdfile); 2193 exit(1); 2194 } 2195 if ((fd = open(at_cmdfile, O_RDONLY)) == -1) { 2196 mail((e->u)->name, BADJOBOPEN, ERR_CANTEXECCRON); 2197 cron_unlink(at_cmdfile); 2198 exit(1); 2199 } 2200 if (fd != 0) { 2201 (void) dup2(fd, 0); 2202 (void) close(fd); 2203 } 2204 /* 2205 * retrieve the project id of the at job and convert it 2206 * to a project name. fail if it's not a valid project 2207 * or if the user isn't a member of the project. 2208 */ 2209 if (projflag == 1) { 2210 if ((pproj = getprojbyid(projid, &proj, 2211 (void *)&mybuf, sizeof (mybuf))) == NULL || 2212 !inproj(e->u->name, pproj->pj_name, 2213 mybuf2, sizeof (mybuf2))) { 2214 cron_unlink(at_cmdfile); 2215 mail((e->u)->name, BADPROJID, ERR_CANTEXECAT); 2216 exit(1); 2217 } 2218 } 2219 } 2220 2221 /* 2222 * Put process in a new session, and create a new task. 2223 */ 2224 if (setsid() < 0) { 2225 msg("setsid failed with errno = %d. job failed (%s)" 2226 " for user %s", errno, e->cmd, e->u->name); 2227 if (e->etype != CRONEVENT) 2228 cron_unlink(at_cmdfile); 2229 exit(1); 2230 } 2231 2232 /* 2233 * set correct user identification and check his account 2234 */ 2235 r = set_user_cred(e->u, pproj); 2236 if (r == VUC_EXPIRED) { 2237 msg("user (%s) account is expired", e->u->name); 2238 audit_cron_user_acct_expired(e->u->name); 2239 clean_out_user(e->u); 2240 exit(1); 2241 } 2242 if (r == VUC_NEW_AUTH) { 2243 msg("user (%s) password has expired", e->u->name); 2244 audit_cron_user_acct_expired(e->u->name); 2245 clean_out_user(e->u); 2246 exit(1); 2247 } 2248 if (r != VUC_OK) { 2249 msg("bad user (%s)", e->u->name); 2250 audit_cron_bad_user(e->u->name); 2251 clean_out_user(e->u); 2252 exit(1); 2253 } 2254 /* 2255 * check user and initialize the supplementary group access list. 2256 * bugid 1230784: deleted from parent to avoid cron hang. Now 2257 * only child handles the call. 2258 */ 2259 2260 if (verify_user_cred(e->u) != VUC_OK || 2261 setgid(e->u->gid) == -1 || 2262 initgroups(e->u->name, e->u->gid) == -1) { 2263 msg("bad user (%s) or setgid failed (%s)", 2264 e->u->name, e->u->name); 2265 audit_cron_bad_user(e->u->name); 2266 clean_out_user(e->u); 2267 exit(1); 2268 } 2269 2270 if ((e->u)->uid == 0) { /* set default path */ 2271 /* path settable in defaults file */ 2272 envinit[2] = supath; 2273 } else { 2274 envinit[2] = path; 2275 } 2276 2277 if (e->etype != CRONEVENT) { 2278 r = audit_cron_session(e->u->name, NULL, 2279 e->u->uid, e->u->gid, at_cmdfile); 2280 cron_unlink(at_cmdfile); 2281 } else { 2282 r = audit_cron_session(e->u->name, CRONDIR, 2283 e->u->uid, e->u->gid, NULL); 2284 } 2285 if (r != 0) { 2286 msg("cron audit problem. job failed (%s) for user %s", 2287 e->cmd, e->u->name); 2288 exit(1); 2289 } 2290 2291 audit_cron_new_job(e->cmd, e->etype, (void *)e); 2292 2293 if (setuid(e->u->uid) == -1) { 2294 msg("setuid failed (%s)", e->u->name); 2295 clean_out_user(e->u); 2296 exit(1); 2297 } 2298 2299 if (e->etype == CRONEVENT) { 2300 /* check for standard input to command */ 2301 if (e->of.ct.input != NULL) { 2302 if ((tmpfile = strdup(TMPINFILE)) == NULL) { 2303 mail((e->u)->name, MALLOCERR, 2304 ERR_CANTEXECCRON); 2305 exit(1); 2306 } 2307 if ((fd = mkstemp(tmpfile)) == -1 || 2308 (fptr = fdopen(fd, "w")) == NULL) { 2309 mail((e->u)->name, NOSTDIN, 2310 ERR_CANTEXECCRON); 2311 cron_unlink(tmpfile); 2312 free(tmpfile); 2313 exit(1); 2314 } 2315 if ((fwrite(e->of.ct.input, sizeof (char), 2316 strlen(e->of.ct.input), fptr)) != 2317 strlen(e->of.ct.input)) { 2318 mail((e->u)->name, NOSTDIN, ERR_CANTEXECCRON); 2319 cron_unlink(tmpfile); 2320 free(tmpfile); 2321 (void) close(fd); 2322 (void) fclose(fptr); 2323 exit(1); 2324 } 2325 if (fseek(fptr, (off_t)0, SEEK_SET) != -1) { 2326 if (fd != 0) { 2327 (void) dup2(fd, 0); 2328 (void) close(fd); 2329 } 2330 } 2331 cron_unlink(tmpfile); 2332 free(tmpfile); 2333 (void) fclose(fptr); 2334 } else if ((fd = open("/dev/null", O_RDONLY)) > 0) { 2335 (void) dup2(fd, 0); 2336 (void) close(fd); 2337 } 2338 } 2339 2340 /* redirect stdout and stderr for the shell */ 2341 if ((fd = open(rp->outfile, O_WRONLY|O_CREAT|O_EXCL, OUTMODE)) == 1) 2342 fd = open("/dev/null", O_WRONLY); 2343 2344 if (fd >= 0 && fd != 1) 2345 (void) dup2(fd, 1); 2346 2347 if (fd >= 0 && fd != 2) { 2348 (void) dup2(fd, 2); 2349 if (fd != 1) 2350 (void) close(fd); 2351 } 2352 2353 (void) strlcat(homedir, (e->u)->home, sizeof (homedir)); 2354 (void) strlcat(logname, (e->u)->name, sizeof (logname)); 2355 environ = envinit; 2356 if (chdir((e->u)->home) == -1) { 2357 mail((e->u)->name, CANTCDHOME, 2358 e->etype == CRONEVENT ? ERR_CANTEXECCRON : 2359 ERR_CANTEXECAT); 2360 exit(1); 2361 } 2362 #ifdef TESTING 2363 exit(1); 2364 #endif 2365 /* 2366 * make sure that all file descriptors EXCEPT 0, 1 and 2 2367 * will be closed. 2368 */ 2369 closefrom(3); 2370 2371 if ((e->u)->uid != 0) 2372 (void) nice(qp->nice); 2373 if (e->etype == CRONEVENT) 2374 (void) execl(SHELL, "sh", "-c", e->cmd, 0); 2375 else /* type == ATEVENT */ 2376 (void) execl(SHELL, "sh", 0); 2377 mail((e->u)->name, CANTEXECSH, 2378 e->etype == CRONEVENT ? ERR_CANTEXECCRON : ERR_CANTEXECAT); 2379 exit(1); 2380 /*NOTREACHED*/ 2381 } 2382 2383 static int 2384 idle(long t) 2385 { 2386 time_t now; 2387 2388 while (t > 0L) { 2389 2390 if (msg_wait(t) != 0) { 2391 /* we need to run next job immediately */ 2392 return (0); 2393 } 2394 2395 reap_child(); 2396 2397 now = time(NULL); 2398 if (last_time > now) { 2399 /* clock has been reset */ 2400 return (1); 2401 } 2402 2403 if (next_event == NULL && !el_empty()) { 2404 next_event = (struct event *)el_first(); 2405 } 2406 if (next_event == NULL) 2407 t = INFINITY; 2408 else 2409 t = (long)next_event->time - now; 2410 } 2411 return (0); 2412 } 2413 2414 /* 2415 * This used to be in the idle(), but moved to the separate function. 2416 * This called from various place when cron needs to reap the 2417 * child. It includes the situation that cron hit maxrun, and needs 2418 * to reschedule the job. 2419 */ 2420 static void 2421 reap_child() 2422 { 2423 pid_t pid; 2424 int prc; 2425 struct runinfo *rp; 2426 2427 for (;;) { 2428 pid = waitpid((pid_t)-1, &prc, WNOHANG); 2429 if (pid <= 0) 2430 break; 2431 #ifdef DEBUG 2432 fprintf(stderr, 2433 "wait returned %x for process %d\n", prc, pid); 2434 #endif 2435 if ((rp = rinfo_get(pid)) == NULL) { 2436 if (miscpid_delete(pid) == 0) { 2437 /* not found in anywhere */ 2438 msg(PIDERR, pid); 2439 } 2440 } else if (rp->que == ZOMB) { 2441 (void) unlink(rp->outfile); 2442 rinfo_free(rp); 2443 } else { 2444 cleanup(rp, prc); 2445 } 2446 } 2447 } 2448 2449 static void 2450 cleanup(struct runinfo *pr, int rc) 2451 { 2452 int nextfork = 1; 2453 struct usr *p; 2454 struct stat buf; 2455 2456 logit(ECHAR, pr, rc); 2457 --qt[pr->que].nrun; 2458 p = pr->rusr; 2459 if (pr->que != CRONEVENT) 2460 --p->aruncnt; 2461 else 2462 --p->cruncnt; 2463 2464 if (lstat(pr->outfile, &buf) == 0) { 2465 if (!S_ISLNK(buf.st_mode) && 2466 (buf.st_size > 0 || pr->mailwhendone)) { 2467 /* mail user stdout and stderr */ 2468 for (;;) { 2469 if ((pr->pid = fork()) < 0) { 2470 /* 2471 * if fork fails try forever in doubling 2472 * retry times, up to 16 seconds 2473 */ 2474 (void) sleep(nextfork); 2475 if (nextfork < 16) 2476 nextfork += nextfork; 2477 continue; 2478 } else if (pr->pid == 0) { 2479 child_sigreset(); 2480 contract_clear_template(); 2481 2482 mail_result(p, pr, buf.st_size); 2483 /* NOTREACHED */ 2484 } else { 2485 contract_abandon_latest(pr->pid); 2486 pr->que = ZOMB; 2487 break; 2488 } 2489 } 2490 } else { 2491 (void) unlink(pr->outfile); 2492 rinfo_free(pr); 2493 } 2494 } else { 2495 rinfo_free(pr); 2496 } 2497 2498 free_if_unused(p); 2499 } 2500 2501 /* 2502 * Mail stdout and stderr of a job to user. Get uid for real user and become 2503 * that person. We do this so that mail won't come from root since this 2504 * could be a security hole. If failure, quit - don't send mail as root. 2505 */ 2506 static void 2507 mail_result(struct usr *p, struct runinfo *pr, size_t filesize) 2508 { 2509 struct passwd *ruser_ids; 2510 FILE *mailpipe; 2511 FILE *st; 2512 struct utsname name; 2513 int nbytes; 2514 char iobuf[BUFSIZ]; 2515 char *cmd; 2516 2517 (void) uname(&name); 2518 if ((ruser_ids = getpwnam(p->name)) == NULL) 2519 exit(0); 2520 (void) setuid(ruser_ids->pw_uid); 2521 2522 cmd = xmalloc(strlen(MAIL) + strlen(p->name)+2); 2523 (void) sprintf(cmd, "%s %s", MAIL, p->name); 2524 mailpipe = popen(cmd, "w"); 2525 free(cmd); 2526 if (mailpipe == NULL) 2527 exit(127); 2528 (void) fprintf(mailpipe, "To: %s\n", p->name); 2529 if (pr->jobtype == CRONEVENT) { 2530 (void) fprintf(mailpipe, CRONOUT); 2531 (void) fprintf(mailpipe, "Your \"cron\" job on %s\n", 2532 name.nodename); 2533 if (pr->jobname != NULL) { 2534 (void) fprintf(mailpipe, "%s\n\n", pr->jobname); 2535 } 2536 } else { 2537 (void) fprintf(mailpipe, "Subject: Output from \"at\" job\n\n"); 2538 (void) fprintf(mailpipe, "Your \"at\" job on %s\n", 2539 name.nodename); 2540 if (pr->jobname != NULL) { 2541 (void) fprintf(mailpipe, "\"%s\"\n\n", pr->jobname); 2542 } 2543 } 2544 /* Tmp. file is fopen'ed w/ "r", secure open */ 2545 if (filesize > 0 && 2546 (st = fopen(pr->outfile, "r")) != NULL) { 2547 (void) fprintf(mailpipe, 2548 "produced the following output:\n\n"); 2549 while ((nbytes = fread(iobuf, sizeof (char), BUFSIZ, st)) != 0) 2550 (void) fwrite(iobuf, sizeof (char), nbytes, mailpipe); 2551 (void) fclose(st); 2552 } else { 2553 (void) fprintf(mailpipe, "completed.\n"); 2554 } 2555 (void) pclose(mailpipe); 2556 exit(0); 2557 } 2558 2559 static int 2560 msg_wait(long tim) 2561 { 2562 struct message msg; 2563 int cnt; 2564 time_t reftime; 2565 struct pollfd pfd[2]; 2566 int64_t tl; 2567 int timeout; 2568 static int pending_msg; 2569 static time_t pending_reftime; 2570 2571 if (pending_msg) { 2572 process_msg(&msgbuf, pending_reftime); 2573 pending_msg = 0; 2574 return (0); 2575 } 2576 2577 /* 2578 * We are opening the signal mask to receive SIGCLD. The notifypipe 2579 * is used to avoid race condition between SIGCLD and poll system 2580 * call. 2581 * If SIGCLD is delivered in poll(), poll will be interrupted, and 2582 * we will return to idle() to reap the dead children. 2583 * If SIGCLD is delivered between sigprocmask() below and poll(), 2584 * there is no way we can detect the SIGCLD because poll() won't 2585 * be interrupted. In such case, the dead children can't be wait'ed 2586 * until poll returns by timeout or a new job. To avoid this race 2587 * condition, child_handler write to the notifypipe, so that 2588 * poll() will be able to return with POLLIN which indicates that 2589 * we have received SIGCLD. 2590 * 2591 * Since the notifypipe is used to just let poll return from 2592 * system call, the data in the pipe won't be read. Therefore, 2593 * any data in the pipe needs to be flushed before opening signal 2594 * mask. 2595 * 2596 * Note that we can probably re-write this code with pselect() 2597 * which can handle this situation easily. 2598 */ 2599 (void) ioctl(notifypipe[0], I_FLUSH, FLUSHW); 2600 2601 pfd[0].fd = msgfd; 2602 pfd[0].events = POLLIN; 2603 pfd[1].fd = notifypipe[1]; 2604 pfd[1].events = POLLIN; 2605 2606 #ifdef CRON_MAXSLEEP 2607 /* 2608 * CRON_MAXSLEEP can be defined to have cron periodically wake 2609 * up, so that cron can detect a change of TOD and adjust the 2610 * sleep time accordingly. 2611 */ 2612 tim = (tim > CRON_MAXSLEEP) ? CRON_MAXSLEEP : tim; 2613 #endif 2614 tl = (tim == INFINITY) ? -1ll : (int64_t)tim * 1000; 2615 2616 accept_sigcld = 1; 2617 (void) sigprocmask(SIG_UNBLOCK, &childmask, NULL); 2618 do { 2619 timeout = (tl > INT_MAX ? INT_MAX : (int)tl); 2620 tl -= timeout; 2621 cnt = poll(pfd, 2, timeout); 2622 if (cnt == -1 && errno != EINTR) { 2623 perror("! poll"); 2624 } 2625 } while (tl > 0 && cnt == 0); 2626 (void) sigprocmask(SIG_BLOCK, &childmask, NULL); 2627 accept_sigcld = 0; 2628 2629 /* 2630 * poll timeout or interrupted. 2631 */ 2632 if (cnt <= 0) 2633 return (0); 2634 2635 /* 2636 * Not the timeout or new job, but a SIGCLD has been delivered. 2637 */ 2638 if ((pfd[0].revents & POLLIN) == 0) 2639 return (0); 2640 2641 errno = 0; 2642 if ((cnt = read(msgfd, &msg, sizeof (msg))) != sizeof (msg)) { 2643 if (cnt != -1 || errno != EAGAIN) 2644 perror("! read"); 2645 return (0); 2646 } 2647 reftime = time(NULL); 2648 if (next_event != NULL && reftime >= next_event->time) { 2649 /* 2650 * we need to run the job before reloading crontab. 2651 */ 2652 (void) memcpy(&msgbuf, &msg, sizeof (msg)); 2653 pending_msg = 1; 2654 pending_reftime = reftime; 2655 return (1); 2656 } 2657 process_msg(&msg, reftime); 2658 return (0); 2659 } 2660 2661 /* 2662 * process the message supplied via pipe. This will be called either 2663 * immediately after cron read the message from pipe, or idle time 2664 * if the message was pending due to the job execution. 2665 */ 2666 static void 2667 process_msg(struct message *pmsg, time_t reftime) 2668 { 2669 if (pmsg->etype == NULL) 2670 return; 2671 2672 switch (pmsg->etype) { 2673 case AT: 2674 if (pmsg->action == DELETE) 2675 del_atjob(pmsg->fname, pmsg->logname); 2676 else 2677 mod_atjob(pmsg->fname, (time_t)0); 2678 break; 2679 case CRON: 2680 if (pmsg->action == DELETE) 2681 del_ctab(pmsg->fname); 2682 else 2683 mod_ctab(pmsg->fname, reftime); 2684 break; 2685 default: 2686 msg("message received - bad format"); 2687 break; 2688 } 2689 if (next_event != NULL) { 2690 if (next_event->etype == CRONEVENT) 2691 el_add(next_event, next_event->time, 2692 (next_event->u)->ctid); 2693 else /* etype == ATEVENT */ 2694 el_add(next_event, next_event->time, 2695 next_event->of.at.eventid); 2696 next_event = NULL; 2697 } 2698 (void) fflush(stdout); 2699 pmsg->etype = NULL; 2700 } 2701 2702 /* 2703 * Allocate a new or find an existing runinfo structure 2704 */ 2705 static struct runinfo * 2706 rinfo_get(pid_t pid) 2707 { 2708 struct runinfo *rp; 2709 2710 if (pid == 0) { /* allocate a new entry */ 2711 rp = xcalloc(1, sizeof (struct runinfo)); 2712 rp->next = rthead; /* link the entry into the list */ 2713 rthead = rp; 2714 return (rp); 2715 } 2716 /* search the list for an existing entry */ 2717 for (rp = rthead; rp != NULL; rp = rp->next) { 2718 if (rp->pid == pid) 2719 break; 2720 } 2721 return (rp); 2722 } 2723 2724 /* 2725 * Free a runinfo structure and its associated memory 2726 */ 2727 static void 2728 rinfo_free(struct runinfo *entry) 2729 { 2730 struct runinfo **rpp; 2731 struct runinfo *rp; 2732 2733 #ifdef DEBUG 2734 (void) fprintf(stderr, "freeing job %s\n", entry->jobname); 2735 #endif 2736 for (rpp = &rthead; (rp = *rpp) != NULL; rpp = &rp->next) { 2737 if (rp == entry) { 2738 *rpp = rp->next; /* unlink the entry */ 2739 free(rp->outfile); 2740 free(rp->jobname); 2741 free(rp); 2742 break; 2743 } 2744 } 2745 } 2746 2747 /* ARGSUSED */ 2748 static void 2749 thaw_handler(int sig) 2750 { 2751 ; 2752 } 2753 2754 2755 /* ARGSUSED */ 2756 static void 2757 cronend(int sig) 2758 { 2759 crabort("SIGTERM", REMOVE_FIFO); 2760 } 2761 2762 /*ARGSUSED*/ 2763 static void 2764 child_handler(int sig) 2765 { 2766 /* 2767 * Just in case someone changes the signal mask. 2768 * we don't want to notify the SIGCLD. 2769 */ 2770 if (accept_sigcld) { 2771 (void) write(notifypipe[0], &sig, 1); 2772 } 2773 } 2774 2775 static void 2776 child_sigreset(void) 2777 { 2778 (void) signal(SIGCLD, SIG_DFL); 2779 (void) sigprocmask(SIG_SETMASK, &defmask, NULL); 2780 } 2781 2782 /* 2783 * crabort() - handle exits out of cron 2784 */ 2785 static void 2786 crabort(char *mssg, int action) 2787 { 2788 int c; 2789 2790 if (action & REMOVE_FIFO) { 2791 /* FIFO vanishes when cron finishes */ 2792 if (unlink(FIFO) < 0) 2793 perror("cron could not unlink FIFO"); 2794 } 2795 2796 if (action & CONSOLE_MSG) { 2797 /* write error msg to console */ 2798 if ((c = open(CONSOLE, O_WRONLY)) >= 0) { 2799 (void) write(c, "cron aborted: ", 14); 2800 (void) write(c, mssg, strlen(mssg)); 2801 (void) write(c, "\n", 1); 2802 (void) close(c); 2803 } 2804 } 2805 2806 /* always log the message */ 2807 msg(mssg); 2808 msg("******* CRON ABORTED ********"); 2809 exit(1); 2810 } 2811 2812 /* 2813 * msg() - time-stamped error reporting function 2814 */ 2815 /*PRINTFLIKE1*/ 2816 static void 2817 msg(char *fmt, ...) 2818 { 2819 va_list args; 2820 time_t t; 2821 2822 t = time(NULL); 2823 2824 (void) fflush(stdout); 2825 2826 (void) fprintf(stderr, "! "); 2827 2828 va_start(args, fmt); 2829 (void) vfprintf(stderr, fmt, args); 2830 va_end(args); 2831 2832 (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t)); 2833 (void) fprintf(stderr, " %s\n", timebuf); 2834 2835 (void) fflush(stderr); 2836 } 2837 2838 static void 2839 logit(int cc, struct runinfo *rp, int rc) 2840 { 2841 time_t t; 2842 int ret; 2843 2844 if (!log) 2845 return; 2846 2847 t = time(NULL); 2848 if (cc == BCHAR) 2849 (void) printf("%c CMD: %s\n", cc, next_event->cmd); 2850 (void) strftime(timebuf, sizeof (timebuf), FORMAT, localtime(&t)); 2851 (void) printf("%c %.8s %u %c %s", 2852 cc, (rp->rusr)->name, rp->pid, QUE(rp->que), timebuf); 2853 if ((ret = TSTAT(rc)) != 0) 2854 (void) printf(" ts=%d", ret); 2855 if ((ret = RCODE(rc)) != 0) 2856 (void) printf(" rc=%d", ret); 2857 (void) putchar('\n'); 2858 (void) fflush(stdout); 2859 } 2860 2861 static void 2862 resched(int delay) 2863 { 2864 time_t nt; 2865 2866 /* run job at a later time */ 2867 nt = next_event->time + delay; 2868 if (next_event->etype == CRONEVENT) { 2869 next_event->time = next_time(next_event, (time_t)0); 2870 if (nt < next_event->time) 2871 next_event->time = nt; 2872 el_add(next_event, next_event->time, (next_event->u)->ctid); 2873 delayed = 1; 2874 msg("rescheduling a cron job"); 2875 return; 2876 } 2877 add_atevent(next_event->u, next_event->cmd, nt, next_event->etype); 2878 msg("rescheduling at job"); 2879 } 2880 2881 static void 2882 quedefs(int action) 2883 { 2884 int i; 2885 int j; 2886 char qbuf[QBUFSIZ]; 2887 FILE *fd; 2888 2889 /* set up default queue definitions */ 2890 for (i = 0; i < NQUEUE; i++) { 2891 qt[i].njob = qd.njob; 2892 qt[i].nice = qd.nice; 2893 qt[i].nwait = qd.nwait; 2894 } 2895 if (action == DEFAULT) 2896 return; 2897 if ((fd = fopen(QUEDEFS, "r")) == NULL) { 2898 msg("cannot open quedefs file"); 2899 msg("using default queue definitions"); 2900 return; 2901 } 2902 while (fgets(qbuf, QBUFSIZ, fd) != NULL) { 2903 if ((j = qbuf[0]-'a') < 0 || j >= NQUEUE || qbuf[1] != '.') 2904 continue; 2905 parsqdef(&qbuf[2]); 2906 qt[j].njob = qq.njob; 2907 qt[j].nice = qq.nice; 2908 qt[j].nwait = qq.nwait; 2909 } 2910 (void) fclose(fd); 2911 } 2912 2913 static void 2914 parsqdef(char *name) 2915 { 2916 int i; 2917 2918 qq = qd; 2919 while (*name) { 2920 i = 0; 2921 while (isdigit(*name)) { 2922 i *= 10; 2923 i += *name++ - '0'; 2924 } 2925 switch (*name++) { 2926 case JOBF: 2927 qq.njob = i; 2928 break; 2929 case NICEF: 2930 qq.nice = i; 2931 break; 2932 case WAITF: 2933 qq.nwait = i; 2934 break; 2935 } 2936 } 2937 } 2938 2939 /* 2940 * defaults - read defaults from /etc/default/cron 2941 */ 2942 static void 2943 defaults() 2944 { 2945 int flags; 2946 char *deflog; 2947 char *hz, *tz; 2948 2949 /* 2950 * get HZ value for environment 2951 */ 2952 if ((hz = getenv("HZ")) == (char *)NULL) 2953 (void) sprintf(hzname, "HZ=%d", HZ); 2954 else 2955 (void) snprintf(hzname, sizeof (hzname), "HZ=%s", hz); 2956 /* 2957 * get TZ value for environment 2958 */ 2959 (void) snprintf(tzone, sizeof (tzone), "TZ=%s", 2960 ((tz = getenv("TZ")) != NULL) ? tz : DEFTZ); 2961 2962 if (defopen(DEFFILE) == 0) { 2963 /* ignore case */ 2964 flags = defcntl(DC_GETFLAGS, 0); 2965 TURNOFF(flags, DC_CASE); 2966 (void) defcntl(DC_SETFLAGS, flags); 2967 2968 if (((deflog = defread("CRONLOG=")) == NULL) || 2969 (*deflog == 'N') || (*deflog == 'n')) 2970 log = 0; 2971 else 2972 log = 1; 2973 /* fix for 1087611 - allow paths to be set in defaults file */ 2974 if ((Def_path = defread("PATH=")) != NULL) { 2975 (void) strlcat(path, Def_path, LINE_MAX); 2976 } else { 2977 (void) strlcpy(path, NONROOTPATH, LINE_MAX); 2978 } 2979 if ((Def_supath = defread("SUPATH=")) != NULL) { 2980 (void) strlcat(supath, Def_supath, LINE_MAX); 2981 } else { 2982 (void) strlcpy(supath, ROOTPATH, LINE_MAX); 2983 } 2984 (void) defopen(NULL); 2985 } 2986 } 2987 2988 /* 2989 * Determine if a user entry for a job is still ok. The method used here 2990 * is a lot (about 75x) faster than using setgrent() / getgrent() 2991 * endgrent(). It should be safe because we use the sysconf to determine 2992 * the max, and it tolerates the max being 0. 2993 */ 2994 2995 static int 2996 verify_user_cred(struct usr *u) 2997 { 2998 struct passwd *pw; 2999 size_t numUsrGrps = 0; 3000 size_t numOrigGrps = 0; 3001 size_t i; 3002 int retval; 3003 3004 /* 3005 * Maximum number of groups a user may be in concurrently. This 3006 * is a value which we obtain at runtime through a sysconf() 3007 * call. 3008 */ 3009 3010 static size_t nGroupsMax = (size_t)-1; 3011 3012 /* 3013 * Arrays for cron user's group list, constructed at startup to 3014 * be nGroupsMax elements long, used for verifying user 3015 * credentials prior to execution. 3016 */ 3017 3018 static gid_t *UsrGrps; 3019 static gid_t *OrigGrps; 3020 3021 if ((pw = getpwnam(u->name)) == NULL) 3022 return (VUC_BADUSER); 3023 if (u->home != NULL) { 3024 if (strcmp(u->home, pw->pw_dir) != 0) { 3025 free(u->home); 3026 u->home = xmalloc(strlen(pw->pw_dir) + 1); 3027 (void) strcpy(u->home, pw->pw_dir); 3028 } 3029 } else { 3030 u->home = xmalloc(strlen(pw->pw_dir) + 1); 3031 (void) strcpy(u->home, pw->pw_dir); 3032 } 3033 if (u->uid != pw->pw_uid) 3034 u->uid = pw->pw_uid; 3035 if (u->gid != pw->pw_gid) 3036 u->gid = pw->pw_gid; 3037 3038 /* 3039 * Create the group id lists needed for job credential 3040 * verification. 3041 */ 3042 3043 if (nGroupsMax == (size_t)-1) { 3044 if ((nGroupsMax = sysconf(_SC_NGROUPS_MAX)) > 0) { 3045 UsrGrps = xcalloc(nGroupsMax, sizeof (gid_t)); 3046 OrigGrps = xcalloc(nGroupsMax, sizeof (gid_t)); 3047 } 3048 3049 #ifdef DEBUG 3050 (void) fprintf(stderr, "nGroupsMax = %ld\n", nGroupsMax); 3051 #endif 3052 } 3053 3054 #ifdef DEBUG 3055 (void) fprintf(stderr, "verify_user_cred (%s-%d)\n", pw->pw_name, 3056 pw->pw_uid); 3057 (void) fprintf(stderr, "verify_user_cred: pw->pw_gid = %d, " 3058 "u->gid = %d\n", pw->pw_gid, u->gid); 3059 #endif 3060 3061 retval = (u->gid == pw->pw_gid) ? VUC_OK : VUC_NOTINGROUP; 3062 3063 if (nGroupsMax > 0) { 3064 numOrigGrps = getgroups(nGroupsMax, OrigGrps); 3065 3066 (void) initgroups(pw->pw_name, pw->pw_gid); 3067 numUsrGrps = getgroups(nGroupsMax, UsrGrps); 3068 3069 for (i = 0; i < numUsrGrps; i++) { 3070 if (UsrGrps[i] == u->gid) { 3071 retval = VUC_OK; 3072 break; 3073 } 3074 } 3075 3076 if (OrigGrps) { 3077 (void) setgroups(numOrigGrps, OrigGrps); 3078 } 3079 } 3080 3081 #ifdef DEBUG 3082 (void) fprintf(stderr, "verify_user_cred: VUC = %d\n", retval); 3083 #endif 3084 3085 return (retval); 3086 } 3087 3088 static int 3089 set_user_cred(const struct usr *u, struct project *pproj) 3090 { 3091 static char *progname = "cron"; 3092 int r = 0, rval = 0; 3093 3094 if ((r = pam_start(progname, u->name, &pam_conv, &pamh)) 3095 != PAM_SUCCESS) { 3096 #ifdef DEBUG 3097 msg("pam_start returns %d\n", r); 3098 #endif 3099 rval = VUC_BADUSER; 3100 goto set_eser_cred_exit; 3101 } 3102 3103 r = pam_acct_mgmt(pamh, 0); 3104 #ifdef DEBUG 3105 msg("pam_acc_mgmt returns %d\n", r); 3106 #endif 3107 if (r == PAM_ACCT_EXPIRED) { 3108 rval = VUC_EXPIRED; 3109 goto set_eser_cred_exit; 3110 } 3111 if (r == PAM_NEW_AUTHTOK_REQD) { 3112 rval = VUC_NEW_AUTH; 3113 goto set_eser_cred_exit; 3114 } 3115 if (r != PAM_SUCCESS) { 3116 rval = VUC_BADUSER; 3117 goto set_eser_cred_exit; 3118 } 3119 3120 if (pproj != NULL) { 3121 size_t sz = sizeof (PROJECT) + strlen(pproj->pj_name); 3122 char *buf = alloca(sz); 3123 3124 (void) snprintf(buf, sz, PROJECT "%s", pproj->pj_name); 3125 (void) pam_set_item(pamh, PAM_RESOURCE, buf); 3126 } 3127 3128 r = pam_setcred(pamh, PAM_ESTABLISH_CRED); 3129 if (r != PAM_SUCCESS) 3130 rval = VUC_BADUSER; 3131 3132 set_eser_cred_exit: 3133 (void) pam_end(pamh, r); 3134 return (rval); 3135 } 3136 3137 static void 3138 clean_out_user(struct usr *u) 3139 { 3140 if (next_event->u == u) { 3141 next_event = NULL; 3142 } 3143 3144 clean_out_ctab(u); 3145 clean_out_atjobs(u); 3146 free_if_unused(u); 3147 } 3148 3149 static void 3150 clean_out_atjobs(struct usr *u) 3151 { 3152 struct event *ev, *pv; 3153 3154 for (pv = NULL, ev = u->atevents; 3155 ev != NULL; 3156 pv = ev, ev = ev->link, free(pv)) { 3157 el_remove(ev->of.at.eventid, 1); 3158 if (cwd == AT) 3159 cron_unlink(ev->cmd); 3160 else { 3161 char buf[PATH_MAX]; 3162 if (strlen(ATDIR) + strlen(ev->cmd) + 2 3163 < PATH_MAX) { 3164 (void) sprintf(buf, "%s/%s", ATDIR, ev->cmd); 3165 cron_unlink(buf); 3166 } 3167 } 3168 free(ev->cmd); 3169 } 3170 3171 u->atevents = NULL; 3172 } 3173 3174 static void 3175 clean_out_ctab(struct usr *u) 3176 { 3177 rm_ctevents(u); 3178 el_remove(u->ctid, 0); 3179 u->ctid = 0; 3180 u->ctexists = 0; 3181 } 3182 3183 static void 3184 cron_unlink(char *name) 3185 { 3186 int r; 3187 3188 r = unlink(name); 3189 if (r == 0 || (r == -1 && errno == ENOENT)) { 3190 (void) audit_cron_delete_anc_file(name, NULL); 3191 } 3192 } 3193 3194 static void 3195 create_anc_ctab(struct event *e) 3196 { 3197 if (audit_cron_create_anc_file(e->u->name, 3198 (cwd == CRON) ? NULL:CRONDIR, 3199 e->u->name, e->u->uid) == -1) { 3200 process_anc_files(CRON_ANC_DELETE); 3201 crabort("cannot create ancillary files for crontabs", 3202 REMOVE_FIFO|CONSOLE_MSG); 3203 } 3204 } 3205 3206 static void 3207 delete_anc_ctab(struct event *e) 3208 { 3209 (void) audit_cron_delete_anc_file(e->u->name, 3210 (cwd == CRON) ? NULL:CRONDIR); 3211 } 3212 3213 static void 3214 create_anc_atjob(struct event *e) 3215 { 3216 if (!e->of.at.exists) 3217 return; 3218 3219 if (audit_cron_create_anc_file(e->cmd, 3220 (cwd == AT) ? NULL:ATDIR, 3221 e->u->name, e->u->uid) == -1) { 3222 process_anc_files(CRON_ANC_DELETE); 3223 crabort("cannot create ancillary files for atjobs", 3224 REMOVE_FIFO|CONSOLE_MSG); 3225 } 3226 } 3227 3228 static void 3229 delete_anc_atjob(struct event *e) 3230 { 3231 if (!e->of.at.exists) 3232 return; 3233 3234 (void) audit_cron_delete_anc_file(e->cmd, 3235 (cwd == AT) ? NULL:ATDIR); 3236 } 3237 3238 3239 static void 3240 process_anc_files(int del) 3241 { 3242 struct usr *u = uhead; 3243 struct event *e; 3244 3245 if (!audit_cron_mode()) 3246 return; 3247 3248 for (;;) { 3249 if (u->ctexists && u->ctevents != NULL) { 3250 e = u->ctevents; 3251 for (;;) { 3252 if (del) 3253 delete_anc_ctab(e); 3254 else 3255 create_anc_ctab(e); 3256 if ((e = e->link) == NULL) 3257 break; 3258 } 3259 } 3260 3261 if (u->atevents != NULL) { 3262 e = u->atevents; 3263 for (;;) { 3264 if (del) 3265 delete_anc_atjob(e); 3266 else 3267 create_anc_atjob(e); 3268 if ((e = e->link) == NULL) 3269 break; 3270 } 3271 } 3272 3273 if ((u = u->nextusr) == NULL) 3274 break; 3275 } 3276 } 3277 3278 /*ARGSUSED*/ 3279 static int 3280 cron_conv(int num_msg, struct pam_message **msgs, 3281 struct pam_response **response, void *appdata_ptr) 3282 { 3283 struct pam_message **m = msgs; 3284 int i; 3285 3286 for (i = 0; i < num_msg; i++) { 3287 switch (m[i]->msg_style) { 3288 case PAM_ERROR_MSG: 3289 case PAM_TEXT_INFO: 3290 if (m[i]->msg != NULL) { 3291 (void) msg("%s\n", m[i]->msg); 3292 } 3293 break; 3294 3295 default: 3296 break; 3297 } 3298 } 3299 return (0); 3300 } 3301 3302 /* 3303 * Cron creates process for other than job. Mail process is the 3304 * one which rinfo does not cover. Therefore, miscpid will keep 3305 * track of the pids executed from cron. Otherwise, we will see 3306 * "unexpected pid returned.." messages appear in the log file. 3307 */ 3308 static void 3309 miscpid_insert(pid_t pid) 3310 { 3311 struct miscpid *mp; 3312 3313 mp = xmalloc(sizeof (*mp)); 3314 mp->pid = pid; 3315 mp->next = miscpid_head; 3316 miscpid_head = mp; 3317 } 3318 3319 static int 3320 miscpid_delete(pid_t pid) 3321 { 3322 struct miscpid *mp, *omp; 3323 int found = 0; 3324 3325 omp = NULL; 3326 for (mp = miscpid_head; mp != NULL; mp = mp->next) { 3327 if (mp->pid == pid) { 3328 found = 1; 3329 break; 3330 } 3331 omp = mp; 3332 } 3333 if (found) { 3334 if (omp != NULL) 3335 omp->next = mp->next; 3336 else 3337 miscpid_head = NULL; 3338 free(mp); 3339 } 3340 return (found); 3341 } 3342 3343 /* 3344 * Establish contract terms such that all children are in abandoned 3345 * process contracts. 3346 */ 3347 static void 3348 contract_set_template(void) 3349 { 3350 int fd; 3351 3352 if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0) 3353 crabort("cannot open process contract template", 3354 REMOVE_FIFO | CONSOLE_MSG); 3355 3356 if (ct_pr_tmpl_set_param(fd, 0) || 3357 ct_tmpl_set_informative(fd, 0) || 3358 ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR)) 3359 crabort("cannot establish contract template terms", 3360 REMOVE_FIFO | CONSOLE_MSG); 3361 3362 if (ct_tmpl_activate(fd)) 3363 crabort("cannot activate contract template", 3364 REMOVE_FIFO | CONSOLE_MSG); 3365 3366 (void) close(fd); 3367 } 3368 3369 /* 3370 * Clear active process contract template. 3371 */ 3372 static void 3373 contract_clear_template(void) 3374 { 3375 int fd; 3376 3377 if ((fd = open64(CTFS_ROOT "/process/template", O_RDWR)) < 0) 3378 crabort("cannot open process contract template", 3379 REMOVE_FIFO | CONSOLE_MSG); 3380 3381 if (ct_tmpl_clear(fd)) 3382 crabort("cannot clear contract template", 3383 REMOVE_FIFO | CONSOLE_MSG); 3384 3385 (void) close(fd); 3386 } 3387 3388 /* 3389 * Abandon latest process contract unconditionally. If we have leaked [some 3390 * critical amount], exit such that the kernel reaps our contracts. 3391 */ 3392 static void 3393 contract_abandon_latest(pid_t pid) 3394 { 3395 int r; 3396 ctid_t id; 3397 static uint_t cts_lost; 3398 3399 if (cts_lost > MAX_LOST_CONTRACTS) 3400 crabort("repeated failure to abandon contracts", 3401 REMOVE_FIFO | CONSOLE_MSG); 3402 3403 if (r = contract_latest(&id)) { 3404 msg("could not obtain latest contract for " 3405 "PID %ld: %s", pid, strerror(r)); 3406 cts_lost++; 3407 return; 3408 } 3409 3410 if (r = contract_abandon_id(id)) { 3411 msg("could not abandon latest contract %ld: %s", id, 3412 strerror(r)); 3413 cts_lost++; 3414 return; 3415 } 3416 } 3417