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