summaryrefslogtreecommitdiff
path: root/libs/libssh2/src/publickey.c
diff options
context:
space:
mode:
authorGeorge Hazan <ghazan@miranda.im>2019-02-08 13:53:20 +0300
committerGeorge Hazan <ghazan@miranda.im>2019-02-08 13:53:20 +0300
commit9e3fe90d27add4dbca66c534e3a557502192d9bc (patch)
tree2b153bcb23cefe334415546e4043d5c8bc88377e /libs/libssh2/src/publickey.c
parenta41c3238e5dadf27751c5b0e50a5dbe6c52b3804 (diff)
libssh2 added to Miranda to support SFTP transfers in libcurl
Diffstat (limited to 'libs/libssh2/src/publickey.c')
-rw-r--r--libs/libssh2/src/publickey.c1059
1 files changed, 1059 insertions, 0 deletions
diff --git a/libs/libssh2/src/publickey.c b/libs/libssh2/src/publickey.c
new file mode 100644
index 0000000000..bfee0a8420
--- /dev/null
+++ b/libs/libssh2/src/publickey.c
@@ -0,0 +1,1059 @@
+/* Copyright (c) 2004-2007, Sara Golemon <sarag@libssh2.org>
+ * Copyright (c) 2010-2014 by Daniel Stenberg
+ * 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"
+#include "channel.h"
+#include "session.h"
+
+#define LIBSSH2_PUBLICKEY_VERSION 2
+
+/* Numericised response codes -- Not IETF, just 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;
+ const char *name;
+ int name_len;
+} LIBSSH2_PUBLICKEY_CODE_LIST;
+
+static const LIBSSH2_PUBLICKEY_CODE_LIST 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 const LIBSSH2_PUBLICKEY_CODE_LIST 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}
+};
+
+/*
+ * publickey_status_error
+ *
+ * Format an error message from a status code
+ */
+static void
+publickey_status_error(const LIBSSH2_PUBLICKEY *pkey,
+ LIBSSH2_SESSION *session, int status)
+{
+ const char *msg;
+
+ /* 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) {
+ msg = "unknown";
+ } else {
+ msg = publickey_status_codes[status].name;
+ }
+
+ _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL, msg);
+}
+
+/*
+ * publickey_packet_receive
+ *
+ * Read a packet from the subsystem
+ */
+static int
+publickey_packet_receive(LIBSSH2_PUBLICKEY * pkey,
+ unsigned char **data, size_t *data_len)
+{
+ LIBSSH2_CHANNEL *channel = pkey->channel;
+ LIBSSH2_SESSION *session = channel->session;
+ unsigned char buffer[4];
+ int rc;
+ *data = NULL; /* default to nothing returned */
+ *data_len = 0;
+
+ if (pkey->receive_state == libssh2_NB_state_idle) {
+ rc = _libssh2_channel_read(channel, 0, (char *) buffer, 4);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc != 4) {
+ return _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Invalid response from publickey subsystem");
+ }
+
+ pkey->receive_packet_len = _libssh2_ntohu32(buffer);
+ pkey->receive_packet =
+ LIBSSH2_ALLOC(session, pkey->receive_packet_len);
+ if (!pkey->receive_packet) {
+ return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate publickey response "
+ "buffer");
+ }
+
+ pkey->receive_state = libssh2_NB_state_sent;
+ }
+
+ if (pkey->receive_state == libssh2_NB_state_sent) {
+ rc = _libssh2_channel_read(channel, 0, (char *) pkey->receive_packet,
+ pkey->receive_packet_len);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc != (int)pkey->receive_packet_len) {
+ LIBSSH2_FREE(session, pkey->receive_packet);
+ pkey->receive_packet = NULL;
+ pkey->receive_state = libssh2_NB_state_idle;
+ return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT,
+ "Timeout waiting for publickey subsystem "
+ "response packet");
+ }
+
+ *data = pkey->receive_packet;
+ *data_len = pkey->receive_packet_len;
+ }
+
+ pkey->receive_state = libssh2_NB_state_idle;
+
+ return 0;
+}
+
+/* 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
+publickey_response_id(unsigned char **pdata, size_t data_len)
+{
+ size_t response_len;
+ unsigned char *data = *pdata;
+ const LIBSSH2_PUBLICKEY_CODE_LIST *codes = 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 ((unsigned long)codes->name_len == response_len &&
+ strncmp(codes->name, (char *) data, response_len) == 0) {
+ *pdata = data + response_len;
+ return codes->code;
+ }
+ codes++;
+ }
+
+ return -1;
+}
+
+/* publickey_response_success
+ *
+ * Generic helper routine to wait for success response and nothing else
+ */
+static int
+publickey_response_success(LIBSSH2_PUBLICKEY * pkey)
+{
+ LIBSSH2_SESSION *session = pkey->channel->session;
+ unsigned char *data, *s;
+ size_t data_len;
+ int response;
+
+ while (1) {
+ int rc = publickey_packet_receive(pkey, &data, &data_len);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT,
+ "Timeout waiting for response from "
+ "publickey subsystem");
+ }
+
+ s = data;
+ response = publickey_response_id(&s, data_len);
+
+ switch (response) {
+ case LIBSSH2_PUBLICKEY_RESPONSE_STATUS:
+ /* Error, or processing complete */
+ {
+ unsigned long status = _libssh2_ntohu32(s);
+
+ LIBSSH2_FREE(session, data);
+
+ if (status == LIBSSH2_PUBLICKEY_SUCCESS)
+ return 0;
+
+ publickey_status_error(pkey, session, status);
+ return -1;
+ }
+ default:
+ LIBSSH2_FREE(session, data);
+ if (response < 0) {
+ return _libssh2_error(session,
+ LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Invalid publickey subsystem response");
+ }
+ /* Unknown/Unexpected */
+ _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Unexpected publickey subsystem response");
+ data = NULL;
+ }
+ }
+ /* never reached, but include `return` to silence compiler warnings */
+ return -1;
+}
+
+/* *****************
+ * Publickey API *
+ ***************** */
+
+/*
+ * publickey_init
+ *
+ * Startup the publickey subsystem
+ */
+static LIBSSH2_PUBLICKEY *publickey_init(LIBSSH2_SESSION *session)
+{
+ int response;
+ int rc;
+
+ if (session->pkeyInit_state == libssh2_NB_state_idle) {
+ session->pkeyInit_data = NULL;
+ session->pkeyInit_pkey = NULL;
+ session->pkeyInit_channel = NULL;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY,
+ "Initializing publickey subsystem");
+
+ session->pkeyInit_state = libssh2_NB_state_allocated;
+ }
+
+ if (session->pkeyInit_state == libssh2_NB_state_allocated) {
+
+ session->pkeyInit_channel =
+ _libssh2_channel_open(session, "session",
+ sizeof("session") - 1,
+ LIBSSH2_CHANNEL_WINDOW_DEFAULT,
+ LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL,
+ 0);
+ if (!session->pkeyInit_channel) {
+ if (libssh2_session_last_errno(session) == LIBSSH2_ERROR_EAGAIN)
+ /* The error state is already set, so leave it */
+ return NULL;
+ _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
+ "Unable to startup channel");
+ goto err_exit;
+ }
+
+ session->pkeyInit_state = libssh2_NB_state_sent;
+ }
+
+ if (session->pkeyInit_state == libssh2_NB_state_sent) {
+ rc = _libssh2_channel_process_startup(session->pkeyInit_channel,
+ "subsystem",
+ sizeof("subsystem") - 1,
+ "publickey",
+ sizeof("publickey") - 1);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ _libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
+ "Would block starting publickey subsystem");
+ return NULL;
+ } else if (rc) {
+ _libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
+ "Unable to request publickey subsystem");
+ goto err_exit;
+ }
+
+ session->pkeyInit_state = libssh2_NB_state_sent1;
+ }
+
+ if (session->pkeyInit_state == libssh2_NB_state_sent1) {
+ unsigned char *s;
+ rc = _libssh2_channel_extended_data(session->pkeyInit_channel,
+ LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ _libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
+ "Would block starting publickey subsystem");
+ return NULL;
+ }
+
+ session->pkeyInit_pkey =
+ LIBSSH2_CALLOC(session, sizeof(LIBSSH2_PUBLICKEY));
+ if (!session->pkeyInit_pkey) {
+ _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate a new publickey structure");
+ goto err_exit;
+ }
+ session->pkeyInit_pkey->channel = session->pkeyInit_channel;
+ session->pkeyInit_pkey->version = 0;
+
+ s = session->pkeyInit_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);
+
+ session->pkeyInit_buffer_sent = 0;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY,
+ "Sending publickey advertising version %d support",
+ (int) LIBSSH2_PUBLICKEY_VERSION);
+
+ session->pkeyInit_state = libssh2_NB_state_sent2;
+ }
+
+ if (session->pkeyInit_state == libssh2_NB_state_sent2) {
+ rc = _libssh2_channel_write(session->pkeyInit_channel, 0,
+ session->pkeyInit_buffer,
+ 19 - session->pkeyInit_buffer_sent);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ _libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
+ "Would block sending publickey version packet");
+ return NULL;
+ } else if (rc < 0) {
+ _libssh2_error(session, rc,
+ "Unable to send publickey version packet");
+ goto err_exit;
+ }
+ session->pkeyInit_buffer_sent += rc;
+ if(session->pkeyInit_buffer_sent < 19) {
+ _libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
+ "Need to be called again to complete this");
+ return NULL;
+ }
+
+ session->pkeyInit_state = libssh2_NB_state_sent3;
+ }
+
+ if (session->pkeyInit_state == libssh2_NB_state_sent3) {
+ while (1) {
+ unsigned char *s;
+ rc = publickey_packet_receive(session->pkeyInit_pkey,
+ &session->pkeyInit_data,
+ &session->pkeyInit_data_len);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ _libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
+ "Would block waiting for response from "
+ "publickey subsystem");
+ return NULL;
+ } else if (rc) {
+ _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT,
+ "Timeout waiting for response from "
+ "publickey subsystem");
+ goto err_exit;
+ }
+
+ s = session->pkeyInit_data;
+ if ((response =
+ publickey_response_id(&s, session->pkeyInit_data_len)) < 0) {
+ _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Invalid publickey subsystem response code");
+ goto err_exit;
+ }
+
+ switch (response) {
+ case LIBSSH2_PUBLICKEY_RESPONSE_STATUS:
+ /* Error */
+ {
+ unsigned long status, descr_len, lang_len;
+
+ status = _libssh2_ntohu32(s);
+ s += 4;
+ descr_len = _libssh2_ntohu32(s);
+ s += 4;
+ /* description starts here */
+ s += descr_len;
+ lang_len = _libssh2_ntohu32(s);
+ s += 4;
+ /* lang starts here */
+ s += lang_len;
+
+ if (s >
+ session->pkeyInit_data + session->pkeyInit_data_len) {
+ _libssh2_error(session,
+ LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Malformed publickey subsystem packet");
+ goto err_exit;
+ }
+
+ publickey_status_error(NULL, session, status);
+
+ goto err_exit;
+ }
+
+ case LIBSSH2_PUBLICKEY_RESPONSE_VERSION:
+ /* What we want */
+ session->pkeyInit_pkey->version = _libssh2_ntohu32(s);
+ if (session->pkeyInit_pkey->version >
+ LIBSSH2_PUBLICKEY_VERSION) {
+ _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY,
+ "Truncate remote publickey version from %lu",
+ session->pkeyInit_pkey->version);
+ session->pkeyInit_pkey->version =
+ LIBSSH2_PUBLICKEY_VERSION;
+ }
+ _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY,
+ "Enabling publickey subsystem version %lu",
+ session->pkeyInit_pkey->version);
+ LIBSSH2_FREE(session, session->pkeyInit_data);
+ session->pkeyInit_data = NULL;
+ session->pkeyInit_state = libssh2_NB_state_idle;
+ return session->pkeyInit_pkey;
+
+ default:
+ /* Unknown/Unexpected */
+ _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Unexpected publickey subsystem response, "
+ "ignoring");
+ LIBSSH2_FREE(session, session->pkeyInit_data);
+ session->pkeyInit_data = NULL;
+ }
+ }
+ }
+
+ /* Never reached except by direct goto */
+ err_exit:
+ session->pkeyInit_state = libssh2_NB_state_sent4;
+ if (session->pkeyInit_channel) {
+ rc = _libssh2_channel_close(session->pkeyInit_channel);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ _libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
+ "Would block closing channel");
+ return NULL;
+ }
+ }
+ if (session->pkeyInit_pkey) {
+ LIBSSH2_FREE(session, session->pkeyInit_pkey);
+ session->pkeyInit_pkey = NULL;
+ }
+ if (session->pkeyInit_data) {
+ LIBSSH2_FREE(session, session->pkeyInit_data);
+ session->pkeyInit_data = NULL;
+ }
+ session->pkeyInit_state = libssh2_NB_state_idle;
+ return NULL;
+}
+
+/*
+ * libssh2_publickey_init
+ *
+ * Startup the publickey subsystem
+ */
+LIBSSH2_API LIBSSH2_PUBLICKEY *
+libssh2_publickey_init(LIBSSH2_SESSION *session)
+{
+ LIBSSH2_PUBLICKEY *ptr;
+
+ BLOCK_ADJUST_ERRNO(ptr, session,
+ publickey_init(session));
+ return ptr;
+}
+
+
+
+/*
+ * 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,
+ const libssh2_publickey_attribute attrs[])
+{
+ LIBSSH2_CHANNEL *channel;
+ LIBSSH2_SESSION *session;
+ /* 19 = packet_len(4) + add_len(4) + "add"(3) + name_len(4) + {name}
+ blob_len(4) + {blob} */
+ unsigned long i, packet_len = 19 + name_len + blob_len;
+ unsigned char *comment = NULL;
+ unsigned long comment_len = 0;
+ int rc;
+
+ if(!pkey)
+ return LIBSSH2_ERROR_BAD_USE;
+
+ channel = pkey->channel;
+ session = channel->session;
+
+ if (pkey->add_state == libssh2_NB_state_idle) {
+ pkey->add_packet = NULL;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY, "Adding %s publickey",
+ name);
+
+ 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 = (unsigned char *) 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) */
+ }
+ }
+
+ pkey->add_packet = LIBSSH2_ALLOC(session, packet_len);
+ if (!pkey->add_packet) {
+ return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for "
+ "publickey \"add\" packet");
+ }
+
+ pkey->add_s = pkey->add_packet;
+ _libssh2_htonu32(pkey->add_s, packet_len - 4);
+ pkey->add_s += 4;
+ _libssh2_htonu32(pkey->add_s, sizeof("add") - 1);
+ pkey->add_s += 4;
+ memcpy(pkey->add_s, "add", sizeof("add") - 1);
+ pkey->add_s += sizeof("add") - 1;
+ if (pkey->version == 1) {
+ _libssh2_htonu32(pkey->add_s, comment_len);
+ pkey->add_s += 4;
+ if (comment) {
+ memcpy(pkey->add_s, comment, comment_len);
+ pkey->add_s += comment_len;
+ }
+
+ _libssh2_htonu32(pkey->add_s, name_len);
+ pkey->add_s += 4;
+ memcpy(pkey->add_s, name, name_len);
+ pkey->add_s += name_len;
+ _libssh2_htonu32(pkey->add_s, blob_len);
+ pkey->add_s += 4;
+ memcpy(pkey->add_s, blob, blob_len);
+ pkey->add_s += blob_len;
+ } else {
+ /* Version == 2 */
+
+ _libssh2_htonu32(pkey->add_s, name_len);
+ pkey->add_s += 4;
+ memcpy(pkey->add_s, name, name_len);
+ pkey->add_s += name_len;
+ _libssh2_htonu32(pkey->add_s, blob_len);
+ pkey->add_s += 4;
+ memcpy(pkey->add_s, blob, blob_len);
+ pkey->add_s += blob_len;
+ *(pkey->add_s++) = overwrite ? 0x01 : 0;
+ _libssh2_htonu32(pkey->add_s, num_attrs);
+ pkey->add_s += 4;
+ for(i = 0; i < num_attrs; i++) {
+ _libssh2_htonu32(pkey->add_s, attrs[i].name_len);
+ pkey->add_s += 4;
+ memcpy(pkey->add_s, attrs[i].name, attrs[i].name_len);
+ pkey->add_s += attrs[i].name_len;
+ _libssh2_htonu32(pkey->add_s, attrs[i].value_len);
+ pkey->add_s += 4;
+ memcpy(pkey->add_s, attrs[i].value, attrs[i].value_len);
+ pkey->add_s += attrs[i].value_len;
+ *(pkey->add_s++) = attrs[i].mandatory ? 0x01 : 0;
+ }
+ }
+
+ _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY,
+ "Sending publickey \"add\" packet: "
+ "type=%s blob_len=%ld num_attrs=%ld",
+ name, blob_len, num_attrs);
+
+ pkey->add_state = libssh2_NB_state_created;
+ }
+
+ if (pkey->add_state == libssh2_NB_state_created) {
+ rc = _libssh2_channel_write(channel, 0, pkey->add_packet,
+ (pkey->add_s - pkey->add_packet));
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if ((pkey->add_s - pkey->add_packet) != rc) {
+ LIBSSH2_FREE(session, pkey->add_packet);
+ pkey->add_packet = NULL;
+ return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND,
+ "Unable to send publickey add packet");
+ }
+ LIBSSH2_FREE(session, pkey->add_packet);
+ pkey->add_packet = NULL;
+
+ pkey->add_state = libssh2_NB_state_sent;
+ }
+
+ rc = publickey_response_success(pkey);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ }
+
+ pkey->add_state = libssh2_NB_state_idle;
+
+ return rc;
+}
+
+/* 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;
+ LIBSSH2_SESSION *session;
+ /* 22 = packet_len(4) + remove_len(4) + "remove"(6) + name_len(4) + {name}
+ + blob_len(4) + {blob} */
+ unsigned long packet_len = 22 + name_len + blob_len;
+ int rc;
+
+ if(!pkey)
+ return LIBSSH2_ERROR_BAD_USE;
+
+ channel = pkey->channel;
+ session = channel->session;
+
+ if (pkey->remove_state == libssh2_NB_state_idle) {
+ pkey->remove_packet = NULL;
+
+ pkey->remove_packet = LIBSSH2_ALLOC(session, packet_len);
+ if (!pkey->remove_packet) {
+ return _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for "
+ "publickey \"remove\" packet");
+ }
+
+ pkey->remove_s = pkey->remove_packet;
+ _libssh2_htonu32(pkey->remove_s, packet_len - 4);
+ pkey->remove_s += 4;
+ _libssh2_htonu32(pkey->remove_s, sizeof("remove") - 1);
+ pkey->remove_s += 4;
+ memcpy(pkey->remove_s, "remove", sizeof("remove") - 1);
+ pkey->remove_s += sizeof("remove") - 1;
+ _libssh2_htonu32(pkey->remove_s, name_len);
+ pkey->remove_s += 4;
+ memcpy(pkey->remove_s, name, name_len);
+ pkey->remove_s += name_len;
+ _libssh2_htonu32(pkey->remove_s, blob_len);
+ pkey->remove_s += 4;
+ memcpy(pkey->remove_s, blob, blob_len);
+ pkey->remove_s += blob_len;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY,
+ "Sending publickey \"remove\" packet: "
+ "type=%s blob_len=%ld",
+ name, blob_len);
+
+ pkey->remove_state = libssh2_NB_state_created;
+ }
+
+ if (pkey->remove_state == libssh2_NB_state_created) {
+ rc = _libssh2_channel_write(channel, 0, pkey->remove_packet,
+ (pkey->remove_s - pkey->remove_packet));
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if ((pkey->remove_s - pkey->remove_packet) != rc) {
+ LIBSSH2_FREE(session, pkey->remove_packet);
+ pkey->remove_packet = NULL;
+ pkey->remove_state = libssh2_NB_state_idle;
+ return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND,
+ "Unable to send publickey remove packet");
+ }
+ LIBSSH2_FREE(session, pkey->remove_packet);
+ pkey->remove_packet = NULL;
+
+ pkey->remove_state = libssh2_NB_state_sent;
+ }
+
+ rc = publickey_response_success(pkey);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ }
+
+ pkey->remove_state = libssh2_NB_state_idle;
+
+ return rc;
+}
+
+/* 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;
+ LIBSSH2_SESSION *session;
+ libssh2_publickey_list *list = NULL;
+ unsigned long buffer_len = 12, keys = 0, max_keys = 0, i;
+ /* 12 = packet_len(4) + list_len(4) + "list"(4) */
+ int response;
+ int rc;
+
+ if(!pkey)
+ return LIBSSH2_ERROR_BAD_USE;
+
+ channel = pkey->channel;
+ session = channel->session;
+
+ if (pkey->listFetch_state == libssh2_NB_state_idle) {
+ pkey->listFetch_data = NULL;
+
+ pkey->listFetch_s = pkey->listFetch_buffer;
+ _libssh2_htonu32(pkey->listFetch_s, buffer_len - 4);
+ pkey->listFetch_s += 4;
+ _libssh2_htonu32(pkey->listFetch_s, sizeof("list") - 1);
+ pkey->listFetch_s += 4;
+ memcpy(pkey->listFetch_s, "list", sizeof("list") - 1);
+ pkey->listFetch_s += sizeof("list") - 1;
+
+ _libssh2_debug(session, LIBSSH2_TRACE_PUBLICKEY,
+ "Sending publickey \"list\" packet");
+
+ pkey->listFetch_state = libssh2_NB_state_created;
+ }
+
+ if (pkey->listFetch_state == libssh2_NB_state_created) {
+ rc = _libssh2_channel_write(channel, 0,
+ pkey->listFetch_buffer,
+ (pkey->listFetch_s -
+ pkey->listFetch_buffer));
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if ((pkey->listFetch_s - pkey->listFetch_buffer) != rc) {
+ pkey->listFetch_state = libssh2_NB_state_idle;
+ return _libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND,
+ "Unable to send publickey list packet");
+ }
+
+ pkey->listFetch_state = libssh2_NB_state_sent;
+ }
+
+ while (1) {
+ rc = publickey_packet_receive(pkey, &pkey->listFetch_data,
+ &pkey->listFetch_data_len);
+ if (rc == LIBSSH2_ERROR_EAGAIN) {
+ return rc;
+ } else if (rc) {
+ _libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT,
+ "Timeout waiting for response from "
+ "publickey subsystem");
+ goto err_exit;
+ }
+
+ pkey->listFetch_s = pkey->listFetch_data;
+ if ((response =
+ publickey_response_id(&pkey->listFetch_s,
+ pkey->listFetch_data_len)) < 0) {
+ _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Invalid publickey subsystem response code");
+ goto err_exit;
+ }
+
+ switch (response) {
+ case LIBSSH2_PUBLICKEY_RESPONSE_STATUS:
+ /* Error, or processing complete */
+ {
+ unsigned long status, descr_len, lang_len;
+
+ status = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ descr_len = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ /* description starts at pkey->listFetch_s */
+ pkey->listFetch_s += descr_len;
+ lang_len = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ /* lang starts at pkey->listFetch_s */
+ pkey->listFetch_s += lang_len;
+
+ if (pkey->listFetch_s >
+ pkey->listFetch_data + pkey->listFetch_data_len) {
+ _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Malformed publickey subsystem packet");
+ goto err_exit;
+ }
+
+ if (status == LIBSSH2_PUBLICKEY_SUCCESS) {
+ LIBSSH2_FREE(session, pkey->listFetch_data);
+ pkey->listFetch_data = NULL;
+ *pkey_list = list;
+ *num_keys = keys;
+ pkey->listFetch_state = libssh2_NB_state_idle;
+ return 0;
+ }
+
+ publickey_status_error(pkey, session, status);
+ goto err_exit;
+ }
+ case LIBSSH2_PUBLICKEY_RESPONSE_PUBLICKEY:
+ /* What we want */
+ if (keys >= max_keys) {
+ libssh2_publickey_list *newlist;
+ /* Grow the key list if necessary */
+ max_keys += 8;
+ newlist =
+ LIBSSH2_REALLOC(session, list,
+ (max_keys +
+ 1) * sizeof(libssh2_publickey_list));
+ if (!newlist) {
+ _libssh2_error(session, LIBSSH2_ERROR_ALLOC,
+ "Unable to allocate memory for "
+ "publickey list");
+ goto err_exit;
+ }
+ list = newlist;
+ }
+ if (pkey->version == 1) {
+ unsigned long comment_len;
+
+ comment_len = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_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");
+ goto err_exit;
+ }
+ list[keys].attrs[0].name = "comment";
+ list[keys].attrs[0].name_len = sizeof("comment") - 1;
+ list[keys].attrs[0].value = (char *) pkey->listFetch_s;
+ list[keys].attrs[0].value_len = comment_len;
+ list[keys].attrs[0].mandatory = 0;
+
+ pkey->listFetch_s += comment_len;
+ } else {
+ list[keys].num_attrs = 0;
+ list[keys].attrs = NULL;
+ }
+ list[keys].name_len = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ list[keys].name = pkey->listFetch_s;
+ pkey->listFetch_s += list[keys].name_len;
+ list[keys].blob_len = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ list[keys].blob = pkey->listFetch_s;
+ pkey->listFetch_s += list[keys].blob_len;
+ } else {
+ /* Version == 2 */
+ list[keys].name_len = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ list[keys].name = pkey->listFetch_s;
+ pkey->listFetch_s += list[keys].name_len;
+ list[keys].blob_len = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ list[keys].blob = pkey->listFetch_s;
+ pkey->listFetch_s += list[keys].blob_len;
+ list[keys].num_attrs = _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_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");
+ goto err_exit;
+ }
+ for(i = 0; i < list[keys].num_attrs; i++) {
+ list[keys].attrs[i].name_len =
+ _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ list[keys].attrs[i].name = (char *) pkey->listFetch_s;
+ pkey->listFetch_s += list[keys].attrs[i].name_len;
+ list[keys].attrs[i].value_len =
+ _libssh2_ntohu32(pkey->listFetch_s);
+ pkey->listFetch_s += 4;
+ list[keys].attrs[i].value = (char *) pkey->listFetch_s;
+ pkey->listFetch_s += list[keys].attrs[i].value_len;
+
+ /* actually an ignored value */
+ list[keys].attrs[i].mandatory = 0;
+ }
+ } else {
+ list[keys].attrs = NULL;
+ }
+ }
+ /* To be FREEd in libssh2_publickey_list_free() */
+ list[keys].packet = pkey->listFetch_data;
+ keys++;
+
+ list[keys].packet = NULL; /* Terminate the list */
+ pkey->listFetch_data = NULL;
+ break;
+ default:
+ /* Unknown/Unexpected */
+ _libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
+ "Unexpected publickey subsystem response");
+ LIBSSH2_FREE(session, pkey->listFetch_data);
+ pkey->listFetch_data = NULL;
+ }
+ }
+
+ /* Only reached via explicit goto */
+ err_exit:
+ if (pkey->listFetch_data) {
+ LIBSSH2_FREE(session, pkey->listFetch_data);
+ pkey->listFetch_data = NULL;
+ }
+ if (list) {
+ libssh2_publickey_list_free(pkey, list);
+ }
+ pkey->listFetch_state = libssh2_NB_state_idle;
+ 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;
+ libssh2_publickey_list *p = pkey_list;
+
+ if(!pkey || !p)
+ return;
+
+ session = pkey->channel->session;
+
+ 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 int
+libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY *pkey)
+{
+ LIBSSH2_SESSION *session;
+ int rc;
+
+ if(!pkey)
+ return LIBSSH2_ERROR_BAD_USE;
+
+ session = pkey->channel->session;
+
+ /*
+ * Make sure all memory used in the state variables are free
+ */
+ if (pkey->receive_packet) {
+ LIBSSH2_FREE(session, pkey->receive_packet);
+ pkey->receive_packet = NULL;
+ }
+ if (pkey->add_packet) {
+ LIBSSH2_FREE(session, pkey->add_packet);
+ pkey->add_packet = NULL;
+ }
+ if (pkey->remove_packet) {
+ LIBSSH2_FREE(session, pkey->remove_packet);
+ pkey->remove_packet = NULL;
+ }
+ if (pkey->listFetch_data) {
+ LIBSSH2_FREE(session, pkey->listFetch_data);
+ pkey->listFetch_data = NULL;
+ }
+
+ rc = _libssh2_channel_free(pkey->channel);
+ if (rc == LIBSSH2_ERROR_EAGAIN)
+ return rc;
+
+ LIBSSH2_FREE(session, pkey);
+ return 0;
+}