4 * usually setuid root, -c option only works if getuid() == geteuid()
6 * Copyright 1994 Matthew Dillon (dillon@apollo.west.oic.com)
7 * May be distributed under the GNU General Public License
9 * Vladimir Oleynik <dzo@simtreas.ru> (C) 2002 to be used in busybox
27 #include <sys/ioctl.h>
30 #include <sys/resource.h>
33 #define CRONTABS "/var/spool/cron/crontabs"
36 #define TMPDIR "/var/spool/cron"
39 #define CRONUPDATE "cron.update"
42 #define PATH_VI "/usr/bin/vi" /* location of vi */
47 static const char *CDir = CRONTABS;
49 static void EditFile(const char *user, const char *file);
50 static int GetReplaceStream(const char *user, const char *file);
51 static int ChangeUser(const char *user, short dochdir);
54 crontab_main(int ac, char **av)
56 enum { NONE, EDIT, LIST, REPLACE, DELETE } option = NONE;
57 const struct passwd *pas;
58 const char *repFile = NULL;
61 char caller[256]; /* user that ran program */
65 if ((pas = getpwuid(UserId)) == NULL)
66 perror_msg_and_die("getpwuid");
68 strncpy(caller, pas->pw_name, sizeof(caller));
72 if (av[1][0] == '-' && av[1][1] == 0) {
75 } else if (av[1][0] != '-') {
103 if (i + 1 < ac && av[i+1][0] != '-') {
105 if (getuid() == geteuid()) {
106 pas = getpwnam(av[i]);
108 UserId = pas->pw_uid;
110 error_msg_and_die("user %s unknown", av[i]);
113 error_msg_and_die("only the superuser may specify a user");
118 if (getuid() == geteuid()) {
119 CDir = (*ptr) ? ptr : av[++i];
121 error_msg_and_die("-c option: superuser only");
129 if (i != ac || option == NONE)
136 if ((pas = getpwuid(UserId)) == NULL)
137 perror_msg_and_die("getpwuid");
140 * If there is a replacement file, obtain a secure descriptor to it.
144 repFd = GetReplaceStream(caller, repFile);
146 error_msg_and_die("unable to read replacement file");
150 * Change directory to our crontab directory
154 perror_msg_and_die("cannot change dir to %s", CDir);
157 * Handle options as appropriate
166 if ((fi = fopen(pas->pw_name, "r"))) {
167 while (fgets(buf, sizeof(buf), fi) != NULL)
171 error_msg("no crontab for %s", pas->pw_name);
183 snprintf(tmp, sizeof(tmp), TMPDIR "/crontab.%d", getpid());
184 if ((fd = open(tmp, O_RDWR|O_CREAT|O_TRUNC|O_EXCL, 0600)) >= 0) {
185 chown(tmp, getuid(), getgid());
186 if ((fi = fopen(pas->pw_name, "r"))) {
187 while ((n = fread(buf, 1, sizeof(buf), fi)) > 0)
190 EditFile(caller, tmp);
195 error_msg_and_die("unable to create %s", tmp);
208 snprintf(path, sizeof(path), "%s.new", pas->pw_name);
209 if ((fd = open(path, O_CREAT|O_TRUNC|O_EXCL|O_APPEND|O_WRONLY, 0600)) >= 0) {
210 while ((n = read(repFd, buf, sizeof(buf))) > 0) {
214 rename(path, pas->pw_name);
216 error_msg("unable to create %s/%s", CDir, buf);
222 remove(pas->pw_name);
230 * Bump notification file. Handle window where crond picks file up
231 * before we can write our entry out.
234 if (option == REPLACE || option == DELETE) {
238 while ((fo = fopen(CRONUPDATE, "a"))) {
239 fprintf(fo, "%s\n", pas->pw_name);
241 if (fstat(fileno(fo), &st) != 0 || st.st_nlink != 0) {
249 error_msg("unable to append to %s/%s", CDir, CRONUPDATE);
256 GetReplaceStream(const char *user, const char *file)
264 if (pipe(filedes) < 0) {
268 if ((pid = fork()) < 0) {
278 if (read(filedes[0], buf, 1) != 1) {
291 if (ChangeUser(user, 0) < 0)
294 fd = open(file, O_RDONLY);
296 error_msg("unable to open %s", file);
300 write(filedes[1], buf, 1);
301 while ((n = read(fd, buf, sizeof(buf))) > 0) {
302 write(filedes[1], buf, n);
308 EditFile(const char *user, const char *file)
312 if ((pid = fork()) == 0) {
314 * CHILD - change user and run editor
319 if (ChangeUser(user, 1) < 0)
321 if ((ptr = getenv("VISUAL")) == NULL || strlen(ptr) > 256)
324 snprintf(visual, sizeof(visual), "%s %s", ptr, file);
325 execl("/bin/sh", "/bin/sh", "-c", visual, NULL);
333 perror_msg_and_die("fork");
335 wait4(pid, NULL, 0, NULL);
339 log(const char *ctl, ...)
345 vsnprintf(buf, sizeof(buf), ctl, va);
346 syslog(LOG_NOTICE, "%s",buf );
351 ChangeUser(const char *user, short dochdir)
356 * Obtain password entry and change privilages
359 if ((pas = getpwnam(user)) == 0) {
360 log("failed to get uid for %s", user);
363 setenv("USER", pas->pw_name, 1);
364 setenv("HOME", pas->pw_dir, 1);
365 setenv("SHELL", "/bin/sh", 1);
368 * Change running state to the user in question
371 if (initgroups(user, pas->pw_gid) < 0) {
372 log("initgroups failed: %s %m", user);
375 if (setregid(pas->pw_gid, pas->pw_gid) < 0) {
376 log("setregid failed: %s %d", user, pas->pw_gid);
379 if (setreuid(pas->pw_uid, pas->pw_uid) < 0) {
380 log("setreuid failed: %s %d", user, pas->pw_uid);
384 if (chdir(pas->pw_dir) < 0) {
385 if (chdir(TMPDIR) < 0) {
386 log("chdir failed: %s %s", user, pas->pw_dir);
387 log("chdir failed: %s " TMPDIR, user);