Radix cross Linux

The main Radix cross Linux repository contains the build scripts of packages, which have the most complete and common functionality for desktop machines

452 Commits   2 Branches   1 Tag
Index: crontab.c
===================================================================
--- crontab.c	(nonexistent)
+++ crontab.c	(revision 5)
@@ -0,0 +1,369 @@
+
+/*
+ * CRONTAB.C
+ *
+ * crontab [-u user] [-c dir] [-l|-e|-d|file|-]
+ * usually run as setuid root
+ * -u and -c options only work if getuid() == geteuid()
+ *
+ * Copyright 1994 Matthew Dillon (dillon@apollo.backplane.com)
+ * Copyright 2009-2011 James Pryor <profjim@jimpryor.net>
+ * May be distributed under the GNU General Public License
+ */
+
+#include "defs.h"
+
+Prototype void printlogf(int level, const char *ctl, ...);
+
+void Usage(void);
+int GetReplaceStream(const char *user, const char *file);
+void EditFile(const char *user, const char *file);
+
+const char *CDir = CRONTABS;
+int   UserId;
+
+
+int
+main(int ac, char **av)
+{
+	enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;
+	struct passwd *pas;
+	char *repFile = NULL;
+	int repFd = 0;
+	int i;
+	char caller[SMALL_BUFFER];		/* user that ran program */
+
+	UserId = getuid();
+	if ((pas = getpwuid(UserId)) == NULL) {
+		perror("getpwuid");
+		exit(1);
+	}
+	/* [v]snprintf write at most size including \0; they'll null-terminate, even when they truncate */
+	/* return value >= size means result was truncated */
+	if (snprintf(caller, sizeof(caller), "%s", pas->pw_name) >= sizeof(caller)) {
+		printlogf(0, "username '%s' too long", caller);
+		exit(1);
+	}
+
+	opterr = 0;
+	while ((i=getopt(ac,av,"ledu:c:")) != -1) {
+		switch(i) {
+			case 'l':
+				if (option != NONE)
+					Usage();
+				else
+					option = LIST;
+				break;
+			case 'e':
+				if (option != NONE)
+					Usage();
+				else
+					option = EDIT;
+				break;
+			case 'd':
+				if (option != NONE)
+					Usage();
+				else
+					option = DELETE;
+				break;
+			case 'u':
+				/* getopt guarantees optarg != 0 here */
+				if (*optarg != 0 && getuid() == geteuid()) {
+					pas = getpwnam(optarg);
+					if (pas) {
+						UserId = pas->pw_uid;
+						/* paranoia */
+						if ((pas = getpwuid(UserId)) == NULL) {
+							perror("getpwuid");
+							exit(1);
+						}
+					} else {
+						printlogf(0, "user '%s' unknown", optarg);
+						exit(1);
+					}
+				} else {
+					printlogf(0, "-u option: superuser only");
+					exit(1);
+				}
+				break;
+			case 'c':
+				/* getopt guarantees optarg != 0 here */
+				if (*optarg != 0 && getuid() == geteuid()) {
+					CDir = optarg;
+				} else {
+					printlogf(0, "-c option: superuser only");
+					exit(1);
+				}
+				break;
+			default:
+				/* unrecognized -X */
+				option = NONE;
+		}
+	}
+
+	if (option == NONE && optind == ac - 1) {
+		if (av[optind][0] != '-') {
+			option = REPLACE;
+			repFile = av[optind];
+			optind++;
+		} else if (av[optind][1] == 0) {
+			option = REPLACE;
+			optind++;
+		}
+	}
+	if (option == NONE || optind != ac) {
+		Usage();
+	}
+
+	/*
+	 * If there is a replacement file, obtain a secure descriptor to it.
+	 */
+
+	if (repFile) {
+		repFd = GetReplaceStream(caller, repFile);
+		if (repFd < 0) {
+			printlogf(0, "unable to read replacement file %s", repFile);
+			exit(1);
+		}
+	}
+
+	/*
+	 * Change directory to our crontab directory
+	 */
+
+	if (chdir(CDir) < 0) {
+		printlogf(0, "cannot change dir to %s: %s", CDir, strerror(errno));
+		exit(1);
+	}
+
+	/*
+	 * Handle options as appropriate
+	 */
+
+	switch(option) {
+		case LIST:
+			{
+				FILE *fi;
+				char buf[RW_BUFFER];
+
+				if ((fi = fopen(pas->pw_name, "r"))) {
+					while (fgets(buf, sizeof(buf), fi) != NULL)
+						fputs(buf, stdout);
+					fclose(fi);
+				} else {
+					fprintf(stderr, "no crontab for %s\n", pas->pw_name);
+					/* no error code */
+				}
+			}
+			break;
+		case EDIT:
+			{
+				FILE *fi;
+				int fd;
+				int n;
+				char tmp[] = TMPDIR "/crontab.XXXXXX";
+				char buf[RW_BUFFER];
+
+				/*
+				 * Create temp file with perm 0600 and O_EXCL flag, ensuring that this call creates the file
+				 * Read from fi for "$CDir/$USER", write to fd for temp file
+				 * EditFile changes user if necessary, and runs editor on temp file
+				 * Then we delete the temp file, keeping its fd as repFd
+				 */
+				if ((fd = mkstemp(tmp)) >= 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 {
+					printlogf(0, "unable to create %s: %s", tmp, strerror(errno));
+					exit(1);
+				}
+
+			}
+			option = REPLACE;
+			/* fall through */
+		case REPLACE:
+			{
+				char buf[RW_BUFFER];
+				char path[SMALL_BUFFER];
+				int fd;
+				int n;
+
+				/*
+				 * Read from repFd, write to fd for "$CDir/$USER.new"
+				 */
+				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 {
+					fprintf(stderr, "unable to create %s/%s: %s\n",
+							CDir,
+							path,
+							strerror(errno)
+						   );
+				}
+				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) {
+			fprintf(stderr, "unable to append to %s/%s\n", CDir, CRONUPDATE);
+		}
+	}
+	exit(0);
+	/* not reached */
+}
+
+void
+printlogf(int level, const char *ctl, ...)
+{
+	va_list va;
+	char buf[LOG_BUFFER];
+
+	va_start(va, ctl);
+	vsnprintf(buf, sizeof(buf), ctl, va);
+	write(2, buf, strlen(buf));
+	va_end(va);
+}
+
+void
+Usage(void)
+{
+	/*
+	 * parse error
+	 */
+	printf("crontab " VERSION "\n");
+	printf("crontab file [-u user]  replace crontab from file\n");
+	printf("crontab -  [-u user]    replace crontab from stdin\n");
+	printf("crontab -l [-u user]    list crontab\n");
+	printf("crontab -e [-u user]    edit crontab\n");
+	printf("crontab -d [-u user]    delete crontab\n");
+	printf("crontab -c dir <opts>   specify crontab directory\n");
+	exit(2);
+}
+
+int
+GetReplaceStream(const char *user, const char *file)
+{
+	int filedes[2];
+	int pid;
+	int fd;
+	int n;
+	char buf[RW_BUFFER];
+
+	if (pipe(filedes) < 0) {
+		perror("pipe");
+		return(-1);
+	}
+	if ((pid = fork()) < 0) {
+		perror("fork");
+		return(-1);
+	}
+	if (pid > 0) {
+		/*
+		 * PARENT
+		 * Read from pipe[0], return it (or -1 if it's empty)
+		 */
+
+		close(filedes[1]);
+		if (read(filedes[0], buf, 1) != 1) {
+			close(filedes[0]);
+			filedes[0] = -1;
+		}
+		return(filedes[0]);
+	}
+
+	/*
+	 * CHILD
+	 * Read from fd for "$file", write to pipe[1]
+	 */
+
+	close(filedes[0]);
+
+	if (ChangeUser(user, NULL) < 0)
+		exit(0);
+
+	fd = open(file, O_RDONLY);
+	if (fd < 0) {
+		printlogf(0, "unable to open %s: %s", file, strerror(errno));
+		exit(1);
+	}
+	buf[0] = 0;
+	write(filedes[1], buf, 1);
+	while ((n = read(fd, buf, sizeof(buf))) > 0) {
+		write(filedes[1], buf, n);
+	}
+	exit(0);
+}
+
+void
+EditFile(const char *user, const char *file)
+{
+	int pid;
+
+	if ((pid = fork()) == 0) {
+		/*
+		 * CHILD - change user and run editor on "$file"
+		 */
+		const char *ptr;
+		char visual[SMALL_BUFFER];
+
+		if (ChangeUser(user, TMPDIR) < 0)
+			exit(0);
+		if ((ptr = getenv("EDITOR")) == NULL || strlen(ptr) >= sizeof(visual))
+			if ((ptr = getenv("VISUAL")) == NULL || strlen(ptr) >= sizeof(visual))
+				ptr = PATH_VI;
+
+		/* [v]snprintf write at most size including \0; they'll null-terminate, even when they truncate */
+		/* return value >= size means result was truncated */
+		if (snprintf(visual, sizeof(visual), "%s %s", ptr, file) < sizeof(visual))
+			execl("/bin/sh", "/bin/sh", "-c", visual, NULL);
+		printlogf(0, "couldn't exec %s", visual);
+		exit(1);
+	}
+	if (pid < 0) {
+		/*
+		 * PARENT - failure
+		 */
+		perror("fork");
+		exit(1);
+	}
+	waitpid(pid, NULL, 0);
+}
+