/* 3000menu2.c */
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <pwd.h>
#include <errno.h>

#define BUFFER_SIZE 1<<16
#define ARR_SIZE 1<<16
#define COMM_SIZE 32

char *menu[] = {
        "Change user",
        "pinfo",
        "/usr/bin/ls",
        "/usr/bin/ps",
        "/usr/bin/id",
        "/usr/bin/top",
        "QUIT",
        NULL
};
        
pid_t process_exited = 0;
int process_exited_status = 0;
char *become_user = NULL;

extern char **environ;

void make_args(char *program, char *buffer, char **args, 
                size_t args_size, size_t *nargs)
{
        char *maybe_args[args_size];
        char **next_arg, *rest_of_buffer;
        size_t i, j;
        
        rest_of_buffer = buffer;
        maybe_args[0] = program;
        args[0] = program;

        for (next_arg = maybe_args + 1;
             (*next_arg = strsep(&rest_of_buffer, " \n\t")) != NULL;
             next_arg++) {
                
                if (*next_arg != NULL) {
                        if (next_arg >= &maybe_args[args_size])
                                break;
                } 
        }

        j = 0;
        for (i=0; maybe_args[i] != NULL; i++) {
                if (strlen(maybe_args[i]) > 0) {
                        args[j] = maybe_args[i];
                        j++;
                }
        }
        
        *nargs = j;
        args[j] = NULL;
}

void pinfo(void)
{
        printf("uid=%d, euid=%d, gid=%d, egid=%d\n",
               getuid(), geteuid(), getgid(), getegid());
}

int become(char *username)
{
        int result;
        struct passwd *pw_entry;

        pw_entry = getpwnam(username);
        if (pw_entry == NULL) {
                fprintf(stderr, "Could not find user %s.\n", username);
                return -1;
        }
                       
        result = setgid(pw_entry->pw_gid);
        if (result != 0) {
                fprintf(stderr, "Failed to change to gid %d\n",
                        pw_entry->pw_gid);
                return -2;
        }

        result = setuid(pw_entry->pw_uid);
        if (result != 0) {
                fprintf(stderr, "Failed to change to uid %d\n",
                        pw_entry->pw_uid);
                return -3;
        }

        return 0;
}

void run_program(char *args[], int background, char *stdout_fn,
                 char *envp[], char *new_user)
{
        pid_t pid;
        int result;
        int fd;

        pid = fork();
        if (pid) {
                if (background) {
                        fprintf(stderr,
                                "Process %d running in the background.\n",
                                pid);
                        return;
                } else {
                        pid = wait(NULL);
                }
        } else {
                if (new_user) {
                        result = become(new_user);
                        if (result != 0) {
                                fprintf(stderr, "Error becoming %s, aborting\n",
                                        new_user);
                                return;
                        }
                } else {
                        if (setegid(getgid())) {
                                fprintf(stderr,
                                        "Dropping of group privileges failed.\n");
                        }
                        if (seteuid(getuid())) {
                                fprintf(stderr,
                                        "Dropping of user privileges failed.\n");
                        }
                }
                
                if (stdout_fn != NULL) {
                        fd = creat(stdout_fn, 0666);
                        dup2(fd, 1);
                        close(fd);
                }
                
                if (execve(args[0], args, envp)) {
                        puts(strerror(errno));
                        exit(127);
                }
        }
}

void run_chosen_program(int choice)
{
        char *input = NULL;
        char *all_args[ARR_SIZE];
        int result;
        int background = 0;
        size_t n, nargs;
        char *program = menu[choice];
        
        printf("Run in background (Y/N)? ");
        result = getline(&input, &n, stdin);

        if (strcmp(input, "Y\n") == 0) {
                background = 1;
        }

        free(input);
        input = NULL;
        
        printf("Enter arguments for %s: ", menu[choice]);
        result = getline(&input, &n, stdin);

        if (result > 0) {
                make_args(program, input, all_args, ARR_SIZE, &nargs);
        } else {
                all_args[0] = program;
                all_args[1] = '\0';
        }
        
        if (background) {
               printf("Running %s in the background\n", program);
        } else {
               printf("Running %s\n", program);
        }
        
        run_program(all_args, background, NULL, environ, become_user);
                
        return;
}

int choose_program(void)
{
        int i = 0;
        int choice;
        char *input = NULL;
        size_t result, n = 0;

        printf("\nChoose an option:\n\n");
        
        while (menu[i] != NULL) {
                printf("%d. %s\n", i + 1, menu[i]);
                i++;
        }

        if (become_user) {
                printf("Running programs as user %s.\n", become_user);
        } else {
                printf("Running programs as you.\n");
        }

        printf("\nYour choice? ");

        result = getline(&input, &n, stdin);
        choice = atoi(input);
        free(input);

        if ((result > 0) && (choice > 0) && (choice <= i)) {
                return choice - 1;
        } else {
                return -1;
        }
}

void signal_handler(int the_signal)
{
        int pid, status;

        if (the_signal != SIGCHLD) {
                return;
        }

        pid = wait(&status);

        if (pid == -1) {
                return;
        }

        process_exited = pid;
        process_exited_status = WEXITSTATUS(status);
}

void setup_signal_handlers()
{
       struct sigaction signal_handler_struct;

        memset (&signal_handler_struct, 0, sizeof(signal_handler_struct));
        signal_handler_struct.sa_handler = signal_handler;
        signal_handler_struct.sa_flags = SA_RESTART;
        
        if (sigaction(SIGCHLD, &signal_handler_struct, NULL)) {
                fprintf(stderr, "Couldn't register SIGCHLD handler.\n");
        }
}

void handle_exited_process(void)
{
        if (process_exited) {
                if (WIFEXITED(process_exited_status)) {
                        fprintf(stderr, "\nProcess %d exited with status %d.\n",
                                process_exited,
                                WEXITSTATUS(process_exited_status));
                } else {
                        fprintf(stderr, "\nProcess %d aborted.\n",
                                process_exited);
                }

                process_exited = 0;
        }
}

void change_user(void)
{
        printf("Should change the user.");
}

void choice_loop(void)
{
        int choice;

        while (1) {
                handle_exited_process();
                
                choice = choose_program();

                if (choice == -1) {
                        printf("Invalid choice. Please try again.\n");
                } else if (strcmp("QUIT", menu[choice]) == 0) {
                        printf("Goodbye!\n");
                        return;
                } else if (strcmp("Change user", menu[choice]) == 0) {
                        change_user();
                } else if (strcmp("pinfo", menu[choice]) == 0) {
                        pinfo();
                } else {
                        run_chosen_program(choice);
                }
        }
}

int main()
{
        setup_signal_handlers();
        choice_loop();

        return 0;
}
