235 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			235 lines
		
	
	
		
			5.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 "libbb.h"
 | 
						|
 | 
						|
#ifndef CRONTABS
 | 
						|
#define CRONTABS        "/var/spool/cron/crontabs"
 | 
						|
#endif
 | 
						|
#ifndef CRONUPDATE
 | 
						|
#define CRONUPDATE      "cron.update"
 | 
						|
#endif
 | 
						|
 | 
						|
static void change_user(const struct passwd *pas)
 | 
						|
{
 | 
						|
	setenv("USER", pas->pw_name, 1);
 | 
						|
	setenv("HOME", pas->pw_dir, 1);
 | 
						|
	setenv("SHELL", DEFAULT_SHELL, 1);
 | 
						|
 | 
						|
	/* initgroups, setgid, setuid */
 | 
						|
	change_identity(pas);
 | 
						|
 | 
						|
	if (chdir(pas->pw_dir) < 0) {
 | 
						|
		bb_perror_msg("chdir(%s) by %s failed",
 | 
						|
				pas->pw_dir, pas->pw_name);
 | 
						|
		xchdir("/tmp");
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
static void edit_file(const struct passwd *pas, const char *file)
 | 
						|
{
 | 
						|
	const char *ptr;
 | 
						|
	int pid = vfork();
 | 
						|
 | 
						|
	if (pid < 0) /* failure */
 | 
						|
		bb_perror_msg_and_die("vfork");
 | 
						|
	if (pid) { /* parent */
 | 
						|
		wait4pid(pid);
 | 
						|
		return;
 | 
						|
	}
 | 
						|
 | 
						|
	/* CHILD - change user and run editor */
 | 
						|
	change_user(pas);
 | 
						|
	ptr = getenv("VISUAL");
 | 
						|
	if (!ptr) {
 | 
						|
		ptr = getenv("EDITOR");
 | 
						|
		if (!ptr)
 | 
						|
			ptr = "vi";
 | 
						|
	}
 | 
						|
 | 
						|
	BB_EXECLP(ptr, ptr, file, NULL);
 | 
						|
	bb_perror_msg_and_die("exec %s", ptr);
 | 
						|
}
 | 
						|
 | 
						|
static int open_as_user(const struct passwd *pas, const char *file)
 | 
						|
{
 | 
						|
	pid_t pid;
 | 
						|
	char c;
 | 
						|
 | 
						|
	pid = vfork();
 | 
						|
	if (pid < 0) /* ERROR */
 | 
						|
		bb_perror_msg_and_die("vfork");
 | 
						|
	if (pid) { /* PARENT */
 | 
						|
		if (wait4pid(pid) == 0) {
 | 
						|
			/* exitcode 0: child says it can read */
 | 
						|
			return open(file, O_RDONLY);
 | 
						|
		}
 | 
						|
		return -1;
 | 
						|
	}
 | 
						|
 | 
						|
	/* CHILD */
 | 
						|
	/* initgroups, setgid, setuid */
 | 
						|
	change_identity(pas);
 | 
						|
	/* We just try to read one byte. If it works, file is readable
 | 
						|
	 * under this user. We signal that by exiting with 0. */
 | 
						|
	_exit(safe_read(xopen(file, O_RDONLY), &c, 1) < 0);
 | 
						|
}
 | 
						|
 | 
						|
int crontab_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
 | 
						|
int crontab_main(int argc UNUSED_PARAM, char **argv)
 | 
						|
{
 | 
						|
	const struct passwd *pas;
 | 
						|
	const char *crontab_dir = CRONTABS;
 | 
						|
	char *tmp_fname;
 | 
						|
	char *new_fname;
 | 
						|
	char *user_name;  /* -u USER */
 | 
						|
	int fd;
 | 
						|
	int opt_ler;
 | 
						|
 | 
						|
	/* file [opts]     Replace crontab from file
 | 
						|
	 * - [opts]        Replace crontab from stdin
 | 
						|
	 * -u user         User
 | 
						|
	 * -c dir          Crontab directory
 | 
						|
	 * -l              List crontab for user
 | 
						|
	 * -e              Edit crontab for user
 | 
						|
	 * -r              Delete crontab for user
 | 
						|
	 * bbox also supports -d == -r, but most other crontab
 | 
						|
	 * implementations do not. Deprecated.
 | 
						|
	 */
 | 
						|
	enum {
 | 
						|
		OPT_u = (1 << 0),
 | 
						|
		OPT_c = (1 << 1),
 | 
						|
		OPT_l = (1 << 2),
 | 
						|
		OPT_e = (1 << 3),
 | 
						|
		OPT_r = (1 << 4),
 | 
						|
		OPT_ler = OPT_l + OPT_e + OPT_r,
 | 
						|
	};
 | 
						|
 | 
						|
	opt_complementary = "?1:dr"; /* max one argument; -d implies -r */
 | 
						|
	opt_ler = getopt32(argv, "u:c:lerd", &user_name, &crontab_dir);
 | 
						|
	argv += optind;
 | 
						|
 | 
						|
	if (sanitize_env_if_suid()) { /* Clears dangerous stuff, sets PATH */
 | 
						|
		/* run by non-root? */
 | 
						|
		if (opt_ler & (OPT_u|OPT_c))
 | 
						|
			bb_error_msg_and_die("only root can use -c or -u");
 | 
						|
	}
 | 
						|
 | 
						|
	if (opt_ler & OPT_u) {
 | 
						|
		pas = getpwnam(user_name);
 | 
						|
		if (!pas)
 | 
						|
			bb_error_msg_and_die("user %s is not known", user_name);
 | 
						|
	} else {
 | 
						|
/* XXX: xgetpwuid */
 | 
						|
		uid_t my_uid = getuid();
 | 
						|
		pas = getpwuid(my_uid);
 | 
						|
		if (!pas)
 | 
						|
			bb_perror_msg_and_die("unknown uid %d", (int)my_uid);
 | 
						|
	}
 | 
						|
 | 
						|
#define user_name DONT_USE_ME_BEYOND_THIS_POINT
 | 
						|
 | 
						|
	/* From now on, keep only -l, -e, -r bits */
 | 
						|
	opt_ler &= OPT_ler;
 | 
						|
	if ((opt_ler - 1) & opt_ler) /* more than one bit set? */
 | 
						|
		bb_show_usage();
 | 
						|
 | 
						|
	/* Read replacement file under user's UID/GID/group vector */
 | 
						|
	if (!opt_ler) { /* Replace? */
 | 
						|
		if (!argv[0])
 | 
						|
			bb_show_usage();
 | 
						|
		if (NOT_LONE_DASH(argv[0])) {
 | 
						|
			fd = open_as_user(pas, argv[0]);
 | 
						|
			if (fd < 0)
 | 
						|
				bb_error_msg_and_die("user %s cannot read %s",
 | 
						|
						pas->pw_name, argv[0]);
 | 
						|
			xmove_fd(fd, STDIN_FILENO);
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	/* cd to our crontab directory */
 | 
						|
	xchdir(crontab_dir);
 | 
						|
 | 
						|
	tmp_fname = NULL;
 | 
						|
 | 
						|
	/* Handle requested operation */
 | 
						|
	switch (opt_ler) {
 | 
						|
 | 
						|
	default: /* case OPT_r: Delete */
 | 
						|
		unlink(pas->pw_name);
 | 
						|
		break;
 | 
						|
 | 
						|
	case OPT_l: /* List */
 | 
						|
		{
 | 
						|
			char *args[2] = { pas->pw_name, NULL };
 | 
						|
			return bb_cat(args);
 | 
						|
			/* list exits,
 | 
						|
			 * the rest go play with cron update file */
 | 
						|
		}
 | 
						|
 | 
						|
	case OPT_e: /* Edit */
 | 
						|
		tmp_fname = xasprintf("%s.%u", crontab_dir, (unsigned)getpid());
 | 
						|
		/* No O_EXCL: we don't want to be stuck if earlier crontabs
 | 
						|
		 * were killed, leaving stale temp file behind */
 | 
						|
		fd = xopen3(tmp_fname, O_RDWR|O_CREAT|O_TRUNC, 0600);
 | 
						|
		xmove_fd(fd, STDIN_FILENO);
 | 
						|
		fchown(STDIN_FILENO, pas->pw_uid, pas->pw_gid);
 | 
						|
		fd = open(pas->pw_name, O_RDONLY);
 | 
						|
		if (fd >= 0) {
 | 
						|
			bb_copyfd_eof(fd, STDIN_FILENO);
 | 
						|
			close(fd);
 | 
						|
		}
 | 
						|
		edit_file(pas, tmp_fname);
 | 
						|
		xlseek(STDIN_FILENO, 0, SEEK_SET);
 | 
						|
		/* fall through */
 | 
						|
 | 
						|
	case 0: /* Replace (no -l, -e, or -r were given) */
 | 
						|
		new_fname = xasprintf("%s.new", pas->pw_name);
 | 
						|
		fd = open(new_fname, O_WRONLY|O_CREAT|O_TRUNC|O_APPEND, 0600);
 | 
						|
		if (fd >= 0) {
 | 
						|
			bb_copyfd_eof(STDIN_FILENO, fd);
 | 
						|
			close(fd);
 | 
						|
			xrename(new_fname, pas->pw_name);
 | 
						|
		} else {
 | 
						|
			bb_error_msg("cannot create %s/%s",
 | 
						|
					crontab_dir, new_fname);
 | 
						|
		}
 | 
						|
		if (tmp_fname)
 | 
						|
			unlink(tmp_fname);
 | 
						|
		/*free(tmp_fname);*/
 | 
						|
		/*free(new_fname);*/
 | 
						|
 | 
						|
	} /* switch */
 | 
						|
 | 
						|
	/* Bump notification file.  Handle window where crond picks file up
 | 
						|
	 * before we can write our entry out.
 | 
						|
	 */
 | 
						|
	while ((fd = open(CRONUPDATE, O_WRONLY|O_CREAT|O_APPEND, 0600)) >= 0) {
 | 
						|
		struct stat st;
 | 
						|
 | 
						|
		fdprintf(fd, "%s\n", pas->pw_name);
 | 
						|
		if (fstat(fd, &st) != 0 || st.st_nlink != 0) {
 | 
						|
			/*close(fd);*/
 | 
						|
			break;
 | 
						|
		}
 | 
						|
		/* st.st_nlink == 0:
 | 
						|
		 * file was deleted, maybe crond missed our notification */
 | 
						|
		close(fd);
 | 
						|
		/* loop */
 | 
						|
	}
 | 
						|
	if (fd < 0) {
 | 
						|
		bb_error_msg("cannot append to %s/%s",
 | 
						|
				crontab_dir, CRONUPDATE);
 | 
						|
	}
 | 
						|
	return 0;
 | 
						|
}
 |