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