/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2026 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>

#define LWS_SSH_MAX_PACKET_SIZE  (256 * 1024)

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;
	uint8_t hash[32];
	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 > LWS_SSH_MAX_PACKET_SIZE) {
				lwsl_notice("msg size %u exceeds maximum\n", pss->msg_len);
				goto bail;
			}
			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:
				if (pss->ssh_auth_state != SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS) goto bail;
				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
				 */
				if (pss->ssh_auth_state != SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS) goto bail;
				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
				 */
				if (pss->ssh_auth_state != SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS) goto bail;
				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.
				 */
				if (pss->ssh_auth_state != SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS) goto bail;
				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
				 */
				if (pss->ssh_auth_state != SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS) goto bail;
				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;

				if (pss->ssh_auth_state != SSH_AUTH_STATE_GAVE_AUTH_IGNORE_REQS) 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;
			if (pss->len == 0xffffffff || pss->len > 16 * 1024 * 1024 || pss->msg_len - pss->pos < pss->len)
				goto bail;
			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);

			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;

			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;
			}
			{
				int count = 0;
				struct lws_ssh_channel *c = pss->ch_list;
				while (c) {
					count++;
					c = c->next;
				}
				if (count >= 8) {
					lwsl_notice("Too many channels\n");
					pss->reason = 4;
					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);
			if (pss->len > 64 * 1024 * 1024)
				pss->len = 64 * 1024 * 1024;
			pss->ch_temp->window = (int32_t)pss->len;
			state_get_u32(pss, SSHS_NVC_CHOPEN_PKTSIZE);
			break;
		case SSHS_NVC_CHOPEN_PKTSIZE:
			if (pss->len > 16 * 1024 * 1024)
				pss->len = 16 * 1024 * 1024;
			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);
			if (!pss->ch_temp)
				goto bail;

			/* 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);
			if (!ch)
				goto bail;
			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);
			if (!ch)
				goto bail;
			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') {
						char *fname;
						uint64_t max_body;
						
						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);
						max_body = 100 * 1024 * 1024; /* hard limit for SCP in plugin */
						
						if (scp->len > max_body) {
							lwsl_notice("scp payload %llu exceeds max %llu\n",
								    (unsigned long long)scp->len, (unsigned long long)max_body);
							write_task(pss, ch, SSH_WT_SCP_ACK_ERROR);
							pss->parser_state = SSHS_MSG_EAT_PADDING;
							break;
						}
						
						/* find filename and purify it */
						fname = (char *)pp;
						while (*fname != ' ' && *fname != '\x0a')
							fname++;
						if (*fname == ' ') {
							char *end;
							fname++;
							end = fname;
							while (*end != '\x0a' && *end != '\0')
								end++;
							*end = '\0';
							lws_filename_purify_inplace(fname);
							*end = '\x0a';
						}

						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) {
				if ((uint64_t)ch->window + pss->len > 64 * 1024 * 1024) {
					lwsl_notice("window too large\n");
					goto bail;
				}
				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;

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:
		if (!in)
			return 0;

		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_vhost_err(vhd->vhost, "%s: can't find protocol %s",
						    __func__, pvo->value);
			}

			pvo = pvo->next;
		}

		if (!vhd->ops) {
			lwsl_vhost_warn(vhd->vhost, "ssh pvo \"ops\" is mandatory");
			return 0;
		}
		/*
		 * The user code ops api_version has to be current
		 */
		if (vhd->ops->api_version != LWS_SSH_OPS_VERSION) {
			lwsl_vhost_err(vhd->vhost, "FATAL ops is api_version v%d but code is v%d",
				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:
			if (!ch) goto bail;
			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:
			if (!ch) goto bail;
			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:
			if (!ch) goto bail;
			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:
			if (!ch) goto bail;
			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:
			if (!ch) goto bail;
			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:
			if (!ch) goto bail;
			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:
			if (!ch) goto bail;
			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	\
	}

#if !defined (LWS_PLUGIN_STATIC)

LWS_VISIBLE const struct lws_protocols lws_ssh_base_protocols[] = {
	LWS_PLUGIN_PROTOCOL_LWS_RAW_SSHD,
	{ NULL, NULL, 0, 0, 0, NULL, 0 } /* terminator */
};

/*
 * The exported lws_plugin_protocol_t struct MUST be named EXACTLY the same as
 * your plugin's shared object suffix (after removing 'libprotocol_').
 * lwsws uses this exact string directly in its dlsym() lookup on startup.
 */
LWS_VISIBLE const lws_plugin_protocol_t lws_ssh_base = {
	.hdr = {
		.name = "ssh base",
		._class = "lws_protocol_plugin",
		.lws_build_hash = LWS_BUILD_HASH,
		.api_magic = 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
