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