commit - 3bf00f2542ea6e7825f52c155e5f3f5fecb136e3
commit + 40b85cca5d86ebef3a353efd464af989c3ebf18b
blob - fd7f8c11aa319b18aa9f13289797a9809af40003
blob + 01a705f61574e2653156dbe6ccb60035616515ac
--- gotd/auth.c
+++ gotd/auth.c
}
}
-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;
{
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
* 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 + 68f77c0970813d13f62fbcfba19833ca3a1efca1
--- gotd/gotd.c
+++ gotd/gotd.c
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;
if (client->state == GOTD_STATE_EXPECT_LIST_REFS)
evtimer_add(&client->tmo, &auth_timeout);
else
- evtimer_add(&client->tmo, &timeout);
+ evtimer_add(&client->tmo, &gotd.request_timeout);
}
}
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 + ba00f127e7a704c7c95a42f2210fd109af6de5a3
--- gotd/gotd.conf.5
+++ gotd/gotd.conf.5
.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 ...
+.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 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 limit Ic user Ar identity 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
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 {
+ limit user flan_hacker 16
+ limit user anonymous 32
+}
.Ed
.Sh SEE ALSO
.Xr got 1 ,
blob - d17cc8a6c43063e47af1fb6b1ce517a12eb909c5
blob + 82485863fa9ea61c5334cb5ecd4b70eac7128cb4
--- gotd/gotd.h
+++ gotd/gotd.h
#define GOTD_FD_NEEDED 6
#define GOTD_FILENO_MSG_PIPE 3
+#define GOTD_DEFAULT_REQUEST_TIMEOUT 3600
+
/* Client hash tables need some extra room. */
#define GOTD_CLIENT_TABLE_SIZE (GOTD_MAXCLIENTS * 4)
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];
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
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;
STAILQ_FOREACH(c, &gotd_client_uids[slot], entry) {
if (c->euid == euid)
return c;
+ }
+
+ 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;
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;
}
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;
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);
{
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
* 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 + 580f43381ccf163eac2c738800e7efd25c7562e4
--- gotd/parse.y
+++ gotd/parse.y
#include "log.h"
#include "gotd.h"
+#include "auth.h"
+#include "listen.h"
TAILQ_HEAD(files, file) files = TAILQ_HEAD_INITIALIZER(files);
static struct file {
union {
long long number;
char *string;
+ struct timeval tv;
} v;
int lineno;
} YYSTYPE;
%}
%token PATH ERROR ON UNIX_SOCKET UNIX_GROUP USER REPOSITORY PERMIT DENY
-%token RO RW
+%token RO RW CONNECTION LIMIT REQUEST TIMEOUT
%token <v.string> STRING
%token <v.number> NUMBER
%type <v.number> boolean
+%type <v.tv> timeout
%%
| 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,
}
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(gotd->request_timeout));
+ }
+ | LIMIT USER STRING NUMBER {
+ if (gotd_proc_id == PROC_LISTEN &&
+ conf_limit_user_connections($3, $4) == -1) {
+ free($3);
+ YYERROR;
+ }
+ free($3);
+ }
+ ;
+
repository : REPOSITORY STRING {
struct gotd_repo *repo;
{
/* This has to be sorted always. */
static const struct keywords keywords[] = {
+ { "connection", CONNECTION },
{ "deny", DENY },
+ { "limit", LIMIT },
{ "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 },
fprintf(stderr, "%s: user name too long", __func__);
return -1;
}
+
+ gotd->request_timeout.tv_sec = GOTD_DEFAULT_REQUEST_TIMEOUT;
+ gotd->request_timeout.tv_usec = 0;
file = newfile(filename, 0);
if (file == NULL) {
return (0);
}
+static int
+uid_connection_limit_cmp(const void *pa, const void *pb)
+{
+ const struct gotd_uid_connection_limit *a = pa, *b = pb;
+
+ if (a->uid < b->uid)
+ return -1;
+ else if (a->uid > b->uid);
+ return 1;
+
+ return 0;
+}
+
+static int
+conf_limit_user_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)
{