1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-16 00:00:07 +01:00
libwebsockets/plugins/ssh-base/sshd.c
Andy Green d98101d1e3 plugins: generalize and provide public api
Move the common plugin scanning dir stuff to be based on lws_dir, which
already builds for windows.  Previously this was done via dirent for unix
and libuv for windows.

Reduce the dl plat stuff to just wrap instantiation and destruction of
dynlibs, establish common code in lib/misc/dir.c for plugin scanning
itself.

Migrate the libuv windows dl stuff to windows-plugins.c, so that he's
available even if later libuv loop support becomes and event lib plugin.

Remove the existing api exports scheme for plugins, just export a const struct
now which has a fixed header type but then whatever you want afterwards depending
on the class / purpose of the plugin.  Place a "class" string in the header so
there can be different kinds of plugins implying different types exported.

Make the plugin apis public and add support for filter by class string, and
per instantation / destruction callbacks so the subclassed header type can
do its thing for the plugin class.  The user provides a linked-list base
for his class of plugins, so he can manage them completely separately and
in user code / user export types.

Rip out some last hangers-on from generic sessions / tables.

This is all aimed at making the plugins support general enough so it can
provide event lib plugins later.
2020-08-31 16:51:37 +01:00

2581 lines
61 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++ = v >> 24;
*p++ = v >> 16;
*p++ = v >> 8;
*p++ = 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] = 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] = 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 = lws_ptr_diff(*p, start);
uint8_t padc = 4, *bs = start;
if (keys->full_length)
len -= 4;
if ((len + padc) & (keys->padding_alignment - 1))
padc += keys->padding_alignment -
((len + padc) & (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, 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, end - p, "curve25519-sha256@libssh.org");
p += lws_p32(lp, n);
/* Server Host Key Algorithms */
lp = p;
p += 4;
n = lws_snprintf((char *)p, end - p, "%s", keyt);
p += lws_p32(lp, 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, end - p, "chacha20-poly1305@openssh.com");
p += lws_p32(lp, 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, end - p, "chacha20-poly1305@openssh.com");
p += lws_p32(lp, 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, end - p, "hmac-sha2-256");
p += lws_p32(lp, 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, end - p, "hmac-sha2-256");
p += lws_p32(lp, n);
/* Compression Algorithms: C -> S */
lp = p;
p += 4;
n = lws_snprintf((char *)p, end - p, "none");
p += lws_p32(lp, n);
/* Compression Algorithms: S -> C */
lp = p;
p += 4;
n = lws_snprintf((char *)p, end - p, "none");
p += lws_p32(lp, 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 = lws_ptr_diff(p, op);
if (payload_len)
/* starts at buf + 5 and excludes padding */
*payload_len = len - 5;
/* we must give at least 4 bytes of 00 padding */
if ((len + padc) & 7)
padc += 8 - ((len + padc) & 7);
op[4] = padc;
len += 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, 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 = 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 = next;
}
static void
state_get_u32(struct per_session_data__sshd *pss, int next)
{
pss->parser_state = SSHS_GET_U32;
pss->state_after_string = 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], *otmp;
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++] = *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, 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++] = *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++] = *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 = *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, 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(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,
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, 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);
/*
* 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 = memcmp(&otmp[m + 2], hash,
lws_genhash_size(pss->ua->hash_ctx.type));
break;
}
/* go into these */
if (otmp[m] == 0x30) {
m += 2;
continue;
}
/* otherwise skip payloads */
m += otmp[m + 1] + 2;
}
}
free(otmp);
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;
pss->last_alloc = NULL; /* it was adopted */
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 = 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 = 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 (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
/* 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 -= 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 = 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 += 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;
ua_fail1:
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 = 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 = 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));
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_err("ssh pvo \"ops\" is mandatory\n");
return 1;
}
/*
* 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)
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,
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 = 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(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, m);
pss->kex->I_S_payload_len = m; /* without padding */
break;
case SSH_WT_OFFER_REPLY:
memcpy(ps, pss->kex->kex_r, pss->kex->kex_r_len);
n = 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[650],
150 - 1,
lang, (int)sizeof(lang));
lws_p32(pp, n);
pp += 4;
strcpy((char *)pp, (char *)&buf[650]);
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 + pss->ua->pubkey_len;
if (n > (int)sizeof(buf) - LWS_PRE) {
lwsl_notice("pubkey too large\n");
goto bail;
}
ps1 = sshd_zalloc(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->server_ch);
pp += 4;
lws_p32(pp, pss->ch_temp->sender_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->server_ch);
pp += 4;
lws_p32(pp, ch->sender_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->server_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->server_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->server_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->server_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, 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->server_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,
&buf[sizeof(buf) - 1] - pp);
lws_p32(ps + m - 4, 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 = 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, 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 = (int)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 \
}
const struct lws_protocols protocols_sshd[] = {
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 protocol_lws_ssh_base = {
.hdr = {
"ssh base",
"lws_protocol_plugin",
LWS_PLUGIN_API_MAGIC
},
.protocols = protocols_sshd,
.count_protocols = LWS_ARRAY_SIZE(protocols_sshd),
.extensions = NULL,
.count_extensions = 0,
};
#endif