From 3f49a27f8a19269297606d8d2027246c747b07d1 Mon Sep 17 00:00:00 2001
From: =?utf8?q?Am=C3=A9lia=20Coutard-Sander?= <git@ameliathe1st.gay>
Date: Thu, 5 Dec 2024 16:53:15 +0100
Subject: [PATCH] Extensible protocol. Counts as a v1.0.0 methinks !

---
 README.txt        |  3 ++-
 git-remote-gnunet | 59 ++++++++++++++++++++++++-----------------------
 handle-request.sh | 46 +++++++++++++++---------------------
 3 files changed, 51 insertions(+), 57 deletions(-)

diff --git a/README.txt b/README.txt
index 9ca31aa..b967639 100644
--- a/README.txt
+++ b/README.txt
@@ -32,7 +32,7 @@ Features:
 	- Authenticated push to repo !
 
 TODO:
-	- Pulling from anyone using a commit hash ?
+	- Pulling from anyone, using a commit hash ?
 		- DHT to figure out who has what commits ?
 		- Question of trust for pulling from peers ?
 
@@ -54,3 +54,4 @@ Authentication (also done when pulling):
 	- The client decyphers and hashes the passphrase, and sends the hash back.
 		- This avoids the request by a malicious server of decyphering of arbitrary strings.
 	- The server checks the correctness of the hash.
+	PS: The authentication protocol should be extensible in a backwards-compatible way, should there be a need.
diff --git a/git-remote-gnunet b/git-remote-gnunet
index b01e65a..521a95e 100755
--- a/git-remote-gnunet
+++ b/git-remote-gnunet
@@ -10,24 +10,16 @@ fi | awk '
 match($0, /(([^@]*)@)?([^/]*)(\/.*)?/, a) { printf "%s\n%s\n%s\n", a[2], a[3], a[4] }
 ' | { read -r username && read -r gnsname && read -r path && ! grep '.'; }
 if test "$?" != "0"; then
-	echo >&2 "Invalid address: \`$2'."
-	echo >&2 "URI format: \`[gnunet://][user@]gns.name[/path/to/repo]'."
+	echo >&2 "Invalid URI: \`$2'."
+	echo >&2 "Format: \`[gnunet://][user@]gns.name[/path/to/repo]'."
 	exit 1
 fi
-pkey="$(gnunet-identity -qde "$username")"
 
 while read -r cmd arg; do
 	case "$cmd" in
-	capabilities)
-		echo 'connect'
-		echo ''
-		;;
-	connect)
-		break
-		;;
-	*)
-		echo >&2 "Error !"
-		exit 1
+	capabilities) printf '%s\n' 'connect' '' ;;
+	connect) break ;;
+	*) echo >&2 "Error: Git remote helper protocol."; exit 1 ;;
 	esac
 done
 
@@ -43,19 +35,28 @@ echo >&2 "Trying service \`$service' on peer \`$peer'."
 ip="$(gnunet-vpn -s "$service" -p "$peer" -t)"
 echo >&2 "Using tunnel ip \`$ip'."
 
-case "$username" in
-"")
-	{ printf '%s\n' "$path" "$arg" "$pkey"; cat; } | socat stdio tcp4:"$ip":9418
-	;;
-*)
-	fifo="$(mktemp -u)"
-	mkfifo "$fifo" || { echo 2>&1 "Failed to create the fifo."; exit 1; }
-	{ printf '%s\n' "$path" "$arg" "$pkey"; head -n1 "$fifo"; cat; } \
-		| socat stdio tcp4:"$ip":9418 \
-		| { head -n 1 \
-			| { xargs gnunet-identity -e "$username" -R || { echo >&2 'Error !'; exit 1; }; } \
-			| sha256sum > "$fifo";
-			cat; }
-	rm "$fifo"
-	;;
-esac
+fifo="$(mktemp -u "git-over-gnunet.XXXXXXXXXX" --tmpdir)"
+mkfifo "$fifo" || { echo 2>&1 "Failed to create the fifo."; exit 1; }
+{
+	test "" = "$username" || pkey="$(gnunet-identity -qde "$username")" || { echo 2>&1 "Failed to get pkey for username \`$username'."; rm "$fifo"; exit 1; }
+	printf '%s\n' "Path:$path" "Command:$arg" "Key:$pkey";
+	echo;
+	cat "$fifo" -;
+} | socat stdio tcp4:"$ip":9418 | {
+	{ if test "" != "$username"; then
+		read -r cyphertext
+		case "$cyphertext" in
+		"Error: "*)
+			echo >&2 "$cyphertext"
+			rm "$fifo"
+			exit 1
+			;;
+		"Key:"*)
+			plaintext="$(gnunet-identity -e "$username" -R "${cyphertext:4}")" || { rm "$fifo"; exit 1; }
+			printf '%s\n' "$plaintext" | sha256sum
+			;;
+		esac;
+	fi; } > "$fifo";
+	cat;
+}
+rm "$fifo"
diff --git a/handle-request.sh b/handle-request.sh
index e47975b..83fdcbc 100755
--- a/handle-request.sh
+++ b/handle-request.sh
@@ -7,53 +7,45 @@ fi
 base_path="$1"
 pkeys="$2"
 
-read -r path || { echo "No path specified." | tee /dev/stderr; exit 1; }
-read -r comm || { echo "No command specified." | tee /dev/stderr; exit 1; }
-read -r pkey || { echo "No pkey specified." | tee /dev/stderr; exit 1; }
+while read -r line; do
+	case "$line" in
+	"Path:"*) path="${line:5}" ;;
+	"Command:"*) comm="${line:8}" ;;
+	"Key:"*) pkey="${line:4}" ;;
+	"") break ;;
+	*) echo "Error: Unknown information key: \`$line'."; exit ;;
+	esac
+done
 
 full_path="$(realpath -sm "$base_path$path")"
 case "$full_path" in
 "$base_path") ;;
 "$base_path/"*) ;;
-*)	echo "Invalid path: \`$full_path'." | tee /dev/stderr
-	exit 1
+*)	echo "Error: Invalid path: \`$full_path'."
+	exit
 	;;
 esac
 
 if test "" != "$pkey"; then
-	pkey="$(grep -xm1 -- "$pkey" < "$pkeys")" || { echo "Unknown public key." | tee /dev/stderr; exit 1; }
+	grep -qxF -- "$pkey" "$pkeys" || { echo "Error: Unknown public key."; exit; }
 	keyphrase="$(</dev/random base64 2> /dev/null | head -c 64)"
 	hashed="$(printf '%s\n' "$keyphrase" | sha256sum)"
 	encrypted="$(gnunet-identity -k "$pkey" -W "$keyphrase")"
-	printf '%s\n' "$encrypted"
+	printf 'Key:%s\n' "$encrypted"
 	read -r returned_hash
 	case "$returned_hash" in
-	"$hashed")
-		;;
-	*)
-		echo "Incorrect passphrase decryption." | tee /dev/stderr
-		exit 1
-		;;
+	"$hashed") ;;
+	*) echo "Error: Incorrect passphrase decryption."; exit ;;
 	esac
 fi
 
 case "$comm" in
-git-upload-pack)
-	# Unidentified pull is allowed.
-	;;
-git-receive-pack)
-	if test "" = "$pkey"; then
-		echo "Unidentified push is not allowed." | tee /dev/stderr
-		exit 1
-	fi
-	;;
-*)
-	echo "Unknown command: \`$comm'." | tee /dev/stderr
-	exit 1
-	;;
+git-upload-pack) ;; # Unidentified pull is allowed.
+git-receive-pack) test "" = "$pkey" && { echo "Error: Unidentified push is not allowed."; exit; } ;;
+*) echo "Error: Unknown command: \`$comm'."; exit ;;
 esac
 
-git_dir="$(git -C "$full_path" rev-parse --path-format=absolute --git-dir)" || { echo "Not a git repository: \`$full_path'." | tee /dev/stderr; exit 1; }
+git_dir="$(git -C "$full_path" rev-parse --path-format=absolute --git-dir)" || { echo "Error: Not a git repository: \`$full_path'."; exit; }
 
 echo ''
 exec "$comm" "$git_dir"
-- 
2.46.0