5 |
* run as root, but NOT setuid root |
* run as root, but NOT setuid root |
6 |
* |
* |
7 |
* Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) |
* Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com) |
8 |
|
* (version 2.3.2) |
9 |
* Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 |
* Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 |
10 |
* |
* |
11 |
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. |
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball. |
12 |
*/ |
*/ |
13 |
|
|
14 |
#define VERSION "2.3.2" |
#include "libbb.h" |
15 |
|
#include <syslog.h> |
16 |
|
|
17 |
#include "busybox.h" |
/* glibc frees previous setenv'ed value when we do next setenv() |
18 |
#include <sys/syslog.h> |
* of the same variable. uclibc does not do this! */ |
19 |
|
#if (defined(__GLIBC__) && !defined(__UCLIBC__)) /* || OTHER_SAFE_LIBC... */ |
20 |
|
#define SETENV_LEAKS 0 |
21 |
|
#else |
22 |
|
#define SETENV_LEAKS 1 |
23 |
|
#endif |
24 |
|
|
|
#define arysize(ary) (sizeof(ary)/sizeof((ary)[0])) |
|
25 |
|
|
26 |
#ifndef CRONTABS |
#ifndef CRONTABS |
27 |
#define CRONTABS "/var/spool/cron/crontabs" |
#define CRONTABS "/var/spool/cron/crontabs" |
30 |
#define TMPDIR "/var/spool/cron" |
#define TMPDIR "/var/spool/cron" |
31 |
#endif |
#endif |
32 |
#ifndef SENDMAIL |
#ifndef SENDMAIL |
33 |
#define SENDMAIL "/usr/sbin/sendmail" |
#define SENDMAIL "sendmail" |
34 |
#endif |
#endif |
35 |
#ifndef SENDMAIL_ARGS |
#ifndef SENDMAIL_ARGS |
36 |
#define SENDMAIL_ARGS "-ti", "oem" |
#define SENDMAIL_ARGS "-ti", "oem" |
42 |
#define MAXLINES 256 /* max lines in non-root crontabs */ |
#define MAXLINES 256 /* max lines in non-root crontabs */ |
43 |
#endif |
#endif |
44 |
|
|
45 |
|
|
46 |
typedef struct CronFile { |
typedef struct CronFile { |
47 |
struct CronFile *cf_Next; |
struct CronFile *cf_Next; |
48 |
struct CronLine *cf_LineBase; |
struct CronLine *cf_LineBase; |
49 |
char *cf_User; /* username */ |
char *cf_User; /* username */ |
50 |
int cf_Ready; /* bool: one or more jobs ready */ |
smallint cf_Ready; /* bool: one or more jobs ready */ |
51 |
int cf_Running; /* bool: one or more jobs running */ |
smallint cf_Running; /* bool: one or more jobs running */ |
52 |
int cf_Deleted; /* marked for deletion, ignore */ |
smallint cf_Deleted; /* marked for deletion, ignore */ |
53 |
} CronFile; |
} CronFile; |
54 |
|
|
55 |
typedef struct CronLine { |
typedef struct CronLine { |
56 |
struct CronLine *cl_Next; |
struct CronLine *cl_Next; |
57 |
char *cl_Shell; /* shell command */ |
char *cl_Shell; /* shell command */ |
58 |
pid_t cl_Pid; /* running pid, 0, or armed (-1) */ |
pid_t cl_Pid; /* running pid, 0, or armed (-1) */ |
59 |
int cl_MailFlag; /* running pid is for mail */ |
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
60 |
int cl_MailPos; /* 'empty file' size */ |
int cl_MailPos; /* 'empty file' size */ |
61 |
char cl_Mins[60]; /* 0-59 */ |
smallint cl_MailFlag; /* running pid is for mail */ |
62 |
char cl_Hrs[24]; /* 0-23 */ |
char *cl_MailTo; /* whom to mail results */ |
63 |
char cl_Days[32]; /* 1-31 */ |
#endif |
64 |
char cl_Mons[12]; /* 0-11 */ |
/* ordered by size, not in natural order. makes code smaller: */ |
65 |
char cl_Dow[7]; /* 0-6, beginning sunday */ |
char cl_Dow[7]; /* 0-6, beginning sunday */ |
66 |
|
char cl_Mons[12]; /* 0-11 */ |
67 |
|
char cl_Hrs[24]; /* 0-23 */ |
68 |
|
char cl_Days[32]; /* 1-31 */ |
69 |
|
char cl_Mins[60]; /* 0-59 */ |
70 |
} CronLine; |
} CronLine; |
71 |
|
|
|
#define RUN_RANOUT 1 |
|
|
#define RUN_RUNNING 2 |
|
|
#define RUN_FAILED 3 |
|
72 |
|
|
73 |
#define DaemonUid 0 |
#define DaemonUid 0 |
74 |
|
|
75 |
#if ENABLE_DEBUG_CROND_OPTION |
|
76 |
static unsigned DebugOpt; |
enum { |
77 |
|
OPT_l = (1 << 0), |
78 |
|
OPT_L = (1 << 1), |
79 |
|
OPT_f = (1 << 2), |
80 |
|
OPT_b = (1 << 3), |
81 |
|
OPT_S = (1 << 4), |
82 |
|
OPT_c = (1 << 5), |
83 |
|
OPT_d = (1 << 6) * ENABLE_FEATURE_CROND_D, |
84 |
|
}; |
85 |
|
#if ENABLE_FEATURE_CROND_D |
86 |
|
#define DebugOpt (option_mask32 & OPT_d) |
87 |
|
#else |
88 |
|
#define DebugOpt 0 |
89 |
#endif |
#endif |
90 |
|
|
|
static unsigned LogLevel = 8; |
|
|
static const char *LogFile; |
|
|
static const char *CDir = CRONTABS; |
|
91 |
|
|
92 |
static void startlogger(void); |
struct globals { |
93 |
|
unsigned LogLevel; /* = 8; */ |
94 |
|
const char *LogFile; |
95 |
|
const char *CDir; /* = CRONTABS; */ |
96 |
|
CronFile *FileBase; |
97 |
|
#if SETENV_LEAKS |
98 |
|
char *env_var_user; |
99 |
|
char *env_var_home; |
100 |
|
#endif |
101 |
|
}; |
102 |
|
#define G (*(struct globals*)&bb_common_bufsiz1) |
103 |
|
#define LogLevel (G.LogLevel ) |
104 |
|
#define LogFile (G.LogFile ) |
105 |
|
#define CDir (G.CDir ) |
106 |
|
#define FileBase (G.FileBase ) |
107 |
|
#define env_var_user (G.env_var_user ) |
108 |
|
#define env_var_home (G.env_var_home ) |
109 |
|
#define INIT_G() do { \ |
110 |
|
LogLevel = 8; \ |
111 |
|
CDir = CRONTABS; \ |
112 |
|
} while (0) |
113 |
|
|
114 |
|
|
115 |
static void CheckUpdates(void); |
static void CheckUpdates(void); |
116 |
static void SynchronizeDir(void); |
static void SynchronizeDir(void); |
117 |
static int TestJobs(time_t t1, time_t t2); |
static int TestJobs(time_t t1, time_t t2); |
118 |
static void RunJobs(void); |
static void RunJobs(void); |
119 |
static int CheckJobs(void); |
static int CheckJobs(void); |
120 |
|
static void RunJob(const char *user, CronLine *line); |
|
static void RunJob(const char *user, CronLine * line); |
|
|
|
|
121 |
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
122 |
static void EndJob(const char *user, CronLine * line); |
static void EndJob(const char *user, CronLine *line); |
123 |
#else |
#else |
124 |
#define EndJob(user, line) line->cl_Pid = 0 |
#define EndJob(user, line) ((line)->cl_Pid = 0) |
125 |
#endif |
#endif |
|
|
|
126 |
static void DeleteFile(const char *userName); |
static void DeleteFile(const char *userName); |
127 |
|
|
|
static CronFile *FileBase; |
|
128 |
|
|
129 |
|
#define LVL5 "\x05" |
130 |
|
#define LVL7 "\x07" |
131 |
|
#define LVL8 "\x08" |
132 |
|
#define LVL9 "\x09" |
133 |
|
#define WARN9 "\x49" |
134 |
|
#define DIE9 "\xc9" |
135 |
|
/* level >= 20 is "error" */ |
136 |
|
#define ERR20 "\x14" |
137 |
|
|
138 |
static void crondlog(const char *ctl, ...) |
static void crondlog(const char *ctl, ...) |
139 |
{ |
{ |
140 |
va_list va; |
va_list va; |
141 |
const char *fmt; |
int level = (ctl[0] & 0x1f); |
|
int level = (int) (ctl[0] & 0xf); |
|
|
int type = level == 20 ? |
|
|
LOG_ERR : ((ctl[0] & 0100) ? LOG_WARNING : LOG_NOTICE); |
|
|
|
|
142 |
|
|
143 |
va_start(va, ctl); |
va_start(va, ctl); |
144 |
fmt = ctl + 1; |
if (level >= (int)LogLevel) { |
145 |
if (level >= LogLevel) { |
/* Debug mode: all to (non-redirected) stderr, */ |
146 |
|
/* Syslog mode: all to syslog (logmode = LOGMODE_SYSLOG), */ |
147 |
#if ENABLE_DEBUG_CROND_OPTION |
if (!DebugOpt && LogFile) { |
148 |
if (DebugOpt) { |
/* Otherwise (log to file): we reopen log file at every write: */ |
149 |
vfprintf(stderr, fmt, va); |
int logfd = open3_or_warn(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); |
150 |
} else |
if (logfd >= 0) |
151 |
#endif |
xmove_fd(logfd, STDERR_FILENO); |
|
if (LogFile == 0) { |
|
|
vsyslog(type, fmt, va); |
|
|
} else { |
|
|
int logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600); |
|
|
if (logfd >= 0) { |
|
|
vdprintf(logfd, fmt, va); |
|
|
close(logfd); |
|
|
#if ENABLE_DEBUG_CROND_OPTION |
|
|
} else { |
|
|
bb_perror_msg("can't open log file"); |
|
|
#endif |
|
|
} |
|
152 |
} |
} |
153 |
|
// TODO: ERR -> error, WARN -> warning, LVL -> info |
154 |
|
bb_verror_msg(ctl + 1, va, /* strerr: */ NULL); |
155 |
} |
} |
156 |
va_end(va); |
va_end(va); |
157 |
if (ctl[0] & 0200) { |
if (ctl[0] & 0x80) |
158 |
exit(20); |
exit(20); |
|
} |
|
159 |
} |
} |
160 |
|
|
161 |
int crond_main(int ac, char **av) |
int crond_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; |
162 |
|
int crond_main(int argc UNUSED_PARAM, char **argv) |
163 |
{ |
{ |
164 |
unsigned opt; |
unsigned opt; |
|
char *lopt, *Lopt, *copt; |
|
|
|
|
|
#if ENABLE_DEBUG_CROND_OPTION |
|
|
char *dopt; |
|
165 |
|
|
166 |
opt_complementary = "f-b:b-f:S-L:L-S:d-l"; |
INIT_G(); |
|
#else |
|
|
opt_complementary = "f-b:b-f:S-L:L-S"; |
|
|
#endif |
|
167 |
|
|
168 |
opterr = 0; /* disable getopt 'errors' message. */ |
/* "-b after -f is ignored", and so on for every pair a-b */ |
169 |
opt = getopt32(ac, av, "l:L:fbSc:" |
opt_complementary = "f-b:b-f:S-L:L-S" USE_FEATURE_CROND_D(":d-l") |
170 |
#if ENABLE_DEBUG_CROND_OPTION |
":l+:d+"; /* -l and -d have numeric param */ |
171 |
"d:" |
opt = getopt32(argv, "l:L:fbSc:" USE_FEATURE_CROND_D("d:"), |
172 |
#endif |
&LogLevel, &LogFile, &CDir |
173 |
, &lopt, &Lopt, &copt |
USE_FEATURE_CROND_D(,&LogLevel)); |
174 |
#if ENABLE_DEBUG_CROND_OPTION |
/* both -d N and -l N set the same variable: LogLevel */ |
175 |
, &dopt |
|
176 |
#endif |
if (!(opt & OPT_f)) { |
177 |
); |
/* close stdin, stdout, stderr. |
178 |
if (opt & 1) { |
* close unused descriptors - don't need them. */ |
179 |
LogLevel = xatou(lopt); |
bb_daemonize_or_rexec(DAEMON_CLOSE_EXTRA_FDS, argv); |
180 |
} |
} |
|
if (opt & 2) { |
|
|
if (*Lopt != 0) { |
|
|
LogFile = Lopt; |
|
|
} |
|
|
} |
|
|
if (opt & 32) { |
|
|
if (*copt != 0) { |
|
|
CDir = copt; |
|
|
} |
|
|
} |
|
|
#if ENABLE_DEBUG_CROND_OPTION |
|
|
if (opt & 64) { |
|
|
DebugOpt = xatou(dopt); |
|
|
LogLevel = 0; |
|
|
} |
|
|
#endif |
|
|
|
|
|
/* |
|
|
* change directory |
|
|
*/ |
|
181 |
|
|
182 |
xchdir(CDir); |
if (!DebugOpt && LogFile == NULL) { |
183 |
signal(SIGHUP, SIG_IGN); /* hmm.. but, if kill -HUP original |
/* logging to syslog */ |
184 |
* version - his died. ;( |
openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); |
185 |
*/ |
logmode = LOGMODE_SYSLOG; |
|
/* |
|
|
* close stdin and stdout, stderr. |
|
|
* close unused descriptors - don't need. |
|
|
* optional detach from controlling terminal |
|
|
*/ |
|
|
|
|
|
if (!(opt & 4)) { |
|
|
#ifdef BB_NOMMU |
|
|
/* reexec for vfork() do continue parent */ |
|
|
vfork_daemon_rexec(1, 0, ac, av, "-f"); |
|
|
#else |
|
|
xdaemon(1, 0); |
|
|
#endif |
|
186 |
} |
} |
187 |
|
|
188 |
(void) startlogger(); /* need if syslog mode selected */ |
xchdir(CDir); |
189 |
|
//signal(SIGHUP, SIG_IGN); /* ? original crond dies on HUP... */ |
190 |
/* |
xsetenv("SHELL", DEFAULT_SHELL); /* once, for all future children */ |
191 |
* main loop - synchronize to 1 second after the minute, minimum sleep |
crondlog(LVL9 "crond (busybox "BB_VER") started, log level %d", LogLevel); |
|
* of 1 second. |
|
|
*/ |
|
|
|
|
|
crondlog("\011%s " VERSION " dillon, started, log level %d\n", |
|
|
applet_name, LogLevel); |
|
|
|
|
192 |
SynchronizeDir(); |
SynchronizeDir(); |
193 |
|
|
194 |
|
/* main loop - synchronize to 1 second after the minute, minimum sleep |
195 |
|
* of 1 second. */ |
196 |
{ |
{ |
197 |
time_t t1 = time(NULL); |
time_t t1 = time(NULL); |
198 |
time_t t2; |
time_t t2; |
199 |
long dt; |
long dt; |
200 |
int rescan = 60; |
int rescan = 60; |
201 |
short sleep_time = 60; |
int sleep_time = 60; |
202 |
|
|
203 |
|
write_pidfile("/var/run/crond.pid"); |
204 |
for (;;) { |
for (;;) { |
205 |
sleep((sleep_time + 1) - (short) (time(NULL) % sleep_time)); |
sleep((sleep_time + 1) - (time(NULL) % sleep_time)); |
206 |
|
|
207 |
t2 = time(NULL); |
t2 = time(NULL); |
208 |
dt = t2 - t1; |
dt = (long)t2 - (long)t1; |
209 |
|
|
210 |
/* |
/* |
211 |
* The file 'cron.update' is checked to determine new cron |
* The file 'cron.update' is checked to determine new cron |
223 |
* when running jobs, the inequality used is greater but not |
* when running jobs, the inequality used is greater but not |
224 |
* equal to t1, and less then or equal to t2. |
* equal to t1, and less then or equal to t2. |
225 |
*/ |
*/ |
|
|
|
226 |
if (--rescan == 0) { |
if (--rescan == 0) { |
227 |
rescan = 60; |
rescan = 60; |
228 |
SynchronizeDir(); |
SynchronizeDir(); |
229 |
} |
} |
230 |
CheckUpdates(); |
CheckUpdates(); |
|
#if ENABLE_DEBUG_CROND_OPTION |
|
231 |
if (DebugOpt) |
if (DebugOpt) |
232 |
crondlog("\005Wakeup dt=%d\n", dt); |
crondlog(LVL5 "wakeup dt=%ld", dt); |
|
#endif |
|
233 |
if (dt < -60 * 60 || dt > 60 * 60) { |
if (dt < -60 * 60 || dt > 60 * 60) { |
234 |
t1 = t2; |
crondlog(WARN9 "time disparity of %d minutes detected", dt / 60); |
|
crondlog("\111time disparity of %d minutes detected\n", dt / 60); |
|
235 |
} else if (dt > 0) { |
} else if (dt > 0) { |
236 |
TestJobs(t1, t2); |
TestJobs(t1, t2); |
237 |
RunJobs(); |
RunJobs(); |
241 |
} else { |
} else { |
242 |
sleep_time = 60; |
sleep_time = 60; |
243 |
} |
} |
|
t1 = t2; |
|
244 |
} |
} |
245 |
|
t1 = t2; |
246 |
} |
} |
247 |
} |
} |
248 |
return 0; /* not reached */ |
return 0; /* not reached */ |
249 |
} |
} |
250 |
|
|
251 |
static int ChangeUser(const char *user) |
#if SETENV_LEAKS |
252 |
|
/* We set environment *before* vfork (because we want to use vfork), |
253 |
|
* so we cannot use setenv() - repeated calls to setenv() may leak memory! |
254 |
|
* Using putenv(), and freeing memory after unsetenv() won't leak */ |
255 |
|
static void safe_setenv4(char **pvar_val, const char *var, const char *val /*, int len*/) |
256 |
{ |
{ |
257 |
struct passwd *pas; |
const int len = 4; /* both var names are 4 char long */ |
258 |
const char *err_msg; |
char *var_val = *pvar_val; |
|
|
|
|
/* |
|
|
* Obtain password entry and change privileges |
|
|
*/ |
|
|
pas = getpwnam(user); |
|
|
if (pas == 0) { |
|
|
crondlog("\011failed to get uid for %s", user); |
|
|
return -1; |
|
|
} |
|
|
setenv("USER", pas->pw_name, 1); |
|
|
setenv("HOME", pas->pw_dir, 1); |
|
|
setenv("SHELL", DEFAULT_SHELL, 1); |
|
259 |
|
|
260 |
/* |
if (var_val) { |
261 |
* Change running state to the user in question |
var_val[len] = '\0'; /* nuke '=' */ |
262 |
*/ |
unsetenv(var_val); |
263 |
err_msg = change_identity_e2str(pas); |
free(var_val); |
|
if (err_msg) { |
|
|
crondlog("\011%s for user %s", err_msg, user); |
|
|
return -1; |
|
|
} |
|
|
if (chdir(pas->pw_dir) < 0) { |
|
|
crondlog("\011chdir failed: %s: %m", pas->pw_dir); |
|
|
if (chdir(TMPDIR) < 0) { |
|
|
crondlog("\011chdir failed: %s: %m", TMPDIR); |
|
|
return -1; |
|
|
} |
|
264 |
} |
} |
265 |
return pas->pw_uid; |
*pvar_val = xasprintf("%s=%s", var, val); |
266 |
|
putenv(*pvar_val); |
267 |
} |
} |
268 |
|
#endif |
269 |
|
|
270 |
static void startlogger(void) |
static void SetEnv(struct passwd *pas) |
271 |
{ |
{ |
272 |
if (LogFile == 0) { |
#if SETENV_LEAKS |
273 |
openlog(applet_name, LOG_CONS | LOG_PID, LOG_CRON); |
safe_setenv4(&env_var_user, "USER", pas->pw_name); |
274 |
} |
safe_setenv4(&env_var_home, "HOME", pas->pw_dir); |
275 |
#if ENABLE_DEBUG_CROND_OPTION |
/* if we want to set user's shell instead: */ |
276 |
else { /* test logfile */ |
/*safe_setenv(env_var_user, "SHELL", pas->pw_shell, 5);*/ |
277 |
int logfd; |
#else |
278 |
|
xsetenv("USER", pas->pw_name); |
279 |
|
xsetenv("HOME", pas->pw_dir); |
280 |
|
#endif |
281 |
|
/* currently, we use constant one: */ |
282 |
|
/*setenv("SHELL", DEFAULT_SHELL, 1); - done earlier */ |
283 |
|
} |
284 |
|
|
285 |
if ((logfd = open(LogFile, O_WRONLY | O_CREAT | O_APPEND, 0600)) >= 0) { |
static void ChangeUser(struct passwd *pas) |
286 |
close(logfd); |
{ |
287 |
} else { |
/* careful: we're after vfork! */ |
288 |
bb_perror_msg("failed to open log file '%s': ", LogFile); |
change_identity(pas); /* - initgroups, setgid, setuid */ |
289 |
|
if (chdir(pas->pw_dir) < 0) { |
290 |
|
crondlog(LVL9 "can't chdir(%s)", pas->pw_dir); |
291 |
|
if (chdir(TMPDIR) < 0) { |
292 |
|
crondlog(DIE9 "can't chdir(%s)", TMPDIR); /* exits */ |
293 |
} |
} |
294 |
} |
} |
|
#endif |
|
295 |
} |
} |
296 |
|
|
297 |
|
static const char DowAry[] ALIGN1 = |
298 |
|
"sun""mon""tue""wed""thu""fri""sat" |
299 |
|
/* "Sun""Mon""Tue""Wed""Thu""Fri""Sat" */ |
300 |
|
; |
301 |
|
|
302 |
static const char *const DowAry[] = { |
static const char MonAry[] ALIGN1 = |
303 |
"sun", |
"jan""feb""mar""apr""may""jun""jul""aug""sep""oct""nov""dec" |
304 |
"mon", |
/* "Jan""Feb""Mar""Apr""May""Jun""Jul""Aug""Sep""Oct""Nov""Dec" */ |
305 |
"tue", |
; |
|
"wed", |
|
|
"thu", |
|
|
"fri", |
|
|
"sat", |
|
|
|
|
|
"Sun", |
|
|
"Mon", |
|
|
"Tue", |
|
|
"Wed", |
|
|
"Thu", |
|
|
"Fri", |
|
|
"Sat", |
|
|
NULL |
|
|
}; |
|
306 |
|
|
307 |
static const char *const MonAry[] = { |
static void ParseField(char *user, char *ary, int modvalue, int off, |
308 |
"jan", |
const char *names, char *ptr) |
309 |
"feb", |
/* 'names' is a pointer to a set of 3-char abbreviations */ |
|
"mar", |
|
|
"apr", |
|
|
"may", |
|
|
"jun", |
|
|
"jul", |
|
|
"aug", |
|
|
"sep", |
|
|
"oct", |
|
|
"nov", |
|
|
"dec", |
|
|
|
|
|
"Jan", |
|
|
"Feb", |
|
|
"Mar", |
|
|
"Apr", |
|
|
"May", |
|
|
"Jun", |
|
|
"Jul", |
|
|
"Aug", |
|
|
"Sep", |
|
|
"Oct", |
|
|
"Nov", |
|
|
"Dec", |
|
|
NULL |
|
|
}; |
|
|
|
|
|
static char *ParseField(char *user, char *ary, int modvalue, int off, |
|
|
const char *const *names, char *ptr) |
|
310 |
{ |
{ |
311 |
char *base = ptr; |
char *base = ptr; |
312 |
int n1 = -1; |
int n1 = -1; |
313 |
int n2 = -1; |
int n2 = -1; |
314 |
|
|
315 |
if (base == NULL) { |
// this can't happen due to config_read() |
316 |
return NULL; |
/*if (base == NULL) |
317 |
} |
return;*/ |
318 |
|
|
319 |
while (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { |
while (1) { |
320 |
int skip = 0; |
int skip = 0; |
321 |
|
|
322 |
/* Handle numeric digit or symbol or '*' */ |
/* Handle numeric digit or symbol or '*' */ |
|
|
|
323 |
if (*ptr == '*') { |
if (*ptr == '*') { |
324 |
n1 = 0; /* everything will be filled */ |
n1 = 0; /* everything will be filled */ |
325 |
n2 = modvalue - 1; |
n2 = modvalue - 1; |
326 |
skip = 1; |
skip = 1; |
327 |
++ptr; |
++ptr; |
328 |
} else if (*ptr >= '0' && *ptr <= '9') { |
} else if (isdigit(*ptr)) { |
329 |
if (n1 < 0) { |
if (n1 < 0) { |
330 |
n1 = strtol(ptr, &ptr, 10) + off; |
n1 = strtol(ptr, &ptr, 10) + off; |
331 |
} else { |
} else { |
335 |
} else if (names) { |
} else if (names) { |
336 |
int i; |
int i; |
337 |
|
|
338 |
for (i = 0; names[i]; ++i) { |
for (i = 0; names[i]; i += 3) { |
339 |
if (strncmp(ptr, names[i], strlen(names[i])) == 0) { |
/* was using strncmp before... */ |
340 |
|
if (strncasecmp(ptr, &names[i], 3) == 0) { |
341 |
|
ptr += 3; |
342 |
|
if (n1 < 0) { |
343 |
|
n1 = i / 3; |
344 |
|
} else { |
345 |
|
n2 = i / 3; |
346 |
|
} |
347 |
|
skip = 1; |
348 |
break; |
break; |
349 |
} |
} |
350 |
} |
} |
|
if (names[i]) { |
|
|
ptr += strlen(names[i]); |
|
|
if (n1 < 0) { |
|
|
n1 = i; |
|
|
} else { |
|
|
n2 = i; |
|
|
} |
|
|
skip = 1; |
|
|
} |
|
351 |
} |
} |
352 |
|
|
353 |
/* handle optional range '-' */ |
/* handle optional range '-' */ |
|
|
|
354 |
if (skip == 0) { |
if (skip == 0) { |
355 |
crondlog("\111failed user %s parsing %s\n", user, base); |
goto err; |
|
return NULL; |
|
356 |
} |
} |
357 |
if (*ptr == '-' && n2 < 0) { |
if (*ptr == '-' && n2 < 0) { |
358 |
++ptr; |
++ptr; |
363 |
* collapse single-value ranges, handle skipmark, and fill |
* collapse single-value ranges, handle skipmark, and fill |
364 |
* in the character array appropriately. |
* in the character array appropriately. |
365 |
*/ |
*/ |
|
|
|
366 |
if (n2 < 0) { |
if (n2 < 0) { |
367 |
n2 = n1; |
n2 = n1; |
368 |
} |
} |
369 |
if (*ptr == '/') { |
if (*ptr == '/') { |
370 |
skip = strtol(ptr + 1, &ptr, 10); |
skip = strtol(ptr + 1, &ptr, 10); |
371 |
} |
} |
372 |
|
|
373 |
/* |
/* |
374 |
* fill array, using a failsafe is the easiest way to prevent |
* fill array, using a failsafe is the easiest way to prevent |
375 |
* an endless loop |
* an endless loop |
376 |
*/ |
*/ |
|
|
|
377 |
{ |
{ |
378 |
int s0 = 1; |
int s0 = 1; |
379 |
int failsafe = 1024; |
int failsafe = 1024; |
386 |
ary[n1 % modvalue] = 1; |
ary[n1 % modvalue] = 1; |
387 |
s0 = skip; |
s0 = skip; |
388 |
} |
} |
389 |
} |
if (--failsafe == 0) { |
390 |
while (n1 != n2 && --failsafe); |
goto err; |
391 |
|
} |
392 |
|
} while (n1 != n2); |
393 |
|
|
|
if (failsafe == 0) { |
|
|
crondlog("\111failed user %s parsing %s\n", user, base); |
|
|
return NULL; |
|
|
} |
|
394 |
} |
} |
395 |
if (*ptr != ',') { |
if (*ptr != ',') { |
396 |
break; |
break; |
400 |
n2 = -1; |
n2 = -1; |
401 |
} |
} |
402 |
|
|
403 |
if (*ptr != ' ' && *ptr != '\t' && *ptr != '\n') { |
if (*ptr) { |
404 |
crondlog("\111failed user %s parsing %s\n", user, base); |
err: |
405 |
return NULL; |
crondlog(WARN9 "user %s: parse error at %s", user, base); |
406 |
|
return; |
407 |
} |
} |
408 |
|
|
409 |
while (*ptr == ' ' || *ptr == '\t' || *ptr == '\n') { |
if (DebugOpt && (LogLevel <= 5)) { /* like LVL5 */ |
410 |
++ptr; |
/* can't use crondlog, it inserts '\n' */ |
|
} |
|
|
#if ENABLE_DEBUG_CROND_OPTION |
|
|
if (DebugOpt) { |
|
411 |
int i; |
int i; |
412 |
|
for (i = 0; i < modvalue; ++i) |
413 |
for (i = 0; i < modvalue; ++i) { |
fprintf(stderr, "%d", (unsigned char)ary[i]); |
414 |
crondlog("\005%d", ary[i]); |
fputc('\n', stderr); |
|
} |
|
|
crondlog("\005\n"); |
|
415 |
} |
} |
|
#endif |
|
|
|
|
|
return ptr; |
|
416 |
} |
} |
417 |
|
|
418 |
static void FixDayDow(CronLine * line) |
static void FixDayDow(CronLine *line) |
419 |
{ |
{ |
420 |
int i; |
unsigned i; |
421 |
int weekUsed = 0; |
int weekUsed = 0; |
422 |
int daysUsed = 0; |
int daysUsed = 0; |
423 |
|
|
424 |
for (i = 0; i < (int)(arysize(line->cl_Dow)); ++i) { |
for (i = 0; i < ARRAY_SIZE(line->cl_Dow); ++i) { |
425 |
if (line->cl_Dow[i] == 0) { |
if (line->cl_Dow[i] == 0) { |
426 |
weekUsed = 1; |
weekUsed = 1; |
427 |
break; |
break; |
428 |
} |
} |
429 |
} |
} |
430 |
for (i = 0; i < (int)(arysize(line->cl_Days)); ++i) { |
for (i = 0; i < ARRAY_SIZE(line->cl_Days); ++i) { |
431 |
if (line->cl_Days[i] == 0) { |
if (line->cl_Days[i] == 0) { |
432 |
daysUsed = 1; |
daysUsed = 1; |
433 |
break; |
break; |
434 |
} |
} |
435 |
} |
} |
436 |
if (weekUsed && !daysUsed) { |
if (weekUsed != daysUsed) { |
437 |
memset(line->cl_Days, 0, sizeof(line->cl_Days)); |
if (weekUsed) |
438 |
} |
memset(line->cl_Days, 0, sizeof(line->cl_Days)); |
439 |
if (daysUsed && !weekUsed) { |
else /* daysUsed */ |
440 |
memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); |
memset(line->cl_Dow, 0, sizeof(line->cl_Dow)); |
441 |
} |
} |
442 |
} |
} |
443 |
|
|
|
|
|
|
|
|
444 |
static void SynchronizeFile(const char *fileName) |
static void SynchronizeFile(const char *fileName) |
445 |
{ |
{ |
446 |
int maxEntries = MAXLINES; |
struct parser_t *parser; |
447 |
|
struct stat sbuf; |
448 |
int maxLines; |
int maxLines; |
449 |
char buf[1024]; |
char *tokens[6]; |
450 |
|
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
451 |
if (strcmp(fileName, "root") == 0) { |
char *mailTo = NULL; |
452 |
maxEntries = 65535; |
#endif |
|
} |
|
|
maxLines = maxEntries * 10; |
|
|
|
|
|
if (fileName) { |
|
|
FILE *fi; |
|
|
|
|
|
DeleteFile(fileName); |
|
|
|
|
|
fi = fopen(fileName, "r"); |
|
|
if (fi != NULL) { |
|
|
struct stat sbuf; |
|
|
|
|
|
if (fstat(fileno(fi), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { |
|
|
CronFile *file = xzalloc(sizeof(CronFile)); |
|
|
CronLine **pline; |
|
|
|
|
|
file->cf_User = strdup(fileName); |
|
|
pline = &file->cf_LineBase; |
|
453 |
|
|
454 |
while (fgets(buf, sizeof(buf), fi) != NULL && --maxLines) { |
if (!fileName) |
455 |
CronLine line; |
return; |
|
char *ptr; |
|
456 |
|
|
457 |
trim(buf); |
DeleteFile(fileName); |
458 |
if (buf[0] == 0 || buf[0] == '#') { |
parser = config_open(fileName); |
459 |
continue; |
if (!parser) |
460 |
} |
return; |
|
if (--maxEntries == 0) { |
|
|
break; |
|
|
} |
|
|
memset(&line, 0, sizeof(line)); |
|
461 |
|
|
462 |
#if ENABLE_DEBUG_CROND_OPTION |
maxLines = (strcmp(fileName, "root") == 0) ? 65535 : MAXLINES; |
|
if (DebugOpt) { |
|
|
crondlog("\111User %s Entry %s\n", fileName, buf); |
|
|
} |
|
|
#endif |
|
463 |
|
|
464 |
/* parse date ranges */ |
if (fstat(fileno(parser->fp), &sbuf) == 0 && sbuf.st_uid == DaemonUid) { |
465 |
ptr = ParseField(file->cf_User, line.cl_Mins, 60, 0, NULL, buf); |
CronFile *file = xzalloc(sizeof(CronFile)); |
466 |
ptr = ParseField(file->cf_User, line.cl_Hrs, 24, 0, NULL, ptr); |
CronLine **pline; |
467 |
ptr = ParseField(file->cf_User, line.cl_Days, 32, 0, NULL, ptr); |
int n; |
|
ptr = ParseField(file->cf_User, line.cl_Mons, 12, -1, MonAry, ptr); |
|
|
ptr = ParseField(file->cf_User, line.cl_Dow, 7, 0, DowAry, ptr); |
|
|
|
|
|
/* check failure */ |
|
|
if (ptr == NULL) { |
|
|
continue; |
|
|
} |
|
468 |
|
|
469 |
/* |
file->cf_User = xstrdup(fileName); |
470 |
* fix days and dow - if one is not * and the other |
pline = &file->cf_LineBase; |
|
* is *, the other is set to 0, and vise-versa |
|
|
*/ |
|
471 |
|
|
472 |
FixDayDow(&line); |
while (1) { |
473 |
|
CronLine *line; |
474 |
|
|
475 |
*pline = xzalloc(sizeof(CronLine)); |
if (!--maxLines) |
476 |
**pline = line; |
break; |
477 |
|
n = config_read(parser, tokens, 6, 1, "# \t", PARSE_NORMAL | PARSE_KEEP_COPY); |
478 |
|
if (!n) |
479 |
|
break; |
480 |
|
|
481 |
/* copy command */ |
if (DebugOpt) |
482 |
(*pline)->cl_Shell = strdup(ptr); |
crondlog(LVL5 "user:%s entry:%s", fileName, parser->data); |
483 |
|
|
484 |
#if ENABLE_DEBUG_CROND_OPTION |
/* check if line is setting MAILTO= */ |
485 |
if (DebugOpt) { |
if (0 == strncmp(tokens[0], "MAILTO=", 7)) { |
486 |
crondlog("\111 Command %s\n", ptr); |
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
487 |
} |
free(mailTo); |
488 |
|
mailTo = (tokens[0][7]) ? xstrdup(&tokens[0][7]) : NULL; |
489 |
|
#endif /* otherwise just ignore such lines */ |
490 |
|
continue; |
491 |
|
} |
492 |
|
/* check if a minimum of tokens is specified */ |
493 |
|
if (n < 6) |
494 |
|
continue; |
495 |
|
*pline = line = xzalloc(sizeof(*line)); |
496 |
|
/* parse date ranges */ |
497 |
|
ParseField(file->cf_User, line->cl_Mins, 60, 0, NULL, tokens[0]); |
498 |
|
ParseField(file->cf_User, line->cl_Hrs, 24, 0, NULL, tokens[1]); |
499 |
|
ParseField(file->cf_User, line->cl_Days, 32, 0, NULL, tokens[2]); |
500 |
|
ParseField(file->cf_User, line->cl_Mons, 12, -1, MonAry, tokens[3]); |
501 |
|
ParseField(file->cf_User, line->cl_Dow, 7, 0, DowAry, tokens[4]); |
502 |
|
/* |
503 |
|
* fix days and dow - if one is not "*" and the other |
504 |
|
* is "*", the other is set to 0, and vise-versa |
505 |
|
*/ |
506 |
|
FixDayDow(line); |
507 |
|
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
508 |
|
/* copy mailto (can be NULL) */ |
509 |
|
line->cl_MailTo = xstrdup(mailTo); |
510 |
#endif |
#endif |
511 |
|
/* copy command */ |
512 |
|
line->cl_Shell = xstrdup(tokens[5]); |
513 |
|
if (DebugOpt) { |
514 |
|
crondlog(LVL5 " command:%s", tokens[5]); |
515 |
|
} |
516 |
|
pline = &line->cl_Next; |
517 |
|
//bb_error_msg("M[%s]F[%s][%s][%s][%s][%s][%s]", mailTo, tokens[0], tokens[1], tokens[2], tokens[3], tokens[4], tokens[5]); |
518 |
|
} |
519 |
|
*pline = NULL; |
520 |
|
|
521 |
pline = &((*pline)->cl_Next); |
file->cf_Next = FileBase; |
522 |
} |
FileBase = file; |
|
*pline = NULL; |
|
|
|
|
|
file->cf_Next = FileBase; |
|
|
FileBase = file; |
|
523 |
|
|
524 |
if (maxLines == 0 || maxEntries == 0) { |
if (maxLines == 0) { |
525 |
crondlog("\111Maximum number of lines reached for user %s\n", fileName); |
crondlog(WARN9 "user %s: too many lines", fileName); |
|
} |
|
|
} |
|
|
fclose(fi); |
|
526 |
} |
} |
527 |
} |
} |
528 |
|
config_close(parser); |
529 |
} |
} |
530 |
|
|
531 |
static void CheckUpdates(void) |
static void CheckUpdates(void) |
533 |
FILE *fi; |
FILE *fi; |
534 |
char buf[256]; |
char buf[256]; |
535 |
|
|
536 |
fi = fopen(CRONUPDATE, "r"); |
fi = fopen_for_read(CRONUPDATE); |
537 |
if (fi != NULL) { |
if (fi != NULL) { |
538 |
remove(CRONUPDATE); |
unlink(CRONUPDATE); |
539 |
while (fgets(buf, sizeof(buf), fi) != NULL) { |
while (fgets(buf, sizeof(buf), fi) != NULL) { |
540 |
|
/* use first word only */ |
541 |
SynchronizeFile(strtok(buf, " \t\r\n")); |
SynchronizeFile(strtok(buf, " \t\r\n")); |
542 |
} |
} |
543 |
fclose(fi); |
fclose(fi); |
546 |
|
|
547 |
static void SynchronizeDir(void) |
static void SynchronizeDir(void) |
548 |
{ |
{ |
549 |
|
CronFile *file; |
550 |
/* Attempt to delete the database. */ |
/* Attempt to delete the database. */ |
551 |
|
again: |
552 |
for (;;) { |
for (file = FileBase; file; file = file->cf_Next) { |
553 |
CronFile *file; |
if (!file->cf_Deleted) { |
554 |
|
DeleteFile(file->cf_User); |
555 |
for (file = FileBase; file && file->cf_Deleted; file = file->cf_Next); |
goto again; |
|
if (file == NULL) { |
|
|
break; |
|
556 |
} |
} |
|
DeleteFile(file->cf_User); |
|
557 |
} |
} |
558 |
|
|
559 |
/* |
/* |
564 |
* |
* |
565 |
* scan directory and add associated users |
* scan directory and add associated users |
566 |
*/ |
*/ |
567 |
|
unlink(CRONUPDATE); |
|
remove(CRONUPDATE); |
|
568 |
if (chdir(CDir) < 0) { |
if (chdir(CDir) < 0) { |
569 |
crondlog("\311cannot find %s\n", CDir); |
crondlog(DIE9 "can't chdir(%s)", CDir); |
570 |
} |
} |
571 |
{ |
{ |
572 |
DIR *dir = opendir("."); |
DIR *dir = opendir("."); |
573 |
struct dirent *den; |
struct dirent *den; |
574 |
|
|
575 |
if (dir) { |
if (!dir) |
576 |
while ((den = readdir(dir))) { |
crondlog(DIE9 "can't chdir(%s)", "."); /* exits */ |
577 |
if (strchr(den->d_name, '.') != NULL) { |
while ((den = readdir(dir)) != NULL) { |
578 |
continue; |
if (strchr(den->d_name, '.') != NULL) { |
579 |
} |
continue; |
580 |
if (getpwnam(den->d_name)) { |
} |
581 |
SynchronizeFile(den->d_name); |
if (getpwnam(den->d_name)) { |
582 |
} else { |
SynchronizeFile(den->d_name); |
583 |
crondlog("\007ignoring %s\n", den->d_name); |
} else { |
584 |
} |
crondlog(LVL7 "ignoring %s", den->d_name); |
585 |
} |
} |
|
closedir(dir); |
|
|
} else { |
|
|
crondlog("\311cannot open current dir!\n"); |
|
586 |
} |
} |
587 |
|
closedir(dir); |
588 |
} |
} |
589 |
} |
} |
590 |
|
|
|
|
|
591 |
/* |
/* |
592 |
* DeleteFile() - delete user database |
* DeleteFile() - delete user database |
593 |
* |
* |
594 |
* Note: multiple entries for same user may exist if we were unable to |
* Note: multiple entries for same user may exist if we were unable to |
595 |
* completely delete a database due to running processes. |
* completely delete a database due to running processes. |
596 |
*/ |
*/ |
|
|
|
597 |
static void DeleteFile(const char *userName) |
static void DeleteFile(const char *userName) |
598 |
{ |
{ |
599 |
CronFile **pfile = &FileBase; |
CronFile **pfile = &FileBase; |
637 |
* period is about a minute (one scan). Worst case it will be one |
* period is about a minute (one scan). Worst case it will be one |
638 |
* hour (60 scans). |
* hour (60 scans). |
639 |
*/ |
*/ |
|
|
|
640 |
static int TestJobs(time_t t1, time_t t2) |
static int TestJobs(time_t t1, time_t t2) |
641 |
{ |
{ |
642 |
int nJobs = 0; |
int nJobs = 0; |
645 |
/* Find jobs > t1 and <= t2 */ |
/* Find jobs > t1 and <= t2 */ |
646 |
|
|
647 |
for (t = t1 - t1 % 60; t <= t2; t += 60) { |
for (t = t1 - t1 % 60; t <= t2; t += 60) { |
648 |
if (t > t1) { |
struct tm *tp; |
649 |
struct tm *tp = localtime(&t); |
CronFile *file; |
650 |
CronFile *file; |
CronLine *line; |
651 |
CronLine *line; |
|
652 |
|
if (t <= t1) |
653 |
|
continue; |
654 |
|
|
655 |
for (file = FileBase; file; file = file->cf_Next) { |
tp = localtime(&t); |
656 |
#if ENABLE_DEBUG_CROND_OPTION |
for (file = FileBase; file; file = file->cf_Next) { |
657 |
|
if (DebugOpt) |
658 |
|
crondlog(LVL5 "file %s:", file->cf_User); |
659 |
|
if (file->cf_Deleted) |
660 |
|
continue; |
661 |
|
for (line = file->cf_LineBase; line; line = line->cl_Next) { |
662 |
if (DebugOpt) |
if (DebugOpt) |
663 |
crondlog("\005FILE %s:\n", file->cf_User); |
crondlog(LVL5 " line %s", line->cl_Shell); |
664 |
#endif |
if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] |
665 |
if (file->cf_Deleted) |
&& (line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) |
666 |
continue; |
&& line->cl_Mons[tp->tm_mon] |
667 |
for (line = file->cf_LineBase; line; line = line->cl_Next) { |
) { |
668 |
#if ENABLE_DEBUG_CROND_OPTION |
if (DebugOpt) { |
669 |
if (DebugOpt) |
crondlog(LVL5 " job: %d %s", |
670 |
crondlog("\005 LINE %s\n", line->cl_Shell); |
(int)line->cl_Pid, line->cl_Shell); |
671 |
#endif |
} |
672 |
if (line->cl_Mins[tp->tm_min] && line->cl_Hrs[tp->tm_hour] && |
if (line->cl_Pid > 0) { |
673 |
(line->cl_Days[tp->tm_mday] || line->cl_Dow[tp->tm_wday]) |
crondlog(LVL8 "user %s: process already running: %s", |
674 |
&& line->cl_Mons[tp->tm_mon]) { |
file->cf_User, line->cl_Shell); |
675 |
#if ENABLE_DEBUG_CROND_OPTION |
} else if (line->cl_Pid == 0) { |
676 |
if (DebugOpt) { |
line->cl_Pid = -1; |
677 |
crondlog("\005 JobToDo: %d %s\n", |
file->cf_Ready = 1; |
678 |
line->cl_Pid, line->cl_Shell); |
++nJobs; |
|
} |
|
|
#endif |
|
|
if (line->cl_Pid > 0) { |
|
|
crondlog("\010 process already running: %s %s\n", |
|
|
file->cf_User, line->cl_Shell); |
|
|
} else if (line->cl_Pid == 0) { |
|
|
line->cl_Pid = -1; |
|
|
file->cf_Ready = 1; |
|
|
++nJobs; |
|
|
} |
|
679 |
} |
} |
680 |
} |
} |
681 |
} |
} |
690 |
CronLine *line; |
CronLine *line; |
691 |
|
|
692 |
for (file = FileBase; file; file = file->cf_Next) { |
for (file = FileBase; file; file = file->cf_Next) { |
693 |
if (file->cf_Ready) { |
if (!file->cf_Ready) |
694 |
file->cf_Ready = 0; |
continue; |
|
|
|
|
for (line = file->cf_LineBase; line; line = line->cl_Next) { |
|
|
if (line->cl_Pid < 0) { |
|
|
|
|
|
RunJob(file->cf_User, line); |
|
695 |
|
|
696 |
crondlog("\010USER %s pid %3d cmd %s\n", |
file->cf_Ready = 0; |
697 |
file->cf_User, line->cl_Pid, line->cl_Shell); |
for (line = file->cf_LineBase; line; line = line->cl_Next) { |
698 |
if (line->cl_Pid < 0) { |
if (line->cl_Pid >= 0) |
699 |
file->cf_Ready = 1; |
continue; |
700 |
} |
|
701 |
else if (line->cl_Pid > 0) { |
RunJob(file->cf_User, line); |
702 |
file->cf_Running = 1; |
crondlog(LVL8 "USER %s pid %3d cmd %s", |
703 |
} |
file->cf_User, (int)line->cl_Pid, line->cl_Shell); |
704 |
} |
if (line->cl_Pid < 0) { |
705 |
|
file->cf_Ready = 1; |
706 |
|
} else if (line->cl_Pid > 0) { |
707 |
|
file->cf_Running = 1; |
708 |
} |
} |
709 |
} |
} |
710 |
} |
} |
716 |
* Check for job completion, return number of jobs still running after |
* Check for job completion, return number of jobs still running after |
717 |
* all done. |
* all done. |
718 |
*/ |
*/ |
|
|
|
719 |
static int CheckJobs(void) |
static int CheckJobs(void) |
720 |
{ |
{ |
721 |
CronFile *file; |
CronFile *file; |
727 |
file->cf_Running = 0; |
file->cf_Running = 0; |
728 |
|
|
729 |
for (line = file->cf_LineBase; line; line = line->cl_Next) { |
for (line = file->cf_LineBase; line; line = line->cl_Next) { |
730 |
if (line->cl_Pid > 0) { |
int status, r; |
731 |
int status; |
if (line->cl_Pid <= 0) |
732 |
int r = wait4(line->cl_Pid, &status, WNOHANG, NULL); |
continue; |
733 |
|
|
734 |
if (r < 0 || r == line->cl_Pid) { |
r = waitpid(line->cl_Pid, &status, WNOHANG); |
735 |
EndJob(file->cf_User, line); |
if (r < 0 || r == line->cl_Pid) { |
736 |
if (line->cl_Pid) { |
EndJob(file->cf_User, line); |
737 |
file->cf_Running = 1; |
if (line->cl_Pid) { |
|
} |
|
|
} else if (r == 0) { |
|
738 |
file->cf_Running = 1; |
file->cf_Running = 1; |
739 |
} |
} |
740 |
|
} else if (r == 0) { |
741 |
|
file->cf_Running = 1; |
742 |
} |
} |
743 |
} |
} |
744 |
} |
} |
747 |
return nStillRunning; |
return nStillRunning; |
748 |
} |
} |
749 |
|
|
|
|
|
750 |
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
#if ENABLE_FEATURE_CROND_CALL_SENDMAIL |
751 |
|
|
752 |
|
// TODO: sendmail should be _run-time_ option, not compile-time! |
753 |
|
|
754 |
static void |
static void |
755 |
ForkJob(const char *user, CronLine * line, int mailFd, |
ForkJob(const char *user, CronLine *line, int mailFd, |
756 |
const char *prog, const char *cmd, const char *arg, const char *mailf) |
const char *prog, const char *cmd, const char *arg, |
757 |
|
const char *mail_filename) |
758 |
{ |
{ |
759 |
/* Fork as the user in question and run program */ |
struct passwd *pas; |
760 |
pid_t pid = fork(); |
pid_t pid; |
761 |
|
|
762 |
line->cl_Pid = pid; |
/* prepare things before vfork */ |
763 |
|
pas = getpwnam(user); |
764 |
|
if (!pas) { |
765 |
|
crondlog(LVL9 "can't get uid for %s", user); |
766 |
|
goto err; |
767 |
|
} |
768 |
|
SetEnv(pas); |
769 |
|
|
770 |
|
pid = vfork(); |
771 |
if (pid == 0) { |
if (pid == 0) { |
772 |
/* CHILD */ |
/* CHILD */ |
773 |
|
/* change running state to the user in question */ |
774 |
/* Change running state to the user in question */ |
ChangeUser(pas); |
|
|
|
|
if (ChangeUser(user) < 0) { |
|
|
exit(0); |
|
|
} |
|
|
#if ENABLE_DEBUG_CROND_OPTION |
|
775 |
if (DebugOpt) { |
if (DebugOpt) { |
776 |
crondlog("\005Child Running %s\n", prog); |
crondlog(LVL5 "child running %s", prog); |
777 |
} |
} |
|
#endif |
|
|
|
|
778 |
if (mailFd >= 0) { |
if (mailFd >= 0) { |
779 |
dup2(mailFd, mailf != NULL); |
xmove_fd(mailFd, mail_filename ? 1 : 0); |
780 |
dup2((mailf ? mailFd : 1), 2); |
dup2(1, 2); |
781 |
close(mailFd); |
} |
782 |
} |
/* crond 3.0pl1-100 puts tasks in separate process groups */ |
783 |
execl(prog, prog, cmd, arg, NULL); |
bb_setpgrp(); |
784 |
crondlog("\024cannot exec, user %s cmd %s %s %s\n", user, prog, cmd, arg); |
execlp(prog, prog, cmd, arg, NULL); |
785 |
if (mailf) { |
crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, prog, cmd, arg); |
786 |
|
if (mail_filename) { |
787 |
fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); |
fdprintf(1, "Exec failed: %s -c %s\n", prog, arg); |
788 |
} |
} |
789 |
exit(0); |
_exit(EXIT_SUCCESS); |
790 |
} else if (pid < 0) { |
} |
791 |
|
|
792 |
|
line->cl_Pid = pid; |
793 |
|
if (pid < 0) { |
794 |
/* FORK FAILED */ |
/* FORK FAILED */ |
795 |
crondlog("\024cannot fork, user %s\n", user); |
crondlog(ERR20 "can't vfork"); |
796 |
|
err: |
797 |
line->cl_Pid = 0; |
line->cl_Pid = 0; |
798 |
if (mailf) { |
if (mail_filename) { |
799 |
remove(mailf); |
unlink(mail_filename); |
800 |
} |
} |
801 |
} else if (mailf) { |
} else if (mail_filename) { |
802 |
/* PARENT, FORK SUCCESS |
/* PARENT, FORK SUCCESS |
803 |
* rename mail-file based on pid of process |
* rename mail-file based on pid of process |
804 |
*/ |
*/ |
805 |
char mailFile2[128]; |
char mailFile2[128]; |
806 |
|
|
807 |
snprintf(mailFile2, sizeof(mailFile2), TMPDIR "/cron.%s.%d", user, pid); |
snprintf(mailFile2, sizeof(mailFile2), "%s/cron.%s.%d", TMPDIR, user, pid); |
808 |
rename(mailf, mailFile2); |
rename(mail_filename, mailFile2); // TODO: xrename? |
809 |
} |
} |
810 |
|
|
811 |
/* |
/* |
812 |
* Close the mail file descriptor.. we can't just leave it open in |
* Close the mail file descriptor.. we can't just leave it open in |
813 |
* a structure, closing it later, because we might run out of descriptors |
* a structure, closing it later, because we might run out of descriptors |
814 |
*/ |
*/ |
|
|
|
815 |
if (mailFd >= 0) { |
if (mailFd >= 0) { |
816 |
close(mailFd); |
close(mailFd); |
817 |
} |
} |
818 |
} |
} |
819 |
|
|
820 |
static void RunJob(const char *user, CronLine * line) |
static void RunJob(const char *user, CronLine *line) |
821 |
{ |
{ |
822 |
char mailFile[128]; |
char mailFile[128]; |
823 |
int mailFd; |
int mailFd = -1; |
824 |
|
|
825 |
line->cl_Pid = 0; |
line->cl_Pid = 0; |
826 |
line->cl_MailFlag = 0; |
line->cl_MailFlag = 0; |
827 |
|
|
828 |
/* open mail file - owner root so nobody can screw with it. */ |
if (line->cl_MailTo) { |
829 |
|
/* open mail file - owner root so nobody can screw with it. */ |
830 |
|
snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, getpid()); |
831 |
|
mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); |
832 |
|
|
833 |
snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, getpid()); |
if (mailFd >= 0) { |
834 |
mailFd = open(mailFile, O_CREAT | O_TRUNC | O_WRONLY | O_EXCL | O_APPEND, 0600); |
line->cl_MailFlag = 1; |
835 |
|
fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", line->cl_MailTo, |
836 |
if (mailFd >= 0) { |
line->cl_Shell); |
837 |
line->cl_MailFlag = 1; |
line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR); |
838 |
fdprintf(mailFd, "To: %s\nSubject: cron: %s\n\n", user, |
} else { |
839 |
line->cl_Shell); |
crondlog(ERR20 "cannot create mail file %s for user %s, " |
840 |
line->cl_MailPos = lseek(mailFd, 0, SEEK_CUR); |
"discarding output", mailFile, user); |
841 |
} else { |
} |
|
crondlog("\024cannot create mail file user %s file %s, output to /dev/null\n", user, mailFile); |
|
842 |
} |
} |
843 |
|
|
844 |
ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile); |
ForkJob(user, line, mailFd, DEFAULT_SHELL, "-c", line->cl_Shell, mailFile); |
847 |
/* |
/* |
848 |
* EndJob - called when job terminates and when mail terminates |
* EndJob - called when job terminates and when mail terminates |
849 |
*/ |
*/ |
850 |
|
static void EndJob(const char *user, CronLine *line) |
|
static void EndJob(const char *user, CronLine * line) |
|
851 |
{ |
{ |
852 |
int mailFd; |
int mailFd; |
853 |
char mailFile[128]; |
char mailFile[128]; |
854 |
struct stat sbuf; |
struct stat sbuf; |
855 |
|
|
856 |
/* No job */ |
/* No job */ |
|
|
|
857 |
if (line->cl_Pid <= 0) { |
if (line->cl_Pid <= 0) { |
858 |
line->cl_Pid = 0; |
line->cl_Pid = 0; |
859 |
return; |
return; |
863 |
* End of job and no mail file |
* End of job and no mail file |
864 |
* End of sendmail job |
* End of sendmail job |
865 |
*/ |
*/ |
866 |
|
snprintf(mailFile, sizeof(mailFile), "%s/cron.%s.%d", TMPDIR, user, line->cl_Pid); |
|
snprintf(mailFile, sizeof(mailFile), TMPDIR "/cron.%s.%d", user, line->cl_Pid); |
|
867 |
line->cl_Pid = 0; |
line->cl_Pid = 0; |
868 |
|
|
869 |
if (line->cl_MailFlag != 1) { |
if (line->cl_MailFlag == 0) { |
870 |
return; |
return; |
871 |
} |
} |
872 |
line->cl_MailFlag = 0; |
line->cl_MailFlag = 0; |
875 |
* End of primary job - check for mail file. If size has increased and |
* End of primary job - check for mail file. If size has increased and |
876 |
* the file is still valid, we sendmail it. |
* the file is still valid, we sendmail it. |
877 |
*/ |
*/ |
|
|
|
878 |
mailFd = open(mailFile, O_RDONLY); |
mailFd = open(mailFile, O_RDONLY); |
879 |
remove(mailFile); |
unlink(mailFile); |
880 |
if (mailFd < 0) { |
if (mailFd < 0) { |
881 |
return; |
return; |
882 |
} |
} |
883 |
|
|
884 |
if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid || sbuf.st_nlink != 0 || |
if (fstat(mailFd, &sbuf) < 0 || sbuf.st_uid != DaemonUid |
885 |
sbuf.st_size == line->cl_MailPos || !S_ISREG(sbuf.st_mode)) { |
|| sbuf.st_nlink != 0 || sbuf.st_size == line->cl_MailPos |
886 |
|
|| !S_ISREG(sbuf.st_mode) |
887 |
|
) { |
888 |
close(mailFd); |
close(mailFd); |
889 |
return; |
return; |
890 |
} |
} |
891 |
ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); |
if (line->cl_MailTo) |
892 |
|
ForkJob(user, line, mailFd, SENDMAIL, SENDMAIL_ARGS, NULL); |
893 |
} |
} |
|
#else |
|
|
/* crond without sendmail */ |
|
894 |
|
|
895 |
static void RunJob(const char *user, CronLine * line) |
#else /* crond without sendmail */ |
896 |
|
|
897 |
|
static void RunJob(const char *user, CronLine *line) |
898 |
{ |
{ |
899 |
/* Fork as the user in question and run program */ |
struct passwd *pas; |
900 |
pid_t pid = fork(); |
pid_t pid; |
901 |
|
|
902 |
|
/* prepare things before vfork */ |
903 |
|
pas = getpwnam(user); |
904 |
|
if (!pas) { |
905 |
|
crondlog(LVL9 "can't get uid for %s", user); |
906 |
|
goto err; |
907 |
|
} |
908 |
|
SetEnv(pas); |
909 |
|
|
910 |
|
/* fork as the user in question and run program */ |
911 |
|
pid = vfork(); |
912 |
if (pid == 0) { |
if (pid == 0) { |
913 |
/* CHILD */ |
/* CHILD */ |
914 |
|
/* change running state to the user in question */ |
915 |
/* Change running state to the user in question */ |
ChangeUser(pas); |
|
|
|
|
if (ChangeUser(user) < 0) { |
|
|
exit(0); |
|
|
} |
|
|
#if ENABLE_DEBUG_CROND_OPTION |
|
916 |
if (DebugOpt) { |
if (DebugOpt) { |
917 |
crondlog("\005Child Running %s\n", DEFAULT_SHELL); |
crondlog(LVL5 "child running %s", DEFAULT_SHELL); |
918 |
} |
} |
919 |
#endif |
/* crond 3.0pl1-100 puts tasks in separate process groups */ |
920 |
|
bb_setpgrp(); |
921 |
execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL); |
execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", line->cl_Shell, NULL); |
922 |
crondlog("\024cannot exec, user %s cmd %s -c %s\n", user, |
crondlog(ERR20 "can't exec, user %s cmd %s %s %s", user, |
923 |
DEFAULT_SHELL, line->cl_Shell); |
DEFAULT_SHELL, "-c", line->cl_Shell); |
924 |
exit(0); |
_exit(EXIT_SUCCESS); |
925 |
} else if (pid < 0) { |
} |
926 |
|
if (pid < 0) { |
927 |
/* FORK FAILED */ |
/* FORK FAILED */ |
928 |
crondlog("\024cannot, user %s\n", user); |
crondlog(ERR20 "can't vfork"); |
929 |
|
err: |
930 |
pid = 0; |
pid = 0; |
931 |
} |
} |
932 |
line->cl_Pid = pid; |
line->cl_Pid = pid; |
933 |
} |
} |
934 |
|
|
935 |
#endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */ |
#endif /* ENABLE_FEATURE_CROND_CALL_SENDMAIL */ |