mirror of
https://libwebsockets.org/repo/libwebsockets
synced 2024-11-22 00:57:52 +00:00
2630 lines
64 KiB
C
2630 lines
64 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
* of this software and associated documentation files (the "Software"), to
|
|
* deal in the Software without restriction, including without limitation the
|
|
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
|
* sell copies of the Software, and to permit persons to whom the Software is
|
|
* furnished to do so, subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in
|
|
* all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
|
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
|
* IN THE SOFTWARE.
|
|
*/
|
|
|
|
#include "libwebsockets.h"
|
|
#include "lws-ssh.h"
|
|
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
|
|
void *sshd_zalloc(size_t s)
|
|
{
|
|
void *p = malloc(s);
|
|
|
|
if (p)
|
|
memset(p, 0, s);
|
|
|
|
return p;
|
|
}
|
|
|
|
uint32_t
|
|
lws_g32(uint8_t **p)
|
|
{
|
|
uint32_t v = 0;
|
|
|
|
v = (v << 8) | *((*p)++);
|
|
v = (v << 8) | *((*p)++);
|
|
v = (v << 8) | *((*p)++);
|
|
v = (v << 8) | *((*p)++);
|
|
|
|
return v;
|
|
}
|
|
|
|
uint32_t
|
|
lws_p32(uint8_t *p, uint32_t v)
|
|
{
|
|
*p++ = (uint8_t)(v >> 24);
|
|
*p++ = (uint8_t)(v >> 16);
|
|
*p++ = (uint8_t)(v >> 8);
|
|
*p++ = (uint8_t)v;
|
|
|
|
return v;
|
|
}
|
|
|
|
int
|
|
lws_cstr(uint8_t **p, const char *s, uint32_t max)
|
|
{
|
|
uint32_t n = (uint32_t)strlen(s);
|
|
|
|
if (n > max)
|
|
return 1;
|
|
|
|
lws_p32(*p, n);
|
|
*p += 4;
|
|
strcpy((char *)(*p), s);
|
|
*p += n;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_buf(uint8_t **p, void *s, uint32_t len)
|
|
{
|
|
lws_p32(*p, len);
|
|
*p += 4;
|
|
memcpy((char *)(*p), s, len);
|
|
*p += len;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void
|
|
write_task(struct per_session_data__sshd *pss, struct lws_ssh_channel *ch,
|
|
int task)
|
|
{
|
|
pss->write_task[pss->wt_head] = (uint8_t)task;
|
|
pss->write_channel[pss->wt_head] = ch;
|
|
pss->wt_head = (pss->wt_head + 1) & 7;
|
|
lws_callback_on_writable(pss->wsi);
|
|
}
|
|
|
|
void
|
|
write_task_insert(struct per_session_data__sshd *pss, struct lws_ssh_channel *ch,
|
|
int task)
|
|
{
|
|
pss->wt_tail = (pss->wt_tail - 1) & 7;
|
|
pss->write_task[pss->wt_tail] = (uint8_t)task;
|
|
pss->write_channel[pss->wt_tail] = ch;
|
|
lws_callback_on_writable(pss->wsi);
|
|
}
|
|
|
|
|
|
void
|
|
lws_pad_set_length(struct per_session_data__sshd *pss, void *start, uint8_t **p,
|
|
struct lws_ssh_keys *keys)
|
|
{
|
|
uint32_t len = (uint32_t)lws_ptr_diff(*p, start);
|
|
uint8_t padc = 4, *bs = start;
|
|
|
|
if (keys->full_length)
|
|
len -= 4;
|
|
|
|
if ((len + padc) & (uint32_t)(keys->padding_alignment - 1))
|
|
padc = (uint8_t)((uint8_t)padc + (uint8_t)(keys->padding_alignment -
|
|
((len + padc) & (uint32_t)(keys->padding_alignment - 1))));
|
|
|
|
bs[4] = padc;
|
|
len += padc;
|
|
|
|
if (!keys->valid) /* no crypto = pad with 00 */
|
|
while (padc--)
|
|
*((*p)++) = 0;
|
|
else { /* crypto active = pad with random */
|
|
lws_get_random(pss->vhd->context, *p, padc);
|
|
(*p) += padc;
|
|
}
|
|
if (keys->full_length)
|
|
len += 4;
|
|
|
|
lws_p32(start, len - 4);
|
|
}
|
|
|
|
static uint32_t
|
|
offer(struct per_session_data__sshd *pss, uint8_t *p, uint32_t len, int first,
|
|
int *payload_len)
|
|
{
|
|
uint8_t *op = p, *lp, *end = p + len - 1;
|
|
int n, padc = 4, keylen;
|
|
char keyt[32];
|
|
uint8_t keybuf[256];
|
|
|
|
keylen = (int)get_gen_server_key_25519(pss, keybuf, (int)sizeof(keybuf));
|
|
if (!keylen) {
|
|
lwsl_notice("get_gen_server_key failed\n");
|
|
return 1;
|
|
}
|
|
lwsl_info("keylen %d\n", keylen);
|
|
n = ed25519_key_parse(keybuf, (unsigned int)keylen,
|
|
keyt, sizeof(keyt), NULL, NULL);
|
|
if (n) {
|
|
lwsl_notice("unable to parse server key: %d\n", n);
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* byte SSH_MSG_KEXINIT
|
|
* byte[16] cookie (random bytes)
|
|
* name-list kex_algorithms
|
|
* name-list server_host_key_algorithms
|
|
* name-list encryption_algorithms_client_to_server
|
|
* name-list encryption_algorithms_server_to_client
|
|
* name-list mac_algorithms_client_to_server
|
|
* name-list mac_algorithms_server_to_client
|
|
* name-list compression_algorithms_client_to_server
|
|
* name-list compression_algorithms_server_to_client
|
|
* name-list langua->es_client_to_server
|
|
* name-list langua->es_server_to_client
|
|
* boolean first_kex_packet_follows
|
|
* uint32 0 (reserved for future extension)
|
|
*/
|
|
|
|
p += 5; /* msg len + padding */
|
|
|
|
*p++ = SSH_MSG_KEXINIT;
|
|
lws_get_random(pss->vhd->context, p, 16);
|
|
p += 16;
|
|
|
|
/* KEX algorithms */
|
|
|
|
lp = p;
|
|
p += 4;
|
|
n = lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "curve25519-sha256@libssh.org");
|
|
p += lws_p32(lp, (uint32_t)n);
|
|
|
|
/* Server Host Key Algorithms */
|
|
|
|
lp = p;
|
|
p += 4;
|
|
n = lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "%s", keyt);
|
|
p += lws_p32(lp, (uint32_t)n);
|
|
|
|
/* Encryption Algorithms: C -> S */
|
|
|
|
lp = p;
|
|
p += 4;
|
|
// n = lws_snprintf((char *)p, end - p, "aes256-gcm@openssh.com");
|
|
n = lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "chacha20-poly1305@openssh.com");
|
|
p += lws_p32(lp, (uint32_t)n);
|
|
|
|
/* Encryption Algorithms: S -> C */
|
|
|
|
lp = p;
|
|
p += 4;
|
|
// n = lws_snprintf((char *)p, end - p, "aes256-gcm@openssh.com");
|
|
n = lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "chacha20-poly1305@openssh.com");
|
|
p += lws_p32(lp, (uint32_t)n);
|
|
|
|
/* MAC Algorithms: C -> S */
|
|
|
|
lp = p;
|
|
p += 4;
|
|
/* bogus: chacha20 does not use MACs, but 'none' is not offered */
|
|
n = lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "hmac-sha2-256");
|
|
p += lws_p32(lp, (uint32_t)n);
|
|
|
|
/* MAC Algorithms: S -> C */
|
|
|
|
lp = p;
|
|
p += 4;
|
|
/* bogus: chacha20 does not use MACs, but 'none' is not offered */
|
|
n = lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "hmac-sha2-256");
|
|
p += lws_p32(lp, (uint32_t)n);
|
|
|
|
/* Compression Algorithms: C -> S */
|
|
|
|
lp = p;
|
|
p += 4;
|
|
n = lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "none");
|
|
p += lws_p32(lp, (uint32_t)n);
|
|
|
|
/* Compression Algorithms: S -> C */
|
|
|
|
lp = p;
|
|
p += 4;
|
|
n = lws_snprintf((char *)p, lws_ptr_diff_size_t(end, p), "none");
|
|
p += lws_p32(lp, (uint32_t)n);
|
|
|
|
if (p - op < 13 + padc + 8)
|
|
return 0;
|
|
|
|
/* Languages: C -> S */
|
|
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
|
|
/* Languages: S -> C */
|
|
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
|
|
/* First KEX packet coming */
|
|
|
|
*p++ = !!first;
|
|
|
|
/* Reserved */
|
|
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
*p++ = 0;
|
|
|
|
len = (uint32_t)lws_ptr_diff(p, op);
|
|
if (payload_len)
|
|
/* starts at buf + 5 and excludes padding */
|
|
*payload_len = (int)(len - 5);
|
|
|
|
/* we must give at least 4 bytes of 00 padding */
|
|
|
|
if (((int)len + padc) & 7)
|
|
padc += 8 - (((int)len + padc) & 7);
|
|
|
|
op[4] = (uint8_t)padc;
|
|
len += (uint32_t)padc;
|
|
|
|
while (padc--)
|
|
*p++ = 0;
|
|
|
|
/* recorded length does not include the uint32_t len itself */
|
|
lws_p32(op, len - 4);
|
|
|
|
return len;
|
|
}
|
|
|
|
static int
|
|
handle_name(struct per_session_data__sshd *pss)
|
|
{
|
|
struct lws_kex *kex = pss->kex;
|
|
char keyt[32];
|
|
uint8_t keybuf[256];
|
|
int n = 0, len;
|
|
|
|
switch (pss->parser_state) {
|
|
case SSH_KEX_NL_KEX_ALGS:
|
|
if (!strcmp(pss->name, "curve25519-sha256@libssh.org"))
|
|
kex->match_bitfield |= 1;
|
|
break;
|
|
case SSH_KEX_NL_SHK_ALGS:
|
|
len = (int)get_gen_server_key_25519(pss, keybuf, (int)sizeof(keybuf));
|
|
if (!len)
|
|
break;
|
|
if (ed25519_key_parse(keybuf, (unsigned int)len,
|
|
keyt, sizeof(keyt),
|
|
NULL, NULL)) {
|
|
lwsl_err("Unable to parse host key %d\n", n);
|
|
} else {
|
|
if (!strcmp(pss->name, keyt)) {
|
|
kex->match_bitfield |= 2;
|
|
break;
|
|
}
|
|
}
|
|
break;
|
|
case SSH_KEX_NL_EACTS_ALGS:
|
|
if (!strcmp(pss->name, "chacha20-poly1305@openssh.com"))
|
|
kex->match_bitfield |= 4;
|
|
break;
|
|
case SSH_KEX_NL_EASTC_ALGS:
|
|
if (!strcmp(pss->name, "chacha20-poly1305@openssh.com"))
|
|
kex->match_bitfield |= 8;
|
|
break;
|
|
case SSH_KEX_NL_MACTS_ALGS:
|
|
if (!strcmp(pss->name, "hmac-sha2-256"))
|
|
kex->match_bitfield |= 16;
|
|
break;
|
|
case SSH_KEX_NL_MASTC_ALGS:
|
|
if (!strcmp(pss->name, "hmac-sha2-256"))
|
|
kex->match_bitfield |= 32;
|
|
break;
|
|
case SSH_KEX_NL_CACTS_ALGS:
|
|
if (!strcmp(pss->name, "none"))
|
|
kex->match_bitfield |= 64;
|
|
break;
|
|
case SSH_KEX_NL_CASTC_ALGS:
|
|
if (!strcmp(pss->name, "none"))
|
|
kex->match_bitfield |= 128;
|
|
break;
|
|
case SSH_KEX_NL_LCTS_ALGS:
|
|
case SSH_KEX_NL_LSTC_ALGS:
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
static int
|
|
lws_kex_create(struct per_session_data__sshd *pss)
|
|
{
|
|
pss->kex = sshd_zalloc(sizeof(struct lws_kex));
|
|
lwsl_info("%s\n", __func__);
|
|
return !pss->kex;
|
|
}
|
|
|
|
static void
|
|
lws_kex_destroy(struct per_session_data__sshd *pss)
|
|
{
|
|
if (!pss->kex)
|
|
return;
|
|
|
|
lwsl_info("Destroying KEX\n");
|
|
|
|
if (pss->kex->I_C) {
|
|
free(pss->kex->I_C);
|
|
pss->kex->I_C = NULL;
|
|
}
|
|
if (pss->kex->I_S) {
|
|
free(pss->kex->I_S);
|
|
pss->kex->I_S = NULL;
|
|
}
|
|
|
|
lws_explicit_bzero(pss->kex, sizeof(*pss->kex));
|
|
free(pss->kex);
|
|
pss->kex = NULL;
|
|
}
|
|
|
|
static void
|
|
ssh_free(void *p)
|
|
{
|
|
if (!p)
|
|
return;
|
|
|
|
lwsl_debug("%s: FREE %p\n", __func__, p);
|
|
free(p);
|
|
}
|
|
|
|
#define ssh_free_set_NULL(x) if (x) { ssh_free(x); (x) = NULL; }
|
|
|
|
static void
|
|
lws_ua_destroy(struct per_session_data__sshd *pss)
|
|
{
|
|
if (!pss->ua)
|
|
return;
|
|
|
|
lwsl_info("%s\n", __func__);
|
|
|
|
if (pss->ua->username)
|
|
ssh_free(pss->ua->username);
|
|
if (pss->ua->service)
|
|
ssh_free(pss->ua->service);
|
|
if (pss->ua->alg)
|
|
ssh_free(pss->ua->alg);
|
|
if (pss->ua->pubkey)
|
|
ssh_free(pss->ua->pubkey);
|
|
if (pss->ua->sig) {
|
|
lws_explicit_bzero(pss->ua->sig, pss->ua->sig_len);
|
|
ssh_free(pss->ua->sig);
|
|
}
|
|
|
|
lws_explicit_bzero(pss->ua, sizeof(*pss->ua));
|
|
free(pss->ua);
|
|
pss->ua = NULL;
|
|
}
|
|
|
|
|
|
static int
|
|
rsa_hash_alg_from_ident(const char *ident)
|
|
{
|
|
if (strcmp(ident, "ssh-rsa") == 0 ||
|
|
strcmp(ident, "ssh-rsa-cert-v01@openssh.com") == 0)
|
|
return LWS_GENHASH_TYPE_SHA1;
|
|
if (strcmp(ident, "rsa-sha2-256") == 0)
|
|
return LWS_GENHASH_TYPE_SHA256;
|
|
if (strcmp(ident, "rsa-sha2-512") == 0)
|
|
return LWS_GENHASH_TYPE_SHA512;
|
|
|
|
return -1;
|
|
}
|
|
|
|
static void
|
|
state_get_string_alloc(struct per_session_data__sshd *pss, int next)
|
|
{
|
|
pss->parser_state = SSHS_GET_STRING_LEN_ALLOC;
|
|
pss->state_after_string = (char)next;
|
|
}
|
|
|
|
static void
|
|
state_get_string(struct per_session_data__sshd *pss, int next)
|
|
{
|
|
pss->parser_state = SSHS_GET_STRING_LEN;
|
|
pss->state_after_string = (char)next;
|
|
}
|
|
|
|
static void
|
|
state_get_u32(struct per_session_data__sshd *pss, int next)
|
|
{
|
|
pss->parser_state = SSHS_GET_U32;
|
|
pss->state_after_string = (char)next;
|
|
}
|
|
|
|
static struct lws_ssh_channel *
|
|
ssh_get_server_ch(struct per_session_data__sshd *pss, uint32_t chi)
|
|
{
|
|
struct lws_ssh_channel *ch = pss->ch_list;
|
|
|
|
while (ch) {
|
|
if (ch->server_ch == chi)
|
|
return ch;
|
|
ch = ch->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#if 0
|
|
static struct lws_ssh_channel *
|
|
ssh_get_peer_ch(struct per_session_data__sshd *pss, uint32_t chi)
|
|
{
|
|
struct lws_ssh_channel *ch = pss->ch_list;
|
|
|
|
while (ch) {
|
|
if (ch->sender_ch == chi)
|
|
return ch;
|
|
ch = ch->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
static void
|
|
ssh_destroy_channel(struct per_session_data__sshd *pss,
|
|
struct lws_ssh_channel *ch)
|
|
{
|
|
lws_start_foreach_llp(struct lws_ssh_channel **, ppch, pss->ch_list) {
|
|
if (*ppch == ch) {
|
|
lwsl_info("Deleting ch %p\n", ch);
|
|
if (pss->vhd && pss->vhd->ops &&
|
|
pss->vhd->ops->channel_destroy)
|
|
pss->vhd->ops->channel_destroy(ch->priv);
|
|
*ppch = ch->next;
|
|
if (ch->sub)
|
|
free(ch->sub);
|
|
free(ch);
|
|
|
|
return;
|
|
}
|
|
} lws_end_foreach_llp(ppch, next);
|
|
|
|
lwsl_notice("Failed to delete ch\n");
|
|
}
|
|
|
|
static void
|
|
lws_ssh_exec_finish(void *finish_handle, int retcode)
|
|
{
|
|
struct lws_ssh_channel *ch = (struct lws_ssh_channel *)finish_handle;
|
|
struct per_session_data__sshd *pss = ch->pss;
|
|
|
|
ch->retcode = retcode;
|
|
write_task(pss, ch, SSH_WT_EXIT_STATUS);
|
|
ch->scheduled_close = 1;
|
|
write_task(pss, ch, SSH_WT_CH_CLOSE);
|
|
}
|
|
|
|
static int
|
|
lws_ssh_parse_plaintext(struct per_session_data__sshd *pss, uint8_t *p, size_t len)
|
|
{
|
|
struct lws_gencrypto_keyelem e[LWS_GENCRYPTO_RSA_KEYEL_COUNT];
|
|
struct lws_genrsa_ctx ctx;
|
|
struct lws_ssh_channel *ch;
|
|
struct lws_subprotocol_scp *scp;
|
|
uint8_t *pp, *ps, hash[64];
|
|
#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < 0x03000000
|
|
uint8_t *otmp = NULL;
|
|
#endif
|
|
uint32_t m;
|
|
int n;
|
|
|
|
while (len --) {
|
|
again:
|
|
switch(pss->parser_state) {
|
|
case SSH_INITIALIZE_TRANSIENT:
|
|
pss->parser_state = SSHS_IDSTRING;
|
|
pss->ctr = 0;
|
|
pss->copy_to_I_C = 0;
|
|
|
|
/* fallthru */
|
|
case SSHS_IDSTRING:
|
|
if (*p == 0x0d) {
|
|
pss->V_C[pss->npos] = '\0';
|
|
pss->npos = 0;
|
|
lwsl_info("peer id: %s\n", pss->V_C);
|
|
p++;
|
|
pss->parser_state = SSHS_IDSTRING_CR;
|
|
break;
|
|
}
|
|
if (pss->npos < sizeof(pss->V_C) - 1)
|
|
pss->V_C[pss->npos++] = (char)*p;
|
|
p++;
|
|
break;
|
|
|
|
case SSHS_IDSTRING_CR:
|
|
if (*p++ != 0x0a) {
|
|
lwsl_notice("mangled id string\n");
|
|
return 1;
|
|
}
|
|
pss->ssh_sequence_ctr_cts = 0;
|
|
pss->parser_state = SSHS_MSG_LEN;
|
|
break;
|
|
|
|
case SSHS_MSG_LEN:
|
|
pss->msg_len = (pss->msg_len << 8) | *p++;
|
|
if (++pss->ctr != 4)
|
|
break;
|
|
|
|
if (pss->active_keys_cts.valid) {
|
|
uint8_t b[4];
|
|
|
|
POKE_U32(b, (uint32_t)pss->msg_len);
|
|
pss->msg_len = lws_chachapoly_get_length(
|
|
&pss->active_keys_cts,
|
|
pss->ssh_sequence_ctr_cts, b);
|
|
} else
|
|
pss->ssh_sequence_ctr_cts++;
|
|
|
|
lwsl_info("msg len %d\n", pss->msg_len);
|
|
|
|
pss->parser_state = SSHS_MSG_PADDING;
|
|
pss->ctr = 0;
|
|
pss->pos = 4;
|
|
if (pss->msg_len < 2 + 4) {
|
|
lwsl_notice("illegal msg size\n");
|
|
goto bail;
|
|
}
|
|
break;
|
|
|
|
case SSHS_MSG_PADDING:
|
|
pss->msg_padding = *p++;
|
|
pss->parser_state = SSHS_MSG_ID;
|
|
break;
|
|
|
|
case SSHS_MSG_ID:
|
|
pss->msg_id = *p++;
|
|
pss->ctr = 0;
|
|
switch (pss->msg_id) {
|
|
case SSH_MSG_DISCONNECT:
|
|
/*
|
|
* byte SSH_MSG_DISCONNECT
|
|
* uint32 reason code
|
|
* string description in ISO-10646
|
|
* UTF-8 encoding [RFC3629]
|
|
* string language tag [RFC3066]
|
|
*/
|
|
lwsl_notice("SSH_MSG_DISCONNECT\n");
|
|
state_get_u32(pss, SSHS_NVC_DISCONNECT_REASON);
|
|
break;
|
|
case SSH_MSG_IGNORE:
|
|
lwsl_notice("SSH_MSG_IGNORE\n");
|
|
break;
|
|
case SSH_MSG_UNIMPLEMENTED:
|
|
lwsl_notice("SSH_MSG_UNIMPLEMENTED\n");
|
|
break;
|
|
case SSH_MSG_DEBUG:
|
|
lwsl_notice("SSH_MSG_DEBUG\n");
|
|
break;
|
|
case SSH_MSG_SERVICE_REQUEST:
|
|
lwsl_info("SSH_MSG_SERVICE_REQUEST\n");
|
|
/* payload is a string */
|
|
state_get_string(pss, SSHS_DO_SERVICE_REQUEST);
|
|
break;
|
|
case SSH_MSG_SERVICE_ACCEPT:
|
|
lwsl_notice("SSH_MSG_ACCEPT\n");
|
|
break;
|
|
|
|
case SSH_MSG_KEXINIT:
|
|
if (pss->kex_state !=
|
|
KEX_STATE_EXPECTING_CLIENT_OFFER) {
|
|
pss->parser_state = SSH_KEX_STATE_SKIP;
|
|
break;
|
|
}
|
|
if (!pss->kex) {
|
|
lwsl_notice("%s: SSH_MSG_KEXINIT: NULL pss->kex\n", __func__);
|
|
goto bail;
|
|
}
|
|
pss->parser_state = SSH_KEX_STATE_COOKIE;
|
|
pss->kex->I_C_payload_len = 0;
|
|
pss->kex->I_C_alloc_len = pss->msg_len;
|
|
pss->kex->I_C = sshd_zalloc(pss->kex->I_C_alloc_len);
|
|
if (!pss->kex->I_C) {
|
|
lwsl_notice("OOM 3\n");
|
|
goto bail;
|
|
}
|
|
pss->kex->I_C[pss->kex->I_C_payload_len++] =
|
|
pss->msg_id;
|
|
pss->copy_to_I_C = 1;
|
|
break;
|
|
case SSH_MSG_KEX_ECDH_INIT:
|
|
pss->parser_state = SSH_KEX_STATE_ECDH_KEYLEN;
|
|
break;
|
|
|
|
case SSH_MSG_NEWKEYS:
|
|
if (pss->kex_state !=
|
|
KEX_STATE_REPLIED_TO_OFFER &&
|
|
pss->kex_state !=
|
|
KEX_STATE_CRYPTO_INITIALIZED) {
|
|
lwsl_notice("unexpected newkeys\n");
|
|
|
|
goto bail;
|
|
}
|
|
/*
|
|
* it means we should now use the keys we
|
|
* agreed on
|
|
*/
|
|
lwsl_info("Activating CTS keys\n");
|
|
pss->active_keys_cts = pss->kex->keys_next_cts;
|
|
if (lws_chacha_activate(&pss->active_keys_cts))
|
|
goto bail;
|
|
|
|
pss->kex->newkeys |= 2;
|
|
if (pss->kex->newkeys == 3)
|
|
lws_kex_destroy(pss);
|
|
|
|
if (pss->msg_padding) {
|
|
pss->copy_to_I_C = 0;
|
|
pss->parser_state =
|
|
SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
} else {
|
|
pss->parser_state = SSHS_MSG_LEN;
|
|
}
|
|
break;
|
|
|
|
case SSH_MSG_USERAUTH_REQUEST:
|
|
/*
|
|
* byte SSH_MSG_USERAUTH_REQUEST
|
|
* string user name in UTF-8
|
|
* encoding [RFC3629]
|
|
* string service name in US-ASCII
|
|
* string "publickey"
|
|
* boolean FALSE
|
|
* string public key alg
|
|
* string public key blob
|
|
*/
|
|
lwsl_info("SSH_MSG_USERAUTH_REQUEST\n");
|
|
if (pss->ua) {
|
|
lwsl_notice("pss->ua overwrite\n");
|
|
|
|
goto bail;
|
|
}
|
|
|
|
pss->ua = sshd_zalloc(sizeof(*pss->ua));
|
|
if (!pss->ua)
|
|
goto bail;
|
|
|
|
state_get_string_alloc(pss, SSHS_DO_UAR_SVC);
|
|
/* username is destroyed with userauth struct */
|
|
if (!pss->sent_banner) {
|
|
if (pss->vhd->ops->banner)
|
|
write_task(pss, NULL,
|
|
SSH_WT_UA_BANNER);
|
|
pss->sent_banner = 1;
|
|
}
|
|
break;
|
|
case SSH_MSG_USERAUTH_FAILURE:
|
|
goto bail;
|
|
case SSH_MSG_USERAUTH_SUCCESS:
|
|
goto bail;
|
|
case SSH_MSG_USERAUTH_BANNER:
|
|
goto bail;
|
|
|
|
case SSH_MSG_CHANNEL_OPEN:
|
|
state_get_string(pss, SSHS_NVC_CHOPEN_TYPE);
|
|
break;
|
|
|
|
case SSH_MSG_CHANNEL_REQUEST:
|
|
/* RFC4254
|
|
*
|
|
* byte SSH_MSG_CHANNEL_REQUEST
|
|
* uint32 recipient channel
|
|
* string "pty-req"
|
|
* boolean want_reply
|
|
* string TERM environment variable value
|
|
* (e.g., vt100)
|
|
* uint32 terminal width, characters
|
|
* (e.g., 80)
|
|
* uint32 terminal height, rows (e.g., 24)
|
|
* uint32 terminal width, px (e.g., 640)
|
|
* uint32 terminal height, px (e.g., 480)
|
|
* string encoded terminal modes
|
|
*/
|
|
state_get_u32(pss, SSHS_NVC_CHRQ_RECIP);
|
|
break;
|
|
|
|
case SSH_MSG_CHANNEL_EOF:
|
|
/* RFC4254
|
|
* When a party will no longer send more data
|
|
* to a channel, it SHOULD send
|
|
* SSH_MSG_CHANNEL_EOF.
|
|
*
|
|
* byte SSH_MSG_CHANNEL_EOF
|
|
* uint32 recipient channel
|
|
*/
|
|
state_get_u32(pss, SSHS_NVC_CH_EOF);
|
|
break;
|
|
|
|
case SSH_MSG_CHANNEL_CLOSE:
|
|
/* RFC4254
|
|
*
|
|
* byte SSH_MSG_CHANNEL_CLOSE
|
|
* uint32 recipient channel
|
|
*
|
|
* This message does not consume window space
|
|
* and can be sent even if no window space is
|
|
* available.
|
|
*
|
|
* It is RECOMMENDED that all data sent before
|
|
* this message be delivered to the actual
|
|
* destination, if possible.
|
|
*/
|
|
state_get_u32(pss, SSHS_NVC_CH_CLOSE);
|
|
break;
|
|
|
|
case SSH_MSG_CHANNEL_DATA:
|
|
/* RFC4254
|
|
*
|
|
* byte SSH_MSG_CHANNEL_DATA
|
|
* uint32 recipient channel
|
|
* string data
|
|
*/
|
|
state_get_u32(pss, SSHS_NVC_CD_RECIP);
|
|
break;
|
|
|
|
case SSH_MSG_CHANNEL_WINDOW_ADJUST:
|
|
/* RFC452
|
|
*
|
|
* byte SSH_MSG_CHANNEL_WINDOW_ADJUST
|
|
* uint32 recipient channel
|
|
* uint32 bytes to add
|
|
*/
|
|
if (!pss->ch_list)
|
|
goto bail;
|
|
|
|
state_get_u32(pss, SSHS_NVC_WA_RECIP);
|
|
break;
|
|
default:
|
|
lwsl_notice("unk msg_id %d\n", pss->msg_id);
|
|
|
|
goto bail;
|
|
}
|
|
break;
|
|
|
|
case SSH_KEX_STATE_COOKIE:
|
|
if (pss->msg_len < 16 + 1 + 1 + (10 * 4) + 5) {
|
|
lwsl_notice("sanity: kex length failed\n");
|
|
goto bail;
|
|
}
|
|
pss->kex->kex_cookie[pss->ctr++] = *p++;
|
|
if (pss->ctr != sizeof(pss->kex->kex_cookie))
|
|
break;
|
|
pss->parser_state = SSH_KEX_NL_KEX_ALGS_LEN;
|
|
pss->ctr = 0;
|
|
break;
|
|
case SSH_KEX_NL_KEX_ALGS_LEN:
|
|
case SSH_KEX_NL_SHK_ALGS_LEN:
|
|
case SSH_KEX_NL_EACTS_ALGS_LEN:
|
|
case SSH_KEX_NL_EASTC_ALGS_LEN:
|
|
case SSH_KEX_NL_MACTS_ALGS_LEN:
|
|
case SSH_KEX_NL_MASTC_ALGS_LEN:
|
|
case SSH_KEX_NL_CACTS_ALGS_LEN:
|
|
case SSH_KEX_NL_CASTC_ALGS_LEN:
|
|
case SSH_KEX_NL_LCTS_ALGS_LEN:
|
|
case SSH_KEX_NL_LSTC_ALGS_LEN:
|
|
case SSH_KEX_STATE_ECDH_KEYLEN:
|
|
|
|
pss->len = (pss->len << 8) | *p++;
|
|
if (++pss->ctr != 4)
|
|
break;
|
|
|
|
switch (pss->parser_state) {
|
|
case SSH_KEX_STATE_ECDH_KEYLEN:
|
|
pss->parser_state = SSH_KEX_STATE_ECDH_Q_C;
|
|
break;
|
|
default:
|
|
pss->parser_state++;
|
|
if (pss->len == 0)
|
|
pss->parser_state++;
|
|
break;
|
|
}
|
|
pss->ctr = 0;
|
|
pss->npos = 0;
|
|
if (pss->msg_len - pss->pos < pss->len) {
|
|
lwsl_notice("sanity: length %d - %d < %d\n",
|
|
pss->msg_len, pss->pos, pss->len);
|
|
goto bail;
|
|
}
|
|
break;
|
|
|
|
case SSH_KEX_NL_KEX_ALGS:
|
|
case SSH_KEX_NL_SHK_ALGS:
|
|
case SSH_KEX_NL_EACTS_ALGS:
|
|
case SSH_KEX_NL_EASTC_ALGS:
|
|
case SSH_KEX_NL_MACTS_ALGS:
|
|
case SSH_KEX_NL_MASTC_ALGS:
|
|
case SSH_KEX_NL_CACTS_ALGS:
|
|
case SSH_KEX_NL_CASTC_ALGS:
|
|
case SSH_KEX_NL_LCTS_ALGS:
|
|
case SSH_KEX_NL_LSTC_ALGS:
|
|
if (*p != ',') {
|
|
if (pss->npos < sizeof(pss->name) - 1)
|
|
pss->name[pss->npos++] = (char)*p;
|
|
} else {
|
|
pss->name[pss->npos] = '\0';
|
|
pss->npos = 0;
|
|
handle_name(pss);
|
|
}
|
|
p++;
|
|
if (!--pss->len) {
|
|
pss->name[pss->npos] = '\0';
|
|
if (pss->npos)
|
|
handle_name(pss);
|
|
pss->parser_state++;
|
|
break;
|
|
}
|
|
break;
|
|
|
|
case SSH_KEX_FIRST_PKT:
|
|
pss->first_coming = !!*p++;
|
|
pss->parser_state = SSH_KEX_RESERVED;
|
|
break;
|
|
|
|
case SSH_KEX_RESERVED:
|
|
pss->len = (pss->len << 8) | *p++;
|
|
if (++pss->ctr != 4)
|
|
break;
|
|
pss->ctr = 0;
|
|
if (pss->msg_padding) {
|
|
pss->copy_to_I_C = 0;
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
}
|
|
pss->parser_state = SSHS_MSG_LEN;
|
|
break;
|
|
|
|
case SSH_KEX_STATE_ECDH_Q_C:
|
|
if (pss->len != 32) {
|
|
lwsl_notice("wrong key len\n");
|
|
goto bail;
|
|
}
|
|
pss->kex->Q_C[pss->ctr++] = *p++;
|
|
if (pss->ctr != 32)
|
|
break;
|
|
lwsl_info("Q_C parsed\n");
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
case SSH_KEX_STATE_SKIP:
|
|
if (pss->pos - 4 < pss->msg_len) {
|
|
p++;
|
|
break;
|
|
}
|
|
lwsl_debug("skip done pos %d, msg_len %d len=%ld, \n",
|
|
pss->pos, pss->msg_len, (long)len);
|
|
pss->parser_state = SSHS_MSG_LEN;
|
|
pss->ctr = 0;
|
|
break;
|
|
|
|
case SSHS_MSG_EAT_PADDING:
|
|
p++;
|
|
if (--pss->msg_padding)
|
|
break;
|
|
if (pss->msg_len + 4 != pss->pos) {
|
|
lwsl_notice("sanity: kex end mismatch %d %d\n",
|
|
pss->pos, pss->msg_len);
|
|
goto bail;
|
|
}
|
|
|
|
switch (pss->msg_id) {
|
|
case SSH_MSG_KEX_ECDH_INIT:
|
|
if (pss->kex->match_bitfield != 0xff) {
|
|
lwsl_notice("unable to negotiate\n");
|
|
goto bail;
|
|
}
|
|
if (kex_ecdh(pss, pss->kex->kex_r,
|
|
&pss->kex->kex_r_len)) {
|
|
lwsl_notice("hex_ecdh failed\n");
|
|
goto bail;
|
|
}
|
|
write_task(pss, NULL, SSH_WT_OFFER_REPLY);
|
|
break;
|
|
}
|
|
|
|
pss->parser_state = SSHS_MSG_LEN;
|
|
pss->ctr = 0;
|
|
break;
|
|
|
|
case SSHS_GET_STRING_LEN:
|
|
pss->npos = 0;
|
|
pss->len = (pss->len << 8) | *p++;
|
|
if (++pss->ctr != 4)
|
|
break;
|
|
pss->ctr = 0;
|
|
pss->parser_state = SSHS_GET_STRING;
|
|
break;
|
|
|
|
case SSHS_GET_STRING:
|
|
if (pss->npos >= sizeof(pss->name) - 1) {
|
|
lwsl_notice("non-alloc string too big\n");
|
|
goto bail;
|
|
}
|
|
pss->name[pss->npos++] = (char)*p++;
|
|
if (pss->npos != pss->len)
|
|
break;
|
|
|
|
pss->name[pss->npos] = '\0';
|
|
pss->parser_state = pss->state_after_string;
|
|
goto again;
|
|
|
|
case SSHS_GET_STRING_LEN_ALLOC:
|
|
pss->npos = 0;
|
|
pss->len = (pss->len << 8) | *p++;
|
|
if (++pss->ctr != 4)
|
|
break;
|
|
pss->ctr = 0;
|
|
pss->last_alloc = sshd_zalloc(pss->len + 1);
|
|
lwsl_debug("SSHS_GET_STRING_LEN_ALLOC: %p, state %d\n",
|
|
pss->last_alloc, pss->state_after_string);
|
|
if (!pss->last_alloc) {
|
|
lwsl_notice("alloc string too big\n");
|
|
goto bail;
|
|
}
|
|
pss->parser_state = SSHS_GET_STRING_ALLOC;
|
|
break;
|
|
|
|
case SSHS_GET_STRING_ALLOC:
|
|
if (pss->npos >= pss->len)
|
|
goto bail;
|
|
pss->last_alloc[pss->npos++] = *p++;
|
|
if (pss->npos != pss->len)
|
|
break;
|
|
pss->last_alloc[pss->npos] = '\0';
|
|
pss->parser_state = pss->state_after_string;
|
|
goto again;
|
|
|
|
/*
|
|
* User Authentication
|
|
*/
|
|
|
|
case SSHS_DO_SERVICE_REQUEST:
|
|
pss->okayed_userauth = 1;
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
/*
|
|
* this only 'accepts' that we can negotiate auth for
|
|
* this service, not accepts the auth
|
|
*/
|
|
write_task(pss, NULL, SSH_WT_UA_ACCEPT);
|
|
break;
|
|
|
|
case SSHS_DO_UAR_SVC:
|
|
pss->ua->username = (char *)pss->last_alloc;
|
|
pss->last_alloc = NULL; /* it was adopted */
|
|
state_get_string_alloc(pss, SSHS_DO_UAR_PUBLICKEY);
|
|
/* destroyed with UA struct */
|
|
break;
|
|
|
|
case SSHS_DO_UAR_PUBLICKEY:
|
|
pss->ua->service = (char *)pss->last_alloc;
|
|
pss->last_alloc = NULL; /* it was adopted */
|
|
|
|
/* Sect 5, RFC4252
|
|
*
|
|
* The 'user name' and 'service name' are repeated in
|
|
* every new authentication attempt, and MAY change.
|
|
*
|
|
* The server implementation MUST carefully check them
|
|
* in every message, and MUST flush any accumulated
|
|
* authentication states if they change. If it is
|
|
* unable to flush an authentication state, it MUST
|
|
* disconnect if the 'user name' or 'service name'
|
|
* changes.
|
|
*/
|
|
|
|
if (pss->seen_auth_req_before && (
|
|
strcmp(pss->ua->username,
|
|
pss->last_auth_req_username) ||
|
|
strcmp(pss->ua->service,
|
|
pss->last_auth_req_service))) {
|
|
lwsl_notice("username / svc changed\n");
|
|
|
|
goto bail;
|
|
}
|
|
|
|
pss->seen_auth_req_before = 1;
|
|
lws_strncpy(pss->last_auth_req_username,
|
|
pss->ua->username,
|
|
sizeof(pss->last_auth_req_username));
|
|
lws_strncpy(pss->last_auth_req_service,
|
|
pss->ua->service,
|
|
sizeof(pss->last_auth_req_service));
|
|
|
|
if (strcmp(pss->ua->service, "ssh-connection"))
|
|
goto ua_fail;
|
|
state_get_string(pss, SSHS_NVC_DO_UAR_CHECK_PUBLICKEY);
|
|
break;
|
|
|
|
case SSHS_NVC_DO_UAR_CHECK_PUBLICKEY:
|
|
if (!strcmp(pss->name, "none")) {
|
|
/* we must fail it */
|
|
lwsl_info("got 'none' req, refusing\n");
|
|
goto ua_fail;
|
|
}
|
|
|
|
if (strcmp(pss->name, "publickey")) {
|
|
lwsl_notice("expected 'publickey' got '%s'\n",
|
|
pss->name);
|
|
goto ua_fail;
|
|
}
|
|
pss->parser_state = SSHS_DO_UAR_SIG_PRESENT;
|
|
break;
|
|
|
|
case SSHS_DO_UAR_SIG_PRESENT:
|
|
lwsl_info("SSHS_DO_UAR_SIG_PRESENT\n");
|
|
pss->ua->sig_present = (char)*p++;
|
|
state_get_string_alloc(pss, SSHS_NVC_DO_UAR_ALG);
|
|
/* destroyed with UA struct */
|
|
break;
|
|
|
|
case SSHS_NVC_DO_UAR_ALG:
|
|
pss->ua->alg = (char *)pss->last_alloc;
|
|
pss->last_alloc = NULL; /* it was adopted */
|
|
if (rsa_hash_alg_from_ident(pss->ua->alg) < 0) {
|
|
lwsl_notice("unknown alg\n");
|
|
goto ua_fail;
|
|
}
|
|
state_get_string_alloc(pss, SSHS_NVC_DO_UAR_PUBKEY_BLOB);
|
|
/* destroyed with UA struct */
|
|
break;
|
|
|
|
case SSHS_NVC_DO_UAR_PUBKEY_BLOB:
|
|
pss->ua->pubkey = pss->last_alloc;
|
|
pss->last_alloc = NULL; /* it was adopted */
|
|
pss->ua->pubkey_len = pss->npos;
|
|
/*
|
|
* RFC4253
|
|
*
|
|
* ssh-rsa
|
|
*
|
|
* The structure inside the blob is
|
|
*
|
|
* mpint e
|
|
* mpint n
|
|
*
|
|
* Let's see if this key is authorized
|
|
*/
|
|
|
|
n = 1;
|
|
if (pss->vhd->ops && pss->vhd->ops->is_pubkey_authorized)
|
|
n = pss->vhd->ops->is_pubkey_authorized(
|
|
pss->ua->username, pss->ua->alg,
|
|
pss->ua->pubkey, (int)pss->ua->pubkey_len);
|
|
if (n) {
|
|
lwsl_info("rejecting peer pubkey\n");
|
|
goto ua_fail;
|
|
}
|
|
|
|
if (pss->ua->sig_present) {
|
|
state_get_string_alloc(pss, SSHS_NVC_DO_UAR_SIG);
|
|
/* destroyed with UA struct */
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* This key is at least one we would be prepared
|
|
* to accept if he really has it... since no sig
|
|
* client should resend everything with a sig
|
|
* appended. OK it and delete this initial UA
|
|
*/
|
|
write_task(pss, NULL, SSH_WT_UA_PK_OK);
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
case SSHS_NVC_DO_UAR_SIG:
|
|
/*
|
|
* Now the pubkey is coming with a sig
|
|
*/
|
|
/* Sect 5.1 RFC4252
|
|
*
|
|
* SSH_MSG_USERAUTH_SUCCESS MUST be sent only once.
|
|
* When SSH_MSG_USERAUTH_SUCCESS has been sent, any
|
|
* further authentication requests received after that
|
|
* SHOULD be silently ignored.
|
|
*/
|
|
if (pss->ssh_auth_state == SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS) {
|
|
lwsl_info("Silently ignoring auth req after accepted\n");
|
|
goto ua_fail_silently;
|
|
}
|
|
lwsl_info("SSHS_DO_UAR_SIG\n");
|
|
pss->ua->sig = pss->last_alloc;
|
|
pss->last_alloc = NULL; /* it was adopted */
|
|
pss->ua->sig_len = pss->npos;
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
|
|
/*
|
|
* RFC 4252 p9
|
|
*
|
|
* The value of 'signature' is a signature with
|
|
* the private host key of the following data, in
|
|
* this order:
|
|
*
|
|
* string session identifier
|
|
* byte SSH_MSG_USERAUTH_REQUEST
|
|
* string user name
|
|
* string service name
|
|
* string "publickey"
|
|
* boolean TRUE
|
|
* string public key algorithm name
|
|
* string public key to be used for auth
|
|
*
|
|
* We reproduce the signature plaintext and the
|
|
* hash, and then decrypt the incoming signed block.
|
|
* What comes out is some ASN1, in there is the
|
|
* hash decrypted. We find it and confirm it
|
|
* matches the hash we computed ourselves.
|
|
*
|
|
* First step is generate the sig plaintext
|
|
*/
|
|
n = 4 + 32 +
|
|
1 +
|
|
4 + (int)strlen(pss->ua->username) +
|
|
4 + (int)strlen(pss->ua->service) +
|
|
4 + 9 +
|
|
1 +
|
|
4 + (int)strlen(pss->ua->alg) +
|
|
4 + (int)pss->ua->pubkey_len;
|
|
|
|
ps = sshd_zalloc((unsigned int)n);
|
|
if (!ps) {
|
|
lwsl_notice("OOM 4\n");
|
|
goto ua_fail;
|
|
}
|
|
|
|
pp = ps;
|
|
lws_buf(&pp, pss->session_id, 32);
|
|
*pp++ = SSH_MSG_USERAUTH_REQUEST;
|
|
lws_cstr(&pp, pss->ua->username, 64);
|
|
lws_cstr(&pp, pss->ua->service, 64);
|
|
lws_cstr(&pp, "publickey", 64);
|
|
*pp++ = 1;
|
|
lws_cstr(&pp, pss->ua->alg, 64);
|
|
lws_buf(&pp, pss->ua->pubkey, pss->ua->pubkey_len);
|
|
|
|
/* Next hash the plaintext */
|
|
|
|
if (lws_genhash_init(&pss->ua->hash_ctx,
|
|
(enum lws_genhash_types)rsa_hash_alg_from_ident(pss->ua->alg))) {
|
|
lwsl_notice("genhash init failed\n");
|
|
free(ps);
|
|
goto ua_fail;
|
|
}
|
|
|
|
if (lws_genhash_update(&pss->ua->hash_ctx, ps, lws_ptr_diff_size_t(pp, ps))) {
|
|
lwsl_notice("genhash update failed\n");
|
|
free(ps);
|
|
goto ua_fail;
|
|
}
|
|
lws_genhash_destroy(&pss->ua->hash_ctx, hash);
|
|
free(ps);
|
|
|
|
/*
|
|
* Prepare the RSA decryption context: load in
|
|
* the E and N factors
|
|
*/
|
|
|
|
memset(e, 0, sizeof(e));
|
|
pp = pss->ua->pubkey;
|
|
m = lws_g32(&pp);
|
|
pp += m;
|
|
m = lws_g32(&pp);
|
|
e[LWS_GENCRYPTO_RSA_KEYEL_E].buf = pp;
|
|
e[LWS_GENCRYPTO_RSA_KEYEL_E].len = m;
|
|
pp += m;
|
|
m = lws_g32(&pp);
|
|
e[LWS_GENCRYPTO_RSA_KEYEL_N].buf = pp;
|
|
e[LWS_GENCRYPTO_RSA_KEYEL_N].len = m;
|
|
|
|
if (lws_genrsa_create(&ctx, e, pss->vhd->context,
|
|
LGRSAM_PKCS1_1_5,
|
|
LWS_GENHASH_TYPE_UNKNOWN))
|
|
goto ua_fail;
|
|
/*
|
|
* point to the encrypted signature payload we
|
|
* were sent
|
|
*/
|
|
pp = pss->ua->sig;
|
|
m = lws_g32(&pp);
|
|
pp += m;
|
|
m = lws_g32(&pp);
|
|
#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < 0x03000000
|
|
|
|
/*
|
|
* decrypt it, resulting in an error, or some ASN1
|
|
* including the decrypted signature
|
|
*/
|
|
otmp = sshd_zalloc(m);
|
|
if (!otmp)
|
|
/* ua_fail1 frees bn_e, bn_n and rsa */
|
|
goto ua_fail1;
|
|
|
|
n = lws_genrsa_public_decrypt(&ctx, pp, m, otmp, m);
|
|
if (n > 0) {
|
|
/* the decrypted sig is in ASN1 format */
|
|
m = 0;
|
|
while ((int)m < n) {
|
|
/* sig payload */
|
|
if (otmp[m] == 0x04 &&
|
|
otmp[m + 1] == lws_genhash_size(
|
|
pss->ua->hash_ctx.type)) {
|
|
m = (uint32_t)memcmp(&otmp[m + 2], hash,
|
|
(unsigned int)lws_genhash_size(pss->ua->hash_ctx.type));
|
|
break;
|
|
}
|
|
/* go into these */
|
|
if (otmp[m] == 0x30) {
|
|
m += 2;
|
|
continue;
|
|
}
|
|
/* otherwise skip payloads */
|
|
m += (uint32_t)(otmp[m + 1] + 2);
|
|
}
|
|
}
|
|
|
|
free(otmp);
|
|
#else
|
|
ctx.ctx->MBEDTLS_PRIVATE(len) = m;
|
|
n = lws_genrsa_hash_sig_verify(&ctx, hash,
|
|
(enum lws_genhash_types)rsa_hash_alg_from_ident(pss->ua->alg),
|
|
pp, m) == 0 ? 1 : 0;
|
|
#endif
|
|
lws_genrsa_destroy(&ctx);
|
|
|
|
/*
|
|
* if no good, m is nonzero and inform peer
|
|
*/
|
|
if (n <= 0) {
|
|
lwsl_notice("hash sig verify fail: %d\n", m);
|
|
goto ua_fail;
|
|
}
|
|
|
|
/* if it checks out, inform peer */
|
|
|
|
lwsl_info("sig check OK\n");
|
|
|
|
/* Sect 5.1 RFC4252
|
|
*
|
|
* SSH_MSG_USERAUTH_SUCCESS MUST be sent only once.
|
|
* When SSH_MSG_USERAUTH_SUCCESS has been sent, any
|
|
* further authentication requests received after that
|
|
* SHOULD be silently ignored.
|
|
*/
|
|
pss->ssh_auth_state = SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS;
|
|
|
|
write_task(pss, NULL, SSH_WT_UA_SUCCESS);
|
|
lws_ua_destroy(pss);
|
|
break;
|
|
|
|
/*
|
|
* Channels
|
|
*/
|
|
|
|
case SSHS_GET_U32:
|
|
pss->len = (pss->len << 8) | *p++;
|
|
if (++pss->ctr != 4)
|
|
break;
|
|
pss->ctr = 0;
|
|
pss->parser_state = pss->state_after_string;
|
|
goto again;
|
|
|
|
/*
|
|
* Channel: Disconnect
|
|
*/
|
|
|
|
case SSHS_NVC_DISCONNECT_REASON:
|
|
pss->disconnect_reason = pss->len;
|
|
state_get_string_alloc(pss, SSHS_NVC_DISCONNECT_DESC);
|
|
break;
|
|
|
|
case SSHS_NVC_DISCONNECT_DESC:
|
|
pss->disconnect_desc = (char *)pss->last_alloc;
|
|
state_get_string(pss, SSHS_NVC_DISCONNECT_LANG);
|
|
break;
|
|
|
|
case SSHS_NVC_DISCONNECT_LANG:
|
|
lwsl_notice("SSHS_NVC_DISCONNECT_LANG\n");
|
|
if (pss->vhd->ops && pss->vhd->ops->disconnect_reason)
|
|
pss->vhd->ops->disconnect_reason(
|
|
pss->disconnect_reason,
|
|
pss->disconnect_desc, pss->name);
|
|
ssh_free_set_NULL(pss->last_alloc);
|
|
break;
|
|
|
|
/*
|
|
* Channel: Open
|
|
*/
|
|
|
|
case SSHS_NVC_CHOPEN_TYPE:
|
|
/* channel open */
|
|
if (strcmp(pss->name, "session")) {
|
|
lwsl_notice("Failing on not session\n");
|
|
pss->reason = 3;
|
|
goto ch_fail;
|
|
}
|
|
lwsl_info("SSHS_NVC_CHOPEN_TYPE: creating session\n");
|
|
pss->ch_temp = sshd_zalloc(sizeof(*pss->ch_temp));
|
|
if (!pss->ch_temp)
|
|
return -1;
|
|
|
|
pss->ch_temp->type = SSH_CH_TYPE_SESSION;
|
|
pss->ch_temp->pss = pss;
|
|
state_get_u32(pss, SSHS_NVC_CHOPEN_SENDER_CH);
|
|
break;
|
|
|
|
case SSHS_NVC_CHOPEN_SENDER_CH:
|
|
pss->ch_temp->sender_ch = pss->len;
|
|
state_get_u32(pss, SSHS_NVC_CHOPEN_WINSIZE);
|
|
break;
|
|
case SSHS_NVC_CHOPEN_WINSIZE:
|
|
lwsl_info("Initial window set to %d\n", pss->len);
|
|
pss->ch_temp->window = (int32_t)pss->len;
|
|
state_get_u32(pss, SSHS_NVC_CHOPEN_PKTSIZE);
|
|
break;
|
|
case SSHS_NVC_CHOPEN_PKTSIZE:
|
|
pss->ch_temp->max_pkt = pss->len;
|
|
pss->ch_temp->peer_window_est = LWS_SSH_INITIAL_WINDOW;
|
|
pss->ch_temp->server_ch = (uint32_t)pss->next_ch_num++;
|
|
/*
|
|
* add us to channel list... leave as ch_temp
|
|
* as write task needs it and will NULL down
|
|
*/
|
|
lwsl_info("creating new session ch\n");
|
|
pss->ch_temp->next = pss->ch_list;
|
|
pss->ch_list = pss->ch_temp;
|
|
if (pss->vhd->ops && pss->vhd->ops->channel_create)
|
|
pss->vhd->ops->channel_create(pss->wsi,
|
|
&pss->ch_temp->priv);
|
|
write_task(pss, pss->ch_temp, SSH_WT_CH_OPEN_CONF);
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
/*
|
|
* SSH_MSG_CHANNEL_REQUEST
|
|
*/
|
|
|
|
case SSHS_NVC_CHRQ_RECIP:
|
|
pss->ch_recip = pss->len;
|
|
state_get_string(pss, SSHS_NVC_CHRQ_TYPE);
|
|
break;
|
|
|
|
case SSHS_NVC_CHRQ_TYPE:
|
|
pss->parser_state = SSHS_CHRQ_WANT_REPLY;
|
|
break;
|
|
|
|
case SSHS_CHRQ_WANT_REPLY:
|
|
pss->rq_want_reply = *p++;
|
|
lwsl_info("SSHS_CHRQ_WANT_REPLY: %s, wantrep: %d\n",
|
|
pss->name, pss->rq_want_reply);
|
|
|
|
pss->ch_temp = ssh_get_server_ch(pss, pss->ch_recip);
|
|
|
|
/* after this they differ by the request */
|
|
|
|
/*
|
|
* a PTY for a shell
|
|
*/
|
|
if (!strcmp(pss->name, "pty-req")) {
|
|
state_get_string(pss, SSHS_NVC_CHRQ_TERM);
|
|
break;
|
|
}
|
|
/*
|
|
* a shell
|
|
*/
|
|
if (!strcmp(pss->name, "shell")) {
|
|
pss->channel_doing_spawn = pss->ch_temp->server_ch;
|
|
if (pss->vhd->ops && pss->vhd->ops->shell &&
|
|
!pss->vhd->ops->shell(pss->ch_temp->priv,
|
|
pss->wsi,
|
|
lws_ssh_exec_finish, pss->ch_temp)) {
|
|
|
|
if (pss->rq_want_reply)
|
|
write_task_insert(pss, pss->ch_temp,
|
|
SSH_WT_CHRQ_SUCC);
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
}
|
|
|
|
goto chrq_fail;
|
|
}
|
|
/*
|
|
* env vars to be set in the shell
|
|
*/
|
|
if (!strcmp(pss->name, "env")) {
|
|
state_get_string(pss, SSHS_NVC_CHRQ_ENV_NAME);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* exec something
|
|
*/
|
|
if (!strcmp(pss->name, "exec")) {
|
|
state_get_string_alloc(pss, SSHS_NVC_CHRQ_EXEC_CMD);
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* spawn a subsystem
|
|
*/
|
|
if (!strcmp(pss->name, "subsystem")) {
|
|
lwsl_notice("subsystem\n");
|
|
state_get_string_alloc(pss,
|
|
SSHS_NVC_CHRQ_SUBSYSTEM);
|
|
break;
|
|
}
|
|
if (!strcmp(pss->name, "window-change")) {
|
|
lwsl_info("%s: window-change\n", __func__);
|
|
state_get_u32(pss,
|
|
SSHS_NVC_CHRQ_WNDCHANGE_TW);
|
|
break;
|
|
}
|
|
|
|
if (pss->rq_want_reply)
|
|
goto chrq_fail;
|
|
|
|
pss->parser_state = SSH_KEX_STATE_SKIP;
|
|
break;
|
|
|
|
/* CHRQ pty-req */
|
|
|
|
case SSHS_NVC_CHRQ_TERM:
|
|
memcpy(pss->args.pty.term, pss->name,
|
|
sizeof(pss->args.pty.term) - 1);
|
|
state_get_u32(pss, SSHS_NVC_CHRQ_TW);
|
|
break;
|
|
case SSHS_NVC_CHRQ_TW:
|
|
pss->args.pty.width_ch = pss->len;
|
|
state_get_u32(pss, SSHS_NVC_CHRQ_TH);
|
|
break;
|
|
case SSHS_NVC_CHRQ_TH:
|
|
pss->args.pty.height_ch = pss->len;
|
|
state_get_u32(pss, SSHS_NVC_CHRQ_TWP);
|
|
break;
|
|
case SSHS_NVC_CHRQ_TWP:
|
|
pss->args.pty.width_px = pss->len;
|
|
state_get_u32(pss, SSHS_NVC_CHRQ_THP);
|
|
break;
|
|
case SSHS_NVC_CHRQ_THP:
|
|
pss->args.pty.height_px = pss->len;
|
|
state_get_string_alloc(pss, SSHS_NVC_CHRQ_MODES);
|
|
break;
|
|
case SSHS_NVC_CHRQ_MODES:
|
|
/* modes is a stream of byte-pairs, not a string */
|
|
pss->args.pty.modes = (char *)pss->last_alloc;
|
|
pss->last_alloc = NULL; /* it was adopted */
|
|
pss->args.pty.modes_len = pss->npos;
|
|
n = 0;
|
|
if (pss->vhd->ops && pss->vhd->ops->pty_req)
|
|
n = pss->vhd->ops->pty_req(pss->ch_temp->priv,
|
|
&pss->args.pty);
|
|
ssh_free_set_NULL(pss->args.pty.modes);
|
|
if (n)
|
|
goto chrq_fail;
|
|
if (pss->rq_want_reply)
|
|
write_task(pss, pss->ch_temp, SSH_WT_CHRQ_SUCC);
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
/* CHRQ env */
|
|
|
|
case SSHS_NVC_CHRQ_ENV_NAME:
|
|
strcpy(pss->args.aux, pss->name);
|
|
state_get_string(pss, SSHS_NVC_CHRQ_ENV_VALUE);
|
|
break;
|
|
|
|
case SSHS_NVC_CHRQ_ENV_VALUE:
|
|
if (pss->vhd->ops && pss->vhd->ops->set_env)
|
|
if (pss->vhd->ops->set_env(pss->ch_temp->priv,
|
|
pss->args.aux, pss->name))
|
|
goto chrq_fail;
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
/* CHRQ exec */
|
|
|
|
case SSHS_NVC_CHRQ_EXEC_CMD:
|
|
/*
|
|
* byte SSH_MSG_CHANNEL_REQUEST
|
|
* uint32 recipient channel
|
|
* string "exec"
|
|
* boolean want reply
|
|
* string command
|
|
*
|
|
* This message will request that the server start the
|
|
* execution of the given command. The 'command' string
|
|
* may contain a path. Normal precautions MUST be taken
|
|
* to prevent the execution of unauthorized commands.
|
|
*
|
|
* scp sends "scp -t /path/..."
|
|
*/
|
|
lwsl_info("exec cmd: %s %02X\n", pss->last_alloc, *p);
|
|
|
|
pss->channel_doing_spawn = pss->ch_temp->server_ch;
|
|
|
|
if (pss->vhd->ops && pss->vhd->ops->exec &&
|
|
!pss->vhd->ops->exec(pss->ch_temp->priv, pss->wsi,
|
|
(const char *)pss->last_alloc,
|
|
lws_ssh_exec_finish, pss->ch_temp)) {
|
|
ssh_free_set_NULL(pss->last_alloc);
|
|
if (pss->rq_want_reply)
|
|
write_task_insert(pss, pss->ch_temp,
|
|
SSH_WT_CHRQ_SUCC);
|
|
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* even if he doesn't want to exec it, we know how to
|
|
* fake scp
|
|
*/
|
|
|
|
/* we only alloc "exec" of scp for scp destination */
|
|
n = 1;
|
|
if (pss->last_alloc[0] != 's' ||
|
|
pss->last_alloc[1] != 'c' ||
|
|
pss->last_alloc[2] != 'p' ||
|
|
pss->last_alloc[3] != ' ')
|
|
/* disallow it */
|
|
n = 0;
|
|
|
|
ssh_free_set_NULL(pss->last_alloc);
|
|
if (!n)
|
|
goto chrq_fail;
|
|
|
|
/* our channel speaks SCP protocol now */
|
|
|
|
scp = sshd_zalloc(sizeof(*scp));
|
|
if (!scp)
|
|
return -1;
|
|
|
|
pss->ch_temp->type = SSH_CH_TYPE_SCP;
|
|
pss->ch_temp->sub = (lws_subprotocol *)scp;
|
|
|
|
scp->ips = SSHS_SCP_COLLECTSTR;
|
|
|
|
if (pss->rq_want_reply)
|
|
write_task(pss, pss->ch_temp, SSH_WT_CHRQ_SUCC);
|
|
|
|
/* we start the scp protocol first by sending an ACK */
|
|
write_task(pss, pss->ch_temp, SSH_WT_SCP_ACK_OKAY);
|
|
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
case SSHS_NVC_CHRQ_SUBSYSTEM:
|
|
lwsl_notice("subsystem: %s", pss->last_alloc);
|
|
n = 0;
|
|
#if 0
|
|
if (!strcmp(pss->name, "sftp")) {
|
|
lwsl_notice("SFTP session\n");
|
|
pss->ch_temp->type = SSH_CH_TYPE_SFTP;
|
|
n = 1;
|
|
}
|
|
#endif
|
|
ssh_free_set_NULL(pss->last_alloc);
|
|
// if (!n)
|
|
goto ch_fail;
|
|
#if 0
|
|
if (pss->rq_want_reply)
|
|
write_task(pss, ssh_get_server_ch(pss,
|
|
pss->ch_recip), SSH_WT_CHRQ_SUCC);
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
#endif
|
|
|
|
/* CHRQ window-change */
|
|
|
|
case SSHS_NVC_CHRQ_WNDCHANGE_TW:
|
|
pss->args.pty.width_ch = pss->len;
|
|
state_get_u32(pss, SSHS_NVC_CHRQ_WNDCHANGE_TH);
|
|
break;
|
|
case SSHS_NVC_CHRQ_WNDCHANGE_TH:
|
|
pss->args.pty.height_ch = pss->len;
|
|
state_get_u32(pss, SSHS_NVC_CHRQ_WNDCHANGE_TWP);
|
|
break;
|
|
case SSHS_NVC_CHRQ_WNDCHANGE_TWP:
|
|
pss->args.pty.width_px = pss->len;
|
|
state_get_u32(pss, SSHS_NVC_CHRQ_WNDCHANGE_THP);
|
|
break;
|
|
case SSHS_NVC_CHRQ_WNDCHANGE_THP:
|
|
pss->args.pty.height_px = pss->len;
|
|
pss->args.pty.term[0] = 0;
|
|
pss->args.pty.modes = NULL;
|
|
pss->args.pty.modes_len = 0;
|
|
n = 0;
|
|
if (pss->vhd->ops && pss->vhd->ops->pty_req)
|
|
n = pss->vhd->ops->pty_req(pss->ch_temp->priv,
|
|
&pss->args.pty);
|
|
if (n)
|
|
goto chrq_fail;
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
/* SSH_MSG_CHANNEL_DATA */
|
|
|
|
case SSHS_NVC_CD_RECIP:
|
|
pss->ch_recip = pss->len;
|
|
|
|
ch = ssh_get_server_ch(pss, pss->ch_recip);
|
|
ch->peer_window_est -= (int32_t)pss->msg_len;
|
|
|
|
if (pss->msg_len < sizeof(pss->name))
|
|
state_get_string(pss, SSHS_NVC_CD_DATA);
|
|
else
|
|
state_get_string_alloc(pss,
|
|
SSHS_NVC_CD_DATA_ALLOC);
|
|
break;
|
|
|
|
case SSHS_NVC_CD_DATA_ALLOC:
|
|
case SSHS_NVC_CD_DATA:
|
|
/*
|
|
* Actual protocol incoming payload
|
|
*/
|
|
if (pss->parser_state == SSHS_NVC_CD_DATA_ALLOC)
|
|
pp = pss->last_alloc;
|
|
else
|
|
pp = (uint8_t *)pss->name;
|
|
lwsl_info("SSHS_NVC_CD_DATA\n");
|
|
|
|
ch = ssh_get_server_ch(pss, pss->ch_recip);
|
|
switch (ch->type) {
|
|
case SSH_CH_TYPE_SCP:
|
|
scp = &ch->sub->scp;
|
|
switch (scp->ips) {
|
|
case SSHS_SCP_COLLECTSTR:
|
|
/* gather the ascii-coded headers */
|
|
for (n = 0; n < (int)pss->npos; n++)
|
|
lwsl_notice("0x%02X %c\n",
|
|
pp[n], pp[n]);
|
|
|
|
/* Header triggers the transfer? */
|
|
if (pp[0] == 'C' && pp[pss->npos - 1] == '\x0a') {
|
|
while (*pp != ' ' && *pp != '\x0a')
|
|
pp++;
|
|
if (*pp++ != ' ') {
|
|
write_task(pss, ch,
|
|
SSH_WT_SCP_ACK_ERROR);
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
}
|
|
scp->len = (uint64_t)atoll((const char *)pp);
|
|
lwsl_notice("scp payload %llu expected\n",
|
|
(unsigned long long)scp->len);
|
|
scp->ips = SSHS_SCP_PAYLOADIN;
|
|
}
|
|
/* ack it */
|
|
write_task(pss, pss->ch_temp,
|
|
SSH_WT_SCP_ACK_OKAY);
|
|
break;
|
|
case SSHS_SCP_PAYLOADIN:
|
|
/* the scp file payload */
|
|
if (pss->vhd->ops)
|
|
pss->vhd->ops->rx(ch->priv,
|
|
pss->wsi, pp, pss->npos);
|
|
if (scp->len >= pss->npos)
|
|
scp->len -= pss->npos;
|
|
else
|
|
scp->len = 0;
|
|
if (!scp->len) {
|
|
lwsl_notice("scp txfer completed\n");
|
|
scp->ips = SSHS_SCP_COLLECTSTR;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
default: /* scp payload */
|
|
if (pss->vhd->ops)
|
|
pss->vhd->ops->rx(ch->priv, pss->wsi,
|
|
pp, pss->npos);
|
|
break;
|
|
}
|
|
if (pss->parser_state == SSHS_NVC_CD_DATA_ALLOC)
|
|
ssh_free_set_NULL(pss->last_alloc);
|
|
|
|
if (ch->peer_window_est < 32768) {
|
|
write_task(pss, ch, SSH_WT_WINDOW_ADJUST);
|
|
ch->peer_window_est += 32768;
|
|
lwsl_info("extra peer WINDOW_ADJUST (~ %d)\n",
|
|
ch->peer_window_est);
|
|
}
|
|
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
case SSHS_NVC_WA_RECIP:
|
|
pss->ch_recip = pss->len;
|
|
state_get_u32(pss, SSHS_NVC_WA_ADD);
|
|
break;
|
|
|
|
case SSHS_NVC_WA_ADD:
|
|
ch = ssh_get_server_ch(pss, pss->ch_recip);
|
|
if (ch) {
|
|
ch->window += (int32_t)pss->len;
|
|
lwsl_notice("got additional window %d (now %d)\n",
|
|
pss->len, ch->window);
|
|
}
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
/*
|
|
* channel close
|
|
*/
|
|
|
|
case SSHS_NVC_CH_EOF:
|
|
/*
|
|
* No explicit response is sent to this
|
|
* message. However, the application may send
|
|
* EOF to whatever is at the other end of the
|
|
* channel. Note that the channel remains open
|
|
* after this message, and more data may still
|
|
* be sent in the other direction. This message
|
|
* does not consume window space and can be sent
|
|
* even if no window space is available.
|
|
*/
|
|
lwsl_notice("SSH_MSG_CHANNEL_EOF: %d\n", pss->ch_recip);
|
|
ch = ssh_get_server_ch(pss, pss->ch_recip);
|
|
if (!ch) {
|
|
lwsl_notice("unknown ch %d\n", pss->ch_recip);
|
|
return -1;
|
|
}
|
|
|
|
if (!ch->scheduled_close) {
|
|
lwsl_notice("scheduling CLOSE\n");
|
|
ch->scheduled_close = 1;
|
|
write_task(pss, ch, SSH_WT_CH_CLOSE);
|
|
}
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
break;
|
|
|
|
case SSHS_NVC_CH_CLOSE:
|
|
/*
|
|
* When either party wishes to terminate the
|
|
* channel, it sends SSH_MSG_CHANNEL_CLOSE.
|
|
* Upon receiving this message, a party MUST
|
|
* send back an SSH_MSG_CHANNEL_CLOSE unless it
|
|
* has already sent this message for the
|
|
* channel. The channel is considered closed
|
|
* for a party when it has both sent and
|
|
* received SSH_MSG_CHANNEL_CLOSE, and the
|
|
* party may then reuse the channel number.
|
|
* A party MAY send SSH_MSG_CHANNEL_CLOSE
|
|
* without having sent or received
|
|
* SSH_MSG_CHANNEL_EOF.
|
|
*/
|
|
lwsl_notice("SSH_MSG_CHANNEL_CLOSE ch %d\n",
|
|
pss->ch_recip);
|
|
ch = ssh_get_server_ch(pss, pss->ch_recip);
|
|
if (!ch)
|
|
goto bail;
|
|
|
|
pss->parser_state = SSHS_MSG_EAT_PADDING;
|
|
|
|
if (ch->sent_close) {
|
|
/*
|
|
* This is acking our sent close...
|
|
* we can destroy the channel with no
|
|
* further communication.
|
|
*/
|
|
ssh_destroy_channel(pss, ch);
|
|
break;
|
|
}
|
|
|
|
ch->received_close = 1;
|
|
ch->scheduled_close = 1;
|
|
write_task(pss, ch, SSH_WT_CH_CLOSE);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
|
|
chrq_fail:
|
|
lwsl_notice("chrq_fail\n");
|
|
write_task(pss, pss->ch_temp, SSH_WT_CHRQ_FAILURE);
|
|
pss->parser_state = SSH_KEX_STATE_SKIP;
|
|
break;
|
|
|
|
ch_fail:
|
|
if (pss->ch_temp) {
|
|
free(pss->ch_temp);
|
|
pss->ch_temp = NULL;
|
|
}
|
|
write_task(pss, pss->ch_temp, SSH_WT_CH_FAILURE);
|
|
pss->parser_state = SSH_KEX_STATE_SKIP;
|
|
break;
|
|
|
|
#if !defined(MBEDTLS_VERSION_NUMBER) || MBEDTLS_VERSION_NUMBER < 0x03000000
|
|
ua_fail1:
|
|
#endif
|
|
lws_genrsa_destroy(&ctx);
|
|
ua_fail:
|
|
write_task(pss, NULL, SSH_WT_UA_FAILURE);
|
|
ua_fail_silently:
|
|
lws_ua_destroy(pss);
|
|
/* Sect 4, RFC4252
|
|
*
|
|
* Additionally, the implementation SHOULD limit the
|
|
* number of failed authentication attempts a client
|
|
* may perform in a single session (the RECOMMENDED
|
|
* limit is 20 attempts). If the threshold is
|
|
* exceeded, the server SHOULD disconnect.
|
|
*/
|
|
if (pss->count_auth_attempts++ > 20)
|
|
goto bail;
|
|
|
|
pss->parser_state = SSH_KEX_STATE_SKIP;
|
|
break;
|
|
}
|
|
|
|
pss->pos++;
|
|
}
|
|
|
|
return 0;
|
|
bail:
|
|
lws_kex_destroy(pss);
|
|
lws_ua_destroy(pss);
|
|
|
|
return SSH_DISCONNECT_KEY_EXCHANGE_FAILED;
|
|
}
|
|
|
|
static int
|
|
parse(struct per_session_data__sshd *pss, uint8_t *p, size_t len)
|
|
{
|
|
while (len--) {
|
|
|
|
if (pss->copy_to_I_C && pss->kex->I_C_payload_len <
|
|
pss->kex->I_C_alloc_len &&
|
|
pss->parser_state != SSHS_MSG_EAT_PADDING)
|
|
pss->kex->I_C[pss->kex->I_C_payload_len++] = *p;
|
|
|
|
if (pss->active_keys_cts.valid &&
|
|
pss->parser_state == SSHS_MSG_LEN)
|
|
/* take a copy for full decrypt */
|
|
pss->packet_assembly[pss->pa_pos++] = *p;
|
|
|
|
if (pss->active_keys_cts.valid &&
|
|
pss->parser_state == SSHS_MSG_PADDING &&
|
|
pss->msg_len) {
|
|
/* we are going to have to decrypt it */
|
|
uint32_t cp, l = pss->msg_len + 4 +
|
|
pss->active_keys_cts.MAC_length;
|
|
uint8_t pt[2048];
|
|
|
|
len++;
|
|
cp = (uint32_t)len;
|
|
|
|
if (cp > l - pss->pa_pos)
|
|
cp = l - pss->pa_pos;
|
|
|
|
if (cp > sizeof(pss->packet_assembly) -
|
|
pss->pa_pos) {
|
|
lwsl_err("Packet is too big to decrypt\n");
|
|
|
|
goto bail;
|
|
}
|
|
if (pss->msg_len < 2 + 4) {
|
|
lwsl_err("packet too small\n");
|
|
|
|
goto bail;
|
|
}
|
|
|
|
memcpy(&pss->packet_assembly[pss->pa_pos], p, cp);
|
|
pss->pa_pos += cp;
|
|
len -= cp;
|
|
p += cp;
|
|
|
|
if (pss->pa_pos != l)
|
|
return 0;
|
|
|
|
/* decrypt it */
|
|
cp = (uint32_t)lws_chacha_decrypt(&pss->active_keys_cts,
|
|
pss->ssh_sequence_ctr_cts++,
|
|
pss->packet_assembly,
|
|
pss->pa_pos, pt);
|
|
if (cp) {
|
|
lwsl_notice("Decryption failed: %d\n", cp);
|
|
goto bail;
|
|
}
|
|
|
|
if (lws_ssh_parse_plaintext(pss, pt + 4, pss->msg_len))
|
|
goto bail;
|
|
|
|
pss->pa_pos = 0;
|
|
pss->ctr = 0;
|
|
continue;
|
|
}
|
|
|
|
if (lws_ssh_parse_plaintext(pss, p, 1))
|
|
goto bail;
|
|
|
|
p++;
|
|
}
|
|
|
|
return 0;
|
|
|
|
bail:
|
|
lws_kex_destroy(pss);
|
|
lws_ua_destroy(pss);
|
|
|
|
return SSH_DISCONNECT_KEY_EXCHANGE_FAILED;
|
|
}
|
|
|
|
static uint32_t
|
|
pad_and_encrypt(uint8_t *dest, void *ps, uint8_t *pp,
|
|
struct per_session_data__sshd *pss, int skip_pad)
|
|
{
|
|
uint32_t n;
|
|
|
|
if (!skip_pad)
|
|
lws_pad_set_length(pss, ps, &pp, &pss->active_keys_stc);
|
|
n = (uint32_t)lws_ptr_diff(pp, ps);
|
|
|
|
if (!pss->active_keys_stc.valid) {
|
|
memcpy(dest, ps, n);
|
|
return n;
|
|
}
|
|
|
|
lws_chacha_encrypt(&pss->active_keys_stc, pss->ssh_sequence_ctr_stc,
|
|
ps, n, dest);
|
|
n += pss->active_keys_stc.MAC_length;
|
|
|
|
return n;
|
|
}
|
|
|
|
static int
|
|
lws_callback_raw_sshd(struct lws *wsi, enum lws_callback_reasons reason,
|
|
void *user, void *in, size_t len)
|
|
{
|
|
struct per_session_data__sshd *pss =
|
|
(struct per_session_data__sshd *)user, **p;
|
|
struct per_vhost_data__sshd *vhd = NULL;
|
|
uint8_t buf[LWS_PRE + 1024], *pp, *ps = &buf[LWS_PRE + 512], *ps1 = NULL;
|
|
const struct lws_protocol_vhost_options *pvo;
|
|
const struct lws_protocols *prot;
|
|
struct lws_ssh_channel *ch;
|
|
char lang[10] = "";
|
|
int n, m, o;
|
|
|
|
/*
|
|
* Because we are an abstract protocol plugin, we will get called by
|
|
* wsi that actually bind to a plugin "on top of us" that calls thru
|
|
* to our callback.
|
|
*
|
|
* Under those circumstances, we can't simply get a pointer to our own
|
|
* protocol from the wsi. If there's a pss already, we can get it from
|
|
* there, but the first time for each connection we have to look it up.
|
|
*/
|
|
if (pss && pss->vhd)
|
|
vhd = (struct per_vhost_data__sshd *)
|
|
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
|
pss->vhd->protocol);
|
|
else
|
|
if (lws_get_vhost(wsi))
|
|
vhd = (struct per_vhost_data__sshd *)
|
|
lws_protocol_vh_priv_get(lws_get_vhost(wsi),
|
|
lws_vhost_name_to_protocol(
|
|
lws_get_vhost(wsi), "lws-ssh-base"));
|
|
|
|
switch ((int)reason) {
|
|
case LWS_CALLBACK_PROTOCOL_INIT:
|
|
vhd = lws_protocol_vh_priv_zalloc(lws_get_vhost(wsi),
|
|
lws_get_protocol(wsi),
|
|
sizeof(struct per_vhost_data__sshd));
|
|
if (!vhd)
|
|
return 0;
|
|
vhd->context = lws_get_context(wsi);
|
|
vhd->protocol = lws_get_protocol(wsi);
|
|
vhd->vhost = lws_get_vhost(wsi);
|
|
|
|
pvo = (const struct lws_protocol_vhost_options *)in;
|
|
while (pvo) {
|
|
/*
|
|
* the user code passes the ops struct address to us
|
|
* using a pvo (per-vhost option)
|
|
*/
|
|
if (!strcmp(pvo->name, "ops"))
|
|
vhd->ops = (const struct lws_ssh_ops *)pvo->value;
|
|
|
|
/*
|
|
* the user code is telling us to get the ops struct
|
|
* from another protocol's protocol.user pointer
|
|
*/
|
|
if (!strcmp(pvo->name, "ops-from")) {
|
|
prot = lws_vhost_name_to_protocol(vhd->vhost,
|
|
pvo->value);
|
|
if (prot)
|
|
vhd->ops = (const struct lws_ssh_ops *)prot->user;
|
|
else
|
|
lwsl_err("%s: can't find protocol %s\n",
|
|
__func__, pvo->value);
|
|
}
|
|
|
|
pvo = pvo->next;
|
|
}
|
|
|
|
if (!vhd->ops) {
|
|
lwsl_warn("ssh pvo \"ops\" is mandatory\n");
|
|
return 0;
|
|
}
|
|
/*
|
|
* The user code ops api_version has to be current
|
|
*/
|
|
if (vhd->ops->api_version != LWS_SSH_OPS_VERSION) {
|
|
lwsl_err("FATAL ops is api_version v%d but code is v%d\n",
|
|
vhd->ops->api_version, LWS_SSH_OPS_VERSION);
|
|
return 1;
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_ADOPT:
|
|
lwsl_info("LWS_CALLBACK_RAW_ADOPT\n");
|
|
if (!vhd || !pss)
|
|
return -1;
|
|
pss->next = vhd->live_pss_list;
|
|
vhd->live_pss_list = pss;
|
|
pss->parser_state = SSH_INITIALIZE_TRANSIENT;
|
|
pss->wsi = wsi;
|
|
pss->vhd = vhd;
|
|
pss->kex_state = KEX_STATE_EXPECTING_CLIENT_OFFER;
|
|
pss->active_keys_cts.padding_alignment = 8;
|
|
pss->active_keys_stc.padding_alignment = 8;
|
|
if (lws_kex_create(pss))
|
|
return -1;
|
|
write_task(pss, NULL, SSH_WT_VERSION);
|
|
|
|
/* sect 4 RFC4252
|
|
*
|
|
* The server SHOULD have a timeout for authentication and
|
|
* disconnect if the authentication has not been accepted
|
|
* within the timeout period.
|
|
*
|
|
* The RECOMMENDED timeout period is 10 minutes.
|
|
*/
|
|
lws_set_timeout(wsi, (enum pending_timeout)
|
|
SSH_PENDING_TIMEOUT_CONNECT_TO_SUCCESSFUL_AUTH, 10 * 60);
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_CLOSE:
|
|
if (!pss)
|
|
return -1;
|
|
lwsl_info("LWS_CALLBACK_RAW_CLOSE\n");
|
|
lws_kex_destroy(pss);
|
|
lws_ua_destroy(pss);
|
|
|
|
ssh_free_set_NULL(pss->last_alloc);
|
|
|
|
while (pss->ch_list)
|
|
ssh_destroy_channel(pss, pss->ch_list);
|
|
|
|
lws_chacha_destroy(&pss->active_keys_cts);
|
|
lws_chacha_destroy(&pss->active_keys_stc);
|
|
|
|
p = &vhd->live_pss_list;
|
|
|
|
while (*p) {
|
|
if ((*p) == pss) {
|
|
*p = pss->next;
|
|
continue;
|
|
}
|
|
p = &((*p)->next);
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_RX:
|
|
if (!pss)
|
|
return -1;
|
|
if (parse(pss, in, len))
|
|
return -1;
|
|
break;
|
|
|
|
case LWS_CALLBACK_RAW_WRITEABLE:
|
|
if (!pss)
|
|
break;
|
|
n = 0;
|
|
o = pss->write_task[pss->wt_tail];
|
|
ch = pss->write_channel[pss->wt_tail];
|
|
|
|
if (pss->wt_head == pss->wt_tail)
|
|
o = SSH_WT_NONE;
|
|
|
|
switch (o) {
|
|
case SSH_WT_VERSION:
|
|
if (!pss->vhd)
|
|
break;
|
|
n = lws_snprintf((char *)buf + LWS_PRE,
|
|
sizeof(buf) - LWS_PRE - 1, "%s\r\n",
|
|
pss->vhd->ops->server_string);
|
|
write_task(pss, NULL, SSH_WT_OFFER);
|
|
break;
|
|
|
|
case SSH_WT_OFFER:
|
|
if (!pss->vhd)
|
|
break;
|
|
m = 0;
|
|
n = (int)offer(pss, buf + LWS_PRE,
|
|
sizeof(buf) - LWS_PRE, 0, &m);
|
|
if (n == 0) {
|
|
lwsl_notice("Too small\n");
|
|
|
|
return -1;
|
|
}
|
|
|
|
if (!pss->kex) {
|
|
lwsl_notice("%s: SSH_WT_OFFER: pss->kex is NULL\n",
|
|
__func__);
|
|
return -1;
|
|
}
|
|
|
|
/* we need a copy of it to generate the hash later */
|
|
if (pss->kex->I_S)
|
|
free(pss->kex->I_S);
|
|
pss->kex->I_S = sshd_zalloc((unsigned int)m);
|
|
if (!pss->kex->I_S) {
|
|
lwsl_notice("OOM 5: %d\n", m);
|
|
|
|
return -1;
|
|
}
|
|
/* without length + padcount part */
|
|
memcpy(pss->kex->I_S, buf + LWS_PRE + 5, (unsigned int)m);
|
|
pss->kex->I_S_payload_len = (uint32_t)m; /* without padding */
|
|
break;
|
|
|
|
case SSH_WT_OFFER_REPLY:
|
|
memcpy(ps, pss->kex->kex_r, pss->kex->kex_r_len);
|
|
n = (int)pad_and_encrypt(&buf[LWS_PRE], ps,
|
|
ps + pss->kex->kex_r_len, pss, 1);
|
|
pss->kex_state = KEX_STATE_REPLIED_TO_OFFER;
|
|
/* afterwards, must do newkeys */
|
|
write_task(pss, NULL, SSH_WT_SEND_NEWKEYS);
|
|
break;
|
|
|
|
case SSH_WT_SEND_NEWKEYS:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_NEWKEYS;
|
|
goto pac;
|
|
|
|
case SSH_WT_UA_ACCEPT:
|
|
/*
|
|
* If the server supports the service (and permits
|
|
* the client to use it), it MUST respond with the
|
|
* following:
|
|
*
|
|
* byte SSH_MSG_SERVICE_ACCEPT
|
|
* string service name
|
|
*/
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_SERVICE_ACCEPT;
|
|
lws_p32(pp, pss->npos);
|
|
pp += 4;
|
|
strcpy((char *)pp, pss->name);
|
|
pp += pss->npos;
|
|
goto pac;
|
|
|
|
case SSH_WT_UA_FAILURE:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_USERAUTH_FAILURE;
|
|
lws_p32(pp, 9);
|
|
pp += 4;
|
|
strcpy((char *)pp, "publickey");
|
|
pp += 9;
|
|
*pp++ = 0;
|
|
goto pac;
|
|
|
|
case SSH_WT_UA_BANNER:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_USERAUTH_BANNER;
|
|
if (pss->vhd && pss->vhd->ops->banner)
|
|
n = (int)pss->vhd->ops->banner((char *)&buf[750],
|
|
150 - 1,
|
|
lang, (int)sizeof(lang));
|
|
lws_p32(pp, (uint32_t)n);
|
|
pp += 4;
|
|
strcpy((char *)pp, (char *)&buf[750]);
|
|
pp += n;
|
|
if (lws_cstr(&pp, lang, sizeof(lang)))
|
|
goto bail;
|
|
goto pac;
|
|
|
|
case SSH_WT_UA_PK_OK:
|
|
/*
|
|
* The server MUST respond to this message with
|
|
* either SSH_MSG_USERAUTH_FAILURE or with the
|
|
* following:
|
|
*
|
|
* byte SSH_MSG_USERAUTH_PK_OK
|
|
* string public key alg name from the request
|
|
* string public key blob from the request
|
|
*/
|
|
n = 74 + (int)pss->ua->pubkey_len;
|
|
if (n > (int)sizeof(buf) - LWS_PRE) {
|
|
lwsl_notice("pubkey too large\n");
|
|
goto bail;
|
|
}
|
|
ps1 = sshd_zalloc((unsigned int)n);
|
|
if (!ps1)
|
|
goto bail;
|
|
ps = ps1;
|
|
pp = ps1 + 5;
|
|
*pp++ = SSH_MSG_USERAUTH_PK_OK;
|
|
if (lws_cstr(&pp, pss->ua->alg, 64)) {
|
|
free(ps1);
|
|
goto bail;
|
|
}
|
|
lws_p32(pp, pss->ua->pubkey_len);
|
|
pp += 4;
|
|
memcpy(pp, pss->ua->pubkey, pss->ua->pubkey_len);
|
|
pp += pss->ua->pubkey_len;
|
|
|
|
/* we no longer need the UA now we judged it */
|
|
lws_ua_destroy(pss);
|
|
|
|
goto pac;
|
|
|
|
case SSH_WT_UA_SUCCESS:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_USERAUTH_SUCCESS;
|
|
/* end SSH_PENDING_TIMEOUT_CONNECT_TO_SUCCESSFUL_AUTH */
|
|
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
|
|
goto pac;
|
|
|
|
case SSH_WT_CH_OPEN_CONF:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_OPEN_CONFIRMATION;
|
|
lws_p32(pp, pss->ch_temp->sender_ch);
|
|
pp += 4;
|
|
lws_p32(pp, pss->ch_temp->server_ch);
|
|
pp += 4;
|
|
/* tx initial window size towards us */
|
|
lws_p32(pp, LWS_SSH_INITIAL_WINDOW);
|
|
pp += 4;
|
|
/* maximum packet size towards us */
|
|
lws_p32(pp, 800);
|
|
pp += 4;
|
|
lwsl_info("SSH_WT_CH_OPEN_CONF\n");
|
|
/* it's on the linked-list */
|
|
pss->ch_temp = NULL;
|
|
goto pac;
|
|
|
|
case SSH_WT_CH_FAILURE:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_OPEN_FAILURE;
|
|
lws_p32(pp, ch->sender_ch);
|
|
pp += 4;
|
|
lws_p32(pp, ch->server_ch);
|
|
pp += 4;
|
|
lws_cstr(&pp, "reason", 64);
|
|
lws_cstr(&pp, "en/US", 64);
|
|
lwsl_info("SSH_WT_CH_FAILURE\n");
|
|
goto pac;
|
|
|
|
case SSH_WT_CHRQ_SUCC:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_SUCCESS;
|
|
lws_p32(pp, ch->sender_ch);
|
|
lwsl_info("SSH_WT_CHRQ_SUCC\n");
|
|
pp += 4;
|
|
goto pac;
|
|
|
|
case SSH_WT_CHRQ_FAILURE:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_FAILURE;
|
|
lws_p32(pp, ch->sender_ch);
|
|
pp += 4;
|
|
lwsl_info("SSH_WT_CHRQ_FAILURE\n");
|
|
goto pac;
|
|
|
|
case SSH_WT_CH_CLOSE:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_CLOSE;
|
|
lws_p32(pp, ch->sender_ch);
|
|
lwsl_info("SSH_WT_CH_CLOSE\n");
|
|
pp += 4;
|
|
goto pac;
|
|
|
|
case SSH_WT_CH_EOF:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_EOF;
|
|
lws_p32(pp, ch->sender_ch);
|
|
lwsl_info("SSH_WT_CH_EOF\n");
|
|
pp += 4;
|
|
goto pac;
|
|
|
|
case SSH_WT_SCP_ACK_ERROR:
|
|
case SSH_WT_SCP_ACK_OKAY:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_DATA;
|
|
/* ps + 6 */
|
|
lws_p32(pp, ch->sender_ch);
|
|
pp += 4;
|
|
lws_p32(pp, 1);
|
|
pp += 4;
|
|
if (o == SSH_WT_SCP_ACK_ERROR)
|
|
*pp++ = 2;
|
|
else
|
|
*pp++ = 0;
|
|
lwsl_info("SSH_WT_SCP_ACK_OKAY\n");
|
|
goto pac;
|
|
|
|
case SSH_WT_WINDOW_ADJUST:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_WINDOW_ADJUST;
|
|
/* ps + 6 */
|
|
lws_p32(pp, ch->sender_ch);
|
|
pp += 4;
|
|
lws_p32(pp, 32768);
|
|
pp += 4;
|
|
lwsl_info("send SSH_MSG_CHANNEL_WINDOW_ADJUST\n");
|
|
goto pac;
|
|
|
|
case SSH_WT_EXIT_STATUS:
|
|
pp = ps + 5;
|
|
*pp++ = SSH_MSG_CHANNEL_REQUEST;
|
|
lws_p32(pp, ch->sender_ch);
|
|
pp += 4;
|
|
lws_p32(pp, 11);
|
|
pp += 4;
|
|
strcpy((char *)pp, "exit-status");
|
|
pp += 11;
|
|
*pp++ = 0;
|
|
lws_p32(pp, (uint32_t)ch->retcode);
|
|
pp += 4;
|
|
lwsl_info("send SSH_MSG_CHANNEL_EXIT_STATUS\n");
|
|
goto pac;
|
|
|
|
case SSH_WT_NONE:
|
|
default:
|
|
/* sending payload */
|
|
|
|
ch = ssh_get_server_ch(pss, 0);
|
|
/* have a channel up to send on? */
|
|
if (!ch)
|
|
break;
|
|
|
|
if (!pss->vhd || !pss->vhd->ops)
|
|
break;
|
|
n = pss->vhd->ops->tx_waiting(ch->priv);
|
|
if (n < 0)
|
|
return -1;
|
|
if (!n)
|
|
/* nothing to send */
|
|
break;
|
|
|
|
if (n == (LWS_STDOUT | LWS_STDERR)) {
|
|
/* pick one using round-robin */
|
|
if (pss->serviced_stderr_last)
|
|
n = LWS_STDOUT;
|
|
else
|
|
n = LWS_STDERR;
|
|
}
|
|
|
|
pss->serviced_stderr_last = !!(n & LWS_STDERR);
|
|
|
|
/* stdout or stderr */
|
|
pp = ps + 5;
|
|
if (n == LWS_STDOUT)
|
|
*pp++ = SSH_MSG_CHANNEL_DATA;
|
|
else
|
|
*pp++ = SSH_MSG_CHANNEL_EXTENDED_DATA;
|
|
/* ps + 6 */
|
|
lws_p32(pp, pss->ch_list->sender_ch);
|
|
m = 14;
|
|
if (n == LWS_STDERR) {
|
|
pp += 4;
|
|
/* data type code... 1 for stderr payload */
|
|
lws_p32(pp, SSH_EXTENDED_DATA_STDERR);
|
|
m = 18;
|
|
}
|
|
/* also skip another strlen u32 at + 10 / +14 */
|
|
pp += 8;
|
|
/* ps + 14 / + 18 */
|
|
|
|
pp += pss->vhd->ops->tx(ch->priv, n, pp,
|
|
lws_ptr_diff_size_t(
|
|
&buf[sizeof(buf) - 1], pp));
|
|
|
|
lws_p32(ps + m - 4, (uint32_t)lws_ptr_diff(pp, (ps + m)));
|
|
|
|
if (pss->vhd->ops->tx_waiting(ch->priv) > 0)
|
|
lws_callback_on_writable(wsi);
|
|
|
|
ch->window -= lws_ptr_diff(pp, ps) - m;
|
|
//lwsl_debug("our send window: %d\n", ch->window);
|
|
|
|
/* fallthru */
|
|
pac:
|
|
if (!pss->vhd)
|
|
break;
|
|
n = (int)pad_and_encrypt(&buf[LWS_PRE], ps, pp, pss, 0);
|
|
break;
|
|
|
|
bail:
|
|
lws_ua_destroy(pss);
|
|
lws_kex_destroy(pss);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
if (n > 0) {
|
|
m = lws_write(wsi, (unsigned char *)buf + LWS_PRE, (unsigned int)n,
|
|
LWS_WRITE_HTTP);
|
|
|
|
switch(o) {
|
|
case SSH_WT_SEND_NEWKEYS:
|
|
lwsl_info("Activating STC keys\n");
|
|
pss->active_keys_stc = pss->kex->keys_next_stc;
|
|
lws_chacha_activate(&pss->active_keys_stc);
|
|
pss->kex_state = KEX_STATE_CRYPTO_INITIALIZED;
|
|
pss->kex->newkeys |= 1;
|
|
if (pss->kex->newkeys == 3)
|
|
lws_kex_destroy(pss);
|
|
break;
|
|
case SSH_WT_UA_PK_OK:
|
|
free(ps1);
|
|
break;
|
|
case SSH_WT_CH_CLOSE:
|
|
if (ch->received_close) {
|
|
/*
|
|
* We are sending this at the behest of
|
|
* the remote peer...
|
|
* we can destroy the channel with no
|
|
* further communication.
|
|
*/
|
|
ssh_destroy_channel(pss, ch);
|
|
break;
|
|
}
|
|
ch->sent_close = 1;
|
|
break;
|
|
}
|
|
if (m < 0) {
|
|
lwsl_err("ERR %d from write\n", m);
|
|
goto bail;
|
|
}
|
|
|
|
if (o != SSH_WT_VERSION)
|
|
pss->ssh_sequence_ctr_stc++;
|
|
|
|
if (o != SSH_WT_NONE)
|
|
pss->wt_tail =
|
|
(pss->wt_tail + 1) & 7;
|
|
} else
|
|
if (o == SSH_WT_UA_PK_OK) /* free it either way */
|
|
free(ps1);
|
|
|
|
ch = ssh_get_server_ch(pss, 0);
|
|
|
|
if (pss->wt_head != pss->wt_tail ||
|
|
(ch && ch->priv && pss->vhd &&
|
|
pss->vhd->ops->tx_waiting(ch->priv)))
|
|
lws_callback_on_writable(wsi);
|
|
|
|
break;
|
|
|
|
case LWS_CALLBACK_SSH_UART_SET_RXFLOW:
|
|
/*
|
|
* this is sent to set rxflow state on any connections that
|
|
* sink on a particular sink. The sink index affected is in len
|
|
*
|
|
* More than one protocol may sink to the same uart, and the
|
|
* protocol may select the sink itself, eg, in the URL used
|
|
* to set up the connection.
|
|
*/
|
|
lwsl_notice("sshd LWS_CALLBACK_SSH_UART_SET_RXFLOW: wsi %p, %d\n",
|
|
wsi, (int)len & 1);
|
|
lws_rx_flow_control(wsi, len & 1);
|
|
break;
|
|
|
|
case LWS_CALLBACK_CGI:
|
|
if (!pss)
|
|
break;
|
|
if (pss->vhd && pss->vhd->ops &&
|
|
pss->vhd->ops->child_process_io &&
|
|
pss->vhd->ops->child_process_io(pss->ch_temp->priv,
|
|
pss->wsi, (struct lws_cgi_args *)in))
|
|
return -1;
|
|
break;
|
|
|
|
case LWS_CALLBACK_CGI_PROCESS_ATTACH:
|
|
if (!pss)
|
|
break;
|
|
ch = ssh_get_server_ch(pss, pss->channel_doing_spawn);
|
|
if (ch) {
|
|
ch->spawn_pid = (uint32_t)len; /* child process PID */
|
|
lwsl_notice("associated PID %d to ch %d\n", (int)len,
|
|
pss->channel_doing_spawn);
|
|
}
|
|
break;
|
|
|
|
case LWS_CALLBACK_CGI_TERMINATED:
|
|
if (!pss)
|
|
break;
|
|
if (pss->vhd && pss->vhd->ops &&
|
|
pss->vhd->ops->child_process_terminated)
|
|
pss->vhd->ops->child_process_terminated(pss->ch_temp->priv,
|
|
pss->wsi);
|
|
/*
|
|
* we have the child PID in len... we need to match it to a
|
|
* channel that is on the wsi
|
|
*/
|
|
ch = pss->ch_list;
|
|
|
|
while (ch) {
|
|
if (ch->spawn_pid == len) {
|
|
lwsl_notice("starting close of ch with PID %d\n",
|
|
(int)len);
|
|
ch->scheduled_close = 1;
|
|
write_task(pss, ch, SSH_WT_CH_CLOSE);
|
|
break;
|
|
}
|
|
ch = ch->next;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#define LWS_PLUGIN_PROTOCOL_LWS_RAW_SSHD { \
|
|
"lws-ssh-base", \
|
|
lws_callback_raw_sshd, \
|
|
sizeof(struct per_session_data__sshd), \
|
|
1024, 0, NULL, 900 \
|
|
}
|
|
|
|
LWS_VISIBLE const struct lws_protocols lws_ssh_base_protocols[] = {
|
|
LWS_PLUGIN_PROTOCOL_LWS_RAW_SSHD,
|
|
{ NULL, NULL, 0, 0, 0, NULL, 0 } /* terminator */
|
|
};
|
|
|
|
#if !defined (LWS_PLUGIN_STATIC)
|
|
|
|
LWS_VISIBLE const lws_plugin_protocol_t lws_ssh_base = {
|
|
.hdr = {
|
|
"ssh base",
|
|
"lws_protocol_plugin",
|
|
LWS_BUILD_HASH,
|
|
LWS_PLUGIN_API_MAGIC
|
|
},
|
|
|
|
.protocols = lws_ssh_base_protocols,
|
|
.count_protocols = LWS_ARRAY_SIZE(lws_ssh_base_protocols) - 1,
|
|
.extensions = NULL,
|
|
.count_extensions = 0,
|
|
};
|
|
|
|
#endif
|