/* Copyright (c) 2004-2006, Sara Golemon <sarag@libssh2.org>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms,
 * with or without modification, are permitted provided
 * that the following conditions are met:
 *
 *   Redistributions of source code must retain the above
 *   copyright notice, this list of conditions and the
 *   following disclaimer.
 *
 *   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.
 *
 *   Neither the name of the copyright holder nor the names
 *   of any other contributors may be used to endorse or
 *   promote products derived from this software without
 *   specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT OWNER 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 "libssh2_priv.h"

/* Needed for struct iovec on some platforms */
#ifdef HAVE_SYS_UIO_H
#include <sys/uio.h>
#endif


/* {{{ proto libssh2_userauth_list
 * List authentication methods
 * Will yield successful login if "none" happens to be allowable for this user
 * Not a common configuration for any SSH server though
 * username should be NULL, or a null terminated string
 */
LIBSSH2_API char *libssh2_userauth_list(LIBSSH2_SESSION *session, const char *username, int username_len)
{
	unsigned char reply_codes[3] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, 0 };
	unsigned long data_len = username_len + 31; /* packet_type(1) + username_len(4) + service_len(4) + service(14)"ssh-connection" +
												   method_len(4) + method(4)"none" */
	unsigned long methods_len;
	unsigned char *data, *s;

	s = data = LIBSSH2_ALLOC(session, data_len);
	if (!data) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth_list", 0);
		return NULL;
	}

	*(s++) = SSH_MSG_USERAUTH_REQUEST;
	libssh2_htonu32(s, username_len);				s += 4;
	if (username) {
		memcpy(s, username, username_len);			s += username_len;
	}

	libssh2_htonu32(s, 14);							s += 4;
	memcpy(s, "ssh-connection", 14);				s += 14;

	libssh2_htonu32(s, 4);							s += 4;
	memcpy(s, "none", 4);							s += 4;

	if (libssh2_packet_write(session, data, data_len)) {
		libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-none request", 0);
		LIBSSH2_FREE(session, data);
		return NULL;
	}
	LIBSSH2_FREE(session, data);

	if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) {
		return NULL;
	}

	if (data[0] == SSH_MSG_USERAUTH_SUCCESS) {
		/* Wow, who'dve thought... */
		LIBSSH2_FREE(session, data);
		session->state |= LIBSSH2_STATE_AUTHENTICATED;
		return NULL;
	}

	methods_len = libssh2_ntohu32(data + 1);
	memcpy(data, data + 5, methods_len);
	data[methods_len] = '\0';
#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Permitted auth methods: %s", data);
#endif
	return data;
}
/* }}} */

/* {{{ libssh2_userauth_authenticated
 * 0 if not yet authenticated
 * non-zero is already authenticated
 */
LIBSSH2_API int libssh2_userauth_authenticated(LIBSSH2_SESSION *session)
{
	return session->state & LIBSSH2_STATE_AUTHENTICATED;
}
/* }}} */

/* {{{ libssh2_userauth_password
 * Plain ol' login
 */
LIBSSH2_API int libssh2_userauth_password_ex(LIBSSH2_SESSION *session, const char *username, int username_len,
																					  const char *password, int password_len,
																					  LIBSSH2_PASSWD_CHANGEREQ_FUNC((*passwd_change_cb)))
{
	unsigned char *data, *s, reply_codes[4] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_PASSWD_CHANGEREQ, 0 };
	unsigned long data_len = username_len + password_len + 40; /* packet_type(1) + username_len(4) + service_len(4) + service(14)"ssh-connection" + 
																  method_len(4) + method(8)"password" + chgpwdbool(1) + password_len(4) */

	s = data = LIBSSH2_ALLOC(session, data_len);
	if (!data) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth-password request", 0);
		return -1;
	}

	*(s++) = SSH_MSG_USERAUTH_REQUEST;
	libssh2_htonu32(s, username_len);							s += 4;
	memcpy(s, username, username_len);							s += username_len;

	libssh2_htonu32(s, sizeof("ssh-connection") - 1);			s += 4;
	memcpy(s, "ssh-connection", sizeof("ssh-connection") - 1);	s += sizeof("ssh-connection") - 1;

	libssh2_htonu32(s, sizeof("password") - 1);					s += 4;
	memcpy(s, "password", sizeof("password") - 1);				s += sizeof("password") - 1;

	*s = '\0';													s++;

	libssh2_htonu32(s, password_len);							s += 4;
	memcpy(s, password, password_len);							s += password_len;

#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting to login using password authentication");
#endif
	if (libssh2_packet_write(session, data, data_len)) {
		libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-password request", 0);
		LIBSSH2_FREE(session, data);
		return -1;
	}
	LIBSSH2_FREE(session, data);

 password_response:
	if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) {
		return -1;
	}

	if (data[0] == SSH_MSG_USERAUTH_SUCCESS) {
#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Password authentication successful");
#endif
		LIBSSH2_FREE(session, data);
		session->state |= LIBSSH2_STATE_AUTHENTICATED;
		return 0;
	}

	if (data[0] == SSH_MSG_USERAUTH_PASSWD_CHANGEREQ) {
		char *newpw = NULL;
		int newpw_len = 0;

#ifdef LIBSSH2_DEBUG_USERAUTH
		_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Password change required");
#endif
		LIBSSH2_FREE(session, data);
		if (passwd_change_cb) {
			passwd_change_cb(session, &newpw, &newpw_len, &session->abstract);
			if (!newpw) {
				libssh2_error(session, LIBSSH2_ERROR_PASSWORD_EXPIRED, "Password expired, and callback failed", 0);
				return -1;
			}
			data_len = username_len + password_len + 44 + newpw_len; /* basic data_len + newpw_len(4) */
			s = data = LIBSSH2_ALLOC(session, data_len);
			if (!data) {
				libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for userauth-password-change request", 0);
				return -1;
			}

			*(s++) = SSH_MSG_USERAUTH_REQUEST;
			libssh2_htonu32(s, username_len);							s += 4;
			memcpy(s, username, username_len);							s += username_len;

			libssh2_htonu32(s, sizeof("ssh-connection") - 1);			s += 4;
			memcpy(s, "ssh-connection", sizeof("ssh-connection") - 1);	s += sizeof("ssh-connection") - 1;

			libssh2_htonu32(s, sizeof("password") - 1);					s += 4;
			memcpy(s, "password", sizeof("password") - 1);				s += sizeof("password") - 1;

			*s = 0xFF;													s++;

			libssh2_htonu32(s, password_len);							s += 4;
			memcpy(s, password, password_len);							s += password_len;

			libssh2_htonu32(s, newpw_len);								s += 4;
			memcpy(s, newpw, newpw_len);								s += newpw_len;

			if (libssh2_packet_write(session, data, data_len)) {
				libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-password-change request", 0);
				LIBSSH2_FREE(session, data);
				return -1;
			}
			LIBSSH2_FREE(session, data);
			LIBSSH2_FREE(session, newpw);

			/* Ugliest use of goto ever.  Blame it on the askN => requirev migration. */
			goto password_response;
		} else {
			libssh2_error(session, LIBSSH2_ERROR_PASSWORD_EXPIRED, "Password Expired, and no callback specified", 0);
			return -1;
		}
	}

	/* FAILURE */
	LIBSSH2_FREE(session, data);
	return -1;
}
/* }}} */

/* {{{ libssh2_file_read_publickey
 * Read a public key from an id_???.pub style file
 */
static int libssh2_file_read_publickey(LIBSSH2_SESSION *session, unsigned char **method, unsigned long *method_len,
																 unsigned char **pubkeydata, unsigned long *pubkeydata_len,
																 const char *pubkeyfile)
{
	FILE *fd;
	char *pubkey = NULL, c, *sp1, *sp2, *tmp;
	int pubkey_len = 0, tmp_len;

#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Loading public key file: %s", pubkeyfile);
#endif
	/* Read Public Key */
	fd = fopen(pubkeyfile, "r");
	if (!fd) {
		libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to open public key file", 0);
		return -1;
	}
	while (!feof(fd) && (c = fgetc(fd)) != '\r' && c != '\n')	pubkey_len++;
	if (c != '\r' || c != '\n')
                pubkey_len--;
	rewind(fd);

	if (pubkey_len <= 1) {
		libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid data in public key file", 0);
		fclose(fd);
		return -1;
	}

	pubkey = LIBSSH2_ALLOC(session, pubkey_len);
	if (!pubkey) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for public key data", 0);
		fclose(fd);
		return -1;
	}
	if (fread(pubkey, 1, pubkey_len, fd) != pubkey_len) {
		libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to read public key from file", 0);
		LIBSSH2_FREE(session, pubkey);
		fclose(fd);
		return -1;
	}
	fclose(fd);
	while (pubkey_len && (pubkey[pubkey_len-1] == '\r' || pubkey[pubkey_len-1] == '\n')) pubkey_len--;

	if (!pubkey_len) {
		libssh2_error(session, LIBSSH2_ERROR_FILE, "Missing public key data", 0);
		LIBSSH2_FREE(session, pubkey);
		return -1;
	}

	if ((sp1 = memchr(pubkey, ' ', pubkey_len)) == NULL) {
		libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid public key data", 0);
		LIBSSH2_FREE(session, pubkey);
		return -1;
	}
	/* Wasting some bytes here (okay, more than some),
	 * but since it's likely to be freed soon anyway, 
	 * we'll just avoid the extra free/alloc and call it a wash */
	*method = pubkey;
	*method_len = sp1 - pubkey;

	sp1++;

	if ((sp2 = memchr(sp1, ' ', pubkey_len - *method_len)) == NULL) {
		/* Assume that the id string is missing, but that it's okay */
		sp2 = pubkey + pubkey_len;
	}

	if (libssh2_base64_decode(session, &tmp, &tmp_len, sp1, sp2 - sp1)) {
		libssh2_error(session, LIBSSH2_ERROR_FILE, "Invalid key data, not base64 encoded", 0);
		LIBSSH2_FREE(session, pubkey);
		return -1;
	}
	*pubkeydata = tmp;
	*pubkeydata_len = tmp_len;

	return 0;
}
/* }}} */

/* {{{ libssh2_file_read_privatekey
 * Read a PEM encoded private key from an id_??? style file
 */
static int libssh2_file_read_privatekey(LIBSSH2_SESSION *session,	LIBSSH2_HOSTKEY_METHOD **hostkey_method, void **hostkey_abstract,
																	const char *method, int method_len,
																	const char *privkeyfile, const char *passphrase)
{
	LIBSSH2_HOSTKEY_METHOD **hostkey_methods_avail = libssh2_hostkey_methods();

#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Loading private key file: %s", privkeyfile);
#endif
	*hostkey_method = NULL;
	*hostkey_abstract = NULL;
	while (*hostkey_methods_avail && (*hostkey_methods_avail)->name) {
		if ((*hostkey_methods_avail)->initPEM &&
			strncmp((*hostkey_methods_avail)->name, method, method_len) == 0) {
			*hostkey_method = *hostkey_methods_avail;
			break;
		}
		hostkey_methods_avail++;
	}
	if (!*hostkey_method) {
		libssh2_error(session, LIBSSH2_ERROR_METHOD_NONE, "No handler for specified private key", 0);
		return -1;
	}

	if ((*hostkey_method)->initPEM(session, privkeyfile, passphrase, hostkey_abstract)) {
		libssh2_error(session, LIBSSH2_ERROR_FILE, "Unable to initialize private key from file", 0);
		return -1;
	}

	return 0;
} 
/* }}} */

/* {{{ libssh2_userauth_hostbased_fromfile_ex
 * Authenticate using a keypair found in the named files
 */
LIBSSH2_API int libssh2_userauth_hostbased_fromfile_ex(LIBSSH2_SESSION *session, const char *username, int username_len,
                                                                                 const char *publickey, const char *privatekey,
                                                                                 const char *passphrase,
																				 const char *hostname, int hostname_len,
																				 const char *local_username, int local_username_len)
{
	LIBSSH2_HOSTKEY_METHOD *privkeyobj;
	void *abstract;
	unsigned char buf[5];
	struct iovec datavec[4];
	unsigned char *method, *pubkeydata, *packet, *s, *sig, *data, reply_codes[3] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, 0 };
	unsigned long method_len, pubkeydata_len, packet_len, sig_len, data_len;

	if (libssh2_file_read_publickey(session, &method, &method_len, &pubkeydata, &pubkeydata_len, publickey)) {
		return -1;
	}

	packet_len = username_len + method_len + hostname_len + local_username_len + pubkeydata_len + 48;
	/* packet_type(1) + username_len(4) + servicename_len(4) + service_name(14)"ssh-connection" + 
	 * authmethod_len(4) + authmethod(9)"hostbased" + method_len(4) + pubkeydata_len(4) + 
	 * local_username_len(4)
	 */
	/* Preallocate space for an overall length,  method name again,
	 * and the signature, which won't be any larger than the size of the publickeydata itself */
	s = packet = LIBSSH2_ALLOC(session, packet_len + 4 + (4 + method_len) + (4 + pubkeydata_len));

	*(s++) = SSH_MSG_USERAUTH_REQUEST;
	libssh2_htonu32(s, username_len);				s += 4;
	memcpy(s, username, username_len);				s += username_len;

	libssh2_htonu32(s, 14);							s += 4;
	memcpy(s, "ssh-connection", 14);				s += 14;

	libssh2_htonu32(s, 9);							s += 4;
	memcpy(s, "hostbased", 9);						s += 9;

	libssh2_htonu32(s, method_len);					s += 4;
	memcpy(s, method, method_len);					s += method_len;

	libssh2_htonu32(s, pubkeydata_len);				s += 4;
	memcpy(s, pubkeydata, pubkeydata_len);			s += pubkeydata_len;

	libssh2_htonu32(s, hostname_len);				s += 4;
	memcpy(s, hostname, hostname_len);				s += hostname_len;

	libssh2_htonu32(s, local_username_len);			s += 4;
	memcpy(s, local_username, local_username_len);	s += local_username_len;

	if (libssh2_file_read_privatekey(session, &privkeyobj, &abstract, method, method_len, privatekey, passphrase)) {
		LIBSSH2_FREE(session, method);
		LIBSSH2_FREE(session, packet);
		return -1;
	}

	libssh2_htonu32(buf, session->session_id_len);
	datavec[0].iov_base = buf;
	datavec[0].iov_len = 4;
	datavec[1].iov_base = session->session_id;
	datavec[1].iov_len = session->session_id_len;
	datavec[2].iov_base = packet;
	datavec[2].iov_len = packet_len;

	if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) {
		LIBSSH2_FREE(session, method);
		LIBSSH2_FREE(session, packet);
		if (privkeyobj->dtor) {
			privkeyobj->dtor(session, &abstract);
		}
		return -1;
	}

	if (privkeyobj->dtor) {
		privkeyobj->dtor(session, &abstract);
	}

	if (sig_len > pubkeydata_len ) {
		/* Should *NEVER* happen, but...well.. better safe than sorry */
		packet = LIBSSH2_REALLOC(session, packet, packet_len + 4 + (4 + method_len) + (4 + sig_len)); /* PK sigblob */
		if (!packet) {
			libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Failed allocating additional space for userauth-hostbased packet", 0);
			LIBSSH2_FREE(session, method);
			return -1;
		}
	}

	s = packet + packet_len;

	libssh2_htonu32(s, 4 + method_len + 4 + sig_len);	s += 4;

	libssh2_htonu32(s, method_len);						s += 4;
	memcpy(s, method, method_len);						s += method_len;
	LIBSSH2_FREE(session, method);

	libssh2_htonu32(s, sig_len);						s += 4;
	memcpy(s, sig, sig_len);							s += sig_len;
	LIBSSH2_FREE(session, sig);

#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting hostbased authentication");
#endif
	if (libssh2_packet_write(session, packet, s - packet)) {
		libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-hostbased request", 0);
		LIBSSH2_FREE(session, packet);
		return -1;
	}
	LIBSSH2_FREE(session, packet);

	if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) {
		return -1;
	}

	if (data[0] == SSH_MSG_USERAUTH_SUCCESS) {
#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Hostbased authentication successful");
#endif
		/* We are us and we've proved it. */
		LIBSSH2_FREE(session, data);
		session->state |= LIBSSH2_STATE_AUTHENTICATED;
		return 0;
	}

	/* This public key is not allowed for this user on this server */
	LIBSSH2_FREE(session, data);
	libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, "Invalid signature for supplied public key, or bad username/public key combination", 0);
	return -1;
}
/* }}} */

/* {{{ libssh2_userauth_publickey_fromfile_ex
 * Authenticate using a keypair found in the named files
 */
LIBSSH2_API int libssh2_userauth_publickey_fromfile_ex(LIBSSH2_SESSION *session, const char *username, int username_len,
                                                                                 const char *publickey, const char *privatekey,
                                                                                 const char *passphrase)
{
	LIBSSH2_HOSTKEY_METHOD *privkeyobj;
	void *abstract;
	unsigned char buf[5];
	struct iovec datavec[4];
	unsigned char *method, *pubkeydata, *packet, *s, *b, *sig, *data;
	unsigned char reply_codes[4] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_PK_OK, 0 };
	unsigned long method_len, pubkeydata_len, packet_len, sig_len, data_len;

	if (libssh2_file_read_publickey(session, &method, &method_len, &pubkeydata, &pubkeydata_len, publickey)) {
		return -1;
	}

	packet_len = username_len + method_len + pubkeydata_len + 45;	/* packet_type(1) + username_len(4) + servicename_len(4) + 
																	   service_name(14)"ssh-connection" + authmethod_len(4) + 
																	   authmethod(9)"publickey" + sig_included(1)'\0' + 
																	   algmethod_len(4) + publickey_len(4) */
	/* Preallocate space for an overall length,  method name again,
	 * and the signature, which won't be any larger than the size of the publickeydata itself */
	s = packet = LIBSSH2_ALLOC(session, packet_len + 4 + (4 + method_len) + (4 + pubkeydata_len));

	*(s++) = SSH_MSG_USERAUTH_REQUEST;
	libssh2_htonu32(s, username_len);				s += 4;
	memcpy(s, username, username_len);				s += username_len;

	libssh2_htonu32(s, 14);							s += 4;
	memcpy(s, "ssh-connection", 14);				s += 14;

	libssh2_htonu32(s, 9);							s += 4;
	memcpy(s, "publickey", 9);						s += 9;

	b = s;
	*(s++) = 0; /* Not sending signature with *this* packet */

	libssh2_htonu32(s, method_len);					s += 4;
	memcpy(s, method, method_len);					s += method_len;

	libssh2_htonu32(s, pubkeydata_len);				s += 4;
	memcpy(s, pubkeydata, pubkeydata_len);			s += pubkeydata_len;

#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting publickey authentication");
#endif
	if (libssh2_packet_write(session, packet, packet_len)) {
		libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-publickey request", 0);
		LIBSSH2_FREE(session, packet);
		LIBSSH2_FREE(session, method);
		LIBSSH2_FREE(session, pubkeydata);
		return -1;
	}

	if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) {
		LIBSSH2_FREE(session, packet);
		LIBSSH2_FREE(session, method);
		LIBSSH2_FREE(session, pubkeydata);
		return -1;
	}

	if (data[0] == SSH_MSG_USERAUTH_SUCCESS) {
#ifdef LIBSSH2_DEBUG_USERAUTH
		_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Pubkey authentication prematurely successful");
#endif
		/* God help any SSH server that allows an UNVERIFIED public key to validate the user */
		LIBSSH2_FREE(session, data);
		LIBSSH2_FREE(session, packet);
		LIBSSH2_FREE(session, method);
		LIBSSH2_FREE(session, pubkeydata);
		session->state |= LIBSSH2_STATE_AUTHENTICATED;
		return 0;
	}

	if (data[0] == SSH_MSG_USERAUTH_FAILURE) {
		/* This public key is not allowed for this user on this server */
		LIBSSH2_FREE(session, data);
		LIBSSH2_FREE(session, packet);
		LIBSSH2_FREE(session, method);
		LIBSSH2_FREE(session, pubkeydata);
		libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNRECOGNIZED, "Username/PublicKey combination invalid", 0);
		return -1;
	}

	/* Semi-Success! */
	LIBSSH2_FREE(session, data);
	LIBSSH2_FREE(session, pubkeydata);

	if (libssh2_file_read_privatekey(session, &privkeyobj, &abstract, method, method_len, privatekey, passphrase)) {
		LIBSSH2_FREE(session, method);
		LIBSSH2_FREE(session, packet);
		return -1;
	}

	*b = 0xFF;

	libssh2_htonu32(buf, session->session_id_len);
	datavec[0].iov_base = buf;
	datavec[0].iov_len = 4;
	datavec[1].iov_base = session->session_id;
	datavec[1].iov_len = session->session_id_len;
	datavec[2].iov_base = packet;
	datavec[2].iov_len = packet_len;

	if (privkeyobj->signv(session, &sig, &sig_len, 3, datavec, &abstract)) {
		LIBSSH2_FREE(session, method);
		LIBSSH2_FREE(session, packet);
		if (privkeyobj->dtor) {
			privkeyobj->dtor(session, &abstract);
		}
		return -1;
	}

	if (privkeyobj->dtor) {
		privkeyobj->dtor(session, &abstract);
	}

	if (sig_len > pubkeydata_len) {
		/* Should *NEVER* happen, but...well.. better safe than sorry */
		packet = LIBSSH2_REALLOC(session, packet, packet_len + 4 + (4 + method_len) + (4 + sig_len)); /* PK sigblob */
		if (!packet) {
			libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Failed allocating additional space for userauth-publickey packet", 0);
			LIBSSH2_FREE(session, method);
			return -1;
		}
	}

	s = packet + packet_len;

	libssh2_htonu32(s, 4 + method_len + 4 + sig_len);	s += 4;

	libssh2_htonu32(s, method_len);						s += 4;
	memcpy(s, method, method_len);						s += method_len;
	LIBSSH2_FREE(session, method);

	libssh2_htonu32(s, sig_len);						s += 4;
	memcpy(s, sig, sig_len);							s += sig_len;
	LIBSSH2_FREE(session, sig);

#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting publickey authentication -- phase 2");
#endif
	if (libssh2_packet_write(session, packet, s - packet)) {
		libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-publickey request", 0);
		LIBSSH2_FREE(session, packet);
		return -1;
	}
	LIBSSH2_FREE(session, packet);

	/* PK_OK is no longer valid */
	reply_codes[2] = 0;

	if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) {
		return -1;
	}

	if (data[0] == SSH_MSG_USERAUTH_SUCCESS) {
#ifdef LIBSSH2_DEBUG_USERAUTH
		_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Publickey authentication successful");
#endif
		/* We are us and we've proved it. */
		LIBSSH2_FREE(session, data);
		session->state |= LIBSSH2_STATE_AUTHENTICATED;
		return 0;
	}

	/* This public key is not allowed for this user on this server */
	LIBSSH2_FREE(session, data);
	libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_UNVERIFIED, "Invalid signature for supplied public key, or bad username/public key combination", 0);
	return -1;
}
/* }}} */

/* {{{ libssh2_userauth_keyboard_interactive
 * Authenticate using a challenge-response authentication
 */
LIBSSH2_API int libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION *session, const char *username, int username_len,
														 LIBSSH2_USERAUTH_KBDINT_RESPONSE_FUNC((*response_callback)))
{
	unsigned char *s, *data; /* packet */
	unsigned long packet_len;

	packet_len = 1         /* byte      SSH_MSG_USERAUTH_REQUEST */
		+ 4 + username_len /* string    user name (ISO-10646 UTF-8, as defined in [RFC-3629]) */
		+ 4 + 14           /* string    service name (US-ASCII) */
		+ 4 + 20           /* string    "keyboard-interactive" (US-ASCII) */
		+ 4 + 0            /* string    language tag (as defined in [RFC-3066]) */
		+ 4 + 0            /* string    submethods (ISO-10646 UTF-8) */
		;

	if (!(data = s = LIBSSH2_ALLOC(session, packet_len))) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive authentication", 0);
		return -1;
	}

	*s++ = SSH_MSG_USERAUTH_REQUEST;

	/* user name */
	libssh2_htonu32(s, username_len);										s += 4;
	memcpy(s, username, username_len);										s += username_len;

	/* service name */
	libssh2_htonu32(s, sizeof("ssh-connection") - 1);						s += 4;
	memcpy(s, "ssh-connection", sizeof("ssh-connection") - 1);				s += sizeof("ssh-connection") - 1;

	/* "keyboard-interactive" */
	libssh2_htonu32(s, sizeof("keyboard-interactive") - 1);					s += 4;
	memcpy(s, "keyboard-interactive", sizeof("keyboard-interactive") - 1);	s += sizeof("keyboard-interactive") - 1;

	/* language tag */
	libssh2_htonu32(s, 0);													s += 4;

	/* submethods */
	libssh2_htonu32(s, 0);													s += 4;

#ifdef LIBSSH2_DEBUG_USERAUTH
	_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Attempting keyboard-interactive authentication");
#endif
	if (libssh2_packet_write(session, data, packet_len)) {
		libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send keyboard-interactive request", 0);
		LIBSSH2_FREE(session, data);
		return -1;
	}
	LIBSSH2_FREE(session, data);

	for (;;) {
		unsigned char reply_codes[4] = { SSH_MSG_USERAUTH_SUCCESS, SSH_MSG_USERAUTH_FAILURE, SSH_MSG_USERAUTH_INFO_REQUEST, 0 };
		unsigned int auth_name_len;
		char* auth_name = NULL;
		unsigned auth_instruction_len;
		char* auth_instruction = NULL;
		unsigned int language_tag_len;
		unsigned long data_len;
		unsigned int num_prompts = 0;
		unsigned int i;
		int auth_failure = 1;
		LIBSSH2_USERAUTH_KBDINT_PROMPT* prompts = NULL;
		LIBSSH2_USERAUTH_KBDINT_RESPONSE* responses = NULL;

		if (libssh2_packet_requirev(session, reply_codes, &data, &data_len)) {
			return -1;
		}

		if (data[0] == SSH_MSG_USERAUTH_SUCCESS) {
#ifdef LIBSSH2_DEBUG_USERAUTH
			_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Keyboard-interactive authentication successful");
#endif
			LIBSSH2_FREE(session, data);
			session->state |= LIBSSH2_STATE_AUTHENTICATED;
			return 0;
		}

		if (data[0] == SSH_MSG_USERAUTH_FAILURE) {
			LIBSSH2_FREE(session, data);
			return -1;
		}

		/* server requested PAM-like conversation */

		s = data + 1;

		/* string    name (ISO-10646 UTF-8) */
		auth_name_len = libssh2_ntohu32(s);								s += 4;
		if (!(auth_name = LIBSSH2_ALLOC(session, auth_name_len))) {
			libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive 'name' request field", 0);
			goto cleanup;
		}
		memcpy(auth_name, s, auth_name_len);							s += auth_name_len;

		/* string    instruction (ISO-10646 UTF-8) */
		auth_instruction_len = libssh2_ntohu32(s); s += 4;
		if (!(auth_instruction = LIBSSH2_ALLOC(session, auth_instruction_len))) {
			libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive 'instruction' request field", 0);
			goto cleanup;
		}
		memcpy(auth_instruction, s, auth_instruction_len);				s += auth_instruction_len;

		/* string    language tag (as defined in [RFC-3066]) */
		language_tag_len = libssh2_ntohu32(s);							s += 4;
		/* ignoring this field as deprecated */							s += language_tag_len;

		/* int       num-prompts */
		num_prompts = libssh2_ntohu32(s);								s += 4;

		prompts = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * num_prompts);
		if (!prompts) {
			libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive prompts array", 0);
			goto cleanup;
		}
		memset(prompts, 0, sizeof(LIBSSH2_USERAUTH_KBDINT_PROMPT) * num_prompts);

		responses = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * num_prompts);
		if (!responses) {
			libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive responses array", 0);
			goto cleanup;
		}
		memset(responses, 0, sizeof(LIBSSH2_USERAUTH_KBDINT_RESPONSE) * num_prompts);

		for(i = 0; i != num_prompts; ++i) {
			/* string    prompt[1] (ISO-10646 UTF-8) */
			prompts[i].length = libssh2_ntohu32(s);						s += 4;
			if (!(prompts[i].text = LIBSSH2_ALLOC(session, prompts[i].length))) {
				libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive prompt message", 0);
				goto cleanup;
			}
			memcpy(prompts[i].text, s, prompts[i].length);				s += prompts[i].length;

			/* boolean   echo[1] */
			prompts[i].echo = *s++;
		}

		response_callback(auth_name, auth_name_len,  auth_instruction, auth_instruction_len, num_prompts, prompts, responses, &session->abstract);

#ifdef LIBSSH2_DEBUG_USERAUTH
		_libssh2_debug(session, LIBSSH2_DBG_AUTH, "Keyboard-interactive response callback function invoked");
#endif

		packet_len = 1 /* byte      SSH_MSG_USERAUTH_INFO_RESPONSE */
			+ 4        /* int       num-responses */
			;

		for (i = 0; i != num_prompts; ++i) {
			packet_len += 4 + responses[i].length; /* string    response[1] (ISO-10646 UTF-8) */
		}

		if (!(data = s = LIBSSH2_ALLOC(session, packet_len))) {
			libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for keyboard-interactive response packet", 0);
			goto cleanup;
		}

		*s = SSH_MSG_USERAUTH_INFO_RESPONSE; s++;
		libssh2_htonu32(s, num_prompts);								s += 4;

		for (i = 0; i != num_prompts; ++i) {
			libssh2_htonu32(s, responses[i].length);					s += 4;
			memcpy(s, responses[i].text, responses[i].length);			s += responses[i].length;
		}

		if (libssh2_packet_write(session, data, packet_len)) {
			libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send userauth-keyboard-interactive request", 0);
			goto cleanup;
		}

		auth_failure = 0;

	cleanup:
		/* It's safe to clean all the data here, because unallocated pointers
		 * are filled by zeroes
		 */

		LIBSSH2_FREE(session, data);

		if (prompts) {
			for (i = 0; i != num_prompts; ++i) {
				LIBSSH2_FREE(session, prompts[i].text);
			}
		}

		if (responses) {
			for (i = 0; i != num_prompts; ++i) {
				LIBSSH2_FREE(session, responses[i].text);
			}
		}

		LIBSSH2_FREE(session, prompts);
		LIBSSH2_FREE(session, responses);

		if (auth_failure) {
			return -1;
		}
	}
}
/* }}} */
