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