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