/* Anders Brander <anders@brander.dk> 2004-02-10

Simple debugger for vchkpw (and other checkpassword utilities).

Shares no code with vpopmail or any other checkpassword util. This is on purpose
to keep us from falling into the "check the implementation with the
implementation" syndrome :)

Use like:
vd -h

or you could:
vd -l test@example.com -p TESTPW -u 89 -g 89

or maybe:
vd -l test@example.com

and even:
vd -v -c /usr/src/vpopdev/vchkpw -l test@example.com

or how about:
vd -c /bin/checkpassword -l normaluser

Enjoy :)

*/

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <pwd.h>
#include <grp.h>
#include <sys/types.h>
#include <sys/wait.h>

extern char **environ;

char *
readline(char *text)
{
	char *buf;
	
	buf = (char *) malloc(4096); /* yep, this is intended, maybe someone would like to overflow vchkpw */
	printf("%s: ", text);
	memset(buf, 0, 4096);
	scanf("%s", buf);
	buf[4095]='\0';
	return(buf);
}

int
main(int argc, char **argv)
{
	int verbosity = 0;
	int do_not_exit = 0;
	char *username=NULL, *password=NULL, *vchkpw="/home/vpopmail/bin/vchkpw";
	char *remote_ip=NULL, *local_port=NULL;
	int fd[2], status;
	pid_t child;
	char c;
	gid_t gid = -1; /* trouble? */
	uid_t uid = -1; /* trouble? */
	if (argv[0][0]=='.')
	{
		/* we need to be able to call ourself back! */
		printf("We _MUST_ be called with full path, or placed in $PATH!\n");
		exit(1);
	}
	
	while((c = getopt(argc, argv, "vu:g:l:p:dCc:R:L:h?")) >= 0)
	{
		switch(c)
		{
			case 'v': /* verbosity */
				verbosity++;
				break;
			case 'u': /* username/uid */
				if (optarg)
				{
					if (isalpha(optarg[0])) /* username */
					{
						struct passwd *pwd;
						pwd = getpwnam(optarg);
						if (pwd == NULL)
							perror("getpwnam");
						else
							uid = pwd->pw_uid;
					}
					else if (isdigit(optarg[0])) /* uid */
						uid = (uid_t) atoi(optarg);
				}
				break;
			case 'g': /* group/gid */
				if (optarg)
				{
					if (isalpha(optarg[0])) /* groupname */
					{
						struct group *grp;
						grp = getgrnam(optarg);
						if (grp == NULL)
							perror("getgrnam");
						else
							gid = grp->gr_gid;
					}
					else if (isdigit(optarg[0])) /* gid */
						gid = (gid_t) atoi(optarg);
				}
				break;
			case 'l': /* login */
				if (optarg)
					username = optarg;
				break;
			case 'p': /* password */
				if (optarg)
					password = optarg;
				break;
			case 'd': /* stay in infinite loop */
				do_not_exit = 1;
				break;
			case 'C': /* callback */
				{
					char *buf;
					int n=0;
					printf("\033[32m"); /* green tty-color */
					printf("*** CALLBACK\n");
					buf = (char *) malloc(4096);
					if (buf == NULL)
					{
						perror("malloc()");
						exit(1);
					}
					buf = getcwd(buf, 4096);
					printf("workdir: [%s]\n", buf);
					printf("uid: [%d]\n", getuid());
					printf("gid: [%d]\n", getgid());
					while(environ[n]!=NULL)
						printf("env: [%s]\n", environ[n++]);
					printf("*** CALLBACK EXITING\n");
					printf("\033[0m"); /* reset tty-color */
					free(buf);
					exit(0);
				}
				break;
			case 'c':
				if (optarg)
					vchkpw = optarg;
				break;
			case 'L': /* local port */
				if (optarg)
					local_port = optarg;
				break;
			case 'R': /* remote ip */
				if (optarg)
					remote_ip = optarg;
				break;
			case 'h': /* help */
			case '?':
			default:
				printf("Usage %s [options]\n", argv[0]);
				printf("            -v (increase verbosity)\n");
				printf("            -u uid/user (switch to other user before calling cvhkpw)\n");
				printf("            -g gid/group (switch to group before calling vchkpw)\n");
				printf("            -l login (sets the login used for vchkpw)\n");
				printf("            -p passwd (sets the password user for vchkpw)\n");
				printf("            -L port (sets TCPLOCALPORT to port for vchkpw)\n");
				printf("            -R ip (sets TCPREMOTEIP to ip for vchkpw)\n");
				printf("            -d (do not exit - enter infinite loop)\n");
				printf("            -c vchkpw (sets the path to vchkpw, defaults to %s)\n", vchkpw);
				printf("            -h (this message)\n");
				exit(0);
				break;
		}
	}

	if (gid != -1)
	{
		if (verbosity>0)
			printf("switching from gid %d to %d\n", getgid(), gid);
		if (setgid(gid)!=0)
			perror("setgid");
	}

	if (uid != -1)
	{
		if (verbosity>0)
			printf("switching from uid %d to %d\n", getuid(), uid);
		if (setuid(uid)!=0)
			perror("setuid");
	}

	pipe(fd);
	
	if (username == NULL)
		username = readline("Please enter username");
	if (password == NULL)
		password = readline("Please enter password");

	child = fork();
	
	if (child == -1) /* fork() failed?! */
	{
		perror("fork()");
		close(fd[0]);
		close(fd[1]);
		exit(1);
	}
	else if (child == 0)
	{	/* child process */
		int n=0;
		char *child_argv[5] = {vchkpw, argv[0], "-C", NULL};
		char *child_envp[3];
		if(remote_ip!=NULL)
		{
			child_envp[n] = (char *) malloc(strlen("TCPREMOTEIP=")+strlen(remote_ip)+1);
			sprintf(child_envp[n++], "TCPREMOTEIP=%s", remote_ip);
		}
		if(local_port!=NULL)
		{
			child_envp[n] = (char *) malloc(strlen("TCPLOCALPORT=")+strlen(local_port)+1);
			sprintf(child_envp[n++], "TCPLOCALPORT=%s", local_port);
		}
		child_envp[n] = NULL;
		dup2(fd[0], 3);
		close(fd[1]);
		execve(child_argv[0], child_argv, child_envp);
	}
	else
	{	/* parent */
		close(fd[0]);
		if (verbosity>0)
		{
			printf("sending \"%sNULL%sNULL0NULL\" (%d bytes) to vchkpw "
				"with uid/gid: %d/%d\n",
				username, password, (strlen(username)+strlen(password)+4),
				getuid(), getgid());
		}
		write(fd[1], username, strlen(username)); /* username */
		write(fd[1], "\0", 1);
		write(fd[1], password, strlen(password)); /* password */
		write(fd[1], "\0", 1);
		write(fd[1], "0", 2); /* dummy timestamp */
		close(fd[1]);
		if (!do_not_exit)
		{
			if (verbosity>0) printf("waiting...\n");
			waitpid(child, &status, 0);
			if (verbosity>0) printf("done\n");
			if (WIFEXITED(status))
			{
				if (verbosity>1)
					printf("normal exit from vchkpw\n");
				printf("vchkpw exit value: %d\n", WEXITSTATUS(status));
			}
			else if (WIFSIGNALED(status))
			{
				if (verbosity>1)
					printf("vchkpw exited from signal\n");
				printf("vchkpw exit signal: %d\n", WTERMSIG(status));
			}
			exit(0);
		}
		else
			while(1);
	}
}

