diff options
author | Todd C. Miller <millert@cvs.openbsd.org> | 1997-03-01 23:40:13 +0000 |
---|---|---|
committer | Todd C. Miller <millert@cvs.openbsd.org> | 1997-03-01 23:40:13 +0000 |
commit | cebd26ca95c8ec2ad44a2d722c369feeb72a4b5d (patch) | |
tree | 20ada13dff8a81a3a4e3c603dd290ce39f46bf3d /libexec/atrun/atrun.c | |
parent | f7c17ce3922e56d34d748f6e157ed1c9a91c58d1 (diff) |
Merge in changes from at 2.9
Diffstat (limited to 'libexec/atrun/atrun.c')
-rw-r--r-- | libexec/atrun/atrun.c | 334 |
1 files changed, 247 insertions, 87 deletions
diff --git a/libexec/atrun/atrun.c b/libexec/atrun/atrun.c index 7d1b499067b..0d2e26647ba 100644 --- a/libexec/atrun/atrun.c +++ b/libexec/atrun/atrun.c @@ -1,7 +1,8 @@ +/* $OpenBSD: atrun.c,v 1.3 1997/03/01 23:39:43 millert Exp $ */ + /* - * atrun.c - run jobs queued by at; run with root privileges. - * Copyright (c) 1993 by Thomas Koenig - * All rights reserved. + * atrun.c - run jobs queued by at; run with root privileges. + * Copyright (C) 1993, 1994 Thomas Koenig * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -15,7 +16,7 @@ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY @@ -30,9 +31,11 @@ #include <sys/types.h> #include <sys/stat.h> #include <sys/wait.h> +#include <ctype.h> #include <dirent.h> #include <errno.h> #include <pwd.h> +#include <grp.h> #include <signal.h> #include <stddef.h> #include <stdio.h> @@ -54,14 +57,33 @@ /* File scope variables */ static char *namep; -static char rcsid[] = "$Id: atrun.c,v 1.2 1996/12/22 03:41:10 tholo Exp $"; +static char rcsid[] = "$OpenBSD: atrun.c,v 1.3 1997/03/01 23:39:43 millert Exp $"; +static int debug = 0; /* Local functions */ + static void perr(a) const char *a; { - syslog(LOG_ERR, "%s: %m", a); + if (debug) + perror(a); + else + syslog(LOG_ERR, "%s: %m", a); + + exit(EXIT_FAILURE); +} + +static void +perr2(a, b) + char *a, *b; +{ + if (debug) { + (void)fputs(a, stderr); + perror(b); + } else + syslog(LOG_ERR, "%s%s: %m", a, b); + exit(EXIT_FAILURE); } @@ -70,13 +92,14 @@ write_string(fd, a) int fd; const char *a; { - return write(fd, a, strlen(a)); + return(write(fd, a, strlen(a))); } static void -run_file(filename, uid) +run_file(filename, uid, gid) const char *filename; uid_t uid; + gid_t gid; { /* * Run a file by by spawning off a process which redirects I/O, @@ -90,15 +113,24 @@ run_file(filename, uid) char *mailname = NULL; FILE *stream; int send_mail = 0; - struct stat buf; + struct stat buf, lbuf; off_t size; struct passwd *pentry; int fflags; + uid_t nuid; + gid_t ngid; + + PRIV_START + + if (chmod(filename, S_IRUSR) != 0) + perr("Cannot change file permissions"); + + PRIV_END pid = fork(); if (pid == -1) perr("Cannot fork"); - else if (pid > 0) + else if (pid != 0) return; /* @@ -107,15 +139,23 @@ run_file(filename, uid) * root. */ + pentry = getpwuid(uid); + if (pentry == NULL) { + syslog(LOG_ERR,"Userid %u not found - aborting job %s", + uid, filename); + exit(EXIT_FAILURE); + } PRIV_START - stream = fopen(filename, "r"); + stream = fopen(filename, "r"); PRIV_END - pentry = getpwuid(uid); - if (pentry == NULL) - perr("UID not in password file!"); + if (pentry->pw_expire && time(NULL) >= pentry->pw_expire) { + syslog(LOG_ERR, "Userid %u has expired - aborting job %s", + uid, filename); + exit(EXIT_FAILURE); + } if (stream == NULL) perr("Cannot open input file"); @@ -123,37 +163,86 @@ run_file(filename, uid) if ((fd_in = dup(fileno(stream))) < 0) perr("Error duplicating input file descriptor"); + if (fstat(fd_in, &buf) == -1) + perr("Error in fstat of input file descriptor"); + + PRIV_START + + if (lstat(filename, &lbuf) == -1) + perr("Error in lstat of input file"); + + PRIV_END + + if (S_ISLNK(lbuf.st_mode)) { + syslog(LOG_ERR, "Symbolic link encountered in job %s - aborting", + filename); + exit(EXIT_FAILURE); + } + if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) || + (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) || + (lbuf.st_size!=buf.st_size)) { + syslog(LOG_ERR, "Somebody changed files from under us for job %s - aborting", filename); + exit(EXIT_FAILURE); + } + if (buf.st_nlink > 1) { + syslog(LOG_ERR, "Somebody is trying to run a linked script for job %s", + filename); + exit(EXIT_FAILURE); + } if ((fflags = fcntl(fd_in, F_GETFD)) < 0) perr("Error in fcntl"); - fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC); + (void)fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC); - if (fscanf(stream, "#! /bin/sh\n# mail %8s %d", mailbuf, &send_mail) == 2) { - mailname = mailbuf; - } else { - mailname = pentry->pw_name; + if (fscanf(stream, "#!/bin/sh\n# atrun uid=%u gid=%u\n# mail %8s %d", + &nuid, &ngid, mailbuf, &send_mail) != 4) { + syslog(LOG_ERR, "File %s is in wrong format - aborting", + filename); + exit(EXIT_FAILURE); } - fclose(stream); + if (mailbuf[0] == '-') { + syslog(LOG_ERR, "illegal mail name %s in %s", mailbuf, filename); + exit(EXIT_FAILURE); + } + mailname = mailbuf; + if (nuid != uid) { + syslog(LOG_ERR, "Job %s - userid %u does not match file uid %u", + filename, nuid, uid); + exit(EXIT_FAILURE); + } + if (ngid != gid) { + syslog(LOG_ERR, "Job %s - groupid %u does not match file gid %u", + filename, ngid, gid); + exit(EXIT_FAILURE); + } + (void)fclose(stream); + + PRIV_START + if (chdir(_PATH_ATSPOOL) < 0) - perr("Cannot chdir to " _PATH_ATSPOOL); + perr2("Cannot chdir to ", _PATH_ATSPOOL); /* * Create a file to hold the output of the job we are about to * run. Write the mail header. */ + if ((fd_out = open(filename, O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0) perr("Cannot create output file"); + PRIV_END + write_string(fd_out, "Subject: Output from your job "); write_string(fd_out, filename); write_string(fd_out, "\n\n"); - fstat(fd_out, &buf); + if (fstat(fd_out, &buf) == -1) + perr("Error in fstat of output file descriptor"); size = buf.st_size; - close(STDIN_FILENO); - close(STDOUT_FILENO); - close(STDERR_FILENO); + (void)close(STDIN_FILENO); + (void)close(STDOUT_FILENO); + (void)close(STDERR_FILENO); pid = fork(); if (pid < 0) @@ -167,7 +256,6 @@ run_file(filename, uid) * the input file, and standard output and error sent to * our output file. */ - if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0) perr("Error in lseek"); @@ -180,60 +268,79 @@ run_file(filename, uid) if (dup(fd_out) != STDERR_FILENO) perr("Error in I/O redirection"); - close(fd_in); - close(fd_out); + (void)close(fd_in); + (void)close(fd_out); + + PRIV_START + if (chdir(_PATH_ATJOBS) < 0) - perr("Cannot chdir to " _PATH_ATJOBS); + perr2("Cannot chdir to ", _PATH_ATJOBS); queue = *filename; - PRIV_START + if (queue > 'b') + nice(queue - 'b'); - if (queue > 'b') - nice(queue - 'b'); + if (chdir(pentry->pw_dir) < 0) + chdir("/"); if (initgroups(pentry->pw_name, pentry->pw_gid) < 0) perr("Cannot init group list"); - if (setegid(pentry->pw_gid) < 0) - perr("Cannot change primary group"); - if (setgid(pentry->pw_gid) < 0) + if (setegid(pentry->pw_gid) < 0 || setgid(pentry->pw_gid) < 0) perr("Cannot change primary group"); - if (seteuid(uid) < 0) - perr("Cannot set user id"); - if (setuid(uid) < 0) + if (seteuid(uid) < 0 || setuid(uid) < 0) perr("Cannot set user id"); - chdir("/"); - - if (execle("/bin/sh", "sh", (char *) NULL, nenvp) != 0) - perr("Exec failed"); + if (execle("/bin/sh", "sh", (char *)NULL, nenvp) != 0) + perr("Exec failed for /bin/sh"); PRIV_END } /* We're the parent. Let's wait. */ - close(fd_in); - close(fd_out); - waitpid(pid, (int *) NULL, 0); + (void)close(fd_in); + (void)close(fd_out); + waitpid(pid, (int *)NULL, 0); + + /* + * Send mail. Unlink the output file first, so it is deleted + * after the run. + */ + PRIV_START + + if (stat(filename, &buf) == -1) + perr("Error in stat of output file"); + if (open(filename, O_RDONLY) != STDIN_FILENO) + perr("Open of jobfile failed"); + + (void)unlink(filename); + + PRIV_END - stat(filename, &buf); if ((buf.st_size != size) || send_mail) { /* Fork off a child for sending mail */ - pid = fork(); - if (pid < 0) - perr("Fork failed"); - else if (pid == 0) { - if (open(filename, O_RDONLY) != STDIN_FILENO) - perr("Cannot reopen output file"); - - execl(_PATH_SENDMAIL, _PATH_SENDMAIL, mailname, - (char *) NULL); - perr("Exec failed"); - } - waitpid(pid, (int *) NULL, 0); + + PRIV_START + + if (chdir(pentry->pw_dir)) + chdir("/"); + + if (initgroups(pentry->pw_name, pentry->pw_gid)) + perr("Cannot init group list"); + + if (setegid(gid) < 0 || setgid(gid) < 0) + perr("Cannot change primary group"); + + if (seteuid(uid) < 0 || setuid(uid) < 0) + perr("Cannot set user id"); + + execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service", + "-odi", "-oem", mailname, (char *) NULL); + perr("Exec failed for mail command"); + + PRIV_END } - unlink(filename); exit(EXIT_SUCCESS); } @@ -260,21 +367,57 @@ main(argc, argv) DIR *spool; struct dirent *dirent; struct stat buf; - int older; unsigned long ctm; + int jobno; char queue; + time_t now, run_time; + char batch_name[] = "Z2345678901234"; + uid_t batch_uid; + gid_t batch_gid; + int c; + int run_batch; + double la, load_avg = ATRUN_MAXLOAD; /* * We don't need root privileges all the time; running under uid - * and gid daemon is fine. + * and gid nobody is fine except for priviledged operations. */ + RELINQUISH_PRIVS_ROOT(NOBODY_UID, NOBODY_GID) - RELINQUISH_PRIVS_ROOT(0) /* it's setuid root */ openlog("atrun", LOG_PID, LOG_CRON); + opterr = 0; + errno = 0; + while ((c = getopt(argc, argv, "dl:")) != -1) { + switch (c) { + case 'l': + if (sscanf(optarg, "%lf", &load_avg) != 1) + perr("garbled option -l"); + if (load_avg <= 0.) + load_avg = ATRUN_MAXLOAD; + break; + + case 'd': + debug++; + break; + + case '?': + perr("unknown option"); + break; + + default: + perr("idiotic option - aborted"); + break; + } + } + namep = argv[0]; + + PRIV_START + if (chdir(_PATH_ATJOBS) != 0) - perr("Cannot change to " _PATH_ATJOBS); + perr2("Cannot change to ", _PATH_ATJOBS); + /* * Main loop. Open spool directory for reading and look over all @@ -284,50 +427,67 @@ main(argc, argv) * which executes the shell script. Unlink older files if they * should no longer be run. For deletion, their r bit has to be * turned on. + * + * Also, pick the oldest batch job to run, at most one per + * invocation of atrun. */ if ((spool = opendir(".")) == NULL) - perr("Cannot read " _PATH_ATJOBS); + perr2("Cannot read ", _PATH_ATJOBS); + + PRIV_END + + now = time(NULL); + run_batch = 0; + batch_uid = (uid_t) -1; + batch_gid = (gid_t) -1; while ((dirent = readdir(spool)) != NULL) { - double la; + PRIV_START if (stat(dirent->d_name, &buf) != 0) - perr("Cannot stat in " _PATH_ATJOBS); + perr2("Cannot stat in ", _PATH_ATJOBS); + + PRIV_END /* We don't want directories */ if (!S_ISREG(buf.st_mode)) continue; - if (sscanf(dirent->d_name, "%c%8lx", &queue, &ctm) != 2) - continue; - - if ((queue == 'b') && ((getloadavg(&la, 1) != 1) || - (la > ATRUN_MAXLOAD))) + if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno, &ctm) != 3) continue; - older = (time_t) ctm *60 <= time(NULL); - - /* The file is executable and old enough */ - if (older && (S_IXUSR & buf.st_mode)) { - /* - * Now we know we want to run the file, we can turn - * off the execute bit - */ + run_time = (time_t) ctm * 60; + + if ((S_IXUSR & buf.st_mode) && (run_time <= now)) { + if (isupper(queue) && + (strcmp(batch_name, dirent->d_name) > 0)) { + run_batch = 1; + (void)strncpy(batch_name, dirent->d_name, + sizeof(batch_name)); + batch_uid = buf.st_uid; + batch_gid = buf.st_gid; + } + + /* The file is executable and old enough */ + if (islower(queue)) + run_file(dirent->d_name, buf.st_uid, buf.st_gid); + } + /* Delete older files */ + if ((run_time < now) && !(S_IXUSR & buf.st_mode) && + (S_IRUSR & buf.st_mode)) { PRIV_START - if (chmod(dirent->d_name, S_IRUSR) != 0) - perr("Cannot change file permissions"); + (void)unlink(dirent->d_name); PRIV_END - - run_file(dirent->d_name, buf.st_uid); } - /* Delete older files */ - if (older && !(S_IXUSR & buf.st_mode) && - (S_IRUSR & buf.st_mode)) - unlink(dirent->d_name); } + + /* Run the single batch file, if any */ + if (run_batch && ((getloadavg(&la, 1) == 1) && la < load_avg)) + run_file(batch_name, batch_uid, batch_gid); + closelog(); exit(EXIT_SUCCESS); } |