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