/* -*- mode: c; c-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * 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"
#include "adm_proto.h"
#include <sys/types.h>
#include <sys/stat.h>

/******************************************************************
krb5_cache_copy

gets rid of any expired tickets in the secondary cache,
copies the default cache into the secondary cache,

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

void show_credential();

/* modifies only the cc_other, the algorithm may look a bit funny,
   but I had to do it this way, since remove function did not come
   with k5 beta 3 release.
*/

krb5_error_code krb5_ccache_copy (context, cc_def, cc_other_tag,
                                  primary_principal, cc_out, stored, target_uid)
/* IN */
    krb5_context context;
    krb5_ccache cc_def;
    char *cc_other_tag;
    krb5_principal primary_principal;
    uid_t target_uid;
    /* OUT */
    krb5_ccache *cc_out;
    krb5_boolean *stored;
{
    int i=0;
    krb5_ccache  * cc_other;
    const char * cc_def_name;
    const char * cc_other_name;
    krb5_error_code retval=0;
    krb5_creds ** cc_def_creds_arr = NULL;
    krb5_creds ** cc_other_creds_arr = NULL;
    struct stat st_temp;

    cc_other = (krb5_ccache *)  xcalloc(1, sizeof (krb5_ccache));

    if ((retval = krb5_cc_resolve(context, cc_other_tag, cc_other))){
        com_err(prog_name, retval, _("resolving ccache %s"), cc_other_tag);
        return retval;
    }

    cc_def_name = krb5_cc_get_name(context, cc_def);
    cc_other_name = krb5_cc_get_name(context, *cc_other);

    if ( ! stat(cc_def_name, &st_temp)){
        if((retval = krb5_get_nonexp_tkts(context,cc_def,&cc_def_creds_arr))){
            return retval;
        }
    }

    *stored = krb5_find_princ_in_cred_list(context, cc_def_creds_arr,
                                           primary_principal);

    if (!lstat( cc_other_name, &st_temp))
        return EINVAL;

    if (krb5_seteuid(0)||krb5_seteuid(target_uid)) {
        return errno;
    }


    if ((retval = krb5_cc_initialize(context, *cc_other, primary_principal))){
        return retval;
    }

    retval = krb5_store_all_creds(context, * cc_other, cc_def_creds_arr,
                                  cc_other_creds_arr);

    if (cc_def_creds_arr){
        while (cc_def_creds_arr[i]){
            krb5_free_creds(context, cc_def_creds_arr[i]);
            i++;
        }
    }

    i=0;

    if(cc_other_creds_arr){
        while (cc_other_creds_arr[i]){
            krb5_free_creds(context, cc_other_creds_arr[i]);
            i++;
        }
    }

    *cc_out = *cc_other;
    return retval;
}


krb5_error_code krb5_store_all_creds(context, cc, creds_def, creds_other)
    krb5_context context;
    krb5_ccache cc;
    krb5_creds **creds_def;
    krb5_creds **creds_other;
{

    int i = 0;
    krb5_error_code retval = 0;
    krb5_creds ** temp_creds= NULL;


    if ((creds_def == NULL) && (creds_other == NULL))
        return 0;

    if ((creds_def == NULL) && (creds_other != NULL))
        temp_creds = creds_other;

    if ((creds_def != NULL) && (creds_other == NULL))
        temp_creds = creds_def;


    if (temp_creds){
        while(temp_creds[i]){
            if ((retval= krb5_cc_store_cred(context, cc,
                                            temp_creds[i]))){
                return retval;
            }
            i++;
        }
    }
    else { /* both arrays have elements in them */

        return  KRB5KRB_ERR_GENERIC;

/************   while(creds_other[i]){
                        cmp = FALSE;
                        j = 0;
                        while(creds_def[j]){
                           cmp = compare_creds(creds_other[i],creds_def[j]);

                           if( cmp == TRUE) break;

                           j++;
                        }
                        if (cmp == FALSE){
                                if (retval= krb5_cc_store_cred(context, cc,
                                                         creds_other[i])){
                                                return retval;
                                }
                        }
                        i ++;
                }

                i=0;
                while(creds_def[i]){
                        if (retval= krb5_cc_store_cred(context, cc,
                                                       creds_def[i])){
                                return retval;
                        }
                        i++;
                }

**************/
    }
    return 0;
}

krb5_boolean compare_creds(context, cred1, cred2)
    krb5_context context;
    krb5_creds *cred1;
    krb5_creds *cred2;
{
    krb5_boolean retval;

    retval = krb5_principal_compare (context, cred1->client, cred2->client);

    if (retval == TRUE)
        retval = krb5_principal_compare (context, cred1->server,                                                         cred2->server);

    return retval;
}




krb5_error_code krb5_get_nonexp_tkts(context, cc, creds_array)
    krb5_context context;
    krb5_ccache cc;
    krb5_creds ***creds_array;
{

    krb5_creds creds, temp_tktq, temp_tkt;
    krb5_creds **temp_creds;
    krb5_error_code retval=0;
    krb5_cc_cursor cur;
    int count = 0;
    int chunk_count = 1;

    if ( ! ( temp_creds = (krb5_creds **) malloc( CHUNK * sizeof(krb5_creds *)))){
        return ENOMEM;
    }


    memset(&temp_tktq, 0, sizeof(temp_tktq));
    memset(&temp_tkt, 0, sizeof(temp_tkt));
    memset(&creds, 0, sizeof(creds));

    /* initialize the cursor */
    if ((retval = krb5_cc_start_seq_get(context, cc, &cur))) {
        return retval;
    }

    while (!(retval = krb5_cc_next_cred(context, cc, &cur, &creds))){

        if ((retval = krb5_check_exp(context, creds.times))){
            if (retval != KRB5KRB_AP_ERR_TKT_EXPIRED){
                return retval;
            }
            if (auth_debug){
                fprintf(stderr,"krb5_ccache_copy: CREDS EXPIRED:\n");
                fputs("  Valid starting         Expires         Service principal\n",stdout);
                show_credential(context, &creds, cc);
                fprintf(stderr,"\n");
            }
        }
        else {   /* these credentials didn't expire */

            if ((retval = krb5_copy_creds(context, &creds,
                                          &temp_creds[count]))){
                return retval;
            }
            count ++;

            if (count == (chunk_count * CHUNK -1)){
                chunk_count ++;
                if (!(temp_creds = (krb5_creds **) realloc(temp_creds,
                                                           chunk_count * CHUNK * sizeof(krb5_creds *)))){
                    return ENOMEM;
                }
            }
        }

    }

    temp_creds[count] = NULL;
    *creds_array   = temp_creds;

    if (retval == KRB5_CC_END) {
        retval = krb5_cc_end_seq_get(context, cc, &cur);
    }

    return retval;

}


krb5_error_code krb5_check_exp(context, tkt_time)
    krb5_context context;
    krb5_ticket_times tkt_time;
{
    krb5_error_code retval =0;
    krb5_timestamp currenttime;

    if ((retval = krb5_timeofday (context, &currenttime))){
        return retval;
    }
    if (auth_debug){
        fprintf(stderr,"krb5_check_exp: the krb5_clockskew is %d \n",
                context->clockskew);

        fprintf(stderr,"krb5_check_exp: currenttime - endtime %d \n",
                (currenttime - tkt_time.endtime ));

    }

    if (currenttime - tkt_time.endtime > context->clockskew){
        retval = KRB5KRB_AP_ERR_TKT_EXPIRED ;
        return retval;
    }

    return 0;
}


char *flags_string(cred)
    krb5_creds *cred;
{
    static char buf[32];
    int i = 0;

    if (cred->ticket_flags & TKT_FLG_FORWARDABLE)
        buf[i++] = 'F';
    if (cred->ticket_flags & TKT_FLG_FORWARDED)
        buf[i++] = 'f';
    if (cred->ticket_flags & TKT_FLG_PROXIABLE)
        buf[i++] = 'P';
    if (cred->ticket_flags & TKT_FLG_PROXY)
        buf[i++] = 'p';
    if (cred->ticket_flags & TKT_FLG_MAY_POSTDATE)
        buf[i++] = 'D';
    if (cred->ticket_flags & TKT_FLG_POSTDATED)
        buf[i++] = 'd';
    if (cred->ticket_flags & TKT_FLG_INVALID)
        buf[i++] = 'i';
    if (cred->ticket_flags & TKT_FLG_RENEWABLE)
        buf[i++] = 'R';
    if (cred->ticket_flags & TKT_FLG_INITIAL)
        buf[i++] = 'I';
    if (cred->ticket_flags & TKT_FLG_HW_AUTH)
        buf[i++] = 'H';
    if (cred->ticket_flags & TKT_FLG_PRE_AUTH)
        buf[i++] = 'A';
    buf[i] = '\0';
    return(buf);
}

void printtime(tv)
    time_t tv;
{
    char fmtbuf[18];
    char fill;
    krb5_timestamp tstamp;

    /* XXXX ASSUMES sizeof(krb5_timestamp) >= sizeof(time_t) */
    (void) localtime((time_t *)&tv);
    tstamp = tv;
    fill = ' ';
    if (!krb5_timestamp_to_sfstring(tstamp,
                                    fmtbuf,
                                    sizeof(fmtbuf),
                                    &fill))
        printf("%s", fmtbuf);
}


krb5_error_code
krb5_get_login_princ(luser, princ_list)
    const char *luser;
    char ***princ_list;
{
    struct stat sbuf;
    struct passwd *pwd;
    char pbuf[MAXPATHLEN];
    FILE *fp;
    char * linebuf;
    char *newline;
    int gobble, result;
    char ** buf_out;
    struct stat st_temp;
    int count = 0, chunk_count = 1;

    /* no account => no access */

    if ((pwd = getpwnam(luser)) == NULL) {
        return 0;
    }
    result = snprintf(pbuf, sizeof(pbuf), "%s/.k5login", pwd->pw_dir);
    if (SNPRINTF_OVERFLOW(result, sizeof(pbuf))) {
        fprintf(stderr, _("home directory path for %s too long\n"), luser);
        exit (1);
    }

    if (stat(pbuf, &st_temp)) {  /* not accessible */
        return 0;
    }


    /* open ~/.k5login */
    if ((fp = fopen(pbuf, "r")) == NULL) {
        return 0;
    }
    /*
     * For security reasons, the .k5login file must be owned either by
     * the user himself, or by root.  Otherwise, don't grant access.
     */
    if (fstat(fileno(fp), &sbuf)) {
        fclose(fp);
        return 0;
    }
    if ((sbuf.st_uid != pwd->pw_uid) && sbuf.st_uid) {
        fclose(fp);
        return 0;
    }

    /* check each line */


    if( !(linebuf = (char *) calloc (BUFSIZ, sizeof(char)))) return ENOMEM;

    if (!(buf_out = (char **) malloc( CHUNK * sizeof(char *)))) return ENOMEM;

    while ( fgets(linebuf, BUFSIZ, fp) != NULL) {
        /* null-terminate the input string */
        linebuf[BUFSIZ-1] = '\0';
        newline = NULL;
        /* nuke the newline if it exists */
        if ((newline = strchr(linebuf, '\n')))
            *newline = '\0';

        buf_out[count] = linebuf;
        count ++;

        if (count == (chunk_count * CHUNK -1)){
            chunk_count ++;
            if (!(buf_out = (char **) realloc(buf_out,
                                              chunk_count * CHUNK * sizeof(char *)))){
                return ENOMEM;
            }
        }

        /* clean up the rest of the line if necessary */
        if (!newline)
            while (((gobble = getc(fp)) != EOF) && gobble != '\n');

        if( !(linebuf = (char *) calloc (BUFSIZ, sizeof(char)))) return ENOMEM;
    }

    buf_out[count] = NULL;
    *princ_list = buf_out;
    fclose(fp);
    return 0;
}



void
show_credential(context, cred, cc)
    krb5_context context;
    krb5_creds *cred;
    krb5_ccache cc;
{
    krb5_error_code retval;
    char *name, *sname, *flags;
    int first = 1;
    krb5_principal princ;
    char * defname;
    int show_flags =1;

    retval = krb5_unparse_name(context, cred->client, &name);
    if (retval) {
        com_err(prog_name, retval, _("while unparsing client name"));
        return;
    }
    retval = krb5_unparse_name(context, cred->server, &sname);
    if (retval) {
        com_err(prog_name, retval, _("while unparsing server name"));
        free(name);
        return;
    }

    if ((retval = krb5_cc_get_principal(context, cc, &princ))) {
        com_err(prog_name, retval, _("while retrieving principal name"));
        return;
    }
    if ((retval = krb5_unparse_name(context, princ, &defname))) {
        com_err(prog_name, retval, _("while unparsing principal name"));
        return;
    }

    if (!cred->times.starttime)
        cred->times.starttime = cred->times.authtime;

    printtime(cred->times.starttime);
    putchar(' '); putchar(' ');
    printtime(cred->times.endtime);
    putchar(' '); putchar(' ');

    printf("%s\n", sname);

    if (strcmp(name, defname)) {
        printf(_("\tfor client %s"), name);
        first = 0;
    }

    if (cred->times.renew_till) {
        if (first)
            fputs("\t",stdout);
        else
            fputs(", ",stdout);
        fputs(_("renew until "), stdout);
        printtime(cred->times.renew_till);
    }
    if (show_flags) {
        flags = flags_string(cred);
        if (flags && *flags) {
            if (first)
                fputs("\t",stdout);
            else
                fputs(", ",stdout);
            printf(_("Flags: %s"), flags);
            first = 0;
        }
    }
    putchar('\n');
    free(name);
    free(sname);
}

int gen_sym(){
    static int i = 0;
    i ++;
    return i;
}

krb5_error_code krb5_ccache_overwrite(context, ccs, cct, primary_principal)
    krb5_context context;
    krb5_ccache ccs;
    krb5_ccache cct;
    krb5_principal primary_principal;
{
    const char * cct_name;
    const char * ccs_name;
    krb5_error_code retval=0;
    krb5_principal temp_principal;
    krb5_creds ** ccs_creds_arr = NULL;
    int i=0;
    struct stat st_temp;

    ccs_name = krb5_cc_get_name(context, ccs);
    cct_name = krb5_cc_get_name(context, cct);

    if ( ! stat(ccs_name, &st_temp)){
        if ((retval = krb5_get_nonexp_tkts(context,  ccs, &ccs_creds_arr))){
            return retval;
        }
    }

    if ( ! stat(cct_name, &st_temp)){
        if ((retval = krb5_cc_get_principal(context, cct, &temp_principal))){
            return retval;
        }
    }else{
        temp_principal = primary_principal;
    }

    if ((retval = krb5_cc_initialize(context, cct, temp_principal))){
        return retval;
    }

    retval = krb5_store_all_creds(context, cct, ccs_creds_arr, NULL);

    if (ccs_creds_arr){
        while (ccs_creds_arr[i]){
            krb5_free_creds(context, ccs_creds_arr[i]);
            i++;
        }
    }

    return retval;
}

krb5_error_code krb5_store_some_creds(context, cc, creds_def, creds_other, prst,
                                      stored)
    krb5_context context;
    krb5_ccache cc;
    krb5_creds **creds_def;
    krb5_creds **creds_other;
    krb5_principal prst;
    krb5_boolean *stored;
{

    int i = 0;
    krb5_error_code retval = 0;
    krb5_creds ** temp_creds= NULL;
    krb5_boolean temp_stored = FALSE;


    if ((creds_def == NULL) && (creds_other == NULL))
        return 0;

    if ((creds_def == NULL) && (creds_other != NULL))
        temp_creds = creds_other;

    if ((creds_def != NULL) && (creds_other == NULL))
        temp_creds = creds_def;


    if (temp_creds){
        while(temp_creds[i]){
            if (krb5_principal_compare(context,
                                       temp_creds[i]->client,
                                       prst)== TRUE) {

                if ((retval = krb5_cc_store_cred(context,
                                                 cc,temp_creds[i]))){
                    return retval;
                }
                temp_stored = TRUE;
            }

            i++;
        }
    }
    else { /* both arrays have elements in them */
        return KRB5KRB_ERR_GENERIC;
    }

    *stored = temp_stored;
    return 0;
}
/******************************************************************
krb5_cache_copy_restricted

gets rid of any expired tickets in the secondary cache,
copies the default cache into the secondary cache,
only credentials that are for prst are copied.

the algorithm may look a bit funny,
but I had to do it this way, since cc_remove function did not come
with k5 beta 3 release.
************************************************************************/

krb5_error_code krb5_ccache_copy_restricted (context, cc_def, cc_other_tag,
                                             prst, cc_out, stored, target_uid)
    krb5_context context;
    krb5_ccache cc_def;
    char *cc_other_tag;
    krb5_principal prst;
    uid_t target_uid;
    /* OUT */
    krb5_ccache *cc_out;
    krb5_boolean *stored;
{

    int i=0;
    krb5_ccache  * cc_other;
    const char * cc_def_name;
    const char * cc_other_name;
    krb5_error_code retval=0;
    krb5_creds ** cc_def_creds_arr = NULL;
    krb5_creds ** cc_other_creds_arr = NULL;
    struct stat st_temp;

    cc_other = (krb5_ccache *)  xcalloc(1, sizeof (krb5_ccache));

    if ((retval = krb5_cc_resolve(context, cc_other_tag, cc_other))){
        com_err(prog_name, retval, _("resolving ccache %s"), cc_other_tag);
        return retval;
    }

    cc_def_name = krb5_cc_get_name(context, cc_def);
    cc_other_name = krb5_cc_get_name(context, *cc_other);

    if ( ! stat(cc_def_name, &st_temp)){
        if((retval = krb5_get_nonexp_tkts(context,cc_def,&cc_def_creds_arr))){
            return retval;
        }

    }

    if (!lstat( cc_other_name, &st_temp)) {
        return EINVAL;
    }

    if (krb5_seteuid(0)||krb5_seteuid(target_uid)) {
        return errno;
    }


    if ((retval = krb5_cc_initialize(context, *cc_other, prst))){
        return retval;
    }

    retval = krb5_store_some_creds(context, * cc_other,
                                   cc_def_creds_arr, cc_other_creds_arr, prst, stored);



    if (cc_def_creds_arr){
        while (cc_def_creds_arr[i]){
            krb5_free_creds(context, cc_def_creds_arr[i]);
            i++;
        }
    }

    i=0;

    if(cc_other_creds_arr){
        while (cc_other_creds_arr[i]){
            krb5_free_creds(context, cc_other_creds_arr[i]);
            i++;
        }
    }

    *cc_out = *cc_other;
    return retval;
}

krb5_error_code krb5_ccache_filter (context, cc, prst)
    krb5_context context;
    krb5_ccache cc;
    krb5_principal prst;
{

    int i=0;
    krb5_error_code retval=0;
    krb5_principal temp_principal;
    krb5_creds ** cc_creds_arr = NULL;
    const char * cc_name;
    krb5_boolean stored;
    struct stat st_temp;

    cc_name = krb5_cc_get_name(context, cc);

    if ( ! stat(cc_name, &st_temp)){

        if (auth_debug) {
            fprintf(stderr,"putting cache %s through a filter for -z option\n",                     cc_name);
        }

        if ((retval = krb5_get_nonexp_tkts(context, cc, &cc_creds_arr))){
            return retval;
        }

        if ((retval = krb5_cc_get_principal(context, cc, &temp_principal))){
            return retval;
        }

        if ((retval = krb5_cc_initialize(context, cc, temp_principal))){
            return retval;
        }

        if ((retval = krb5_store_some_creds(context, cc, cc_creds_arr,
                                            NULL, prst, &stored))){
            return retval;
        }

        if (cc_creds_arr){
            while (cc_creds_arr[i]){
                krb5_free_creds(context, cc_creds_arr[i]);
                i++;
            }
        }
    }
    return 0;
}

krb5_boolean  krb5_find_princ_in_cred_list (context, creds_list, princ)
    krb5_context context;
    krb5_creds **creds_list;
    krb5_principal princ;
{

    int i = 0;
    krb5_boolean temp_stored = FALSE;

    if (creds_list){
        while(creds_list[i]){
            if (krb5_principal_compare(context,
                                       creds_list[i]->client,
                                       princ)== TRUE){
                temp_stored = TRUE;
                break;
            }

            i++;
        }
    }

    return temp_stored;
}

krb5_error_code  krb5_find_princ_in_cache (context, cc, princ, found)
    krb5_context context;
    krb5_ccache cc;
    krb5_principal princ;
    krb5_boolean *found;
{
    krb5_error_code retval;
    krb5_creds ** creds_list = NULL;
    const char * cc_name;
    struct stat st_temp;

    cc_name = krb5_cc_get_name(context, cc);

    if ( ! stat(cc_name, &st_temp)){
        if ((retval = krb5_get_nonexp_tkts(context, cc, &creds_list))){
            return retval;
        }
    }

    *found = krb5_find_princ_in_cred_list(context, creds_list, princ);
    return 0;
}
