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"
45 static struct gotd_auth
{
48 struct gotd_repo
*repo
;
51 static void auth_shutdown(void);
54 auth_sighdlr(int sig
, short event
, void *arg
)
57 * Normal signal handler rules don't apply because libevent
72 fatalx("unexpected signal");
77 uidcheck(const char *s
, uid_t desired
)
81 if (gotd_parseuid(s
, &uid
) != 0)
89 parsegid(const char *s
, gid_t
*gid
)
94 if ((gr
= getgrnam(s
)) != NULL
) {
100 *gid
= strtonum(s
, 0, GID_MAX
- 1, &errstr
);
107 match_identifier(const char *identifier
, gid_t
*groups
, int ngroups
,
108 uid_t euid
, gid_t egid
)
112 if (identifier
[0] == ':') {
114 if (parsegid(identifier
+ 1, &rgid
) == -1)
118 for (i
= 0; i
< ngroups
; i
++) {
119 if (rgid
== groups
[i
])
124 } else if (uidcheck(identifier
, euid
) != 0)
130 static const struct got_error
*
131 auth_check(struct gotd_access_rule_list
*rules
, const char *repo_name
,
132 uid_t euid
, gid_t egid
, int required_auth
)
134 struct gotd_access_rule
*rule
;
135 enum gotd_access access
= GOTD_ACCESS_DENIED
;
137 gid_t groups
[NGROUPS_MAX
];
138 int ngroups
= NGROUPS_MAX
;
143 return got_error_from_errno("getpwuid");
145 return got_error_set_errno(EACCES
, repo_name
);
148 if (getgrouplist(pw
->pw_name
, pw
->pw_gid
, groups
, &ngroups
) == -1)
149 log_warnx("group membership list truncated");
151 STAILQ_FOREACH(rule
, rules
, entry
) {
152 if (!match_identifier(rule
->identifier
, groups
, ngroups
,
156 access
= rule
->access
;
157 if (rule
->access
== GOTD_ACCESS_PERMITTED
&&
158 (rule
->authorization
& required_auth
) != required_auth
)
159 access
= GOTD_ACCESS_DENIED
;
162 if (access
== GOTD_ACCESS_DENIED
)
163 return got_error_set_errno(EACCES
, repo_name
);
165 if (access
== GOTD_ACCESS_PERMITTED
)
168 /* should not happen, this would be a bug */
169 return got_error_msg(GOT_ERR_NOT_IMPL
, "bad access rule");
172 static const struct got_error
*
173 recv_authreq(struct imsg
*imsg
, struct gotd_imsgev
*iev
)
175 const struct got_error
*err
;
176 struct imsgbuf
*ibuf
= &iev
->ibuf
;
177 struct gotd_imsg_auth iauth
;
182 log_debug("authentication request received");
184 datalen
= imsg
->hdr
.len
- IMSG_HEADER_SIZE
;
185 if (datalen
!= sizeof(iauth
))
186 return got_error(GOT_ERR_PRIVSEP_LEN
);
188 memcpy(&iauth
, imsg
->data
, datalen
);
191 return got_error(GOT_ERR_PRIVSEP_NO_FD
);
193 if (getpeereid(imsg
->fd
, &euid
, &egid
) == -1)
194 return got_error_from_errno("getpeerid");
196 if (iauth
.euid
!= euid
)
197 return got_error(GOT_ERR_UID
);
198 if (iauth
.egid
!= egid
)
199 return got_error(GOT_ERR_GID
);
201 log_debug("authenticating uid %d gid %d", euid
, egid
);
203 err
= auth_check(&gotd_auth
.repo
->rules
, gotd_auth
.repo
->name
,
204 iauth
.euid
, iauth
.egid
, iauth
.required_auth
);
206 gotd_imsg_send_error(ibuf
, PROC_AUTH
, iauth
.client_id
, err
);
210 if (gotd_imsg_compose_event(iev
, GOTD_IMSG_ACCESS_GRANTED
,
211 PROC_AUTH
, -1, NULL
, 0) == -1)
212 return got_error_from_errno("imsg compose ACCESS_GRANTED");
218 auth_dispatch(int fd
, short event
, void *arg
)
220 const struct got_error
*err
= NULL
;
221 struct gotd_imsgev
*iev
= arg
;
222 struct imsgbuf
*ibuf
= &iev
->ibuf
;
227 if (event
& EV_READ
) {
228 if ((n
= imsg_read(ibuf
)) == -1 && errno
!= EAGAIN
)
229 fatal("imsg_read error");
230 if (n
== 0) /* Connection closed. */
234 if (event
& EV_WRITE
) {
235 n
= msgbuf_write(&ibuf
->w
);
236 if (n
== -1 && errno
!= EAGAIN
)
237 fatal("msgbuf_write");
238 if (n
== 0) /* Connection closed. */
243 if ((n
= imsg_get(ibuf
, &imsg
)) == -1)
244 fatal("%s: imsg_get", __func__
);
245 if (n
== 0) /* No more messages. */
248 switch (imsg
.hdr
.type
) {
249 case GOTD_IMSG_AUTHENTICATE
:
250 err
= recv_authreq(&imsg
, iev
);
252 log_warnx("%s", err
->msg
);
255 log_debug("unexpected imsg %d", imsg
.hdr
.type
);
263 gotd_imsg_event_add(iev
);
265 /* This pipe is dead. Remove its event handler */
267 event_loopexit(NULL
);
272 auth_main(const char *title
, struct gotd_repolist
*repos
,
273 const char *repo_path
)
275 struct gotd_repo
*repo
= NULL
;
276 struct gotd_imsgev iev
;
277 struct event evsigint
, evsigterm
, evsighup
, evsigusr1
;
279 gotd_auth
.title
= title
;
280 gotd_auth
.pid
= getpid();
281 TAILQ_FOREACH(repo
, repos
, entry
) {
282 if (got_path_cmp(repo
->path
, repo_path
,
283 strlen(repo
->path
), strlen(repo_path
)) == 0)
287 fatalx("repository %s not found in config", repo_path
);
288 gotd_auth
.repo
= repo
;
290 signal_set(&evsigint
, SIGINT
, auth_sighdlr
, NULL
);
291 signal_set(&evsigterm
, SIGTERM
, auth_sighdlr
, NULL
);
292 signal_set(&evsighup
, SIGHUP
, auth_sighdlr
, NULL
);
293 signal_set(&evsigusr1
, SIGUSR1
, auth_sighdlr
, NULL
);
294 signal(SIGPIPE
, SIG_IGN
);
296 signal_add(&evsigint
, NULL
);
297 signal_add(&evsigterm
, NULL
);
298 signal_add(&evsighup
, NULL
);
299 signal_add(&evsigusr1
, NULL
);
301 imsg_init(&iev
.ibuf
, GOTD_FILENO_MSG_PIPE
);
302 iev
.handler
= auth_dispatch
;
303 iev
.events
= EV_READ
;
304 iev
.handler_arg
= NULL
;
305 event_set(&iev
.ev
, iev
.ibuf
.fd
, EV_READ
, auth_dispatch
, &iev
);
306 if (event_add(&iev
.ev
, NULL
) == -1)
317 log_debug("shutting down");