/* saslserver.c -- shared SASL code for server-side authentication
 *
 * Copyright (c) 1994-2008 Carnegie Mellon University.  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. The name "Carnegie Mellon University" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For permission or any legal
 *    details, please contact
 *      Carnegie Mellon University
 *      Center for Technology Transfer and Enterprise Creation
 *      4615 Forbes Avenue
 *      Suite 302
 *      Pittsburgh, PA  15213
 *      (412) 268-7393, fax: (412) 268-7395
 *      innovation@andrew.cmu.edu
 *
 * 4. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by Computing Services
 *     at Carnegie Mellon University (http://www.cmu.edu/computing/)."
 *
 * CARNEGIE MELLON UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO
 * THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
 * AND FITNESS, IN NO EVENT SHALL CARNEGIE MELLON UNIVERSITY BE LIABLE
 * FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN
 * AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING
 * OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * $Id: saslserver.c,v 1.12 2010/01/06 17:01:39 murch Exp $
 */

#include <config.h>

#include <string.h>
#include <sasl/sasl.h>
#include <sasl/saslutil.h>

#include "prot.h"
#include "imap_err.h"
#include "xmalloc.h"

#define BASE64_BUF_SIZE 21848	/* per RFC 2222bis: ((16K / 3) + 1) * 4  */

/* NOTE: success_data will need to be free()d by the caller */
int saslserver(sasl_conn_t *conn, const char *mech,
	       const char *init_resp, const char *resp_prefix,
	       const char *continuation, const char *empty_chal,
               struct protstream *pin, struct protstream *pout,
	       int *sasl_result, char **success_data)
{
    char base64[BASE64_BUF_SIZE+1];
    char *clientin = NULL;
    unsigned int clientinlen = 0;
    const char *serverout = NULL;
    unsigned int serveroutlen = 0;
    int r = SASL_OK;

    if (success_data) *success_data = NULL;

    /* initial response */
    if (init_resp) {
	clientin = base64;
	if (!strcmp(init_resp, "=")) {
	    /* zero-length initial response */
	    base64[0] = '\0';
	}
	else {
	    r = sasl_decode64(init_resp, strlen(init_resp),
			      clientin, BASE64_BUF_SIZE, &clientinlen);
	}
    }

    /* start the exchange */
    if (r == SASL_OK)
	r = sasl_server_start(conn, mech, clientin, clientinlen,
			      &serverout, &serveroutlen);

    while (r == SASL_CONTINUE) {
	char *p;

	/* send the challenge to the client */
	if (serveroutlen) {
	    r = sasl_encode64(serverout, serveroutlen,
			      base64, BASE64_BUF_SIZE, NULL);
	    if (r != SASL_OK) break;
	    serverout = base64;
	}
	else {
	    serverout = empty_chal;
	}

	prot_printf(pout, "%s%s\r\n", continuation, serverout);
	prot_flush(pout);

	/* get response from the client */
	if (!prot_fgets(base64, BASE64_BUF_SIZE, pin) ||
	    strncasecmp(base64, resp_prefix, strlen(resp_prefix))) {
	    if (sasl_result) *sasl_result = SASL_FAIL;
	    return IMAP_SASL_PROTERR;
	}

	/* trim CRLF */
	p = base64 + strlen(base64) - 1;
	if (p >= base64 && *p == '\n') *p-- = '\0';
	if (p >= base64 && *p == '\r') *p-- = '\0';

	/* trim prefix */
	p = base64 + strlen(resp_prefix);

	/* check if client cancelled */
	if (p[0] == '*') {
	    if(sasl_result) *sasl_result = SASL_BADPROT;
	    return IMAP_SASL_CANCEL;
	}

	/* decode the response */
	clientin = base64;
	r = sasl_decode64(p, strlen(p),
			  clientin, BASE64_BUF_SIZE, &clientinlen);
	if (r != SASL_OK) break;

	/* do the next step */
	r = sasl_server_step(conn, clientin, clientinlen,
			     &serverout, &serveroutlen);
    }

    /* success data */
    if (r == SASL_OK && serverout && success_data) {
	r = sasl_encode64(serverout, serveroutlen,
			  base64, BASE64_BUF_SIZE, NULL);
	if (r == SASL_OK)
	    *success_data = (char *) xstrdup(base64);
    }

    if (sasl_result) *sasl_result = r;
    return (r == SASL_OK ? 0 : IMAP_SASL_FAIL);
}
