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.
25 #include "got_compat.h"
28 #include <sys/types.h>
29 #include <sys/queue.h>
46 #include "got_error.h"
47 #include "got_object.h"
49 #include "got_reference.h"
56 TAILQ_HEAD
(files
, file
) files
= TAILQ_HEAD_INITIALIZER
(files
);
58 TAILQ_ENTRY
(file
) entry
;
64 struct file
*newfile
(const char *, int, int);
65 static void closefile
(struct file
*);
66 int check_file_secrecy
(int, const char *);
69 int yyerror(const char *, ...
)
70 __attribute__
((__format__
(printf
, 1, 2)))
71 __attribute__
((__nonnull__
(1)));
72 int kw_cmp
(const void *, const void *);
77 static char *port_sprintf
(int);
79 TAILQ_HEAD
(symhead
, sym
) symhead
= TAILQ_HEAD_INITIALIZER
(symhead
);
81 TAILQ_ENTRY
(sym
) entry
;
88 int symset
(const char *, const char *, int);
89 char *symget
(const char *);
93 static struct gotd
*gotd
;
94 static struct gotd_repo
*new_repo
;
95 static int conf_limit_user_connections
(const char *, int);
96 static struct gotd_repo
*conf_new_repo
(const char *);
97 static void conf_new_access_rule
(struct gotd_repo
*,
98 enum gotd_access
, int, char *);
99 static int conf_protect_ref_namespace
(char **,
100 struct got_pathlist_head
*, char *);
101 static int conf_protect_tag_namespace
(struct gotd_repo
*,
103 static int conf_protect_branch_namespace
(
104 struct gotd_repo
*, char *);
105 static int conf_protect_branch
(struct gotd_repo
*,
107 static int conf_notify_branch
(struct gotd_repo
*,
109 static int conf_notify_ref_namespace
(struct gotd_repo
*,
111 static int conf_notify_email
(struct gotd_repo
*,
112 char *, char *, char *, char *, char *);
113 static int conf_notify_http
(struct gotd_repo
*,
114 char *, char *, char *, int);
115 static enum gotd_procid gotd_proc_id
;
128 %token PATH ERROR LISTEN ON USER REPOSITORY PERMIT DENY
129 %token RO RW CONNECTION LIMIT REQUEST TIMEOUT
130 %token PROTECT NAMESPACE BRANCH TAG REFERENCE RELAY PORT
131 %token NOTIFY EMAIL FROM REPLY TO URL PASSWORD INSECURE
133 %token
<v.
string> STRING
134 %token
<v.number
> NUMBER
141 | grammar varset
'\n'
143 | grammar repository
'\n'
146 varset
: STRING
'=' STRING
{
149 if
(isspace
((unsigned char)*s
)) {
150 yyerror("macro name cannot contain "
157 if
(symset
($1, $3, 0) == -1)
158 fatal
("cannot store variable");
166 yyerror("invalid timeout: %lld", $1);
174 const char *type
= "seconds";
179 yyerror("invalid number of seconds: %s", $1);
185 switch
($1[len
- 1]) {
205 $$.tv_sec
= strtonum
($1, 0, INT_MAX
/ mul
, &errstr
);
207 yyerror("number of %s is %s: %s", type
,
218 main
: LISTEN ON STRING
{
219 if
(!got_path_is_absolute
($3))
220 yyerror("bad unix socket path \"%s\": "
221 "must be an absolute path", $3);
223 if
(gotd_proc_id
== PROC_LISTEN
) {
224 if
(strlcpy
(gotd
->unix_socket_path
, $3,
225 sizeof
(gotd
->unix_socket_path
)) >=
226 sizeof
(gotd
->unix_socket_path
)) {
227 yyerror("%s: unix socket path too long",
236 if
(strlcpy
(gotd
->user_name
, $2,
237 sizeof
(gotd
->user_name
)) >=
238 sizeof
(gotd
->user_name
)) {
239 yyerror("%s: user name too long", __func__
);
248 connection
: CONNECTION
'{' optnl conflags_l
'}'
249 | CONNECTION conflags
251 conflags_l
: conflags optnl conflags_l
255 conflags
: REQUEST TIMEOUT timeout
{
256 if
($3.tv_sec
<= 0) {
257 yyerror("invalid timeout: %lld",
258 (long long)$3.tv_sec
);
261 memcpy
(&gotd
->request_timeout
, &$3,
262 sizeof
(gotd
->request_timeout
));
264 | LIMIT USER STRING NUMBER
{
265 if
(gotd_proc_id
== PROC_LISTEN
&&
266 conf_limit_user_connections
($3, $4) == -1) {
274 protect
: PROTECT
'{' optnl protectflags_l
'}'
275 | PROTECT protectflags
277 protectflags_l
: protectflags optnl protectflags_l
281 protectflags
: TAG NAMESPACE STRING
{
282 if
(gotd_proc_id
== PROC_GOTD ||
283 gotd_proc_id
== PROC_REPO_WRITE
) {
284 if
(conf_protect_tag_namespace
(new_repo
, $3)) {
291 | BRANCH NAMESPACE STRING
{
292 if
(gotd_proc_id
== PROC_GOTD ||
293 gotd_proc_id
== PROC_REPO_WRITE
) {
294 if
(conf_protect_branch_namespace
(new_repo
,
303 if
(gotd_proc_id
== PROC_GOTD ||
304 gotd_proc_id
== PROC_REPO_WRITE
) {
305 if
(conf_protect_branch
(new_repo
, $2)) {
314 notify
: NOTIFY
'{' optnl notifyflags_l
'}'
317 notifyflags_l
: notifyflags optnl notifyflags_l
321 notifyflags
: BRANCH STRING
{
322 if
(gotd_proc_id
== PROC_GOTD ||
323 gotd_proc_id
== PROC_SESSION_WRITE ||
324 gotd_proc_id
== PROC_NOTIFY
) {
325 if
(conf_notify_branch
(new_repo
, $2)) {
332 | REFERENCE NAMESPACE STRING
{
333 if
(gotd_proc_id
== PROC_GOTD ||
334 gotd_proc_id
== PROC_SESSION_WRITE ||
335 gotd_proc_id
== PROC_NOTIFY
) {
336 if
(conf_notify_ref_namespace
(new_repo
, $3)) {
344 if
(gotd_proc_id
== PROC_GOTD ||
345 gotd_proc_id
== PROC_SESSION_WRITE ||
346 gotd_proc_id
== PROC_NOTIFY
) {
347 if
(conf_notify_email
(new_repo
, NULL
, $3,
355 | EMAIL FROM STRING TO STRING
{
356 if
(gotd_proc_id
== PROC_GOTD ||
357 gotd_proc_id
== PROC_SESSION_WRITE ||
358 gotd_proc_id
== PROC_NOTIFY
) {
359 if
(conf_notify_email
(new_repo
, $3, $5,
369 | EMAIL TO STRING REPLY TO STRING
{
370 if
(gotd_proc_id
== PROC_GOTD ||
371 gotd_proc_id
== PROC_SESSION_WRITE ||
372 gotd_proc_id
== PROC_NOTIFY
) {
373 if
(conf_notify_email
(new_repo
, NULL
, $3,
383 | EMAIL FROM STRING TO STRING REPLY TO STRING
{
384 if
(gotd_proc_id
== PROC_GOTD ||
385 gotd_proc_id
== PROC_SESSION_WRITE ||
386 gotd_proc_id
== PROC_NOTIFY
) {
387 if
(conf_notify_email
(new_repo
, $3, $5,
399 | EMAIL TO STRING RELAY STRING
{
400 if
(gotd_proc_id
== PROC_GOTD ||
401 gotd_proc_id
== PROC_SESSION_WRITE ||
402 gotd_proc_id
== PROC_NOTIFY
) {
403 if
(conf_notify_email
(new_repo
, NULL
, $3,
413 | EMAIL FROM STRING TO STRING RELAY STRING
{
414 if
(gotd_proc_id
== PROC_GOTD ||
415 gotd_proc_id
== PROC_SESSION_WRITE ||
416 gotd_proc_id
== PROC_NOTIFY
) {
417 if
(conf_notify_email
(new_repo
, $3, $5,
429 | EMAIL TO STRING REPLY TO STRING RELAY STRING
{
430 if
(gotd_proc_id
== PROC_GOTD ||
431 gotd_proc_id
== PROC_SESSION_WRITE ||
432 gotd_proc_id
== PROC_NOTIFY
) {
433 if
(conf_notify_email
(new_repo
, NULL
, $3,
445 | EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING
{
446 if
(gotd_proc_id
== PROC_GOTD ||
447 gotd_proc_id
== PROC_SESSION_WRITE ||
448 gotd_proc_id
== PROC_NOTIFY
) {
449 if
(conf_notify_email
(new_repo
, $3, $5,
463 | EMAIL TO STRING RELAY STRING PORT STRING
{
464 if
(gotd_proc_id
== PROC_GOTD ||
465 gotd_proc_id
== PROC_SESSION_WRITE ||
466 gotd_proc_id
== PROC_NOTIFY
) {
467 if
(conf_notify_email
(new_repo
, NULL
, $3,
479 | EMAIL FROM STRING TO STRING RELAY STRING PORT STRING
{
480 if
(gotd_proc_id
== PROC_GOTD ||
481 gotd_proc_id
== PROC_SESSION_WRITE ||
482 gotd_proc_id
== PROC_NOTIFY
) {
483 if
(conf_notify_email
(new_repo
, $3, $5,
497 | EMAIL TO STRING REPLY TO STRING RELAY STRING PORT STRING
{
498 if
(gotd_proc_id
== PROC_GOTD ||
499 gotd_proc_id
== PROC_SESSION_WRITE ||
500 gotd_proc_id
== PROC_NOTIFY
) {
501 if
(conf_notify_email
(new_repo
, NULL
, $3,
515 | EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING PORT STRING
{
516 if
(gotd_proc_id
== PROC_GOTD ||
517 gotd_proc_id
== PROC_SESSION_WRITE ||
518 gotd_proc_id
== PROC_NOTIFY
) {
519 if
(conf_notify_email
(new_repo
, $3, $5,
535 | EMAIL TO STRING RELAY STRING PORT NUMBER
{
536 if
(gotd_proc_id
== PROC_GOTD ||
537 gotd_proc_id
== PROC_SESSION_WRITE ||
538 gotd_proc_id
== PROC_NOTIFY
) {
539 if
(conf_notify_email
(new_repo
, NULL
, $3,
540 NULL
, $5, port_sprintf
($7))) {
549 | EMAIL FROM STRING TO STRING RELAY STRING PORT NUMBER
{
550 if
(gotd_proc_id
== PROC_GOTD ||
551 gotd_proc_id
== PROC_SESSION_WRITE ||
552 gotd_proc_id
== PROC_NOTIFY
) {
553 if
(conf_notify_email
(new_repo
, $3, $5,
554 NULL
, $7, port_sprintf
($9))) {
565 | EMAIL TO STRING REPLY TO STRING RELAY STRING PORT NUMBER
{
566 if
(gotd_proc_id
== PROC_GOTD ||
567 gotd_proc_id
== PROC_SESSION_WRITE ||
568 gotd_proc_id
== PROC_NOTIFY
) {
569 if
(conf_notify_email
(new_repo
, NULL
, $3,
570 $6, $8, port_sprintf
($10))) {
581 | EMAIL FROM STRING TO STRING REPLY TO STRING RELAY STRING PORT NUMBER
{
582 if
(gotd_proc_id
== PROC_GOTD ||
583 gotd_proc_id
== PROC_SESSION_WRITE ||
584 gotd_proc_id
== PROC_NOTIFY
) {
585 if
(conf_notify_email
(new_repo
, $3, $5,
586 $8, $10, port_sprintf
($12))) {
600 if
(gotd_proc_id
== PROC_GOTD ||
601 gotd_proc_id
== PROC_SESSION_WRITE ||
602 gotd_proc_id
== PROC_NOTIFY
) {
603 if
(conf_notify_http
(new_repo
, $2, NULL
,
611 | URL STRING USER STRING PASSWORD STRING
{
612 if
(gotd_proc_id
== PROC_GOTD ||
613 gotd_proc_id
== PROC_SESSION_WRITE ||
614 gotd_proc_id
== PROC_NOTIFY
) {
615 if
(conf_notify_http
(new_repo
, $2, $4, $6, 0)) {
626 | URL STRING USER STRING PASSWORD STRING INSECURE
{
627 if
(gotd_proc_id
== PROC_GOTD ||
628 gotd_proc_id
== PROC_SESSION_WRITE ||
629 gotd_proc_id
== PROC_NOTIFY
) {
630 if
(conf_notify_http
(new_repo
, $2, $4, $6, 1)) {
643 repository
: REPOSITORY STRING
{
644 struct gotd_repo
*repo
;
646 TAILQ_FOREACH
(repo
, &gotd
->repos
, entry
) {
647 if
(strcmp
(repo
->name
, $2) == 0) {
648 yyerror("duplicate repository '%s'", $2);
654 if
(gotd_proc_id
== PROC_GOTD ||
655 gotd_proc_id
== PROC_AUTH ||
656 gotd_proc_id
== PROC_REPO_WRITE ||
657 gotd_proc_id
== PROC_SESSION_WRITE ||
658 gotd_proc_id
== PROC_GITWRAPPER |
659 gotd_proc_id
== PROC_NOTIFY
) {
660 new_repo
= conf_new_repo
($2);
663 } '{' optnl repoopts2
'}' {
667 repoopts1
: PATH STRING
{
668 if
(gotd_proc_id
== PROC_GOTD ||
669 gotd_proc_id
== PROC_AUTH ||
670 gotd_proc_id
== PROC_REPO_WRITE ||
671 gotd_proc_id
== PROC_SESSION_WRITE ||
672 gotd_proc_id
== PROC_GITWRAPPER ||
673 gotd_proc_id
== PROC_NOTIFY
) {
674 if
(!got_path_is_absolute
($2)) {
675 yyerror("%s: path %s is not absolute",
680 if
(realpath
($2, new_repo
->path
) == NULL
) {
682 * To give admins a chance to create
683 * missing repositories at run-time
684 * we only warn about ENOENT here.
686 * And ignore 'permission denied' when
687 * running in gitwrapper. Users may be
688 * able to access this repository via
691 if
(errno
== ENOENT
) {
692 yyerror("realpath %s: %s", $2,
694 } else if
(errno
!= EACCES ||
695 gotd_proc_id
!= PROC_GITWRAPPER
) {
696 yyerror("realpath %s: %s", $2,
702 if
(strlcpy
(new_repo
->path
, $2,
703 sizeof
(new_repo
->path
)) >=
704 sizeof
(new_repo
->path
))
705 yyerror("path too long");
711 if
(gotd_proc_id
== PROC_AUTH
) {
712 conf_new_access_rule
(new_repo
,
713 GOTD_ACCESS_PERMITTED
, GOTD_AUTH_READ
, $3);
718 if
(gotd_proc_id
== PROC_AUTH
) {
719 conf_new_access_rule
(new_repo
,
720 GOTD_ACCESS_PERMITTED
,
721 GOTD_AUTH_READ | GOTD_AUTH_WRITE
, $3);
726 if
(gotd_proc_id
== PROC_AUTH
) {
727 conf_new_access_rule
(new_repo
,
728 GOTD_ACCESS_DENIED
, 0, $2);
736 repoopts2
: repoopts2 repoopts1 nl
743 optnl
: '\n' optnl
/* zero or more newlines */
755 yyerror(const char *fmt
, ...
)
762 if
(vasprintf
(&msg
, fmt
, ap
) == -1)
763 fatalx
("yyerror vasprintf");
765 logit
(LOG_CRIT
, "%s:%d: %s", file
->name
, yylval.lineno
, msg
);
771 kw_cmp
(const void *k
, const void *e
)
773 return
(strcmp
(k
, ((const struct keywords
*)e
)->k_name
));
779 /* This has to be sorted always. */
780 static const struct keywords keywords
[] = {
781 { "branch", BRANCH
},
782 { "connection", CONNECTION
},
786 { "insecure", INSECURE
},
788 { "listen", LISTEN
},
789 { "namespace", NAMESPACE
},
790 { "notify", NOTIFY
},
792 { "password", PASSWORD
},
794 { "permit", PERMIT
},
796 { "protect", PROTECT
},
797 { "reference", REFERENCE
},
800 { "repository", REPOSITORY
},
801 { "request", REQUEST
},
805 { "timeout", TIMEOUT
},
810 const struct keywords
*p
;
812 p
= bsearch
(s
, keywords
, sizeof
(keywords
)/sizeof
(keywords
[0]),
813 sizeof
(keywords
[0]), kw_cmp
);
821 #define MAXPUSHBACK 128
823 unsigned char *parsebuf
;
825 unsigned char pushback_buffer
[MAXPUSHBACK
];
826 int pushback_index
= 0;
834 /* Read character from the parsebuffer instead of input. */
835 if
(parseindex
>= 0) {
836 c
= parsebuf
[parseindex
++];
845 return
(pushback_buffer
[--pushback_index
]);
848 c
= getc
(file
->stream
);
850 yyerror("reached end of file while parsing "
855 c
= getc
(file
->stream
);
857 next
= getc
(file
->stream
);
862 yylval.lineno
= file
->lineno
;
864 c
= getc
(file
->stream
);
880 if
(pushback_index
< MAXPUSHBACK
-1)
881 return
(pushback_buffer
[pushback_index
++] = c
);
893 /* Skip to either EOF or the first real EOL. */
896 c
= pushback_buffer
[--pushback_index
];
912 unsigned char buf
[8096];
913 unsigned char *p
, *val
;
920 while
(c
== ' ' || c
== '\t')
921 c
= lgetc
(0); /* nothing */
923 yylval.lineno
= file
->lineno
;
926 while
(c
!= '\n' && c
!= EOF
)
927 c
= lgetc
(0); /* nothing */
929 if
(c
== '$' && parsebuf
== NULL
) {
935 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
936 yyerror("string too long");
939 if
(isalnum
(c
) || c
== '_') {
949 yyerror("macro '%s' not defined", buf
);
968 } else if
(c
== '\\') {
969 next
= lgetc
(quotec
);
972 if
(next
== quotec || c
== ' ' || c
== '\t')
974 else if
(next
== '\n') {
979 } else if
(c
== quotec
) {
982 } else if
(c
== '\0') {
983 yyerror("syntax error");
986 if
(p
+ 1 >= buf
+ sizeof
(buf
) - 1) {
987 yyerror("string too long");
992 yylval.v.
string = strdup
(buf
);
993 if
(yylval.v.
string == NULL
)
994 err
(1, "yylex: strdup");
998 #define allowed_to_end_number(x) \
999 (isspace
(x
) || x
== ')' || x
==',' || x
== '/' || x
== '}' || x
== '=')
1001 if
(c
== '-' || isdigit
(c
)) {
1004 if
((unsigned)(p
-buf
) >= sizeof
(buf
)) {
1005 yyerror("string too long");
1009 } while
(c
!= EOF
&& isdigit
(c
));
1011 if
(p
== buf
+ 1 && buf
[0] == '-')
1013 if
(c
== EOF || allowed_to_end_number
(c
)) {
1014 const char *errstr
= NULL
;
1017 yylval.v.number
= strtonum
(buf
, LLONG_MIN
,
1018 LLONG_MAX
, &errstr
);
1020 yyerror("\"%s\" invalid number: %s",
1035 #define allowed_in_string(x) \
1036 (isalnum
(x
) ||
(ispunct
(x
) && x
!= '(' && x
!= ')' && \
1037 x
!= '{' && x
!= '}' && \
1038 x
!= '!' && x
!= '=' && x
!= '#' && \
1041 if
(isalnum
(c
) || c
== ':' || c
== '_') {
1044 if
((unsigned)(p
-buf
) >= sizeof
(buf
)) {
1045 yyerror("string too long");
1049 } while
(c
!= EOF
&& (allowed_in_string
(c
)));
1052 token
= lookup
(buf
);
1053 if
(token
== STRING
) {
1054 yylval.v.
string = strdup
(buf
);
1055 if
(yylval.v.
string == NULL
)
1056 err
(1, "yylex: strdup");
1061 yylval.lineno
= file
->lineno
;
1070 check_file_secrecy
(int fd
, const char *fname
)
1074 if
(fstat
(fd
, &st
)) {
1075 log_warn
("cannot stat %s", fname
);
1078 if
(st.st_uid
!= 0 && st.st_uid
!= getuid
()) {
1079 log_warnx
("%s: owner not root or current user", fname
);
1082 if
(st.st_mode
& (S_IWGRP | S_IXGRP | S_IRWXO
)) {
1083 log_warnx
("%s: group writable or world read/writable", fname
);
1090 newfile
(const char *name
, int secret
, int required
)
1094 nfile
= calloc
(1, sizeof
(struct file
));
1095 if
(nfile
== NULL
) {
1099 nfile
->name
= strdup
(name
);
1100 if
(nfile
->name
== NULL
) {
1105 nfile
->stream
= fopen
(nfile
->name
, "r");
1106 if
(nfile
->stream
== NULL
) {
1108 log_warn
("open %s", nfile
->name
);
1112 } else if
(secret
&&
1113 check_file_secrecy
(fileno
(nfile
->stream
), nfile
->name
)) {
1114 fclose
(nfile
->stream
);
1124 closefile
(struct file
*xfile
)
1126 fclose
(xfile
->stream
);
1132 parse_config
(const char *filename
, enum gotd_procid proc_id
,
1135 struct sym
*sym
, *next
;
1136 struct gotd_repo
*repo
;
1137 int require_config_file
= (proc_id
!= PROC_GITWRAPPER
);
1139 memset
(env
, 0, sizeof
(*env
));
1142 gotd_proc_id
= proc_id
;
1143 TAILQ_INIT
(&gotd
->repos
);
1145 /* Apply default values. */
1146 if
(strlcpy
(gotd
->unix_socket_path
, GOTD_UNIX_SOCKET
,
1147 sizeof
(gotd
->unix_socket_path
)) >= sizeof
(gotd
->unix_socket_path
)) {
1148 fprintf
(stderr
, "%s: unix socket path too long", __func__
);
1151 if
(strlcpy
(gotd
->user_name
, GOTD_USER
,
1152 sizeof
(gotd
->user_name
)) >= sizeof
(gotd
->user_name
)) {
1153 fprintf
(stderr
, "%s: user name too long", __func__
);
1157 gotd
->request_timeout.tv_sec
= GOTD_DEFAULT_REQUEST_TIMEOUT
;
1158 gotd
->request_timeout.tv_usec
= 0;
1160 file
= newfile
(filename
, 0, require_config_file
);
1162 return require_config_file ?
-1 : 0;
1165 errors
= file
->errors
;
1168 /* Free macros and check which have not been used. */
1169 TAILQ_FOREACH_SAFE
(sym
, &symhead
, entry
, next
) {
1170 if
((gotd
->verbosity
> 1) && !sym
->used
)
1171 fprintf
(stderr
, "warning: macro '%s' not used\n",
1173 if
(!sym
->persist
) {
1176 TAILQ_REMOVE
(&symhead
, sym
, entry
);
1184 TAILQ_FOREACH
(repo
, &gotd
->repos
, entry
) {
1185 if
(repo
->path
[0] == '\0') {
1186 log_warnx
("repository \"%s\": no path provided in "
1187 "configuration file", repo
->name
);
1192 if
(proc_id
== PROC_GOTD
&& TAILQ_EMPTY
(&gotd
->repos
)) {
1193 log_warnx
("no repository defined in configuration file");
1201 uid_connection_limit_cmp
(const void *pa
, const void *pb
)
1203 const struct gotd_uid_connection_limit
*a
= pa
, *b
= pb
;
1205 if
(a
->uid
< b
->uid
)
1207 else if
(a
->uid
> b
->uid
);
1214 conf_limit_user_connections
(const char *user
, int maximum
)
1217 struct gotd_uid_connection_limit
*limit
;
1221 yyerror("max connections cannot be smaller 1");
1224 if
(maximum
> GOTD_MAXCLIENTS
) {
1225 yyerror("max connections must be <= %d", GOTD_MAXCLIENTS
);
1229 if
(gotd_parseuid
(user
, &uid
) == -1) {
1230 yyerror("%s: no such user", user
);
1234 limit
= gotd_find_uid_connection_limit
(gotd
->connection_limits
,
1235 gotd
->nconnection_limits
, uid
);
1237 limit
->max_connections
= maximum
;
1241 limit
= gotd
->connection_limits
;
1242 nlimits
= gotd
->nconnection_limits
+ 1;
1243 limit
= reallocarray
(limit
, nlimits
, sizeof
(*limit
));
1245 fatal
("reallocarray");
1247 limit
[nlimits
- 1].uid
= uid
;
1248 limit
[nlimits
- 1].max_connections
= maximum
;
1250 gotd
->connection_limits
= limit
;
1251 gotd
->nconnection_limits
= nlimits
;
1252 qsort
(gotd
->connection_limits
, gotd
->nconnection_limits
,
1253 sizeof
(gotd
->connection_limits
[0]), uid_connection_limit_cmp
);
1258 static struct gotd_repo
*
1259 conf_new_repo
(const char *name
)
1261 struct gotd_repo
*repo
;
1263 if
(name
[0] == '\0') {
1264 fatalx
("syntax error: empty repository name found in %s",
1268 if
(strchr
(name
, '\n') != NULL
)
1269 fatalx
("repository names must not contain linefeeds: %s", name
);
1271 repo
= calloc
(1, sizeof
(*repo
));
1273 fatalx
("%s: calloc", __func__
);
1275 STAILQ_INIT
(&repo
->rules
);
1276 TAILQ_INIT
(&repo
->protected_tag_namespaces
);
1277 TAILQ_INIT
(&repo
->protected_branch_namespaces
);
1278 TAILQ_INIT
(&repo
->protected_branches
);
1279 TAILQ_INIT
(&repo
->protected_branches
);
1280 TAILQ_INIT
(&repo
->notification_refs
);
1281 TAILQ_INIT
(&repo
->notification_ref_namespaces
);
1282 STAILQ_INIT
(&repo
->notification_targets
);
1284 if
(strlcpy
(repo
->name
, name
, sizeof
(repo
->name
)) >=
1286 fatalx
("%s: strlcpy", __func__
);
1288 TAILQ_INSERT_TAIL
(&gotd
->repos
, repo
, entry
);
1295 conf_new_access_rule
(struct gotd_repo
*repo
, enum gotd_access access
,
1296 int authorization
, char *identifier
)
1298 struct gotd_access_rule
*rule
;
1300 rule
= calloc
(1, sizeof
(*rule
));
1304 rule
->access
= access
;
1305 rule
->authorization
= authorization
;
1306 rule
->identifier
= identifier
;
1308 STAILQ_INSERT_TAIL
(&repo
->rules
, rule
, entry
);
1312 refname_is_valid
(char *refname
)
1314 if
(strncmp
(refname
, "refs/", 5) != 0) {
1315 yyerror("reference name must begin with \"refs/\": %s",
1320 if
(!got_ref_name_is_valid
(refname
)) {
1321 yyerror("invalid reference name: %s", refname
);
1329 conf_protect_ref_namespace
(char **new
, struct got_pathlist_head
*refs
,
1332 const struct got_error
*error;
1333 struct got_pathlist_entry
*pe
;
1338 got_path_strip_trailing_slashes
(namespace
);
1339 if
(!refname_is_valid
(namespace
))
1341 if
(asprintf
(&s
, "%s/", namespace
) == -1) {
1342 yyerror("asprintf: %s", strerror
(errno
));
1346 error = got_pathlist_insert
(&pe
, refs
, s
, NULL
);
1347 if
(error || pe
== NULL
) {
1350 yyerror("got_pathlist_insert: %s", error->msg
);
1352 yyerror("duplicate protected namespace %s", namespace
);
1361 conf_protect_tag_namespace
(struct gotd_repo
*repo
, char *namespace
)
1363 struct got_pathlist_entry
*pe
;
1366 if
(conf_protect_ref_namespace
(&new
, &repo
->protected_tag_namespaces
,
1370 TAILQ_FOREACH
(pe
, &repo
->protected_branch_namespaces
, entry
) {
1371 if
(strcmp
(pe
->path
, new
) == 0) {
1372 yyerror("duplicate protected namespace %s", namespace
);
1381 conf_protect_branch_namespace
(struct gotd_repo
*repo
, char *namespace
)
1383 struct got_pathlist_entry
*pe
;
1386 if
(conf_protect_ref_namespace
(&new
,
1387 &repo
->protected_branch_namespaces
, namespace
) == -1)
1390 TAILQ_FOREACH
(pe
, &repo
->protected_tag_namespaces
, entry
) {
1391 if
(strcmp
(pe
->path
, new
) == 0) {
1392 yyerror("duplicate protected namespace %s", namespace
);
1401 conf_protect_branch
(struct gotd_repo
*repo
, char *branchname
)
1403 const struct got_error
*error;
1404 struct got_pathlist_entry
*new
;
1407 if
(strncmp
(branchname
, "refs/heads/", 11) != 0) {
1408 if
(asprintf
(&refname
, "refs/heads/%s", branchname
) == -1) {
1409 yyerror("asprintf: %s", strerror
(errno
));
1413 refname
= strdup
(branchname
);
1414 if
(refname
== NULL
) {
1415 yyerror("strdup: %s", strerror
(errno
));
1420 if
(!refname_is_valid
(refname
)) {
1425 error = got_pathlist_insert
(&new
, &repo
->protected_branches
,
1427 if
(error || new
== NULL
) {
1430 yyerror("got_pathlist_insert: %s", error->msg
);
1432 yyerror("duplicate protect branch %s", branchname
);
1440 conf_notify_branch
(struct gotd_repo
*repo
, char *branchname
)
1442 const struct got_error
*error;
1443 struct got_pathlist_entry
*pe
;
1446 if
(strncmp
(branchname
, "refs/heads/", 11) != 0) {
1447 if
(asprintf
(&refname
, "refs/heads/%s", branchname
) == -1) {
1448 yyerror("asprintf: %s", strerror
(errno
));
1452 refname
= strdup
(branchname
);
1453 if
(refname
== NULL
) {
1454 yyerror("strdup: %s", strerror
(errno
));
1459 if
(!refname_is_valid
(refname
)) {
1464 error = got_pathlist_insert
(&pe
, &repo
->notification_refs
,
1468 yyerror("got_pathlist_insert: %s", error->msg
);
1478 conf_notify_ref_namespace
(struct gotd_repo
*repo
, char *namespace
)
1480 const struct got_error
*error;
1481 struct got_pathlist_entry
*pe
;
1484 got_path_strip_trailing_slashes
(namespace
);
1485 if
(!refname_is_valid
(namespace
))
1488 if
(asprintf
(&s
, "%s/", namespace
) == -1) {
1489 yyerror("asprintf: %s", strerror
(errno
));
1493 error = got_pathlist_insert
(&pe
, &repo
->notification_ref_namespaces
,
1497 yyerror("got_pathlist_insert: %s", error->msg
);
1507 conf_notify_email
(struct gotd_repo
*repo
, char *sender
, char *recipient
,
1508 char *responder
, char *hostname
, char *port
)
1510 struct gotd_notification_target
*target
;
1512 STAILQ_FOREACH
(target
, &repo
->notification_targets
, entry
) {
1513 if
(target
->type
!= GOTD_NOTIFICATION_VIA_EMAIL
)
1515 if
(strcmp
(target
->conf.email.recipient
, recipient
) == 0) {
1516 yyerror("duplicate email notification for '%s' in "
1517 "repository '%s'", recipient
, repo
->name
);
1522 target
= calloc
(1, sizeof
(*target
));
1525 target
->type
= GOTD_NOTIFICATION_VIA_EMAIL
;
1527 target
->conf.email.sender
= strdup
(sender
);
1528 if
(target
->conf.email.sender
== NULL
)
1531 target
->conf.email.recipient
= strdup
(recipient
);
1532 if
(target
->conf.email.recipient
== NULL
)
1535 target
->conf.email.responder
= strdup
(responder
);
1536 if
(target
->conf.email.responder
== NULL
)
1540 target
->conf.email.hostname
= strdup
(hostname
);
1541 if
(target
->conf.email.hostname
== NULL
)
1545 target
->conf.email.port
= strdup
(port
);
1546 if
(target
->conf.email.port
== NULL
)
1550 STAILQ_INSERT_TAIL
(&repo
->notification_targets
, target
, entry
);
1555 conf_notify_http
(struct gotd_repo
*repo
, char *url
, char *user
, char *password
,
1558 const struct got_error
*error;
1559 struct gotd_notification_target
*target
;
1560 char *proto
, *hostname
, *port
, *path
;
1561 int tls
= 0, ret
= 0;
1563 error = gotd_parse_url
(&proto
, &hostname
, &port
, &path
, url
);
1565 yyerror("invalid HTTP notification URL '%s' in "
1566 "repository '%s': %s", url
, repo
->name
, error->msg
);
1570 tls
= !strcmp
(proto
, "https");
1572 if
(strcmp
(proto
, "http") != 0 && strcmp
(proto
, "https") != 0) {
1573 yyerror("invalid protocol '%s' in notification URL '%s' in "
1574 "repository '%s", proto
, url
, repo
->name
);
1580 if
(strcmp
(proto
, "http") == 0)
1581 port
= strdup
("80");
1582 if
(strcmp
(proto
, "https") == 0)
1583 port
= strdup
("443");
1585 error = got_error_from_errno
("strdup");
1591 if
((user
!= NULL
&& password
== NULL
) ||
1592 (user
== NULL
&& password
!= NULL
)) {
1593 yyerror("missing username or password");
1598 if
(!insecure
&& strcmp
(proto
, "http") == 0 &&
1599 (user
!= NULL || password
!= NULL
)) {
1600 yyerror("%s: HTTP notifications with basic authentication "
1601 "over plaintext HTTP will leak credentials; add the "
1602 "'insecure' config keyword if this is intentional", url
);
1607 STAILQ_FOREACH
(target
, &repo
->notification_targets
, entry
) {
1608 if
(target
->type
!= GOTD_NOTIFICATION_VIA_HTTP
)
1610 if
(target
->conf.http.tls
== tls
&&
1611 !strcmp
(target
->conf.http.hostname
, hostname
) &&
1612 !strcmp
(target
->conf.http.port
, port
) &&
1613 !strcmp
(target
->conf.http.path
, path
)) {
1614 yyerror("duplicate notification for URL '%s' in "
1615 "repository '%s'", url
, repo
->name
);
1621 target
= calloc
(1, sizeof
(*target
));
1624 target
->type
= GOTD_NOTIFICATION_VIA_HTTP
;
1625 target
->conf.http.tls
= tls
;
1626 target
->conf.http.hostname
= hostname
;
1627 target
->conf.http.port
= port
;
1628 target
->conf.http.path
= path
;
1629 hostname
= port
= path
= NULL
;
1632 target
->conf.http.user
= strdup
(user
);
1633 if
(target
->conf.http.user
== NULL
)
1635 target
->conf.http.password
= strdup
(password
);
1636 if
(target
->conf.http.password
== NULL
)
1640 STAILQ_INSERT_TAIL
(&repo
->notification_targets
, target
, entry
);
1650 symset
(const char *nam
, const char *val
, int persist
)
1654 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
1655 if
(strcmp
(nam
, sym
->nam
) == 0)
1660 if
(sym
->persist
== 1)
1665 TAILQ_REMOVE
(&symhead
, sym
, entry
);
1669 sym
= calloc
(1, sizeof
(*sym
));
1673 sym
->nam
= strdup
(nam
);
1674 if
(sym
->nam
== NULL
) {
1678 sym
->val
= strdup
(val
);
1679 if
(sym
->val
== NULL
) {
1685 sym
->persist
= persist
;
1686 TAILQ_INSERT_TAIL
(&symhead
, sym
, entry
);
1691 symget
(const char *nam
)
1695 TAILQ_FOREACH
(sym
, &symhead
, entry
) {
1696 if
(strcmp
(nam
, sym
->nam
) == 0) {
1705 gotd_find_repo_by_name
(const char *repo_name
, struct gotd_repolist
*repos
)
1707 struct gotd_repo
*repo
;
1710 TAILQ_FOREACH
(repo
, repos
, entry
) {
1711 namelen
= strlen
(repo
->name
);
1712 if
(strncmp
(repo
->name
, repo_name
, namelen
) != 0)
1714 if
(repo_name
[namelen
] == '\0' ||
1715 strcmp
(&repo_name
[namelen
], ".git") == 0)
1723 gotd_find_repo_by_path
(const char *repo_path
, struct gotd
*gotd
)
1725 struct gotd_repo
*repo
;
1727 TAILQ_FOREACH
(repo
, &gotd
->repos
, entry
) {
1728 if
(strcmp
(repo
->path
, repo_path
) == 0)
1735 struct gotd_uid_connection_limit
*
1736 gotd_find_uid_connection_limit
(struct gotd_uid_connection_limit
*limits
,
1737 size_t nlimits
, uid_t uid
)
1739 /* This array is always sorted to allow for binary search. */
1740 int i
, left
= 0, right
= nlimits
- 1;
1742 while
(left
<= right
) {
1743 i
= ((left
+ right
) / 2);
1744 if
(limits
[i
].uid
== uid
)
1746 if
(limits
[i
].uid
> uid
)
1756 gotd_parseuid
(const char *s
, uid_t
*uid
)
1761 if
((pw
= getpwnam
(s
)) != NULL
) {
1763 if
(*uid
== UID_MAX
)
1767 *uid
= strtonum
(s
, 0, UID_MAX
- 1, &errstr
);
1773 const struct got_error
*
1774 gotd_parse_url
(char **proto
, char **host
, char **port
,
1775 char **request_path
, const char *url
)
1777 const struct got_error
*err
= NULL
;
1780 *proto
= *host
= *port
= *request_path
= NULL
;
1782 p
= strstr
(url
, "://");
1784 return got_error
(GOT_ERR_PARSE_URI
);
1786 *proto
= strndup
(url
, p
- url
);
1787 if
(*proto
== NULL
) {
1788 err
= got_error_from_errno
("strndup");
1795 err
= got_error
(GOT_ERR_PARSE_URI
);
1799 q
= memchr
(s
, ':', p
- s
);
1801 *host
= strndup
(s
, q
- s
);
1802 if
(*host
== NULL
) {
1803 err
= got_error_from_errno
("strndup");
1806 if
((*host
)[0] == '\0') {
1807 err
= got_error
(GOT_ERR_PARSE_URI
);
1810 *port
= strndup
(q
+ 1, p
- (q
+ 1));
1811 if
(*port
== NULL
) {
1812 err
= got_error_from_errno
("strndup");
1815 if
((*port
)[0] == '\0') {
1816 err
= got_error
(GOT_ERR_PARSE_URI
);
1820 *host
= strndup
(s
, p
- s
);
1821 if
(*host
== NULL
) {
1822 err
= got_error_from_errno
("strndup");
1825 if
((*host
)[0] == '\0') {
1826 err
= got_error
(GOT_ERR_PARSE_URI
);
1831 while
(p
[0] == '/' && p
[1] == '/')
1833 *request_path
= strdup
(p
);
1834 if
(*request_path
== NULL
) {
1835 err
= got_error_from_errno
("strdup");
1838 if
((*request_path
)[0] == '\0') {
1839 err
= got_error
(GOT_ERR_PARSE_URI
);
1850 free
(*request_path
);
1851 *request_path
= NULL
;
1859 static char portno
[32];
1862 n
= snprintf
(portno
, sizeof
(portno
), "%lld", (long long)p
);
1863 if
(n
< 0 ||
(size_t)n
>= sizeof
(portno
))
1864 fatalx
("port number too long: %lld", (long long)p
);