/*
 *  sudo version 1.1 allows users to execute commands as root
 *  Copyright (C) 1991  The Root Group, Inc.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 1, or (at your option)
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 *  If you make modifications to the source, we would be happy to have
 *  them to include in future releases.  Feel free to send them to:
 *      Jeff Nieusma                       nieusma@rootgroup.com
 *      3959 Arbol CT                      (303) 447-8093
 *      Boulder, CO 80301-1752             
 *
********************************************************************************
* parse.c, sudo project
* David R. Hieb
* March 18, 1991
*
* routines to implement and maintain the parsing and list management.
*******************************************************************************/
#include <stdio.h>
#include <strings.h>
#include <ctype.h>

#include "sudo.h"

/* there are 3 main lists (User, Host_Alias, Cmnd_Alias) and 1 extra list */
#define NUM_LISTS 3+1

extern char *user, *host, *cmnd;
extern FILE *yyin, *yyout;

int user_list_found = FALSE;
int list_num, new_list[NUM_LISTS];
int parse_error = FALSE, found_user = FALSE;
int next_type, num_host_alias = 0, num_cmnd_alias = 0;
LINK tmp_ptr, reset_ptr, save_ptr, list_ptr[NUM_LISTS];

/*******************************************************************************
* inserts a node into list 'list_num' and updates list_ptr[list_num]
*******************************************************************************/
void insert_member(list_num, token_type, op_type, data_string)
int token_type, list_num;
char op_type;
char *data_string;
{
    tmp_ptr = (LINK)malloc(sizeof(LIST));
    tmp_ptr->type = token_type;
    tmp_ptr->op = op_type;
    tmp_ptr->data = (char *)malloc(strlen(data_string)+1);
    strcpy(tmp_ptr->data, data_string);
    tmp_ptr->next = (new_list[list_num] == TRUE) ? NULL : list_ptr[list_num];

    list_ptr[list_num] = list_ptr[EXTRA_LIST] = tmp_ptr;
}

/*******************************************************************************
* diagnostic list printing utility that prints list 'list_num'
*******************************************************************************/
void print_list(list_num)
int list_num;
{
LINK tmptmp_ptr;

tmptmp_ptr = list_ptr[list_num];

while (list_ptr[list_num] != NULL) {
    printf("type = %d, op = %c, data = %s\n", list_ptr[list_num]->type,
        list_ptr[list_num]->op, list_ptr[list_num]->data);
    tmp_ptr = list_ptr[list_num];
    list_ptr[list_num] = tmp_ptr->next;
    }
list_ptr[list_num] = tmptmp_ptr;
}

/*******************************************************************************
* delete list utility that deletes list 'list_num'
*******************************************************************************/
void delete_list(list_num)
int list_num;
{
while (list_ptr[list_num] != NULL) {
    tmp_ptr = list_ptr[list_num];
    list_ptr[list_num] = tmp_ptr->next;
/*  free(tmp_ptr);   */
    }
}

/*******************************************************************************
* this routine is what the lex/yacc code calls to build the different lists.
* once the lists are all built, control eventually returns to validate().
*******************************************************************************/
int call_back(token_type, op_type, data_string)
int token_type;
char op_type;
char *data_string;
{
/* all nodes start out in the extra list since the node name is received last */
list_num = EXTRA_LIST;

/*
 * if the last received node is TYPE1, then we can classify the list
 * and effectively transfer the extra list to the correct list type.
 */
if (token_type == TYPE1) {
    /* we have just build a "Host_Alias" list */
    if (strcmp(data_string, "Host_Alias") == 0) {
        list_num = HOST_LIST;
        if (num_host_alias > 0) {
            reset_ptr->next = list_ptr[HOST_LIST];
            }
        num_host_alias++;
        }
    /* we have just build a "Cmnd_Alias" list */
    else if (strcmp(data_string, "Cmnd_Alias") == 0) {
        list_num = CMND_LIST;
        if (num_cmnd_alias > 0) {
            reset_ptr->next = list_ptr[CMND_LIST];
            }
        num_cmnd_alias++;
        }
    /* we have just build a "User" list */
    else {
        list_num = USER_LIST;
        user_list_found = TRUE;
        }
    new_list[EXTRA_LIST] = TRUE;
    new_list[list_num] = FALSE;
    list_ptr[list_num] = list_ptr[EXTRA_LIST];
    }

/* actually link the new node into list 'list_num' */
insert_member(list_num, token_type, op_type, data_string);

if (new_list[list_num] == TRUE) {
    reset_ptr = list_ptr[list_num];
    new_list[list_num] = FALSE;
    }

/*
 * we process one user record at a time from the sudoers file. if we
 * find the user were looking for, we return to lex/yacc declaring 
 * that we have done so. otherwise, we reset the user list, delete the 
 * nodes and start over again looking for the user.
 */
if (user_list_found == TRUE) {
    if (list_ptr[list_num]->type == TYPE1 &&
        strcmp(list_ptr[list_num]->data, user) == 0) {
            return(FOUND_USER);
            }
        else {
            new_list[list_num] = TRUE;
            user_list_found = FALSE;
            delete_list(list_num);
            }
    }
return(NOT_FOUND_USER);
}

/*******************************************************************************
* this routine is called from cmnd_check() to resolve whether or not
* a user is permitted to perform a to-yet-be-determined command for
* a certain host name.
*******************************************************************************/
int host_type_ok()
{
/* check for the reserved keyword 'ALL'. if so, don't check the host name */
if (strcmp(list_ptr[USER_LIST]->data, "ALL") == 0) {
    return(TRUE);
    }
/* this case is the normal lowercase hostname */
else if (isupper(list_ptr[USER_LIST]->data[0]) == FALSE) {
    return(strcmp(list_ptr[USER_LIST]->data, host) == 0);
    }
/* by now we have a Host_Alias that will have to be expanded */
else {
    save_ptr = list_ptr[HOST_LIST];
    while (list_ptr[HOST_LIST] != NULL) {
        if ((list_ptr[HOST_LIST]->type == TYPE2) &&
            (strcmp(list_ptr[HOST_LIST]->data, 
                    list_ptr[USER_LIST]->data) == 0)) {
            next_type = list_ptr[HOST_LIST]->next->type;
            tmp_ptr = list_ptr[HOST_LIST]; 
            list_ptr[HOST_LIST] = tmp_ptr->next;
            while (next_type == TYPE3) {
                if (strcmp(list_ptr[HOST_LIST]->data, host) == 0) {
                    list_ptr[HOST_LIST] = save_ptr;
                    return(TRUE);
                    }
                if (list_ptr[HOST_LIST]->next != NULL) {
                    next_type = list_ptr[HOST_LIST]->next->type;
                    tmp_ptr = list_ptr[HOST_LIST];
                    list_ptr[HOST_LIST] = tmp_ptr->next;
                    }
                else {
                    next_type = ~TYPE3;
                    }
                }
            }
        else {
            tmp_ptr = list_ptr[HOST_LIST];
            list_ptr[HOST_LIST] = tmp_ptr->next;
            }
        }
    list_ptr[HOST_LIST] = save_ptr;
    return(FALSE);
    }
}

/*******************************************************************************
* this routine is called from cmnd_check() to resolve whether or not
* a user is permitted to perform a certain command on the already
* established host.
*******************************************************************************/
int cmnd_type_ok()
{
/* check for the reserved keyword 'ALL'. */
if (strcmp(list_ptr[USER_LIST]->data, "ALL") == 0) {
    /* if the command has an absolute path, let them do it */
    if (cmnd[0] == '/') {
        return(MATCH);
        }
    /* if the command does not have an absolute path, forget it */
    else {
        return(NO_MATCH);
        }
    }
/* if the command has an absolute path, check it out */
else if (list_ptr[USER_LIST]->data[0] == '/') {
    /* op  |   data   | return value
     *---------------------------------
     * ' ' | No Match | return(NO_MATCH) 
     * '!' | No Match | return(NO_MATCH) 
     * ' ' |  A Match | return(MATCH) 
     * '!' |  A Match | return(QUIT_NOW)
     *
     * these special cases are important in subtracting from the
     * Universe of commands in something like:
     *   user machine=ALL,!/bin/rm,!/etc/named ...
     */
    if (strcmp(list_ptr[USER_LIST]->data, cmnd) == 0) {
        if (list_ptr[USER_LIST]->op == '!') {
            return(QUIT_NOW);
            }
        else {
            return(MATCH);
            }
        }
    else {
        return(NO_MATCH);
        }
    }
/* by now we have a Cmnd_Alias that will have to be expanded */
else {
    save_ptr = list_ptr[CMND_LIST];
    while (list_ptr[CMND_LIST] != NULL) {
        if ((list_ptr[CMND_LIST]->type == TYPE2) &&
            (strcmp(list_ptr[CMND_LIST]->data, 
                    list_ptr[USER_LIST]->data) == 0)) {
            next_type = list_ptr[CMND_LIST]->next->type;
            tmp_ptr = list_ptr[CMND_LIST]; 
            list_ptr[CMND_LIST] = tmp_ptr->next;
            while (next_type == TYPE3) {
                if (strcmp(list_ptr[CMND_LIST]->data, cmnd) == 0) {
                    if (list_ptr[USER_LIST]->op == '!') {
                        list_ptr[CMND_LIST] = save_ptr;
                        return(QUIT_NOW);
                        }
                    else {
                        list_ptr[CMND_LIST] = save_ptr;
                        return(MATCH);
                        }
                    }
                if (list_ptr[CMND_LIST]->next != NULL) {
                    next_type = list_ptr[CMND_LIST]->next->type;
                    tmp_ptr = list_ptr[CMND_LIST];
                    list_ptr[CMND_LIST] = tmp_ptr->next;
                    }
                else {
                    next_type = ~TYPE3;
                    }
                }
            }
        else {
            tmp_ptr = list_ptr[CMND_LIST];
            list_ptr[CMND_LIST] = tmp_ptr->next;
            }
        }
    list_ptr[CMND_LIST] = save_ptr;
    return(NO_MATCH);
    }
}

/*******************************************************************************
* this routine is called from validate() after the call_back() routine
* has built all the possible lists. this routine steps thru the user list
* calling on host_type_ok() and cmnd_type_ok() trying to resolve whether
* or not the user will be able to execute the command on the host.
*******************************************************************************/
int cmnd_check()
{
int return_code;

while (list_ptr[USER_LIST] != NULL) {
    if ((list_ptr[USER_LIST]->type == TYPE2) && host_type_ok()) {
        next_type = list_ptr[USER_LIST]->next->type;
        tmp_ptr = list_ptr[USER_LIST]; 
        list_ptr[USER_LIST] = tmp_ptr->next;
        while (next_type == TYPE3) {
            return_code = cmnd_type_ok();
            if (return_code == MATCH) {
                return(VALIDATE_OK);
                }
            else if (return_code == QUIT_NOW) {
                return(VALIDATE_NOT_OK);
                }
            if (list_ptr[USER_LIST]->next != NULL) {
                next_type = list_ptr[USER_LIST]->next->type;
                tmp_ptr = list_ptr[USER_LIST];
                list_ptr[USER_LIST] = tmp_ptr->next;
                }
            else {
                next_type = ~TYPE3;
                }
            }
        }
    else {
        tmp_ptr = list_ptr[USER_LIST];
        list_ptr[USER_LIST] = tmp_ptr->next;
        }
    }
return(VALIDATE_NOT_OK);
}

/*******************************************************************************
* this routine is called from the sudo.c module and tries to validate
* the user, host and command triplet.
*******************************************************************************/
int validate()
{
FILE *sudoers_fp;
int i, return_code;

if ((sudoers_fp = fopen(SUDOERS, "r")) == NULL ) {
    perror(SUDOERS);
    log_error( NO_SUDOERS_FILE );
    exit(1);
    }

yyin = sudoers_fp;
yyout = stdout;

for (i = 0; i < NUM_LISTS; i++)
    new_list[i] = TRUE;

/*
 * yyparse() returns with one of 3 values: 0) yyparse() worked fine; 
 * 1) yyparse() failed; FOUND_USER) the user was found and yyparse()
 * was returned from prematurely.
 */
return_code = yyparse();

/* don't need to keep this open... */
(void) fclose (sudoers_fp);

/* if a parsing error occurred, set return_code accordingly */
if (parse_error == TRUE) {
    return_code = PARSE_ERROR;
    }

/* if the user was not found, set the return_code accordingly */
if (found_user == FALSE) {
    return_code = NOT_FOUND_USER;
    }

/* handle the 3 cases individually &*/
switch(return_code) {
    case FOUND_USER:
        return_code = cmnd_check();
        delete_list(USER_LIST);
        delete_list(HOST_LIST);
        delete_list(CMND_LIST);
        return(return_code);
        break;
    case NOT_FOUND_USER:    
        return(VALIDATE_NO_USER);
        break;
    case PARSE_ERROR: 
        return(VALIDATE_ERROR);
        break;
    }
}
