/*
 * $Id: wrap.c,v 1.3 1996/01/29 17:13:42 daveho Exp paulr $ 
 * Setuid root wrapper script for cgi-bin scripts
 */

#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdarg.h>
#include <pwd.h>
#include <unistd.h>

#include "www.h"

/*
 * Function prototypes.
 */
static void sigchld_handler(int);
static int execute_program(const char *program_path, const char *program_name,
			   const char *post_args);

/*
 * Definitions.
 */
#define UID_ERR_MSG	\
	"You have no permission to run this program. Go away.\n%s\n"
#define AUTH_ERR_MSG	"Error authenticating password.\n%s\n"
#define AUTH_USER_MSG	"Error authenticating username.\n%s\n"
#define EXEC_ERR_MSG	"Error executing program.\n%s\n"

#define PIPE_READ	0
#define PIPE_WRITE	1

#define STAFF_CGI_BIN_PATH	"/usr/local/etc/httpd/cgi-bin/staff/"
#define SUB_CGI_BIN_PATH	"/usr/local/etc/httpd/cgi-bin/sub/"

/*
 * Variables.
 */
static volatile int child_exited = 0;
char *html_footer;

int main(void)
{
    char *post_args;
    char *user_name;
    char *user_password;
    char *root_password;
    char *program;
    char path[MAXPATHLEN];

    /*
     * Establish signal handler.
     */
    signal(SIGCHLD, sigchld_handler);

    /*
     * XXX Are these necessary?
     */
    signal(SIGINT, SIG_IGN);
    signal(SIGHUP, SIG_IGN);
    
    /*
     * Extract POST arguments.
     */
    post_args = get_post_args();
    if (!post_args) {
		print_html("Wrap: couldn't get arguments.\n");
		exit(1);
    }

    /*
     * See if there's an HTML footer we should add to error messages.
     */
    html_footer = getval(post_args, "htmlfooter");
    if (!html_footer) {
		html_footer = "";
    }

    /*
     * Only real uid == "www" is allowed.
     */
    if (!verify_real_uid("www")) {
		print_html(UID_ERR_MSG, html_footer);
		exit(1);
    }

    /*
     * If we were passed the root password then Encrypt it
     * and compare it to the actual password.
     * Otherwise we are being run by a subscriber so Encrypt
     * the passed password and compare it to
     * the actual password.  Print the invalid message
     * and exit if it doesn't match.
     */
    root_password = getval(post_args, "rootpasswd");
    user_password = getval(post_args, "password");
    user_name = getval(post_args, "username");
    if (!root_password) {
	if (!user_password) {
           print_html(AUTH_ERR_MSG, html_footer);
           exit(1);
	}
        if (!user_name) {
           print_html(AUTH_USER_MSG, html_footer);
           exit(1);
        }
	if (!verify_password(user_password, user_name)) {
	   int ruid = getuid();
	   int euid = geteuid();
	   char t[20];
	   sprintf(t, "RealUID %d\n EUID %d\n", ruid, euid);
           print_html(AUTH_ERR_MSG, t, html_footer);
           exit(1);
        }   
    }
    else {
	if (!verify_password(root_password, "root")) {
	   print_html(AUTH_ERR_MSG, html_footer);
	   exit(1);
	}
    }

    /*
     * Execute the passed cgi-bin program; print error message
     * on failure.
     */
    program = getval(post_args, "program");
    if (!program) {
		print_html(EXEC_ERR_MSG, html_footer);
		exit(1);
    }
    if (*program == '/') {
		/*
		 * We don't want a path encoded.
		 */
		print_html(EXEC_ERR_MSG, html_footer);
		exit(1);
    }
    /*
     * If root password was given only execute things out of the 
     * staff cgi-bin directory
     */
    if (!user_password) {
    	strcpy(path, STAFF_CGI_BIN_PATH);
    	strcat(path, program);
    }
    /*
     * Otherwise, we are a subscriber so execute from the subscriber
     * cgi-bin directory
     */
    else {
	strcpy(path, SUB_CGI_BIN_PATH);
    	strcat(path, program);
    }

    if (!execute_program(path, program, post_args)) {
		print_html(EXEC_ERR_MSG, html_footer);
		exit(1);
    }

    return 0;
}

static void sigchld_handler(int na)
{
    child_exited = 1;
}

/*
 * Execute given program with given POST args.  Returns 1 on
 * succesful fork, 0 otherwise.
 * Note that error conditions in the forked child are handled by
 * printing an error message, since once the child is created
 * it should print all of the HTML output.
 */
static int execute_program(const char *program_path, const char *program_name,
			   const char *post_args)
{
    int fd[2];
    pid_t pid;

    /*
     * Open a pipe for sending args to child.
     */
    if (pipe(fd) < 0) {
	return 0;
    }

    /*
     * Fork child.
     */
    switch (pid = fork()) {
     case -1:
	/*
	 * Fork failed.
	 */
	return 0;

     case 0:
	/*
	 * In child: close writing end of pipe.
	 */
	close(fd[PIPE_WRITE]);

	if (fd[PIPE_READ] != STDIN_FILENO) {
	    if (dup2(fd[PIPE_READ], STDIN_FILENO) < 0) {
		print_html(EXEC_ERR_MSG, html_footer);
		exit(1);
	    } else {
		close(fd[PIPE_READ]); /* stdin is now the pipe */
	    }
	}

	/*
	 * NOW we can execute the program, which will read its
	 * args from stdin.
	 */
	if (execl(program_path, program_name, (char *)0) < 0) {
	    print_html(EXEC_ERR_MSG, html_footer);
	    exit(1);
	}
	/* control never gets here */

     default:
	/*
	 * In parent: close reading end of pipe.
	 */
	close(fd[PIPE_READ]);

	/*
	 * Write POST args to pipe.
	 */
	write(fd[PIPE_WRITE], post_args, strlen(post_args));
	close(fd[PIPE_WRITE]);

	/*
	 * Wait for child to finish.
	 */
	if (!child_exited) {
	    wait(NULL);
	}

	break;
    }

    return 1;
}

