1 | /* vi: set sw=4 ts=4: */
|
---|
2 | /*
|
---|
3 | * crond -d[#] -c <crondir> -f -b
|
---|
4 | *
|
---|
5 | * run as root, but NOT setuid root
|
---|
6 | *
|
---|
7 | * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
|
---|
8 | * (version 2.3.2)
|
---|
9 | * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
|
---|
10 | *
|
---|
11 | * Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
|
---|
12 | */
|
---|
13 |
|
---|
14 | #include <sys/syslog.h>
|
---|
15 | #include "libbb.h"
|
---|
16 |
|
---|
17 | #ifndef CRONTABS
|
---|
18 | #define CRONTABS "/var/spool/cron/crontabs"
|
---|
19 | #endif
|
---|
20 | #ifndef TMPDIR
|
---|
21 | #define TMPDIR "/var/spool/cron"
|
---|
22 | #endif
|
---|
23 | #ifndef SENDMAIL
|
---|
24 | #define SENDMAIL "sendmail"
|
---|
25 | #endif
|
---|
26 | #ifndef SENDMAIL_ARGS
|
---|
27 | #define SENDMAIL_ARGS "-ti", "oem"
|
---|
28 | #endif
|
---|
29 | #ifndef CRONUPDATE
|
---|
30 | #define CRONUPDATE "cron.update"
|
---|
31 | #endif
|
---|
32 | #ifndef MAXLINES
|
---|
33 | #define MAXLINES 256 /* max lines in non-root crontabs */
|
---|
34 | #endif
|
---|
35 |
|
---|
36 | typedef struct CronFile {
|
---|
37 | struct CronFile *cf_Next;
|
---|
38 | struct CronLine *cf_LineBase;
|
---|
39 | char *cf_User; /* username */
|
---|
40 | int cf_Ready; /* bool: one or more jobs ready */
|
---|
41 | int cf_Running; /* bool: one or more jobs running */
|
---|
42 | int cf_Deleted; /* marked for deletion, ignore */
|
---|
43 | } CronFile;
|
---|
44 |
|
---|
45 | typedef struct CronLine {
|
---|
46 | struct CronLine *cl_Next;
|
---|
47 | char *cl_Shell; /* shell command */
|
---|
48 | pid_t cl_Pid; /* running pid, 0, or armed (-1) */
|
---|
49 | int cl_MailFlag; /* running pid is for mail */
|
---|
50 | int cl_MailPos; /* 'empty file' size */
|
---|
51 | char cl_Mins[60]; /* 0-59 */
|
---|
52 | char cl_Hrs[24]; /* 0-23 */
|
---|
53 | char cl_Days[32]; /* 1-31 */
|
---|
54 | char cl_Mons[12]; /* 0-11 */
|
---|
55 | char cl_Dow[7]; /* 0-6, beginning sunday */
|
---|
56 | } CronLine;
|
---|
57 |
|
---|
58 | #define RUN_RANOUT 1
|
---|
59 | #define RUN_RUNNING 2
|
---|
60 | #define RUN_FAILED 3
|
---|
61 |
|
---|
62 | #define DaemonUid 0
|
---|
63 |
|
---|
64 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
65 | static unsigned DebugOpt;
|
---|
66 | #endif
|
---|
67 |
|
---|
68 | static unsigned LogLevel = 8;
|
---|
69 | static const char *LogFile;
|
---|
70 | static const char *CDir = CRONTABS;
|
---|
71 |
|
---|
72 | static void startlogger(void);
|
---|
73 |
|
---|
74 | static void CheckUpdates(void);
|
---|
75 | static void SynchronizeDir(void);
|
---|
76 | static int TestJobs(time_t t1, time_t t2);
|
---|
77 | static void RunJobs(void);
|
---|
78 | static int CheckJobs(void);
|
---|
79 |
|
---|
80 | static void RunJob(const char *user, CronLine * line);
|
---|
81 |
|
---|
82 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
|
---|
83 | static void EndJob(const char *user, CronLine * line);
|
---|
84 | #else
|
---|
85 | #define EndJob(user, line) line->cl_Pid = 0
|
---|
86 | #endif
|
---|
87 |
|
---|
88 | static void DeleteFile(const char *userName);
|
---|
89 |
|
---|
90 | static CronFile *FileBase;
|
---|
91 |
|
---|
92 |
|
---|
93 | static void crondlog(const char *ctl, ...)
|
---|
94 | {
|
---|
95 | va_list va;
|
---|
96 | const char *fmt;
|
---|
97 | int level = (int) (ctl[0] & 0xf);
|
---|
98 | int type = level == 20 ?
|
---|
99 | LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE);
|
---|
100 |
|
---|
101 |
|
---|
102 | va_start(va, ctl);
|
---|
103 | fmt = ctl + 1;
|
---|
104 | if (level >= LogLevel) {
|
---|
105 |
|
---|
106 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
107 | if (DebugOpt) {
|
---|
108 | vfprintf(stderr, fmt, va);
|
---|
109 | } else
|
---|
110 | #endif
|
---|
111 | if (LogFile == 0) {
|
---|
112 | vsyslog(type, fmt, va);
|
---|
113 | } else {
|
---|
114 | #if !ENABLE_DEBUG_CROND_OPTION
|
---|
115 | int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
|
---|
116 | #else
|
---|
117 | int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
|
---|
118 | #endif
|
---|
119 | if (logfd >= 0) {
|
---|
120 | vdprintf(logfd, fmt, va);
|
---|
121 | close(logfd);
|
---|
122 | }
|
---|
123 | }
|
---|
124 | }
|
---|
125 | va_end(va);
|
---|
126 | if (ctl[0] & 0200) {
|
---|
127 | exit(20);
|
---|
128 | }
|
---|
129 | }
|
---|
130 |
|
---|
131 | int crond_main(int ac, char **av);
|
---|
132 | int crond_main(int ac, char **av)
|
---|
133 | {
|
---|
134 | unsigned opt;
|
---|
135 | char *lopt, *Lopt, *copt;
|
---|
136 | USE_DEBUG_CROND_OPTION(char *dopt;)
|
---|
137 |
|
---|
138 | opt_complementary = "f-b:b-f:S-L:L-S" USE_DEBUG_CROND_OPTION(":d-l");
|
---|
139 | opterr = 0; /* disable getopt 'errors' message. */
|
---|
140 | opt = getopt32(av, "l:L:fbSc:" USE_DEBUG_CROND_OPTION("d:"),
|
---|
141 | &lopt, &Lopt, &copt USE_DEBUG_CROND_OPTION(, &dopt));
|
---|
142 | if (opt & 1) /* -l */
|
---|
143 | LogLevel = xatou(lopt);
|
---|
144 | if (opt & 2) /* -L */
|
---|
145 | if (*Lopt)
|
---|
146 | LogFile = Lopt;
|
---|
147 | if (opt & 32) /* -c */
|
---|
148 | if (*copt)
|
---|
149 | CDir = copt;
|
---|
150 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
151 | if (opt & 64) { /* -d */
|
---|
152 | DebugOpt = xatou(dopt);
|
---|
153 | LogLevel = 0;
|
---|
154 | }
|
---|
155 | #endif
|
---|
156 |
|
---|
157 | /* close stdin and stdout, stderr.
|
---|
158 | * close unused descriptors - don't need.
|
---|
159 | * optional detach from controlling terminal
|
---|
160 | */
|
---|
161 | if (!(opt & 4))
|
---|
162 | bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, av);
|
---|
163 |
|
---|
164 | xchdir(CDir);
|
---|
165 | signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */
|
---|
166 |
|
---|
167 | startlogger(); /* need if syslog mode selected */
|
---|
168 |
|
---|
169 | /*
|
---|
170 | * main loop - synchronize to 1 second after the minute, minimum sleep
|
---|
171 | * of 1 second.
|
---|
172 | */
|
---|
173 | crondlog("\011%s " BB_VER " started, log level %d\n",
|
---|
174 | applet_name, LogLevel);
|
---|
175 |
|
---|
176 | SynchronizeDir();
|
---|
177 |
|
---|
178 | {
|
---|
179 | time_t t1 = time(NULL);
|
---|
180 | time_t t2;
|
---|
181 | long dt;
|
---|
182 | int rescan = 60;
|
---|
183 | short sleep_time = 60;
|
---|
184 |
|
---|
185 | write_pidfile("/var/run/crond.pid");
|
---|
186 | for (;;) {
|
---|
187 | sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time));
|
---|
188 |
|
---|
189 | t2 = time(NULL);
|
---|
190 | dt = t2 - t1;
|
---|
191 |
|
---|
192 | /*
|
---|
193 | * The file 'cron.update' is checked to determine new cron
|
---|
194 | * jobs. The directory is rescanned once an hour to deal
|
---|
195 | * with any screwups.
|
---|
196 | *
|
---|
197 | * check for disparity. Disparities over an hour either way
|
---|
198 | * result in resynchronization. A reverse-indexed disparity
|
---|
199 | * less then an hour causes us to effectively sleep until we
|
---|
200 | * match the original time (i.e. no re-execution of jobs that
|
---|
201 | * have just been run). A forward-indexed disparity less then
|
---|
202 | * an hour causes intermediate jobs to be run, but only once
|
---|
203 | * in the worst case.
|
---|
204 | *
|
---|
205 | * when running jobs, the inequality used is greater but not
|
---|
206 | * equal to t1, and less then or equal to t2.
|
---|
207 | */
|
---|
208 |
|
---|
209 | if (--rescan == 0) {
|
---|
210 | rescan = 60;
|
---|
211 | SynchronizeDir();
|
---|
212 | }
|
---|
213 | CheckUpdates();
|
---|
214 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
215 | if (DebugOpt)
|
---|
216 | crondlog("\005Wakeup dt=%d\n", dt);
|
---|
217 | #endif
|
---|
218 | if (dt < -60 * 60 || dt > 60 * 60) {
|
---|
219 | t1 = t2;
|
---|
220 | crondlog("\111time disparity of %d minutes detected\n", dt / 60);
|
---|
221 | } else if (dt > 0) {
|
---|
222 | TestJobs(t1, t2);
|
---|
223 | RunJobs();
|
---|
224 | sleep(5);
|
---|
225 | if (CheckJobs() > 0) {
|
---|
226 | sleep_time = 10;
|
---|
227 | } else {
|
---|
228 | sleep_time = 60;
|
---|
229 | }
|
---|
230 | t1 = t2;
|
---|
231 | }
|
---|
232 | }
|
---|
233 | }
|
---|
234 | return 0; /* not reached */
|
---|
235 | }
|
---|
236 |
|
---|
237 | static int ChangeUser(const char *user)
|
---|
238 | {
|
---|
239 | struct passwd *pas;
|
---|
240 | const char *err_msg;
|
---|
241 |
|
---|
242 | /*
|
---|
243 | * Obtain password entry and change privileges
|
---|
244 | */
|
---|
245 | pas = getpwnam(user);
|
---|
246 | if (pas == 0) {
|
---|
247 | crondlog("\011failed to get uid for %s", user);
|
---|
248 | return -1;
|
---|
249 | }
|
---|
250 | setenv("USER", pas->pw_name, 1);
|
---|
251 | setenv("HOME", pas->pw_dir, 1);
|
---|
252 | setenv("SHELL", DEFAULT_SHELL, 1);
|
---|
253 |
|
---|
254 | /*
|
---|
255 | * Change running state to the user in question
|
---|
256 | */
|
---|
257 | err_msg = change_identity_e2str(pas);
|
---|
258 | if (err_msg) {
|
---|
259 | crondlog("\011%s for user %s", err_msg, user);
|
---|
260 | return -1;
|
---|
261 | }
|
---|
262 | if (chdir(pas->pw_dir) < 0) {
|
---|
263 | crondlog("\011chdir failed: %s: %m", pas->pw_dir);
|
---|
264 | if (chdir(TMPDIR) < 0) {
|
---|
265 | crondlog("\011chdir failed: %s: %m", TMPDIR);
|
---|
266 | return -1;
|
---|
267 | }
|
---|
268 | }
|
---|
269 | return pas->pw_uid;
|
---|
270 | }
|
---|
271 |
|
---|
272 | static void startlogger(void)
|
---|
273 | {
|
---|
274 | if (LogFile == 0) {
|
---|
275 | openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON);
|
---|
276 | }
|
---|
277 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
278 | else { /* test logfile */
|
---|
279 | int logfd;
|
---|
280 |
|
---|
281 | logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600);
|
---|
282 | if (logfd >= 0) {
|
---|
283 | close(logfd);
|
---|
284 | }
|
---|
285 | }
|
---|
286 | #endif
|
---|
287 | }
|
---|
288 |
|
---|
289 |
|
---|
290 | static const char DowAry[] ALIGN1 =
|
---|
291 | "sun""mon""tue""wed""thu""fri""sat"
|
---|
292 | /* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */
|
---|
293 | ;
|
---|
294 |
|
---|
295 | static const char MonAry[] ALIGN1 =
|
---|
296 | "jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec"
|
---|
297 | /* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */
|
---|
298 | ;
|
---|
299 |
|
---|
300 | static char *ParseField(char *user, char *ary, int modvalue, int off,
|
---|
301 | const char *names, char *ptr)
|
---|
302 | /* 'names' is a pointer to a set of 3-char abbreviations */
|
---|
303 | {
|
---|
304 | char *base = ptr;
|
---|
305 | int n1 = -1;
|
---|
306 | int n2 = -1;
|
---|
307 |
|
---|
308 | if (base == NULL) {
|
---|
309 | return NULL;
|
---|
310 | }
|
---|
311 |
|
---|
312 | while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
|
---|
313 | int skip = 0;
|
---|
314 |
|
---|
315 | /* Handle numeric digit or symbol or '*' */
|
---|
316 |
|
---|
317 | if (*ptr == '*') {
|
---|
318 | n1 = 0; /* everything will be filled */
|
---|
319 | n2 = modvalue - 1;
|
---|
320 | skip = 1;
|
---|
321 | ++ptr;
|
---|
322 | } else if (*ptr >= '0' && *ptr <= '9') {
|
---|
323 | if (n1 < 0) {
|
---|
324 | n1 = strtol(ptr, &ptr, 10) + off;
|
---|
325 | } else {
|
---|
326 | n2 = strtol(ptr, &ptr, 10) + off;
|
---|
327 | }
|
---|
328 | skip = 1;
|
---|
329 | } else if (names) {
|
---|
330 | int i;
|
---|
331 |
|
---|
332 | for (i = 0; names[i]; i += 3) {
|
---|
333 | /* was using strncmp before... */
|
---|
334 | if (strncasecmp(ptr, &names[i], 3) == 0) {
|
---|
335 | ptr += 3;
|
---|
336 | if (n1 < 0) {
|
---|
337 | n1 = i / 3;
|
---|
338 | } else {
|
---|
339 | n2 = i / 3;
|
---|
340 | }
|
---|
341 | skip = 1;
|
---|
342 | break;
|
---|
343 | }
|
---|
344 | }
|
---|
345 | }
|
---|
346 |
|
---|
347 | /* handle optional range '-' */
|
---|
348 |
|
---|
349 | if (skip == 0) {
|
---|
350 | crondlog("\111failed user %s parsing %s\n", user, base);
|
---|
351 | return NULL;
|
---|
352 | }
|
---|
353 | if (*ptr == '-' && n2 < 0) {
|
---|
354 | ++ptr;
|
---|
355 | continue;
|
---|
356 | }
|
---|
357 |
|
---|
358 | /*
|
---|
359 | * collapse single-value ranges, handle skipmark, and fill
|
---|
360 | * in the character array appropriately.
|
---|
361 | */
|
---|
362 |
|
---|
363 | if (n2 < 0) {
|
---|
364 | n2 = n1;
|
---|
365 | }
|
---|
366 | if (*ptr == '/') {
|
---|
367 | skip = strtol(ptr + 1, &ptr, 10);
|
---|
368 | }
|
---|
369 | /*
|
---|
370 | * fill array, using a failsafe is the easiest way to prevent
|
---|
371 | * an endless loop
|
---|
372 | */
|
---|
373 |
|
---|
374 | {
|
---|
375 | int s0 = 1;
|
---|
376 | int failsafe = 1024;
|
---|
377 |
|
---|
378 | --n1;
|
---|
379 | do {
|
---|
380 | n1 = (n1 + 1) % modvalue;
|
---|
381 |
|
---|
382 | if (--s0 == 0) {
|
---|
383 | ary[n1 % modvalue] = 1;
|
---|
384 | s0 = skip;
|
---|
385 | }
|
---|
386 | }
|
---|
387 | while (n1 != n2 && --failsafe);
|
---|
388 |
|
---|
389 | if (failsafe == 0) {
|
---|
390 | crondlog("\111failed user %s parsing %s\n", user, base);
|
---|
391 | return NULL;
|
---|
392 | }
|
---|
393 | }
|
---|
394 | if (*ptr != ',') {
|
---|
395 | break;
|
---|
396 | }
|
---|
397 | ++ptr;
|
---|
398 | n1 = -1;
|
---|
399 | n2 = -1;
|
---|
400 | }
|
---|
401 |
|
---|
402 | if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') {
|
---|
403 | crondlog("\111failed user %s parsing %s\n", user, base);
|
---|
404 | return NULL;
|
---|
405 | }
|
---|
406 |
|
---|
407 | while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') {
|
---|
408 | ++ptr;
|
---|
409 | }
|
---|
410 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
411 | if (DebugOpt) {
|
---|
412 | int i;
|
---|
413 |
|
---|
414 | for (i = 0; i < modvalue; ++i) {
|
---|
415 | crondlog("\005%d", ary[i]);
|
---|
416 | }
|
---|
417 | crondlog("\005\n");
|
---|
418 | }
|
---|
419 | #endif
|
---|
420 |
|
---|
421 | return ptr;
|
---|
422 | }
|
---|
423 |
|
---|
424 | static void FixDayDow(CronLine * line)
|
---|
425 | {
|
---|
426 | int i;
|
---|
427 | int weekUsed = 0;
|
---|
428 | int daysUsed = 0;
|
---|
429 |
|
---|
430 | for (i = 0; i < (int)(ARRAY_SIZE(line->cl_Dow)); ++i) {
|
---|
431 | if (line->cl_Dow[i] == 0) {
|
---|
432 | weekUsed = 1;
|
---|
433 | break;
|
---|
434 | }
|
---|
435 | }
|
---|
436 | for (i = 0; i < (int)(ARRAY_SIZE(line->cl_Days)); ++i) {
|
---|
437 | if (line->cl_Days[i] == 0) {
|
---|
438 | daysUsed = 1;
|
---|
439 | break;
|
---|
440 | }
|
---|
441 | }
|
---|
442 | if (weekUsed && !daysUsed) {
|
---|
443 | memset(line->cl_Days, 0, sizeof(line->cl_Days));
|
---|
444 | }
|
---|
445 | if (daysUsed && !weekUsed) {
|
---|
446 | memset(line->cl_Dow, 0, sizeof(line->cl_Dow));
|
---|
447 | }
|
---|
448 | }
|
---|
449 |
|
---|
450 |
|
---|
451 |
|
---|
452 | static void SynchronizeFile(const char *fileName)
|
---|
453 | {
|
---|
454 | int maxEntries = MAXLINES;
|
---|
455 | int maxLines;
|
---|
456 | char buf[1024];
|
---|
457 |
|
---|
458 | if (strcmp(fileName, "root") == 0) {
|
---|
459 | maxEntries = 65535;
|
---|
460 | }
|
---|
461 | maxLines = maxEntries * 10;
|
---|
462 |
|
---|
463 | if (fileName) {
|
---|
464 | FILE *fi;
|
---|
465 |
|
---|
466 | DeleteFile(fileName);
|
---|
467 |
|
---|
468 | fi = fopen(fileName, "r");
|
---|
469 | if (fi != NULL) {
|
---|
470 | struct stat sbuf;
|
---|
471 |
|
---|
472 | if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) {
|
---|
473 | CronFile *file = xzalloc(sizeof(CronFile));
|
---|
474 | CronLine **pline;
|
---|
475 |
|
---|
476 | file->cf_User = strdup(fileName);
|
---|
477 | pline = &file->cf_LineBase;
|
---|
478 |
|
---|
479 | while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) {
|
---|
480 | CronLine line;
|
---|
481 | char *ptr;
|
---|
482 |
|
---|
483 | trim(buf);
|
---|
484 | if (buf[0] == 0 || buf[0] == '#') {
|
---|
485 | continue;
|
---|
486 | }
|
---|
487 | if (--maxEntries == 0) {
|
---|
488 | break;
|
---|
489 | }
|
---|
490 | memset(&line, 0, sizeof(line));
|
---|
491 |
|
---|
492 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
493 | if (DebugOpt) {
|
---|
494 | crondlog("\111User %s Entry %s\n", fileName, buf);
|
---|
495 | }
|
---|
496 | #endif
|
---|
497 |
|
---|
498 | /* parse date ranges */
|
---|
499 | ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf);
|
---|
500 | ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr);
|
---|
501 | ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr);
|
---|
502 | ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr);
|
---|
503 | ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr);
|
---|
504 |
|
---|
505 | /* check failure */
|
---|
506 | if (ptr == NULL) {
|
---|
507 | continue;
|
---|
508 | }
|
---|
509 |
|
---|
510 | /*
|
---|
511 | * fix days and dow - if one is not * and the other
|
---|
512 | * is *, the other is set to 0, and vise-versa
|
---|
513 | */
|
---|
514 |
|
---|
515 | FixDayDow(&line);
|
---|
516 |
|
---|
517 | *pline = xzalloc(sizeof(CronLine));
|
---|
518 | **pline = line;
|
---|
519 |
|
---|
520 | /* copy command */
|
---|
521 | (*pline)->cl_Shell = strdup(ptr);
|
---|
522 |
|
---|
523 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
524 | if (DebugOpt) {
|
---|
525 | crondlog("\111 Command %s\n", ptr);
|
---|
526 | }
|
---|
527 | #endif
|
---|
528 |
|
---|
529 | pline = &((*pline)->cl_Next);
|
---|
530 | }
|
---|
531 | *pline = NULL;
|
---|
532 |
|
---|
533 | file->cf_Next = FileBase;
|
---|
534 | FileBase = file;
|
---|
535 |
|
---|
536 | if (maxLines == 0 || maxEntries == 0) {
|
---|
537 | crondlog("\111Maximum number of lines reached for user %s\n", fileName);
|
---|
538 | }
|
---|
539 | }
|
---|
540 | fclose(fi);
|
---|
541 | }
|
---|
542 | }
|
---|
543 | }
|
---|
544 |
|
---|
545 | static void CheckUpdates(void)
|
---|
546 | {
|
---|
547 | FILE *fi;
|
---|
548 | char buf[256];
|
---|
549 |
|
---|
550 | fi = fopen(CRONUPDATE, "r");
|
---|
551 | if (fi != NULL) {
|
---|
552 | remove(CRONUPDATE);
|
---|
553 | while (fgets(buf, sizeof(buf), fi) != NULL) {
|
---|
554 | SynchronizeFile(strtok(buf, " \t\r\n"));
|
---|
555 | }
|
---|
556 | fclose(fi);
|
---|
557 | }
|
---|
558 | }
|
---|
559 |
|
---|
560 | static void SynchronizeDir(void)
|
---|
561 | {
|
---|
562 | /* Attempt to delete the database. */
|
---|
563 |
|
---|
564 | for (;;) {
|
---|
565 | CronFile *file;
|
---|
566 |
|
---|
567 | for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next);
|
---|
568 | if (file == NULL) {
|
---|
569 | break;
|
---|
570 | }
|
---|
571 | DeleteFile(file->cf_User);
|
---|
572 | }
|
---|
573 |
|
---|
574 | /*
|
---|
575 | * Remove cron update file
|
---|
576 | *
|
---|
577 | * Re-chdir, in case directory was renamed & deleted, or otherwise
|
---|
578 | * screwed up.
|
---|
579 | *
|
---|
580 | * scan directory and add associated users
|
---|
581 | */
|
---|
582 |
|
---|
583 | remove(CRONUPDATE);
|
---|
584 | if (chdir(CDir) < 0) {
|
---|
585 | crondlog("\311cannot find %s\n", CDir);
|
---|
586 | }
|
---|
587 | {
|
---|
588 | DIR *dir = opendir(".");
|
---|
589 | struct dirent *den;
|
---|
590 |
|
---|
591 | if (dir) {
|
---|
592 | while ((den = readdir(dir))) {
|
---|
593 | if (strchr(den->d_name, '.') != NULL) {
|
---|
594 | continue;
|
---|
595 | }
|
---|
596 | if (getpwnam(den->d_name)) {
|
---|
597 | SynchronizeFile(den->d_name);
|
---|
598 | } else {
|
---|
599 | crondlog("\007ignoring %s\n", den->d_name);
|
---|
600 | }
|
---|
601 | }
|
---|
602 | closedir(dir);
|
---|
603 | } else {
|
---|
604 | crondlog("\311cannot open current dir!\n");
|
---|
605 | }
|
---|
606 | }
|
---|
607 | }
|
---|
608 |
|
---|
609 |
|
---|
610 | /*
|
---|
611 | * DeleteFile() - delete user database
|
---|
612 | *
|
---|
613 | * Note: multiple entries for same user may exist if we were unable to
|
---|
614 | * completely delete a database due to running processes.
|
---|
615 | */
|
---|
616 |
|
---|
617 | static void DeleteFile(const char *userName)
|
---|
618 | {
|
---|
619 | CronFile **pfile = &FileBase;
|
---|
620 | CronFile *file;
|
---|
621 |
|
---|
622 | while ((file = *pfile) != NULL) {
|
---|
623 | if (strcmp(userName, file->cf_User) == 0) {
|
---|
624 | CronLine **pline = &file->cf_LineBase;
|
---|
625 | CronLine *line;
|
---|
626 |
|
---|
627 | file->cf_Running = 0;
|
---|
628 | file->cf_Deleted = 1;
|
---|
629 |
|
---|
630 | while ((line = *pline) != NULL) {
|
---|
631 | if (line->cl_Pid > 0) {
|
---|
632 | file->cf_Running = 1;
|
---|
633 | pline = &line->cl_Next;
|
---|
634 | } else {
|
---|
635 | *pline = line->cl_Next;
|
---|
636 | free(line->cl_Shell);
|
---|
637 | free(line);
|
---|
638 | }
|
---|
639 | }
|
---|
640 | if (file->cf_Running == 0) {
|
---|
641 | *pfile = file->cf_Next;
|
---|
642 | free(file->cf_User);
|
---|
643 | free(file);
|
---|
644 | } else {
|
---|
645 | pfile = &file->cf_Next;
|
---|
646 | }
|
---|
647 | } else {
|
---|
648 | pfile = &file->cf_Next;
|
---|
649 | }
|
---|
650 | }
|
---|
651 | }
|
---|
652 |
|
---|
653 | /*
|
---|
654 | * TestJobs()
|
---|
655 | *
|
---|
656 | * determine which jobs need to be run. Under normal conditions, the
|
---|
657 | * period is about a minute (one scan). Worst case it will be one
|
---|
658 | * hour (60 scans).
|
---|
659 | */
|
---|
660 |
|
---|
661 | static int TestJobs(time_t t1, time_t t2)
|
---|
662 | {
|
---|
663 | int nJobs = 0;
|
---|
664 | time_t t;
|
---|
665 |
|
---|
666 | /* Find jobs > t1 and <= t2 */
|
---|
667 |
|
---|
668 | for (t = t1 - t1 % 60; t <= t2; t += 60) {
|
---|
669 | if (t > t1) {
|
---|
670 | struct tm *tp = localtime(&t);
|
---|
671 | CronFile *file;
|
---|
672 | CronLine *line;
|
---|
673 |
|
---|
674 | for (file = FileBase; file; file = file->cf_Next) {
|
---|
675 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
676 | if (DebugOpt)
|
---|
677 | crondlog("\005FILE %s:\n", file->cf_User);
|
---|
678 | #endif
|
---|
679 | if (file->cf_Deleted)
|
---|
680 | continue;
|
---|
681 | for (line = file->cf_LineBase; line; line = line->cl_Next) {
|
---|
682 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
683 | if (DebugOpt)
|
---|
684 | crondlog("\005 LINE %s\n", line->cl_Shell);
|
---|
685 | #endif
|
---|
686 | if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] &&
|
---|
687 | (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday])
|
---|
688 | && line->cl_Mons[tp->tm_mon]) {
|
---|
689 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
690 | if (DebugOpt) {
|
---|
691 | crondlog("\005 JobToDo: %d %s\n",
|
---|
692 | line->cl_Pid, line->cl_Shell);
|
---|
693 | }
|
---|
694 | #endif
|
---|
695 | if (line->cl_Pid > 0) {
|
---|
696 | crondlog("\010 process already running: %s %s\n",
|
---|
697 | file->cf_User, line->cl_Shell);
|
---|
698 | } else if (line->cl_Pid == 0) {
|
---|
699 | line->cl_Pid = -1;
|
---|
700 | file->cf_Ready = 1;
|
---|
701 | ++nJobs;
|
---|
702 | }
|
---|
703 | }
|
---|
704 | }
|
---|
705 | }
|
---|
706 | }
|
---|
707 | }
|
---|
708 | return nJobs;
|
---|
709 | }
|
---|
710 |
|
---|
711 | static void RunJobs(void)
|
---|
712 | {
|
---|
713 | CronFile *file;
|
---|
714 | CronLine *line;
|
---|
715 |
|
---|
716 | for (file = FileBase; file; file = file->cf_Next) {
|
---|
717 | if (file->cf_Ready) {
|
---|
718 | file->cf_Ready = 0;
|
---|
719 |
|
---|
720 | for (line = file->cf_LineBase; line; line = line->cl_Next) {
|
---|
721 | if (line->cl_Pid < 0) {
|
---|
722 |
|
---|
723 | RunJob(file->cf_User, line);
|
---|
724 |
|
---|
725 | crondlog("\010USER %s pid %3d cmd %s\n",
|
---|
726 | file->cf_User, line->cl_Pid, line->cl_Shell);
|
---|
727 | if (line->cl_Pid < 0) {
|
---|
728 | file->cf_Ready = 1;
|
---|
729 | }
|
---|
730 | else if (line->cl_Pid > 0) {
|
---|
731 | file->cf_Running = 1;
|
---|
732 | }
|
---|
733 | }
|
---|
734 | }
|
---|
735 | }
|
---|
736 | }
|
---|
737 | }
|
---|
738 |
|
---|
739 | /*
|
---|
740 | * CheckJobs() - check for job completion
|
---|
741 | *
|
---|
742 | * Check for job completion, return number of jobs still running after
|
---|
743 | * all done.
|
---|
744 | */
|
---|
745 |
|
---|
746 | static int CheckJobs(void)
|
---|
747 | {
|
---|
748 | CronFile *file;
|
---|
749 | CronLine *line;
|
---|
750 | int nStillRunning = 0;
|
---|
751 |
|
---|
752 | for (file = FileBase; file; file = file->cf_Next) {
|
---|
753 | if (file->cf_Running) {
|
---|
754 | file->cf_Running = 0;
|
---|
755 |
|
---|
756 | for (line = file->cf_LineBase; line; line = line->cl_Next) {
|
---|
757 | if (line->cl_Pid > 0) {
|
---|
758 | int status;
|
---|
759 | int r = wait4(line->cl_Pid, &status, WNOHANG, NULL);
|
---|
760 |
|
---|
761 | if (r < 0 || r == line->cl_Pid) {
|
---|
762 | EndJob(file->cf_User, line);
|
---|
763 | if (line->cl_Pid) {
|
---|
764 | file->cf_Running = 1;
|
---|
765 | }
|
---|
766 | } else if (r == 0) {
|
---|
767 | file->cf_Running = 1;
|
---|
768 | }
|
---|
769 | }
|
---|
770 | }
|
---|
771 | }
|
---|
772 | nStillRunning += file->cf_Running;
|
---|
773 | }
|
---|
774 | return nStillRunning;
|
---|
775 | }
|
---|
776 |
|
---|
777 |
|
---|
778 | #if ENABLE_FEATURE_CROND_CALL_SENDMAIL
|
---|
779 | static void
|
---|
780 | ForkJob(const char *user, CronLine * line, int mailFd,
|
---|
781 | const char *prog, const char *cmd, const char *arg, const char *mailf)
|
---|
782 | {
|
---|
783 | /* Fork as the user in question and run program */
|
---|
784 | pid_t pid = fork();
|
---|
785 |
|
---|
786 | line->cl_Pid = pid;
|
---|
787 | if (pid == 0) {
|
---|
788 | /* CHILD */
|
---|
789 |
|
---|
790 | /* Change running state to the user in question */
|
---|
791 |
|
---|
792 | if (ChangeUser(user) < 0) {
|
---|
793 | exit(0);
|
---|
794 | }
|
---|
795 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
796 | if (DebugOpt) {
|
---|
797 | crondlog("\005Child Running %s\n", prog);
|
---|
798 | }
|
---|
799 | #endif
|
---|
800 |
|
---|
801 | if (mailFd >= 0) {
|
---|
802 | dup2(mailFd, mailf != NULL);
|
---|
803 | dup2((mailf ? mailFd : 1), 2);
|
---|
804 | close(mailFd);
|
---|
805 | }
|
---|
806 | execl(prog, prog, cmd, arg, NULL);
|
---|
807 | crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg);
|
---|
808 | if (mailf) {
|
---|
809 | fdprintf(1, "Exec failed: %s -c %s\n", prog, arg);
|
---|
810 | }
|
---|
811 | exit(0);
|
---|
812 | } else if (pid < 0) {
|
---|
813 | /* FORK FAILED */
|
---|
814 | crondlog("\024cannot fork, user %s\n", user);
|
---|
815 | line->cl_Pid = 0;
|
---|
816 | if (mailf) {
|
---|
817 | remove(mailf);
|
---|
818 | }
|
---|
819 | } else if (mailf) {
|
---|
820 | /* PARENT, FORK SUCCESS
|
---|
821 | * rename mail-file based on pid of process
|
---|
822 | */
|
---|
823 | char mailFile2[128];
|
---|
824 |
|
---|
825 | snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid);
|
---|
826 | rename(mailf, mailFile2);
|
---|
827 | }
|
---|
828 | /*
|
---|
829 | * Close the mail file descriptor.. we can't just leave it open in
|
---|
830 | * a structure, closing it later, because we might run out of descriptors
|
---|
831 | */
|
---|
832 |
|
---|
833 | if (mailFd >= 0) {
|
---|
834 | close(mailFd);
|
---|
835 | }
|
---|
836 | }
|
---|
837 |
|
---|
838 | static void RunJob(const char *user, CronLine * line)
|
---|
839 | {
|
---|
840 | char mailFile[128];
|
---|
841 | int mailFd;
|
---|
842 |
|
---|
843 | line->cl_Pid = 0;
|
---|
844 | line->cl_MailFlag = 0;
|
---|
845 |
|
---|
846 | /* open mail file - owner root so nobody can screw with it. */
|
---|
847 |
|
---|
848 | snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid());
|
---|
849 | mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600);
|
---|
850 |
|
---|
851 | if (mailFd >= 0) {
|
---|
852 | line->cl_MailFlag = 1;
|
---|
853 | fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user,
|
---|
854 | line->cl_Shell);
|
---|
855 | line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR);
|
---|
856 | } else {
|
---|
857 | crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile);
|
---|
858 | }
|
---|
859 |
|
---|
860 | ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile);
|
---|
861 | }
|
---|
862 |
|
---|
863 | /*
|
---|
864 | * EndJob - called when job terminates and when mail terminates
|
---|
865 | */
|
---|
866 |
|
---|
867 | static void EndJob(const char *user, CronLine * line)
|
---|
868 | {
|
---|
869 | int mailFd;
|
---|
870 | char mailFile[128];
|
---|
871 | struct stat sbuf;
|
---|
872 |
|
---|
873 | /* No job */
|
---|
874 |
|
---|
875 | if (line->cl_Pid <= 0) {
|
---|
876 | line->cl_Pid = 0;
|
---|
877 | return;
|
---|
878 | }
|
---|
879 |
|
---|
880 | /*
|
---|
881 | * End of job and no mail file
|
---|
882 | * End of sendmail job
|
---|
883 | */
|
---|
884 |
|
---|
885 | snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid);
|
---|
886 | line->cl_Pid = 0;
|
---|
887 |
|
---|
888 | if (line->cl_MailFlag != 1) {
|
---|
889 | return;
|
---|
890 | }
|
---|
891 | line->cl_MailFlag = 0;
|
---|
892 |
|
---|
893 | /*
|
---|
894 | * End of primary job - check for mail file. If size has increased and
|
---|
895 | * the file is still valid, we sendmail it.
|
---|
896 | */
|
---|
897 |
|
---|
898 | mailFd = open(mailFile, O_RDONLY);
|
---|
899 | remove(mailFile);
|
---|
900 | if (mailFd < 0) {
|
---|
901 | return;
|
---|
902 | }
|
---|
903 |
|
---|
904 | if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid
|
---|
905 | || sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos
|
---|
906 | || !S_ISREG(sbuf.st_mode)
|
---|
907 | ) {
|
---|
908 | close(mailFd);
|
---|
909 | return;
|
---|
910 | }
|
---|
911 | ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL);
|
---|
912 | }
|
---|
913 | #else
|
---|
914 | /* crond without sendmail */
|
---|
915 |
|
---|
916 | static void RunJob(const char *user, CronLine * line)
|
---|
917 | {
|
---|
918 | /* Fork as the user in question and run program */
|
---|
919 | pid_t pid = fork();
|
---|
920 |
|
---|
921 | if (pid == 0) {
|
---|
922 | /* CHILD */
|
---|
923 |
|
---|
924 | /* Change running state to the user in question */
|
---|
925 |
|
---|
926 | if (ChangeUser(user) < 0) {
|
---|
927 | exit(0);
|
---|
928 | }
|
---|
929 | #if ENABLE_DEBUG_CROND_OPTION
|
---|
930 | if (DebugOpt) {
|
---|
931 | crondlog("\005Child Running %s\n", DEFAULT_SHELL);
|
---|
932 | }
|
---|
933 | #endif
|
---|
934 |
|
---|
935 | execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL);
|
---|
936 | crondlog("\024cannot exec, user %s cmd %s -c %s\n", user,
|
---|
937 | DEFAULT_SHELL, line->cl_Shell);
|
---|
938 | exit(0);
|
---|
939 | } else if (pid < 0) {
|
---|
940 | /* FORK FAILED */
|
---|
941 | crondlog("\024cannot, user %s\n", user);
|
---|
942 | pid = 0;
|
---|
943 | }
|
---|
944 | line->cl_Pid = pid;
|
---|
945 | }
|
---|
946 | #endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */
|
---|