static void parse_args(int argc, char *argv[]) { int argch; if (!(pw = getpwuid(getuid()))) { errx(ERROR_EXIT, "your UID isn't in the passwd file. bailingo out"); } if (strlen(pw->pw_name) >= sizeof User) { errx(ERROR_EXIT, "username too long"); } (void)strlcpy(User, pw->pw_name, sizeof(User)); (void)strlcpy(RealUser, User, sizeof(RealUser)); Filename[0] = '\0'; Option = opt_unknown; while (-1 != (argch = getopt(argc, argv, getoptargs))) { switch (argch) { #if DEBUGGING case 'x': if (!set_debug_flags(optarg)) usage("bad debug option"); break; #endif case 'u': if (MY_UID(pw) != ROOT_UID) { errx(ERROR_EXIT, "must be privileged to use -u"); } if (!(pw = getpwnam(optarg))) { errx(ERROR_EXIT, "user `%s' unknown", optarg); } if (strlen(optarg) >= sizeof User) usage("username too long"); (void) strlcpy(User, optarg, sizeof(User)); break; case 'l': if (Option != opt_unknown) usage("only one operation permitted"); Option = opt_list; break; case 'r': if (Option != opt_unknown) usage("only one operation permitted"); Option = opt_delete; break; case 'e': if (Option != opt_unknown) usage("only one operation permitted"); Option = opt_edit; break; default: usage("unrecognized option"); } } endpwent(); if (Option != opt_unknown) { if (argv[optind] != NULL) usage("no arguments permitted after this option"); } else { if (argv[optind] != NULL) { Option = opt_replace; if (strlen(argv[optind]) >= sizeof Filename) usage("filename too long"); (void)strlcpy(Filename, argv[optind], sizeof(Filename)); } else usage("file name must be specified for replace"); } if (Option == opt_replace) { /* we have to open the file here because we're going to * chdir(2) into /var/cron before we get around to * reading the file. */ if (!strcmp(Filename, "-")) NewCrontab = stdin; else { /* relinquish the setuid status of the binary during * the open, lest nonroot users read files they should * not be able to read. we can't use access() here * since there's a race condition. thanks go out to * Arnt Gulbrandsen <*****@*****.**> for spotting * the race. */ if (relinguish_priv() < OK) { err(ERROR_EXIT, "swapping uids"); } if (!(NewCrontab = fopen(Filename, "r"))) { err(ERROR_EXIT, "cannot open `%s'", Filename); } if (regain_priv() < OK) { err(ERROR_EXIT, "swapping uids back"); } } } Debug(DMISC, ("user=%s, file=%s, option=%s\n", User, Filename, Options[(int)Option])); }
static void edit_cmd(void) { char n[MAX_FNAME], q[MAX_TEMPSTR]; const char *editor; FILE *f; int ch, t, x; sig_t oint, oabrt, oquit, ohup; struct stat statbuf; struct utimbuf utimebuf; long mtimensec; WAIT_T waiter; PID_T pid, xpid; log_it(RealUser, Pid, "BEGIN EDIT", User); if (!glue_strings(n, sizeof n, SPOOL_DIR, User, '/')) { errx(ERROR_EXIT, "path too long"); } if (!(f = fopen(n, "r"))) { if (errno != ENOENT) { err(ERROR_EXIT, "cannot open `%s'", n); } warnx("no crontab for `%s' - using an empty one", User); if (!(f = fopen(_PATH_DEVNULL, "r"))) { err(ERROR_EXIT, "cannot open `%s'", _PATH_DEVNULL); } } if (fstat(fileno(f), &statbuf) < 0) { warn("cannot stat crontab file"); goto fatal; } utimebuf.actime = statbuf.st_atime; utimebuf.modtime = statbuf.st_mtime; mtimensec = statbuf.st_mtimensec; /* Turn off signals. */ ohup = signal(SIGHUP, SIG_IGN); oint = signal(SIGINT, SIG_IGN); oquit = signal(SIGQUIT, SIG_IGN); oabrt = signal(SIGABRT, SIG_IGN); if (!glue_strings(Filename, sizeof Filename, _PATH_TMP, "crontab.XXXXXXXXXX", '/')) { warnx("path too long"); goto fatal; } if (-1 == (t = mkstemp(Filename))) { warn("cannot create `%s'", Filename); goto fatal; } #ifdef HAS_FCHOWN x = fchown(t, MY_UID(pw), MY_GID(pw)); #else x = chown(Filename, MY_UID(pw), MY_GID(pw)); #endif if (x < 0) { warn("cannot chown `%s'", Filename); goto fatal; } if (!(NewCrontab = fdopen(t, "r+"))) { warn("cannot open fd"); goto fatal; } Set_LineNum(1); skip_header(&ch, f); /* copy the rest of the crontab (if any) to the temp file. */ for (; EOF != ch; ch = get_char(f)) (void)putc(ch, NewCrontab); (void)fclose(f); if (fflush(NewCrontab) < OK) { err(ERROR_EXIT, "cannot flush output for `%s'", Filename); } (void)utime(Filename, &utimebuf); again: rewind(NewCrontab); if (ferror(NewCrontab)) { warn("error while writing new crontab to `%s'", Filename); fatal: (void)unlink(Filename); exit(ERROR_EXIT); } if (((editor = getenv("VISUAL")) == NULL || *editor == '\0') && ((editor = getenv("EDITOR")) == NULL || *editor == '\0')) { editor = EDITOR; } /* we still have the file open. editors will generally rewrite the * original file rather than renaming/unlinking it and starting a * new one; even backup files are supposed to be made by copying * rather than by renaming. if some editor does not support this, * then don't use it. the security problems are more severe if we * close and reopen the file around the edit. */ switch (pid = fork()) { case -1: warn("cannot fork"); goto fatal; case 0: /* child */ if (setgid(MY_GID(pw)) < 0) { err(ERROR_EXIT, "cannot setgid(getgid())"); } if (setuid(MY_UID(pw)) < 0) { err(ERROR_EXIT, "cannot setuid(getuid())"); } if (chdir(_PATH_TMP) < 0) { err(ERROR_EXIT, "cannot chdir to `%s'", _PATH_TMP); } if (!glue_strings(q, sizeof q, editor, Filename, ' ')) { errx(ERROR_EXIT, "editor command line too long"); } (void)execlp(_PATH_BSHELL, _PATH_BSHELL, "-c", q, (char *)0); err(ERROR_EXIT, "cannot start `%s'", editor); /*NOTREACHED*/ default: /* parent */ break; } /* parent */ for (;;) { xpid = waitpid(pid, &waiter, WUNTRACED); if (xpid == -1) { if (errno != EINTR) warn("waitpid() failed waiting for PID %ld " "from `%s'", (long)pid, editor); } else if (xpid != pid) { warnx("wrong PID (%ld != %ld) from `%s'", (long)xpid, (long)pid, editor); goto fatal; } else if (WIFSTOPPED(waiter)) { (void)kill(getpid(), WSTOPSIG(waiter)); } else if (WIFEXITED(waiter) && WEXITSTATUS(waiter)) { warnx("`%s' exited with status %d\n", editor, WEXITSTATUS(waiter)); goto fatal; } else if (WIFSIGNALED(waiter)) { warnx("`%s' killed; signal %d (%score dumped)", editor, WTERMSIG(waiter), WCOREDUMP(waiter) ? "" : "no "); goto fatal; } else break; } (void)signal(SIGHUP, ohup); (void)signal(SIGINT, oint); (void)signal(SIGQUIT, oquit); (void)signal(SIGABRT, oabrt); if (fstat(t, &statbuf) < 0) { warn("cannot stat `%s'", Filename); goto fatal; } if (utimebuf.modtime == statbuf.st_mtime && mtimensec == statbuf.st_mtimensec) { warnx("no changes made to crontab"); goto remove; } warnx("installing new crontab"); switch (replace_cmd()) { case 0: break; case -1: for (;;) { (void)fpurge(stdin); (void)printf("Do you want to retry the same edit? "); (void)fflush(stdout); q[0] = '\0'; (void) fgets(q, (int)sizeof(q), stdin); switch (q[0]) { case 'y': case 'Y': goto again; case 'n': case 'N': goto abandon; default: (void)printf("Enter Y or N\n"); } } /*NOTREACHED*/ case -2: abandon: warnx("edits left in `%s'", Filename); goto done; default: warnx("panic: bad switch() in replace_cmd()"); goto fatal; } remove: (void)unlink(Filename); done: log_it(RealUser, Pid, "END EDIT", User); }
int main(int argc,char *argv[]) { int ret; int orig_argc = argc; char **orig_argv = argv; signal(SIGUSR1, sigusr1_handler); signal(SIGUSR2, sigusr2_handler); signal(SIGCHLD, sigchld_handler); #ifdef MAINTAINER_MODE signal(SIGSEGV, rsync_panic_handler); signal(SIGFPE, rsync_panic_handler); signal(SIGABRT, rsync_panic_handler); signal(SIGBUS, rsync_panic_handler); #endif /* def MAINTAINER_MODE */ starttime = time(NULL); am_root = (MY_UID() == 0); memset(&stats, 0, sizeof(stats)); if (argc < 2) { usage(FERROR); exit_cleanup(RERR_SYNTAX); } /* we set a 0 umask so that correct file permissions can be * carried across */ orig_umask = (int)umask(0); if (!parse_arguments(&argc, (const char ***) &argv, 1)) { /* FIXME: We ought to call the same error-handling * code here, rather than relying on getopt. */ option_error(); exit_cleanup(RERR_SYNTAX); } signal(SIGINT,SIGNAL_CAST sig_int); signal(SIGHUP,SIGNAL_CAST sig_int); signal(SIGTERM,SIGNAL_CAST sig_int); /* Ignore SIGPIPE; we consistently check error codes and will * see the EPIPE. */ signal(SIGPIPE, SIG_IGN); #if defined CONFIG_LOCALE && defined HAVE_SETLOCALE setlocale(LC_CTYPE, ""); #endif /* Initialize push_dir here because on some old systems getcwd * (implemented by forking "pwd" and reading its output) doesn't * work when there are other child processes. Also, on all systems * that implement getcwd that way "pwd" can't be found after chroot. */ push_dir(NULL); init_flist(); if ((write_batch || read_batch) && !am_server) { if (write_batch) write_batch_shell_file(orig_argc, orig_argv, argc); if (read_batch && strcmp(batch_name, "-") == 0) batch_fd = STDIN_FILENO; else { batch_fd = do_open(batch_name, write_batch ? O_WRONLY | O_CREAT | O_TRUNC : O_RDONLY, S_IRUSR | S_IWUSR); } if (batch_fd < 0) { rsyserr(FERROR, errno, "Batch file %s open error", full_fname(batch_name)); exit_cleanup(RERR_FILEIO); } if (read_batch) read_stream_flags(batch_fd); } if (write_batch < 0) dry_run = 1; if (am_daemon && !am_server) return daemon_main(); if (argc < 1) { usage(FERROR); exit_cleanup(RERR_SYNTAX); } if (am_server) { set_nonblocking(STDIN_FILENO); set_nonblocking(STDOUT_FILENO); if (am_daemon) return start_daemon(STDIN_FILENO, STDOUT_FILENO); start_server(STDIN_FILENO, STDOUT_FILENO, argc, argv); } ret = start_client(argc, argv); if (ret == -1) exit_cleanup(RERR_STARTCLIENT); else exit_cleanup(ret); return ret; }