2 * Copyright (c) 2022 Stefan Sperling <stsp@openbsd.org>
3 * Copyright (c) 2015 Ted Unangst <tedu@openbsd.org>
5 * Permission to use, copy, modify, and distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice appear in all copies.
9 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
10 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
11 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
12 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
13 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
14 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
15 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18 #include "got_compat.h"
20 #include <sys/types.h>
21 #include <sys/socket.h>
22 #include <sys/queue.h>
38 #include "got_error.h"
39 #include "got_object.h"
46 static struct gotd_auth
{
49 struct gotd_repo
*repo
;
52 static void auth_shutdown(void);
55 auth_sighdlr(int sig
, short event
, void *arg
)
58 * Normal signal handler rules don't apply because libevent
73 fatalx("unexpected signal");
78 uidcheck(const char *s
, uid_t desired
)
82 if (gotd_parseuid(s
, &uid
) != 0)
90 parsegid(const char *s
, gid_t
*gid
)
95 if ((gr
= getgrnam(s
)) != NULL
) {
101 *gid
= strtonum(s
, 0, GID_MAX
- 1, &errstr
);
108 match_identifier(const char *identifier
, gid_t
*groups
, int ngroups
,
109 uid_t euid
, gid_t egid
)
113 if (identifier
[0] == ':') {
115 if (parsegid(identifier
+ 1, &rgid
) == -1)
119 for (i
= 0; i
< ngroups
; i
++) {
120 if (rgid
== groups
[i
])
125 } else if (uidcheck(identifier
, euid
) != 0)
131 static const struct got_error
*
132 auth_check(char **username
, struct gotd_access_rule_list
*rules
,
133 const char *repo_name
, uid_t euid
, gid_t egid
, int required_auth
)
135 struct gotd_access_rule
*rule
;
136 enum gotd_access access
= GOTD_ACCESS_DENIED
;
138 gid_t groups
[NGROUPS_MAX
];
139 int ngroups
= NGROUPS_MAX
;
140 int matched_user
= 0;
147 return got_error_from_errno("getpwuid");
149 return got_error_set_errno(EACCES
, repo_name
);
152 *username
= strdup(pw
->pw_name
);
153 if (*username
== NULL
)
154 return got_error_from_errno("strdup");
156 if (getgrouplist(pw
->pw_name
, pw
->pw_gid
, groups
, &ngroups
) == -1)
157 log_warnx("group membership list truncated");
159 STAILQ_FOREACH(rule
, rules
, entry
) {
160 if (!match_identifier(rule
->identifier
, groups
, ngroups
,
165 access
= rule
->access
;
166 if (rule
->access
== GOTD_ACCESS_PERMITTED
&&
167 (rule
->authorization
& required_auth
) != required_auth
)
168 access
= GOTD_ACCESS_DENIED
;
171 if (access
== GOTD_ACCESS_DENIED
) {
173 * If a user has no explicit read or write access then
174 * do not leak the existence of a repository to them.
177 return got_error(GOT_ERR_NOT_GIT_REPO
);
179 return got_error_set_errno(EACCES
, repo_name
);
182 if (access
== GOTD_ACCESS_PERMITTED
)
185 /* should not happen, this would be a bug */
186 return got_error_msg(GOT_ERR_NOT_IMPL
, "bad access rule");
189 static const struct got_error
*
190 recv_authreq(struct imsg
*imsg
, struct gotd_imsgev
*iev
)
192 const struct got_error
*err
;
193 struct imsgbuf
*ibuf
= &iev
->ibuf
;
194 struct gotd_imsg_auth iauth
;
198 char *username
= NULL
;
200 const size_t maxlen
= MAX_IMSGSIZE
- IMSG_HEADER_SIZE
;
203 log_debug("authentication request received");
205 datalen
= imsg
->hdr
.len
- IMSG_HEADER_SIZE
;
206 if (datalen
!= sizeof(iauth
))
207 return got_error(GOT_ERR_PRIVSEP_LEN
);
209 memcpy(&iauth
, imsg
->data
, datalen
);
211 fd
= imsg_get_fd(imsg
);
213 return got_error(GOT_ERR_PRIVSEP_NO_FD
);
215 if (getpeereid(fd
, &euid
, &egid
) == -1)
216 return got_error_from_errno("getpeerid");
218 if (iauth
.euid
!= euid
)
219 return got_error(GOT_ERR_UID
);
220 if (iauth
.egid
!= egid
)
221 return got_error(GOT_ERR_GID
);
223 log_debug("authenticating uid %d gid %d", euid
, egid
);
225 err
= auth_check(&username
, &gotd_auth
.repo
->rules
,
226 gotd_auth
.repo
->name
, iauth
.euid
, iauth
.egid
, iauth
.required_auth
);
228 gotd_imsg_send_error(ibuf
, PROC_AUTH
, iauth
.client_id
, err
);
232 len
= strlen(username
);
236 if (gotd_imsg_compose_event(iev
, GOTD_IMSG_ACCESS_GRANTED
,
237 PROC_AUTH
, -1, username
, len
) == -1)
238 err
= got_error_from_errno("imsg compose ACCESS_GRANTED");
245 auth_dispatch(int fd
, short event
, void *arg
)
247 const struct got_error
*err
= NULL
;
248 struct gotd_imsgev
*iev
= arg
;
249 struct imsgbuf
*ibuf
= &iev
->ibuf
;
254 if (event
& EV_READ
) {
255 if ((n
= imsg_read(ibuf
)) == -1 && errno
!= EAGAIN
)
256 fatal("imsg_read error");
257 if (n
== 0) /* Connection closed. */
261 if (event
& EV_WRITE
) {
262 n
= msgbuf_write(&ibuf
->w
);
263 if (n
== -1 && errno
!= EAGAIN
)
264 fatal("msgbuf_write");
265 if (n
== 0) /* Connection closed. */
270 if ((n
= imsg_get(ibuf
, &imsg
)) == -1)
271 fatal("%s: imsg_get", __func__
);
272 if (n
== 0) /* No more messages. */
275 switch (imsg
.hdr
.type
) {
276 case GOTD_IMSG_AUTHENTICATE
:
277 err
= recv_authreq(&imsg
, iev
);
279 log_warnx("%s", err
->msg
);
282 log_debug("unexpected imsg %d", imsg
.hdr
.type
);
290 gotd_imsg_event_add(iev
);
292 /* This pipe is dead. Remove its event handler */
294 event_loopexit(NULL
);
299 auth_main(const char *title
, struct gotd_repolist
*repos
,
300 const char *repo_path
)
302 struct gotd_repo
*repo
= NULL
;
303 struct gotd_imsgev iev
;
304 struct event evsigint
, evsigterm
, evsighup
, evsigusr1
;
306 gotd_auth
.title
= title
;
307 gotd_auth
.pid
= getpid();
308 TAILQ_FOREACH(repo
, repos
, entry
) {
309 if (got_path_cmp(repo
->path
, repo_path
,
310 strlen(repo
->path
), strlen(repo_path
)) == 0)
314 fatalx("repository %s not found in config", repo_path
);
315 gotd_auth
.repo
= repo
;
317 signal_set(&evsigint
, SIGINT
, auth_sighdlr
, NULL
);
318 signal_set(&evsigterm
, SIGTERM
, auth_sighdlr
, NULL
);
319 signal_set(&evsighup
, SIGHUP
, auth_sighdlr
, NULL
);
320 signal_set(&evsigusr1
, SIGUSR1
, auth_sighdlr
, NULL
);
321 signal(SIGPIPE
, SIG_IGN
);
323 signal_add(&evsigint
, NULL
);
324 signal_add(&evsigterm
, NULL
);
325 signal_add(&evsighup
, NULL
);
326 signal_add(&evsigusr1
, NULL
);
328 imsg_init(&iev
.ibuf
, GOTD_FILENO_MSG_PIPE
);
329 iev
.handler
= auth_dispatch
;
330 iev
.events
= EV_READ
;
331 iev
.handler_arg
= NULL
;
332 event_set(&iev
.ev
, iev
.ibuf
.fd
, EV_READ
, auth_dispatch
, &iev
);
333 if (event_add(&iev
.ev
, NULL
) == -1)
344 log_debug("%s: shutting down", gotd_auth
.title
);