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