#include <sys/types.h>
#include <sys/wait.h>
#include <bitstring.h>
#include <bsd_auth.h>
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <login_cap.h>
#include <pwd.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <time.h>
#include <unistd.h>
#include <vis.h>
#include "config.h"
#include "pathnames.h"
#include "macros.h"
#include "structs.h"
#include "funcs.h"
static void child_process(entry *, user *);
pid_t
do_command(entry *e, user *u)
{
pid_t pid;
switch ((pid = fork())) {
case -1:
syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)");
break;
case 0:
child_process(e, u);
_exit(EXIT_SUCCESS);
break;
default:
if ((e->flags & SINGLE_JOB) == 0)
pid = -1;
break;
}
return (pid);
}
static void
child_process(entry *e, user *u)
{
FILE *in;
int stdin_pipe[2], stdout_pipe[2];
char **p, *input_data, *usernm;
auth_session_t *as;
login_cap_t *lc;
extern char **environ;
setproctitle("running job");
closefrom(3);
usernm = e->pwd->pw_name;
(void) signal(SIGCHLD, SIG_DFL);
if (pipe(stdin_pipe) != 0 || pipe(stdout_pipe) != 0) {
syslog(LOG_ERR, "(CRON) PIPE (%m)");
_exit(EXIT_FAILURE);
}
{
int escaped = FALSE;
int ch;
char *p;
for (input_data = p = e->cmd;
(ch = *input_data) != '\0';
input_data++, p++) {
if (p != input_data)
*p = ch;
if (escaped) {
if (ch == '%')
*--p = ch;
escaped = FALSE;
continue;
}
if (ch == '\\') {
escaped = TRUE;
continue;
}
if (ch == '%') {
*input_data++ = '\0';
break;
}
}
*p = '\0';
}
pid_t jobpid;
switch (jobpid = fork()) {
case -1:
syslog(LOG_ERR, "(CRON) CAN'T FORK (%m)");
_exit(EXIT_FAILURE);
case 0:
if ((e->flags & DONT_LOG) == 0) {
char *x;
if (stravis(&x, e->cmd, 0) != -1) {
syslog(LOG_INFO, "(%s) CMD (%s)", usernm, x);
free(x);
}
}
(void) setsid();
close(stdin_pipe[WRITE_PIPE]);
close(stdout_pipe[READ_PIPE]);
if (stdin_pipe[READ_PIPE] != STDIN_FILENO) {
dup2(stdin_pipe[READ_PIPE], STDIN_FILENO);
close(stdin_pipe[READ_PIPE]);
}
if (stdout_pipe[WRITE_PIPE] != STDOUT_FILENO) {
dup2(stdout_pipe[WRITE_PIPE], STDOUT_FILENO);
close(stdout_pipe[WRITE_PIPE]);
}
dup2(STDOUT_FILENO, STDERR_FILENO);
if ((lc = login_getclass(e->pwd->pw_class)) == NULL) {
warnx("unable to get login class for %s",
e->pwd->pw_name);
syslog(LOG_ERR, "(CRON) CAN'T GET LOGIN CLASS (%s)",
e->pwd->pw_name);
_exit(EXIT_FAILURE);
}
if (setusercontext(lc, e->pwd, e->pwd->pw_uid, LOGIN_SETALL) == -1) {
warn("setusercontext failed for %s", e->pwd->pw_name);
syslog(LOG_ERR, "(%s) SETUSERCONTEXT FAILED (%m)",
e->pwd->pw_name);
_exit(EXIT_FAILURE);
}
as = auth_open();
if (as == NULL || auth_setpwd(as, e->pwd) != 0) {
warn("auth_setpwd");
syslog(LOG_ERR, "(%s) AUTH_SETPWD FAILED (%m)",
e->pwd->pw_name);
_exit(EXIT_FAILURE);
}
if (auth_approval(as, lc, usernm, "cron") <= 0) {
warnx("approval failed for %s", e->pwd->pw_name);
syslog(LOG_ERR, "(%s) APPROVAL FAILED (cron)",
e->pwd->pw_name);
_exit(EXIT_FAILURE);
}
auth_close(as);
login_close(lc);
if (env_get("PATH", e->envp) == NULL && environ != NULL) {
for (p = environ; *p; p++) {
if (strncmp(*p, "PATH=", 5) == 0) {
e->envp = env_set(e->envp, *p);
break;
}
}
}
chdir(env_get("HOME", e->envp));
(void) signal(SIGPIPE, SIG_DFL);
{
char *shell = env_get("SHELL", e->envp);
execle(shell, shell, "-c", e->cmd, (char *)NULL, e->envp);
warn("unable to execute %s", shell);
syslog(LOG_ERR, "(%s) CAN'T EXEC (%s: %m)",
e->pwd->pw_name, shell);
_exit(EXIT_FAILURE);
}
break;
default:
break;
}
close(stdin_pipe[READ_PIPE]);
close(stdout_pipe[WRITE_PIPE]);
pid_t stdinjob;
if (*input_data && (stdinjob = fork()) == 0) {
FILE *out = fdopen(stdin_pipe[WRITE_PIPE], "w");
int need_newline = FALSE;
int escaped = FALSE;
int ch;
close(stdout_pipe[READ_PIPE]);
while ((ch = *input_data++) != '\0') {
if (escaped) {
if (ch != '%')
putc('\\', out);
} else {
if (ch == '%')
ch = '\n';
}
if (!(escaped = (ch == '\\'))) {
putc(ch, out);
need_newline = (ch != '\n');
}
}
if (escaped)
putc('\\', out);
if (need_newline)
putc('\n', out);
fclose(out);
_exit(EXIT_SUCCESS);
}
close(stdin_pipe[WRITE_PIPE]);
(void) signal(SIGPIPE, SIG_IGN);
in = fdopen(stdout_pipe[READ_PIPE], "r");
char *mailto;
FILE *mail = NULL;
int status = 0;
pid_t mailpid;
size_t bytes = 1;
if (in != NULL) {
int ch = getc(in);
if (ch != EOF) {
mailto = env_get("MAILTO", e->envp);
if (!mailto) {
mailto = usernm;
} else if (!*mailto || !safe_p(usernm, mailto)) {
mailto = NULL;
}
if (mailto) {
char **env;
char mailcmd[MAX_COMMAND];
char hostname[HOST_NAME_MAX + 1];
gethostname(hostname, sizeof(hostname));
if (snprintf(mailcmd, sizeof mailcmd, MAILFMT,
MAILARG) >= sizeof mailcmd) {
syslog(LOG_ERR,
"(%s) ERROR (mailcmd too long)",
e->pwd->pw_name);
(void) _exit(EXIT_FAILURE);
}
if (!(mail = cron_popen(mailcmd, "w", e->pwd,
&mailpid))) {
syslog(LOG_ERR, "(%s) POPEN (%s)",
e->pwd->pw_name, mailcmd);
(void) _exit(EXIT_FAILURE);
}
fprintf(mail, "From: root (Cron Daemon)\n");
fprintf(mail, "To: %s\n", mailto);
fprintf(mail, "Subject: Cron <%s@%s> %s\n",
usernm, first_word(hostname, "."),
e->cmd);
fprintf(mail, "Auto-Submitted: auto-generated\n");
for (env = e->envp; *env; env++)
fprintf(mail, "X-Cron-Env: <%s>\n",
*env);
fprintf(mail, "\n");
fputc(ch, mail);
}
while ((ch = getc(in)) != EOF) {
bytes++;
if (mail)
fputc(ch, mail);
}
}
fclose(in);
}
int waiter;
if (jobpid > 0) {
while (waitpid(jobpid, &waiter, 0) == -1 && errno == EINTR)
;
if (WIFEXITED(waiter) && WEXITSTATUS(waiter) == 0
&& (e->flags & MAIL_WHEN_ERR) == MAIL_WHEN_ERR
&& mail) {
kill(mailpid, SIGKILL);
(void)fclose(mail);
mail = NULL;
}
if (mail) {
status = cron_pclose(mail, mailpid);
}
if (mail && status) {
syslog(LOG_NOTICE, "(%s) MAIL (mailed %zu byte"
"%s of output but got status 0x%04x)", usernm,
bytes, (bytes == 1) ? "" : "s", status);
}
}
if (stdinjob > 0)
while (waitpid(stdinjob, &waiter, 0) == -1 && errno == EINTR)
;
}
int
safe_p(const char *usernm, const char *s)
{
static const char safe_delim[] = "@!:%+-.,";
const char *t;
int ch, first;
for (t = s, first = 1; (ch = (unsigned char)*t++) != '\0'; first = 0) {
if (isascii(ch) && isprint(ch) &&
(isalnum(ch) || ch == '_' ||
(!first && strchr(safe_delim, ch))))
continue;
syslog(LOG_WARNING, "(%s) UNSAFE (%s)", usernm, s);
return (FALSE);
}
return (TRUE);
}