#include <sys/types.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <pwd.h>
#include <unistd.h>
#include <locale.h>
#include <nl_types.h>
#include <langinfo.h>
#include <libintl.h>
#include <security/pam_appl.h>
#include <limits.h>
#include <libzoneinfo.h>
#include "cron.h"
#include "getresponse.h"
#if defined(XPG4)
#define VIPATH "/usr/xpg4/bin/vi"
#elif defined(XPG6)
#define VIPATH "/usr/xpg6/bin/vi"
#else
#define _XPG_NOTDEFINED
#define VIPATH "vi"
#endif
#define TMPFILE "_cron"
#define CRMODE 0600
#define BADCREATE \
"can't create your crontab file in the crontab directory."
#define BADOPEN "can't open your crontab file."
#define BADSHELL \
"because your login shell isn't /usr/bin/sh, you can't use cron."
#define WARNSHELL "warning: commands will be executed using /usr/bin/sh\n"
#define BADUSAGE \
"usage:\n" \
"\tcrontab [-u username] [file]\n" \
"\tcrontab [-u username] { -e | -l | -r }\n" \
"\tcrontab { -e | -l | -r } [username]"
#define INVALIDUSER "you are not a valid user (no entry in /etc/passwd)."
#define NOTALLOWED "you are not authorized to use cron. Sorry."
#define NOTROOT \
"you must be super-user to access another user's crontab file"
#define AUDITREJECT "The audit context for your shell has not been set."
#define EOLN "unexpected end of line."
#define UNEXPECT "unexpected character found in line."
#define OUTOFBOUND "number out of bounds."
#define OVERFLOW "too many elements."
#define ERRSFND "errors detected in input, no crontab file generated."
#define ED_ERROR \
" The editor indicates that an error occurred while you were\n"\
" editing the crontab data - usually a minor typing error.\n\n"
#define BADREAD "error reading your crontab file"
#define ED_PROMPT \
" Edit again, to ensure crontab information is intact (%s/%s)?\n"\
" ('%s' will discard edits.)"
#define NAMETOOLONG "login name too long"
#define BAD_TZ "Timezone unrecognized in: %s"
#define BAD_SHELL "Invalid shell specified: %s"
#define BAD_HOME "Unable to access directory: %s\t%s\n"
#define BAD_RAND_DELAY "Invalid delay: %s"
extern int per_errno;
extern int audit_crontab_modify(char *, char *, int);
extern int audit_crontab_delete(char *, int);
extern int audit_crontab_not_allowed(uid_t, char *);
static int err;
int cursor;
char *cf;
char *tnam;
char edtemp[5+13+1];
char line[CTLINESIZE];
static char login[UNAMESIZE];
static void catch(int);
static void crabort(char *);
static void cerror(char *);
static void copycron(FILE *);
int
main(int argc, char **argv)
{
int c, r;
int rflag = 0;
int lflag = 0;
int eflag = 0;
int errflg = 0;
char *pp;
FILE *fp, *tmpfp;
struct stat stbuf;
struct passwd *pwp;
time_t omodtime;
char *editor;
uid_t ruid;
pid_t pid;
int stat_loc;
int ret;
char real_login[UNAMESIZE];
char *user = NULL;
int tmpfd = -1;
pam_handle_t *pamh;
int pam_error;
char *buf;
size_t buflen;
(void) setlocale(LC_ALL, "");
#if !defined(TEXT_DOMAIN)
#define TEXT_DOMAIN "SYS_TEST"
#endif
(void) textdomain(TEXT_DOMAIN);
if (init_yes() < 0) {
(void) fprintf(stderr, gettext(ERR_MSG_INIT_YES),
strerror(errno));
exit(1);
}
while ((c = getopt(argc, argv, "elru:")) != EOF) {
switch (c) {
case 'e':
eflag++;
break;
case 'l':
lflag++;
break;
case 'r':
rflag++;
break;
case 'u':
user = optarg;
break;
case '?':
errflg++;
break;
}
}
argc -= optind;
argv += optind;
if (eflag + lflag + rflag > 1)
errflg++;
if ((eflag || lflag || rflag) && argc > 0) {
if (user != NULL)
errflg++;
else
user = *argv;
}
if (errflg || argc > 1)
crabort(BADUSAGE);
ruid = getuid();
if ((pwp = getpwuid(ruid)) == NULL)
crabort(INVALIDUSER);
if (strlcpy(real_login, pwp->pw_name, sizeof (real_login))
>= sizeof (real_login)) {
crabort(NAMETOOLONG);
}
if (user != NULL) {
if ((pwp = getpwnam(user)) == NULL)
crabort(INVALIDUSER);
if (!cron_admin(real_login)) {
if (pwp->pw_uid != ruid)
crabort(NOTROOT);
else
pp = getuser(ruid);
} else {
pp = user;
}
} else {
pp = getuser(ruid);
}
if (pp == NULL) {
if (per_errno == 2)
crabort(BADSHELL);
else
crabort(INVALIDUSER);
}
if (strlcpy(login, pp, sizeof (login)) >= sizeof (login))
crabort(NAMETOOLONG);
if (!allowed(login, CRONALLOW, CRONDENY))
crabort(NOTALLOWED);
pam_error = pam_start("cron", pp, NULL, &pamh);
if (pam_error != PAM_SUCCESS) {
crabort((char *)pam_strerror(pamh, pam_error));
}
pam_error = pam_acct_mgmt(pamh, PAM_SILENT);
if (pam_error != PAM_SUCCESS) {
(void) fprintf(stderr, gettext("Warning - Invalid account: "
"'%s' not allowed to execute cronjobs\n"), pp);
}
(void) pam_end(pamh, PAM_SUCCESS);
if (audit_crontab_not_allowed(ruid, pp))
crabort(AUDITREJECT);
cf = xmalloc(strlen(CRONDIR)+strlen(login)+2);
strcat(strcat(strcpy(cf, CRONDIR), "/"), login);
if (rflag) {
r = unlink(cf);
cron_sendmsg(DELETE, login, login, CRON);
audit_crontab_delete(cf, r);
exit(0);
}
if (lflag) {
if ((fp = fopen(cf, "r")) == NULL)
crabort(BADOPEN);
while (fgets(line, CTLINESIZE, fp) != NULL)
fputs(line, stdout);
fclose(fp);
exit(0);
}
if (eflag) {
if ((fp = fopen(cf, "r")) == NULL) {
if (errno != ENOENT)
crabort(BADOPEN);
}
(void) strcpy(edtemp, "/tmp/crontabXXXXXX");
tmpfd = mkstemp(edtemp);
if (fchown(tmpfd, ruid, -1) == -1) {
(void) close(tmpfd);
crabort("fchown of temporary file failed");
}
(void) close(tmpfd);
if ((pid = fork()) == (pid_t)-1)
crabort("fork failed");
if (pid == 0) {
setuid(ruid);
if ((tmpfp = fopen(edtemp, "w")) == NULL)
crabort("can't create temporary file");
if (fp != NULL) {
while (fgets(line, CTLINESIZE, fp) != NULL) {
fputs(line, tmpfp);
if (ferror(tmpfp)) {
fclose(fp);
fclose(tmpfp);
crabort("write error on"
"temporary file");
}
}
if (ferror(fp)) {
fclose(fp);
fclose(tmpfp);
crabort(BADREAD);
}
fclose(fp);
}
if (fclose(tmpfp) == EOF)
crabort("write error on temporary file");
if (stat(edtemp, &stbuf) < 0)
crabort("can't stat temporary file");
omodtime = stbuf.st_mtime;
#ifdef _XPG_NOTDEFINED
editor = getenv("VISUAL");
if (editor == NULL) {
#endif
editor = getenv("EDITOR");
if (editor == NULL)
editor = VIPATH;
#ifdef _XPG_NOTDEFINED
}
#endif
buflen = strlen(editor) + strlen(edtemp) + 2;
buf = xmalloc(buflen);
(void) snprintf(buf, buflen, "%s %s", editor, edtemp);
sleep(1);
while (1) {
ret = system(buf);
if ((tmpfp = fopen(edtemp, "r")) == NULL)
crabort("can't open temporary file");
if (fstat(fileno(tmpfp), &stbuf) < 0)
crabort("can't stat temporary file");
if (stbuf.st_size == 0)
crabort("temporary file empty");
if (omodtime == stbuf.st_mtime) {
(void) unlink(edtemp);
fprintf(stderr, gettext(
"The crontab file was not"
" changed.\n"));
exit(1);
}
if ((ret) && (errno != EINTR)) {
fprintf(stderr, gettext(ED_ERROR));
fflush(stderr);
if (isatty(fileno(stdin))) {
fprintf(stdout,
gettext(ED_PROMPT),
yesstr, nostr, nostr);
fflush(stdout);
if (yes()) {
continue;
} else {
(void) unlink(edtemp);
exit(1);
}
} else {
(void) unlink(edtemp);
exit(1);
}
}
exit(0);
}
}
(void) signal(SIGINT, SIG_IGN);
(void) signal(SIGHUP, SIG_IGN);
(void) signal(SIGQUIT, SIG_IGN);
(void) signal(SIGTERM, SIG_IGN);
wait(&stat_loc);
if ((stat_loc & 0xFF00) != 0)
exit(1);
if (((ret = seteuid(ruid)) < 0) ||
((tmpfp = fopen(edtemp, "r")) == NULL) ||
(unlink(edtemp) == -1)) {
fprintf(stderr, "crontab: %s: %s\n",
edtemp, errmsg(errno));
if ((ret < 0) || (tmpfp == NULL))
(void) unlink(edtemp);
exit(1);
} else
seteuid(0);
copycron(tmpfp);
} else {
if (argc == 0)
copycron(stdin);
else if (seteuid(getuid()) != 0 || (fp = fopen(argv[0], "r"))
== NULL)
crabort(BADOPEN);
else {
seteuid(0);
copycron(fp);
}
}
cron_sendmsg(ADD, login, login, CRON);
return (0);
}
static void
copycron(FILE *fp)
{
FILE *tfp;
char pid[6], *tnam_end;
int t;
char buf[LINE_MAX];
const char *errstr;
cferror_t cferr;
sprintf(pid, "%-5d", getpid());
tnam = xmalloc(strlen(CRONDIR)+strlen(TMPFILE)+7);
strcat(strcat(strcat(strcpy(tnam, CRONDIR), "/"), TMPFILE), pid);
tnam_end = strchr(tnam, ' ');
if (tnam_end != NULL)
*tnam_end = 0;
if (signal(SIGINT, catch) == SIG_IGN)
signal(SIGINT, SIG_IGN);
if (signal(SIGHUP, catch) == SIG_IGN) signal(SIGHUP, SIG_IGN);
if (signal(SIGQUIT, catch) == SIG_IGN) signal(SIGQUIT, SIG_IGN);
if (signal(SIGTERM, catch) == SIG_IGN) signal(SIGTERM, SIG_IGN);
if ((t = creat(tnam, CRMODE)) == -1) crabort(BADCREATE);
if ((tfp = fdopen(t, "w")) == NULL) {
unlink(tnam);
crabort(BADCREATE);
}
err = 0;
while (fgets(line, CTLINESIZE, fp) != NULL) {
cursor = 0;
while (line[cursor] == ' ' || line[cursor] == '\t')
cursor++;
if (line[cursor] == '#' || line[cursor] == '\n')
goto cont;
if (strncmp(&line[cursor], ENV_TZ, strlen(ENV_TZ)) == 0) {
char *x;
(void) strncpy(buf, &line[cursor + strlen(ENV_TZ)],
sizeof (buf));
if ((x = strchr(buf, '\n')) != NULL)
*x = '\0';
if (isvalid_tz(buf, NULL, _VTZ_ALL)) {
goto cont;
} else {
err = 1;
fprintf(stderr, BAD_TZ, &line[cursor]);
continue;
}
} else if (strncmp(&line[cursor], ENV_SHELL,
strlen(ENV_SHELL)) == 0) {
char *x;
(void) strncpy(buf, &line[cursor + strlen(ENV_SHELL)],
sizeof (buf));
if ((x = strchr(buf, '\n')) != NULL)
*x = '\0';
if (isvalid_shell(buf)) {
goto cont;
} else {
err = 1;
fprintf(stderr, BAD_SHELL, &line[cursor]);
continue;
}
} else if (strncmp(&line[cursor], ENV_HOME,
strlen(ENV_HOME)) == 0) {
char *x;
(void) strncpy(buf, &line[cursor + strlen(ENV_HOME)],
sizeof (buf));
if ((x = strchr(buf, '\n')) != NULL)
*x = '\0';
if (chdir(buf) == 0) {
goto cont;
} else {
err = 1;
fprintf(stderr, BAD_HOME, &line[cursor],
strerror(errno));
continue;
}
} else if (strncmp(&line[cursor], ENV_RANDOM_DELAY,
strlen(ENV_RANDOM_DELAY)) == 0) {
char *x;
(void) strncpy(buf,
&line[cursor + strlen(ENV_RANDOM_DELAY)],
sizeof (buf));
if ((x = strchr(buf, '\n')) != NULL)
*x = '\0';
(void) strtonum(buf, 0, UINT32_MAX / 60, &errstr);
if (errstr == NULL) {
goto cont;
} else {
err = 1;
fprintf(stderr, BAD_RAND_DELAY,
&line[cursor], strerror(errno));
continue;
}
}
if ((cferr = next_field(0, 59, line, &cursor, NULL)) != CFOK ||
(cferr = next_field(0, 23, line, &cursor, NULL)) != CFOK ||
(cferr = next_field(1, 31, line, &cursor, NULL)) != CFOK ||
(cferr = next_field(1, 12, line, &cursor, NULL)) != CFOK ||
(cferr = next_field(0, 6, line, &cursor, NULL)) != CFOK) {
switch (cferr) {
case CFEOLN:
cerror(EOLN);
break;
case CFUNEXPECT:
cerror(UNEXPECT);
break;
case CFOUTOFBOUND:
cerror(OUTOFBOUND);
break;
case CFEOVERFLOW:
cerror(OVERFLOW);
break;
case CFENOMEM:
(void) fprintf(stderr, "Out of memory\n");
exit(55);
break;
default:
break;
}
continue;
}
if (line[++cursor] == '\0') {
cerror(EOLN);
continue;
}
cont:
if (fputs(line, tfp) == EOF) {
unlink(tnam);
crabort(BADCREATE);
}
}
fclose(fp);
fclose(tfp);
audit_crontab_modify(cf, tnam, err);
if (!err) {
unlink(cf);
if (link(tnam, cf) == -1) {
unlink(tnam);
crabort(BADCREATE);
}
} else {
crabort(ERRSFND);
}
unlink(tnam);
}
static void
cerror(char *msg)
{
fprintf(stderr, gettext("%scrontab: error on previous line; %s\n"),
line, msg);
err = 1;
}
static void
catch(int x)
{
unlink(tnam);
exit(1);
}
static void
crabort(char *msg)
{
int sverrno;
if (strcmp(edtemp, "") != 0) {
sverrno = errno;
(void) unlink(edtemp);
errno = sverrno;
}
if (tnam != NULL) {
sverrno = errno;
(void) unlink(tnam);
errno = sverrno;
}
fprintf(stderr, "crontab: %s\n", gettext(msg));
exit(1);
}