/*
 * Copyright (c) 1995-2001 Kungliga Tekniska Högskolan
 * (Royal Institute of Technology, Stockholm, Sweden).
 * All rights reserved.
 * 
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "sia_locl.h"

RCSID("$Id: sia.c 14838 2005-04-19 04:41:07Z lha $");

int 
siad_init(void)
{
    return SIADSUCCESS;
}

int 
siad_chk_invoker(void)
{
    SIA_DEBUG(("DEBUG", "siad_chk_invoker"));
    return SIADFAIL;
}

int 
siad_ses_init(SIAENTITY *entity, int pkgind)
{
    struct state *s = malloc(sizeof(*s));

    SIA_DEBUG(("DEBUG", "siad_ses_init"));
    if(s == NULL)
	return SIADFAIL;
    memset(s, 0, sizeof(*s));
#ifdef SIA_KRB5
    {
      krb5_error_code ret;
      ret = krb5_init_context(&s->context);
      if (ret)
	return SIADFAIL;
    }
#endif
    entity->mech[pkgind] = (int*)s;
    return SIADSUCCESS;
}

static int
setup_name(SIAENTITY *e, prompt_t *p)
{
    SIA_DEBUG(("DEBUG", "setup_name"));
    e->name = malloc(SIANAMEMIN + 1);
    if(e->name == NULL){
	SIA_DEBUG(("DEBUG", "failed to malloc %u bytes", SIANAMEMIN+1));
	return SIADFAIL;
    }
    p->prompt = (unsigned char*)"login: ";
    p->result = (unsigned char*)e->name;
    p->min_result_length = 1;
    p->max_result_length = SIANAMEMIN;
    p->control_flags = 0;
    return SIADSUCCESS;
}

static int
setup_password(SIAENTITY *e, prompt_t *p)
{
    SIA_DEBUG(("DEBUG", "setup_password"));
    e->password = malloc(SIAMXPASSWORD + 1);
    if(e->password == NULL){
	SIA_DEBUG(("DEBUG", "failed to malloc %u bytes", SIAMXPASSWORD+1));
	return SIADFAIL;
    }
    p->prompt = (unsigned char*)"Password: ";
    p->result = (unsigned char*)e->password;
    p->min_result_length = 0;
    p->max_result_length = SIAMXPASSWORD;
    p->control_flags = SIARESINVIS;
    return SIADSUCCESS;
}


static int
doauth(SIAENTITY *entity, int pkgind, char *name)
{
    struct passwd pw, *pwd;
    char pwbuf[1024];
    struct state *s = (struct state*)entity->mech[pkgind];
#ifdef SIA_KRB5
    krb5_realm *realms, *r;
    krb5_principal principal;
    krb5_ccache ccache;
    krb5_error_code ret;
#endif
#ifdef SIA_KRB4
    char realm[REALM_SZ];
    char *toname, *toinst;
    int ret;
    struct passwd fpw, *fpwd;
    char fpwbuf[1024];
    int secure;
#endif
	
    if(getpwnam_r(name, &pw, pwbuf, sizeof(pwbuf), &pwd) != 0 || pwd == NULL){
	SIA_DEBUG(("DEBUG", "failed to getpwnam(%s)", name));
	return SIADFAIL;
    }

#ifdef SIA_KRB5
    ret = krb5_get_default_realms(s->context, &realms);

    for (r = realms; *r != NULL; ++r) {
	krb5_make_principal (s->context, &principal, *r, entity->name, NULL);

	if(krb5_kuserok(s->context, principal, entity->name))
	    break;
    }
    krb5_free_host_realm (s->context, realms);
    if (*r == NULL)
	return SIADFAIL;

    sprintf(s->ticket, "FILE:/tmp/krb5_cc%d_%d", pwd->pw_uid, getpid());
    ret = krb5_cc_resolve(s->context, s->ticket, &ccache);
    if(ret)
	return SIADFAIL;
#endif
	
#ifdef SIA_KRB4
    snprintf(s->ticket, sizeof(s->ticket),
	     "%s%u_%u", TKT_ROOT, (unsigned)pwd->pw_uid, (unsigned)getpid());
    krb_get_lrealm(realm, 1);
    toname = name;
    toinst = "";
    if(entity->authtype == SIA_A_SUAUTH){
	uid_t ouid;
#ifdef HAVE_SIAENTITY_OUID
	ouid = entity->ouid;
#else
	ouid = getuid();
#endif
	if(getpwuid_r(ouid, &fpw, fpwbuf, sizeof(fpwbuf), &fpwd) != 0 || fpwd == NULL){
	    SIA_DEBUG(("DEBUG", "failed to getpwuid(%u)", ouid));
	    return SIADFAIL;
	}
	snprintf(s->ticket, sizeof(s->ticket), "%s_%s_to_%s_%d", 
		 TKT_ROOT, fpwd->pw_name, pwd->pw_name, getpid());
	if(strcmp(pwd->pw_name, "root") == 0){
	    toname = fpwd->pw_name;
	    toinst = pwd->pw_name;
	}
    }
    if(entity->authtype == SIA_A_REAUTH) 
	snprintf(s->ticket, sizeof(s->ticket), "%s", tkt_string());
    
    krb_set_tkt_string(s->ticket);
	
    setuid(0); /* XXX fix for fix in tf_util.c */
    if(krb_kuserok(toname, toinst, realm, name)){
	SIA_DEBUG(("DEBUG", "%s.%s@%s is not allowed to login as %s", 
		   toname, toinst, realm, name));
	return SIADFAIL;
    }
#endif
#ifdef SIA_KRB5
    ret = krb5_verify_user_lrealm(s->context, principal, ccache,
				  entity->password, 1, NULL);
    if(ret){
	/* if this is most likely a local user (such as
	   root), just silently return failure when the
	   principal doesn't exist */
	if(ret != KRB5KDC_ERR_C_PRINCIPAL_UNKNOWN && 
	   ret != KRB5KDC_ERR_S_PRINCIPAL_UNKNOWN)
	    SIALOG("WARNING", "krb5_verify_user(%s): %s", 
		   entity->name, error_message(ret));
	return SIADFAIL;
    }
#endif
#ifdef SIA_KRB4
    if (getuid () == 0)
	secure = KRB_VERIFY_SECURE;
    else
	secure = KRB_VERIFY_NOT_SECURE;
	
    ret = krb_verify_user(toname, toinst, realm,
			  entity->password, secure, NULL);
    if(ret){
	SIA_DEBUG(("DEBUG", "krb_verify_user: %s", krb_get_err_text(ret)));
	if(ret != KDC_PR_UNKNOWN)
	    /* since this is most likely a local user (such as
	       root), just silently return failure when the
	       principal doesn't exist */
	    SIALOG("WARNING", "krb_verify_user(%s.%s): %s", 
		   toname, toinst, krb_get_err_text(ret));
	return SIADFAIL;
    }
#endif
    if(sia_make_entity_pwd(pwd, entity) == SIAFAIL)
	return SIADFAIL;
    s->valid = 1;
    return SIADSUCCESS;
}


static int 
common_auth(sia_collect_func_t *collect, 
	    SIAENTITY *entity, 
	    int siastat,
	    int pkgind)
{
    prompt_t prompts[2], *pr;
    char *name;

    SIA_DEBUG(("DEBUG", "common_auth"));
    if((siastat == SIADSUCCESS) && (geteuid() == 0))
	return SIADSUCCESS;
    if(entity == NULL) {
	SIA_DEBUG(("DEBUG", "entity == NULL"));
	return SIADFAIL | SIADSTOP;
    }
    name = entity->name;
    if(entity->acctname)
	name = entity->acctname;
    
    if((collect != NULL) && entity->colinput) {
	int num;
	pr = prompts;
	if(name == NULL){
	    if(setup_name(entity, pr) != SIADSUCCESS)
		return SIADFAIL;
	    pr++;
	}
	if(entity->password == NULL){
	    if(setup_password(entity, pr) != SIADSUCCESS)
		return SIADFAIL;
	    pr++;
	}
	num = pr - prompts;
	if(num == 1){
	    if((*collect)(240, SIAONELINER, (unsigned char*)"", num, 
			  prompts) != SIACOLSUCCESS){
		SIA_DEBUG(("DEBUG", "collect failed"));
		return SIADFAIL | SIADSTOP;
	    }
	} else if(num > 0){
	    if((*collect)(0, SIAFORM, (unsigned char*)"", num, 
			  prompts) != SIACOLSUCCESS){
		SIA_DEBUG(("DEBUG", "collect failed"));
		return SIADFAIL | SIADSTOP;
	    }
	}
    }
    if(name == NULL)
	name = entity->name;
    if(name == NULL || name[0] == '\0'){
	SIA_DEBUG(("DEBUG", "name is null"));
	return SIADFAIL;
    }

    if(entity->password == NULL || strlen(entity->password) > SIAMXPASSWORD){
	SIA_DEBUG(("DEBUG", "entity->password is null"));
	return SIADFAIL;
    }
    
    return doauth(entity, pkgind, name);
}


int 
siad_ses_authent(sia_collect_func_t *collect, 
		 SIAENTITY *entity, 
		 int siastat,
		 int pkgind)
{
    SIA_DEBUG(("DEBUG", "siad_ses_authent"));
    return common_auth(collect, entity, siastat, pkgind);
}

int 
siad_ses_estab(sia_collect_func_t *collect, 
	       SIAENTITY *entity, int pkgind)
{
    SIA_DEBUG(("DEBUG", "siad_ses_estab"));
    return SIADFAIL;
}

int 
siad_ses_launch(sia_collect_func_t *collect,
		SIAENTITY *entity,
		int pkgind)
{
    static char env[MaxPathLen];
    struct state *s = (struct state*)entity->mech[pkgind];
    SIA_DEBUG(("DEBUG", "siad_ses_launch"));
    if(s->valid){
#ifdef SIA_KRB5
	chown(s->ticket + sizeof("FILE:") - 1, 
	      entity->pwd->pw_uid, 
	      entity->pwd->pw_gid);
	snprintf(env, sizeof(env), "KRB5CCNAME=%s", s->ticket);
#endif
#ifdef SIA_KRB4
	chown(s->ticket, entity->pwd->pw_uid, entity->pwd->pw_gid);
	snprintf(env, sizeof(env), "KRBTKFILE=%s", s->ticket);
#endif
	putenv(env);
    }
#ifdef SIA_KRB5
    if (k_hasafs()) {
	char cell[64];
	krb5_ccache ccache;
	if(krb5_cc_resolve(s->context, s->ticket, &ccache) == 0) {
	    k_setpag();
	    if(k_afs_cell_of_file(entity->pwd->pw_dir, cell, sizeof(cell)) == 0)
		krb5_afslog(s->context, ccache, cell, 0);
	    krb5_afslog_home(s->context, ccache, 0, 0, entity->pwd->pw_dir);
	}
    }
#endif
#ifdef SIA_KRB4
    if (k_hasafs()) {
	char cell[64];
	k_setpag();
	if(k_afs_cell_of_file(entity->pwd->pw_dir, cell, sizeof(cell)) == 0)
	    krb_afslog(cell, 0);
	krb_afslog_home(0, 0, entity->pwd->pw_dir);
    }
#endif
    return SIADSUCCESS;
}

int 
siad_ses_release(SIAENTITY *entity, int pkgind)
{
    SIA_DEBUG(("DEBUG", "siad_ses_release"));
    if(entity->mech[pkgind]){
#ifdef SIA_KRB5
	struct state *s = (struct state*)entity->mech[pkgind];
	krb5_free_context(s->context);
#endif
	free(entity->mech[pkgind]);
    }
    return SIADSUCCESS;
}

int 
siad_ses_suauthent(sia_collect_func_t *collect,
		   SIAENTITY *entity,
		   int siastat,
		   int pkgind)
{
    SIA_DEBUG(("DEBUG", "siad_ses_suauth"));
    if(geteuid() != 0)
	return SIADFAIL;
    if(entity->name == NULL)
	return SIADFAIL;
    if(entity->name[0] == '\0') {
	free(entity->name);
	entity->name = strdup("root");
	if (entity->name == NULL)
	    return SIADFAIL;
    }
    return common_auth(collect, entity, siastat, pkgind);
}

int
siad_ses_reauthent (sia_collect_func_t *collect,
		    SIAENTITY *entity,
		    int siastat,
		    int pkgind)
{
    int ret;
    SIA_DEBUG(("DEBUG", "siad_ses_reauthent"));
    if(entity == NULL || entity->name == NULL)
	return SIADFAIL;
    ret = common_auth(collect, entity, siastat, pkgind);
    if((ret & SIADSUCCESS)){
	/* launch isn't (always?) called when doing reauth, so we must
           duplicate some code here... */
	struct state *s = (struct state*)entity->mech[pkgind];
	chown(s->ticket, entity->pwd->pw_uid, entity->pwd->pw_gid);
#ifdef SIA_KRB5
	if (k_hasafs()) {
	    char cell[64];
	    krb5_ccache ccache;
	    if(krb5_cc_resolve(s->context, s->ticket, &ccache) == 0) {
		k_setpag();
		if(k_afs_cell_of_file(entity->pwd->pw_dir, 
				      cell, sizeof(cell)) == 0)
		    krb5_afslog(s->context, ccache, cell, 0);
		krb5_afslog_home(s->context, ccache, 0, 0, entity->pwd->pw_dir);
	    }
	}
#endif
#ifdef SIA_KRB4
	if(k_hasafs()) {
	    char cell[64];
	    if(k_afs_cell_of_file(entity->pwd->pw_dir, 
				  cell, sizeof(cell)) == 0)
		krb_afslog(cell, 0);
	    krb_afslog_home(0, 0, entity->pwd->pw_dir);
	}
#endif
    }
    return ret;
}

int
siad_chg_finger (sia_collect_func_t *collect,
		     const char *username, 
		     int argc, 
		     char *argv[])
{
    SIA_DEBUG(("DEBUG", "siad_chg_finger"));
    return SIADFAIL;
}

#ifdef SIA_KRB5
int
siad_chg_password (sia_collect_func_t *collect,
		     const char *username, 
		     int argc, 
		     char *argv[])
{
    return SIADFAIL;
}
#endif

#ifdef SIA_KRB4
static void
sia_message(sia_collect_func_t *collect, int rendition, 
	    const char *title, const char *message)
{
    prompt_t prompt;
    prompt.prompt = (unsigned char*)message;
    (*collect)(0, rendition, (unsigned char*)title, 1, &prompt);
}

static int
init_change(sia_collect_func_t *collect, krb_principal *princ)
{
    prompt_t prompt;
    char old_pw[MAX_KPW_LEN+1];
    char *msg;
    char tktstring[128];
    int ret;
    
    SIA_DEBUG(("DEBUG", "init_change"));
    prompt.prompt = (unsigned char*)"Old password: ";
    prompt.result = (unsigned char*)old_pw;
    prompt.min_result_length = 0;
    prompt.max_result_length = sizeof(old_pw) - 1;
    prompt.control_flags = SIARESINVIS;
    asprintf(&msg, "Changing password for %s", krb_unparse_name(princ));
    if(msg == NULL){
	SIA_DEBUG(("DEBUG", "out of memory"));
	return SIADFAIL;
    }
    ret = (*collect)(60, SIAONELINER, (unsigned char*)msg, 1, &prompt);
    free(msg);
    SIA_DEBUG(("DEBUG", "ret = %d", ret));
    if(ret != SIACOLSUCCESS)
	return SIADFAIL;
    snprintf(tktstring, sizeof(tktstring), 
	     "%s_cpw_%u", TKT_ROOT, (unsigned)getpid());
    krb_set_tkt_string(tktstring);
    
    ret = krb_get_pw_in_tkt(princ->name, princ->instance, princ->realm, 
			    PWSERV_NAME, KADM_SINST, 1, old_pw);
    if (ret != KSUCCESS) {
	SIA_DEBUG(("DEBUG", "krb_get_pw_in_tkt: %s", krb_get_err_text(ret)));
	if (ret == INTK_BADPW)
	    sia_message(collect, SIAWARNING, "", "Incorrect old password.");
	else
	    sia_message(collect, SIAWARNING, "", "Kerberos error.");
	memset(old_pw, 0, sizeof(old_pw));
	return SIADFAIL;
    }
    if(chown(tktstring, getuid(), -1) < 0){
	dest_tkt();
	return SIADFAIL;
    }
    memset(old_pw, 0, sizeof(old_pw));
    return SIADSUCCESS;
}

int
siad_chg_password (sia_collect_func_t *collect,
		   const char *username, 
		   int argc, 
		   char *argv[])
{
    prompt_t prompts[2];
    krb_principal princ;
    int ret;
    char new_pw1[MAX_KPW_LEN+1];
    char new_pw2[MAX_KPW_LEN+1];
    static struct et_list *et_list;

    setprogname(argv[0]);

    SIA_DEBUG(("DEBUG", "siad_chg_password"));
    if(collect == NULL)
	return SIADFAIL;

    if(username == NULL)
	username = getlogin();

    ret = krb_parse_name(username, &princ);
    if(ret)
	return SIADFAIL;
    if(princ.realm[0] == '\0')
	krb_get_lrealm(princ.realm, 1);

    if(et_list == NULL) {
	initialize_kadm_error_table_r(&et_list);
	initialize_krb_error_table_r(&et_list);
    }

    ret = init_change(collect, &princ);
    if(ret != SIADSUCCESS)
	return ret;

again:
    prompts[0].prompt = (unsigned char*)"New password: ";
    prompts[0].result = (unsigned char*)new_pw1;
    prompts[0].min_result_length = MIN_KPW_LEN;
    prompts[0].max_result_length = sizeof(new_pw1) - 1;
    prompts[0].control_flags = SIARESINVIS;
    prompts[1].prompt = (unsigned char*)"Verify new password: ";
    prompts[1].result = (unsigned char*)new_pw2;
    prompts[1].min_result_length = MIN_KPW_LEN;
    prompts[1].max_result_length = sizeof(new_pw2) - 1;
    prompts[1].control_flags = SIARESINVIS;
    if((*collect)(120, SIAFORM, (unsigned char*)"", 2, prompts) != 
       SIACOLSUCCESS) {
	dest_tkt();
	return SIADFAIL;
    }
    if(strcmp(new_pw1, new_pw2) != 0){
	sia_message(collect, SIAWARNING, "", "Password mismatch.");
	goto again;
    }
    ret = kadm_check_pw(new_pw1);
    if(ret) {
	sia_message(collect, SIAWARNING, "", com_right(et_list, ret));
	goto again;
    }
    
    memset(new_pw2, 0, sizeof(new_pw2));
    ret = kadm_init_link (PWSERV_NAME, KRB_MASTER, princ.realm);
    if (ret != KADM_SUCCESS)
	sia_message(collect, SIAWARNING, "Error initing kadmin connection", 
		    com_right(et_list, ret));
    else {
	des_cblock newkey;
	char *pw_msg; /* message from server */

	des_string_to_key(new_pw1, &newkey);
	ret = kadm_change_pw_plain((unsigned char*)&newkey, new_pw1, &pw_msg);
	memset(newkey, 0, sizeof(newkey));
      
	if (ret == KADM_INSECURE_PW)
	    sia_message(collect, SIAWARNING, "Insecure password", pw_msg);
	else if (ret != KADM_SUCCESS)
	    sia_message(collect, SIAWARNING, "Error changing password", 
			com_right(et_list, ret));
    }
    memset(new_pw1, 0, sizeof(new_pw1));

    if (ret != KADM_SUCCESS)
	sia_message(collect, SIAWARNING, "", "Password NOT changed.");
    else
	sia_message(collect, SIAINFO, "", "Password changed.");
    
    dest_tkt();
    if(ret)
	return SIADFAIL;
    return SIADSUCCESS;
}
#endif

int
siad_chg_shell (sia_collect_func_t *collect,
		     const char *username, 
		     int argc, 
		     char *argv[])
{
    return SIADFAIL;
}

int
siad_getpwent(struct passwd *result, 
	      char *buf, 
	      int bufsize, 
	      struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_getpwuid (uid_t uid, 
	       struct passwd *result, 
	       char *buf, 
	       int bufsize, 
	       struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_getpwnam (const char *name, 
	       struct passwd *result, 
	       char *buf, 
	       int bufsize, 
	       struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_setpwent (struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_endpwent (struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_getgrent(struct group *result, 
	      char *buf, 
	      int bufsize, 
	      struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_getgrgid (gid_t gid, 
	       struct group *result, 
	       char *buf, 
	       int bufsize, 
	       struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_getgrnam (const char *name, 
	       struct group *result, 
	       char *buf, 
	       int bufsize, 
	       struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_setgrent (struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_endgrent (struct sia_context *context)
{
    return SIADFAIL;
}

int
siad_chk_user (const char *logname, int checkflag)
{
    if(checkflag != CHGPASSWD)
	return SIADFAIL;
    return SIADSUCCESS;
}
