From: Omar Polo Subject: Re: got-notify-http: implement basic auth To: Omar Polo Cc: gameoftrees@openbsd.org Date: Tue, 16 Apr 2024 10:11:53 +0200 On 2024/04/16 01:25:04 +0200, Omar Polo 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