From: Stefan Sperling Subject: Re: add gotd.conf connection options To: gameoftrees@openbsd.org Date: Mon, 2 Jan 2023 16:52:15 +0100 On Mon, Jan 02, 2023 at 04:15:44PM +0100, Stefan Sperling wrote: > 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? Ooops, the previous diff was not actually working because I forgot to copy connection_limits to the gotd_listen struct in listen_main(). Fixed version. diff 3bf00f2542ea6e7825f52c155e5f3f5fecb136e3 a9284cf9de090824bf09849e4b94ab5ba3d590de commit - 3bf00f2542ea6e7825f52c155e5f3f5fecb136e3 commit + a9284cf9de090824bf09849e4b94ab5ba3d590de 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 + 0244dadfadb3fbe5d3d608eaee023f5df9744ce8 --- 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; @@ -437,6 +470,8 @@ listen_main(const char *title, int gotd_socket) gotd_listen.title = title; gotd_listen.pid = getpid(); gotd_listen.fd = gotd_socket; + gotd_listen.connection_limits = connection_limits; + gotd_listen.nconnection_limits = nconnection_limits; signal_set(&evsigint, SIGINT, listen_sighdlr, NULL); signal_set(&evsigterm, SIGTERM, listen_sighdlr, NULL); @@ -473,6 +508,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 STRING %token NUMBER %type boolean +%type 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) {