/* SafeLoad v0.1
 *
 * Author: Willy Tarreau <tarreau@aemiaif.lip6.fr>
 *
 * THIS PROGRAM IS DISTRIBUTED WITH NO EXPRESSED OR IMPLIED SORT OF WARANTY.
 * THE AUTHORS SHOULD NOT BE LIABLE FOR ANY DAMAGE OR LOSS CAUSED TO YOU,
 * YOUR SYSTEM OR ANYBODY.
 *
 */

#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <grp.h>
#include <pwd.h>
#include <sys/types.h>
#include <time.h>


/* number of seconds to wait when an error has been detected in LLSTOP mode. */
#define WAITTIME 5

/* log levels */
#define LLNONE          0       /* don't log anything about this prog */
#define LL2LONG         1       /* log only if too long an arg is given */
#define LL2MANY         2       /* also log if too many args given */
#define LLCALL          3       /* also systematically log any call to the prog */
#define LLSTOP          4       /* wait and stop if any error occurs */

#define toend(p)        {while(*p&&*p!=' '&&*p!='\t'&&*p!='\r'&&*p!='\n')p++;if(*p)*p++=0;}
#define tonext(p)       {while(*p==' '||*p=='\t')p++;}


int i,j,s;

/* CAUTION ! Don't make the initial string longer than 1023 chars! */
char *suidtable="/etc/safeload.tbl";
char *logfile="/var/log/safeload.log";

void usage(char *name) {
    close(1); dup(2);
    printf("SafeLoad v0.1 - 1998/03/01 - Willy Tarreau <tarreau@aemiaif.lip6.fr>\n",name);
    printf("\tSafeLoad is buffer overflow wrapper for suided programs.\n");
    printf("\tThis program should never be called directly, but only with\n");
    printf("\tthe name of the program it replaces. The simplest way to do\n");
    printf("\tthis is to rename the insecure program and to replace it by\n");
    printf("\ta symlink to safeload. SafeLoad will then look in the table file\n");
    printf("\tfor a line containing this name, and will then execute the proper\n");
    printf("\tprogram with proper euid, which must be, of course, non suided.\n");
    printf("\tAll args will be truncated to a length specified in this file,\n");
    printf("\tand all attempts to overflow any arg will be logged.\n");
    printf("\nCompile-time configuration:\n");
    printf("\ttable file: %s\n\tlog file: %s\n",suidtable,logfile);
    exit(1);
}


int log(char *name, char *error, int verbose) {
    FILE *logf;
    time_t tim;
    struct tm *tm;
    /* let's open the log file */
    if ((logf=fopen(logfile,"a+"))==NULL) {
        fprintf(stderr,"safeload: error while opening log file %s:",logfile);
        perror("");
        fprintf(stderr,"Error was: %s for %s\n",error,name);
        exit(1);
    }
    time(&tim); tm=localtime(&tim);
    fprintf(logf,"%04d/%02d/%02d %02d:%02d: uid=%d, %s for %s\n",tm->tm_year+1900,tm->tm_mon,tm->tm_mday,tm->t
m_hour,tm->tm_min,
            getuid(),error,name);
    fclose(logf);
    if (verbose)
        fprintf(stderr,"SafeLoad logged this error:\n\t%s for %s\n",error,name);
    return 1;  /* just to give a value to a direct call to exit() */
}


int longwait() {
    int s;
#ifdef LONGWAIT
    for (s=0;s<32;s++)
        signal(s,SIG_IGN);
    sleep(WAITTIME);
    for (s=0;s<32;s++)
        signal(s,SIG_DFL);
#endif
    return 1; /* give value to exit */
}

main(int argc, char **argv) {
    FILE *tbl;
    char *line, *myname;
    char *p, *callname, *realname, *np, *nerr;
    int euid, egid, nbargs, lmax, loglevel;

    int i,j,s;

    /* Make myname point to the name under which safeload was called, without the directory */
    if (myname=strrchr(argv[0],'/'))
        myname++;
    else
        myname=argv[0];

    /* if safeload is called as-is it's an error so we tell the user how to use it */
    if (!strcmp(myname,"safeload"))
        usage(myname);

    /* let's open the table */
    if ((tbl=fopen(suidtable,"r"))==NULL) {
        fprintf(stderr,"safeload: error while opening table file %s:",suidtable);
        perror("");
        exit(1);
    }

    /* now, search in the table for a line beginning with myname */
    line=(char *)malloc(1024);
    while (fgets(line,1024,tbl)) {
        callname=p=line; toend(p);
        if (*callname=='#')
            continue;
        if (!strcmp(myname,callname))   /* not the right name. We go on. */
            break;
    }
    if (strcmp(myname,callname)) {
        /* if we get here, this means that safeload has been called with a wrong
           name. We have to log this because it can be the source of an attack. */
        
        log(myname,"missing entry",1);
        exit(1);
    }

    /* Ok, we got the right name. Let's discover what we have to do now. */

    tonext(p); realname=p; toend(p);
    if (!*realname) exit(log(myname,"incomplete entry",0));

    tonext(p); np=p; toend(p);
    if (!isalpha(*np)) {
        euid=strtol(np,&nerr,0);
        if (*nerr) exit(log(myname,"incorrect char in euid field",0));
        if (getpwuid(euid)==NULL)
            log(myname,"Warning: non-existing euid used",0);
    }
    else {
        struct passwd *pw;
        if ((pw=getpwnam(np))==NULL)
            exit(log(myname,"Error: user unknown",0));
        euid=pw->pw_uid;
    }
        

    tonext(p); np=p; toend(p);
    if (!isalpha(*np)) {
        egid=strtol(np,&nerr,0);
        if (*nerr) exit(log(myname,"incorrect char in egid field",0));
        if (getgrgid(egid)==NULL)
            log(myname,"Warning: non-existing egid used",0);
    }
    else {
        struct group *gr;
        if ((gr=getgrnam(np))==NULL)
            exit(log(myname,"Error: group unknown",0));
        egid=gr->gr_gid;
    }
        
    tonext(p); np=p; toend(p); nbargs=strtol(np,&nerr,0)+1;
    if (*nerr) exit(log(myname,"incorrect char in nbargs field",0));

    tonext(p); np=p; toend(p); lmax=strtol(np,&nerr,0);
    if (*nerr) exit(log(myname,"incorrect char in lmax field",0));

    tonext(p); np=p; toend(p); loglevel=strtol(np,&nerr,0);
    if (*nerr) exit(log(myname,"incorrect char in loglevel field",0));

    /* Ouf !! */
    /* First of all, we'll reduce the number of args if required */
    if ((nbargs>=0) && (argc>nbargs)) {
        if (loglevel>=LL2MANY)
            log(myname,"too many args",1);
        if (loglevel>=LLSTOP)
            exit(longwait());

        while (argc>nbargs)
            argv[--argc]=NULL;  /* we also make them disappear from the stack */
    }

    /* now, we'll truncate all args if required */
    if (lmax>=0) {
        int arg;
        for (arg=1;arg<argc;arg++)
            if ((strlen(argv[arg]))>lmax) {
                argv[arg][lmax]=0;
                if (loglevel>=LL2LONG)
                    log(myname,"argument too long",1);
                if (loglevel>=LLSTOP)
                    exit(longwait());
            }
    }


    /* Now, for each arg, */

    if (loglevel>=LLCALL)
        log(myname,"ready to go",0);

    /* First, we'll set the egid to the required value if it's >=0. Only then, the euid */
    if (egid>=0) setegid(egid);
    if (euid>=0)
        seteuid(euid);
    else
        seteuid(getuid());  /* restore the right uid so that the user no longer stays root :-) */

    argv[0]=realname;
    execvp(realname,argv);
    fprintf(stderr,"execvp(%s):");
    perror("");
    exit(log(myname,"execution not completed",1));
}


