blob: 8ca499c8168b7a478b85d40122dd89a864b160d5 [file] [log] [blame]
/* Copyright (c) 2004-2007, 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"
#include "channel.h"
#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;
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
*/
#define LIBSSH2_PUBLICKEY_STATUS_TEXT_START "Publickey Subsystem Error: \""
#define LIBSSH2_PUBLICKEY_STATUS_TEXT_MID "\" Server Reports: \""
#define LIBSSH2_PUBLICKEY_STATUS_TEXT_END "\""
static void
publickey_status_error(const LIBSSH2_PUBLICKEY * pkey,
LIBSSH2_SESSION * session, int status,
const unsigned char *message, int message_len)
{
const 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 = publickey_status_codes[status].name;
status_text_len = 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);
}
/*
* publickey_packet_receive
*
* Read a packet from the subsystem
*/
static int
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];
int rc;
if (pkey->receive_state == libssh2_NB_state_idle) {
rc = _libssh2_channel_read(channel, 0, (char *) buffer, 4);
if (rc == PACKET_EAGAIN) {
return rc;
} else if (rc != 4) {
libssh2_error(session, LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
"Invalid response from publickey subsystem", 0);
return -1;
}
pkey->receive_packet_len = _libssh2_ntohu32(buffer);
pkey->receive_packet =
LIBSSH2_ALLOC(session, pkey->receive_packet_len);
if (!pkey->receive_packet) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate publickey response buffer", 0);
return -1;
}
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 == PACKET_EAGAIN) {
return rc;
} else if (rc != (int)pkey->receive_packet_len) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT,
"Timeout waiting for publickey subsystem response packet",
0);
LIBSSH2_FREE(session, pkey->receive_packet);
pkey->receive_packet = NULL;
pkey->receive_state = libssh2_NB_state_idle;
return -1;
}
*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, int data_len)
{
unsigned long 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 < (int)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;
}
/* libssh2_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;
unsigned long data_len;
int response;
int rc;
while (1) {
rc = publickey_packet_receive(pkey, &data, &data_len);
if (rc == PACKET_EAGAIN) {
return rc;
} else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT,
"Timeout waiting for response from publickey subsystem",
0);
return -1;
}
s = data;
if ((response = 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;
}
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)
{
/* 19 = packet_len(4) + version_len(4) + "version"(7) + version_num(4) */
unsigned char buffer[19];
unsigned char *s;
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_DBG_PUBLICKEY,
"Initializing publickey subsystem");
session->pkeyInit_state = libssh2_NB_state_allocated;
}
if (session->pkeyInit_state == libssh2_NB_state_allocated) {
do {
session->pkeyInit_channel =
libssh2_channel_open_ex(session, "session",
sizeof("session") - 1,
LIBSSH2_CHANNEL_WINDOW_DEFAULT,
LIBSSH2_CHANNEL_PACKET_DEFAULT, NULL,
0);
if (!session->pkeyInit_channel
&& (libssh2_session_last_errno(session) ==
LIBSSH2_ERROR_EAGAIN)) {
/* The error state is already set, so leave it */
libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
"Would block to startup channel", 0);
return NULL;
} else if (!session->pkeyInit_channel
&& (libssh2_session_last_errno(session) !=
LIBSSH2_ERROR_EAGAIN)) {
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
"Unable to startup channel", 0);
goto err_exit;
}
} while (!session->pkeyInit_channel);
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", strlen("publickey"));
if (rc == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
"Would block starting publickey subsystem", 0);
return NULL;
} else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_CHANNEL_FAILURE,
"Unable to request publickey subsystem", 0);
goto err_exit;
}
session->pkeyInit_state = libssh2_NB_state_sent1;
}
if (session->pkeyInit_state == libssh2_NB_state_sent1) {
rc = libssh2_channel_handle_extended_data2(session->pkeyInit_channel,
LIBSSH2_CHANNEL_EXTENDED_DATA_IGNORE);
if (rc == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
"Would block starting publickey subsystem", 0);
return NULL;
}
session->pkeyInit_pkey =
LIBSSH2_ALLOC(session, sizeof(LIBSSH2_PUBLICKEY));
if (!session->pkeyInit_pkey) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate a new publickey structure", 0);
goto err_exit;
}
memset(session->pkeyInit_pkey, 0, sizeof(LIBSSH2_PUBLICKEY));
session->pkeyInit_pkey->channel = session->pkeyInit_channel;
session->pkeyInit_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;
_libssh2_debug(session, LIBSSH2_DBG_PUBLICKEY,
"Sending publickey version packet 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,
(char *) buffer, (s - buffer));
if (rc == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
"Would block sending publickey version packet", 0);
return NULL;
} else if ((s - buffer) != rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND,
"Unable to send publickey version packet", 0);
goto err_exit;
}
session->pkeyInit_state = libssh2_NB_state_sent3;
}
if (session->pkeyInit_state == libssh2_NB_state_sent3) {
while (1) {
rc = publickey_packet_receive(session->pkeyInit_pkey,
&session->pkeyInit_data,
&session->pkeyInit_data_len);
if (rc == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
"Would block waiting for response from publickey subsystem",
0);
return NULL;
} else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT,
"Timeout waiting for response from publickey subsystem",
0);
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", 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 >
session->pkeyInit_data + session->pkeyInit_data_len) {
libssh2_error(session,
LIBSSH2_ERROR_PUBLICKEY_PROTOCOL,
"Malformed publickey subsystem packet",
0);
goto err_exit;
}
publickey_status_error(NULL, session, status,
descr, descr_len);
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_DBG_PUBLICKEY,
"Truncating remote publickey version from %lu",
session->pkeyInit_pkey->version);
session->pkeyInit_pkey->version =
LIBSSH2_PUBLICKEY_VERSION;
}
_libssh2_debug(session, LIBSSH2_DBG_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",
0);
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 == PACKET_EAGAIN) {
libssh2_error(session, LIBSSH2_ERROR_EAGAIN,
"Would block closing channel", 0);
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_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 = pkey->channel;
LIBSSH2_SESSION *session = channel->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->add_state == libssh2_NB_state_idle) {
pkey->add_packet = NULL;
_libssh2_debug(session, LIBSSH2_DBG_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) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate memory for publickey \"add\" packet",
0);
return -1;
}
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_DBG_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, (char *) pkey->add_packet,
(pkey->add_s - pkey->add_packet));
if (rc == PACKET_EAGAIN) {
return rc;
} else if ((pkey->add_s - pkey->add_packet) != rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND,
"Unable to send publickey add packet", 0);
LIBSSH2_FREE(session, pkey->add_packet);
pkey->add_packet = NULL;
return -1;
}
LIBSSH2_FREE(session, pkey->add_packet);
pkey->add_packet = NULL;
pkey->add_state = libssh2_NB_state_sent;
}
rc = publickey_response_success(pkey);
if (rc == PACKET_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 = pkey->channel;
LIBSSH2_SESSION *session = channel->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->remove_state == libssh2_NB_state_idle) {
pkey->remove_packet = NULL;
pkey->remove_packet = LIBSSH2_ALLOC(session, packet_len);
if (!pkey->remove_packet) {
libssh2_error(session, LIBSSH2_ERROR_ALLOC,
"Unable to allocate memory for publickey \"remove\" packet",
0);
return -1;
}
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_DBG_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, (char *) pkey->remove_packet,
(pkey->remove_s - pkey->remove_packet));
if (rc == PACKET_EAGAIN) {
return rc;
} else if ((pkey->remove_s - pkey->remove_packet) != rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND,
"Unable to send publickey remove packet", 0);
LIBSSH2_FREE(session, pkey->remove_packet);
pkey->remove_packet = NULL;
pkey->remove_state = libssh2_NB_state_idle;
return -1;
}
LIBSSH2_FREE(session, pkey->remove_packet);
pkey->remove_packet = NULL;
pkey->remove_state = libssh2_NB_state_sent;
}
rc = publickey_response_success(pkey);
if (rc == PACKET_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 = pkey->channel;
LIBSSH2_SESSION *session = channel->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->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_DBG_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,
(char *) pkey->listFetch_buffer,
(pkey->listFetch_s -
pkey->listFetch_buffer));
if (rc == PACKET_EAGAIN) {
return rc;
} else if ((pkey->listFetch_s - pkey->listFetch_buffer) != rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_SEND,
"Unable to send publickey list packet", 0);
pkey->listFetch_state = libssh2_NB_state_idle;
return -1;
}
pkey->listFetch_state = libssh2_NB_state_sent;
}
while (1) {
rc = publickey_packet_receive(pkey, &pkey->listFetch_data,
&pkey->listFetch_data_len);
if (rc == PACKET_EAGAIN) {
return rc;
} else if (rc) {
libssh2_error(session, LIBSSH2_ERROR_SOCKET_TIMEOUT,
"Timeout waiting for response from publickey subsystem",
0);
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", 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(pkey->listFetch_s);
pkey->listFetch_s += 4;
descr_len = _libssh2_ntohu32(pkey->listFetch_s);
pkey->listFetch_s += 4;
descr = pkey->listFetch_s;
pkey->listFetch_s += descr_len;
lang_len = _libssh2_ntohu32(pkey->listFetch_s);
pkey->listFetch_s += 4;
lang = 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", 0);
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, descr,
descr_len);
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",
0);
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",
0);
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",
0);
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;
list[keys].attrs[i].mandatory = 0; /* actually an ignored value */
}
} else {
list[keys].attrs = NULL;
}
}
list[keys].packet = pkey->listFetch_data; /* To be FREEd in libssh2_publickey_list_free() */
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, ignoring",
0);
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 = 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 int
libssh2_publickey_shutdown(LIBSSH2_PUBLICKEY * pkey)
{
LIBSSH2_SESSION *session = pkey->channel->session;
int rc;
/*
* 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 == PACKET_EAGAIN)
return rc;
LIBSSH2_FREE(session, pkey);
return 0;
}