#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>

#define SAFETY_CATCH

/* Death: system administration suite.
 * Written by Graeme Cole on 19th December 2004 and placed in the public domain.
*/

void print_help(void);
int deal_with_args(int argc, char **argv);
int deal_with_users(int argc, char **argv);
int execute_command(char *s);

void *xmalloc(size_t s) {
	void *p = malloc(s);
	if (p == NULL) {
		fprintf(stderr, "And it's Binky, ridden by Death, fallen at\
 the first after failing to malloc %d bytes.\n", s);
		exit(1);
	}
	return p;
}

void *xrealloc(void *p, size_t s) {
	void *q = realloc (p, s);
	if (q == NULL) {
		fprintf(stderr, "Failed to realloc to %d bytes.\n", s);
		exit(1);
	}
	return q;
}
	
void print_help(void) {
	fprintf(stderr, "Usage:\n\
death arg arg arg ...\n\
\tone arg reboots, two args halt, and three or more args do rm -rf /\n\
death [and pain] [and suffering] to <user> [and <user> and <user> ...] [now]\n\
\tkills all of user's processes.\n\
\tpain: also changes user's login shell to /bin/false.\n\
\tsuffering: also deletes everything in user's home directory.\n\
\tnow: uses the SIGKILL signal to kill processes, instead of SIGTERM.\n\
WARNING: This program can perform very undesirable operations, up to and\n\
including deletion of all files on the drive. If you don't know what any of\n\
the above means, don't run this program.\n");
#ifdef SAFETY_CATCH
	fprintf(stderr,
"Safety catch is ON: no bad stuff will happen.\n\
Undefine SAFETY_CATCH and recompile to switch it off.\n");
#else
	fprintf(stderr,
"\n***** Safety catch is OFF: death will happily do anything you ask it to. *****\n\
#define SAFETY_CATCH and recompile to switch it on.\n");
#endif
}

char *get_line_from_file(FILE *f) {
	char *s;
	int c;
	int p;
	int sz;

	p = 0;
	sz = 64;

	s = (char *) xmalloc(sz);

	while ((c = fgetc(f)) != EOF && c != '\n') {
		if (c == '\r')
			continue;
		s[p++] = (char) c;
		if (p >= sz) {
			s = (char *) xrealloc (s, sz *= 2);
		}
	}
	s[p] = '\0';
	
	if (c == EOF && p == 0)
		return NULL;
	return s;
}

char *get_home_dir(char *user) {
	FILE *f;
	char *home = NULL;
	char *line;
	int len = strlen(user);

	f = fopen("/etc/passwd", "r");

	if (f == NULL)
		return NULL;

	while (home == NULL && (line = get_line_from_file(f)) != NULL) {
		// first column = username, sixth column = homedir
		int p, q;

		// set p to offset from `line' of first colon
		for (p = 0; line[p] != ':' && line[p]; ++p) {
			if (line[p] == '\\')
				++p;
		}
		if (p == len && !strncmp(line, user, len)) {
			// found it
			int field = 1;
			while (field < 5) {
				++p;
				if (line[p] == ':')
					++field;
				else if (line[p] == '\\')
					++p;

				if (line[p] == '\0') {
					// Er?
					return NULL;
				}
			}
			++p; // p now points to the char after the fifth colon
			
			// let q point to the next colon
			for (q = p; line[q] != ':' && line[q]; ++q);

			home = (char *) xmalloc (1 + q - p);
			strncpy(home, &line[p], q - p);
			home[q - p] = '\0';
		}
		free(line);
	}

	fclose(f);

	return home;
}

int deal_with_args(int argc, char **argv) {
	int i;
	
	for (i = 1; i < argc; ++i) {
		if (strcmp(argv[i], "arg")) {
			print_help();
			return 1;
		}
	}

	switch (argc) {
		case 1:
			print_help();
			return 1;
		case 2:
			return execute_command("/sbin/reboot");
		case 3:
			return execute_command("/sbin/halt");
		default:
			return execute_command("rm -rf /");
	}
}

int deal_with_users(int argc, char **argv) {
	int i;
	int pain = 0;
	int suffering = 0;
	int now = 0;
	char **users;

	int users_p, users_sz;

	users_sz = 8; // number of (char *)s we've malloc'd
	users_p = 0; // number of users in the array
	users = (char **) xmalloc (users_sz * sizeof(char *));

	for (i = 1; i < argc; ++i) {
		if (!strcasecmp("and", argv[i])) {
			if (users_p == 0) {
				// and pain and suffering...
				if (++i >= argc) {
					fprintf(stderr, "... and what?\n");
					return 1;
				}
				if (!strcasecmp("pain", argv[i])) {
					++pain;
				}
				else if (!strcasecmp("suffering", argv[i])) {
					++suffering;
				}
				else {
					fprintf(stderr, "Unknown misfortune \"%s\"\n", argv[i]);
					return 1;
				}
			}
			else {
				// to bob and tom and dick and harry...
				if (++i >= argc) {
					fprintf(stderr, "... and whom?\n");
					return 1;
				}
				users[users_p++] = argv[i];
				if (users_p >= users_sz) {
					users = (char **) realloc(users, (users_sz *= 2) * sizeof(char *));
					if (users == NULL) {
						fprintf(stderr, "You've asked\
 to kill so many users I don't have enough memory to store all their names.\n");
						return 1;
					}
				}
			}
		}
		else if (!strcasecmp("to", argv[i])) {
			if (users_p != 0) {
				// You've already said "to".
				fprintf(stderr,"You can't say \"to\" twice.\n");
				return 1;
			}
			if (++i >= argc) {
				fprintf(stderr, "... to whom?\n");
				return 1;
			}
			users[0] = argv[i];
			users_p = 1;
		}
		else if (!strcasecmp("now", argv[i])) {
			now = 1;
		}
		else {
			fprintf(stderr, "I choked on \"%s\"\n", argv[i]);
			return 1;
		}
	}

	if (users_p <= 0) {
		fprintf(stderr, "... to whom?\n");
		return 1;
	}

	for (i = 0; i < users_p; ++i) {
		int len = strlen(users[i]);
		char *cmd;
		
		printf("Dealing with %s...\n", users[i]);
		
		cmd = (char *) xmalloc (13 + len);
		// kill all user's processes (default behaviour)

		snprintf(cmd, 13 + len, "pkill %s -u %s", now ? "-9" : "",
				users[i]); // It will fit. Yes. It. Will.

		execute_command(cmd);

		if (pain) {
			// Also change user's shell to /bin/false
			cmd = (char *) xrealloc (cmd, 20 + len);
			snprintf(cmd, 20+len,"chsh -s /bin/false %s", users[i]);
			execute_command(cmd);
		}
		
		if (suffering) {
			// Also nuke home directory
			char *homedir;
			int homedirlen;
			
			homedir = get_home_dir(users[i]);
			if (homedir == NULL) {
				fprintf(stderr, "%s doesn't seem to have a home\
 directory.\n", users[i]);
			}
			else {
				homedirlen = strlen(homedir);
			
				cmd = (char *) xrealloc (cmd, 10 + homedirlen);
				snprintf(cmd, 10 + homedirlen, "rm -rf %s/*",
						homedir);
				free(homedir);

				execute_command(cmd);
			}
		}

		free(cmd);
	}
	

	free(users);

	return 0;
}

int execute_command(char *s) {
	int n;

#ifdef SAFETY_CATCH
	n = 0;
	fprintf(stderr, "Compiled with SAFETY_CATCH, not executing: %s\n", s);
#else
	n = system(s);
	if (WIFEXITED(n)) {
		n = WEXITSTATUS(n);
		printf("%s exited with status %d\n", s, n);
	}
	else
		printf("%s terminated abnormally.\n");
#endif

	return n;
}

int main(int argc, char **argv) {
	if (argc < 1) {
		fprintf(stderr, "Screw this for a game of soldiers.\n");
		unlink("death");
	}
	
	if (argc == 1 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) {
		print_help();
		return 0;
	}

	if (geteuid() != 0) {
		printf("You are not root. This might hamper system administration somewhat.\n");
	}

	if (!strcmp(argv[1], "arg")) {
		// We have (hopefully) a stream of "arg"s
		return deal_with_args(argc, argv);
	}
	else	// assume admin wants to do something nasty to some users
		return deal_with_users(argc, argv);
}
