2 * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
3 * Copyright (c) 2016-2019, 2020-2021 Tracey Emery <tracey@traceyemery.net>
4 * Copyright (c) 2004, 2005 Esben Norby <norby@openbsd.org>
5 * Copyright (c) 2004 Ryan McBride <mcbride@openbsd.org>
6 * Copyright (c) 2002, 2003, 2004 Henning Brauer <henning@openbsd.org>
7 * Copyright (c) 2001 Markus Friedl. All rights reserved.
8 * Copyright (c) 2001 Daniel Hartmeier. All rights reserved.
9 * Copyright (c) 2001 Theo de Raadt. All rights reserved.
11 * Permission to use, copy, modify, and distribute this software for any
12 * purpose with or without fee is hereby granted, provided that the above
13 * copyright notice and this permission notice appear in all copies.
15 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
16 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
17 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
18 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
19 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
20 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
21 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 #include <sys/types.h>
27 #include <sys/queue.h>
46 #include "got_error.h"
48 #include "got_reference.h"
55 TAILQ_HEAD
(files
, file
) files
= TAILQ_HEAD_INITIALIZER
(files
);
57 TAILQ_ENTRY
(file
) entry
;
63 struct file
*newfile
(const char *, int, int);
64 static void closefile
(struct file
*);
65 int check_file_secrecy
(int, const char *);
68 int yyerror(const char *, ...
)
69 __attribute__
((__format__
(printf
, 1, 2)))
70 __attribute__
((__nonnull__
(1)));
71 int kw_cmp
(const void *, const void *);
77 TAILQ_HEAD
(symhead
, sym
) symhead
= TAILQ_HEAD_INITIALIZER
(symhead
);
79 TAILQ_ENTRY
(sym
) entry
;
86 int symset
(const char *, const char *, int);
87 char *symget
(const char *);
91 static struct gotd
*gotd
;
92 static struct gotd_repo
*new_repo
;
93 static int conf_limit_user_connections
(const char *, int);
94 static struct gotd_repo
*conf_new_repo
(const char *);
95 static void conf_new_access_rule
(struct gotd_repo
*,
96 enum gotd_access
, int, char *);
97 static int conf_protect_ref_namespace
(char **,
98 struct got_pathlist_head
*, char *);
99 static int conf_protect_tag_namespace
(struct gotd_repo
*,
101 static int conf_protect_branch_namespace
(
102 struct gotd_repo
*, char *);
103 static int conf_protect_branch
(struct gotd_repo
*,
105 static enum gotd_procid gotd_proc_id
;
118 %token PATH ERROR LISTEN ON USER REPOSITORY PERMIT DENY
119 %token RO RW CONNECTION LIMIT REQUEST TIMEOUT
120 %token PROTECT NAMESPACE BRANCH TAG
122 %token
<v.
string> STRING
123 %token
<v.number
> NUMBER
131 | grammar repository
'\n'
136 yyerror("invalid timeout: %lld", $1);
144 const char *type
= "seconds";
149 yyerror("invalid number of seconds: %s", $1);
155 switch
($1[len
- 1]) {
175 $$.tv_sec
= strtonum
($1, 0, INT_MAX
/ mul
, &errstr
);
177 yyerror("number of %s is %s: %s", type
,
188 main
: LISTEN ON STRING
{
189 if
(!got_path_is_absolute
($3))
190 yyerror("bad unix socket path \"%s\": "
191 "must be an absolute path", $3);
193 if
(gotd_proc_id
== PROC_LISTEN
) {
194 if
(strlcpy
(gotd
->unix_socket_path
, $3,
195 sizeof
(gotd
->unix_socket_path
)) >=
196 sizeof
(gotd
->unix_socket_path
)) {
197 yyerror("%s: unix socket path too long",
206 if
(strlcpy
(gotd
->user_name
, $2,
207 sizeof
(gotd
->user_name
)) >=
208 sizeof
(gotd
->user_name
)) {
209 yyerror("%s: user name too long", __func__
);
218 connection
: CONNECTION
'{' optnl conflags_l
'}'
219 | CONNECTION conflags
221 conflags_l
: conflags optnl conflags_l
225 conflags
: REQUEST TIMEOUT timeout
{
226 if
($3.tv_sec
<= 0) {
227 yyerror("invalid timeout: %lld", $3.tv_sec
);
230 memcpy
(&gotd
->request_timeout
, &$3,
231 sizeof
(gotd
->request_timeout
));
233 | LIMIT USER STRING NUMBER
{
234 if
(gotd_proc_id
== PROC_LISTEN
&&
235 conf_limit_user_connections
($3, $4) == -1) {
243 protect
: PROTECT
'{' optnl protectflags_l
'}'
244 | PROTECT protectflags
246 protectflags_l
: protectflags optnl protectflags_l
250 protectflags
: TAG NAMESPACE STRING
{
251 if
(gotd_proc_id
== PROC_GOTD ||
252 gotd_proc_id
== PROC_REPO_WRITE
) {
253 if
(conf_protect_tag_namespace
(new_repo
, $3)) {
260 | BRANCH NAMESPACE STRING
{
261 if
(gotd_proc_id
== PROC_GOTD ||
262 gotd_proc_id
== PROC_REPO_WRITE
) {
263 if
(conf_protect_branch_namespace
(new_repo
,
272 if
(gotd_proc_id
== PROC_GOTD ||
273 gotd_proc_id
== PROC_REPO_WRITE
) {
274 if
(conf_protect_branch
(new_repo
, $2)) {
283 repository
: REPOSITORY STRING
{
284 struct gotd_repo
*repo
;
286 TAILQ_FOREACH
(repo
, &gotd
->repos
, entry
) {
287 if
(strcmp
(repo
->name
, $2) == 0) {
288 yyerror("duplicate repository '%s'", $2);
294 if
(gotd_proc_id
== PROC_GOTD ||
295 gotd_proc_id
== PROC_AUTH ||
296 gotd_proc_id
== PROC_REPO_WRITE
) {
297 new_repo
= conf_new_repo
($2);
300 } '{' optnl repoopts2
'}' {
304 repoopts1
: PATH STRING
{
305 if
(gotd_proc_id
== PROC_GOTD ||
306 gotd_proc_id
== PROC_AUTH ||
307 gotd_proc_id
== PROC_REPO_WRITE
) {
308 if
(!got_path_is_absolute
($2)) {
309 yyerror("%s: path %s is not absolute",
314 if
(realpath
($2, new_repo
->path
) == NULL
) {
315 yyerror("realpath %s: %s", $2,
318 * Give admin a chance to create
319 * missing repositories at run-time.
321 if
(errno
!= ENOENT
) {
324 } else if
(strlcpy
(new_repo
->path
, $2,
325 sizeof
(new_repo
->path
)) >=
326 sizeof
(new_repo
->path
))
327 yyerror("path too long");
333 if
(gotd_proc_id
== PROC_AUTH
) {
334 conf_new_access_rule
(new_repo
,
335 GOTD_ACCESS_PERMITTED
, GOTD_AUTH_READ
, $3);
340 if
(gotd_proc_id
== PROC_AUTH
) {
341 conf_new_access_rule
(new_repo
,
342 GOTD_ACCESS_PERMITTED
,
343 GOTD_AUTH_READ | GOTD_AUTH_WRITE
, $3);
348 if
(gotd_proc_id
== PROC_AUTH
) {
349 conf_new_access_rule
(new_repo
,
350 GOTD_ACCESS_DENIED
, 0, $2);
357 repoopts2
: repoopts2 repoopts1 nl
364 optnl
: '\n' optnl
/* zero or more newlines */
376 yyerror(const char *fmt
, ...
)
383 if
(vasprintf
(&msg
, fmt
, ap
) == -1)
384 fatalx
("yyerror vasprintf");
386 logit
(LOG_CRIT
, "%s:%d: %s", file
->name
, yylval.lineno
, msg
);
392 kw_cmp
(const void *k
, const void *e
)
394 return
(strcmp
(k
, ((const struct keywords
*)e
)->k_name
));
400 /* This has to be sorted always. */
401 static const struct keywords keywords
[] = {
402 { "branch", BRANCH
},
403 { "connection", CONNECTION
},
406 { "listen", LISTEN
},
407 { "namespace", NAMESPACE
},
410 { "permit", PERMIT
},
411 { "protect", PROTECT
},
412 { "repository", REPOSITORY
},
413 { "request", REQUEST
},
417 { "timeout", TIMEOUT
},
420 const struct keywords
*p
;
422 p
= bsearch
(s
, keywords
, sizeof
(keywords
)/sizeof
(keywords
[0]),
423 sizeof
(keywords
[0]), kw_cmp
);
431 #define MAXPUSHBACK 128
433 unsigned char *parsebuf
;
435 unsigned char pushback_buffer
[MAXPUSHBACK
];
436 int pushback_index
= 0;
444 /* Read character from the parsebuffer instead of input. */
445 if
(parseindex
>= 0) {
446 c
= parsebuf
[parseindex
++];
455 return
(pushback_buffer
[--pushback_index
]);
458 c
= getc
(file
->stream
);
460 yyerror("reached end of file while parsing "
465 c
= getc
(file
->stream
);
467 next
= getc
(file
->stream
);
472 yylval.lineno
= file
->lineno
;
474 c
= getc
(file
->stream
);
490 if
(pushback_index
< MAXPUSHBACK
-1)
491 return
(pushback_buffer
[pushback_index
++] = c
);
503 /* Skip to either EOF or the first real EOL. */
506 c
= pushback_buffer
[--pushback_index
];
522 unsigned char buf
[8096];
523 unsigned char *p
, *val
;
530 while
(c
== ' ' || c
== '\t')
531 c
= lgetc
(0); /* nothing */
533 yylval.lineno
= file
->lineno
;
536 while
(c
!= '\n' && c
!= EOF
)
537 c
= lgetc
(0); /* nothing */
539 if
(c
== '$' && parsebuf
== NULL
) {
545 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
546 yyerror("string too long");
549 if
(isalnum
(c
) || c
== '_') {
559 yyerror("macro '%s' not defined", buf
);
578 } else if
(c
== '\\') {
579 next
= lgetc
(quotec
);
582 if
(next
== quotec || c
== ' ' || c
== '\t')
584 else if
(next
== '\n') {
589 } else if
(c
== quotec
) {
592 } else if
(c
== '\0') {
593 yyerror("syntax error");
596 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
597 yyerror("string too long");
602 yylval.v.
string = strdup
(buf
);
603 if
(yylval.v.
string == NULL
)
604 err
(1, "yylex: strdup");
608 #define allowed_to_end_number(x) \
609 (isspace
(x
) || x
== ')' || x
==',' || x
== '/' || x
== '}' || x
== '=')
611 if
(c
== '-' || isdigit
(c
)) {
614 if
((unsigned)(p
-buf
) >= sizeof
(buf
)) {
615 yyerror("string too long");
619 } while
(c
!= EOF
&& isdigit
(c
));
621 if
(p
== buf
+ 1 && buf
[0] == '-')
623 if
(c
== EOF || allowed_to_end_number
(c
)) {
624 const char *errstr
= NULL
;
627 yylval.v.number
= strtonum
(buf
, LLONG_MIN
,
630 yyerror("\"%s\" invalid number: %s",
645 #define allowed_in_string(x) \
646 (isalnum
(x
) ||
(ispunct
(x
) && x
!= '(' && x
!= ')' && \
647 x
!= '{' && x
!= '}' && \
648 x
!= '!' && x
!= '=' && x
!= '#' && \
651 if
(isalnum
(c
) || c
== ':' || c
== '_') {
654 if
((unsigned)(p
-buf
) >= sizeof
(buf
)) {
655 yyerror("string too long");
659 } while
(c
!= EOF
&& (allowed_in_string
(c
)));
663 if
(token
== STRING
) {
664 yylval.v.
string = strdup
(buf
);
665 if
(yylval.v.
string == NULL
)
666 err
(1, "yylex: strdup");
671 yylval.lineno
= file
->lineno
;
680 check_file_secrecy
(int fd
, const char *fname
)
684 if
(fstat
(fd
, &st
)) {
685 log_warn
("cannot stat %s", fname
);
688 if
(st.st_uid
!= 0 && st.st_uid
!= getuid
()) {
689 log_warnx
("%s: owner not root or current user", fname
);
692 if
(st.st_mode
& (S_IWGRP | S_IXGRP | S_IRWXO
)) {
693 log_warnx
("%s: group writable or world read/writable", fname
);
700 newfile
(const char *name
, int secret
, int required
)
704 nfile
= calloc
(1, sizeof
(struct file
));
709 nfile
->name
= strdup
(name
);
710 if
(nfile
->name
== NULL
) {
715 nfile
->stream
= fopen
(nfile
->name
, "r");
716 if
(nfile
->stream
== NULL
) {
718 log_warn
("open %s", nfile
->name
);
723 check_file_secrecy
(fileno
(nfile
->stream
), nfile
->name
)) {
724 fclose
(nfile
->stream
);
734 closefile
(struct file
*xfile
)
736 fclose
(xfile
->stream
);
742 parse_config
(const char *filename
, enum gotd_procid proc_id
,
743 struct gotd
*env
, int require_config_file
)
745 struct sym
*sym
, *next
;
746 struct gotd_repo
*repo
;
748 memset
(env
, 0, sizeof
(*env
));
751 gotd_proc_id
= proc_id
;
752 TAILQ_INIT
(&gotd
->repos
);
754 /* Apply default values. */
755 if
(strlcpy
(gotd
->unix_socket_path
, GOTD_UNIX_SOCKET
,
756 sizeof
(gotd
->unix_socket_path
)) >= sizeof
(gotd
->unix_socket_path
)) {
757 fprintf
(stderr
, "%s: unix socket path too long", __func__
);
760 if
(strlcpy
(gotd
->user_name
, GOTD_USER
,
761 sizeof
(gotd
->user_name
)) >= sizeof
(gotd
->user_name
)) {
762 fprintf
(stderr
, "%s: user name too long", __func__
);
766 gotd
->request_timeout.tv_sec
= GOTD_DEFAULT_REQUEST_TIMEOUT
;
767 gotd
->request_timeout.tv_usec
= 0;
769 file
= newfile
(filename
, 0, require_config_file
);
771 return require_config_file ?
-1 : 0;
774 errors
= file
->errors
;
777 /* Free macros and check which have not been used. */
778 TAILQ_FOREACH_SAFE
(sym
, &symhead
, entry
, next
) {
779 if
((gotd
->verbosity
> 1) && !sym
->used
)
780 fprintf
(stderr
, "warning: macro '%s' not used\n",
785 TAILQ_REMOVE
(&symhead
, sym
, entry
);
793 TAILQ_FOREACH
(repo
, &gotd
->repos
, entry
) {
794 if
(repo
->path
[0] == '\0') {
795 log_warnx
("repository \"%s\": no path provided in "
796 "configuration file", repo
->name
);
801 if
(proc_id
== PROC_GOTD
&& TAILQ_EMPTY
(&gotd
->repos
)) {
802 log_warnx
("no repository defined in configuration file");
810 uid_connection_limit_cmp
(const void *pa
, const void *pb
)
812 const struct gotd_uid_connection_limit
*a
= pa
, *b
= pb
;
816 else if
(a
->uid
> b
->uid
);
823 conf_limit_user_connections
(const char *user
, int maximum
)
826 struct gotd_uid_connection_limit
*limit
;
830 yyerror("max connections cannot be smaller 1");
833 if
(maximum
> GOTD_MAXCLIENTS
) {
834 yyerror("max connections must be <= %d", GOTD_MAXCLIENTS
);
838 if
(gotd_parseuid
(user
, &uid
) == -1) {
839 yyerror("%s: no such user", user
);
843 limit
= gotd_find_uid_connection_limit
(gotd
->connection_limits
,
844 gotd
->nconnection_limits
, uid
);
846 limit
->max_connections
= maximum
;
850 limit
= gotd
->connection_limits
;
851 nlimits
= gotd
->nconnection_limits
+ 1;
852 limit
= reallocarray
(limit
, nlimits
, sizeof
(*limit
));
854 fatal
("reallocarray");
856 limit
[nlimits
- 1].uid
= uid
;
857 limit
[nlimits
- 1].max_connections
= maximum
;
859 gotd
->connection_limits
= limit
;
860 gotd
->nconnection_limits
= nlimits
;
861 qsort
(gotd
->connection_limits
, gotd
->nconnection_limits
,
862 sizeof
(gotd
->connection_limits
[0]), uid_connection_limit_cmp
);
867 static struct gotd_repo
*
868 conf_new_repo
(const char *name
)
870 struct gotd_repo
*repo
;
872 if
(name
[0] == '\0') {
873 fatalx
("syntax error: empty repository name found in %s",
877 if
(strchr
(name
, '\n') != NULL
)
878 fatalx
("repository names must not contain linefeeds: %s", name
);
880 repo
= calloc
(1, sizeof
(*repo
));
882 fatalx
("%s: calloc", __func__
);
884 STAILQ_INIT
(&repo
->rules
);
885 TAILQ_INIT
(&repo
->protected_tag_namespaces
);
886 TAILQ_INIT
(&repo
->protected_branch_namespaces
);
887 TAILQ_INIT
(&repo
->protected_branches
);
889 if
(strlcpy
(repo
->name
, name
, sizeof
(repo
->name
)) >=
891 fatalx
("%s: strlcpy", __func__
);
893 TAILQ_INSERT_TAIL
(&gotd
->repos
, repo
, entry
);
900 conf_new_access_rule
(struct gotd_repo
*repo
, enum gotd_access access
,
901 int authorization
, char *identifier
)
903 struct gotd_access_rule
*rule
;
905 rule
= calloc
(1, sizeof
(*rule
));
909 rule
->access
= access
;
910 rule
->authorization
= authorization
;
911 rule
->identifier
= identifier
;
913 STAILQ_INSERT_TAIL
(&repo
->rules
, rule
, entry
);
917 refname_is_valid
(char *refname
)
919 if
(strlen
(refname
) < 5 || strncmp
(refname
, "refs/", 5) != 0) {
920 yyerror("reference name must begin with \"refs/\": %s",
925 if
(!got_ref_name_is_valid
(refname
)) {
926 yyerror("invalid reference name: %s", refname
);
934 conf_protect_ref_namespace
(char **new
, struct got_pathlist_head
*refs
,
937 const struct got_error
*error;
938 struct got_pathlist_entry
*pe
;
943 got_path_strip_trailing_slashes
(namespace
);
944 if
(!refname_is_valid
(namespace
))
946 if
(asprintf
(&s
, "%s/", namespace
) == -1) {
947 yyerror("asprintf: %s", strerror
(errno
));
951 error = got_pathlist_insert
(&pe
, refs
, s
, NULL
);
952 if
(error || pe
== NULL
) {
955 yyerror("got_pathlist_insert: %s", error->msg
);
957 yyerror("duplicate protected namespace %s", namespace
);
966 conf_protect_tag_namespace
(struct gotd_repo
*repo
, char *namespace
)
968 struct got_pathlist_entry
*pe
;
971 if
(conf_protect_ref_namespace
(&new
, &repo
->protected_tag_namespaces
,
975 TAILQ_FOREACH
(pe
, &repo
->protected_branch_namespaces
, entry
) {
976 if
(strcmp
(pe
->path
, new
) == 0) {
977 yyerror("duplicate protected namespace %s", namespace
);
986 conf_protect_branch_namespace
(struct gotd_repo
*repo
, char *namespace
)
988 struct got_pathlist_entry
*pe
;
991 if
(conf_protect_ref_namespace
(&new
,
992 &repo
->protected_branch_namespaces
, namespace
) == -1)
995 TAILQ_FOREACH
(pe
, &repo
->protected_tag_namespaces
, entry
) {
996 if
(strcmp
(pe
->path
, new
) == 0) {
997 yyerror("duplicate protected namespace %s", namespace
);
1006 conf_protect_branch
(struct gotd_repo
*repo
, char *branchname
)
1008 const struct got_error
*error;
1009 struct got_pathlist_entry
*new
;
1012 if
(strncmp
(branchname
, "refs/heads/", 11) != 0) {
1013 if
(asprintf
(&refname
, "refs/heads/%s", branchname
) == -1) {
1014 yyerror("asprintf: %s", strerror
(errno
));
1018 refname
= strdup
(branchname
);
1019 if
(refname
== NULL
) {
1020 yyerror("strdup: %s", strerror
(errno
));
1025 if
(!refname_is_valid
(refname
)) {
1030 error = got_pathlist_insert
(&new
, &repo
->protected_branches
,
1032 if
(error || new
== NULL
) {
1035 yyerror("got_pathlist_insert: %s", error->msg
);
1037 yyerror("duplicate protect branch %s", branchname
);
1045 symset
(const char *nam
, const char *val
, int persist
)
1049 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
1050 if
(strcmp
(nam
, sym
->nam
) == 0)
1055 if
(sym
->persist
== 1)
1060 TAILQ_REMOVE
(&symhead
, sym
, entry
);
1064 sym
= calloc
(1, sizeof
(*sym
));
1068 sym
->nam
= strdup
(nam
);
1069 if
(sym
->nam
== NULL
) {
1073 sym
->val
= strdup
(val
);
1074 if
(sym
->val
== NULL
) {
1080 sym
->persist
= persist
;
1081 TAILQ_INSERT_TAIL
(&symhead
, sym
, entry
);
1086 symget
(const char *nam
)
1090 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
1091 if
(strcmp
(nam
, sym
->nam
) == 0) {
1100 gotd_find_repo_by_name
(const char *repo_name
, struct gotd
*gotd
)
1102 struct gotd_repo
*repo
;
1105 TAILQ_FOREACH
(repo
, &gotd
->repos
, entry
) {
1106 namelen
= strlen
(repo
->name
);
1107 if
(strncmp
(repo
->name
, repo_name
, namelen
) != 0)
1109 if
(repo_name
[namelen
] == '\0' ||
1110 strcmp
(&repo_name
[namelen
], ".git") == 0)
1118 gotd_find_repo_by_path
(const char *repo_path
, struct gotd
*gotd
)
1120 struct gotd_repo
*repo
;
1122 TAILQ_FOREACH
(repo
, &gotd
->repos
, entry
) {
1123 if
(strcmp
(repo
->path
, repo_path
) == 0)
1130 struct gotd_uid_connection_limit
*
1131 gotd_find_uid_connection_limit
(struct gotd_uid_connection_limit
*limits
,
1132 size_t nlimits
, uid_t uid
)
1134 /* This array is always sorted to allow for binary search. */
1135 int i
, left
= 0, right
= nlimits
- 1;
1137 while
(left
<= right
) {
1138 i
= ((left
+ right
) / 2);
1139 if
(limits
[i
].uid
== uid
)
1141 if
(limits
[i
].uid
> uid
)
1151 gotd_parseuid
(const char *s
, uid_t
*uid
)
1156 if
((pw
= getpwnam
(s
)) != NULL
) {
1158 if
(*uid
== UID_MAX
)
1162 *uid
= strtonum
(s
, 0, UID_MAX
- 1, &errstr
);