From 907db1f5b9e73a1f088ee4553b729caa2782f36e Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Deuc=D0=B5?= <shurd@sasktel.net>
Date: Wed, 27 Dec 2023 12:39:20 -0500
Subject: [PATCH] More refinement and bug fixes...

We can now get the path to the users home directory on the remote
system.

This introduces the use of error fields in the sftpc_state struct,
which should allow result-or-error style stuff pretty easily.
---
 src/sftp/sftp.h        |  17 ++--
 src/sftp/sftp_client.c | 183 ++++++++++++++++++++++++++++++++++++-----
 src/sftp/sftp_pkt.c    |  12 ++-
 src/sftp/sftp_str.c    |   2 +-
 src/syncterm/ssh.c     |  12 ++-
 5 files changed, 193 insertions(+), 33 deletions(-)

diff --git a/src/sftp/sftp.h b/src/sftp/sftp.h
index 58a70932b7..5d3e81cd5b 100644
--- a/src/sftp/sftp.h
+++ b/src/sftp/sftp.h
@@ -5,10 +5,10 @@
 extern "C" {
 #endif
 
-#include <stdbool.h>
+#include <eventwrap.h>
 #include <inttypes.h>
-
-#include "eventwrap.h"
+#include <stdbool.h>
+#include <threadwrap.h>
 
 // draft-ietf-secsh-filexfer-02
 
@@ -96,8 +96,14 @@ typedef struct sftp_client_state {
 	xpevent_t recv_event;
 	sftp_rx_pkt_t rxp;
 	sftp_tx_pkt_t txp;
-	pthread_t thread;
+	sftp_str_t home;
 	void *cb_data;
+	sftp_str_t err_msg;
+	sftp_str_t err_lang;
+	pthread_t thread;
+	uint32_t id;
+	uint32_t err_id;
+	uint32_t err_code;
 } *sftpc_state_t;
 
 /* sftp_pkt.c */
@@ -110,7 +116,7 @@ bool sftp_have_full_pkt(sftp_rx_pkt_t pkt);
 void sftp_remove_packet(sftp_rx_pkt_t pkt);
 uint32_t sftp_get32(sftp_rx_pkt_t pkt);
 uint32_t sftp_get64(sftp_rx_pkt_t pkt);
-sftp_str_t sftp_getstring(sftp_rx_pkt_t pkt, uint8_t **str);
+sftp_str_t sftp_getstring(sftp_rx_pkt_t pkt);
 bool sftp_rx_pkt_append(sftp_rx_pkt_t *pkt, uint8_t *inbuf, uint32_t len);
 bool sftp_tx_pkt_reset(sftp_tx_pkt_t *pktp);
 bool sftp_appendbyte(sftp_tx_pkt_t *pktp, uint8_t u8);
@@ -132,5 +138,6 @@ void sftpc_finish(sftpc_state_t state);
 sftpc_state_t sftpc_begin(bool (*send_cb)(uint8_t *buf, size_t len, void *cb_data), void *cb_data);
 bool sftpc_init(sftpc_state_t state);
 bool sftpc_recv(sftpc_state_t state, uint8_t *buf, uint32_t sz);
+bool sftpc_realpath(sftpc_state_t state, char *path, sftp_str_t *ret);
 
 #endif
diff --git a/src/sftp/sftp_client.c b/src/sftp/sftp_client.c
index e2ce1c253f..965954778e 100644
--- a/src/sftp/sftp_client.c
+++ b/src/sftp/sftp_client.c
@@ -5,6 +5,51 @@
 
 #include "sftp.h"
 
+static uint32_t
+get32(sftpc_state_t state)
+{
+	return sftp_get32(state->rxp);
+}
+
+static uint64_t
+get64(sftpc_state_t state)
+{
+	return sftp_get64(state->rxp);
+}
+
+static sftp_str_t
+getstring(sftpc_state_t state)
+{
+	return sftp_getstring(state->rxp);
+}
+
+static bool
+appendbyte(sftpc_state_t state, uint8_t u)
+{
+	return sftp_appendbyte(&state->txp, u);
+}
+
+static bool
+append32(sftpc_state_t state, uint32_t u)
+{
+	return sftp_append32(&state->txp, u);
+}
+
+static bool
+append64(sftpc_state_t state, uint64_t u)
+{
+	return sftp_append64(&state->txp, u);
+}
+
+static bool
+appendstring(sftpc_state_t state, sftp_str_t *s)
+{
+	bool ret = sftp_appendstring(&state->txp, *s);
+	free_sftp_str(*s);
+	*s = NULL;
+	return ret;
+}
+
 void
 sftpc_finish(sftpc_state_t state)
 {
@@ -41,42 +86,110 @@ sftpc_begin(bool (*send_cb)(uint8_t *buf, size_t len, void *cb_data), void *cb_d
 	ret->send_cb = send_cb;
 	ret->cb_data = cb_data;
 	ret->thread = pthread_self();
+	ret->id = 0;
+	ret->err_lang = NULL;
+	ret->err_msg = NULL;
 	return ret;
 }
 
-bool
-sftpc_init(sftpc_state_t state)
+static void
+response_handled(sftpc_state_t state)
+{
+	sftp_remove_packet(state->rxp);
+	if (!sftp_have_full_pkt(state->rxp))
+		ResetEvent(state->recv_event);
+}
+
+static bool
+check_state(sftpc_state_t state)
 {
 	assert(state);
 	if (!state)
-		goto fail;
+		return false;
 	assert(state->thread == pthread_self());
 	if (state->thread != pthread_self())
-		goto fail;
-	if (!sftp_appendbyte(&state->txp, SSH_FXP_INIT))
-		goto fail;
-	if (!sftp_append32(&state->txp, SFTP_VERSION))
-		goto fail;
+		return false;
+	return true;
+}
+
+static bool
+appendheader(sftpc_state_t state, uint8_t type)
+{
+	if (!check_state(state))
+		return false;
+	state->err_code = 0;
+	state->err_id = 0;
+	free_sftp_str(state->err_lang);
+	state->err_lang = NULL;
+	free_sftp_str(state->err_msg);
+	state->err_msg = NULL;
+	if (!sftp_tx_pkt_reset(&state->txp))
+		return false;
+	if (!sftp_appendbyte(&state->txp, type))
+		return false;
+	if (type != SSH_FXP_INIT) {
+		if (!sftp_append32(&state->txp, ++state->id))
+			return false;
+	}
+	return true;
+}
+
+static bool
+get_result(sftpc_state_t state)
+{
 	uint8_t *txbuf;
 	size_t txsz;
+
 	if (!sftp_prep_tx_packet(state->txp, &txbuf, &txsz))
-		goto fail;
+		return false;
 	if (!state->send_cb(txbuf, txsz, state->cb_data))
-		goto fail;
-	sftp_tx_pkt_reset(&state->txp);
+		return false;
 	if (WaitForEvent(state->recv_event, INFINITE) != WAIT_OBJECT_0)
-		goto fail;
+		return false;
+	if (state->rxp->type != SSH_FXP_VERSION) {
+		uint32_t id = sftp_get32(state->rxp);
+		if (id != state->id) {
+			response_handled(state);
+			return false;
+		}
+	}
+	return true;
+}
+
+static void
+handle_error(sftpc_state_t state)
+{
+	if (state->rxp->type == SSH_FXP_STATUS) {
+		state->err_id = get32(state);
+		state->err_code = get32(state);
+		if (state->err_msg != NULL)
+			free_sftp_str(state->err_msg);
+		state->err_msg = getstring(state);
+		if (state->err_lang != NULL)
+			free_sftp_str(state->err_lang);
+		state->err_lang = getstring(state);
+	}
+	response_handled(state);
+}
+
+bool
+sftpc_init(sftpc_state_t state)
+{
+	if (!appendheader(state, SSH_FXP_INIT))
+		return false;
+	if (!append32(state, SFTP_VERSION))
+		return false;
+	if (!get_result(state))
+		return false;
 	if (state->rxp->type != SSH_FXP_VERSION)
-		goto fail;
-	if (sftp_get32(state->rxp) != SFTP_VERSION)
-		goto fail;
-	sftp_remove_packet(state->rxp);
-	if (!sftp_have_full_pkt(state->rxp))
-		ResetEvent(state->recv_event);
+		return false;
+	if (get32(state) != SFTP_VERSION) {
+		response_handled(state);
+		return false;
+	}
+	response_handled(state);
+	state->id = 0;
 	return true;
-fail:
-	sftp_tx_pkt_reset(&state->txp);
-	return false;
 }
 
 bool
@@ -88,3 +201,31 @@ sftpc_recv(sftpc_state_t state, uint8_t *buf, uint32_t sz)
 		SetEvent(state->recv_event);
 	return true;
 }
+
+bool
+sftpc_realpath(sftpc_state_t state, char *path, sftp_str_t *ret)
+{
+	assert(ret);
+	if (ret == NULL)
+		return false;
+	if (*ret != NULL)
+		return false;
+	if (!appendheader(state, SSH_FXP_REALPATH))
+		return false;
+	sftp_str_t pstr = sftp_strdup(path);
+	if (!appendstring(state, &pstr))
+		return false;
+	if (!get_result(state))
+		return false;
+	if (state->rxp->type == SSH_FXP_NAME) {
+		if (get32(state) != 1) {
+			response_handled(state);
+			return false;
+		}
+		*ret = getstring(state);
+		response_handled(state);
+		return true;
+	}
+	handle_error(state);
+	return false;
+}
diff --git a/src/sftp/sftp_pkt.c b/src/sftp/sftp_pkt.c
index 064ab91a0f..4c2cc82db7 100644
--- a/src/sftp/sftp_pkt.c
+++ b/src/sftp/sftp_pkt.c
@@ -121,6 +121,7 @@ sftp_remove_packet(sftp_rx_pkt_t pkt)
 	src += sz;
 	memmove(&pkt->len, src, newsz);
 	pkt->used = newsz;
+	pkt->cur = 0;
 	// TODO: realloc() smaller?
 	return;
 }
@@ -161,7 +162,7 @@ sftp_get64(sftp_rx_pkt_t pkt)
  * cursor
  */
 sftp_str_t
-sftp_getstring(sftp_rx_pkt_t pkt, uint8_t **str)
+sftp_getstring(sftp_rx_pkt_t pkt)
 {
 	assert(pkt);
 	uint32_t sz = sftp_get32(pkt);
@@ -187,11 +188,13 @@ sftp_rx_pkt_append(sftp_rx_pkt_t *pktp, uint8_t *inbuf, uint32_t len)
 	size_t old_sz;
 	size_t new_sz;
 	uint32_t old_used;
+	uint32_t old_cur;
 	sftp_rx_pkt_t pkt = *pktp;
 
 	if (pkt == NULL) {
 		old_sz = 0;
 		old_used = 0;
+		old_cur = 0;
 		new_sz = offsetof(struct sftp_rx_pkt, len) + len;
 	}
 	else {
@@ -211,6 +214,7 @@ sftp_rx_pkt_append(sftp_rx_pkt_t *pktp, uint8_t *inbuf, uint32_t len)
 		*pktp = new_buf;
 		pkt = *pktp;
 		pkt->sz = new_sz;
+		pkt->cur = old_cur;
 	}
 	memcpy(&((uint8_t *)&(pkt->len))[old_used], inbuf, len);
 	pkt->used = old_used + len;
@@ -265,6 +269,8 @@ sftp_tx_pkt_reset(sftp_tx_pkt_t *pktp)
 	if (pktp == NULL)
 		return false;
 	sftp_tx_pkt_t pkt = *pktp;
+	if (pkt == NULL)
+		return true;
 	pkt->used = 0;
 	if (pkt->sz == SFTP_MIN_PACKET_ALLOC)
 		return true;
@@ -310,8 +316,10 @@ sftp_appendstring(sftp_tx_pkt_t *pktp, sftp_str_t s)
 {
 	assert(pktp);
 	sftp_append32(pktp, s->len);
+	if (!grow_tx(pktp, s->len))
+		return false;
 	sftp_tx_pkt_t pkt = *pktp;
-	memcpy(&pkt->data[pkt->used], (uint8_t *)s->c_str, s->len);
+	memcpy(&(&pkt->type)[pkt->used], (uint8_t *)s->c_str, s->len);
 	pkt->used += s->len;
 	return true;
 }
diff --git a/src/sftp/sftp_str.c b/src/sftp/sftp_str.c
index 69a641f450..10aa3fa103 100644
--- a/src/sftp/sftp_str.c
+++ b/src/sftp/sftp_str.c
@@ -2,9 +2,9 @@
 #include <stddef.h>
 #include <stdlib.h>
 #include <string.h>
+#include <xpprintf.h>
 
 #include "sftp.h"
-#include "xpprintf.h"
 
 static sftp_str_t
 alloc_str(uint32_t len)
diff --git a/src/syncterm/ssh.c b/src/syncterm/ssh.c
index 3c5df27f57..907bd2020e 100644
--- a/src/syncterm/ssh.c
+++ b/src/syncterm/ssh.c
@@ -513,10 +513,14 @@ ssh_connect(struct bbslist *bbs)
 		}
 		if (cryptStatusOK(status)) {
 			sftp_state = sftpc_begin(sftp_send, NULL);
-			if (sftp_state == NULL)
-				fprintf(stderr, "Failure!\n");
-			else if (sftpc_init(sftp_state))
-				fprintf(stderr, "Success!\n");
+			if (sftp_state != NULL) {
+				if (sftpc_init(sftp_state)) {
+					sftp_str_t ret = NULL;
+					if (sftpc_realpath(sftp_state, ".", &ret)) {
+						fprintf(stderr, "Home dir: %.*s\n", ret->len, ret->c_str);
+					}
+				}
+			}
 		}
 	}
 #endif
-- 
GitLab