/*
 * $Id: wrap.c,v 1.3 1996/01/29 17:13:42 daveho Exp $ 
 * 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 "infocom/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 EXEC_ERR_MSG	"Error executing program.\n%s\n"

#define PIPE_READ	0
#define PIPE_WRITE	1

#define CGI_BIN_PATH	"/usr/local/www/cgi-bin/"

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

int main(void)
{
    char *post_args;
    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);
    }

    /*
     * Encrypt the passed root password and compare it to
     * the actual root password.  Print the invalid message
     * and exit if it doesn't match.
     */
    root_password = getval(post_args, "rootpasswd");
    if (!root_password) {
	print_html(AUTH_ERR_MSG, html_footer);
	exit(1);
    }
    if (!verify_password(root_password, "root", 0)) {
	print_html(AUTH_ERR_MSG, html_footer);
	exit(1);
    }

    /*
     * Execute the passed cgi-bin program; print error message
     * on failure.
     * We assume that the program to be run is in the current
     * directory, which the server should set to be the cgi-bin
     * directory.
     */
    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);
    }
    /*
     * Only execute things out of the cgi-bin directory.
     */
    strcpy(path, 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;
}

