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

From:
Stefan Sperling <stsp@stsp.name>
Subject:
add gotd.conf connection options
To:
gameoftrees@openbsd.org
Date:
Mon, 2 Jan 2023 16:15:44 +0100

Download raw body.

Thread
Introduce connection options to gotd.conf.

Allow administrators to tweak the default authentication and request
timeouts if needed, and to tweak the limit of concurrent connections
for specific user accounts.

ok?
 
diff 3bf00f2542ea6e7825f52c155e5f3f5fecb136e3 d9bba21cabe166c8334dd273a5071634524b7bcd
commit - 3bf00f2542ea6e7825f52c155e5f3f5fecb136e3
commit + d9bba21cabe166c8334dd273a5071634524b7bcd
blob - fd7f8c11aa319b18aa9f13289797a9809af40003
blob + 01a705f61574e2653156dbe6ccb60035616515ac
--- gotd/auth.c
+++ gotd/auth.c
@@ -72,8 +72,8 @@ static int
 	}
 }
 
-static int
-parseuid(const char *s, uid_t *uid)
+int
+gotd_auth_parseuid(const char *s, uid_t *uid)
 {
 	struct passwd *pw;
 	const char *errstr;
@@ -95,7 +95,7 @@ uidcheck(const char *s, uid_t desired)
 {
 	uid_t uid;
 
-	if (parseuid(s, &uid) != 0)
+	if (gotd_auth_parseuid(s, &uid) != 0)
 		return -1;
 	if (uid != desired)
 		return -1;
blob - fe78e2c9301e0c052e89de790159fb9afaeb5094
blob + 87cd12560d438c7e4b48d2f7a762ff678b51d92e
--- gotd/auth.h
+++ gotd/auth.h
@@ -14,6 +14,6 @@ void
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-void
-auth_main(const char *title, struct gotd_repolist *repos,
+int gotd_auth_parseuid(const char *, uid_t *);
+void auth_main(const char *title, struct gotd_repolist *repos,
     const char *repo_path);
blob - 0f2528c97db20b779a500b205d84cfcdb238b7ad
blob + 59de8b44a2f5a60c66d8b93a79242dd674add08d
--- gotd/gotd.c
+++ gotd/gotd.c
@@ -93,8 +93,6 @@ static struct timeval timeout = { 3600, 0 };
 static struct gotd_clients gotd_clients[GOTD_CLIENT_TABLE_SIZE];
 static SIPHASH_KEY clients_hash_key;
 volatile int client_cnt;
-static struct timeval timeout = { 3600, 0 };
-static struct timeval auth_timeout = { 5, 0 };
 static struct gotd gotd;
 
 void gotd_sighdlr(int sig, short event, void *arg);
@@ -1202,9 +1200,9 @@ gotd_request(int fd, short events, void *arg)
 	} else {
 		gotd_imsg_event_add(&client->iev);
 		if (client->state == GOTD_STATE_EXPECT_LIST_REFS)
-			evtimer_add(&client->tmo, &auth_timeout);
+			evtimer_add(&client->tmo, &gotd.auth_timeout);
 		else
-			evtimer_add(&client->tmo, &timeout);
+			evtimer_add(&client->tmo, &gotd.request_timeout);
 	}
 }
 
@@ -2586,7 +2584,8 @@ main(int argc, char **argv)
 		if (pledge("stdio sendfd unix", NULL) == -1)
 			err(1, "pledge");
 #endif
-		listen_main(title, fd);
+		listen_main(title, fd, gotd.connection_limits,
+		    gotd.nconnection_limits);
 		/* NOTREACHED */
 		break;
 	case PROC_AUTH:
blob - 612eac16fcff173392e5868ae2bfa04f9cee4b0f
blob + 6aae892e95b0d5fb567d7a86854a30b77413a444
--- gotd/gotd.conf.5
+++ gotd/gotd.conf.5
@@ -31,6 +31,56 @@ The available global configuration directives are as f
 .Sh GLOBAL CONFIGURATION
 The available global configuration directives are as follows:
 .Bl -tag -width Ds
+.It Ic connection Ar option
+Set the specified options and limits for connections to the
+.Xr gotd 8
+unix socket.
+.Pp
+The
+.Ic connection
+directive may be specified multiple times, and multiple
+.Ar option
+arguments may be specified within curly braces:
+.Pp
+.Ic connection Brq Ar option1 Ar option2 ...
+.Pp
+Each option should only be specified once.
+If a given option is listed multiple times, the last line which sets this
+option wins.
+.Pp
+Valid connection options are:
+.Bl -tag -width Ds
+.It Ic authentication timeout Ar seconds
+Specify the timeout for client authentication.
+If this timeout is exceeded due to an unexpected failure in the authentication
+process, such as a crash, the connection will be terminated.
+.Pp
+The default timeout is 5 seconds.
+This should only be changed if legitimate connections are exceeding the
+default timeout for some reason.
+.It Ic request timeout Ar seconds
+Specify the inactivity timeout for operations between client and server.
+If this timeout is exceeded while a Git protocol request is being processed,
+the request will be aborted and the connection will be terminated.
+.Pp
+The default timeout is 3600 seconds (1 hour).
+This should only be changed if legitimate requests are exceeding the default
+timeout for some reason, such as the server spending an extraordinary
+amount of time generating a pack file.
+.It Ic user Ar identity Ic max Ar number
+Limit the maximum amount of concurrent connections by the user with
+the username
+.Ar identity
+to
+.Ar number .
+Numeric user IDs are also accepted.
+.Pp
+The default per-user limit is 4.
+This should only be changed if concurrent connections from a given user are
+expected to exceed the default limit, for example if an anonymous user
+is granted read access and many concurrent connections will share this
+anonymous user identity.
+.El
 .It Ic unix_socket Ar path
 Set the path to the unix socket which
 .Xr gotd 8
@@ -161,6 +211,15 @@ repository "openbsd/ports" {
 	permit ro anonymous
 	deny flan_hacker
 }
+
+# Use a larger request timeout value:
+connection request timeout 7200  # 2 hours
+
+# Some users are granted a higher concurrent connection limit:
+connection {
+	user flan_hacker max 16
+	user anonymous max 32
+}
 .Ed
 .Sh SEE ALSO
 .Xr got 1 ,
blob - d17cc8a6c43063e47af1fb6b1ce517a12eb909c5
blob + 5a527cb9b1db88f3a364c3ff9f274fa12f1d81e7
--- gotd/gotd.h
+++ gotd/gotd.h
@@ -28,6 +28,9 @@
 #define GOTD_FD_NEEDED		6
 #define GOTD_FILENO_MSG_PIPE	3
 
+#define GOTD_DEFAULT_REQUEST_TIMEOUT	3600
+#define GOTD_DEFAULT_AUTH_TIMEOUT	5
+
 /* Client hash tables need some extra room. */
 #define GOTD_CLIENT_TABLE_SIZE (GOTD_MAXCLIENTS * 4)
 
@@ -109,6 +112,11 @@ struct gotd {
 	size_t				 nids;
 };
 
+struct gotd_uid_connection_limit {
+	uid_t uid;
+	int max_connections;
+};
+
 struct gotd {
 	pid_t pid;
 	char unix_socket_path[PATH_MAX];
@@ -117,6 +125,10 @@ struct gotd {
 	struct gotd_repolist repos;
 	int nrepos;
 	struct gotd_child_proc listen_proc;
+	struct timeval request_timeout;
+	struct timeval auth_timeout;
+	struct gotd_uid_connection_limit *connection_limits;
+	size_t nconnection_limits;
 
 	char *argv0;
 	const char *confpath;
blob - bcc891f386e858648339da2b211be2df91530d4b
blob + 165791e08de682f3d9eba287935ba600b83dc29b
--- gotd/listen.c
+++ gotd/listen.c
@@ -70,6 +70,8 @@ static struct {
 	int fd;
 	struct gotd_imsgev iev;
 	struct gotd_imsgev pause;
+	struct gotd_uid_connection_limit *connection_limits;
+	size_t nconnection_limits;
 } gotd_listen;
 
 static int inflight;
@@ -178,6 +180,26 @@ static const struct got_error *
 	return NULL;
 }
 
+struct gotd_uid_connection_limit *
+gotd_find_uid_connection_limit(struct gotd_uid_connection_limit *limits,
+    size_t nlimits, uid_t uid)
+{
+	/* This array is always sorted to allow for binary search. */
+	int i, left = 0, right = nlimits - 1;
+
+	while (left <= right) {
+		i = ((left + right) / 2);
+		if (limits[i].uid == uid)
+			return &limits[i];
+		if (limits[i].uid > uid)
+			left = i + 1;
+		else
+			right = i - 1;
+	}
+
+	return NULL;
+}
+
 static const struct got_error *
 disconnect(struct gotd_listen_client *client)
 {
@@ -303,7 +325,16 @@ gotd_accept(int fd, short event, void *arg)
 		counter->nconnections = 1;
 		add_uid_connection_counter(counter);
 	} else {
-		if (counter->nconnections >= GOTD_MAX_CONN_PER_UID) {
+		int max_connections = GOTD_MAX_CONN_PER_UID;
+		struct gotd_uid_connection_limit *limit;
+
+		limit = gotd_find_uid_connection_limit(
+		    gotd_listen.connection_limits,
+		    gotd_listen.nconnection_limits, euid);
+		if (limit)
+			max_connections = limit->max_connections;
+
+		if (counter->nconnections >= max_connections) {
 			log_warnx("maximum connections exceeded for uid %d",
 			    euid);
 			goto err;
@@ -426,7 +457,9 @@ listen_main(const char *title, int gotd_socket)
 }
 
 void
-listen_main(const char *title, int gotd_socket)
+listen_main(const char *title, int gotd_socket,
+    struct gotd_uid_connection_limit *connection_limits,
+    size_t nconnection_limits)
 {
 	struct gotd_imsgev iev;
 	struct event evsigint, evsigterm, evsighup, evsigusr1;
@@ -473,6 +506,7 @@ listen_shutdown(void)
 {
 	log_debug("%s: shutting down", gotd_listen.title);
 
+	free(gotd_listen.connection_limits);
 	if (gotd_listen.fd != -1)
 		close(gotd_listen.fd);
 
blob - 5ecdb0084e77470530899068dc16cb0f553ead7b
blob + e7a67532164502eb5ea8893b35007e6edf63ee36
--- gotd/listen.h
+++ gotd/listen.h
@@ -14,4 +14,9 @@ void listen_main(const char *title, int gotd_socket);
  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  */
 
-void listen_main(const char *title, int gotd_socket);
+struct gotd_uid_connection_limit *gotd_find_uid_connection_limit(
+    struct gotd_uid_connection_limit *limits, size_t nlimits, uid_t uid);
+
+void listen_main(const char *title, int gotd_socket,
+    struct gotd_uid_connection_limit *connection_limits,
+    size_t nconnection_limits);
blob - eb5822c199bae7d3a0ab0b75eefe7854b6409e3c
blob + 9acd293aaafa5ef3d24f7c8bf9a7caf2a5ec0cc5
--- gotd/parse.y
+++ gotd/parse.y
@@ -46,6 +46,8 @@
 
 #include "log.h"
 #include "gotd.h"
+#include "auth.h"
+#include "listen.h"
 
 TAILQ_HEAD(files, file)		 files = TAILQ_HEAD_INITIALIZER(files);
 static struct file {
@@ -94,6 +96,7 @@ typedef struct {
 	union {
 		long long	 number;
 		char		*string;
+		struct timeval	 tv;
 	} v;
 	int lineno;
 } YYSTYPE;
@@ -101,11 +104,12 @@ typedef struct {
 %}
 
 %token	PATH ERROR ON UNIX_SOCKET UNIX_GROUP USER REPOSITORY PERMIT DENY
-%token	RO RW
+%token	RO RW CONNECTION MAXIMUM AUTHENTICATION REQUEST TIMEOUT
 
 %token	<v.string>	STRING
 %token	<v.number>	NUMBER
 %type	<v.number>	boolean
+%type	<v.tv>		timeout
 
 %%
 
@@ -135,6 +139,17 @@ main		: UNIX_SOCKET STRING {
 		| NUMBER { $$ = $1; }
 		;
 
+timeout		: NUMBER
+		{
+			if ($1 < 0) {
+				yyerror("invalid timeout: %lld", $1);
+				YYERROR;
+			}
+			$$.tv_sec = $1;
+			$$.tv_usec = 0;
+		}
+		;
+
 main		: UNIX_SOCKET STRING {
 			if (gotd_proc_id == PROC_LISTEN) {
 				if (strlcpy(gotd->unix_socket_path, $2,
@@ -169,8 +184,34 @@ main		: UNIX_SOCKET STRING {
 			}
 			free($2);
 		}
+		| connection
 		;
 
+connection	: CONNECTION '{' optnl conflags_l '}'
+		| CONNECTION conflags
+
+conflags_l	: conflags optnl conflags_l
+		| conflags optnl
+		;
+
+conflags	: REQUEST TIMEOUT timeout		{
+			memcpy(&gotd->request_timeout, &$3,
+			    sizeof(struct timeval));
+		}
+		| AUTHENTICATION TIMEOUT timeout	{
+			memcpy(&gotd->auth_timeout, &$3,
+			    sizeof(struct timeval));
+		}
+		| USER STRING MAXIMUM NUMBER	{
+			if (gotd_proc_id == PROC_LISTEN &&
+			    conf_new_user_max_connections($2, $4) == -1) {
+				free($2);
+				YYERROR;
+			}
+			free($2);
+		}
+		;
+
 repository	: REPOSITORY STRING {
 			struct gotd_repo *repo;
 
@@ -276,13 +317,18 @@ lookup(char *s)
 {
 	/* This has to be sorted always. */
 	static const struct keywords keywords[] = {
+		{ "authentication",		AUTHENTICATION },
+		{ "connection",			CONNECTION },
 		{ "deny",			DENY },
+		{ "max",			MAXIMUM },
 		{ "on",				ON },
 		{ "path",			PATH },
 		{ "permit",			PERMIT },
 		{ "repository",			REPOSITORY },
+		{ "request",			REQUEST },
 		{ "ro",				RO },
 		{ "rw",				RW },
+		{ "timeout",			TIMEOUT },
 		{ "unix_group",			UNIX_GROUP },
 		{ "unix_socket",		UNIX_SOCKET },
 		{ "user",			USER },
@@ -637,6 +683,11 @@ parse_config(const char *filename, enum gotd_procid pr
 		return -1;
 	}
 
+	gotd->request_timeout.tv_sec = GOTD_DEFAULT_REQUEST_TIMEOUT;
+	gotd->request_timeout.tv_usec = 0;
+	gotd->auth_timeout.tv_sec = GOTD_DEFAULT_AUTH_TIMEOUT;
+	gotd->auth_timeout.tv_usec = 0;
+
 	file = newfile(filename, 0);
 	if (file == NULL) {
 		/* just return, as we don't require a conf file */
@@ -674,6 +725,67 @@ static struct gotd_repo *
 	return (0);
 }
 
+static int
+uid_connection_limit_cmp(const void *pa, const void *pb)
+{
+	const struct gotd_uid_connection_limit *a, *b;
+
+	a = (const struct gotd_uid_connection_limit *)pa;
+	b = (const struct gotd_uid_connection_limit *)pb;
+
+	if (a->uid < b->uid)
+		return -1;
+	else if (a->uid > b->uid);
+		return 1;
+
+	return 0;
+}
+
+static int
+conf_new_user_max_connections(const char *user, int maximum)
+{
+	uid_t uid;
+	struct gotd_uid_connection_limit *limit;
+	size_t nlimits;
+
+	if (maximum < 1) {
+		yyerror("max connections cannot be smaller 1");
+		return -1;
+	}
+	if (maximum > GOTD_MAXCLIENTS) {
+		yyerror("max connections must be <= %d", GOTD_MAXCLIENTS);
+		return -1;
+	}
+
+	if (gotd_auth_parseuid(user, &uid) == -1) {
+		yyerror("%s: no such user", user);
+		return -1;
+	}
+
+	limit = gotd_find_uid_connection_limit(gotd->connection_limits,
+	    gotd->nconnection_limits, uid);
+	if (limit) {
+		limit->max_connections = maximum;
+		return 0;
+	}
+
+	limit = gotd->connection_limits;
+	nlimits = gotd->nconnection_limits + 1;
+	limit = reallocarray(limit, nlimits, sizeof(*limit));
+	if (limit == NULL)
+		fatal("reallocarray");
+
+	limit[nlimits - 1].uid = uid;
+	limit[nlimits - 1].max_connections = maximum;
+
+	gotd->connection_limits = limit;
+	gotd->nconnection_limits = nlimits;
+	qsort(gotd->connection_limits, gotd->nconnection_limits,
+	    sizeof(gotd->connection_limits[0]), uid_connection_limit_cmp);
+
+	return 0;
+}
+
 static struct gotd_repo *
 conf_new_repo(const char *name)
 {