aboutsummaryrefslogtreecommitdiff
path: root/miscutils/crontab.c
diff options
context:
space:
mode:
Diffstat (limited to 'miscutils/crontab.c')
-rw-r--r--miscutils/crontab.c393
1 files changed, 393 insertions, 0 deletions
diff --git a/miscutils/crontab.c b/miscutils/crontab.c
new file mode 100644
index 000000000..c86990653
--- /dev/null
+++ b/miscutils/crontab.c
@@ -0,0 +1,393 @@
+/*
+ * CRONTAB
+ *
+ * usually setuid root, -c option only works if getuid() == geteuid()
+ *
+ * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
+ * May be distributed under the GNU General Public License
+ *
+ * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 to be used in busybox
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <errno.h>
+#include <time.h>
+#include <dirent.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <unistd.h>
+#include <grp.h>
+#include <syslog.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/ioctl.h>
+#include <sys/wait.h>
+#include <sys/stat.h>
+#include <sys/resource.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 "/usr/bin/vi" /* location of vi */
+#endif
+
+#include "busybox.h"
+
+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)
+ perror_msg_and_die("getpwuid");
+
+ 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 {
+ error_msg_and_die("user %s unknown", av[i]);
+ }
+ } else {
+ error_msg_and_die("only the superuser may specify a user");
+ }
+ }
+ break;
+ case 'c':
+ if (getuid() == geteuid()) {
+ CDir = (*ptr) ? ptr : av[++i];
+ } else {
+ error_msg_and_die("-c option: superuser only");
+ }
+ break;
+ default:
+ i = ac;
+ break;
+ }
+ }
+ if (i != ac || option == NONE)
+ show_usage();
+
+ /*
+ * Get password entry
+ */
+
+ if ((pas = getpwuid(UserId)) == NULL)
+ 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)
+ error_msg_and_die("unable to read replacement file");
+ }
+
+ /*
+ * Change directory to our crontab directory
+ */
+
+ if (chdir(CDir) < 0)
+ perror_msg_and_die("cannot change dir to %s", 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 {
+ 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());
+ if ((fd = open(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600)) >= 0) {
+ 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;
+ } else {
+ error_msg_and_die("unable to create %s", tmp);
+ }
+
+ }
+ 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_EXCL|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 {
+ error_msg("unable to create %s/%s", CDir, buf);
+ }
+ 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) {
+ 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);
+
+ fd = open(file, O_RDONLY);
+ if (fd < 0) {
+ error_msg("unable to open %s", file);
+ exit(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("/bin/sh", "/bin/sh", "-c", visual, NULL);
+ perror("exec");
+ exit(0);
+ }
+ if (pid < 0) {
+ /*
+ * PARENT - failure
+ */
+ perror_msg_and_die("fork");
+ }
+ wait4(pid, NULL, 0, NULL);
+}
+
+static void
+log(const char *ctl, ...)
+{
+ va_list va;
+ char buf[1024];
+
+ va_start(va, ctl);
+ vsnprintf(buf, sizeof(buf), ctl, va);
+ syslog(LOG_NOTICE, "%s",buf );
+ va_end(va);
+}
+
+static int
+ChangeUser(const char *user, short dochdir)
+{
+ struct passwd *pas;
+
+ /*
+ * Obtain password entry and change privilages
+ */
+
+ if ((pas = getpwnam(user)) == 0) {
+ log("failed to get uid for %s", user);
+ return(-1);
+ }
+ setenv("USER", pas->pw_name, 1);
+ setenv("HOME", pas->pw_dir, 1);
+ setenv("SHELL", "/bin/sh", 1);
+
+ /*
+ * Change running state to the user in question
+ */
+
+ if (initgroups(user, pas->pw_gid) < 0) {
+ log("initgroups failed: %s %m", user);
+ return(-1);
+ }
+ if (setregid(pas->pw_gid, pas->pw_gid) < 0) {
+ log("setregid failed: %s %d", user, pas->pw_gid);
+ return(-1);
+ }
+ if (setreuid(pas->pw_uid, pas->pw_uid) < 0) {
+ log("setreuid failed: %s %d", user, pas->pw_uid);
+ return(-1);
+ }
+ if (dochdir) {
+ if (chdir(pas->pw_dir) < 0) {
+ if (chdir(TMPDIR) < 0) {
+ log("chdir failed: %s %s", user, pas->pw_dir);
+ log("chdir failed: %s " TMPDIR, user);
+ return(-1);
+ }
+ }
+ }
+ return(pas->pw_uid);
+}