d921b2ecc0
things like xasprintf() into xfuncs.c, remove xprint_file_by_name() (it only had one user), clean up lots of #includes... General cleanup pass. What I've been doing for the last couple days. And it conflicts! I've removed httpd.c from this checkin due to somebody else touching that file. It builds for me. I have to catch a bus. (Now you know why I'm looking forward to Mercurial.)
337 lines
6.6 KiB
C
337 lines
6.6 KiB
C
/* vi: set sw=4 ts=4: */
|
|
/*
|
|
* CRONTAB
|
|
*
|
|
* usually setuid root, -c option only works if getuid() == geteuid()
|
|
*
|
|
* Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
|
|
* Vladimir Oleynik <dzo@simtreas.ru> (C) 2002
|
|
*
|
|
* Licensed under the GPL v2 or later, see the file LICENSE in this tarball.
|
|
*/
|
|
|
|
#include "busybox.h"
|
|
|
|
#ifndef CRONTABS
|
|
#define CRONTABS "/var/spool/cron/crontabs"
|
|
#endif
|
|
#ifndef TMPDIR
|
|
#define TMPDIR "/var/spool/cron"
|
|
#endif
|
|
#ifndef CRONUPDATE
|
|
#define CRONUPDATE "cron.update"
|
|
#endif
|
|
#ifndef PATH_VI
|
|
#define PATH_VI "/bin/vi" /* location of vi */
|
|
#endif
|
|
|
|
static const char *CDir = CRONTABS;
|
|
|
|
static void EditFile(const char *user, const char *file);
|
|
static int GetReplaceStream(const char *user, const char *file);
|
|
static int ChangeUser(const char *user, short dochdir);
|
|
|
|
int crontab_main(int ac, char **av)
|
|
{
|
|
enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;
|
|
const struct passwd *pas;
|
|
const char *repFile = NULL;
|
|
int repFd = 0;
|
|
int i;
|
|
char caller[256]; /* user that ran program */
|
|
int UserId;
|
|
|
|
UserId = getuid();
|
|
if ((pas = getpwuid(UserId)) == NULL)
|
|
bb_perror_msg_and_die("getpwuid");
|
|
|
|
safe_strncpy(caller, pas->pw_name, sizeof(caller));
|
|
|
|
i = 1;
|
|
if (ac > 1) {
|
|
if (av[1][0] == '-' && av[1][1] == 0) {
|
|
option = REPLACE;
|
|
++i;
|
|
} else if (av[1][0] != '-') {
|
|
option = REPLACE;
|
|
++i;
|
|
repFile = av[1];
|
|
}
|
|
}
|
|
|
|
for (; i < ac; ++i) {
|
|
char *ptr = av[i];
|
|
|
|
if (*ptr != '-')
|
|
break;
|
|
ptr += 2;
|
|
|
|
switch(ptr[-1]) {
|
|
case 'l':
|
|
if (ptr[-1] == 'l')
|
|
option = LIST;
|
|
/* fall through */
|
|
case 'e':
|
|
if (ptr[-1] == 'e')
|
|
option = EDIT;
|
|
/* fall through */
|
|
case 'd':
|
|
if (ptr[-1] == 'd')
|
|
option = DELETE;
|
|
/* fall through */
|
|
case 'u':
|
|
if (i + 1 < ac && av[i+1][0] != '-') {
|
|
++i;
|
|
if (getuid() == geteuid()) {
|
|
pas = getpwnam(av[i]);
|
|
if (pas) {
|
|
UserId = pas->pw_uid;
|
|
} else {
|
|
bb_error_msg_and_die("user %s unknown", av[i]);
|
|
}
|
|
} else {
|
|
bb_error_msg_and_die("only the superuser may specify a user");
|
|
}
|
|
}
|
|
break;
|
|
case 'c':
|
|
if (getuid() == geteuid()) {
|
|
CDir = (*ptr) ? ptr : av[++i];
|
|
} else {
|
|
bb_error_msg_and_die("-c option: superuser only");
|
|
}
|
|
break;
|
|
default:
|
|
i = ac;
|
|
break;
|
|
}
|
|
}
|
|
if (i != ac || option == NONE)
|
|
bb_show_usage();
|
|
|
|
/*
|
|
* Get password entry
|
|
*/
|
|
|
|
if ((pas = getpwuid(UserId)) == NULL)
|
|
bb_perror_msg_and_die("getpwuid");
|
|
|
|
/*
|
|
* If there is a replacement file, obtain a secure descriptor to it.
|
|
*/
|
|
|
|
if (repFile) {
|
|
repFd = GetReplaceStream(caller, repFile);
|
|
if (repFd < 0)
|
|
bb_error_msg_and_die("unable to read replacement file");
|
|
}
|
|
|
|
/*
|
|
* Change directory to our crontab directory
|
|
*/
|
|
|
|
xchdir(CDir);
|
|
|
|
/*
|
|
* Handle options as appropriate
|
|
*/
|
|
|
|
switch(option) {
|
|
case LIST:
|
|
{
|
|
FILE *fi;
|
|
char buf[1024];
|
|
|
|
if ((fi = fopen(pas->pw_name, "r"))) {
|
|
while (fgets(buf, sizeof(buf), fi) != NULL)
|
|
fputs(buf, stdout);
|
|
fclose(fi);
|
|
} else {
|
|
bb_error_msg("no crontab for %s", pas->pw_name);
|
|
}
|
|
}
|
|
break;
|
|
case EDIT:
|
|
{
|
|
FILE *fi;
|
|
int fd;
|
|
int n;
|
|
char tmp[128];
|
|
char buf[1024];
|
|
|
|
snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid());
|
|
fd = xopen3(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600);
|
|
chown(tmp, getuid(), getgid());
|
|
if ((fi = fopen(pas->pw_name, "r"))) {
|
|
while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
|
|
write(fd, buf, n);
|
|
}
|
|
EditFile(caller, tmp);
|
|
remove(tmp);
|
|
lseek(fd, 0L, 0);
|
|
repFd = fd;
|
|
}
|
|
option = REPLACE;
|
|
/* fall through */
|
|
case REPLACE:
|
|
{
|
|
char buf[1024];
|
|
char path[1024];
|
|
int fd;
|
|
int n;
|
|
|
|
snprintf(path, sizeof(path), "%s.new", pas->pw_name);
|
|
if ((fd = open(path, O_CREAT|O_TRUNC|O_APPEND|O_WRONLY, 0600)) >= 0) {
|
|
while ((n = read(repFd, buf, sizeof(buf))) > 0) {
|
|
write(fd, buf, n);
|
|
}
|
|
close(fd);
|
|
rename(path, pas->pw_name);
|
|
} else {
|
|
bb_error_msg("unable to create %s/%s", CDir, path);
|
|
}
|
|
close(repFd);
|
|
}
|
|
break;
|
|
case DELETE:
|
|
remove(pas->pw_name);
|
|
break;
|
|
case NONE:
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Bump notification file. Handle window where crond picks file up
|
|
* before we can write our entry out.
|
|
*/
|
|
|
|
if (option == REPLACE || option == DELETE) {
|
|
FILE *fo;
|
|
struct stat st;
|
|
|
|
while ((fo = fopen(CRONUPDATE, "a"))) {
|
|
fprintf(fo, "%s\n", pas->pw_name);
|
|
fflush(fo);
|
|
if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) {
|
|
fclose(fo);
|
|
break;
|
|
}
|
|
fclose(fo);
|
|
/* loop */
|
|
}
|
|
if (fo == NULL) {
|
|
bb_error_msg("unable to append to %s/%s", CDir, CRONUPDATE);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int GetReplaceStream(const char *user, const char *file)
|
|
{
|
|
int filedes[2];
|
|
int pid;
|
|
int fd;
|
|
int n;
|
|
char buf[1024];
|
|
|
|
if (pipe(filedes) < 0) {
|
|
perror("pipe");
|
|
return(-1);
|
|
}
|
|
if ((pid = fork()) < 0) {
|
|
perror("fork");
|
|
return(-1);
|
|
}
|
|
if (pid > 0) {
|
|
/*
|
|
* PARENT
|
|
*/
|
|
|
|
close(filedes[1]);
|
|
if (read(filedes[0], buf, 1) != 1) {
|
|
close(filedes[0]);
|
|
filedes[0] = -1;
|
|
}
|
|
return(filedes[0]);
|
|
}
|
|
|
|
/*
|
|
* CHILD
|
|
*/
|
|
|
|
close(filedes[0]);
|
|
|
|
if (ChangeUser(user, 0) < 0)
|
|
exit(0);
|
|
|
|
bb_default_error_retval = 0;
|
|
fd = xopen3(file, O_RDONLY, 0);
|
|
buf[0] = 0;
|
|
write(filedes[1], buf, 1);
|
|
while ((n = read(fd, buf, sizeof(buf))) > 0) {
|
|
write(filedes[1], buf, n);
|
|
}
|
|
exit(0);
|
|
}
|
|
|
|
static void EditFile(const char *user, const char *file)
|
|
{
|
|
int pid;
|
|
|
|
if ((pid = fork()) == 0) {
|
|
/*
|
|
* CHILD - change user and run editor
|
|
*/
|
|
char *ptr;
|
|
char visual[1024];
|
|
|
|
if (ChangeUser(user, 1) < 0)
|
|
exit(0);
|
|
if ((ptr = getenv("VISUAL")) == NULL || strlen(ptr) > 256)
|
|
ptr = PATH_VI;
|
|
|
|
snprintf(visual, sizeof(visual), "%s %s", ptr, file);
|
|
execl(DEFAULT_SHELL, DEFAULT_SHELL, "-c", visual, NULL);
|
|
perror("exec");
|
|
exit(0);
|
|
}
|
|
if (pid < 0) {
|
|
/*
|
|
* PARENT - failure
|
|
*/
|
|
bb_perror_msg_and_die("fork");
|
|
}
|
|
wait4(pid, NULL, 0, NULL);
|
|
}
|
|
|
|
static int ChangeUser(const char *user, short dochdir)
|
|
{
|
|
struct passwd *pas;
|
|
|
|
/*
|
|
* Obtain password entry and change privileges
|
|
*/
|
|
|
|
if ((pas = getpwnam(user)) == NULL) {
|
|
bb_perror_msg_and_die("failed 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);
|
|
|
|
/*
|
|
* Change running state to the user in question
|
|
*/
|
|
change_identity(pas);
|
|
|
|
if (dochdir) {
|
|
if (chdir(pas->pw_dir) < 0) {
|
|
bb_perror_msg("chdir failed: %s %s", user, pas->pw_dir);
|
|
xchdir(TMPDIR);
|
|
}
|
|
}
|
|
return(pas->pw_uid);
|
|
}
|