atrun.c revision 90148
110154Sache/* 27768Sache * atrun.c - run jobs queued by at; run with root privileges. 37768Sache * Copyright (C) 1993, 1994 Thomas Koenig 4939Snate * 5939Snate * Redistribution and use in source and binary forms, with or without 6939Snate * modification, are permitted provided that the following conditions 7939Snate * are met: 8939Snate * 1. Redistributions of source code must retain the above copyright 9939Snate * notice, this list of conditions and the following disclaimer. 10939Snate * 2. The name of the author(s) may not be used to endorse or promote 11939Snate * products derived from this software without specific prior written 12939Snate * permission. 13939Snate * 14939Snate * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR 15939Snate * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 16939Snate * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 1710154Sache * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT, 18939Snate * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 19939Snate * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 20939Snate * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 21939Snate * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT 22939Snate * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 23939Snate * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24939Snate */ 25939Snate 2631306Scharnier#ifndef lint 2731306Scharnierstatic const char rcsid[] = 2850476Speter "$FreeBSD: head/libexec/atrun/atrun.c 90148 2002-02-03 15:53:02Z imp $"; 2931306Scharnier#endif /* not lint */ 3031306Scharnier 31939Snate/* System Headers */ 32939Snate 33939Snate#include <sys/fcntl.h> 34939Snate#include <sys/types.h> 35939Snate#include <sys/stat.h> 36939Snate#include <sys/wait.h> 3724829Sdavidn#include <sys/param.h> 387768Sache#include <ctype.h> 39939Snate#include <dirent.h> 4031306Scharnier#include <err.h> 4131306Scharnier#include <grp.h> 42939Snate#include <pwd.h> 43939Snate#include <signal.h> 44939Snate#include <stddef.h> 45939Snate#include <stdio.h> 46939Snate#include <stdlib.h> 47939Snate#include <string.h> 4831306Scharnier#include <syslog.h> 49939Snate#include <time.h> 50939Snate#include <unistd.h> 5124829Sdavidn#include <utmp.h> 5210154Sache#ifdef __FreeBSD__ 5310154Sache#include <paths.h> 5410154Sache#else 557768Sache#include <getopt.h> 567768Sache#endif 57939Snate 5824829Sdavidn#if (MAXLOGNAME-1) > UT_NAMESIZE 5924829Sdavidn#define LOGNAMESIZE UT_NAMESIZE 6024829Sdavidn#else 6124829Sdavidn#define LOGNAMESIZE (MAXLOGNAME-1) 6224829Sdavidn#endif 6324829Sdavidn 64939Snate/* Local headers */ 65939Snate 667768Sache#include "gloadavg.h" 67939Snate#define MAIN 68939Snate#include "privs.h" 69939Snate 707768Sache/* Macros */ 717768Sache 727768Sache#ifndef ATJOB_DIR 737768Sache#define ATJOB_DIR "/usr/spool/atjobs/" 747768Sache#endif 757768Sache 767768Sache#ifndef ATSPOOL_DIR 777768Sache#define ATSPOOL_DIR "/usr/spool/atspool/" 787768Sache#endif 797768Sache 807768Sache#ifndef LOADAVG_MX 817768Sache#define LOADAVG_MX 1.5 827768Sache#endif 837768Sache 84939Snate/* File scope variables */ 85939Snate 867768Sachestatic debug = 0; 87939Snate 8810154Sachevoid perr(const char *a); 8990148Simpstatic void usage(void); 9010154Sache 91939Snate/* Local functions */ 92939Snatestatic int 937768Sachewrite_string(int fd, const char* a) 94939Snate{ 957768Sache return write(fd, a, strlen(a)); 96939Snate} 97939Snate 9810154Sache#undef DEBUG_FORK 997768Sache#ifdef DEBUG_FORK 1007768Sachestatic pid_t 10190148Simpmyfork(void) 1027768Sache{ 1037768Sache pid_t res; 1047768Sache res = fork(); 1057768Sache if (res == 0) 1067768Sache kill(getpid(),SIGSTOP); 1077768Sache return res; 1087768Sache} 1097768Sache 1107768Sache#define fork myfork 1117768Sache#endif 1127768Sache 113939Snatestatic void 1147768Sacherun_file(const char *filename, uid_t uid, gid_t gid) 115939Snate{ 11680201Skris/* Run a file by spawning off a process which redirects I/O, 1177768Sache * spawns a subshell, then waits for it to complete and sends 1187768Sache * mail to the user. 1197768Sache */ 1207768Sache pid_t pid; 1217768Sache int fd_out, fd_in; 1227768Sache int queue; 12324829Sdavidn char mailbuf[LOGNAMESIZE + 1], fmt[49]; 1247768Sache char *mailname = NULL; 1257768Sache FILE *stream; 1267768Sache int send_mail = 0; 12710154Sache struct stat buf, lbuf; 1287768Sache off_t size; 1297768Sache struct passwd *pentry; 1307768Sache int fflags; 13110154Sache long nuid; 13210154Sache long ngid; 133939Snate 134939Snate 1357768Sache PRIV_START 136939Snate 1377768Sache if (chmod(filename, S_IRUSR) != 0) 1387768Sache { 13931306Scharnier perr("cannot change file permissions"); 1407768Sache } 141939Snate 1427768Sache PRIV_END 143939Snate 1447768Sache pid = fork(); 1457768Sache if (pid == -1) 14631306Scharnier perr("cannot fork"); 14710154Sache 14810154Sache else if (pid != 0) 1497768Sache return; 150939Snate 1517768Sache /* Let's see who we mail to. Hopefully, we can read it from 1527768Sache * the command file; if not, send it to the owner, or, failing that, 1537768Sache * to root. 1547768Sache */ 155939Snate 1567768Sache pentry = getpwuid(uid); 1577768Sache if (pentry == NULL) 1587768Sache { 1597768Sache syslog(LOG_ERR,"Userid %lu not found - aborting job %s", 1607768Sache (unsigned long) uid, filename); 16110154Sache exit(EXIT_FAILURE); 1627768Sache } 1637768Sache PRIV_START 164939Snate 1657768Sache stream=fopen(filename, "r"); 166939Snate 1677768Sache PRIV_END 168939Snate 16910401Smpp#ifdef __FreeBSD__ 17010401Smpp if (pentry->pw_expire && time(NULL) >= pentry->pw_expire) 17110401Smpp { 17210401Smpp syslog(LOG_ERR, "Userid %lu is expired - aborting job %s", 17310401Smpp (unsigned long) uid, filename); 17410401Smpp exit(EXIT_FAILURE); 17510401Smpp } 17610401Smpp#endif 17710401Smpp 1787768Sache if (stream == NULL) 17931306Scharnier perr("cannot open input file"); 1807768Sache 1817768Sache if ((fd_in = dup(fileno(stream))) <0) 18231306Scharnier perr("error duplicating input file descriptor"); 1837768Sache 18410154Sache if (fstat(fd_in, &buf) == -1) 18531306Scharnier perr("error in fstat of input file descriptor"); 18610154Sache 18710154Sache if (lstat(filename, &lbuf) == -1) 18831306Scharnier perr("error in fstat of input file"); 18910154Sache 19010154Sache if (S_ISLNK(lbuf.st_mode)) { 19110154Sache syslog(LOG_ERR,"Symbolic link encountered in job %s - aborting", 19210154Sache filename); 19310154Sache exit(EXIT_FAILURE); 19410154Sache } 19510154Sache if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) || 19610154Sache (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) || 19710154Sache (lbuf.st_size!=buf.st_size)) { 19810154Sache syslog(LOG_ERR,"Somebody changed files from under us for job %s - " 19910154Sache "aborting",filename); 20010154Sache exit(EXIT_FAILURE); 20110154Sache } 20210154Sache if (buf.st_nlink > 1) { 20310154Sache syslog(LOG_ERR,"Someboy is trying to run a linked script for job %s", 20410154Sache filename); 20510154Sache exit(EXIT_FAILURE); 20610154Sache } 2077768Sache if ((fflags = fcntl(fd_in, F_GETFD)) <0) 20831306Scharnier perr("error in fcntl"); 2097768Sache 2107768Sache fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC); 2117768Sache 21269199Skris snprintf(fmt, sizeof(fmt), 21369199Skris "#!/bin/sh\n# atrun uid=%%ld gid=%%ld\n# mail %%%ds %%d", 21424829Sdavidn LOGNAMESIZE); 21524829Sdavidn if (fscanf(stream, fmt, &nuid, &ngid, mailbuf, &send_mail) != 4) { 21624829Sdavidn syslog(LOG_ERR,"File %s is in wrong format - aborting", filename); 21710154Sache exit(EXIT_FAILURE); 2187768Sache } 21910154Sache if (mailbuf[0] == '-') { 22010154Sache syslog(LOG_ERR,"illegal mail name %s in %s",mailbuf,filename); 22110154Sache exit(EXIT_FAILURE); 2227768Sache } 22310154Sache mailname = mailbuf; 22410154Sache if (nuid != uid) { 22538024Sbde syslog(LOG_ERR,"Job %s - userid %ld does not match file uid %lu", 22638024Sbde filename, nuid, (unsigned long)uid); 22710154Sache exit(EXIT_FAILURE); 22810154Sache } 22910154Sache if (ngid != gid) { 23038024Sbde syslog(LOG_ERR,"Job %s - groupid %ld does not match file gid %lu", 23138024Sbde filename, ngid, (unsigned long)gid); 23210154Sache exit(EXIT_FAILURE); 23310154Sache } 2347768Sache fclose(stream); 2357768Sache if (chdir(ATSPOOL_DIR) < 0) 23631306Scharnier perr("cannot chdir to " ATSPOOL_DIR); 23710154Sache 2387768Sache /* Create a file to hold the output of the job we are about to run. 2397768Sache * Write the mail header. 24010154Sache */ 2417768Sache if((fd_out=open(filename, 2427768Sache O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) < 0) 24331306Scharnier perr("cannot create output file"); 244939Snate 2457768Sache write_string(fd_out, "Subject: Output from your job "); 2467768Sache write_string(fd_out, filename); 2477768Sache write_string(fd_out, "\n\n"); 2487768Sache fstat(fd_out, &buf); 2497768Sache size = buf.st_size; 250939Snate 2517768Sache close(STDIN_FILENO); 2527768Sache close(STDOUT_FILENO); 2537768Sache close(STDERR_FILENO); 25410154Sache 2557768Sache pid = fork(); 2567768Sache if (pid < 0) 25731306Scharnier perr("error in fork"); 258939Snate 2597768Sache else if (pid == 0) 2607768Sache { 2617768Sache char *nul = NULL; 2627768Sache char **nenvp = &nul; 263939Snate 2647768Sache /* Set up things for the child; we want standard input from the input file, 2657768Sache * and standard output and error sent to our output file. 2667768Sache */ 267939Snate 2687768Sache if (lseek(fd_in, (off_t) 0, SEEK_SET) < 0) 26931306Scharnier perr("error in lseek"); 270939Snate 2717768Sache if (dup(fd_in) != STDIN_FILENO) 27231306Scharnier perr("error in I/O redirection"); 273939Snate 2747768Sache if (dup(fd_out) != STDOUT_FILENO) 27531306Scharnier perr("error in I/O redirection"); 276939Snate 2777768Sache if (dup(fd_out) != STDERR_FILENO) 27831306Scharnier perr("error in I/O redirection"); 279939Snate 2807768Sache close(fd_in); 2817768Sache close(fd_out); 2827768Sache if (chdir(ATJOB_DIR) < 0) 28331306Scharnier perr("cannot chdir to " ATJOB_DIR); 284939Snate 2857768Sache queue = *filename; 286939Snate 2877768Sache PRIV_START 288939Snate 2897768Sache nice(tolower(queue) - 'a'); 29010154Sache 2917768Sache if (initgroups(pentry->pw_name,pentry->pw_gid)) 29231306Scharnier perr("cannot delete saved userids"); 293939Snate 29429231Sdima if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0) 29531306Scharnier perr("cannot change group"); 296939Snate 29729231Sdima if (setlogin(pentry->pw_name)) 29831306Scharnier perr("cannot set login name"); 29929231Sdima 30029231Sdima if (setuid(uid) < 0 || seteuid(uid) < 0) 30131306Scharnier perr("cannot set user id"); 302939Snate 30329231Sdima if (chdir(pentry->pw_dir)) 30429231Sdima chdir("/"); 30529231Sdima 3067768Sache if(execle("/bin/sh","sh",(char *) NULL, nenvp) != 0) 30731306Scharnier perr("exec failed for /bin/sh"); 308939Snate 3097768Sache PRIV_END 3107768Sache } 3117768Sache /* We're the parent. Let's wait. 3127768Sache */ 3137768Sache close(fd_in); 3147768Sache close(fd_out); 3157768Sache waitpid(pid, (int *) NULL, 0); 316939Snate 3177768Sache /* Send mail. Unlink the output file first, so it is deleted after 3187768Sache * the run. 3197768Sache */ 3207768Sache stat(filename, &buf); 3217768Sache if (open(filename, O_RDONLY) != STDIN_FILENO) 32231306Scharnier perr("open of jobfile failed"); 323939Snate 3247768Sache unlink(filename); 3257768Sache if ((buf.st_size != size) || send_mail) 32610154Sache { 32710154Sache PRIV_START 32810154Sache 32910154Sache if (initgroups(pentry->pw_name,pentry->pw_gid)) 33031306Scharnier perr("cannot delete saved userids"); 33110154Sache 33229231Sdima if (setgid(gid) < 0 || setegid(pentry->pw_gid) < 0) 33331306Scharnier perr("cannot change group"); 33410154Sache 33529231Sdima if (setlogin(pentry->pw_name)) 33631306Scharnier perr("cannot set login name"); 33729231Sdima 33829231Sdima if (setuid(uid) < 0 || seteuid(uid) < 0) 33931306Scharnier perr("cannot set user id"); 34010154Sache 34129231Sdima if (chdir(pentry->pw_dir)) 34229231Sdima chdir("/"); 34329231Sdima 3447768Sache#ifdef __FreeBSD__ 3457768Sache execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service", 3467777Sache "-odi", "-oem", 3477768Sache mailname, (char *) NULL); 3487768Sache#else 3497768Sache execl(MAIL_CMD, MAIL_CMD, mailname, (char *) NULL); 3507768Sache#endif 35131306Scharnier perr("exec failed for mail command"); 35210154Sache 35310154Sache PRIV_END 3547768Sache } 3557768Sache exit(EXIT_SUCCESS); 356939Snate} 357939Snate 358939Snate/* Global functions */ 359939Snate 36010154Sache/* Needed in gloadavg.c */ 36110154Sachevoid 36210154Sacheperr(const char *a) 36310154Sache{ 36410154Sache if (debug) 36510154Sache { 36631306Scharnier warn("%s", a); 36710154Sache } 36810154Sache else 36910154Sache syslog(LOG_ERR, "%s: %m", a); 37010154Sache 37110154Sache exit(EXIT_FAILURE); 37210154Sache} 37310154Sache 374939Snateint 3757768Sachemain(int argc, char *argv[]) 376939Snate{ 3777768Sache/* Browse through ATJOB_DIR, checking all the jobfiles wether they should 3787768Sache * be executed and or deleted. The queue is coded into the first byte of 3797768Sache * the job filename, the date (in minutes since Eon) as a hex number in the 3807768Sache * following eight bytes, followed by a dot and a serial number. A file 3817768Sache * which has not been executed yet is denoted by its execute - bit set. 3827768Sache * For those files which are to be executed, run_file() is called, which forks 3837768Sache * off a child which takes care of I/O redirection, forks off another child 3847768Sache * for execution and yet another one, optionally, for sending mail. 3857768Sache * Files which already have run are removed during the next invocation. 3867768Sache */ 3877768Sache DIR *spool; 3887768Sache struct dirent *dirent; 3897768Sache struct stat buf; 3907768Sache unsigned long ctm; 39110154Sache unsigned long jobno; 3927768Sache char queue; 3937768Sache time_t now, run_time; 3947768Sache char batch_name[] = "Z2345678901234"; 3957768Sache uid_t batch_uid; 3967768Sache gid_t batch_gid; 3977768Sache int c; 3987768Sache int run_batch; 3997768Sache double load_avg = LOADAVG_MX; 400939Snate 4017768Sache/* We don't need root privileges all the time; running under uid and gid daemon 4027768Sache * is fine. 4037768Sache */ 404939Snate 4057768Sache RELINQUISH_PRIVS_ROOT(DAEMON_UID, DAEMON_GID) 406939Snate 4077768Sache openlog("atrun", LOG_PID, LOG_CRON); 408939Snate 4097768Sache opterr = 0; 41024349Simp while((c=getopt(argc, argv, "dl:"))!= -1) 4117768Sache { 4127768Sache switch (c) 4137768Sache { 41410154Sache case 'l': 4157768Sache if (sscanf(optarg, "%lf", &load_avg) != 1) 4167768Sache perr("garbled option -l"); 4177768Sache if (load_avg <= 0.) 4187768Sache load_avg = LOADAVG_MX; 4197768Sache break; 420939Snate 4217768Sache case 'd': 4227768Sache debug ++; 4237768Sache break; 424939Snate 4257768Sache case '?': 4267768Sache default: 42731306Scharnier usage(); 4287768Sache } 4297768Sache } 430939Snate 4317768Sache if (chdir(ATJOB_DIR) != 0) 43231306Scharnier perr("cannot change to " ATJOB_DIR); 433939Snate 4347768Sache /* Main loop. Open spool directory for reading and look over all the 4357768Sache * files in there. If the filename indicates that the job should be run 4367768Sache * and the x bit is set, fork off a child which sets its user and group 4377768Sache * id to that of the files and exec a /bin/sh which executes the shell 4387768Sache * script. Unlink older files if they should no longer be run. For 4397768Sache * deletion, their r bit has to be turned on. 4407768Sache * 4417768Sache * Also, pick the oldest batch job to run, at most one per invocation of 4427768Sache * atrun. 4437768Sache */ 4447768Sache if ((spool = opendir(".")) == NULL) 44531306Scharnier perr("cannot read " ATJOB_DIR); 446939Snate 4477768Sache now = time(NULL); 4487768Sache run_batch = 0; 4497768Sache batch_uid = (uid_t) -1; 4507768Sache batch_gid = (gid_t) -1; 451939Snate 45210154Sache while ((dirent = readdir(spool)) != NULL) { 4537768Sache if (stat(dirent->d_name,&buf) != 0) 45431306Scharnier perr("cannot stat in " ATJOB_DIR); 455939Snate 4567768Sache /* We don't want directories 4577768Sache */ 45810154Sache if (!S_ISREG(buf.st_mode)) 4597768Sache continue; 460939Snate 46110154Sache if (sscanf(dirent->d_name,"%c%5lx%8lx",&queue,&jobno,&ctm) != 3) 4627768Sache continue; 463939Snate 4647768Sache run_time = (time_t) ctm*60; 465939Snate 46610154Sache if ((S_IXUSR & buf.st_mode) && (run_time <=now)) { 46710154Sache if (isupper(queue) && (strcmp(batch_name,dirent->d_name) > 0)) { 4687768Sache run_batch = 1; 4697768Sache strncpy(batch_name, dirent->d_name, sizeof(batch_name)); 4707768Sache batch_uid = buf.st_uid; 4717768Sache batch_gid = buf.st_gid; 4727768Sache } 47310154Sache 4747768Sache /* The file is executable and old enough 4757768Sache */ 4767768Sache if (islower(queue)) 4777768Sache run_file(dirent->d_name, buf.st_uid, buf.st_gid); 478939Snate } 4797768Sache /* Delete older files 4807768Sache */ 4817768Sache if ((run_time < now) && !(S_IXUSR & buf.st_mode) && (S_IRUSR & buf.st_mode)) 4827768Sache unlink(dirent->d_name); 4837768Sache } 4847768Sache /* run the single batch file, if any 4857768Sache */ 48610154Sache if (run_batch && (gloadavg() < load_avg)) 48710154Sache run_file(batch_name, batch_uid, batch_gid); 4887768Sache 4897768Sache closelog(); 4907768Sache exit(EXIT_SUCCESS); 491939Snate} 49231306Scharnier 49331306Scharnierstatic void 49490148Simpusage(void) 49531306Scharnier{ 49631306Scharnier if (debug) 49731306Scharnier fprintf(stderr, "usage: atrun [-l load_avg] [-d]\n"); 49831306Scharnier else 49931306Scharnier syslog(LOG_ERR, "usage: atrun [-l load_avg] [-d]"); 50031306Scharnier 50131306Scharnier exit(EXIT_FAILURE); 50231306Scharnier} 503