/* 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"
#include "libssh2_publickey.h"

struct _LIBSSH2_PUBLICKEY {
	LIBSSH2_CHANNEL *channel;
	unsigned long version;
};

#define LIBSSH2_PUBLICKEY_VERSION				2

/* Numericised response codes -- Not IETF standard, just a local representation */
#define LIBSSH2_PUBLICKEY_RESPONSE_STATUS		0
#define LIBSSH2_PUBLICKEY_RESPONSE_VERSION		1
#define LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY	2

typedef struct _LIBSSH2_PUBLICKEY_CODE_LIST {
	int code;
	char *name;
	int name_len;
} LIBSSH2_PUBLICKEY_CODE_LIST;

static LIBSSH2_PUBLICKEY_CODE_LIST libssh2_publickey_response_codes[] = {
	{ LIBSSH2_PUBLICKEY_RESPONSE_STATUS, "status", sizeof("status") - 1 },
	{ LIBSSH2_PUBLICKEY_RESPONSE_VERSION, "version", sizeof("version") - 1 },
	{ LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY, "publickey", sizeof("publickey") - 1 },
	{ 0, NULL, 0 }
};

/* PUBLICKEY status codes -- IETF defined */
#define LIBSSH2_PUBLICKEY_SUCCESS				0
#define LIBSSH2_PUBLICKEY_ACCESS_DENIED			1
#define LIBSSH2_PUBLICKEY_STORAGE_EXCEEDED		2
#define LIBSSH2_PUBLICKEY_VERSION_NOT_SUPPORTED	3
#define LIBSSH2_PUBLICKEY_KEY_NOT_FOUND			4
#define LIBSSH2_PUBLICKEY_KEY_NOT_SUPPORTED		5
#define LIBSSH2_PUBLICKEY_KEY_ALREADY_PRESENT	6
#define LIBSSH2_PUBLICKEY_GENERAL_FAILURE		7
#define LIBSSH2_PUBLICKEY_REQUEST_NOT_SUPPORTED	8

#define LIBSSH2_PUBLICKEY_STATUS_CODE_MAX		8

static LIBSSH2_PUBLICKEY_CODE_LIST libssh2_publickey_status_codes[] = {
	{ LIBSSH2_PUBLICKEY_SUCCESS,				"success",					sizeof("success") - 1 },
	{ LIBSSH2_PUBLICKEY_ACCESS_DENIED,			"access denied",			sizeof("access denied") - 1 },
	{ LIBSSH2_PUBLICKEY_STORAGE_EXCEEDED,		"storage exceeded",			sizeof("storage exceeded") - 1 },
	{ LIBSSH2_PUBLICKEY_VERSION_NOT_SUPPORTED,	"version not supported",	sizeof("version not supported") - 1 },
	{ LIBSSH2_PUBLICKEY_KEY_NOT_FOUND,			"key not found",			sizeof("key not found") - 1 },
	{ LIBSSH2_PUBLICKEY_KEY_NOT_SUPPORTED,		"key not supported",		sizeof("key not supported") - 1 },
	{ LIBSSH2_PUBLICKEY_KEY_ALREADY_PRESENT,	"key already present",		sizeof("key already present") - 1 },
	{ LIBSSH2_PUBLICKEY_GENERAL_FAILURE,		"general failure",			sizeof("general failure") - 1 },
	{ LIBSSH2_PUBLICKEY_REQUEST_NOT_SUPPORTED,	"request not supported",	sizeof("request not supported") - 1 },
	{ 0, NULL, 0 }
};

/* {{{ libssh2_publickey_status_error
 * Format an error message from a status code
 */
#define LIBSSH2_PUBLICKEY_STATUS_TEXT_START		"Publickey Subsystem Error: \""
#define LIBSSH2_PUBLICKEY_STATUS_TEXT_MID		"\" Server Resports: \""
#define LIBSSH2_PUBLICKEY_STATUS_TEXT_END		"\""
static void libssh2_publickey_status_error(LIBSSH2_PUBLICKEY *pkey, LIBSSH2_SESSION *session, int status, unsigned char *message, int message_len)
{
	char *status_text;
	int status_text_len;
	char *m, *s;
	int m_len;

	/* GENERAL_FAILURE got remapped between version 1 and 2 */
	if (status == 6 && pkey && pkey->version == 1) {
		status = 7;
	}

	if (status < 0 || status > LIBSSH2_PUBLICKEY_STATUS_CODE_MAX) {
		status_text = "unknown";
		status_text_len = sizeof("unknown") - 1;
	} else {
		status_text = libssh2_publickey_status_codes[status].name;
		status_text_len = libssh2_publickey_status_codes[status].name_len;
	}

	m_len = (sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_START) - 1) + status_text_len + 
			(sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_MID) - 1) + message_len +
			(sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_END) - 1);
	m = LIBSSH2_ALLOC(session, m_len + 1);
	if (!m) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for status message", 0);
		return;
	}
	s = m;
	memcpy(s, LIBSSH2_PUBLICKEY_STATUS_TEXT_START, sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_START) - 1);
															s += sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_START) - 1;
	memcpy(s, status_text, status_text_len);				s += status_text_len;
	memcpy(s, LIBSSH2_PUBLICKEY_STATUS_TEXT_MID, sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_MID) - 1);
															s += sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_MID) - 1;
	memcpy(s, message, message_len);						s += message_len;
	memcpy(s, LIBSSH2_PUBLICKEY_STATUS_TEXT_END, sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_END) - 1);
															s += sizeof(LIBSSH2_PUBLICKEY_STATUS_TEXT_END);
	libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, m, 1);
}
/* }}} */

/* {{{ libssh2_publickey_packet_receive
 * Read a packet from the subsystem
 */
static int libssh2_publickey_packet_receive(LIBSSH2_PUBLICKEY *pkey, unsigned char **data, unsigned long *data_len)
{
	LIBSSH2_CHANNEL *channel = pkey->channel;
	LIBSSH2_SESSION *session = channel->session;
	unsigned char buffer[4];
	unsigned long packet_len;
	unsigned char *packet;

	if (libssh2_channel_read(channel, buffer, 4) != 4) {
		libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Invalid response from publickey subsystem", 0);
		return -1;
	}

	packet_len = libssh2_ntohu32(buffer);
	packet = LIBSSH2_ALLOC(session, packet_len);
	if (!packet) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate publickey response buffer", 0);
		return -1;
	}

	if (libssh2_channel_read(channel, packet, packet_len) != packet_len) {
		libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for publickey subsystem response packet", 0);
		LIBSSH2_FREE(session, packet);
		return -1;
	}

	*data = packet;
	*data_len = packet_len;

	return 0;
}
/* }}} */

/* {{{ libssh2_publickey_response_id
 * Translate a string response name to a numeric code
 * Data will be incremented by 4 + response_len on success only
 */
static int libssh2_publickey_response_id(unsigned char **pdata, int data_len)
{
	unsigned long response_len;
	unsigned char *data = *pdata;
	LIBSSH2_PUBLICKEY_CODE_LIST *codes = libssh2_publickey_response_codes;

	if (data_len < 4) {
		/* Malformed response */
		return -1;
	}
	response_len = libssh2_ntohu32(data);			data += 4;			data_len -= 4;
	if (data_len < response_len) {
		/* Malformed response */
		return -1;
	}

	while (codes->name) {
		if (codes->name_len == response_len &&
			strncmp(codes->name, data, response_len) == 0) {
			*pdata = data + response_len;
			return codes->code;
		}
		codes++;
	}

	return -1;
}
/* }}} */

/* {{{ libssh2_publickey_response_success
 * Generic helper routine to wait for success response and nothing else
 */
static int libssh2_publickey_response_success(LIBSSH2_PUBLICKEY *pkey)
{
	LIBSSH2_SESSION *session = pkey->channel->session;
	unsigned char *data, *s;
	unsigned long data_len, response;

	while (1) {
		if (libssh2_publickey_packet_receive(pkey, &data, &data_len)) {
			libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from publickey subsystem", 0);
			return -1;
		}

		s = data;
		if ((response = libssh2_publickey_response_id(&s, data_len)) < 0) {
			libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Invalid publickey subsystem response code", 0);
			LIBSSH2_FREE(session, data);
			return -1;
		}

		switch (response) {
			case LIBSSH2_PUBLICKEY_RESPONSE_STATUS:
			/* Error, or processing complete */
			{
				unsigned long status, descr_len, lang_len;
				unsigned char *descr, *lang;
				
				status = libssh2_ntohu32(s);					s += 4;
				descr_len = libssh2_ntohu32(s);					s += 4;
				descr = s;										s += descr_len;
				lang_len = libssh2_ntohu32(s);					s += 4;
				lang = s;										s += lang_len;

				if (s > data + data_len) {
					libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Malformed publickey subsystem packet", 0);
					LIBSSH2_FREE(session, data);
					return -1;
				}

				if (status == LIBSSH2_PUBLICKEY_SUCCESS) {
					LIBSSH2_FREE(session, data);
					return 0;
				}

				libssh2_publickey_status_error(pkey, session, status, descr, descr_len);
				LIBSSH2_FREE(session, data);
				return -1;
			}
			default:
				/* Unknown/Unexpected */
				libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Unexpected publickey subsystem response, ignoring", 0);
				LIBSSH2_FREE(session, data);
				data = NULL;
		}
	}
	/* never reached, but include `return` to silence compiler warnings */
	return -1;
}
/* }}} */


/* *****************
   * Publickey API *
   ***************** */

/* {{{ libssh2_publickey_init
 * Startup the publickey subsystem
 */
LIBSSH2_API LIBSSH2_PUBLICKEY *libssh2_publickey_init(LIBSSH2_SESSION *session)
{
	LIBSSH2_PUBLICKEY *pkey = NULL;
	LIBSSH2_CHANNEL *channel = NULL;
	unsigned char buffer[19];
	/*	packet_len(4) + 
		version_len(4) + 
		"version"(7) + 
		version_num(4) */
	unsigned char *s, *data = NULL;
	unsigned long data_len;
	int response;

#ifdef LIBSSH2_DEBUG_PUBLICKEY
	_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Initializing publickey subsystem");
#endif

	channel = libssh2_channel_open_session(session);
	if (!channel) {
		libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to startup channel", 0);
		goto err_exit;
	}
	if (libssh2_channel_subsystem(channel, "publickey")) {
		libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE, "Unable to request publickey subsystem", 0);
		goto err_exit;
	}

	libssh2_channel_set_blocking(channel, 1);
	libssh2_channel_handle_extended_data(channel, LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE);

	pkey = LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PUBLICKEY));
	if (!pkey) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate a new publickey structure", 0);
		goto err_exit;
	}
	pkey->channel = channel;
	pkey->version = 0;

	s = buffer;
	libssh2_htonu32(s, 4 + (sizeof("version") - 1) + 4);	s += 4;
	libssh2_htonu32(s, sizeof("version") - 1);				s += 4;
	memcpy(s, "version", sizeof("version") - 1);			s += sizeof("version") - 1;
	libssh2_htonu32(s, LIBSSH2_PUBLICKEY_VERSION);			s += 4;

#ifdef LIBSSH2_DEBUG_PUBLICKEY
	_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Sending publickey version packet advertising version %d support", (int)LIBSSH2_PUBLICKEY_VERSION);
#endif
    if ((s - buffer) != libssh2_channel_write(channel, buffer, (s - buffer))) {
        libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send publickey version packet", 0);
		goto err_exit;
    }

	while (1) {
		if (libssh2_publickey_packet_receive(pkey, &data, &data_len)) {
			libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from publickey subsystem", 0);
			goto err_exit;
		}

		s = data;
		if ((response = libssh2_publickey_response_id(&s, data_len)) < 0) {
			libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Invalid publickey subsystem response code", 0);
			goto err_exit;
		}

		switch (response) {
			case LIBSSH2_PUBLICKEY_RESPONSE_STATUS:
			/* Error */
			{
				unsigned long status, descr_len, lang_len;
				unsigned char *descr, *lang;
				
				status = libssh2_ntohu32(s);					s += 4;
				descr_len = libssh2_ntohu32(s);					s += 4;
				descr = s;										s += descr_len;
				lang_len = libssh2_ntohu32(s);					s += 4;
				lang = s;										s += lang_len;

				if (s > data + data_len) {
					libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Malformed publickey subsystem packet", 0);
					goto err_exit;
				}

				libssh2_publickey_status_error(NULL, session, status, descr, descr_len);
				goto err_exit;
			}
			case LIBSSH2_PUBLICKEY_RESPONSE_VERSION:
				/* What we want */
				pkey->version = libssh2_ntohu32(s);
				if (pkey->version > LIBSSH2_PUBLICKEY_VERSION) {
#ifdef LIBSSH2_DEBUG_PUBLICKEY
					_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Truncating remote publickey version from %lu", pkey->version);
#endif
					pkey->version = LIBSSH2_PUBLICKEY_VERSION;
				}
#ifdef LIBSSH2_DEBUG_PUBLICKEY
				_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Enabling publickey subsystem version %lu", pkey->version);
#endif
				LIBSSH2_FREE(session, data);
				return pkey;
			default:
				/* Unknown/Unexpected */
				libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Unexpected publickey subsystem response, ignoring", 0);
				LIBSSH2_FREE(session, data);
				data = NULL;
		}
	}

	/* Never reached except by direct goto */
 err_exit:
	if (channel) {
		libssh2_channel_close(channel);
	}
	if (pkey) {
		LIBSSH2_FREE(session, pkey);
	}
	if (data) {
		LIBSSH2_FREE(session, data);
	}
	return NULL;
}
/* }}} */

/* {{{ libssh2_publickey_add_ex
 * Add a new public key entry
 */
LIBSSH2_API int libssh2_publickey_add_ex(LIBSSH2_PUBLICKEY *pkey, const unsigned char *name, unsigned long name_len,
															const unsigned char *blob, unsigned long blob_len, char overwrite,
															unsigned long num_attrs, libssh2_publickey_attribute attrs[])
{
	LIBSSH2_CHANNEL *channel = pkey->channel;
	LIBSSH2_SESSION *session = channel->session;
	unsigned char *packet = NULL, *s;
	unsigned long i, packet_len = 19 + name_len + blob_len;
	unsigned char *comment = NULL;
	unsigned long comment_len = 0;
	/*	packet_len(4) +
		add_len(4) +
		"add"(3) +
		name_len(4) +
		{name}
		blob_len(4) +
		{blob} */

#ifdef LIBSSH2_DEBUG_PUBLICKEY
	_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Adding %s pubickey", name);
#endif

	if (pkey->version == 1) {
		for(i = 0; i < num_attrs; i++) {
			/* Search for a comment attribute */
			if (attrs[i].name_len == (sizeof("comment") - 1) &&
				strncmp(attrs[i].name, "comment", sizeof("comment") - 1) == 0) {
				comment = attrs[i].value;
				comment_len = attrs[i].value_len;
				break;
			}
		}
		packet_len += 4 + comment_len;
	} else {
		packet_len += 5; /* overwrite(1) + attribute_count(4) */
		for(i = 0; i < num_attrs; i++) {
			packet_len += 9 + attrs[i].name_len + attrs[i].value_len;
			/* name_len(4) + value_len(4) + mandatory(1) */
		}
	}

	packet = LIBSSH2_ALLOC(session, packet_len);
	if (!packet) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey \"add\" packet", 0);
		return -1;
	}

	s = packet;
	libssh2_htonu32(s, packet_len - 4);						s += 4;
	libssh2_htonu32(s, sizeof("add") - 1);					s += 4;
	memcpy(s, "add", sizeof("add") - 1);					s += sizeof("add") - 1;
	if (pkey->version == 1) {
		libssh2_htonu32(s, comment_len);					s += 4;
		if (comment) {
			memcpy(s, comment, comment_len);				s += comment_len;
		}

		libssh2_htonu32(s, name_len);						s += 4;
		memcpy(s, name, name_len);							s += name_len;
		libssh2_htonu32(s, blob_len);						s += 4;
		memcpy(s, blob, blob_len);							s += blob_len;
	} else {
		/* Version == 2 */

		libssh2_htonu32(s, name_len);						s += 4;
		memcpy(s, name, name_len);							s += name_len;
		libssh2_htonu32(s, blob_len);						s += 4;
		memcpy(s, blob, blob_len);							s += blob_len;
		*(s++) = overwrite ? 0xFF : 0;
		libssh2_htonu32(s, num_attrs);						s += 4;
		for(i = 0; i < num_attrs; i++) {
			libssh2_htonu32(s, attrs[i].name_len);			s += 4;
			memcpy(s, attrs[i].name, attrs[i].name_len);	s += attrs[i].name_len;
			libssh2_htonu32(s, attrs[i].value_len);			s += 4;
			memcpy(s, attrs[i].value, attrs[i].value_len);	s += attrs[i].value_len;
			*(s++) = attrs[i].mandatory ? 0xFF : 0;
		}
	}

#ifdef LIBSSH2_DEBUG_PUBLICKEY
	_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Sending publickey \"add\" packet: type=%s blob_len=%ld num_attrs=%ld", name, blob_len, num_attrs);
#endif
    if ((s - packet) != libssh2_channel_write(channel, packet, (s - packet))) {
        libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send publickey add packet", 0);
		LIBSSH2_FREE(session, packet);
		return -1;
    }
	LIBSSH2_FREE(session, packet);
	packet = NULL;

	return libssh2_publickey_response_success(pkey);
}
/* }}} */

/* {{{ libssh2_publickey_remove_ex
 * Remove an existing publickey so that authentication can no longer be performed using it
 */
LIBSSH2_API int libssh2_publickey_remove_ex(LIBSSH2_PUBLICKEY *pkey, const unsigned char *name, unsigned long name_len,
                                                            const unsigned char *blob, unsigned long blob_len)
{
	LIBSSH2_CHANNEL *channel = pkey->channel;
	LIBSSH2_SESSION *session = channel->session;
	unsigned char *s, *packet = NULL;
	unsigned long packet_len = 22 + name_len + blob_len;
	/*	packet_len(4) + 
		remove_len(4) +
		"remove"(6) +
		name_len(4) +
		{name}
		blob_len(4) +
		{blob} */

	packet = LIBSSH2_ALLOC(session, packet_len);
	if (!packet) {
		libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey \"remove\" packet", 0);
		return -1;
	}

	s = packet;
	libssh2_htonu32(s, packet_len - 4);							s += 4;
	libssh2_htonu32(s, sizeof("remove") - 1);					s += 4;
	memcpy(s, "remove", sizeof("remove") - 1);					s += sizeof("remove") - 1;
	libssh2_htonu32(s, name_len);								s += 4;
	memcpy(s, name, name_len);									s += name_len;
	libssh2_htonu32(s, blob_len);								s += 4;
	memcpy(s, blob, blob_len);									s += blob_len;

#ifdef LIBSSH2_DEBUG_PUBLICKEY
	_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Sending publickey \"remove\" packet: type=%s blob_len=%ld", name, blob_len);
#endif
    if ((s - packet) != libssh2_channel_write(channel, packet, (s - packet))) {
        libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send publickey remove packet", 0);
		LIBSSH2_FREE(session, packet);
		return -1;
    }
	LIBSSH2_FREE(session, packet);
	packet = NULL;

	return libssh2_publickey_response_success(pkey);
}
/* }}} */

/* {{{ libssh2_publickey_list_fetch
 * Fetch a list of supported public key from a server
 */
LIBSSH2_API int libssh2_publickey_list_fetch(LIBSSH2_PUBLICKEY *pkey, unsigned long *num_keys, libssh2_publickey_list **pkey_list)
{
	LIBSSH2_CHANNEL *channel = pkey->channel;
	LIBSSH2_SESSION *session = channel->session;
	libssh2_publickey_list *list = NULL;
	unsigned char *s, buffer[12], *data = NULL;
	unsigned long buffer_len = 12, keys = 0, max_keys = 0, data_len, i, response;
	/*	packet_len(4) + 
		list_len(4) +
		"list"(4) */

	s = buffer;
	libssh2_htonu32(s, buffer_len - 4);							s += 4;
	libssh2_htonu32(s, sizeof("list") - 1);						s += 4;
	memcpy(s, "list", sizeof("list") - 1);						s += sizeof("list") - 1;

#ifdef LIBSSH2_DEBUG_PUBLICKEY
	_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY, "Sending publickey \"list\" packet");
#endif
    if ((s - buffer) != libssh2_channel_write(channel, buffer, (s - buffer))) {
        libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND, "Unable to send publickey list packet", 0);
		return -1;
    }

	while (1) {
		if (libssh2_publickey_packet_receive(pkey, &data, &data_len)) {
			libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT, "Timeout waiting for response from publickey subsystem", 0);
			goto err_exit;
		}

		s = data;
		if ((response = libssh2_publickey_response_id(&s, data_len)) < 0) {
			libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Invalid publickey subsystem response code", 0);
			goto err_exit;
		}

		switch (response) {
			case LIBSSH2_PUBLICKEY_RESPONSE_STATUS:
			/* Error, or processing complete */
			{
				unsigned long status, descr_len, lang_len;
				unsigned char *descr, *lang;
				
				status = libssh2_ntohu32(s);					s += 4;
				descr_len = libssh2_ntohu32(s);					s += 4;
				descr = s;										s += descr_len;
				lang_len = libssh2_ntohu32(s);					s += 4;
				lang = s;										s += lang_len;

				if (s > data + data_len) {
					libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Malformed publickey subsystem packet", 0);
					goto err_exit;
				}

				if (status == LIBSSH2_PUBLICKEY_SUCCESS) {
					LIBSSH2_FREE(session, data);
					*pkey_list = list;
					*num_keys = keys;
					return 0;
				}

				libssh2_publickey_status_error(pkey, session, status, descr, descr_len);
				goto err_exit;
			}
			case LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY:
				/* What we want */
				if (keys >= max_keys) {
					/* Grow the key list if necessary */
					max_keys += 8;
					list = LIBSSH2_REALLOC(session, list, (max_keys + 1) * sizeof(libssh2_publickey_list));
					if (!list) {
						libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey list", 0);
						goto err_exit;
					}
				}
				if (pkey->version == 1) {
					unsigned long comment_len;

					comment_len = libssh2_ntohu32(s);								s += 4;
					if (comment_len) {
						list[keys].num_attrs = 1;
						list[keys].attrs = LIBSSH2_ALLOC(session, sizeof(libssh2_publickey_attribute));
						if (!list[keys].attrs) {
							libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey attributes", 0);
							goto err_exit;
						}
						list[keys].attrs[0].name = "comment";
						list[keys].attrs[0].name_len = sizeof("comment") - 1;
						list[keys].attrs[0].value = s;
						list[keys].attrs[0].value_len = comment_len;
						list[keys].attrs[0].mandatory = 0;

						s += comment_len;
					} else {
						list[keys].num_attrs = 0;
						list[keys].attrs = NULL;
					}
					list[keys].name_len = libssh2_ntohu32(s);						s += 4;
					list[keys].name = s;											s += list[keys].name_len;
					list[keys].blob_len = libssh2_ntohu32(s);						s += 4;
					list[keys].blob = s;											s += list[keys].blob_len;
				} else {
					/* Version == 2 */
					list[keys].name_len = libssh2_ntohu32(s);						s += 4;
					list[keys].name = s;											s += list[keys].name_len;
					list[keys].blob_len = libssh2_ntohu32(s);						s += 4;
					list[keys].blob = s;											s += list[keys].blob_len;
					list[keys].num_attrs = libssh2_ntohu32(s);						s += 4;
					if (list[keys].num_attrs) {
						list[keys].attrs = LIBSSH2_ALLOC(session, list[keys].num_attrs * sizeof(libssh2_publickey_attribute));
						if (!list[keys].attrs) {
							libssh2_error(session, LIBSSH2_ERROR_ALLOC, "Unable to allocate memory for publickey attributes", 0);
							goto err_exit;
						}
						for(i = 0; i < list[keys].num_attrs; i++) {
							list[keys].attrs[i].name_len = libssh2_ntohu32(s);			s += 4;
							list[keys].attrs[i].name = s;								s += list[keys].attrs[i].name_len;
							list[keys].attrs[i].value_len = libssh2_ntohu32(s);			s += 4;
							list[keys].attrs[i].value = s;								s += list[keys].attrs[i].value_len;
							list[keys].attrs[i].mandatory = 0;	/* actually an ignored value */
						}
					} else {
						list[keys].attrs = NULL;
					}
				}
				list[keys].packet = data; /* To be FREEd in libssh2_publickey_list_free() */
				keys++;

				list[keys].packet = NULL; /* Terminate the list */
				data = NULL;
				break;
			default:
				/* Unknown/Unexpected */
				libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, "Unexpected publickey subsystem response, ignoring", 0);
				LIBSSH2_FREE(session, data);
		}
	}

	/* Only reached via explicit goto */
 err_exit:
	if (data) {
		LIBSSH2_FREE(session, data);
	}
	if (list) {
		libssh2_publickey_list_free(pkey, list);
	}
	return -1;
}
/* }}} */

/* {{{ libssh2_publickey_list_free
 * Free a previously fetched list of public keys
 */
LIBSSH2_API void libssh2_publickey_list_free(LIBSSH2_PUBLICKEY *pkey, libssh2_publickey_list *pkey_list)
{
	LIBSSH2_SESSION *session = pkey->channel->session;
	libssh2_publickey_list *p = pkey_list;

	while (p->packet) {
		if (p->attrs) {
			LIBSSH2_FREE(session, p->attrs);
		}
		LIBSSH2_FREE(session, p->packet);
		p++;
	}

	LIBSSH2_FREE(session, pkey_list);
}
/* }}} */

/* {{{ libssh2_publickey_shutdown
 * Shutdown the publickey subsystem
 */
LIBSSH2_API void libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey)
{
	LIBSSH2_SESSION *session = pkey->channel->session;

	libssh2_channel_free(pkey->channel);
	LIBSSH2_FREE(session, pkey);
}
/* }}} */
