"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Omar Polo <op@omarpolo.com>
Subject:
Re: got-notify-http: implement basic auth
To:
Omar Polo <op@omarpolo.com>
Cc:
gameoftrees@openbsd.org
Date:
Tue, 16 Apr 2024 10:11:53 +0200

Download raw body.

Thread
On 2024/04/16 01:25:04 +0200, Omar Polo <op@omarpolo.com> wrote:
> with this the authentication is properly implemented.  I'm unsure how to
> plug it in the regress, so for the moment no tests.

here's an improved diff without extra include and with the regress too.
It makes the log output more verbose as a drawback though:

: gotd: gotd: WARNING: Using basic authentication over plaintext http://
: will leak credentials; https:// is recommended for URL
: 'http://localhost:8000/'

diff /home/op/w/got
commit - c1003102a22a77d068a14f9ffa7877f67c28e95d
path + /home/op/w/got
blob - 9c8a90e392c27e03eb1dd32a88414aa62a355012
file + gotd/libexec/got-notify-http/got-notify-http.c
--- gotd/libexec/got-notify-http/got-notify-http.c
+++ gotd/libexec/got-notify-http/got-notify-http.c
@@ -730,17 +730,70 @@ jsonify(FILE *fp, const char *repo)
 	return 0;
 }
 
+static char
+sixet2ch(int c)
+{
+	c &= 0x3F;
+
+	if (c < 26)
+		return 'A' + c;
+	c -= 26;
+	if (c < 26)
+		return 'a' + c;
+	c -= 26;
+	if (c < 10)
+		return '0' + c;
+	c -= 10;
+	if (c == 0)
+		return '+';
+	if (c == 1)
+		return '/';
+
+	errx(1, "invalid sixet 0x%x", c);
+}
+
 static char *
 basic_auth(const char *username, const char *password)
 {
-	char	*tmp;
-	int	 len;
+	char	*str, *tmp, *end, *s, *p;
+	char	 buf[3];
+	int	 len, i, r;
 
-	len = asprintf(&tmp, "%s:%s", username, password);
-	if (len == -1)
+	r = asprintf(&str, "%s:%s", username, password);
+	if (r == -1)
 		err(1, "asprintf");
 
-	/* XXX base64-ify */
+	/*
+	 * Will need 4 * r/3 bytes to encode the string, plus a
+	 * rounding to the next multiple of 4 for padding, plus NUL.
+	 */
+	len = 4 * r / 3;
+	len = (len + 3) & ~3;
+	len++;
+
+	tmp = calloc(1, len);
+	if (tmp == NULL)
+		err(1, "malloc");
+
+	s = str;
+	p = tmp;
+	while (*s != '\0') {
+		memset(buf, 0, sizeof(buf));
+		for (i = 0; i < 3 && *s != '\0'; ++i, ++s)
+			buf[i] = *s;
+
+		*p++ = sixet2ch(buf[0] >> 2);
+		*p++ = sixet2ch((buf[1] >> 4) | (buf[0] << 4));
+		if (i > 1)
+			*p++ = sixet2ch((buf[1] << 2) | (buf[2] >> 6));
+		if (i > 2)
+			*p++ = sixet2ch(buf[2]);
+	}
+
+	for (end = tmp + len - 1; p < end; ++p)
+		*p = '=';
+
+	free(str);
 	return tmp;
 }
 
blob - 682368c09afd8f26371f3c0eb5b63f700ac39026
file + gotd/notify.c
--- gotd/notify.c
+++ gotd/notify.c
@@ -161,7 +161,8 @@ gotd_notify_sighdlr(int sig, short event, void *arg)
 }
 
 static void
-run_notification_helper(const char *prog, const char **argv, int fd)
+run_notification_helper(const char *prog, const char **argv, int fd,
+    const char *user, const char *pass)
 {
 	const struct got_error *err = NULL;
 	pid_t pid;
@@ -185,6 +186,11 @@ run_notification_helper(const char *prog, const char *
 
 		closefrom(STDERR_FILENO + 1);
 
+		if (user != NULL && pass != NULL) {
+			setenv("GOT_NOTIFY_HTTP_USER", user, 1);
+			setenv("GOT_NOTIFY_HTTP_PASS", pass, 1);
+		}
+
 		if (execv(prog, (char *const *)argv) == -1) {
 			fprintf(stderr, "%s: exec %s: %s\n", getprogname(),
 			    prog, strerror(errno));
@@ -249,7 +255,8 @@ notify_email(struct gotd_notification_target *target, 
 
 	argv[i] = NULL;
 
-	run_notification_helper(GOTD_PATH_PROG_NOTIFY_EMAIL, argv, fd);
+	run_notification_helper(GOTD_PATH_PROG_NOTIFY_EMAIL, argv, fd,
+	    NULL, NULL);
 }
 
 static void
@@ -273,7 +280,8 @@ notify_http(struct gotd_notification_target *target, c
 
 	argv[argc] = NULL;
 
-	run_notification_helper(GOTD_PATH_PROG_NOTIFY_HTTP, argv, fd);
+	run_notification_helper(GOTD_PATH_PROG_NOTIFY_HTTP, argv, fd,
+	    target->conf.http.user, target->conf.http.password);
 }
 
 static const struct got_error *
blob - 0dee7475233829d5d5fc2cc3f028cfc7789ce428
file + regress/gotd/Makefile
--- regress/gotd/Makefile
+++ regress/gotd/Makefile
@@ -188,7 +188,7 @@ start_gotd_http_notification: ensure_root
 	@echo '    path "$(GOTD_TEST_REPO)"' >> $(PWD)/gotd.conf
 	@echo '    permit rw $(GOTD_DEVUSER)' >> $(PWD)/gotd.conf
 	@echo '    notify {' >> $(PWD)/gotd.conf
-	@echo '         url "http://localhost:${GOTD_TEST_HTTP_PORT}/"' >> $(PWD)/gotd.conf
+	@echo '         url "http://localhost:${GOTD_TEST_HTTP_PORT}/" user flan password "password"' >> $(PWD)/gotd.conf
 	@echo "    }" >> $(PWD)/gotd.conf
 	@echo "}" >> $(PWD)/gotd.conf
 	@$(GOTD_TRAP); $(GOTD_START_CMD)
blob - 5cbff3264bfe341856058856515d41c34ed996bf
file + regress/gotd/http-server
--- regress/gotd/http-server
+++ regress/gotd/http-server
@@ -18,10 +18,11 @@ use v5.36;
 use IPC::Open2;
 use Getopt::Long qw(:config bundling);
 
+my $auth;
 my $port = 8000;
 
-GetOptions("p:i" => \$port)
-    or die("usage: $0 [-p port]\n");
+GetOptions("a:s" => \$auth, "p:i" => \$port)
+    or die("usage: $0 [-a auth] [-p port]\n");
 
 my $pid = open2(my $out, my $in, 'nc', '-l', 'localhost', $port);
 
@@ -61,6 +62,15 @@ while (<$out>) {
 		    unless m/Connection: close$/;
 		next;
 	}
+
+	if (m/Authorization/) {
+		die "bad authorization header"
+		    unless m/Authorization: basic (.*)$/;
+		my $t = $1;
+		die "wrong authorization; got $t want $auth"
+		    if not defined($auth) or $auth ne $t;
+		next;
+	}
 }
 
 die "no Content-Length header" unless defined $clen;
blob - 38661743a189d324f50db59d65ebfb278ed84eff
file + regress/gotd/http_notification.sh
--- regress/gotd/http_notification.sh
+++ regress/gotd/http_notification.sh
@@ -17,6 +17,9 @@
 . ../cmdline/common.sh
 . ./common.sh
 
+# flan:password encoded in base64
+AUTH="ZmxhbjpwYXNzd29yZA=="
+
 test_file_changed() {
 	local testroot=`test_init file_changed 1`
 
@@ -41,7 +44,7 @@ test_file_changed() {
 	local commit_id=`git_show_head $testroot/repo-clone`
 	local author_time=`git_show_author_time $testroot/repo-clone`
 
-	timeout 5 ./http-server -p $GOTD_TEST_HTTP_PORT \
+	timeout 5 ./http-server -a $AUTH -p $GOTD_TEST_HTTP_PORT \
 	    > $testroot/stdout &
 
 	got send -b main -q -r $testroot/repo-clone
@@ -134,7 +137,7 @@ test_bad_utf8() {
 	local commit_id=`git_show_head $testroot/repo-clone`
 	local author_time=`git_show_author_time $testroot/repo-clone`
 
-	timeout 5 ./http-server -p $GOTD_TEST_HTTP_PORT \
+	timeout 5 ./http-server -a $AUTH -p $GOTD_TEST_HTTP_PORT \
 	    > $testroot/stdout &
 
 	got send -b main -q -r $testroot/repo-clone
@@ -229,7 +232,7 @@ test_many_commits_not_summarized() {
 		set -- "$@" "$commit_id $d"
 	done
 
-	timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \
+	timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \
 	    > $testroot/stdout &
 
 	got send -b main -q -r $testroot/repo-clone
@@ -334,7 +337,7 @@ test_many_commits_summarized() {
 		set -- "$@" "$short_commit_id $d"
 	done
 
-	timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \
+	timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \
 	    > $testroot/stdout &
 
 	got send -b main -q -r $testroot/repo-clone
@@ -414,7 +417,7 @@ test_branch_created() {
 	local commit_id=`git_show_branch_head $testroot/repo-clone newbranch`
 	local author_time=`git_show_author_time $testroot/repo-clone $commit_id`
 
-	timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \
+	timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \
 	    > $testroot/stdout &
 
 	got send -b newbranch -q -r $testroot/repo-clone
@@ -501,7 +504,7 @@ test_branch_removed() {
 		return 1
 	fi
 
-	timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \
+	timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \
 	    > $testroot/stdout &
 
 	local commit_id=`git_show_branch_head $testroot/repo-clone newbranch`
@@ -556,7 +559,7 @@ test_tag_created() {
 	local commit_id=`git_show_head $testroot/repo-clone`
 	local tagger_time=`git_show_tagger_time $testroot/repo-clone 1.0`
 
-	timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \
+	timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \
 	    >$testroot/stdout &
 
 	got send -t 1.0 -q -r $testroot/repo-clone
@@ -634,7 +637,7 @@ test_tag_changed() {
 	got tag -r $testroot/repo-clone -m "new tag" 1.0 > /dev/null
 	local tagger_time=`git_show_tagger_time $testroot/repo-clone 1.0`
 
-	timeout 5 ./http-server -p "$GOTD_TEST_HTTP_PORT" \
+	timeout 5 ./http-server -a $AUTH -p "$GOTD_TEST_HTTP_PORT" \
 	    > $testroot/stdout &
 
 	got send -f -t 1.0 -q -r $testroot/repo-clone