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