/*
   BiT '98
   completely rewritten for obvious reasons...
   first of all, it didnt work (sigsegv and shit)
   second.. my house got raided and everything was taken
   third.. i have nothing to do waiting for my phone line to return.......
 */

/*
   Killing processes with (e)uid==0 if they aint owned by root or anyone
   in /etc/rootusers, except if the process name is in /etc/suids
   
   bugfix: forgot a signal() in the SIGHUP handler, im to lazy to use 
 	   sigaction()
 */

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <netdb.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
#include <signal.h>
#include <pwd.h>
#include <time.h>

#define VERSION 	"0.6"
#define ROOTUSERS	"/etc/rootusers"
#define SUIDS		"/etc/suids"
#define PIDFILE		"/var/run/rootcheck.pid"
#define EMAIL		"bit@localhost" /* you might want to change this */
#define NETSTAT 	"/bin/netstat"
#define W 		"/usr/bin/w"
#define LAST		"/usr/bin/last"

struct rootcheck {
    char name[256];
    pid_t pid, ppid;
    uid_t uid, euid;
};

uid_t rootusers[16];		/* more then 16 roots?! */
char suids[64][256];		/* what about removing some root suids? */
int nrootusers, nsuids;

void sighup(int s);
void rehash(int status);
int okuid(uid_t uid);
void foreverloop(void);
struct rootcheck *getinfo(char *d_name);
struct rootcheck *trace_to_init(struct rootcheck par);
void checkup(char *d_name);
void mail_n_kill(struct rootcheck cur, struct rootcheck par);

void main(int argc, char **argv)
{
    int i;
    char buf[512];

    printf("rootcheck %s - BiT '98\n\n", VERSION);

    if (geteuid())
	printf("I prefer euid 0\n"), exit(-1);

    if ((i = open(PIDFILE, O_RDONLY)) != -1) {
	read(i, buf, sizeof(buf));
	if (kill(atoi(buf), SIGCHLD) != -1)
	    printf("Allready running.\n"), exit(-1);
	close(i);
    }
    printf("Loading init files...");
    rehash(0);
    printf("done\nSetting up HUP handler and detaching...");
    fflush(stdout);

    signal(SIGHUP, (void *) &sighup);
    if ((i = fork()) == -1)
	printf("can't fork()?!\n"), exit(-1);
    if (i)
	putchar('\n'), close(0), close(1), close(2), exit(-1);

    if ((i = open(PIDFILE, O_WRONLY | O_CREAT)) != -1) {
	sprintf(buf, "%d", getpid());
	write(i, buf, strlen(buf));
	close(i);
    }
    foreverloop();
}

void foreverloop(void)
{
    DIR *procdir;
    struct dirent *pproc;
    struct stat sbuf;
    chdir("/proc");
    procdir = opendir(".");	/* if this doesnt work............ */

    while (1) {
	if ((pproc = readdir(procdir)) != NULL) {
	    if (stat(pproc->d_name, &sbuf) == -1)
		continue;
	    if (S_ISDIR(sbuf.st_mode))
		checkup(pproc->d_name);
	} else
	    rewinddir(procdir);
	usleep(10);		/* yaya lemme eat yer cpu */
    }
}

void checkup(char *d_name)
{
    struct rootcheck *p, cur, par;
    char buf[512];

    if (!strcmp(d_name, "scsi") || !strcmp(d_name, "net") || !strcmp(d_name, "sys")
	|| !strcmp(d_name, "self") || !strcmp(d_name, ".")
	|| !strcmp(d_name, "..") || !strcmp(d_name, "1"))
	return;


    if ((p = getinfo(d_name)) == NULL)
	return;
    memcpy(&cur, p, sizeof(struct rootcheck));

    if (cur.ppid == 1)
	return;

    if (cur.uid && cur.euid)
	return;

    if (oksuid(cur.name))
	return;

    if ((p = trace_to_init(cur)) == NULL)
	return;
    memcpy(&par, p, sizeof(struct rootcheck));

    if (okuid(par.uid))
	return;

    mail_n_kill(cur, par);
}

void mail_n_kill(struct rootcheck cur, struct rootcheck par)
{
    FILE *fd;
    char buf[256], fn[256];
    struct passwd *pwd;

    unsetenv("IFS");
    srand(time(NULL));
    pwd = getpwuid(par.uid);
    sprintf(fn, "/tmp/rc%d", rand());

    fd = fopen(fn, "w");
    sprintf(buf, "User %s got euid 0 from %s\n\nSystem data follows:\n\n",
	    pwd->pw_name, cur.name);
    fputs(buf, fd);
    fclose(fd);

    sprintf(buf, "%s -a -n >> %s", NETSTAT, fn);
    system(buf);

    sprintf(buf, "%s >> %s", W, fn);
    system(buf);

    sprintf(buf, "%s -20 >> %s", LAST, fn);
    system(buf);

    sprintf(buf, "cat %s | mail %s", fn, EMAIL);
    system(buf);

    unlink(fn);
    kill(par.pid, 9);		/* SPLAM! */
}

/* not really correct name, traces until parents uid is 0 */

struct rootcheck *trace_to_init(struct rootcheck par)
{
    static struct rootcheck *p, cur, old;
    char buf[512];

    sprintf(buf, "%d", par.ppid);
    if ((p = getinfo(buf)) == NULL)
	return NULL;
    memcpy(&old, &par, sizeof(struct rootcheck));
    memcpy(&cur, p, sizeof(struct rootcheck));

    while (1) {
	if (cur.uid == 0 && cur.euid == 0)
	    return &old;
	sprintf(buf, "%d", cur.ppid);
	if ((p = getinfo(buf)) == NULL)
	    return NULL;
	memcpy(&old, &cur, sizeof(struct rootcheck));
	memcpy(&cur, p, sizeof(struct rootcheck));
    }
}

int oksuid(char *owner)
{
    int i;

    for (i = 0; i < nsuids; i++)
	if (strcasecmp(suids[i], owner) == 0)
	    return 1;
    return 0;
}

int okuid(uid_t uid)
{
    int i;

    if (!uid)
	return 1;

    for (i = 0; i < nrootusers; i++) {
	if (rootusers[i] == uid)
	    return 1;
    }
    return 0;
}

struct rootcheck *getinfo(char *d_name)
{
    static struct rootcheck rc;
    char buf[512], fn[512], *p, i;
    FILE *fd;

    sprintf(fn, "%s/status", d_name);

    if ((fd = fopen(fn, "r")) == NULL)
	return NULL;

    if (fgets(buf, sizeof(buf), fd) == NULL)
	return NULL;
    buf[strlen(buf) - 1] = 0;
    strncpy(rc.name, (buf + 6), sizeof(rc.name));

    if (fgets(buf, sizeof(buf), fd) == NULL)
	return NULL;

    if (fgets(buf, sizeof(buf), fd) == NULL)
	return NULL;
    rc.pid = atoi((buf + 5));

    if (fgets(buf, sizeof(buf), fd) == NULL)
	return NULL;
    rc.ppid = atoi((buf + 6));

    if (fgets(buf, sizeof(buf), fd) == NULL)
	return NULL;

    p = buf + 5;
    for (i = 0; *(p + i) != '\t'; i++);
    *(p + i) = 0;
    rc.uid = atoi(p);

    p += i;
    p++;
    for (i = 0; *(p + i) != '\t'; i++);
    *(p + i) = 0;
    rc.euid = atoi(p);
    fclose(fd);

    return &rc;
}

void sighup(int s)
{
    signal(SIGHUP, (void *) &sighup);
    rehash(1);
}

void rehash(int status)
{
    FILE *fd;
    char buf[256];
    struct passwd *pwd;

    nsuids = nrootusers = 0;

    fd = fopen(ROOTUSERS, "r");
    if (fd == NULL && !status)
	printf("can't open %s..", ROOTUSERS);

    if (fd) {
	while (fgets(buf, sizeof(buf), fd) != NULL) {
	    if (buf[strlen(buf) - 1] == '\n')
		buf[strlen(buf) - 1] = 0;
	    if ((pwd = getpwnam(buf)) == NULL)
		break;
	    rootusers[nrootusers++] = pwd->pw_uid;
	}
	fclose(fd);
    }
    fd = fopen(SUIDS, "r");
    if (fd == NULL && !status)
	printf("can't open %s..", SUIDS);

    if (fd) {
	while (fgets(buf, sizeof(buf), fd) != NULL) {
	    if (buf[strlen(buf) - 1] == '\n')
		buf[strlen(buf) - 1] = 0;
	    buf[255] = 0;
	    strcpy(suids[nsuids++], buf);
	}
	fclose(fd);
    }
}
