5 * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com)
6 * Copyright 2009-2011 James Pryor <profjim@jimpryor.net>
7 * May be distributed under the GNU General Public License
12 Prototype
void RunJob(CronFile
*file
, CronLine
*line
);
13 Prototype
void EndJob(CronFile
*file
, CronLine
*line
, int exit_status
);
15 Prototype
const char *SendMail
;
18 RunJob(CronFile
*file
, CronLine
*line
)
20 char mailFile
[SMALL_BUFFER
];
22 const char *value
= Mailto
;
25 line
->cl_MailFlag
= 0;
28 * try to open mail output file - owner root so nobody can screw with it.
31 snprintf(mailFile
, sizeof(mailFile
), TempFileFmt
,
32 file
->cf_UserName
, (int)getpid());
34 if ((mailFd
= open(mailFile
, O_CREAT
|O_TRUNC
|O_WRONLY
|O_EXCL
|O_APPEND
, 0600)) >= 0) {
35 /* success: write headers to mailFile */
36 line
->cl_MailFlag
= 1;
37 /* if we didn't specify a -m Mailto, use the local user */
39 value
= file
->cf_UserName
;
40 fdprintf(mailFd
, "To: %s\nSubject: cron for user %s %s\n\n",
45 /* remember mailFile's size */
46 line
->cl_MailPos
= lseek(mailFd
, 0, 1);
49 * else no mailFd, we complain later and don't check job output
50 * but we still run the job if we can
55 * Fork as the user in question and run program
58 if ((line
->cl_Pid
= fork()) == 0) {
60 * CHILD, FORK OK, PRE-EXEC
62 * Change running state to the user in question
65 if (ChangeUser(file
->cf_UserName
, TempDir
) < 0) {
66 printlogf(LOG_ERR
, "unable to ChangeUser (user %s %s)\n",
73 /* from this point we are unpriviledged */
76 printlogf(LOG_DEBUG
, "child running: %s\n", line
->cl_Description
);
79 * Inside child, we copy our fd 2 (which may be /dev/null) into
80 * an open-until-exec fd 8
83 fcntl(8, F_SETFD
, FD_CLOEXEC
);
87 /* stdin is already /dev/null, setup stdout and stderr > mailFile */
92 /* complain about no mailFd to log (now associated with fd 8) */
93 fdprintlogf(LOG_WARNING
, 8, "unable to create mail file %s: cron output for user %s %s to /dev/null\n",
98 /* stderr > /dev/null */
103 * Start a new process group, so that children still in the crond's process group
108 execl("/bin/sh", "/bin/sh", "-c", line
->cl_Shell
, NULL
);
110 * CHILD FAILED TO EXEC CRONJOB
112 * Complain to our log (now associated with fd 8)
114 fdprintlogf(LOG_ERR
, 8, "unable to exec (user %s cmd /bin/sh -c %s)\n",
119 * Also complain to stdout, which will be either the mailFile or /dev/null
121 fdprintf(1, "unable to exec: /bin/sh -c %s\n", line
->cl_Shell
);
124 } else if (line
->cl_Pid
< 0) {
126 * PARENT, FORK FAILED
128 * Complain to log (with regular fd 2)
130 printlogf(LOG_ERR
, "unable to fork (user %s %s)\n",
139 * PARENT, FORK SUCCESS
141 * rename mail-file based on pid of child process
143 char mailFile2
[SMALL_BUFFER
];
145 snprintf(mailFile2
, sizeof(mailFile2
), TempFileFmt
,
146 file
->cf_UserName
, line
->cl_Pid
);
147 rename(mailFile
, mailFile2
);
151 * Close the mail file descriptor.. we can't just leave it open in
152 * a structure, closing it later, because we might run out of descriptors
160 * EndJob - called when main job terminates
164 EndJob(CronFile
*file
, CronLine
*line
, int exit_status
)
167 char mailFile
[SMALL_BUFFER
];
169 struct CronNotifier
*notif
;
171 if (line
->cl_Pid
<= 0) {
173 * No job. This should never happen.
181 * check return status
183 if (line
->cl_Delay
> 0) {
184 if (exit_status
== EAGAIN
) {
186 * returned EAGAIN, wait cl_Delay then retry
187 * we base off the time the job was scheduled/started waiting, not the time it finished
189 * line->cl_NotUntil = time(NULL) + line->cl_Delay; // use this to base off time finished
190 * line->cl_NotUntil += line->cl_Delay; // already applied
194 * process finished without returning EAGAIN (it may have returned some other error)
195 * mark as having run and update timestamp
198 char buf
[SMALL_BUFFER
];
201 * we base off the time the job was scheduled/started waiting, not the time it finished
203 * line->cl_LastRan = time(NULL); // use this to base off time finished
205 line
->cl_LastRan
= line
->cl_NotUntil
- line
->cl_Delay
;
206 if ((fi
= fopen(line
->cl_Timestamp
, "w")) != NULL
) {
207 if (strftime(buf
, sizeof(buf
), CRONSTAMP_FMT
, localtime(&line
->cl_LastRan
)))
208 if (fputs(buf
, fi
) >= 0)
213 printlogf(LOG_WARNING
, "unable to write timestamp to %s (user %s %s)\n", line
->cl_Timestamp
, file
->cf_UserName
, line
->cl_Description
);
214 line
->cl_NotUntil
= line
->cl_LastRan
;
215 line
->cl_NotUntil
+= (line
->cl_Freq
> 0) ? line
->cl_Freq
: line
->cl_Delay
;
219 if (exit_status
!= EAGAIN
) {
223 notif
= line
->cl_Notifs
;
225 if (notif
->cn_Waiter
) {
226 notif
->cn_Waiter
->cw_Flag
= exit_status
;
228 notif
= notif
->cn_Next
;
233 * log non-zero exit_status
235 printlogf(LOG_NOTICE
, "exit status %d from user %s %s\n",
243 if (!exit_status
|| exit_status
== EAGAIN
)
245 printlogf(LOG_DEBUG
, "exit status %d from user %s %s\n",
252 if (line
->cl_MailFlag
!= 1) {
253 /* End of job and no mail file */
259 * Calculate mailFile's name before clearing cl_Pid
261 snprintf(mailFile
, sizeof(mailFile
), TempFileFmt
,
262 file
->cf_UserName
, line
->cl_Pid
);
265 line
->cl_MailFlag
= 0;
268 * Check mail file. If size has increased and
269 * the file is still valid, we sendmail it.
272 mailFd
= open(mailFile
, O_RDONLY
);
278 /* Was mailFile tampered with, or didn't grow? */
280 if (fstat(mailFd
, &sbuf
) < 0 ||
281 sbuf
.st_uid
!= DaemonUid
||
282 sbuf
.st_nlink
!= 0 ||
283 sbuf
.st_size
== line
->cl_MailPos
||
284 !S_ISREG(sbuf
.st_mode
)
290 if ((line
->cl_Pid
= fork()) == 0) {
292 * CHILD, FORK OK, PRE-EXEC
294 * Change user id - no way in hell security can be compromised
295 * by the mailing and we already verified the mail file.
298 if (ChangeUser(file
->cf_UserName
, TempDir
) < 0) {
299 printlogf(LOG_ERR
, "unable to ChangeUser to send mail (user %s %s)\n",
306 /* from this point we are unpriviledged */
309 * Inside child, we copy our fd 2 (which may be /dev/null) into
310 * an open-until-exec fd 8
314 fcntl(8, F_SETFD
, FD_CLOEXEC
);
318 * Run sendmail with stdin < mailFile and stderr > /dev/null
327 * If using standard sendmail, note in our log (now associated with fd 8)
328 * that we're trying to mail output
330 fdprintlogf(LOG_INFO
, 8, "mailing cron output for user %s %s\n",
334 execl(SENDMAIL
, SENDMAIL
, SENDMAIL_ARGS
, NULL
);
336 /* exec failed: pass through and log the error */
341 * If using custom mailer script, just try to exec it
343 execl(SendMail
, SendMail
, NULL
);
347 * CHILD FAILED TO EXEC SENDMAIL
349 * Complain to our log (now associated with fd 8)
352 fdprintlogf(LOG_WARNING
, 8, "unable to exec %s: cron output for user %s %s to /dev/null\n",
359 } else if (line
->cl_Pid
< 0) {
361 * PARENT, FORK FAILED
363 * Complain to our log (with regular fd 2)
365 printlogf(LOG_WARNING
, "unable to fork: cron output for user %s %s to /dev/null\n",
374 * We clear cl_Pid even when mailjob successfully forked
375 * and catch the dead mailjobs with our SIGCHLD handler.