[BACK]Return to atrun.c CVS log [TXT][DIR] Up to [cvs.NetBSD.org] / src / libexec / atrun

Annotation of src/libexec/atrun/atrun.c, Revision 1.20

1.20    ! mbalmer     1: /*     $NetBSD: atrun.c,v 1.19 2008/04/05 20:17:37 christos Exp $      */
1.3       thorpej     2:
1.1       cgd         3: /*
1.5       christos    4:  *  atrun.c - run jobs queued by at; run with root privileges.
                      5:  *  Copyright (C) 1993, 1994 Thomas Koenig
1.1       cgd         6:  *
                      7:  * Redistribution and use in source and binary forms, with or without
                      8:  * modification, are permitted provided that the following conditions
                      9:  * are met:
                     10:  * 1. Redistributions of source code must retain the above copyright
                     11:  *    notice, this list of conditions and the following disclaimer.
                     12:  * 2. The name of the author(s) may not be used to endorse or promote
                     13:  *    products derived from this software without specific prior written
                     14:  *    permission.
                     15:  *
                     16:  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR(S) ``AS IS'' AND ANY EXPRESS OR
                     17:  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
                     18:  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
1.5       christos   19:  * IN NO EVENT SHALL THE AUTHOR(S) BE LIABLE FOR ANY DIRECT, INDIRECT,
1.1       cgd        20:  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
                     21:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
                     22:  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
                     23:  * THEORY OF LIABILITY, WETHER IN CONTRACT, STRICT LIABILITY, OR TORT
                     24:  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
                     25:  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
                     26:  */
                     27:
                     28: /* System Headers */
1.17      perry      29: #include <sys/cdefs.h>
1.4       mrg        30: #include <sys/types.h>
1.1       cgd        31: #include <sys/wait.h>
1.10      christos   32:
1.5       christos   33: #include <ctype.h>
1.10      christos   34: #include <errno.h>
1.1       cgd        35: #include <dirent.h>
1.6       kleink     36: #include <fcntl.h>
1.1       cgd        37: #include <stdio.h>
                     38: #include <stdlib.h>
                     39: #include <string.h>
1.10      christos   40: #include <limits.h>
1.1       cgd        41: #include <time.h>
                     42: #include <unistd.h>
                     43: #include <syslog.h>
1.10      christos   44: #include <pwd.h>
1.15      christos   45: #include <grp.h>
1.10      christos   46: #include <err.h>
1.1       cgd        47: #include <paths.h>
1.16      christos   48: #include <stdarg.h>
1.1       cgd        49:
                     50: /* Local headers */
                     51:
                     52: #define MAIN
                     53: #include "privs.h"
                     54: #include "pathnames.h"
                     55: #include "atrun.h"
                     56:
                     57: /* File scope variables */
                     58:
1.5       christos   59: #if 0
                     60: static char rcsid[] = "$OpenBSD: atrun.c,v 1.7 1997/09/08 22:12:10 millert Exp $";
                     61: #else
1.20    ! mbalmer    62: __RCSID("$NetBSD: atrun.c,v 1.19 2008/04/05 20:17:37 christos Exp $");
1.4       mrg        63: #endif
1.1       cgd        64:
1.5       christos   65: static int debug = 0;
                     66:
1.1       cgd        67: /* Local functions */
1.18      perry      68: static void perr(const char *, ...) __dead;
                     69: static void perrx(const char *, ...) __dead;
1.16      christos   70: static int write_string(int, const char *);
                     71: static void run_file(const char *, uid_t, gid_t);
                     72: static void become_user(struct passwd *, uid_t);
1.5       christos   73:
1.15      christos   74: static const char nobody[] = "nobody";
                     75:
1.1       cgd        76: static void
1.16      christos   77: perr(const char *fmt, ...)
1.1       cgd        78: {
1.16      christos   79:        char buf[2048];
                     80:        va_list ap;
                     81:
                     82:        va_start(ap, fmt);
                     83:        (void)vsnprintf(buf, sizeof(buf), fmt, ap);
                     84:        va_end(ap);
                     85:
1.5       christos   86:        if (debug)
1.16      christos   87:                warn("%s", buf);
1.5       christos   88:        else
1.16      christos   89:                syslog(LOG_ERR, "%s: %m", buf);
1.5       christos   90:
                     91:        exit(EXIT_FAILURE);
                     92: }
                     93:
                     94: static void
1.16      christos   95: perrx(const char *fmt, ...)
1.5       christos   96: {
1.16      christos   97:        va_list ap;
                     98:
                     99:        va_start(ap, fmt);
                    100:
                    101:        if (debug)
                    102:                vwarnx(fmt, ap);
                    103:        else
                    104:                vsyslog(LOG_ERR, fmt, ap);
                    105:
                    106:        va_end(ap);
1.5       christos  107:
1.1       cgd       108:        exit(EXIT_FAILURE);
                    109: }
                    110:
                    111: static int
1.16      christos  112: write_string(int fd, const char *a)
1.1       cgd       113: {
1.16      christos  114:        return write(fd, a, strlen(a));
1.5       christos  115: }
                    116:
                    117: static void
1.16      christos  118: become_user(struct passwd *pentry, uid_t uid)
1.5       christos  119: {
1.16      christos  120:        if (initgroups(pentry->pw_name, pentry->pw_gid) == -1)
                    121:                perr("Cannot init group list for `%s'", pentry->pw_name);
1.5       christos  122:
1.16      christos  123:        if (setegid(pentry->pw_gid) == -1 || setgid(pentry->pw_gid) == -1)
                    124:                perr("Cannot change primary group to %lu",
                    125:                    (unsigned long)pentry->pw_gid);
1.12      dsl       126:
1.16      christos  127:        if (setsid() == -1)
1.12      dsl       128:                perr("Cannot create a session");
1.5       christos  129:
1.16      christos  130:        if (setlogin(pentry->pw_name) == -1)
                    131:                perr("Cannot set login name to `%s'", pentry->pw_name);
1.5       christos  132:
1.16      christos  133:        if (setuid(uid) == -1 || seteuid(uid) == -1)
                    134:                perr("Cannot set user id to %lu", (unsigned long)uid);
1.5       christos  135:
1.16      christos  136:        if (chdir(pentry->pw_dir) == -1)
                    137:                (void)chdir("/");
1.1       cgd       138: }
                    139:
                    140: static void
1.16      christos  141: run_file(const char *filename, uid_t uid, gid_t gid)
1.1       cgd       142: {
                    143:        /*
                    144:         * Run a file by by spawning off a process which redirects I/O,
                    145:         * spawns a subshell, then waits for it to complete and spawns another
                    146:         * process to send mail to the user.
                    147:         */
                    148:        pid_t pid;
                    149:        int fd_out, fd_in;
                    150:        int queue;
1.8       simonb    151:        char mailbuf[LOGIN_NAME_MAX], fmt[49];
1.1       cgd       152:        char *mailname = NULL;
                    153:        FILE *stream;
                    154:        int send_mail = 0;
1.5       christos  155:        struct stat buf, lbuf;
1.1       cgd       156:        off_t size;
                    157:        struct passwd *pentry;
                    158:        int fflags;
1.16      christos  159:        uid_t nuid;
                    160:        gid_t ngid;
                    161:        int serrno;
1.5       christos  162:
1.19      christos  163:        PRIV_START;
1.5       christos  164:
1.16      christos  165:        if (chmod(filename, S_IRUSR) == -1)
                    166:                perr("Cannot change file permissions to `%s'", filename);
1.5       christos  167:
1.19      christos  168:        PRIV_END;
1.1       cgd       169:
                    170:        pid = fork();
                    171:        if (pid == -1)
                    172:                perr("Cannot fork");
1.5       christos  173:        else if (pid != 0)
1.1       cgd       174:                return;
                    175:
                    176:        /*
                    177:         * Let's see who we mail to.  Hopefully, we can read it from the
                    178:         * command file; if not, send it to the owner, or, failing that, to
                    179:         * root.
                    180:         */
                    181:
1.5       christos  182:        pentry = getpwuid(uid);
1.16      christos  183:        if (pentry == NULL)
                    184:                perrx("Userid %lu not found - aborting job `%s'",
                    185:                    (unsigned long)uid, filename);
                    186:
1.19      christos  187:        PRIV_START;
1.1       cgd       188:
1.5       christos  189:        stream = fopen(filename, "r");
1.16      christos  190:        serrno = errno;
1.1       cgd       191:
1.19      christos  192:        PRIV_END;
1.1       cgd       193:
1.16      christos  194:        if (stream == NULL) {
                    195:                errno = serrno;
                    196:                perr("Cannot open input file");
1.5       christos  197:        }
1.2       cgd       198:
1.16      christos  199:        if (pentry->pw_expire && time(NULL) >= pentry->pw_expire)
                    200:                perrx("Userid %lu has expired - aborting job `%s'",
                    201:                    (unsigned long)uid, filename);
1.1       cgd       202:
1.16      christos  203:        if ((fd_in = dup(fileno(stream))) == -1)
1.1       cgd       204:                perr("Error duplicating input file descriptor");
                    205:
1.5       christos  206:        if (fstat(fd_in, &buf) == -1)
                    207:                perr("Error in fstat of input file descriptor");
                    208:
1.19      christos  209:        PRIV_START;
1.5       christos  210:
                    211:        if (lstat(filename, &lbuf) == -1)
1.16      christos  212:                perr("Error in lstat of `%s'", filename);
1.5       christos  213:
1.19      christos  214:        PRIV_END;
1.5       christos  215:
1.16      christos  216:        if (S_ISLNK(lbuf.st_mode))
                    217:                perrx("Symbolic link encountered in job `%s' - aborting",
1.5       christos  218:                    filename);
1.16      christos  219:
1.5       christos  220:        if ((lbuf.st_dev != buf.st_dev) || (lbuf.st_ino != buf.st_ino) ||
                    221:            (lbuf.st_uid != buf.st_uid) || (lbuf.st_gid != buf.st_gid) ||
1.16      christos  222:            (lbuf.st_size!=buf.st_size))
                    223:                perrx("Somebody changed files from under us for job `%s' "
                    224:                    "- aborting", filename);
                    225:
                    226:        if (buf.st_nlink > 1)
                    227:                perrx("Somebody is trying to run a linked script for job `%s'",
1.5       christos  228:                    filename);
1.16      christos  229:
1.1       cgd       230:        if ((fflags = fcntl(fd_in, F_GETFD)) < 0)
                    231:                perr("Error in fcntl");
                    232:
1.5       christos  233:        (void)fcntl(fd_in, F_SETFD, fflags & ~FD_CLOEXEC);
1.1       cgd       234:
1.5       christos  235:        (void)snprintf(fmt, sizeof(fmt),
1.9       martin    236:            "#!/bin/sh\n# atrun uid=%%u gid=%%u\n# mail %%%ds %%d",
1.8       simonb    237:            LOGIN_NAME_MAX);
1.16      christos  238:
                    239:        if (fscanf(stream, fmt, &nuid, &ngid, mailbuf, &send_mail) != 4)
                    240:                perrx("File `%s' is in wrong format - aborting", filename);
                    241:
                    242:        if (mailbuf[0] == '-')
                    243:                perrx("Illegal mail name `%s' in `%s'", mailbuf, filename);
                    244:
1.5       christos  245:        mailname = mailbuf;
1.16      christos  246:        if (nuid != uid)
                    247:                perrx("Job `%s' - userid %lu does not match file uid %lu",
                    248:                    filename, (unsigned long)nuid, (unsigned long)uid);
                    249:
                    250:        if (ngid != gid)
                    251:                perrx("Job `%s' - groupid %lu does not match file gid %lu",
                    252:                    filename, (unsigned long)ngid, (unsigned long)gid);
                    253:
1.5       christos  254:        (void)fclose(stream);
                    255:
1.19      christos  256:        PRIV_START;
1.5       christos  257:
1.16      christos  258:        if (chdir(_PATH_ATSPOOL) == -1)
                    259:                perr("Cannot chdir to `%s'", _PATH_ATSPOOL);
1.1       cgd       260:
                    261:        /*
                    262:         * Create a file to hold the output of the job we are  about to
                    263:         * run. Write the mail header.
                    264:         */
1.5       christos  265:
1.1       cgd       266:        if ((fd_out = open(filename,
1.16      christos  267:                    O_WRONLY | O_CREAT | O_EXCL, S_IWUSR | S_IRUSR)) == -1)
                    268:                perr("Cannot create output file `%s'", filename);
1.1       cgd       269:
1.19      christos  270:        PRIV_END;
1.5       christos  271:
                    272:        write_string(fd_out, "To: ");
                    273:        write_string(fd_out, mailname);
                    274:        write_string(fd_out, "\nSubject: Output from your job ");
1.1       cgd       275:        write_string(fd_out, filename);
                    276:        write_string(fd_out, "\n\n");
1.5       christos  277:        if (fstat(fd_out, &buf) == -1)
                    278:                perr("Error in fstat of output file descriptor");
1.1       cgd       279:        size = buf.st_size;
                    280:
1.5       christos  281:        (void)close(STDIN_FILENO);
                    282:        (void)close(STDOUT_FILENO);
                    283:        (void)close(STDERR_FILENO);
1.1       cgd       284:
                    285:        pid = fork();
                    286:        if (pid < 0)
                    287:                perr("Error in fork");
                    288:        else if (pid == 0) {
                    289:                char *nul = NULL;
                    290:                char **nenvp = &nul;
                    291:
                    292:                /*
                    293:                 * Set up things for the child; we want standard input from
                    294:                 * the input file, and standard output and error sent to
                    295:                 * our output file.
                    296:                 */
1.16      christos  297:                if (lseek(fd_in, (off_t) 0, SEEK_SET) == (off_t)-1)
1.1       cgd       298:                        perr("Error in lseek");
                    299:
                    300:                if (dup(fd_in) != STDIN_FILENO)
                    301:                        perr("Error in I/O redirection");
                    302:
                    303:                if (dup(fd_out) != STDOUT_FILENO)
                    304:                        perr("Error in I/O redirection");
                    305:
                    306:                if (dup(fd_out) != STDERR_FILENO)
                    307:                        perr("Error in I/O redirection");
                    308:
1.5       christos  309:                (void)close(fd_in);
                    310:                (void)close(fd_out);
                    311:
1.19      christos  312:                PRIV_START;
1.5       christos  313:
1.16      christos  314:                if (chdir(_PATH_ATJOBS) == -1)
                    315:                        perr("Cannot chdir to `%s'", _PATH_ATJOBS);
1.1       cgd       316:
                    317:                queue = *filename;
                    318:
1.5       christos  319:                if (queue > 'b')
                    320:                    nice(queue - 'b');
1.1       cgd       321:
1.5       christos  322:                become_user(pentry, uid);
1.1       cgd       323:
1.16      christos  324:                (void)execle("/bin/sh", "sh", (char *)NULL, nenvp);
                    325:                perr("Exec failed for /bin/sh");
1.5       christos  326:        }
                    327:        /* We're the parent.  Let's wait. */
                    328:        (void)close(fd_in);
                    329:        (void)close(fd_out);
1.16      christos  330:        (void)waitpid(pid, (int *)NULL, 0);
1.1       cgd       331:
1.5       christos  332:        /*
                    333:         * Send mail.  Unlink the output file first, so it is deleted
                    334:         * after the run.
                    335:         */
1.19      christos  336:        PRIV_START;
1.1       cgd       337:
1.5       christos  338:        if (stat(filename, &buf) == -1)
1.16      christos  339:                perr("Error in stat of output file `%s'", filename);
1.5       christos  340:        if (open(filename, O_RDONLY) != STDIN_FILENO)
1.16      christos  341:                perr("Open of jobfile `%s' failed", filename);
1.1       cgd       342:
1.5       christos  343:        (void)unlink(filename);
1.1       cgd       344:
1.19      christos  345:        PRIV_END;
1.1       cgd       346:
                    347:        if ((buf.st_size != size) || send_mail) {
                    348:                /* Fork off a child for sending mail */
1.5       christos  349:
1.19      christos  350:                PRIV_START;
1.5       christos  351:
                    352:                become_user(pentry, uid);
                    353:
                    354:                execl(_PATH_SENDMAIL, "sendmail", "-F", "Atrun Service",
                    355:                    "-odi", "-oem", "-t", (char *) NULL);
1.16      christos  356:                perr("Exec failed for mail command `%s'", _PATH_SENDMAIL);
1.5       christos  357:
1.19      christos  358:                PRIV_END;
1.1       cgd       359:        }
                    360:        exit(EXIT_SUCCESS);
                    361: }
                    362:
                    363: /* Global functions */
                    364:
                    365: int
1.16      christos  366: main(int argc, char *argv[])
1.1       cgd       367: {
                    368:        /*
1.20    ! mbalmer   369:         * Browse through  _PATH_ATJOBS, checking all the jobfiles whether
1.1       cgd       370:         * they should be executed and or deleted. The queue is coded into
                    371:         * the first byte of the job filename, the date (in minutes since
                    372:         * Eon) as a hex number in the following eight bytes, followed by
                    373:         * a dot and a serial number.  A file which has not been executed
                    374:         * yet is denoted by its execute - bit set.  For those files which
                    375:         * are to be executed, run_file() is called, which forks off a
                    376:         * child which takes care of I/O redirection, forks off another
                    377:         * child for execution and yet another one, optionally, for sending
                    378:         * mail.  Files which already have run are removed during the
                    379:         * next invocation.
                    380:         */
                    381:        DIR *spool;
                    382:        struct dirent *dirent;
                    383:        struct stat buf;
                    384:        unsigned long ctm;
1.5       christos  385:        int jobno;
1.1       cgd       386:        char queue;
1.5       christos  387:        time_t now, run_time;
                    388:        char batch_name[] = "Z2345678901234";
                    389:        uid_t batch_uid;
                    390:        gid_t batch_gid;
                    391:        int c;
                    392:        int run_batch;
                    393:        double la, load_avg = ATRUN_MAXLOAD;
1.15      christos  394:        struct group *grp;
                    395:        struct passwd *pwd;
                    396:
1.16      christos  397:        openlog("atrun", LOG_PID, LOG_CRON);
                    398:
1.15      christos  399:        if ((grp = getgrnam(nobody)) == NULL)
1.16      christos  400:                perrx("Cannot get gid for `%s'", nobody);
1.15      christos  401:
                    402:        if ((pwd = getpwnam(nobody)) == NULL)
1.16      christos  403:                perrx("Cannot get uid for `%s'", nobody);
1.1       cgd       404:
                    405:        /*
                    406:         * We don't need root privileges all the time; running under uid
1.7       simonb    407:         * and gid nobody is fine except for privileged operations.
1.1       cgd       408:         */
1.19      christos  409:        RELINQUISH_PRIVS_ROOT(pwd->pw_uid, grp->gr_gid);
1.1       cgd       410:
1.5       christos  411:        opterr = 0;
                    412:        errno = 0;
                    413:        while ((c = getopt(argc, argv, "dl:")) != -1) {
                    414:                switch (c) {
                    415:                case 'l':
                    416:                        if (sscanf(optarg, "%lf", &load_avg) != 1)
1.16      christos  417:                                perrx("Bad argument to option -l: ", optarg);
                    418:                        if (load_avg <= 0)
1.5       christos  419:                                load_avg = ATRUN_MAXLOAD;
                    420:                        break;
                    421:
                    422:                case 'd':
                    423:                        debug++;
                    424:                        break;
                    425:
                    426:                case '?':
1.16      christos  427:                        perrx("Usage: %s [-l <loadav>] [-d]", getprogname());
                    428:                        /*NOTREACHED*/
1.5       christos  429:
                    430:                default:
1.16      christos  431:                        perrx("Invalid option: %c", c);
                    432:                        /*NOTREACHED*/
1.5       christos  433:                }
                    434:        }
                    435:
1.19      christos  436:        PRIV_START;
1.5       christos  437:
1.16      christos  438:        if (chdir(_PATH_ATJOBS) == -1)
                    439:                perr("Cannot change directory to `%s'", _PATH_ATJOBS);
1.1       cgd       440:
                    441:        /*
                    442:         * Main loop. Open spool directory for reading and look over all
                    443:         * the files in there. If the filename indicates that the job
                    444:         * should be run and the x bit is set, fork off a child which sets
                    445:         * its user and group id to that of the files and exec a /bin/sh
                    446:         * which executes the shell script. Unlink older files if they
                    447:         * should no longer be run.  For deletion, their r bit has to be
                    448:         * turned on.
1.5       christos  449:         *
                    450:         * Also, pick the oldest batch job to run, at most one per
                    451:         * invocation of atrun.
1.1       cgd       452:         */
                    453:        if ((spool = opendir(".")) == NULL)
1.16      christos  454:                perr("Cannot open `%s'", _PATH_ATJOBS);
1.5       christos  455:
1.19      christos  456:        PRIV_END;
1.5       christos  457:
                    458:        now = time(NULL);
                    459:        run_batch = 0;
                    460:        batch_uid = (uid_t) -1;
                    461:        batch_gid = (gid_t) -1;
1.1       cgd       462:
                    463:        while ((dirent = readdir(spool)) != NULL) {
1.19      christos  464:                PRIV_START;
1.1       cgd       465:
1.16      christos  466:                if (stat(dirent->d_name, &buf) == -1)
                    467:                        perr("Cannot stat `%s' in `%s'", dirent->d_name,
                    468:                            _PATH_ATJOBS);
1.5       christos  469:
1.19      christos  470:                PRIV_END;
1.1       cgd       471:
                    472:                /* We don't want directories */
                    473:                if (!S_ISREG(buf.st_mode))
                    474:                        continue;
                    475:
1.16      christos  476:                if (sscanf(dirent->d_name, "%c%5x%8lx", &queue, &jobno,
                    477:                    &ctm) != 3)
1.1       cgd       478:                        continue;
                    479:
1.5       christos  480:                run_time = (time_t) ctm * 60;
1.1       cgd       481:
1.5       christos  482:                if ((S_IXUSR & buf.st_mode) && (run_time <= now)) {
1.14      dsl       483:                        if (isupper((unsigned char)queue) &&
1.5       christos  484:                            (strcmp(batch_name, dirent->d_name) > 0)) {
                    485:                                run_batch = 1;
1.13      itojun    486:                                (void)strlcpy(batch_name, dirent->d_name,
1.5       christos  487:                                    sizeof(batch_name));
                    488:                                batch_uid = buf.st_uid;
                    489:                                batch_gid = buf.st_gid;
                    490:                        }
                    491:
                    492:                        /* The file is executable and old enough */
1.14      dsl       493:                        if (islower((unsigned char)queue))
1.16      christos  494:                                run_file(dirent->d_name, buf.st_uid,
                    495:                                    buf.st_gid);
1.5       christos  496:                }
1.1       cgd       497:
1.5       christos  498:                /* Delete older files */
                    499:                if ((run_time < now) && !(S_IXUSR & buf.st_mode) &&
                    500:                    (S_IRUSR & buf.st_mode)) {
1.19      christos  501:                        PRIV_START;
1.1       cgd       502:
1.5       christos  503:                        (void)unlink(dirent->d_name);
1.1       cgd       504:
1.19      christos  505:                        PRIV_END;
1.1       cgd       506:                }
                    507:        }
1.5       christos  508:
                    509:        /* Run the single batch file, if any */
                    510:        if (run_batch && ((getloadavg(&la, 1) == 1) && la < load_avg))
                    511:                run_file(batch_name, batch_uid, batch_gid);
                    512:
1.1       cgd       513:        closelog();
1.16      christos  514:        return EXIT_SUCCESS;
1.1       cgd       515: }

CVSweb <webmaster@jp.NetBSD.org>