/*
 * Copyright 1999-2004 Gentoo Technologies, Inc.
 * Distributed under the terms of the GNU General Public License v2
 * $Header: /home/cvsroot/gentoo-projects/hardened/policycoreutils-extra/src/sestatus.c,v 1.10 2004/03/26 19:25:52 pebenito Exp $
 * Patch provided by Steve Grubb
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <selinux/selinux.h>
#include <selinux/get_default_type.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include <unistd.h>
#include <libgen.h>
#include <ctype.h>
#include <limits.h>

#define PROC_BASE "/proc"
#define MAX_CHECK 50
#define CONF "/etc/sestatus.conf"

/* conf file sections */
#define PROCS "[process]"
#define FILES "[files]"

/* buffer size for cmp_cmdline */
#define BUFSIZE 255

/* column to put the output (must be a multiple of 8) */
static unsigned int COL = 32;

extern char *selinux_mnt;

static int cmp_cmdline(const char *command, int pid)
{

	char buf[BUFSIZE];
	char filename[BUFSIZE];

	memset(buf, '\0', BUFSIZE);

	/* first read the proc entry */
	sprintf(filename, "%s/%d/exe", PROC_BASE, pid);

	if (readlink(filename, buf, BUFSIZE) < 0)
		return 0;

	if (buf[BUFSIZE - 1] != '\0')
		buf[BUFSIZE - 1] = '\0';

	/* check if this is the command we're looking for. */
	if (strcmp(command, buf) == 0)
		return 1;
	else
		return 0;
}

static int pidof(const char *command)
{
/* inspired by killall5.c from psmisc */
	char stackpath[PATH_MAX + 1], *p;
	DIR *dir;
	struct dirent *de;
	int pid, ret = -1, self = getpid();

	if (!(dir = opendir(PROC_BASE))) {
		perror(PROC_BASE);
		return -1;
	}

	/* Resolve the path if it contains symbolic links */
	p = realpath(command, stackpath);
	if (p)
		command = p;

	while ((de = readdir(dir)) != NULL) {
		errno = 0;
		pid = (int)strtol(de->d_name, (char **)NULL, 10);
		if (errno || pid == 0 || pid == self)
			continue;
		if (cmp_cmdline(command, pid)) {
			ret = pid;
			break;
		}
	}

	closedir(dir);
	return ret;
}

static void load_checks(char *pc[], int *npc, char *fc[], int *nfc)
{

	FILE *fp = fopen(CONF, "r");
	char buf[255], *bufp;
	int buf_len, section = -1;
	int proclen = strlen(PROCS);
	int filelen = strlen(FILES);

	if (fp == NULL) {
		printf("\nUnable to open %s.\n", CONF);
		return;
	}

	while (!feof(fp)) {
		if (!fgets(buf, sizeof buf, fp))
			break;

		buf_len = strlen(buf);
		if (buf[buf_len - 1] == '\n')
			buf[buf_len - 1] = 0;

		bufp = buf;
		while (*bufp && isspace(*bufp)) {
			bufp++;
			buf_len--;
		}

		if (*bufp == '#')
			/* skip comments */
			continue;

		if (*bufp) {
			if (!(*bufp))
				goto out;

			if (strncmp(bufp, PROCS, proclen) == 0)
				section = 0;
			else if (strncmp(bufp, FILES, filelen) == 0)
				section = 1;
			else {
				switch (section) {
				case 0:
					if (*npc >= MAX_CHECK)
						break;
					pc[*npc] =
					    (char *)malloc((buf_len) *
							   sizeof(char));
					if (!pc[*npc])
						break;
					memcpy(pc[*npc], bufp, buf_len);
					(*npc)++;
					bufp = NULL;
					break;
				case 1:
					if (*nfc >= MAX_CHECK)
						break;
					fc[*nfc] =
					    (char *)malloc((buf_len) *
							   sizeof(char));
					if (!fc[*nfc])
						break;
					memcpy(fc[*nfc], bufp, buf_len);
					(*nfc)++;
					bufp = NULL;
					break;
				default:
					/* ignore lines before a section */
					printf("Line not in a section: %s.\n",
					       buf);
					break;
				}
			}
		}
	}
      out:
	fclose(fp);
	return;
}

static void printf_tab(const char *outp)
{
	printf("%-*s", COL, outp);

}

int main(int argc, char **argv)
{
	/* these vars are reused several times */
	int rc, opt, i, c;
	char *context, *root_path;

	/* files that need context checks */
	char *fc[MAX_CHECK];
	char *cterm = ttyname(0);
	int nfc = 0;
	struct stat m;

	/* processes that need context checks */
	char *pc[MAX_CHECK];
	int npc = 0;

	/* booleans */
	char **bools;
	int nbool;

	int verbose = 0;
	int show_bools = 0;

	/* policy */
	const char *pol_name, *root_dir;
	char *pol_path;


	while (1) {
		opt = getopt(argc, argv, "vb");
		if (opt == -1)
			break;
		switch (opt) {
		case 'v':
			verbose = 1;
			break;
		case 'b':
			show_bools = 1;
			break;
		default:
			/* invalid option */
			printf("\nUsage: %s [OPTION]\n\n", basename(argv[0]));
			printf("  -v  Verbose check of process and file contexts.\n");
			printf("  -b  Display current state of booleans.\n");
			printf("\nWithout options, show SELinux status.\n");
			return -1;
		}
	}
	printf_tab("SELinux status:");
	rc = is_selinux_enabled();

	switch (rc) {
	case 1:
		printf("enabled\n");
		break;
	case 0:
		printf("disabled\n");
		return 0;
		break;
	default:
		printf("unknown (%s)\n", strerror(errno));
		return 0;
		break;
	}

	printf_tab("SELinuxfs mount:");
	if (selinux_mnt != NULL) {
		printf("%s\n", selinux_mnt);
	} else {
		printf("not mounted\n\n");
		printf("Please mount selinuxfs for proper results.\n");
		return -1;
	}

	printf_tab("SELinux root directory:");
	root_dir = selinux_path();
	if (root_dir == NULL) {
		printf("error (%s)\n", strerror(errno));
		return -1;
	}
	/* The path has a trailing '/' so duplicate to edit */
	root_path = strdup(root_dir);
	if (!root_path) {
		printf("malloc error (%s)\n", strerror(errno));
		return -1;
	}
	/* actually blank the '/' */
	root_path[strlen(root_path) - 1] = '\0';
	printf("%s\n", root_path);
	free(root_path);

	/* Dump all the path information */
	printf_tab("Loaded policy name:");
	pol_path = strdup(selinux_policy_root());
	if (pol_path) {
		pol_name = basename(pol_path);
		puts(pol_name);
		free(pol_path);
	} else {
		printf("error (%s)\n", strerror(errno));
	}

	printf_tab("Current mode:");
	rc = security_getenforce();
	switch (rc) {
	case 1:
		printf("enforcing\n");
		break;
	case 0:
		printf("permissive\n");
		break;
	default:
		printf("unknown (%s)\n", strerror(errno));
		break;
	}

	printf_tab("Mode from config file:");
	if (selinux_getenforcemode(&rc) == 0) {
		switch (rc) {
		case 1:
			printf("enforcing\n");
			break;
		case 0:
			printf("permissive\n");
			break;
		case -1:
			printf("disabled\n");
			break;
		}
	} else {
		printf("error (%s)\n", strerror(errno));
	}

	printf_tab("Policy MLS status:");
	rc = is_selinux_mls_enabled();
	switch (rc) {
		case 0:
			printf("disabled\n");
			break;
		case 1:
			printf("enabled\n");
			break;
		default:
			printf("error (%s)\n", strerror(errno));
			break;
	}

	printf_tab("Policy deny_unknown status:");
	rc = security_deny_unknown();
	switch (rc) {
		case 0:
			printf("allowed\n");
			break;
		case 1:
			printf("denied\n");
			break;
		default:
			printf("error (%s)\n", strerror(errno));
			break;
	}

	printf_tab("Memory protection checking:");
	rc = security_get_checkreqprot();
	switch (rc) {
		case 0:
			printf("actual (secure)\n");
			break;
		case 1:
			printf("requested (insecure)\n");
			break;
		default:
			printf("error (%s)\n", strerror(errno));
			break;
	}

	rc = security_policyvers();
	printf_tab("Max kernel policy version:");
	if (rc < 0)
		printf("unknown (%s)\n", strerror(errno));
	else
		printf("%d\n", rc);


	if (show_bools) {
		/* show booleans */
		if (security_get_boolean_names(&bools, &nbool) >= 0) {
			printf("\nPolicy booleans:\n");

			for (i = 0; i < nbool; i++) {
				if (strlen(bools[i]) + 1 > COL)
					COL = strlen(bools[i]) + 1;
			}
			for (i = 0; i < nbool; i++) {
				printf_tab(bools[i]);

				rc = security_get_boolean_active(bools[i]);
				switch (rc) {
				case 1:
					printf("on");
					break;
				case 0:
					printf("off");
					break;
				default:
					printf("unknown (%s)", strerror(errno));
					break;
				}
				c = security_get_boolean_pending(bools[i]);
				if (c != rc)
					switch (c) {
					case 1:
						printf(" (activate pending)");
						break;
					case 0:
						printf(" (inactivate pending)");
						break;
					default:
						printf(" (pending error: %s)",
						       strerror(errno));
						break;
					}
				printf("\n");

				/* free up the booleans */
				free(bools[i]);
			}
			free(bools);
		}
	}
	/* only show contexts if -v is given */
	if (!verbose)
		return 0;

	load_checks(pc, &npc, fc, &nfc);

	printf("\nProcess contexts:\n");

	printf_tab("Current context:");
	if (getcon(&context) >= 0) {
		printf("%s\n", context);
		freecon(context);
	} else
		printf("unknown (%s)\n", strerror(errno));

	printf_tab("Init context:");
	if (getpidcon(1, &context) >= 0) {
		printf("%s\n", context);
		freecon(context);
	} else
		printf("unknown (%s)\n", strerror(errno));

	for (i = 0; i < npc; i++) {
		rc = pidof(pc[i]);
		if (rc > 0) {
			if (getpidcon(rc, &context) < 0)
				continue;

			printf_tab(pc[i]);
			printf("%s\n", context);
			freecon(context);
		}
		free(pc[i]);
	}

	printf("\nFile contexts:\n");

	/* controlling term */
	printf_tab("Controlling terminal:");
	if (lgetfilecon(cterm, &context) >= 0) {
		printf("%s\n", context);
		freecon(context);
	} else {
		printf("unknown (%s)\n", strerror(errno));
	}

	for (i = 0; i < nfc; i++) {
		if (lgetfilecon(fc[i], &context) >= 0) {
			printf_tab(fc[i]);

			/* check if this is a symlink */
			if (lstat(fc[i], &m)) {
				printf
				    ("%s (could not check link status (%s)!)\n",
				     context, strerror(errno));
				freecon(context);
				free(fc[i]);
				continue;
			}
			if (S_ISLNK(m.st_mode)) {
				/* print link target context */
				printf("%s -> ", context);
				freecon(context);

				if (getfilecon(fc[i], &context) >= 0) {
					printf("%s\n", context);
					freecon(context);
				} else {
					printf("unknown (%s)\n",
					       strerror(errno));
				}
			} else {
				printf("%s\n", context);
				freecon(context);
			}
		}
		free(fc[i]);
	}

	return 0;
}
