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