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

From:
Stefan Sperling <stsp@stsp.name>
Subject:
weblogin hint
To:
gameoftrees@openbsd.org
Date:
Fri, 3 Oct 2025 12:43:51 +0200

Download raw body.

Thread
  • Stefan Sperling:

    weblogin hint

It is currently difficult to browse repositories which allow anonymous
access because the login error page just says "login failed". Unless
the visitor already knows how our authentication scheme works they will
have to ask for help, or more likely they simply won't visit the page.

The patch below adds an optional login hint to the error page which
the administrator can enable via gotwebd.conf.

I am running this on git.chirpysoft.be, where it looks like:

  Log in by running: ssh anonymous@git.chirpysoft.be "weblogin git.chirpysoft.be"

Only the username needs to be configured. The hostname can be derived
automatically.

The weblogin command shown in the hint always passes the server name
explicitly to ensure that it works as expected in multi-server setups.
There is no other way gotsh could tell which hostname ssh is connecting
to, and the hostname becomes part of the signed authentication cookie.

The only downside I see is that this might help abusive bots figure out
how to browse our repositories again. But we cannot really avoid this.

Better ideas?

ok?

allow gotwebd to optionally display a login hint when authentication fails

M  gotwebd/auth.c                   |  22+  2-
M  gotwebd/gotwebd.conf.5           |  23+  1-
M  gotwebd/gotwebd.h                |   3+  0-
M  gotwebd/parse.y                  |  37+  1-
M  regress/gotsysd/Makefile         |   1+  0-
M  regress/gotsysd/test_gotwebd.sh  |   4+  4-

6 files changed, 90 insertions(+), 8 deletions(-)

commit - 6de677cdbf776d788edb0fbd4de32dfa11453ae3
commit + 33c08c8275fce80211ebc1116ba0d1f928fafeb9
blob - 0005c80d6278263625bb9658693f41c86259bc4d
blob + 9da9ece32167915acfad39d45ed3fec2efd09cbe
--- gotwebd/auth.c
+++ gotwebd/auth.c
@@ -412,6 +412,26 @@ err:
 	}
 }
 
+static const struct got_error *
+login_error_hint(struct request *c)
+{
+	struct server *srv;
+	char msg[512];
+	int ret;
+
+	srv = gotweb_get_server(c->fcgi_params.server_name);
+	if (srv == NULL || srv->login_hint_user[0] == '\0')
+		return got_error(GOT_ERR_LOGIN_FAILED);
+
+	ret = snprintf(msg, sizeof(msg),
+	    "Log in by running: ssh %s@%s \"weblogin %s\"",
+	    srv->login_hint_user, srv->name, srv->name);
+	if (ret == -1 || (size_t)ret >= sizeof(msg))
+		return got_error(GOT_ERR_LOGIN_FAILED);
+
+	return got_error_msg(GOT_ERR_LOGIN_FAILED, msg);
+}
+
 static void
 process_request(struct request *c)
 {
@@ -452,7 +472,7 @@ process_request(struct request *c)
 	if (login_check_token(&uid, &hostname, c->fcgi_params.auth_cookie,
 	    auth_token_secret, sizeof(auth_token_secret),
 	    "authentication") == -1) {
-		error = got_error(GOT_ERR_LOGIN_FAILED);
+		error = login_error_hint(c);
 		goto done;
 	}
 
@@ -461,7 +481,7 @@ process_request(struct request *c)
 	 * occurred. This user is not allowed in authentication cookies.
 	 */
 	if (uid == env->www_uid) {
-		error = got_error(GOT_ERR_LOGIN_FAILED);
+		error = login_error_hint(c);
 		goto done;
 	}
 
blob - 8ce5a3ead055f07e224ce103c63dba7a3b4c731d
blob + 1e09ac9d29942a6f6ec798c0b590593d6f06c339
--- gotwebd/gotwebd.conf.5
+++ gotwebd/gotwebd.conf.5
@@ -114,6 +114,20 @@ While the specified
 .Ar path
 must be absolute, it should usually point inside the web server's chroot
 directory such that the web server can access the socket.
+.It Ic login hint user Ar name
+Sets the user name displayed in login hints which are shown on the error
+page if authentication has failed.
+This can be used to advertise the name of an anonymous user account which
+has been given read access to one or more repositories via the
+.Ic permit
+directive.
+.Pp
+If not set then no login hint will be displayed and users will somehow
+need to learn about using the
+.Xr gotsh 1
+weblogin command via other means.
+.Pp
+This setting can also be configured on a per-server basis.
 .It Ic login socket Ar path
 Set the
 .Ar path
@@ -239,6 +253,10 @@ parameter is set to
 .Ar off .
 Repositories will be hidden regardless of whether authentication is
 enabled and has failed or succeeded.
+.It Ic login hint user Ar name
+Sets the user name to use in login hints displayed when authentication fails.
+If not set then the login hint setting in the global configuration context
+will be used.
 .It Ic logo_url Ar url
 Set a hyperlink for the logo.
 Defaults to
@@ -466,12 +484,16 @@ server "secure.example.com" {
 
 	repos_path		"/var/git"
 
+	# Tell vistors who see a "login failed" error page that
+	# they can log in as the "anonymous" user via ssh.
+	login hint user "anonymous"
+
 	repository "got" {  # /var/git/got and /var/git/got.git
 		# Grant access to users who have authenticated as
 		# the anonymous user to gotsh(1), which anyone with
 		# an SSH client sbould be able to do.
 		# Dumb web crawlers will remain locked out.
-		permit anonymous
+		permit "anonymous"
 	}
 
 	repository "public" {
blob - a0e78241ebfc49bb61c5cdd66999b139d270245f
blob + 83049de9457302be8e3129df6e8b4c56577a5dcc
--- gotwebd/gotwebd.h
+++ gotwebd/gotwebd.h
@@ -402,6 +402,7 @@ struct server {
 	char		 logo[GOTWEBD_MAXTEXT];
 	char		 logo_url[GOTWEBD_MAXTEXT];
 	char		 custom_css[PATH_MAX];
+	char		 login_hint_user[MAX_IDENTIFIER_SIZE];
 
 	size_t		 max_repos_display;
 	size_t		 max_commits_display;
@@ -489,6 +490,8 @@ struct gotwebd {
 
 	char		 httpd_chroot[PATH_MAX];
 	uid_t		 www_uid;
+
+	char		 login_hint_user[MAX_IDENTIFIER_SIZE];
 };
 
 /*
blob - 36f7355865c10eef10094f25ce0d544c4b6a2a5d
blob + b071571ae67d256bdb2e8aba3e69c4c4693a844c
--- gotwebd/parse.y
+++ gotwebd/parse.y
@@ -124,7 +124,7 @@ typedef struct {
 %token	LOGO_URL SHOW_REPO_OWNER SHOW_REPO_AGE SHOW_REPO_DESCRIPTION
 %token	MAX_REPOS_DISPLAY REPOS_PATH MAX_COMMITS_DISPLAY ON ERROR
 %token	SHOW_SITE_OWNER SHOW_REPO_CLONEURL PORT PREFORK RESPECT_EXPORTOK
-%token	SERVER CHROOT CUSTOM_CSS SOCKET
+%token	SERVER CHROOT CUSTOM_CSS SOCKET HINT
 %token	SUMMARY_COMMITS_DISPLAY SUMMARY_TAGS_DISPLAY USER AUTHENTICATION
 %token	ENABLE DISABLE INSECURE REPOSITORY REPOSITORIES PERMIT DENY HIDE
 
@@ -324,6 +324,18 @@ main		: PREFORK NUMBER {
 			gotwebd->login_sock = sockets_conf_new_socket(-1, h);
 			free($3);
 		}
+		| GOTWEBD_LOGIN HINT USER STRING {
+			n = strlcpy(gotwebd->login_hint_user, $4,
+			    sizeof(gotwebd->login_hint_user));
+			if (n >= sizeof(gotwebd->login_hint_user)) {
+				yyerror("login hint user name too long, "
+				    "exceeds %zd bytes",
+				    sizeof(gotwebd->login_hint_user) - 1);
+				free($4);
+				YYERROR;
+			}
+			free($4);
+		}
 		;
 
 server		: SERVER STRING {
@@ -449,6 +461,18 @@ serveropts1	: REPOS_PATH STRING {
 		| HIDE REPOSITORIES boolean {
 			new_srv->hide_repositories = $3;
 		}
+		| GOTWEBD_LOGIN HINT USER STRING {
+			n = strlcpy(new_srv->login_hint_user, $4,
+			    sizeof(new_srv->login_hint_user));
+			if (n >= sizeof(new_srv->login_hint_user)) {
+				yyerror("login hint user name too long, "
+				    "exceeds %zd bytes",
+				    sizeof(new_srv->login_hint_user) - 1);
+				free($4);
+				YYERROR;
+			}
+			free($4);
+		}
 		| MAX_REPOS_DISPLAY NUMBER {
 			if ($2 < 0) {
 				yyerror("max_repos_display is too small: %lld",
@@ -634,6 +658,7 @@ lookup(char *s)
 		{ "disable",			DISABLE },
 		{ "enable",			ENABLE },
 		{ "hide",			HIDE },
+		{ "hint",			HINT },
 		{ "insecure",			INSECURE },
 		{ "listen",			LISTEN },
 		{ "login",			GOTWEBD_LOGIN },
@@ -1093,6 +1118,17 @@ parse_config(const char *filename, struct gotwebd *env
 			if (repo->hidden == -1)
 				repo->hidden = srv->hide_repositories;
 		}
+
+		if (srv->login_hint_user[0] == '\0') {
+			if (strlcpy(srv->login_hint_user, env->login_hint_user,
+			    sizeof(srv->login_hint_user)) >=
+			    sizeof(srv->login_hint_user)) {
+				yyerror("login hint user name too long, "
+				    "exceeds %zd bytes",
+				    sizeof(srv->login_hint_user) - 1);
+			}
+		}
+	
 	}
 
 	return (0);
blob - f36eb4030168acdde61eda535b99ef8652b9d22f
blob + 70204cccd779939249ca70e2db5c63c629d0676c
--- regress/gotsysd/Makefile
+++ regress/gotsysd/Makefile
@@ -229,6 +229,7 @@ $(GOTWEBD_CONF):
 	@${UNPRIV} 'echo server \"VMIP\" { >> $@'
 	@${UNPRIV} 'echo \ \ repos_path \"/git\" >> $@'
 	@${UNPRIV} 'echo \ \ hide repositories on >> $@'
+	@${UNPRIV} 'echo \ \ login hint user anonymous >> $@'
 	@${UNPRIV} 'echo \ \ show_repo_age off >> $@'
 	@${UNPRIV} 'echo \ \ show_repo_description off >> $@'
 	@${UNPRIV} 'echo \ \ show_repo_owner off >> $@'
blob - 723bad471fbd3b29c0456b8070a6b7ab86aa3fe1
blob + 5447294b3653b175dd79be0ed3e11749102ccfb5
--- regress/gotsysd/test_gotwebd.sh
+++ regress/gotsysd/test_gotwebd.sh
@@ -25,7 +25,7 @@ test_login() {
 	cat > $testroot/stdout.expected <<EOF
 [got]
 Repos
-login failed
+Log in by running: ssh anonymous@${VMIP} "weblogin ${VMIP}"
 
 EOF
 	cmp -s $testroot/stdout.expected $testroot/stdout
@@ -116,7 +116,7 @@ EOF
 	cat > $testroot/stdout.expected <<EOF
 [got]
 Repos / gotsys.git / tree /
-login failed
+Log in by running: ssh anonymous@${VMIP} "weblogin ${VMIP}"
 
 EOF
 	cmp -s $testroot/stdout.expected $testroot/stdout
@@ -140,7 +140,7 @@ EOF
 	cat > $testroot/stdout.expected <<EOF
 [got]
 Repos / nonexistent.git / tree /
-login failed
+Log in by running: ssh anonymous@${VMIP} "weblogin ${VMIP}"
 
 EOF
 	cmp -s $testroot/stdout.expected $testroot/stdout
@@ -337,7 +337,7 @@ EOF
 	cat > $testroot/stdout.expected <<EOF
 [got]
 Repos / gottest.git / tree /
-login failed
+Log in by running: ssh anonymous@${VMIP} "weblogin ${VMIP}"
 
 EOF
 	cmp -s $testroot/stdout.expected $testroot/stdout