/*
 * Copyright (c) 2004 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 <popper.h>
#include <base64.h>
#include <pop_auth.h>
RCSID("$Id$");


#if defined(SASL) && defined(KRB4)
#include <krb.h>
#include <des.h>

struct krb4_state {
    int stage;
    uint32_t nonce;
};

static int
krb4_loop(POP *p, void *state, 
	 /* const */ void *input, size_t input_length,
	 void **output, size_t *output_length)
{
    struct krb4_state *ks = state;

    int ret;
    des_cblock key;
    unsigned char *data;
    char instance[INST_SZ];  
    des_key_schedule schedule;

    if(ks->stage == 0) {
	if(input_length > 0)
	    return POP_AUTH_FAILURE;
	/* S -> C: 32 bit nonce in MSB base64 */
#ifdef HAVE_OPENSSL
#define des_new_random_key des_random_key
#endif
	des_new_random_key(key);
	ks->nonce = (key[0] | (key[1] << 8) | (key[2] << 16) | (key[3] << 24)
		     | key[4] | (key[5] << 8) | (key[6] << 16) | (key[7] << 24));
	*output = malloc(4);
	if(*output == NULL) {
	    pop_auth_set_error("out of memory");
	    return POP_AUTH_FAILURE;
	}
	krb_put_int(ks->nonce, *output, 4, 4);
	*output_length = 4;
	ks->stage++;
	return POP_AUTH_CONTINUE;
    }

    if(ks->stage == 1) {
	KTEXT_ST authent;
	/* C -> S: ticket and authenticator */

	if (input_length > sizeof(authent.dat)) {
	    pop_auth_set_error("data packet too long");
	    return POP_AUTH_FAILURE;
	}
	memcpy(authent.dat, input, input_length);
	authent.length = input_length;

	k_getsockinst (0, instance, sizeof(instance));
	ret = krb_rd_req(&authent, "pop", instance,
			 0 /* XXX p->in_addr.sin_addr.s_addr */,
			 &p->kdata, NULL);
	if (ret != 0) {
	    pop_auth_set_error(krb_get_err_text(ret));
	    return POP_AUTH_FAILURE;
	}
	if (p->kdata.checksum != ks->nonce) {
	    pop_auth_set_error("data stream modified");
	    return POP_AUTH_FAILURE;
	}
	/* S -> C: nonce + 1 | bit | max segment */

	*output = malloc(8);
	if(*output == NULL) {
	    pop_auth_set_error("out of memory");
	    return POP_AUTH_FAILURE;
	}
	data = *output;
	krb_put_int(ks->nonce + 1, data, 8, 4);
	data[4] = 1;
	data[5] = 0;
	data[6] = 0;
	data[7] = 0;
	des_key_sched(&p->kdata.session, schedule);
	des_pcbc_encrypt((des_cblock*)data,
			 (des_cblock*)data, 8,
			 schedule,
			 &p->kdata.session,
			 DES_ENCRYPT);
	*output_length = 8;
	ks->stage++;
	return POP_AUTH_CONTINUE;
    }

    if(ks->stage == 2) {
	uint32_t nonce_reply;
	/* C -> S: nonce | bit | max segment | username */

	if (input_length % 8 != 0) {
	    pop_auth_set_error("reply is not a multiple of 8 bytes");
	    return POP_AUTH_FAILURE;
	}

	des_key_sched(&p->kdata.session, schedule);
	des_pcbc_encrypt((des_cblock*)input,
			 (des_cblock*)input,
			 input_length,
			 schedule,
			 &p->kdata.session,
			 DES_DECRYPT);

	data = input;
	krb_get_int(data, &nonce_reply, 4, 0);
	if (nonce_reply != ks->nonce) {
	    pop_auth_set_error("data stream modified");
	    return POP_AUTH_FAILURE;
	}
	if(data[4] != 1) {

	}
	if(data[input_length - 1] != '\0') {
	    pop_auth_set_error("bad format of username");
	    return POP_AUTH_FAILURE;
	}
	strlcpy(p->user, data + 8, sizeof(p->user));
	if (kuserok(&p->kdata, p->user)) {
	    pop_log(p, POP_PRIORITY,
		    "%s: (%s.%s@%s) tried to retrieve mail for %s.",
		    p->client, p->kdata.pname, p->kdata.pinst,
		    p->kdata.prealm, p->user);
	    pop_auth_set_error("Permission denied");
	    return POP_AUTH_FAILURE;
	}
	pop_log(p, POP_INFO, "%s: %s.%s@%s -> %s",
		p->ipaddr,
		p->kdata.pname, p->kdata.pinst, p->kdata.prealm,
		p->user);
	return POP_AUTH_COMPLETE;
    }
    return POP_AUTH_FAILURE;
}


static int
krb4_init(POP *p, void **state)
{
    struct krb4_state *ks = malloc(sizeof(*ks));
    if(ks == NULL) {
	pop_auth_set_error("out of memory");
	return POP_AUTH_FAILURE;
    }
    ks->stage = 0;
    *state = ks;
    return POP_AUTH_CONTINUE;
}

static int
krb4_cleanup(POP *p, void *state)
{
    free(state);
    return POP_AUTH_CONTINUE;
}

struct auth_mech krb4_mech = {
    "KERBEROS_V4", krb4_init, krb4_loop, krb4_cleanup
};

#endif /* KRB5 */
