HomePage RecentChanges

root

For years I've been using an ultra simple program to allow root access without a password. It was called 'root' and in spite of a couple of bugs which I'd mainly fixed I generally just liked it. So easy to set up - just put the allowed uid's into /etc/rooters and off you go. Well blow me down, I lost the sources. Not on my backups. Not in cvs. What the heck had I done with it? No good googling - 'root' is just too general a name.

So I re-wrote it from scratch and (apologies to the original author), its a darn sight better now (it even evaluates its arguments correctly if they contain spaces, which the old 'root' didn't). I also got it to add /sbin /usr/sbin and /usr/local/sbin to PATH while I was at it, and allowed root to run it themselves. So now, it's just awesome!

Here it is. To compile just do this as root:

cc -o root root.c
chown root root
chmod 4755 root
cp root /usr/local/bin # or anywhere you like

Then put your allowed uids (use 'id -u') into /etc/rooters and then do this as root:

chown root /etc/rooters
chmod 600 /etc/rooters
/*
Copyright 2008 Bob Hepple

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

http://bhepple.freeshell.org
http://process-getopt.sourceforge.net
$Id: root.c,v 1.1.1.1 2008/08/23 10:19:38 bhepple Exp $
*/

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>

#define ROOTERS "/etc/rooters"
#define MAXLINE 1024
#define MAXPATH 1024

extern int errno;

int executable(char *filename, int verbose) {
    struct stat buf;
    int retval = 0;

    if (verbose) printf("Checking %s\n", filename);

    if (stat(filename, &buf) == 0) {
        if ((buf.st_mode & S_IFMT) == S_IFREG) {
            if (buf.st_mode & 0111) {
                retval = 1;
                goto end;
            }
        }
    }

end:
    return(retval);
}

char *find_exec(char *path, char *name, int verbose) {
    char *p = path;
    static char filename[MAXPATH];
    char *retval = NULL;

    filename[0] = 0;
    if (strchr(name, '/')) {
        if (executable(name, verbose)) {
            strncpy(filename, name, MAXPATH);
            retval = filename;
        }
        goto end;
    }

    for (p = path; *p; ) {
        char *e = NULL;

        while (*p == ':') p++;
        if (!*p) break;

        e = strchr(p, ':');
        if (e) *e = 0;
            
        snprintf(filename, MAXPATH, "%s/%s", p, name);
        if (executable(filename, verbose)) {
            retval = filename;
            goto end;
        }
        if (!e) break;
        p = e + 1;
    }

end:
    return retval;
}

void usage(char *progname) {
    printf("%s: run command as root\n", progname);
    printf("Usage: %s [-hvV] command [OPTIONS] [ARGUMENTS]\n\n", progname);
    printf("Your id must be in %s which must be chown root; chmod 0600\n", 
           ROOTERS);
    printf("Options:\n");
    printf("-h, --help    : print this help and exit\n");
    printf("-v, --verbose : be verbose\n");
    printf("-V, --version : print the version and exit\n");
}

int main(int argc, char **argv, char **envp) {
    char *progname = NULL;
    char *c;
    uid_t ruid = 0;
    uid_t euid = 0;
    uid_t uid = 0;
    int retval = 0;
    FILE *rooters = NULL;
    struct stat buf;
    char inbuf[MAXLINE];
    char *path = NULL;
    char new_path[MAXPATH];
    char *filename = NULL;
    int verbose = 0;

    progname = argv[0];
    for (c = progname + strlen(progname) - 1; c > progname && *c != '/'; c--)
        ;
    if (*c == '/') progname = c + 1;

    /* look for, process and remove options: */
    if (argc < 2) {
        fprintf(stderr, "%s: do what?\n", progname);
        retval = 1;
        goto end;
    }

    if ((strcmp(argv[1], "-h") == 0) || (strcmp(argv[1], "--help") == 0) ||
        (strcmp(argv[1], "-help") == 0)) {
        usage(progname);
        return(0);
    }

    if ((strcmp(argv[1], "-V") == 0) || (strcmp(argv[1], "--version") == 0) ||
        (strcmp(argv[1], "-version") == 0)) {
        printf("%s version 1.0\n", progname);
        return(0);
    }

    if ((strcmp(argv[1], "-v") == 0) || (strcmp(argv[1], "--verbose") == 0) ||
        (strcmp(argv[1], "-verbose") == 0)) {
        verbose = 1;
        argv++; argc--;
    }

    if (argc < 2) {
        fprintf(stderr, "%s: do what?\n", progname);
        retval = 1;
        goto end;
    }

    if (*argv[1] == '-') {
        fprintf(stderr, "%s: unknown option (%s)\n", progname, argv[1]);
        retval = 1;
        goto end;
    }

    /* options are now processed and removed - argv is now the command to run */

    ruid = getuid();
    euid = geteuid();

    if (verbose) printf("real uid = %d, effective uid = %d\n", ruid, euid);

    if (euid != 0) {
        fprintf(stderr, "%s: is not setuid root - it needs chown root; "
                "chmod 4755\n", progname);
        retval = 1;
        goto end; /* nothing else can be checked */
    }
    
    if (stat(ROOTERS, &buf) != 0) {
        fprintf(stderr, "%s: can't stat %s\n", progname, ROOTERS);
        retval = 1;
    }
    
    if (buf.st_mode != (0600 | S_IFREG)) {
        fprintf(stderr, "%s: %s must have mode 0600\n", progname, ROOTERS);
        retval = 1;
    }
    
    if (buf.st_uid != 0) {
        fprintf(stderr, "%s: %s must be owned by root\n", progname, ROOTERS);
        retval = 1;
    }

    if ((rooters = fopen(ROOTERS, "r")) == NULL) {
        fprintf(stderr, "%s: can't open %s\n", progname, ROOTERS);
        retval = 1;
    }
    
    while (ruid != 0) { /* if we're already root then fall through */
        if (fgets(inbuf, MAXLINE, rooters) == NULL) {
            fprintf(stderr, "%s: not permitted\n", progname);
            retval = 1;
            break;
        }

        if (sscanf(inbuf, "%d", &uid) != 1) continue;
        if (uid == ruid) {
            break;
        }
    }
    fclose(rooters);
    rooters = NULL;

    path = getenv("PATH");
    snprintf(new_path, MAXPATH, "%s:/sbin:/usr/sbin:/usr/local/sbin", path);
    if (verbose) printf("PATH=%s\n", new_path);

    argv++; argc--;
    if ((filename = find_exec(new_path, argv[0], verbose)) == NULL) {
        fprintf(stderr, "%s: %s: not found\n", progname, argv[0]);
        retval = 1;
    }
    
    if (retval != 0) {
        goto end;
    }

    if (setuid(0) < 0) {
        fprintf(stderr, "%s: setuid(0) failed\n", progname);
        retval = 1;
        goto end;
    }

    if (verbose) {
        char **arg;
        int first = 1;

        printf("real uid = %d, effective uid = %d\n", getuid(), geteuid());
        printf("Executing: %s as \"", filename);
        for (arg = argv; *arg; arg++) {
            if (!first) printf(" ");
            first = 0;
            printf("%s", *arg);
        }
        printf("\"\n");
    }

    execve(filename, argv, envp);
    fprintf(stderr, "%s: execve(\"%s\") failed errno = %d (%s)\n", progname, 
            filename, errno, strerror(errno));
    retval = 1;

end:
    if (rooters) fclose(rooters);
    return(retval);
}