atrun.c revision 7777
129088Smarkm/* 229088Smarkm * atrun.c - run jobs queued by at; run with root privileges. 329088Smarkm * Copyright (C) 1993, 1994 Thomas Koenig 429088Smarkm * 529088Smarkm * Redistribution and use in source and binary forms, with or without 629088Smarkm * modification, are permitted provided that the following conditions 729088Smarkm * are met: 829088Smarkm * 1. Redistributions of source code must retain the above copyright 929088Smarkm * notice, this list of conditions and the following disclaimer. 1029088Smarkm * 2. The name of the author(s) may not be used to endorse or promote 1129088Smarkm * products derived from this software without specific prior written 1229088Smarkm * permission. 1329088Smarkm * 1429088Smarkm * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 1529088Smarkm * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 1629088Smarkm * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 1729088Smarkm * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 1829088Smarkm * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 1929088Smarkm * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 2029088Smarkm * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 2129088Smarkm * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 2229088Smarkm * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 2329088Smarkm * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2429088Smarkm */ 2529088Smarkm 2629088Smarkm/* System Headers */ 2729088Smarkm 2829088Smarkm#include <sys/fcntl.h> 2929088Smarkm#include <sys/types.h> 3029088Smarkm#include <sys/stat.h> 3129088Smarkm#include <sys/wait.h> 3229088Smarkm#include <ctype.h> 3329088Smarkm#include <dirent.h> 34114630Sobrien#include <errno.h> 3529088Smarkm#include <pwd.h> 3629181Smarkm#include <grp.h> 3731622Scharnier#include <signal.h> 38114630Sobrien#include <stddef.h> 39114630Sobrien#include <stdio.h> 40114630Sobrien#include <stdlib.h> 4129088Smarkm#include <string.h> 4279981Sru#include <time.h> 4329088Smarkm#include <unistd.h> 4487139Smarkm#include <syslog.h> 4529088Smarkm#ifndef __FreeBSD__ 4629088Smarkm#include <getopt.h> 4787139Smarkm#else 4829181Smarkm#include <paths.h> 4929181Smarkm#endif 5029088Smarkm 5129088Smarkm/* Local headers */ 5229088Smarkm 5329088Smarkm#ifndef __FreeBSD__ 5429088Smarkm#include "gloadavg.h" 5529088Smarkm#endif 5629088Smarkm#define MAIN 5729088Smarkm#include "privs.h" 5829088Smarkm 5929088Smarkm/* Macros */ 6029088Smarkm 6129088Smarkm#ifndef ATJOB_DIR 6229088Smarkm#define ATJOB_DIR "/usr/spool/atjobs/" 6329088Smarkm#endif 6429088Smarkm 6529088Smarkm#ifndef ATSPOOL_DIR 6629088Smarkm#define ATSPOOL_DIR "/usr/spool/atspool/" 6729088Smarkm#endif 6829088Smarkm 6929088Smarkm#ifndef LOADAVG_MX 7029088Smarkm#define LOADAVG_MX 1.5 7129088Smarkm#endif 7229088Smarkm 7329088Smarkm/* File scope variables */ 7429088Smarkm 7529088Smarkmstatic char *namep; 7629088Smarkmstatic char rcsid[] = "$Id: atrun.c,v 1.2 1995/04/12 02:52:15 ache Exp $"; 7729088Smarkmstatic debug = 0; 7829088Smarkm 7929088Smarkm/* Local functions */ 8029088Smarkmstatic void 8129088Smarkmperr(const char *a) 8229088Smarkm{ 8329088Smarkm if (debug) 8429088Smarkm { 8529088Smarkm perror(a); 8629088Smarkm } 8729088Smarkm else 8829088Smarkm syslog(LOG_ERR, "%s: %m", a); 8929088Smarkm 9029088Smarkm exit(EXIT_FAILURE); 9129088Smarkm} 9287139Smarkm 9387139Smarkmstatic int 9487139Smarkmwrite_string(int fd, const char* a) 9587139Smarkm{ 9629088Smarkm return write(fd, a, strlen(a)); 9787139Smarkm} 9829088Smarkm 9929088Smarkm#ifdef DEBUG_FORK 10029088Smarkmstatic pid_t 10129088Smarkmmyfork() 10229088Smarkm{ 10329088Smarkm pid_t res; 10429088Smarkm res = fork(); 10529088Smarkm if (res == 0) 10629088Smarkm kill(getpid(),SIGSTOP); 10729088Smarkm return res; 10829088Smarkm} 10929088Smarkm 11029088Smarkm#define fork myfork 11129088Smarkm#endif 11229088Smarkm 11329088Smarkmstatic void 11429088Smarkmrun_file(const char *filename, uid_t uid, gid_t gid) 11529088Smarkm{ 116103956Smarkm/* Run a file by by spawning off a process which redirects I/O, 11729088Smarkm * spawns a subshell, then waits for it to complete and sends 11829088Smarkm * mail to the user. 11929088Smarkm */ 12029088Smarkm pid_t pid; 12129088Smarkm int fd_out, fd_in; 12229088Smarkm int queue; 12329088Smarkm char mailbuf[9]; 12429088Smarkm char *mailname = NULL; 12529088Smarkm FILE *stream; 12629088Smarkm int send_mail = 0; 12729088Smarkm struct stat buf; 12829088Smarkm off_t size; 12929088Smarkm struct passwd *pentry; 13029088Smarkm int fflags; 13129088Smarkm 13229088Smarkm 13329088Smarkm PRIV_START 13429088Smarkm 13529088Smarkm if (chmod(filename, S_IRUSR) != 0) 13629088Smarkm { 13729088Smarkm perr("Cannot change file permissions"); 13829088Smarkm } 13929088Smarkm 14029088Smarkm PRIV_END 14129088Smarkm 14229088Smarkm pid = fork(); 14329088Smarkm if (pid == -1) 14429088Smarkm perr("Cannot fork"); 14529088Smarkm 14629088Smarkm else if (pid > 0) 14729088Smarkm return; 14829088Smarkm 14929088Smarkm /* Let's see who we mail to. Hopefully, we can read it from 15029088Smarkm * the command file; if not, send it to the owner, or, failing that, 15129088Smarkm * to root. 15229088Smarkm */ 15329088Smarkm 15429088Smarkm pentry = getpwuid(uid); 15529088Smarkm if (pentry == NULL) 15629088Smarkm { 15729088Smarkm syslog(LOG_ERR,"Userid %lu not found - aborting job %s", 15829088Smarkm (unsigned long) uid, filename); 15929088Smarkm exit(EXIT_FAILURE); 16029088Smarkm } 16129088Smarkm PRIV_START 16229088Smarkm 16329088Smarkm stream=fopen(filename, "r"); 16429088Smarkm 16529088Smarkm PRIV_END 16629088Smarkm 16729088Smarkm if (stream == NULL) 16829088Smarkm perr("Cannot open input file"); 16929088Smarkm 17029088Smarkm if ((fd_in = dup(fileno(stream))) <0) 17129088Smarkm perr("Error duplicating input file descriptor"); 17229088Smarkm 17329088Smarkm if ((fflags = fcntl(fd_in, F_GETFD)) <0) 17429088Smarkm perr("Error in fcntl"); 17529088Smarkm 17629088Smarkm fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC); 17729088Smarkm 17829088Smarkm if (fscanf(stream, "#! /bin/sh\n# mail %8s %d", mailbuf, &send_mail) == 2) 17929088Smarkm { 18029088Smarkm mailname = mailbuf; 18129088Smarkm pentry = getpwnam(mailname); 18229088Smarkm if (pentry == NULL || pentry->pw_uid != uid) { 18329088Smarkm syslog(LOG_ERR,"Userid %lu mismatch name %s - aborting job %s", 18429088Smarkm (unsigned long) uid, mailname, filename); 18529088Smarkm exit(EXIT_FAILURE); 18629088Smarkm } 18729088Smarkm } 18829088Smarkm else 18929088Smarkm { 19029088Smarkm mailname = pentry->pw_name; 19129088Smarkm } 19229088Smarkm fclose(stream); 19329088Smarkm if (chdir(ATSPOOL_DIR) < 0) 19429088Smarkm perr("Cannot chdir to " ATSPOOL_DIR); 19529088Smarkm 19629088Smarkm /* Create a file to hold the output of the job we are about to run. 19729088Smarkm * Write the mail header. 19829088Smarkm */ 19929088Smarkm if((fd_out=open(filename, 20029088Smarkm O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0) 20129088Smarkm perr("Cannot create output file"); 20229088Smarkm 20329088Smarkm write_string(fd_out, "Subject: Output from your job "); 20429088Smarkm write_string(fd_out, filename); 20529088Smarkm write_string(fd_out, "\n\n"); 20629088Smarkm fstat(fd_out, &buf); 20729088Smarkm size = buf.st_size; 20879981Sru 20929088Smarkm close(STDIN_FILENO); 21029088Smarkm close(STDOUT_FILENO); 21129088Smarkm close(STDERR_FILENO); 21229088Smarkm 21329088Smarkm pid = fork(); 21429088Smarkm if (pid < 0) 21529088Smarkm perr("Error in fork"); 21629088Smarkm 21729088Smarkm else if (pid == 0) 21829088Smarkm { 21929088Smarkm char *nul = NULL; 22029088Smarkm char **nenvp = &nul; 22129088Smarkm 22229088Smarkm /* Set up things for the child; we want standard input from the input file, 22329088Smarkm * and standard output and error sent to our output file. 22429088Smarkm */ 22529088Smarkm 22629088Smarkm if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0) 22729088Smarkm perr("Error in lseek"); 22829088Smarkm 22929088Smarkm if (dup(fd_in) != STDIN_FILENO) 23029088Smarkm perr("Error in I/O redirection"); 23129088Smarkm 23229088Smarkm if (dup(fd_out) != STDOUT_FILENO) 23329088Smarkm perr("Error in I/O redirection"); 23429088Smarkm 23529088Smarkm if (dup(fd_out) != STDERR_FILENO) 23629088Smarkm perr("Error in I/O redirection"); 23729088Smarkm 23829088Smarkm close(fd_in); 23929088Smarkm close(fd_out); 24029088Smarkm if (chdir(ATJOB_DIR) < 0) 24129088Smarkm perr("Cannot chdir to " ATJOB_DIR); 24229088Smarkm 24329088Smarkm queue = *filename; 24429088Smarkm 24529088Smarkm PRIV_START 24629088Smarkm 24729088Smarkm nice(tolower(queue) - 'a'); 24829088Smarkm 24929088Smarkm chdir(pentry->pw_dir); 25029088Smarkm 25129088Smarkm if (initgroups(pentry->pw_name,pentry->pw_gid)) 25229088Smarkm perr("Cannot delete saved userids"); 25329088Smarkm 25429088Smarkm if (setgid(gid) < 0) 25529088Smarkm perr("Cannot change group"); 25629088Smarkm 25729088Smarkm if (setuid(uid) < 0) 25829088Smarkm perr("Cannot set user id"); 25929088Smarkm 26029088Smarkm if(execle("/bin/sh","sh",(char *) NULL, nenvp) != 0) 26129088Smarkm perr("Exec failed"); 26229088Smarkm 26329088Smarkm PRIV_END 26429088Smarkm } 26529088Smarkm /* We're the parent. Let's wait. 26629088Smarkm */ 26729088Smarkm close(fd_in); 26829088Smarkm close(fd_out); 26929088Smarkm waitpid(pid, (int *) NULL, 0); 27029088Smarkm 27129088Smarkm /* Send mail. Unlink the output file first, so it is deleted after 27229088Smarkm * the run. 27329088Smarkm */ 27429088Smarkm stat(filename, &buf); 27529088Smarkm if (open(filename, O_RDONLY) != STDIN_FILENO) 27629088Smarkm perr("Open of jobfile failed"); 27729088Smarkm 27829088Smarkm unlink(filename); 27929088Smarkm if ((buf.st_size != size) || send_mail) 28029088Smarkm { 28129088Smarkm#ifdef __FreeBSD__ 28229088Smarkm execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service", 28329088Smarkm "-odi", "-oem", 28429088Smarkm mailname, (char *) NULL); 28529088Smarkm#else 28629088Smarkm execl(MAIL_CMD, MAIL_CMD, mailname, (char *) NULL); 28729088Smarkm#endif 28829088Smarkm perr("Exec failed"); 28929088Smarkm } 29029088Smarkm exit(EXIT_SUCCESS); 29129088Smarkm} 29229088Smarkm 29329088Smarkm/* Global functions */ 29429088Smarkm 29529088Smarkmint 29629088Smarkmmain(int argc, char *argv[]) 29729088Smarkm{ 29829088Smarkm/* Browse through ATJOB_DIR, checking all the jobfiles wether they should 29929088Smarkm * be executed and or deleted. The queue is coded into the first byte of 30029088Smarkm * the job filename, the date (in minutes since Eon) as a hex number in the 30129088Smarkm * following eight bytes, followed by a dot and a serial number. A file 30229088Smarkm * which has not been executed yet is denoted by its execute - bit set. 30329088Smarkm * For those files which are to be executed, run_file() is called, which forks 30429088Smarkm * off a child which takes care of I/O redirection, forks off another child 30529088Smarkm * for execution and yet another one, optionally, for sending mail. 30629088Smarkm * Files which already have run are removed during the next invocation. 30729088Smarkm */ 30829088Smarkm DIR *spool; 30929088Smarkm struct dirent *dirent; 31029088Smarkm struct stat buf; 31129088Smarkm unsigned long ctm; 31229088Smarkm char queue; 31329088Smarkm time_t now, run_time; 31429088Smarkm char batch_name[] = "Z2345678901234"; 31529088Smarkm uid_t batch_uid; 31629088Smarkm gid_t batch_gid; 31729088Smarkm int c; 31829088Smarkm int run_batch; 31929088Smarkm double load_avg = LOADAVG_MX; 32029088Smarkm 32129088Smarkm/* We don't need root privileges all the time; running under uid and gid daemon 32229088Smarkm * is fine. 32329088Smarkm */ 32429088Smarkm 32529088Smarkm RELINQUISH_PRIVS_ROOT(DAEMON_UID, DAEMON_GID) 32629088Smarkm 32729088Smarkm openlog("atrun", LOG_PID, LOG_CRON); 32829088Smarkm 32929088Smarkm opterr = 0; 33029088Smarkm errno = 0; 33129088Smarkm while((c=getopt(argc, argv, "dl:"))!= EOF) 33229088Smarkm { 33329088Smarkm switch (c) 33429088Smarkm { 33529088Smarkm case 'l': 33629088Smarkm if (sscanf(optarg, "%lf", &load_avg) != 1) 33729088Smarkm perr("garbled option -l"); 33829088Smarkm if (load_avg <= 0.) 33929088Smarkm load_avg = LOADAVG_MX; 34029088Smarkm break; 34129088Smarkm 34229088Smarkm case 'd': 34329088Smarkm debug ++; 34429088Smarkm break; 34529088Smarkm 34629088Smarkm case '?': 34729088Smarkm perr("unknown option"); 34829088Smarkm break; 34929088Smarkm 35029088Smarkm default: 35129088Smarkm perr("idiotic option - aborted"); 35229088Smarkm break; 35329088Smarkm } 35429088Smarkm } 35529088Smarkm 35629088Smarkm namep = argv[0]; 35729088Smarkm if (chdir(ATJOB_DIR) != 0) 35829088Smarkm perr("Cannot change to " ATJOB_DIR); 35929088Smarkm 36029088Smarkm /* Main loop. Open spool directory for reading and look over all the 36129088Smarkm * files in there. If the filename indicates that the job should be run 36229088Smarkm * and the x bit is set, fork off a child which sets its user and group 36329088Smarkm * id to that of the files and exec a /bin/sh which executes the shell 36431622Scharnier * script. Unlink older files if they should no longer be run. For 36529088Smarkm * deletion, their r bit has to be turned on. 36629088Smarkm * 36729088Smarkm * Also, pick the oldest batch job to run, at most one per invocation of 36829088Smarkm * atrun. 36929088Smarkm */ 37029088Smarkm if ((spool = opendir(".")) == NULL) 37129088Smarkm perr("Cannot read " ATJOB_DIR); 37229088Smarkm 37329088Smarkm now = time(NULL); 37429088Smarkm run_batch = 0; 37529088Smarkm batch_uid = (uid_t) -1; 37629088Smarkm batch_gid = (gid_t) -1; 37729088Smarkm 37829088Smarkm while ((dirent = readdir(spool)) != NULL) 37929088Smarkm { 38029088Smarkm if (stat(dirent->d_name,&buf) != 0) 38129088Smarkm perr("Cannot stat in " ATJOB_DIR); 38229088Smarkm 38329088Smarkm /* We don't want directories 38429088Smarkm */ 38529088Smarkm if (!S_ISREG(buf.st_mode)) 38629088Smarkm continue; 38729088Smarkm 38829088Smarkm if (sscanf(dirent->d_name,"%c%8lx",&queue,&ctm) != 2) 38929088Smarkm continue; 39029088Smarkm 39129088Smarkm run_time = (time_t) ctm*60; 39229088Smarkm 39329088Smarkm if ((S_IXUSR & buf.st_mode) && (run_time <=now)) 39429088Smarkm { 39529088Smarkm if (isupper(queue) && (strcmp(batch_name,dirent->d_name) > 0)) 39629088Smarkm { 39729088Smarkm run_batch = 1; 39829088Smarkm strncpy(batch_name, dirent->d_name, sizeof(batch_name)); 39929088Smarkm batch_uid = buf.st_uid; 40029088Smarkm batch_gid = buf.st_gid; 40129088Smarkm } 40229088Smarkm 40329088Smarkm /* The file is executable and old enough 40429088Smarkm */ 40529088Smarkm if (islower(queue)) 40629088Smarkm run_file(dirent->d_name, buf.st_uid, buf.st_gid); 40729088Smarkm } 40829088Smarkm /* Delete older files 40929088Smarkm */ 41029088Smarkm if ((run_time < now) && !(S_IXUSR & buf.st_mode) && (S_IRUSR & buf.st_mode)) 41129088Smarkm unlink(dirent->d_name); 41229088Smarkm } 41329088Smarkm /* run the single batch file, if any 41429088Smarkm */ 41529088Smarkm#ifndef __FreeBSD__ 41629088Smarkm if (run_batch && (gloadavg() < load_avg)) { 41729088Smarkm#else 41829088Smarkm if (run_batch) { 41929088Smarkm double la; 42029088Smarkm 42129088Smarkm if (getloadavg(&la, 1) != 1) 42229088Smarkm perr("Error in getloadavg"); 42329088Smarkm if (la < load_avg) 42429088Smarkm#endif 42529088Smarkm run_file(batch_name, batch_uid, batch_gid); 42629088Smarkm } 42787139Smarkm 42887139Smarkm closelog(); 42929088Smarkm exit(EXIT_SUCCESS); 43029088Smarkm} 43129088Smarkm