/*
 * Copyright (c) 1994 by the University of Southern California
 *
 * EXPORT OF THIS SOFTWARE from the United States of America may
 *     require a specific license from the United States Government.
 *     It is the responsibility of any person or organization contemplating
 *     export to obtain such a license before exporting.
 *
 * WITHIN THAT CONSTRAINT, permission to copy, modify, and distribute
 *     this software and its documentation in source and binary forms is
 *     hereby granted, provided that any documentation or other materials
 *     related to such distribution or use acknowledge that the software
 *     was developed by the University of Southern California.
 *
 * DISCLAIMER OF WARRANTY.  THIS SOFTWARE IS PROVIDED "AS IS".  The
 *     University of Southern California MAKES NO REPRESENTATIONS OR
 *     WARRANTIES, EXPRESS OR IMPLIED.  By way of example, but not
 *     limitation, the University of Southern California MAKES NO
 *     REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
 *     PARTICULAR PURPOSE. The University of Southern
 *     California shall not be held liable for any liability nor for any
 *     direct, indirect, or consequential damages with respect to any
 *     claim by the user or distributor of the ksu software.
 *
 * KSU was writen by:  Ari Medvinsky, ari@isi.edu
 */

#include "ksu.h"

static void auth_cleanup (FILE *, FILE *, char *);

krb5_boolean fowner(fp, uid)
    FILE *fp;
    uid_t uid;
{
    struct stat sbuf;

    /*
     * For security reasons, file must be owned either by
     * the user himself, or by root.  Otherwise, don't grant access.
     */
    if (fstat(fileno(fp), &sbuf)) {
	return(FALSE);
    }

    if ((sbuf.st_uid != uid) && sbuf.st_uid) {
	return(FALSE);
    }

    return(TRUE);
}

/*
 * Given a Kerberos principal "principal", and a local username "luser",
 * determine whether user is authorized to login according to the
 * authorization files ~luser/.k5login" and ~luser/.k5users.  Returns TRUE
 * if authorized, FALSE if not authorized.
 *
 */

krb5_error_code krb5_authorization(context, principal, luser,
				   cmd, ok, out_fcmd)
    /* IN */
    krb5_context context;
    krb5_principal principal;
    const char *luser;
    char *cmd;
    /* OUT */
    krb5_boolean *ok;
    char **out_fcmd;
{
    struct passwd *pwd;
    char *princname;
    int k5login_flag =0;
    int k5users_flag =0;
    krb5_boolean retbool =FALSE;
    FILE * login_fp = 0, * users_fp = 0;
    krb5_error_code retval = 0;
    struct stat st_temp;

    *ok =FALSE;

    /* no account => no access */
    if ((pwd = getpwnam(luser)) == NULL)
	return 0;

    retval = krb5_unparse_name(context, principal, &princname);
    if (retval)
	return retval;

#ifdef DEBUG
    printf("principal to be authorized %s\n", princname);
    printf("login file: %s\n", k5login_path);
    printf("users file: %s\n", k5users_path);
#endif

    k5login_flag = stat(k5login_path, &st_temp);
    k5users_flag = stat(k5users_path, &st_temp);

    /* k5login and k5users must be owned by target user or root */
    if (!k5login_flag){
    	if ((login_fp = fopen(k5login_path, "r")) == NULL)
	    return 0;
	if ( fowner(login_fp, pwd->pw_uid) == FALSE) {
	    fclose(login_fp);
	    return 0;
	}
    }

    if (!k5users_flag){
    	if ((users_fp = fopen(k5users_path, "r")) == NULL) {
	    return 0;
    	}
	if ( fowner(users_fp, pwd->pw_uid) == FALSE){
	    fclose(users_fp);
	    return 0;
    	}
    }

    if (auth_debug){
	fprintf(stderr,
		"In krb5_authorization: if auth files exist -> can access\n");
    }

#if 0
    if (cmd){
	if(k5users_flag){
	    return 0; /* if  kusers does not exist -> done */
	}else{
	    if(retval = k5users_lookup(users_fp,princname,
				       cmd,&retbool,out_fcmd)){
		auth_cleanup(users_fp, login_fp, princname);
		return retval;
	    }else{
		*ok =retbool;
		return retval;
	    }
	}
    }
#endif

    /* if either file exists,
       first see if the principal is in the login in file,
       if it's not there check the k5users file */

    if (!k5login_flag){
	if (auth_debug)
	    fprintf(stderr,
		    "In krb5_authorization: principal to be authorized %s\n",
		    princname);

	retval = k5login_lookup(login_fp,  princname, &retbool);
	if (retval) {
	    auth_cleanup(users_fp, login_fp, princname);
	    return retval;
	}
	if (retbool) {
	    if (cmd)
		*out_fcmd = xstrdup(cmd);
	}
    }

    if ((!k5users_flag) && (retbool == FALSE) ){
	retval = k5users_lookup (users_fp, princname,
				 cmd, &retbool, out_fcmd);
	if(retval) {
	    auth_cleanup(users_fp, login_fp, princname);
	    return retval;
	}
    }

    if (k5login_flag && k5users_flag){

	char * kuser =  (char *) xcalloc (strlen(princname), sizeof(char));
	if (!(krb5_aname_to_localname(context, principal,
				      strlen(princname), kuser))
	    && (strcmp(kuser, luser) == 0)) {
	    retbool = TRUE;
	}

	free(kuser);
    }

    *ok =retbool;
    auth_cleanup(users_fp, login_fp, princname);
    return 0;
}

/***********************************************************
k5login_lookup looks for princname in file fp. Spaces
before the princaname (in the file ) are not ignored
spaces after the princname are ignored. If there are
any tokens after the principal name  FALSE is returned.

***********************************************************/

krb5_error_code k5login_lookup (fp, princname, found)
    FILE *fp;
    char *princname;
    krb5_boolean *found;
{

    krb5_error_code retval;
    char * line;
    char * fprinc;
    char * lp;
    krb5_boolean loc_found = FALSE;

    retval = get_line(fp, &line);
    if (retval)
	return retval;

    while (line){
	fprinc = get_first_token (line, &lp);

	if (fprinc && (!strcmp(princname, fprinc))){
	    if( get_next_token (&lp) ){
		free (line);
		break;  /* nothing should follow princname*/
	    }
	    else{
		loc_found = TRUE;
		free (line);
		break;
	    }
	}

	free (line);

	retval = get_line(fp, &line);
	if (retval)
	    return retval;
    }


    *found = loc_found;
    return 0;

}

/***********************************************************
k5users_lookup looks for princname in file fp. Spaces
before the princaname (in the file ) are not ignored
spaces after the princname are ignored.

authorization alg:

if princname is not found return false.

if princname is found{
	 if cmd == NULL then the file entry after principal
			name must be nothing or *

	 if cmd !=NULL  then entry must be matched (* is ok)
}


***********************************************************/
krb5_error_code k5users_lookup (fp, princname, cmd, found, out_fcmd)
    FILE *fp;
    char *princname;
    char *cmd;
    krb5_boolean *found;
    char **out_fcmd;
{
    krb5_error_code retval;
    char * line;
    char * fprinc, *fcmd;
    char * lp;
    char * loc_fcmd = NULL;
    krb5_boolean loc_found = FALSE;

    retval = get_line(fp, &line);
    if (retval)
	return retval;

    while (line){
	fprinc = get_first_token (line, &lp);

	if (fprinc && (!strcmp(princname, fprinc))){
	    fcmd = get_next_token (&lp);

	    if ((fcmd) && (!strcmp(fcmd, PERMIT_ALL_COMMANDS))){
		if (get_next_token(&lp) == NULL){
		    loc_fcmd =cmd ? xstrdup(cmd): NULL;
		    loc_found = TRUE;
		}
		free (line);
		break;
	    }

	    if (cmd == NULL){
		if (fcmd == NULL)
		    loc_found = TRUE;
		free (line);
		break;

	    }else{
		if (fcmd != NULL) {
		    char * temp_rfcmd, *err;
		    krb5_boolean match;
		    do {
			if(match_commands(fcmd,cmd,&match,
					  &temp_rfcmd, &err)){
			    if (auth_debug){
				fprintf(stderr,"%s",err);
			    }
			    loc_fcmd = err;
			    break;
			}else{
			    if (match == TRUE){
				loc_fcmd = temp_rfcmd;
				loc_found = TRUE;
				break;
			    }
			}

		    }while ((fcmd = get_next_token( &lp)));
		}
		free (line);
		break;
	    }
	}

	free (line);

	retval = get_line(fp, &line);
	if (retval) { 
	    return retval;
	}
    }

    *out_fcmd = loc_fcmd;
    *found = loc_found;
    return 0;

}


/***********************************************
fcmd_resolve -
takes a command specified .k5users file and
resolves it into a full path name.

************************************************/

krb5_boolean fcmd_resolve(fcmd, out_fcmd, out_err)
    char *fcmd;
    char ***out_fcmd;
    char **out_err;
{
    char * out_path;
    char * err;
    char ** tmp_fcmd;
    char * path_ptr, *path;
    char * lp, * tc;
    int i=0;

    tmp_fcmd = (char **) xcalloc (MAX_CMD, sizeof(char *));

    if (*fcmd == '/'){  /* must be full path */
	tmp_fcmd[0] = xstrdup(fcmd);
	tmp_fcmd[1] = NULL;
	*out_fcmd = tmp_fcmd;
	return TRUE;
    }else{
	/* must be either full path or just the cmd name */
	if (strchr(fcmd, '/')){
	    err = (char *) xcalloc((strlen(fcmd) +200) ,sizeof(char));
	    sprintf(err,"Error: bad entry - %s in %s file, must be either full path or just the cmd name\n", fcmd, KRB5_USERS_NAME);
	    *out_err = err;
	    return FALSE;
	}

#ifndef CMD_PATH
	err = (char *) xcalloc(2*(strlen(fcmd) +200) ,sizeof(char));
	sprintf(err,"Error: bad entry - %s in %s file, since %s is just the cmd name, CMD_PATH must be defined \n", fcmd, KRB5_USERS_NAME, fcmd);
	*out_err = err;
	return FALSE;
#else

	path = xstrdup (CMD_PATH);
	path_ptr = path;

	while ((*path_ptr == ' ') || (*path_ptr == '\t')) path_ptr ++;

	tc = get_first_token (path_ptr, &lp);

	if (! tc){
	    err = (char *) xcalloc((strlen(fcmd) +200) ,sizeof(char));
	    sprintf(err,"Error: bad entry - %s in %s file, CMD_PATH contains no paths \n",  fcmd, KRB5_USERS_NAME);
	    *out_err = err;
	    return FALSE;
	}

	i=0;
	do{
	    if (*tc != '/'){  /* must be full path */
		err = (char *) xcalloc((strlen(tc) +200) ,sizeof(char));
		sprintf(err,"Error: bad path %s in CMD_PATH for %s must start with '/' \n",tc, KRB5_USERS_NAME );
		*out_err = err;
		return FALSE;
	    }

	    out_path = (char *) xmalloc(strlen(tc) + strlen(fcmd) + 2);
	    sprintf(out_path,"%s/%s",tc, fcmd );

	    tmp_fcmd[i] = out_path;

	    i++;

	} while((tc = get_next_token (&lp)));

	tmp_fcmd[i] = NULL;
	*out_fcmd = tmp_fcmd;
	return TRUE;

#endif /* CMD_PATH */
    }
}

/********************************************
cmd_single - checks if cmd consists of a path
	     or a single token

********************************************/

krb5_boolean cmd_single(cmd)
    char * cmd;
{

    if ( ( strrchr( cmd, '/')) ==  NULL){
	return TRUE;
    }else{
	return FALSE;
    }
}

/********************************************
cmd_arr_cmp_postfix - compares a command with the postfix
         of fcmd
********************************************/

int cmd_arr_cmp_postfix(fcmd_arr, cmd)
    char **fcmd_arr;
    char *cmd;
{
    char  * temp_fcmd;
    char *ptr;
    int result =1;
    int i = 0;

    while(fcmd_arr[i]){
	if ( (ptr = strrchr( fcmd_arr[i], '/')) ==  NULL){
	    temp_fcmd = fcmd_arr[i];
	}else {
	    temp_fcmd = ptr + 1;
	}

	result = strcmp (temp_fcmd, cmd);
	if (result == 0){
	    break;
	}
	i++;
    }

    return result;


}

/**********************************************
cmd_arr_cmp - checks if cmd matches any
              of the fcmd entries.

**********************************************/

int cmd_arr_cmp (fcmd_arr, cmd)
    char **fcmd_arr;
    char *cmd;
{
    int result =1;
    int i = 0;

    while(fcmd_arr[i]){
	result = strcmp (fcmd_arr[i], cmd);
	if (result == 0){
	    break;
	}
	i++;
    }
    return result;
}


krb5_boolean find_first_cmd_that_exists(fcmd_arr, cmd_out, err_out)
    char **fcmd_arr;
    char **cmd_out;
    char **err_out;
{
    struct stat st_temp;
    int i = 0;
    krb5_boolean retbool= FALSE;
    int j =0;
    char * err;
    unsigned int max_ln=0;
    unsigned int tln=0;

    while(fcmd_arr[i]){
	tln = strlen(fcmd_arr[i]);
	if ( tln > max_ln) max_ln = tln;
	if (!stat (fcmd_arr[i], &st_temp )){
	    *cmd_out = xstrdup(fcmd_arr[i]);
	    retbool = TRUE;
	    break;
	}
	i++;
    }

    if (retbool == FALSE ){
	err = (char *) xcalloc((80 + (max_ln+2)*i) ,sizeof(char));
	strcpy(err,"Error: not found -> ");
	for(j= 0; j < i; j ++){
	    strcat(err, " ");
	    strcat(err, fcmd_arr[j]);
	    strcat(err, " ");
	}
	strcat(err, "\n");
	*err_out = err;
    }


    return retbool;
}

/***************************************************************
returns 1 if there is an error, 0 if no error.

***************************************************************/

int match_commands (fcmd, cmd, match, cmd_out, err_out)
    char *fcmd;
    char *cmd;
    krb5_boolean *match;
    char **cmd_out;
    char **err_out;
{
    char ** fcmd_arr;
    char * err;
    char * cmd_temp;

    if(fcmd_resolve(fcmd, &fcmd_arr, &err )== FALSE ){
	*err_out = err;
	return 1;
    }

    if (cmd_single( cmd ) == TRUE){
	if (!cmd_arr_cmp_postfix(fcmd_arr, cmd)){ /* found */

	    if(find_first_cmd_that_exists( fcmd_arr,&cmd_temp,&err)== TRUE){
		*match = TRUE;
		*cmd_out = cmd_temp;
		return 0;
	    }else{
		*err_out = err;
		return 1;
	    }
	}else{
	    *match = FALSE;
	    return 0;
	}
    }else{
	if (!cmd_arr_cmp(fcmd_arr, cmd)){  /* found */
	    *match = TRUE;
	    *cmd_out = xstrdup(cmd);
	    return 0;
	} else{
	    *match = FALSE;
	    return 0;
	}
    }

}

/*********************************************************
   get_line - returns a line of any length.  out_line
	      is set to null if eof.
*********************************************************/

krb5_error_code get_line (fp, out_line)
    /* IN */
    FILE *fp;
    /* OUT */
    char **out_line;
{
    char * line, *r, *newline , *line_ptr;
    int chunk_count = 1;

    line = (char *) xcalloc (BUFSIZ, sizeof (char ));
    line_ptr = line;
    line[0] = '\0';

    while (( r = fgets(line_ptr, BUFSIZ , fp)) != NULL){
	newline = strchr(line_ptr, '\n');
	if (newline) {
	    *newline = '\0';
	    break;
	}
	else {
	    chunk_count ++;
	    if(!( line = (char *) realloc( line,
					   chunk_count * sizeof(char) * BUFSIZ))){
		return  ENOMEM;
	    }

	    line_ptr = line + (BUFSIZ -1) *( chunk_count -1) ;
	}
    }

    if ((r == NULL) && (strlen(line) == 0)) {
	*out_line = NULL;
    }
    else{
	*out_line = line;
    }

    return 0;
}

/*******************************************************
get_first_token -
Expects a '\0' terminated input line .
If there are any spaces before the first token, they
will be returned as part of the first token.

Note: this routine reuses the space pointed to by line
******************************************************/

char *  get_first_token (line, lnext)
    char *line;
    char **lnext;
{

    char * lptr, * out_ptr;


    out_ptr = line;
    lptr = line;
    
    while (( *lptr == ' ') || (*lptr == '\t')) lptr ++;
    
    if (strlen(lptr) == 0) return NULL;
    
    while (( *lptr != ' ') && (*lptr != '\t') && (*lptr != '\0')) lptr ++;
    
    if (*lptr == '\0'){
	*lnext = lptr;
    } else{
	*lptr = '\0';
	*lnext = lptr + 1;
    }
    
    return out_ptr;
}
/**********************************************************
get_next_token -
returns the next token pointed to by *lnext.
returns NULL if there is no more tokens.
Note: that this function modifies the stream
      pointed to by *lnext and does not allocate
      space for the returned tocken. It also advances
      lnext to the next tocken.
**********************************************************/

char *  get_next_token (lnext)
    char **lnext;
{
    char * lptr, * out_ptr;


    lptr = *lnext;

    while (( *lptr == ' ') || (*lptr == '\t')) lptr ++;

    if (strlen(lptr) == 0) return NULL;

    out_ptr = lptr;

    while (( *lptr != ' ') && (*lptr != '\t') && (*lptr != '\0')) lptr ++;

    if (*lptr == '\0'){
	*lnext = lptr;
    } else{
	*lptr = '\0';
	*lnext = lptr + 1;
    }

    return out_ptr;
}

static void auth_cleanup(users_fp, login_fp, princname)
    FILE *users_fp;
    FILE *login_fp;
    char *princname;
{

    free (princname);
    if (users_fp)
	fclose(users_fp);
    if (login_fp)
	fclose(login_fp);
}

void init_auth_names(pw_dir)
    char *pw_dir;
{
    if (strlen (k5login_path) + 2 + strlen (KRB5_LOGIN_NAME) >= MAXPATHLEN) {
	fprintf (stderr,
		 "home directory name `%s' too long, can't search for .k5login\n",
		 pw_dir);
	exit (1);
    }
    if ((strlen(pw_dir) == 1) && (*pw_dir == '/')){
	sprintf(k5login_path,"%s%s", pw_dir, KRB5_LOGIN_NAME);
	sprintf(k5users_path,"%s%s", pw_dir, KRB5_USERS_NAME);
    } else {
	sprintf(k5login_path,"%s/%s", pw_dir, KRB5_LOGIN_NAME);
	sprintf(k5users_path,"%s/%s", pw_dir, KRB5_USERS_NAME);
    }
}
