summaryrefslogtreecommitdiff
path: root/libexec/atrun/atrun.c
diff options
context:
space:
mode:
authorTodd C. Miller <millert@cvs.openbsd.org>1997-03-01 23:40:13 +0000
committerTodd C. Miller <millert@cvs.openbsd.org>1997-03-01 23:40:13 +0000
commitcebd26ca95c8ec2ad44a2d722c369feeb72a4b5d (patch)
tree20ada13dff8a81a3a4e3c603dd290ce39f46bf3d /libexec/atrun/atrun.c
parentf7c17ce3922e56d34d748f6e157ed1c9a91c58d1 (diff)
Merge in changes from at 2.9
Diffstat (limited to 'libexec/atrun/atrun.c')
-rw-r--r--libexec/atrun/atrun.c334
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);
}