diff --git a/.gitignore b/.gitignore index 8d285f1..a5224e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ doas +parse.c version.h *.a @@ -8,3 +9,6 @@ version.h *.swp *.swo + +config.mk +config.h diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 0000000..2eef88e --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,39 @@ +PROG= doas +MAN= doas.1 doas.conf.5 + +SRCS= parse.y doas.c env.c + +include config.mk + +override CFLAGS:=-I. -Ilibopenbsd -O2 -Wall -Wextra ${OS_CFLAGS} ${CFLAGS} + +all: ${PROG} + +OBJS:= ${SRCS:.y=.c} +OBJS:= ${OBJS:.c=.o} + +${PROG}: ${OBJS} + ${CC} ${CFLAGS} $^ -o $@ ${LDFLAGS} ${LDLIBS} + +install: ${PROG} ${MAN} + mkdir -p -m 0755 ${DESTDIR}${BINDIR} + mkdir -p -m 0755 ${DESTDIR}${MANDIR}/man1 + mkdir -p -m 0755 ${DESTDIR}${MANDIR}/man5 + cp -f ${PROG} ${DESTDIR}${BINDIR} + chown ${BINOWN}:${BINGRP} ${DESTDIR}${BINDIR}/${PROG} + chmod ${BINMODE} ${DESTDIR}${BINDIR}/${PROG} + cp -f doas.1 ${DESTDIR}${MANDIR}/man1 + cp -f doas.conf.5 ${DESTDIR}${MANDIR}/man5 + +uninstall: + rm -f ${DESTDIR}${BINDIR}/${PROG} + rm -f ${DESTDIR}${PAMDIR}/doas + rm -f ${DESTDIR}${MANDIR}/man1/doas.1 + rm -f ${DESTDIR}${MANDIR}/man5/doas.conf.5 + +clean: + rm -f ${PROG} ${OBJS} ${OBJS:.o=.d} parse.c + +-include ${OBJS:.o=.d} + +.PHONY: all clean install uninstall diff --git a/LICENSE b/LICENSE index 0c16551..8dc155d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,19 @@ -portions copyright (c) 2015 Nathan Holstein -portions copyright (c) 2015 Ted Unangst +Copyright (c) 2015 Ted Unangst +Copyright (c) 2015 Nathan Holstein +Copyright (c) 2016 Duncan Overbruck + +Permission to use, copy, modify, and distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -To the best of my knowledge, everything is released under the BSD license. Additional bits copyright of their respective authors, see individual files for details. diff --git a/Makefile b/Makefile deleted file mode 100644 index 9e1dbf3..0000000 --- a/Makefile +++ /dev/null @@ -1,22 +0,0 @@ -# $OpenBSD: Makefile,v 1.9 2014/01/13 01:41:00 tedu Exp $ - -SRCS= parse.y doas.c - -PROG= doas -MAN= doas.1 doas.conf.5 - -BINOWN= root -BINGRP= wheel -BINMODE=4511 - -CFLAGS+= -I${CURDIR} -COPTS+= -Wall -Wextra -Werror -pedantic -std=c11 -LDFLAGS+= -lpam - -include bsd.prog.mk - -doas.o: version.h - -/etc/pam.d/doas: pam.d__doas - cp $< $@ -install: /etc/pam.d/doas diff --git a/README.md b/README.md index 3b54f18..3d532a2 100644 --- a/README.md +++ b/README.md @@ -1,55 +1,64 @@ -![sandwich](https://cloud.githubusercontent.com/assets/13654546/9128676/a583cd0a-3c9a-11e5-9b4f-e03ab0ba37d7.png) - -Apologies to [Randall Monroe](http://www.xkcd.org/149/). - # OpenDoas: a portable version of OpenBSD's `doas` command -`doas` is a minimal replacement for the venerable `sudo`. It was +[`doas`](https://en.wikipedia.org/wiki/Doas) is a minimal replacement for the venerable `sudo`. It was initially [written by Ted Unangst](http://www.tedunangst.com/flak/post/doas) of the OpenBSD project to provide 95% of the features of `sudo` with a fraction of the codebase. -This is still a work in progress! Please do not deploy yet in a critical -environment! Of note, `doas` semantics may yet change, and I haven't -performed even a trivial security audit of my additions. +## Building and Installation Warnings -## Building and installing +There are a few steps you have to carefully consider before building and installing +OpenDoas: -Building `doas` should be just a simple `make` away. +* There are fewer eyes on random `doas` ports, just because `sudo` had a vulnerability + does not mean random doas ports are more secure if they are not reviewed + or [PAM](https://en.wikipedia.org/wiki/Pluggable_authentication_module) is configured incorrectly. + * If you want to use PAM; You have to [configure PAM](#pam-configuration) + and failing to do so correctly might leave a big open door. -The included makefile also has an installation target. Installation -requires root access to properly set the executable permissions. You'll -also need to install a `doas.conf` file: +* Use the `configure` script. +* Use the default make target. +* If you really want to install a setuid binary that depends on + PAM being correctly configured, use the `make install` target + to install the software. -``` -make && sudo make install -echo "permit :admin" | sudo tee /etc/doas.conf -``` +## About the OpenDoas Port -Oh the irony, using `sudo` to install `doas`! - -## About the port +This is not an official port/project from OpenBSD! As much as possible I've attempted to stick to `doas` as tedu desired it. As things stand it's essentially just code lifted from OpenBSD with -PAM based authentication glommed on to it. +PAM or shadow based authentication glommed on to it. + +Compatibility functions in libopenbsd come from OpenBSD directly +(`strtonum.c`, `reallocarray.c`, `strlcpy.c`, `strlcat.c`), +from openssh (`readpassphrase.c`) or from sudo (`closefrom.c`). -I've used cvsync and git-cvsimport to retain the history of the core -source files. I may choose to go back and do the same with some of the -compatibility functions (such as reallocarray.c). +The PAM and shadow authentication code does not come from the OpenBSD project. -I have found it necessary to make some fixes to the codebase. One was -a segfault due to differences in yacc/bison, others were just minor -fixes to warnings. Once this appears stable, I may try to upstream some -of these. +### PAM Configuration -Currently, this is only tested on MacOSX 10.10 with Clang. My next goal -is support for Fedora Linux as well. Contributions gladly accepted. ;-) +I will not ship PAM configuration files, they are distribution specific and +its simply not safe or productive to ship and install those files. -## Copyright +If you want to use OpenDoas on your system and there is no package that +ships with a working PAM configuration file, then you have to write and +test it yourself. -All code from OpenBSD is licensed under the BSD license, please see -individual files for details as the specific text varies from file to +A good starting point is probably the distribution maintained `/etc/pam.d/sudo` file. -All code I've written is licensed with the 2-clause BSD. +### Persist/Timestamp/Timeout + +The persist feature is disabled by default and can be enabled with the +`--with-timestamp` configure flag. + +This feature is new and potentially dangerous, in the original `doas`, a kernel API +is used to set and clear timeouts. This API is OpenBSD specific and no similar API +is available on other operating systems. + +As a workaround, the persist feature is implemented using timestamp files +similar to `sudo`. + +See the comment block in `timestamp.c` for an in-depth description on how +timestamps are created and checked to be as safe as possible. diff --git a/bsd.prog.mk b/bsd.prog.mk deleted file mode 100644 index 80d3231..0000000 --- a/bsd.prog.mk +++ /dev/null @@ -1,57 +0,0 @@ -# Copyright 2015 Nathan Holstein - -BINDIR?=/usr/bin -MANDIR?=/usr/share/man - -default: ${PROG} - -OPENBSD:=reallocarray.c strtonum.c execvpe.c setresuid.c \ - auth_userokay.c setusercontext.c explicit_bzero.c -OPENBSD:=$(addprefix libopenbsd/,${OPENBSD:.c=.o}) -libopenbsd.a: ${OPENBSD} - ${AR} -r $@ $? - -CFLAGS:=${CFLAGS} -I${CURDIR}/libopenbsd ${COPTS} -MD -MP - -OBJS:=${SRCS:.y=.c} -OBJS:=${OBJS:.c=.o} - -${PROG}: ${OBJS} libopenbsd.a - ${CC} ${CFLAGS} ${LDFLAGS} $^ -o $@ - -.%.chmod: % - cp $< $@ - chmod ${BINMODE} $@ - chown ${BINOWN}:${BINGRP} $@ - -${BINDIR}: - mkdir -pm 0755 $@ - -${BINDIR}/${PROG}: .${PROG}.chmod ${BINDIR} - mv $< $@ - -VERSION:=\#define VERSION "$(shell git describe --dirty --tags --long --always)" -OLDVERSION:=$(shell [ -f version.h ] && cat version.h) -version.h: ; @echo '$(VERSION)' > $@ -ifneq ($(VERSION),$(OLDVERSION)) -.PHONY: version.h -endif - -MAN:=$(join $(addprefix ${MANDIR}/man,$(patsubst .%,%/,$(suffix ${MAN}))),${MAN}) -$(foreach M,${MAN},$(eval $M: $(notdir $M); cp $$< $$@)) - -install: ${BINDIR}/${PROG} ${MAN} - -clean: - rm -f version.h - rm -f libopenbsd.a - rm -f ${OPENBSD} - rm -f ${OPENBSD:.o=.d} - rm -f ${OBJS} - rm -f ${OBJS:.o=.d} - rm -f ${PROG} - --include ${objs:.o=.d} ${OPENBSD:.o=.d} - -.PHONY: default clean install man -.INTERMEDIATE: .${PROG}.chmod diff --git a/configure b/configure new file mode 100755 index 0000000..1f92f01 --- /dev/null +++ b/configure @@ -0,0 +1,561 @@ +#!/bin/sh + +die() { + printf "$1\n" >&2 + exit 1 +} + +usage() { + cat <$CONFIG_H +#ifndef CONFIG_H +#define CONFIG_H + +! + +if [ -z "$BUILD" ]; then + BUILD="$(uname -m)-unknown-$(uname -s | tr '[:upper:]' '[:lower:]')" +fi +if [ -z "$HOST" ]; then + [ -z "$TARGET" ] && TARGET=$BUILD + HOST=$TARGET +fi +if [ -z "$TARGET" ]; then + [ -z "$HOST" ] && HOST=$BUILD + TARGET=$HOST +fi + +if [ -z "$OS" ]; then + # Derive OS from cpu-manufacturer-os-kernel + CPU=${TARGET%%-*} + REST=${TARGET#*-} + MANU=${REST%%-*} + REST=${REST#*-} + OS=${REST%%-*} + REST=${REST#*-} + KERNEL=${REST%%-*} +fi + +OS_CFLAGS="-D__${OS}__" + +case "$OS" in + linux) + printf 'Setting UID_MAX\t\t\t\t%d.\n' "$UID_MAX" >&2 + printf '#define UID_MAX %s\n' "$UID_MAX" >>$CONFIG_H + printf 'Setting GID_MAX\t\t\t\t%d.\n' "$GID_MAX" >&2 + printf '#define GID_MAX %s\n' "$GID_MAX" >>$CONFIG_H + OS_CFLAGS="$OS_CFLAGS -D_DEFAULT_SOURCE -D_GNU_SOURCE" + ;; + netbsd) + OS_CFLAGS="$OS_CFLAGS -D_OPENBSD_SOURCE" + printf 'LDLIBS += -lutil\n' >>$CONFIG_MK + : ${BINGRP:=wheel} + ;; + freebsd) + printf 'LDLIBS += -lutil\n' >>$CONFIG_MK + : ${BINGRP:=wheel} + ;; + darwin) + : ${BINGRP:=wheel} + ;; +esac + +: ${PREFIX:=/usr/local} +: ${EPREFIX:=${PREFIX}} +: ${BINDIR:=${PREFIX}/bin} +: ${SHAREDIR:=${PREFIX}/share} +: ${MANDIR:=${SHAREDIR}/man} +: ${SYSCONFDIR:=/etc} +: ${BINMODE:=4755} +: ${BINOWN:=root} +: ${BINGRP:=root} + +cat <>$CONFIG_MK +PREFIX ?= ${PREFIX} +EPREFIX ?= ${EPREFIX} +BINDIR ?= ${BINDIR} +SHAREDIR ?= ${SHAREDIR} +MANDIR ?= ${MANDIR} +SYSCONFDIR?= ${SYSCONFDIR} +BINMODE ?= ${BINMODE} +BINOWN ?= ${BINOWN} +BINGRP ?= ${BINGRP} +EOF + +[ -n "$OS_CFLAGS" ] && \ + printf 'OS_CFLAGS += %s\n' "$OS_CFLAGS" >>$CONFIG_MK + +[ -n "$DEBUG" ] && \ + printf 'CFLAGS += -O0 -g\n' >>$CONFIG_MK + +[ -n "$BUILD_STATIC" ] && \ + printf 'CFLAGS += -static\n' >>$CONFIG_MK + +# Add CPPFLAGS/CFLAGS/LDFLAGS/LDLIBS to CC for testing features +XCC="${CC:=cc} $CFLAGS $OS_CFLAGS $CPPFLAGS $LDFLAGS $LDLIBS" +# Make sure to disable --as-needed for CC tests. + +case "$OS" in + darwin) ;; + *) XCC="$XCC -Wl,--no-as-needed" ;; +esac + +check_func() { + func="$1"; src="$2"; shift 2 + printf 'Checking for %-14s\t\t' "$func ..." >&2 + printf '%s\n' "$src" >"_$func.c" + $XCC "_$func.c" -o "_$func" 2>/dev/null + ret=$? + rm -f "_$func.c" "_$func" + upperfunc="$(printf '%s\n' "$func" | tr '[[:lower:]]' '[[:upper:]]')" + if [ $ret -eq 0 ]; then + printf 'yes.\n' >&2 + printf '#define HAVE_%s\n' "$upperfunc" >>$CONFIG_H + return 0 + else + printf '/* #define HAVE_%s */\n' "$upperfunc" >>$CONFIG_H + printf 'no.\n' >&2 + return 1 + fi +} + +authmethod() { + # + # Check for pam_appl.h. + # + src=' +#include +int main(void) { + return 0; +}' + [ -z "$WITHOUT_PAM" ] && check_func "pam_appl_h" "$src" && { + printf 'SRCS += pam.c\n' >>$CONFIG_MK + printf 'LDLIBS += -lpam\n' >>$CONFIG_MK + printf '#define USE_PAM\n' >>$CONFIG_H + printf 'pam\n' + return 0 + } + + # + # Check for shadow.h. + # + src=' +#include +int main(void) { + return 0; +}' + [ -z "$WITHOUT_SHADOW" ] && check_func "shadow_h" "$src" && { + printf 'SRCS += shadow.c\n' >>$CONFIG_MK + printf 'LDLIBS += -lcrypt\n' >>$CONFIG_MK + printf '#define USE_SHADOW\n' >>$CONFIG_H + printf 'shadow\n' + return 0 + } + + return 1 +} + +persistmethod() { + [ -z "$WITHOUT_TIMESTAMP" ] && { + printf '#define USE_TIMESTAMP\n' >>$CONFIG_H + printf 'SRCS += timestamp.c\n' >>$CONFIG_MK + printf 'timestamp\n' + return 0 + } + return 1 +} + +# +# Check for explicit_bzero(). +# +src=' +#include +int main(void) { + explicit_bzero(NULL, 0); + return 0; +}' +check_func "explicit_bzero" "$src" || { + printf 'SRCS += libopenbsd/explicit_bzero.c\n' >>$CONFIG_MK +} + +# +# Check for strlcat(). +# +src=' +#include +int main(void) { + const char s1[] = "foo"; + char s2[10]; + strlcat(s2, s1, sizeof(s2)); + return 0; +}' +check_func "strlcat" "$src" || { + printf 'SRCS += libopenbsd/strlcat.c\n' >>$CONFIG_MK +} + +# +# Check for strlcpy(). +# +src=' +#include +int main(void) { + const char s1[] = "foo"; + char s2[10]; + strlcpy(s2, s1, sizeof(s2)); + return 0; +}' +check_func "strlcpy" "$src" || { + printf 'SRCS += libopenbsd/strlcpy.c\n' >>$CONFIG_MK +} + +# +# Check for errc(). +# +src=' +#include +int main(void) { + errc(0, 0, ""); + return 0; +}' +check_func "errc" "$src" || { + printf 'SRCS += libopenbsd/errc.c\n' >>$CONFIG_MK +} + +# +# Check for verrc(). +# +src=' +#include +#include +int main(void) { + verrc(0, 0, "x", NULL); + return 0; +}' +check_func "verrc" "$src" || { + printf 'SRCS += libopenbsd/verrc.c\n' >>$CONFIG_MK +} + +# +# Check for setprogname(). +# +src=' +#include +int main(void) { + setprogname(""); + return 0; +}' +check_func "setprogname" "$src" || { + printf 'SRCS += libopenbsd/progname.c\n' >>$CONFIG_MK +} + +# +# Check for readpassphrase(). +# +src=' +#include +int main(void) { + char buf[12]; + readpassphrase("", buf, sizeof(buf), 0); + return 0; +}' +check_func "readpassphrase" "$src" || { + printf 'SRCS += libopenbsd/readpassphrase.c\n' >>$CONFIG_MK +} + +# +# Check for strtonum(). +# +src=' +#include +int main(void) { + const char *errstr; + strtonum("", 1, 64, &errstr); + return 0; +}' +check_func "strtonum" "$src" || { + printf 'SRCS += libopenbsd/strtonum.c\n' >>$CONFIG_MK +} + +# +# Check for reallocarray(). +# +src=' +#include +int main(void) { + reallocarray(NULL, 0, 0); + return 0; +}' +check_func "reallocarray" "$src" || { + printf 'SRCS += libopenbsd/reallocarray.c\n' >>$CONFIG_MK +} + +# +# Check for execvpe(). +# +src=' +#include +int main(void) { + const char *p = { "", NULL }; + execvpe("", p, p); + return 0; +}' +check_func "execvpe" "$src" || { + printf 'SRCS += libopenbsd/execvpe.c\n' >>$CONFIG_MK +} + +# +# Check for setresuid(). +# +src=' +#include +int main(void) { + setresuid(0, 0, 0); + return 0; +}' +check_func "setresuid" "$src" +have_setresuid=$? + +# +# Check for setresgid(). +# +src=' +#include +int main(void) { + setresgid(0, 0, 0); + return 0; +}' +check_func "setresgid" "$src" +have_setresgid=$? + +if [ $have_setresuid -eq 1 -o $have_setresgid -eq 1 ]; then + printf 'SRCS += libopenbsd/bsd-setres_id.c\n' >>$CONFIG_MK +fi + +# +# Check for setreuid(). +# +src=' +#include +int main(void) { + setreuid(0, 0); + return 0; +}' +check_func "setreuid" "$src" + + +# +# Check for setregid(). +# +src=' +#include +int main(void) { + setregid(0, 0); + return 0; +}' +check_func "setregid" "$src" + +# +# Check for closefrom(). +# +src=' +#include +int main(void) { + closefrom(0); + return 0; +}' +check_func "closefrom" "$src" || { + printf 'SRCS += libopenbsd/closefrom.c\n' >>$CONFIG_MK +} + +# +# Check for sysconf(). +# +src=' +#include +int main(void) { + (void)sysconf(0); + return 0; +}' +check_func "sysconf" "$src" + +# +# Check for dirfd(). +# +src=' +#include +int main(void) { + (void)dirfd(0); + return 0; +}' +check_func "dirfd" "$src" + +# +# Check for fcntl.h. +# +src=' +#include +int main(void) { + return 0; +}' +check_func "fcntl_h" "$src" + +# +# Check for F_CLOSEM. +# +src=' +#include +#ifndef F_CLOSEM +#error no F_CLOSEM +#endif +int main(void) { + return 0; +}' +check_func "F_CLOSEM" "$src" + +# +# Check for dirent.h. +# +src=' +#include +int main(void) { + return 0; +}' +check_func "dirent_h" "$src" + +# +# Check for sys/ndir.h. +# +src=' +#include +int main(void) { + return 0; +}' +check_func "sys_ndir_h" "$src" + +# +# Check for sys/dir.h. +# +src=' +#include +int main(void) { + return 0; +}' +check_func "sys_dir_h" "$src" + +# +# Check for ndir.h. +# +src=' +#include +int main(void) { + return 0; +}' +check_func "ndir_h" "$src" + +# +# Check for login_cap.h. +# +src=' +#include +#include +int main(void) { + return 0; +}' +check_func "login_cap_h" "$src" + +# +# +# +src=' +#include +int main(void){return 0;} +__attribute__((__unused__)) static void foo(void){return;} +' +check_func "__attribute__" "$src" || { + printf 'OS_CFLAGS += -DNO_ATTRIBUTE_ON_RETURN_TYPE=1\n' >>$CONFIG_MK +} + +auth=$(authmethod) +if [ $? -eq 0 ]; then + printf 'Using auth method\t\t\t%s.\n' "$auth" >&2 +else + printf 'Error auth method\t\t\n' >&2 + exit 1 +fi + +persist=$(persistmethod) +if [ $? -eq 0 ]; then + printf 'Using persist method\t\t\t%s.\n' "$persist" >&2 +else + printf 'Using persist method\t\t\tnone.\n' >&2 +fi + +printf '#define DOAS_CONF "%s/doas.conf"\n' "${SYSCONFDIR}" >>$CONFIG_H + +printf '\n#endif /* CONFIG_H */\n' >>$CONFIG_H diff --git a/doas.1 b/doas.1 index dd98081..a91705e 100644 --- a/doas.1 +++ b/doas.1 @@ -1,4 +1,4 @@ -.\" $OpenBSD: doas.1,v 1.13 2015/07/26 23:00:15 tedu Exp $ +.\" $OpenBSD: doas.1,v 1.25 2021/01/16 09:18:41 martijn Exp $ .\" .\"Copyright (c) 2015 Ted Unangst .\" @@ -13,7 +13,7 @@ .\"WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\"ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\"OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.Dd $Mdocdate: July 26 2015 $ +.Dd $Mdocdate: January 16 2021 $ .Dt DOAS 1 .Os .Sh NAME @@ -21,7 +21,7 @@ .Nd execute commands as another user .Sh SYNOPSIS .Nm doas -.Op Fl ns +.Op Fl Lns .Op Fl C Ar config .Op Fl u Ar user .Ar command @@ -33,11 +33,37 @@ utility executes the given command as another user. The .Ar command argument is mandatory unless -.Fl C +.Fl C , +.Fl L , or .Fl s is specified. .Pp +The user will be required to authenticate by entering their password, +unless configured otherwise. +.Pp +By default, a new environment is created. +The variables +.Ev HOME , +.Ev LOGNAME , +.Ev PATH , +.Ev SHELL , +and +.Ev USER +and the +.Xr umask 2 +are set to values appropriate for the target user. +.Ev DOAS_USER +is set to the name of the user executing +.Nm . +The variables +.Ev DISPLAY +and +.Ev TERM +are inherited from the current environment. +This behavior may be modified by the config file. +The working directory is not changed. +.Pp The options are as follows: .Bl -tag -width tenletters .It Fl C Ar config @@ -57,11 +83,15 @@ or .Sq deny will be printed on standard output, depending on command matching results. -In either case, no command is executed. +No command is executed. +.It Fl L +Clear any persisted authentications from previous invocations, +then immediately exit. +No command is executed. .It Fl n -Non interactive mode, fail if -.Nm -would prompt for password. +Non interactive mode, fail if the matching rule doesn't have the +.Ic nopass +option. .It Fl s Execute the shell from .Ev SHELL diff --git a/doas.c b/doas.c index 1457925..ac3a42a 100644 --- a/doas.c +++ b/doas.c @@ -1,4 +1,4 @@ -/* $OpenBSD: doas.c,v 1.33 2015/07/30 17:04:33 tedu Exp $ */ +/* $OpenBSD: doas.c,v 1.52 2016/04/28 04:48:56 tedu Exp $ */ /* * Copyright (c) 2015 Ted Unangst * @@ -15,10 +15,16 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" + #include #include +#include #include +#ifdef HAVE_LOGIN_CAP_H +#include +#endif #include #include #include @@ -28,40 +34,19 @@ #include #include #include +#include #include "openbsd.h" - #include "doas.h" -#include "version.h" - -static void __dead -version(void) -{ - fprintf(stderr, "doas: version %s built %s\n", VERSION, __DATE__); - exit(1); -} static void __dead usage(void) { - fprintf(stderr, "usage: doas [-nsv] [-C config] [-u user] command [args]\n"); + fprintf(stderr, "usage: doas [-Lns] [-C config] [-u user]" + " command [args]\n"); exit(1); } -size_t -arraylen(const char **arr) -{ - size_t cnt = 0; - - if (arr) { - while (*arr) { - cnt++; - arr++; - } - } - return cnt; -} - static int parseuid(const char *s, uid_t *uid) { @@ -70,9 +55,11 @@ parseuid(const char *s, uid_t *uid) if ((pw = getpwnam(s)) != NULL) { *uid = pw->pw_uid; + if (*uid == UID_MAX) + return -1; return 0; } - *uid = strtonum(s, 0, UID_MAX, &errstr); + *uid = strtonum(s, 0, UID_MAX - 1, &errstr); if (errstr) return -1; return 0; @@ -98,9 +85,11 @@ parsegid(const char *s, gid_t *gid) if ((gr = getgrnam(s)) != NULL) { *gid = gr->gr_gid; + if (*gid == GID_MAX) + return -1; return 0; } - *gid = strtonum(s, 0, GID_MAX, &errstr); + *gid = strtonum(s, 0, GID_MAX - 1, &errstr); if (errstr) return -1; return 0; @@ -147,10 +136,10 @@ match(uid_t uid, gid_t *groups, int ngroups, uid_t target, const char *cmd, } static int -permit(uid_t uid, gid_t *groups, int ngroups, struct rule **lastr, +permit(uid_t uid, gid_t *groups, int ngroups, const struct rule **lastr, uid_t target, const char *cmd, const char **cmdargs) { - int i; + size_t i; *lastr = NULL; for (i = 0; i < nrules; i++) { @@ -171,10 +160,9 @@ parseconfig(const char *filename, int checkperms) struct stat sb; yyfp = fopen(filename, "r"); - if (!yyfp) { - warn("could not open config file"); - exit(1); - } + if (!yyfp) + err(1, checkperms ? "doas is not enabled, %s" : + "could not open config file %s", filename); if (checkperms) { if (fstat(fileno(yyfp), &sb) != 0) @@ -191,119 +179,15 @@ parseconfig(const char *filename, int checkperms) exit(1); } -/* - * Copy the environment variables in safeset from oldenvp to envp. - */ -static int -copyenvhelper(const char **oldenvp, const char **safeset, size_t nsafe, - char **envp, int ei) -{ - size_t i; - - for (i = 0; i < nsafe; i++) { - const char **oe = oldenvp; - while (*oe) { - size_t len = strlen(safeset[i]); - if (strncmp(*oe, safeset[i], len) == 0 && - (*oe)[len] == '=') { - if (!(envp[ei++] = strdup(*oe))) - err(1, "strdup"); - break; - } - oe++; - } - } - return ei; -} - -static char ** -copyenv(const char **oldenvp, struct rule *rule) -{ - const char *safeset[] = { - "DISPLAY", "HOME", "LOGNAME", "MAIL", - "PATH", "TERM", "USER", "USERNAME", - NULL - }; - const char *badset[] = { - "ENV", - NULL - }; - char **envp; - const char **extra; - int ei; - size_t nsafe, nbad; - size_t nextras = 0; - - /* if there was no envvar whitelist, pass all except badset ones */ - nbad = arraylen(badset); - if ((rule->options & KEEPENV) && !rule->envlist) { - size_t iold, inew; - size_t oldlen = arraylen(oldenvp); - envp = reallocarray(NULL, oldlen + 1, sizeof(char *)); - if (!envp) - err(1, "reallocarray"); - for (inew = iold = 0; iold < oldlen; iold++) { - size_t ibad; - for (ibad = 0; ibad < nbad; ibad++) { - size_t len = strlen(badset[ibad]); - if (strncmp(oldenvp[iold], badset[ibad], len) == 0 && - oldenvp[iold][len] == '=') { - break; - } - } - if (ibad == nbad) { - if (!(envp[inew] = strdup(oldenvp[iold]))) - err(1, "strdup"); - inew++; - } - } - envp[inew] = NULL; - return envp; - } - - nsafe = arraylen(safeset); - if ((extra = rule->envlist)) { - size_t isafe; - nextras = arraylen(extra); - for (isafe = 0; isafe < nsafe; isafe++) { - size_t iextras; - for (iextras = 0; iextras < nextras; iextras++) { - if (strcmp(extra[iextras], safeset[isafe]) == 0) { - nextras--; - extra[iextras] = extra[nextras]; - extra[nextras] = NULL; - iextras--; - } - } - } - } - - envp = reallocarray(NULL, nsafe + nextras + 1, sizeof(char *)); - if (!envp) - err(1, "can't allocate new environment"); - - ei = 0; - ei = copyenvhelper(oldenvp, safeset, nsafe, envp, ei); - ei = copyenvhelper(oldenvp, rule->envlist, nextras, envp, ei); - envp[ei] = NULL; - - return envp; -} - -static void __dead -fail(void) -{ - fprintf(stderr, "Permission denied\n"); - exit(1); -} - static void __dead checkconfig(const char *confpath, int argc, char **argv, uid_t uid, gid_t *groups, int ngroups, uid_t target) { - struct rule *rule; + const struct rule *rule; + + if (setresuid(uid, uid, uid) != 0) + err(1, "setresuid"); - setresuid(uid, uid, uid); parseconfig(confpath, 0); if (!argc) exit(0); @@ -319,34 +203,80 @@ checkconfig(const char *confpath, int argc, char **argv, } int -main(int argc, char **argv, char **envp) +mygetpwuid_r(uid_t uid, struct passwd *pwd, struct passwd **result) +{ + int rv; + char *buf; + static long pwsz = 0; + size_t buflen; + + *result = NULL; + + if (pwsz == 0) + pwsz = sysconf(_SC_GETPW_R_SIZE_MAX); + + buflen = pwsz > 0 ? pwsz : 1024; + + buf = malloc(buflen); + if (buf == NULL) + return errno; + + while ((rv = getpwuid_r(uid, pwd, buf, buflen, result)) == ERANGE) { + size_t newsz; + newsz = buflen * 2; + if (newsz < buflen) + return rv; + buflen = newsz; + buf = realloc(buf, buflen); + if (buf == NULL) + return errno; + } + + return rv; +} + +int +main(int argc, char **argv) { const char *safepath = "/bin:/sbin:/usr/bin:/usr/sbin:" "/usr/local/bin:/usr/local/sbin"; const char *confpath = NULL; char *shargv[] = { NULL, NULL }; char *sh; + const char *p; const char *cmd; char cmdline[LINE_MAX]; - char myname[_PW_NAME_LEN + 1]; - struct passwd *pw; - struct rule *rule; + struct passwd mypwstore, targpwstore; + struct passwd *mypw, *targpw; + const struct rule *rule; uid_t uid; uid_t target = 0; gid_t groups[NGROUPS_MAX + 1]; int ngroups; - int i, ch; + int i, ch, rv; int sflag = 0; int nflag = 0; - int vflag = 0; + char cwdpath[PATH_MAX]; + const char *cwd; + char **envp; + + setprogname("doas"); + + closefrom(STDERR_FILENO + 1); uid = getuid(); - while ((ch = getopt(argc, argv, "C:nsu:v")) != -1) { + while ((ch = getopt(argc, argv, "+C:Lnsu:")) != -1) { switch (ch) { case 'C': confpath = optarg; break; + case 'L': +#if defined(USE_TIMESTAMP) + exit(timestamp_clear() == -1); +#else + exit(0); +#endif case 'u': if (parseuid(optarg, &target) != 0) errx(1, "unknown user"); @@ -357,9 +287,6 @@ main(int argc, char **argv, char **envp) case 's': sflag = 1; break; - case 'v': - vflag = 1; - break; default: usage(); break; @@ -368,20 +295,17 @@ main(int argc, char **argv, char **envp) argv += optind; argc -= optind; - if (vflag) - version(); - if (confpath) { if (sflag) usage(); } else if ((!sflag && !argc) || (sflag && argc)) usage(); - pw = getpwuid(uid); - if (!pw) - err(1, "getpwuid failed"); - if (strlcpy(myname, pw->pw_name, sizeof(myname)) >= sizeof(myname)) - errx(1, "pw_name too long"); + rv = mygetpwuid_r(uid, &mypwstore, &mypw); + if (rv != 0) + err(1, "getpwuid_r failed"); + if (mypw == NULL) + errx(1, "no passwd entry for self"); ngroups = getgroups(NGROUPS_MAX, groups); if (ngroups == -1) err(1, "can't get groups"); @@ -389,9 +313,9 @@ main(int argc, char **argv, char **envp) if (sflag) { sh = getenv("SHELL"); - if (sh == NULL || *sh == '\0') - shargv[0] = pw->pw_shell; - else + if (sh == NULL || *sh == '\0') { + shargv[0] = mypw->pw_shell; + } else shargv[0] = sh; argv = shargv; argc = 1; @@ -403,10 +327,13 @@ main(int argc, char **argv, char **envp) exit(1); /* fail safe */ } - parseconfig("/etc/doas.conf", 1); + if (geteuid()) + errx(1, "not installed setuid"); + + parseconfig(DOAS_CONF, 1); /* cmdline is used only for logging, no need to abort on truncate */ - (void) strlcpy(cmdline, argv[0], sizeof(cmdline)); + (void)strlcpy(cmdline, argv[0], sizeof(cmdline)); for (i = 1; i < argc; i++) { if (strlcat(cmdline, " ", sizeof(cmdline)) >= sizeof(cmdline)) break; @@ -416,35 +343,85 @@ main(int argc, char **argv, char **envp) cmd = argv[0]; if (!permit(uid, groups, ngroups, &rule, target, cmd, - (const char**)argv + 1)) { + (const char **)argv + 1)) { syslog(LOG_AUTHPRIV | LOG_NOTICE, - "failed command for %s: %s", myname, cmdline); - fail(); + "command not permitted for %s: %s", mypw->pw_name, cmdline); + errc(1, EPERM, NULL); } +#if defined(USE_SHADOW) if (!(rule->options & NOPASS)) { if (nflag) - errx(1, "Authorization required"); - if (!auth_userokay(myname, NULL, NULL, NULL)) { - syslog(LOG_AUTHPRIV | LOG_NOTICE, - "failed password for %s", myname); - fail(); - } + errx(1, "Authentication required"); + + shadowauth(mypw->pw_name, rule->options & PERSIST); + } +#elif !defined(USE_PAM) + /* no authentication provider, only allow NOPASS rules */ + (void) nflag; + if (!(rule->options & NOPASS)) + errx(1, "Authentication required"); +#endif + + if ((p = getenv("PATH")) != NULL) + formerpath = strdup(p); + if (formerpath == NULL) + formerpath = ""; + + if (rule->cmd) { + if (setenv("PATH", safepath, 1) == -1) + err(1, "failed to set PATH '%s'", safepath); } - envp = copyenv((const char **)envp, rule); - pw = getpwuid(target); - if (!pw) + rv = mygetpwuid_r(target, &targpwstore, &targpw); + if (rv != 0) + err(1, "getpwuid_r failed"); + if (targpw == NULL) errx(1, "no passwd entry for target"); - if (setusercontext(NULL, pw, target, LOGIN_SETGROUP | + +#if defined(USE_PAM) + pamauth(targpw->pw_name, mypw->pw_name, !nflag, rule->options & NOPASS, + rule->options & PERSIST); +#endif + +#ifdef HAVE_LOGIN_CAP_H + if (setusercontext(NULL, targpw, target, LOGIN_SETGROUP | + LOGIN_SETPATH | LOGIN_SETPRIORITY | LOGIN_SETRESOURCES | LOGIN_SETUMASK | LOGIN_SETUSER) != 0) errx(1, "failed to set user context for target"); - - syslog(LOG_AUTHPRIV | LOG_INFO, "%s ran command as %s: %s", - myname, pw->pw_name, cmdline); +#else + if (setresgid(targpw->pw_gid, targpw->pw_gid, targpw->pw_gid) != 0) + err(1, "setresgid"); + if (initgroups(targpw->pw_name, targpw->pw_gid) != 0) + err(1, "initgroups"); + if (setresuid(target, target, target) != 0) + err(1, "setresuid"); if (setenv("PATH", safepath, 1) == -1) err(1, "failed to set PATH '%s'", safepath); +#endif + + if (getcwd(cwdpath, sizeof(cwdpath)) == NULL) + cwd = "(failed)"; + else + cwd = cwdpath; + + if (!(rule->options & NOLOG)) { + syslog(LOG_AUTHPRIV | LOG_INFO, + "%s ran command %s as %s from %s", + mypw->pw_name, cmdline, targpw->pw_name, cwd); + } + + envp = prepenv(rule, mypw, targpw); + + /* setusercontext set path for the next process, so reset it for us */ + if (rule->cmd) { + if (setenv("PATH", safepath, 1) == -1) + err(1, "failed to set PATH '%s'", safepath); + } else { + if (setenv("PATH", formerpath, 1) == -1) + err(1, "failed to set PATH '%s'", formerpath); + } execvpe(cmd, argv, envp); if (errno == ENOENT) errx(1, "%s: command not found", cmd); diff --git a/doas.conf.5 b/doas.conf.5 index 65d5bef..e98bfbe 100644 --- a/doas.conf.5 +++ b/doas.conf.5 @@ -1,4 +1,4 @@ -.\" $OpenBSD: doas.conf.5,v 1.13 2015/07/27 21:44:11 tedu Exp $ +.\" $OpenBSD: doas.conf.5,v 1.45 2020/10/09 10:24:33 jmc Exp $ .\" .\"Copyright (c) 2015 Ted Unangst .\" @@ -13,7 +13,7 @@ .\"WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN .\"ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF .\"OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -.Dd $Mdocdate: July 27 2015 $ +.Dd $Mdocdate: October 9 2020 $ .Dt DOAS.CONF 5 .Os .Sh NAME @@ -33,7 +33,7 @@ The rules have the following format: .Op Ar options .Ar identity .Op Ic as Ar target -.Op Ic cmd Ar command Op Ic args ... +.Op Ic cmd Ar command Op Ic args No ... .Ed .Pp Rules consist of the following parts: @@ -45,21 +45,28 @@ Options are: .Bl -tag -width keepenv .It Ic nopass The user is not required to enter a password. +.It Ic nolog +Do not log successful command execution to +.Xr syslogd 8 . +.It Ic persist +After the user successfully authenticates, do not ask for a password +again for some time. .It Ic keepenv -The user's environment is maintained. -The default is to reset the environment, except for the variables -.Ev DISPLAY , -.Ev HOME , -.Ev LOGNAME , -.Ev MAIL , -.Ev PATH , -.Ev TERM , -.Ev USER -and -.Ev USERNAME . -.It Ic keepenv { Oo Ar variable ... Oc Ic } -In addition to the variables mentioned above, keep the space-separated -specified variables. +Environment variables other than those listed in +.Xr doas 1 +are retained when creating the environment for the new process. +.It Ic setenv { Oo Ar variable ... Oc Oo Ar variable=value ... Oc Ic } +Keep or set the space-separated specified variables. +Variables may also be removed with a leading +.Sq - +or set using the latter syntax. +If the first character of +.Ar value +is a +.Ql $ +then the value to be set is taken from the existing environment +variable of the indicated name. +This option is processed after the default environment has been created. .El .It Ar identity The username to match. @@ -72,17 +79,20 @@ The default is all users. .It Ic cmd Ar command The command the user is allowed or denied to run. The default is all commands. -Be advised that it's best to specify absolute paths. -.It Ic args ... +Be advised that it is best to specify absolute paths. +If a relative path is specified, only a restricted +.Ev PATH +will be searched. +.It Ic args Op Ar argument ... Arguments to command. -If specified, the command arguments provided by the user -need to match for the command to be successful. -Specifying +The command arguments provided by the user need to match those specified. +The keyword .Ic args -alone means that command should be run without any arguments. +alone means that command must be run without any arguments. .El .Pp The last matching rule determines the action taken. +If no rule matches, the action is denied. .Pp Comments can be put anywhere in the file using a hash mark .Pq Sq # , @@ -101,31 +111,39 @@ escapes the next character, including new line characters, outside comments; as a result, comments may not be extended over multiple lines. .It If quotes or backslashes are used in a word, -it isn't considered a keyword. +it is not considered a keyword. +.El +.Sh FILES +.Bl -tag -width /etc/examples/doas.conf -compact +.It Pa /etc/doas.conf +.Xr doas 1 +configuration file. +.It Pa /etc/examples/doas.conf +Example configuration file. .El .Sh EXAMPLES -The following example permits users in group wsrc to build ports, -wheel to execute commands as any user while keeping the environment +The following example permits user aja to install packages +from a preferred mirror; +group wheel to execute commands as any user while keeping the environment variables -.Ev ENV , -.Ev PS1 , +.Ev PS1 +and +.Ev SSH_AUTH_SOCK and -.Ev SSH_AUTH_SOCK , -and additionally permits tedu to run procmap as root without a password. +unsetting +.Ev ENV ; +permits tedu to run procmap as root without a password; +and additionally permits root to run unrestricted commands as itself +while retaining the original PATH. .Bd -literal -offset indent -# Non-exhaustive list of variables needed to -# build release(8) and ports(7) -permit nopass keepenv { \e - FTPMODE PKG_CACHE PKG_PATH SM_PATH SSH_AUTH_SOCK \e - DESTDIR DISTDIR FETCH_CMD FLAVOR GROUP MAKE MAKECONF \e - MULTI_PACKAGES NOMAN OKAY_FILES OWNER PKG_DBDIR \e - PKG_DESTDIR PKG_TMPDIR PORTSDIR RELEASEDIR SHARED_ONLY \e - SUBPACKAGE WRKOBJDIR SUDO_PORT_V1 } :wsrc -permit nopass keepenv { ENV PS1 SSH_AUTH_SOCK } :wheel +permit persist setenv { PKG_CACHE PKG_PATH } aja cmd pkg_add +permit setenv { -ENV PS1=$DOAS_PS1 SSH_AUTH_SOCK } :wheel permit nopass tedu as root cmd /usr/sbin/procmap +permit nopass keepenv setenv { PATH } root as root .Ed .Sh SEE ALSO -.Xr doas 1 +.Xr doas 1 , +.Xr syslogd 8 .Sh HISTORY The .Nm diff --git a/doas.h b/doas.h index 235df9f..a8aa41b 100644 --- a/doas.h +++ b/doas.h @@ -1,4 +1,19 @@ -/* $OpenBSD: doas.h,v 1.3 2015/07/21 11:04:06 zhuk Exp $ */ +/* $OpenBSD$ */ +/* + * Copyright (c) 2015 Ted Unangst + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ struct rule { int action; @@ -11,13 +26,34 @@ struct rule { }; extern struct rule **rules; -extern int nrules, maxrules; +extern size_t nrules; extern int parse_errors; -size_t arraylen(const char **); +extern const char *formerpath; + +struct passwd; + +char **prepenv(const struct rule *, const struct passwd *, + const struct passwd *); #define PERMIT 1 #define DENY 2 #define NOPASS 0x1 #define KEEPENV 0x2 +#define PERSIST 0x4 +#define NOLOG 0x8 + +#ifdef USE_PAM +void pamauth(const char *, const char *, int, int, int); +#endif + +#ifdef USE_SHADOW +void shadowauth(const char *, int); +#endif + +#ifdef USE_TIMESTAMP +int timestamp_open(int *, int); +int timestamp_set(int, int); +int timestamp_clear(void); +#endif diff --git a/env.c b/env.c new file mode 100644 index 0000000..e2286fc --- /dev/null +++ b/env.c @@ -0,0 +1,238 @@ +/* $OpenBSD$ */ +/* + * Copyright (c) 2016 Ted Unangst + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include "sys-tree.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "openbsd.h" +#include "doas.h" + +const char *formerpath; + +struct envnode { + RB_ENTRY(envnode) node; + const char *key; + const char *value; +}; + +struct env { + RB_HEAD(envtree, envnode) root; + u_int count; +}; + +static void fillenv(struct env *env, const char **envlist); + +static int +envcmp(struct envnode *a, struct envnode *b) +{ + return strcmp(a->key, b->key); +} +RB_GENERATE_STATIC(envtree, envnode, node, envcmp) + +static struct envnode * +createnode(const char *key, const char *value) +{ + struct envnode *node; + + node = malloc(sizeof(*node)); + if (!node) + err(1, NULL); + node->key = strdup(key); + node->value = strdup(value); + if (!node->key || !node->value) + err(1, NULL); + return node; +} + +static void +freenode(struct envnode *node) +{ + free((char *)node->key); + free((char *)node->value); + free(node); +} + +static void +addnode(struct env *env, const char *key, const char *value) +{ + struct envnode *node; + + node = createnode(key, value); + RB_INSERT(envtree, &env->root, node); + env->count++; +} + +static struct env * +createenv(const struct rule *rule, const struct passwd *mypw, + const struct passwd *targpw) +{ + static const char *copyset[] = { + "DISPLAY", "TERM", + NULL + }; + struct env *env; + u_int i; + + env = malloc(sizeof(*env)); + if (!env) + err(1, NULL); + RB_INIT(&env->root); + env->count = 0; + + addnode(env, "DOAS_USER", mypw->pw_name); + addnode(env, "HOME", targpw->pw_dir); + addnode(env, "LOGNAME", targpw->pw_name); + addnode(env, "PATH", getenv("PATH")); + addnode(env, "SHELL", targpw->pw_shell); + addnode(env, "USER", targpw->pw_name); + + fillenv(env, copyset); + + if (rule->options & KEEPENV) { + extern char **environ; + + for (i = 0; environ[i] != NULL; i++) { + struct envnode *node; + const char *e, *eq; + size_t len; + char keybuf[1024]; + + e = environ[i]; + + /* ignore invalid or overlong names */ + if ((eq = strchr(e, '=')) == NULL || eq == e) + continue; + len = eq - e; + if (len > sizeof(keybuf) - 1) + continue; + memcpy(keybuf, e, len); + keybuf[len] = '\0'; + + node = createnode(keybuf, eq + 1); + if (RB_INSERT(envtree, &env->root, node)) { + /* ignore any later duplicates */ + freenode(node); + } else { + env->count++; + } + } + } + + return env; +} + +static char ** +flattenenv(struct env *env) +{ + char **envp; + struct envnode *node; + u_int i; + + envp = reallocarray(NULL, env->count + 1, sizeof(char *)); + if (!envp) + err(1, NULL); + i = 0; + RB_FOREACH(node, envtree, &env->root) { + if (asprintf(&envp[i], "%s=%s", node->key, node->value) == -1) + err(1, NULL); + i++; + } + envp[i] = NULL; + return envp; +} + +static void +fillenv(struct env *env, const char **envlist) +{ + struct envnode *node, key; + const char *e, *eq; + const char *val; + char name[1024]; + u_int i; + size_t len; + + for (i = 0; envlist[i]; i++) { + e = envlist[i]; + + /* parse out env name */ + if ((eq = strchr(e, '=')) == NULL) + len = strlen(e); + else + len = eq - e; + if (len > sizeof(name) - 1) + continue; + memcpy(name, e, len); + name[len] = '\0'; + + /* delete previous copies */ + key.key = name; + if (*name == '-') + key.key = name + 1; + if ((node = RB_FIND(envtree, &env->root, &key))) { + RB_REMOVE(envtree, &env->root, node); + freenode(node); + env->count--; + } + if (*name == '-') + continue; + + /* assign value or inherit from environ */ + if (eq) { + val = eq + 1; + if (*val == '$') { + if (strcmp(val + 1, "PATH") == 0) + val = formerpath; + else + val = getenv(val + 1); + } + } else { + if (strcmp(name, "PATH") == 0) + val = formerpath; + else + val = getenv(name); + } + /* at last, we have something to insert */ + if (val) { + node = createnode(name, val); + RB_INSERT(envtree, &env->root, node); + env->count++; + } + } +} + +char ** +prepenv(const struct rule *rule, const struct passwd *mypw, + const struct passwd *targpw) +{ + struct env *env; + + env = createenv(rule, mypw, targpw); + if (rule->envlist) + fillenv(env, rule->envlist); + + return flattenenv(env); +} diff --git a/libopenbsd/auth_userokay.c b/libopenbsd/auth_userokay.c deleted file mode 100644 index 5565146..0000000 --- a/libopenbsd/auth_userokay.c +++ /dev/null @@ -1,112 +0,0 @@ -/* - * Copyright (c) 2015 Nathan Holstein - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -#include "openbsd.h" - -#define PAM_SERVICE "doas" - -#define __UNUSED __attribute__ ((unused)) - -static char * -pam_prompt(const char *msg, int echo_on, int *pam) -{ - char buf[PAM_MAX_RESP_SIZE]; - int flags = RPP_REQUIRE_TTY | (echo_on ? RPP_ECHO_ON : RPP_ECHO_OFF); - char *ret = readpassphrase(msg, buf, sizeof(buf), flags); - if (!ret) - *pam = PAM_CONV_ERR; - else if (!(ret = strdup(ret))) - *pam = PAM_BUF_ERR; - explicit_bzero(buf, sizeof(buf)); - return ret; -} - -static int -pam_conv(int nmsgs, const struct pam_message **msgs, - struct pam_response **rsps, __UNUSED void *ptr) -{ - struct pam_response *rsp; - int i, style; - int pam = PAM_SUCCESS; - - if (!(rsp = calloc(nmsgs, sizeof(struct pam_response)))) - errx(1, "couldn't malloc pam_response"); - *rsps = rsp; - - for (i = 0; i < nmsgs; i++) { - switch (style = msgs[i]->msg_style) { - case PAM_PROMPT_ECHO_OFF: - case PAM_PROMPT_ECHO_ON: - rsp[i].resp = pam_prompt(msgs[i]->msg, - style == PAM_PROMPT_ECHO_ON, &pam); - break; - - case PAM_ERROR_MSG: - case PAM_TEXT_INFO: - if (fprintf(style == PAM_ERROR_MSG ? stderr : stdout, - "%s\n", msgs[i]->msg) < 0) - pam = PAM_CONV_ERR; - break; - - default: - errx(1, "invalid PAM msg_style %d", style); - } - } - - return PAM_SUCCESS; -} - -int -auth_userokay(char *name, char *style, char *type, char *password) -{ - static const struct pam_conv conv = { - .conv = pam_conv, - .appdata_ptr = NULL, - }; - - int ret, auth; - pam_handle_t *pamh = NULL; - - if (!name) - return 0; - if (style || type || password) - errx(1, "auth_userokay(name, NULL, NULL, NULL)!\n"); - - ret = pam_start(PAM_SERVICE, name, &conv, &pamh); - if (ret != PAM_SUCCESS) - errx(1, "pam_start(\"%s\", \"%s\", ?, ?): failed\n", - PAM_SERVICE, name); - - auth = pam_authenticate(pamh, 0); - - ret = pam_close_session(pamh, 0); - if (ret != PAM_SUCCESS) - errx(1, "pam_close_session(): %s\n", pam_strerror(pamh, ret)); - - return auth == PAM_SUCCESS; -} - diff --git a/libopenbsd/bsd-setres_id.c b/libopenbsd/bsd-setres_id.c new file mode 100644 index 0000000..c244b40 --- /dev/null +++ b/libopenbsd/bsd-setres_id.c @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2012 Darren Tucker (dtucker at zip com au). + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include + +#include +#include +#include +#include + +#if !defined(HAVE_SETRESGID) || defined(BROKEN_SETRESGID) +int +setresgid(gid_t rgid, gid_t egid, gid_t sgid) +{ + int ret = 0; + + if (rgid != sgid) { + errno = ENOSYS; + return -1; + } +#if defined(HAVE_SETREGID) && !defined(BROKEN_SETREGID) + if (setregid(rgid, egid) < 0) { + ret = -1; + } +#else + if (setegid(egid) < 0) { + ret = -1; + } + if (setgid(rgid) < 0) { + ret = -1; + } +#endif + return ret; +} +#endif + +#if !defined(HAVE_SETRESUID) || defined(BROKEN_SETRESUID) +int +setresuid(uid_t ruid, uid_t euid, uid_t suid) +{ + int ret = 0; + + if (ruid != suid) { + errno = ENOSYS; + return -1; + } +#if defined(HAVE_SETREUID) && !defined(BROKEN_SETREUID) + if (setreuid(ruid, euid) < 0) { + ret = -1; + } +#else + +# ifndef SETEUID_BREAKS_SETUID + if (seteuid(euid) < 0) { + ret = -1; + } +# endif + if (setuid(ruid) < 0) { + ret = -1; + } +#endif + return ret; +} +#endif diff --git a/libopenbsd/closefrom.c b/libopenbsd/closefrom.c new file mode 100644 index 0000000..fba73e7 --- /dev/null +++ b/libopenbsd/closefrom.c @@ -0,0 +1,134 @@ +/* + * SPDX-License-Identifier: ISC + * + * Copyright (c) 2004-2005, 2007, 2010, 2012-2015, 2017-2018 + * Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This is an open source non-commercial project. Dear PVS-Studio, please check it. + * PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_PSTAT_GETPROC +# include +#else +# include +#endif + +#include "openbsd.h" + +#ifndef _POSIX_OPEN_MAX +# define _POSIX_OPEN_MAX 20 +#endif + +/* + * Close all file descriptors greater than or equal to lowfd. + * This is the expensive (fallback) method. + */ +static void +closefrom_fallback(int lowfd) +{ + long fd, maxfd; + + /* + * Fall back on sysconf(_SC_OPEN_MAX). We avoid checking + * resource limits since it is possible to open a file descriptor + * and then drop the rlimit such that it is below the open fd. + */ + maxfd = sysconf(_SC_OPEN_MAX); + if (maxfd < 0) + maxfd = _POSIX_OPEN_MAX; + + for (fd = lowfd; fd < maxfd; fd++) { +#ifdef __APPLE__ + /* Avoid potential libdispatch crash when we close its fds. */ + (void) fcntl((int) fd, F_SETFD, FD_CLOEXEC); +#else + (void) close((int) fd); +#endif + } +} + +/* + * Close all file descriptors greater than or equal to lowfd. + * We try the fast way first, falling back on the slow method. + */ +void +closefrom(int lowfd) +{ +#if defined(HAVE_PSTAT_GETPROC) + struct pst_status pstat; +#elif defined(HAVE_DIRFD) + const char *path; + DIR *dirp; +#endif + + /* Try the fast method first, if possible. */ +#if defined(HAVE_FCNTL_CLOSEM) + if (fcntl(lowfd, F_CLOSEM, 0) != -1) + return; +#endif +#if defined(HAVE_PSTAT_GETPROC) + /* + * EOVERFLOW is not a fatal error for the fields we use. + * See the "EOVERFLOW Error" section of pstat_getvminfo(3). + */ + if (pstat_getproc(&pstat, sizeof(pstat), 0, getpid()) != -1 || + errno == EOVERFLOW) { + int fd; + + for (fd = lowfd; fd <= pstat.pst_highestfd; fd++) + (void) close(fd); + return; + } +#elif defined(HAVE_DIRFD) + /* Use /proc/self/fd (or /dev/fd on macOS) if it exists. */ +# ifdef __APPLE__ + path = _PATH_DEV "fd"; +# else + path = "/proc/self/fd"; +# endif + if ((dirp = opendir(path)) != NULL) { + struct dirent *dent; + while ((dent = readdir(dirp)) != NULL) { + const char *errstr; + int fd = strtonum(dent->d_name, lowfd, INT_MAX, &errstr); + if (errstr == NULL && fd != dirfd(dirp)) { +# ifdef __APPLE__ + /* Avoid potential libdispatch crash when we close its fds. */ + (void) fcntl(fd, F_SETFD, FD_CLOEXEC); +# else + (void) close(fd); +# endif + } + } + (void) closedir(dirp); + return; + } +#endif /* HAVE_DIRFD */ + + /* Do things the slow way. */ + closefrom_fallback(lowfd); +} diff --git a/libopenbsd/errc.c b/libopenbsd/errc.c new file mode 100644 index 0000000..cab3e84 --- /dev/null +++ b/libopenbsd/errc.c @@ -0,0 +1,46 @@ +/* $OpenBSD: errc.c,v 1.2 2015/08/31 02:53:57 guenther Exp $ */ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include + +#include "openbsd.h" + +void __dead +errc(int eval, int code, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + verrc(eval, code, fmt, ap); + va_end(ap); +} diff --git a/libopenbsd/execvpe.c b/libopenbsd/execvpe.c index f080148..223daf4 100644 --- a/libopenbsd/execvpe.c +++ b/libopenbsd/execvpe.c @@ -1,4 +1,4 @@ -/* $OpenBSD: exec.c,v 1.20 2013/01/08 02:26:09 deraadt Exp $ */ +/* $OpenBSD: exec.c,v 1.23 2016/03/13 18:34:20 guenther Exp $ */ /*- * Copyright (c) 1991, 1993 * The Regents of the University of California. All rights reserved. @@ -28,6 +28,8 @@ * SUCH DAMAGE. */ +#include "config.h" + #include #include @@ -40,6 +42,8 @@ #include #include +#include "openbsd.h" + int execvpe(const char *name, char *const *argv, char *const *envp) { diff --git a/libopenbsd/explicit_bzero.c b/libopenbsd/explicit_bzero.c index 9a646c6..2758e6c 100644 --- a/libopenbsd/explicit_bzero.c +++ b/libopenbsd/explicit_bzero.c @@ -4,6 +4,8 @@ * Written by Matthew Dempsky. */ +#include "config.h" + #include #define __UNUSED __attribute__ ((unused)) diff --git a/libopenbsd/openbsd.h b/libopenbsd/openbsd.h index 1fa73af..1572b9c 100644 --- a/libopenbsd/openbsd.h +++ b/libopenbsd/openbsd.h @@ -1,41 +1,71 @@ #ifndef _LIB_OPENBSD_H_ #define _LIB_OPENBSD_H_ +#include #include -/* API definitions lifted from OpenBSD src/include */ - -/* bsd_auth.h */ -int auth_userokay(char *, char *, char *, char *); - -/* login_cap.h */ -#define LOGIN_SETGROUP 0x0001 /* Set group */ -#define LOGIN_SETLOGIN 0x0002 /* Set login */ -#define LOGIN_SETPATH 0x0004 /* Set path */ -#define LOGIN_SETPRIORITY 0x0008 /* Set priority */ -#define LOGIN_SETRESOURCES 0x0010 /* Set resource limits */ -#define LOGIN_SETUMASK 0x0020 /* Set umask */ -#define LOGIN_SETUSER 0x0040 /* Set user */ -#define LOGIN_SETENV 0x0080 /* Set environment */ -#define LOGIN_SETALL 0x00ff /* Set all. */ +#ifndef __UNUSED +# define __UNUSED __attribute__ ((unused)) +#endif -typedef struct login_cap login_cap_t; -struct passwd; -int setusercontext(login_cap_t *, struct passwd *, uid_t, unsigned int); +#ifndef __dead +# define __dead __attribute__ ((noreturn)) +#endif -/* pwd.h */ -#define _PW_NAME_LEN 63 +/* API definitions lifted from OpenBSD src/include */ /* stdlib.h */ +#ifndef HAVE_REALLOCARRAY void * reallocarray(void *optr, size_t nmemb, size_t size); +#endif /* HAVE_REALLOCARRAY */ +#ifndef HAVE_STRTONUM long long strtonum(const char *numstr, long long minval, long long maxval, const char **errstrp); +#endif /* !HAVE_STRTONUM */ /* string.h */ +#ifndef HAVE_EXPLICIT_BZERO void explicit_bzero(void *, size_t); +#endif +#ifndef HAVE_STRLCAT +size_t strlcat(char *dst, const char *src, size_t dsize); +#endif /* !HAVE_STRLCAT */ +#ifndef HAVE_STRLCPY +size_t strlcpy(char *dst, const char *src, size_t dsize); +#endif /* !HAVE_STRLCPY */ /* unistd.h */ +#ifndef HAVE_EXECVPE int execvpe(const char *, char *const *, char *const *); +#endif /* !HAVE_EXECVPE */ +#ifndef HAVE_SETRESUID int setresuid(uid_t, uid_t, uid_t); +#endif /* !HAVE_SETRESUID */ +#ifndef HAVE_PLEDGE +int pledge(const char *promises, const char *paths[]); +#endif /* !HAVE_PLEDGE */ +#ifndef HAVE_CLOSEFROM +void closefrom(int); +#endif /* !HAVE_CLOSEFROM */ + +/* err.h */ +#ifndef HAVE_VERRC +void __dead verrc(int eval, int code, const char *fmt, va_list ap); +#endif /* !HAVE_VERRC */ +#ifndef HAVE_ERRC +__dead void errc(int eval, int code, const char *fmt, ...); +#endif /* !HAVE_ERRC */ + +#ifndef HAVE_SETPROGNAME +const char * getprogname(void); +void setprogname(const char *progname); +#endif /* !HAVE_SETPROGNAME */ +#ifndef HAVE_SETRESGID +int setresgid(gid_t, gid_t, gid_t); #endif +#ifndef HAVE_SETRESUID +int setresuid(uid_t, uid_t, uid_t); +#endif + +#endif /* _LIB_OPENBSD_H_ */ diff --git a/libopenbsd/progname.c b/libopenbsd/progname.c new file mode 100644 index 0000000..acf496f --- /dev/null +++ b/libopenbsd/progname.c @@ -0,0 +1,70 @@ +/* + * Copyright © 2006 Robert Millan + * Copyright © 2010-2012 Guillem Jover + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL + * THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * Rejected in glibc + * . + */ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE___PROGNAME +extern const char *__progname; +#else +static const char *__progname = NULL; +#endif + +const char * +getprogname(void) +{ +#if defined(HAVE_PROGRAM_INVOCATION_SHORT_NAME) + if (__progname == NULL) + __progname = program_invocation_short_name; +#elif defined(HAVE_GETEXECNAME) + /* getexecname(3) returns an absolute pathname, normalize it. */ + if (__progname == NULL) + setprogname(getexecname()); +#endif + + return __progname; +} + +void +setprogname(const char *progname) +{ + const char *last_slash; + + last_slash = strrchr(progname, '/'); + if (last_slash == NULL) + __progname = progname; + else + __progname = last_slash + 1; +} diff --git a/libopenbsd/readpassphrase.c b/libopenbsd/readpassphrase.c new file mode 100644 index 0000000..87675aa --- /dev/null +++ b/libopenbsd/readpassphrase.c @@ -0,0 +1,216 @@ +/* $OpenBSD: readpassphrase.c,v 1.26 2016/10/18 12:47:18 millert Exp $ */ + +/* + * Copyright (c) 2000-2002, 2007, 2010 + * Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* OPENBSD ORIGINAL: lib/libc/gen/readpassphrase.c */ + +#include "config.h" + +#ifndef HAVE_READPASSPHRASE + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys-readpassphrase.h" + +#ifndef _PATH_TTY +#define _PATH_TTY "/dev/tty" +#endif + +#ifndef TCSASOFT +/* If we don't have TCSASOFT define it so that ORing it it below is a no-op. */ +# define TCSASOFT 0 +#endif + +/* SunOS 4.x which lacks _POSIX_VDISABLE, but has VDISABLE */ +#if !defined(_POSIX_VDISABLE) && defined(VDISABLE) +# define _POSIX_VDISABLE VDISABLE +#endif + +static volatile sig_atomic_t signo[_NSIG]; + +static void handler(int); + +char * +readpassphrase(const char *prompt, char *buf, size_t bufsiz, int flags) +{ + ssize_t nr; + int input, output, save_errno, i, need_restart; + char ch, *p, *end; + struct termios term, oterm; + struct sigaction sa, savealrm, saveint, savehup, savequit, saveterm; + struct sigaction savetstp, savettin, savettou, savepipe; + + /* I suppose we could alloc on demand in this case (XXX). */ + if (bufsiz == 0) { + errno = EINVAL; + return(NULL); + } + +restart: + for (i = 0; i < _NSIG; i++) + signo[i] = 0; + nr = -1; + save_errno = 0; + need_restart = 0; + /* + * Read and write to /dev/tty if available. If not, read from + * stdin and write to stderr unless a tty is required. + */ + if ((flags & RPP_STDIN) || + (input = output = open(_PATH_TTY, O_RDWR)) == -1) { + if (flags & RPP_REQUIRE_TTY) { + errno = ENOTTY; + return(NULL); + } + input = STDIN_FILENO; + output = STDERR_FILENO; + } + + /* + * Turn off echo if possible. + * If we are using a tty but are not the foreground pgrp this will + * generate SIGTTOU, so do it *before* installing the signal handlers. + */ + if (input != STDIN_FILENO && tcgetattr(input, &oterm) == 0) { + memcpy(&term, &oterm, sizeof(term)); + if (!(flags & RPP_ECHO_ON)) + term.c_lflag &= ~(ECHO | ECHONL); +#ifdef VSTATUS + if (term.c_cc[VSTATUS] != _POSIX_VDISABLE) + term.c_cc[VSTATUS] = _POSIX_VDISABLE; +#endif + (void)tcsetattr(input, TCSAFLUSH|TCSASOFT, &term); + } else { + memset(&term, 0, sizeof(term)); + term.c_lflag |= ECHO; + memset(&oterm, 0, sizeof(oterm)); + oterm.c_lflag |= ECHO; + } + + /* + * Catch signals that would otherwise cause the user to end + * up with echo turned off in the shell. Don't worry about + * things like SIGXCPU and SIGVTALRM for now. + */ + sigemptyset(&sa.sa_mask); + sa.sa_flags = 0; /* don't restart system calls */ + sa.sa_handler = handler; + (void)sigaction(SIGALRM, &sa, &savealrm); + (void)sigaction(SIGHUP, &sa, &savehup); + (void)sigaction(SIGINT, &sa, &saveint); + (void)sigaction(SIGPIPE, &sa, &savepipe); + (void)sigaction(SIGQUIT, &sa, &savequit); + (void)sigaction(SIGTERM, &sa, &saveterm); + (void)sigaction(SIGTSTP, &sa, &savetstp); + (void)sigaction(SIGTTIN, &sa, &savettin); + (void)sigaction(SIGTTOU, &sa, &savettou); + + if (!(flags & RPP_STDIN)) + (void)write(output, prompt, strlen(prompt)); + end = buf + bufsiz - 1; + p = buf; + while ((nr = read(input, &ch, 1)) == 1 && ch != '\n' && ch != '\r') { + if (p < end) { + if ((flags & RPP_SEVENBIT)) + ch &= 0x7f; + if (isalpha((unsigned char)ch)) { + if ((flags & RPP_FORCELOWER)) + ch = (char)tolower((unsigned char)ch); + if ((flags & RPP_FORCEUPPER)) + ch = (char)toupper((unsigned char)ch); + } + *p++ = ch; + } + } + *p = '\0'; + save_errno = errno; + if (!(term.c_lflag & ECHO)) + (void)write(output, "\n", 1); + + /* Restore old terminal settings and signals. */ + if (memcmp(&term, &oterm, sizeof(term)) != 0) { + const int sigttou = signo[SIGTTOU]; + + /* Ignore SIGTTOU generated when we are not the fg pgrp. */ + while (tcsetattr(input, TCSAFLUSH|TCSASOFT, &oterm) == -1 && + errno == EINTR && !signo[SIGTTOU]) + continue; + signo[SIGTTOU] = sigttou; + } + (void)sigaction(SIGALRM, &savealrm, NULL); + (void)sigaction(SIGHUP, &savehup, NULL); + (void)sigaction(SIGINT, &saveint, NULL); + (void)sigaction(SIGQUIT, &savequit, NULL); + (void)sigaction(SIGPIPE, &savepipe, NULL); + (void)sigaction(SIGTERM, &saveterm, NULL); + (void)sigaction(SIGTSTP, &savetstp, NULL); + (void)sigaction(SIGTTIN, &savettin, NULL); + (void)sigaction(SIGTTOU, &savettou, NULL); + if (input != STDIN_FILENO) + (void)close(input); + + /* + * If we were interrupted by a signal, resend it to ourselves + * now that we have restored the signal handlers. + */ + for (i = 0; i < _NSIG; i++) { + if (signo[i]) { + kill(getpid(), i); + switch (i) { + case SIGTSTP: + case SIGTTIN: + case SIGTTOU: + need_restart = 1; + } + } + } + if (need_restart) + goto restart; + + if (save_errno) + errno = save_errno; + return(nr == -1 ? NULL : buf); +} + +#if 0 +char * +getpass(const char *prompt) +{ + static char buf[_PASSWORD_LEN + 1]; + + return(readpassphrase(prompt, buf, sizeof(buf), RPP_ECHO_OFF)); +} +#endif + +static void handler(int s) +{ + + signo[s] = 1; +} +#endif /* HAVE_READPASSPHRASE */ diff --git a/libopenbsd/reallocarray.c b/libopenbsd/reallocarray.c index aa70686..7775fc6 100644 --- a/libopenbsd/reallocarray.c +++ b/libopenbsd/reallocarray.c @@ -15,6 +15,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" + #include #include #include diff --git a/libopenbsd/setresuid.c b/libopenbsd/setresuid.c deleted file mode 100644 index a62b19a..0000000 --- a/libopenbsd/setresuid.c +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright (c) 2015 Nathan Holstein - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include - -/* I don't think we can actually mimic the right semantics? */ -int -setresuid(uid_t ruid, uid_t euid, uid_t suid) -{ - int ret; - if (suid != ruid) { - errno = EPERM; - return -1; - } - if ((ret = setuid(ruid)) != 0) - return ret; - if ((ret = seteuid(euid)) != 0) - return ret; - return 0; -} - diff --git a/libopenbsd/setusercontext.c b/libopenbsd/setusercontext.c deleted file mode 100644 index a6a9aef..0000000 --- a/libopenbsd/setusercontext.c +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright (c) 2015 Nathan Holstein - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include -#include -#include -#include -#include -#include - -#include "openbsd.h" - -int -setusercontext(login_cap_t *lc, struct passwd *pw, uid_t uid, unsigned int flags) -{ - int ret; - - if (lc != NULL || pw == NULL || - (flags & ~(LOGIN_SETGROUP | LOGIN_SETPRIORITY | - LOGIN_SETRESOURCES | LOGIN_SETUMASK | - LOGIN_SETUSER)) != 0) { - errno = EINVAL; - return -1; - } - - if (flags & LOGIN_SETGROUP) { - if ((ret = setgid(pw->pw_gid)) != 0) - return ret; - if ((ret = initgroups(pw->pw_name, pw->pw_gid)) != 0) - return ret; - } - - if (flags & LOGIN_SETPRIORITY) { - if ((ret = setpriority(PRIO_PROCESS, getpid(), 0)) != 0) - return ret; - if ((ret = setpriority(PRIO_USER, uid, 0)) != 0) - return ret; - } - - if (flags & LOGIN_SETRESOURCES) { - } - - if (flags & LOGIN_SETUMASK) - umask(S_IWGRP | S_IWOTH); - - if (flags & LOGIN_SETUSER) - return setuid(uid); - - return 0; -} - diff --git a/libopenbsd/strlcat.c b/libopenbsd/strlcat.c new file mode 100644 index 0000000..68e1de6 --- /dev/null +++ b/libopenbsd/strlcat.c @@ -0,0 +1,57 @@ +/* $OpenBSD: strlcat.c,v 1.16 2015/08/31 02:53:57 guenther Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include + +/* + * Appends src to string dst of size dsize (unlike strncat, dsize is the + * full size of dst, not space left). At most dsize-1 characters + * will be copied. Always NUL terminates (unless dsize <= strlen(dst)). + * Returns strlen(src) + MIN(dsize, strlen(initial dst)). + * If retval >= dsize, truncation occurred. + */ +size_t +strlcat(char *dst, const char *src, size_t dsize) +{ + const char *odst = dst; + const char *osrc = src; + size_t n = dsize; + size_t dlen; + + /* Find the end of dst and adjust bytes left but don't go past end. */ + while (n-- != 0 && *dst != '\0') + dst++; + dlen = dst - odst; + n = dsize - dlen; + + if (n-- == 0) + return(dlen + strlen(src)); + while (*src != '\0') { + if (n != 0) { + *dst++ = *src; + n--; + } + src++; + } + *dst = '\0'; + + return(dlen + (src - osrc)); /* count does not include NUL */ +} diff --git a/libopenbsd/strlcpy.c b/libopenbsd/strlcpy.c new file mode 100644 index 0000000..985c9ef --- /dev/null +++ b/libopenbsd/strlcpy.c @@ -0,0 +1,52 @@ +/* $OpenBSD: strlcpy.c,v 1.13 2015/08/31 02:53:57 guenther Exp $ */ + +/* + * Copyright (c) 1998, 2015 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include + +/* + * Copy string src to buffer dst of size dsize. At most dsize-1 + * chars will be copied. Always NUL terminates (unless dsize == 0). + * Returns strlen(src); if retval >= dsize, truncation occurred. + */ +size_t +strlcpy(char *dst, const char *src, size_t dsize) +{ + const char *osrc = src; + size_t nleft = dsize; + + /* Copy as many bytes as will fit. */ + if (nleft != 0) { + while (--nleft != 0) { + if ((*dst++ = *src++) == '\0') + break; + } + } + + /* Not enough room in dst, add NUL and traverse rest of src. */ + if (nleft == 0) { + if (dsize != 0) + *dst = '\0'; /* NUL-terminate dst */ + while (*src++) + ; + } + + return(src - osrc - 1); /* count does not include NUL */ +} diff --git a/libopenbsd/strtonum.c b/libopenbsd/strtonum.c index 3725177..b7ff28d 100644 --- a/libopenbsd/strtonum.c +++ b/libopenbsd/strtonum.c @@ -17,6 +17,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "config.h" + #include #include #include diff --git a/libopenbsd/sys-readpassphrase.h b/libopenbsd/sys-readpassphrase.h new file mode 100644 index 0000000..0c4a59e --- /dev/null +++ b/libopenbsd/sys-readpassphrase.h @@ -0,0 +1,42 @@ +/* $OpenBSD: readpassphrase.h,v 1.5 2003/06/17 21:56:23 millert Exp $ */ + +/* + * Copyright (c) 2000, 2002 Todd C. Miller + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Sponsored in part by the Defense Advanced Research Projects + * Agency (DARPA) and Air Force Research Laboratory, Air Force + * Materiel Command, USAF, under agreement number F39502-99-1-0512. + */ + +/* OPENBSD ORIGINAL: include/readpassphrase.h */ + +#ifndef _READPASSPHRASE_H_ +#define _READPASSPHRASE_H_ + +#ifndef HAVE_READPASSPHRASE + +#define RPP_ECHO_OFF 0x00 /* Turn off echo (default). */ +#define RPP_ECHO_ON 0x01 /* Leave echo on. */ +#define RPP_REQUIRE_TTY 0x02 /* Fail if there is no tty. */ +#define RPP_FORCELOWER 0x04 /* Force input to lower case. */ +#define RPP_FORCEUPPER 0x08 /* Force input to upper case. */ +#define RPP_SEVENBIT 0x10 /* Strip the high bit from input. */ +#define RPP_STDIN 0x20 /* Read from stdin, not /dev/tty */ + +char * readpassphrase(const char *, char *, size_t, int); + +#endif /* HAVE_READPASSPHRASE */ + +#endif /* !_READPASSPHRASE_H_ */ diff --git a/libopenbsd/sys-time.h b/libopenbsd/sys-time.h new file mode 100644 index 0000000..2a40dc5 --- /dev/null +++ b/libopenbsd/sys-time.h @@ -0,0 +1,60 @@ +/* $OpenBSD: time.h,v 1.37 2017/12/11 23:31:16 jca Exp $ */ +/* $NetBSD: time.h,v 1.18 1996/04/23 10:29:33 mycroft Exp $ */ + +/* + * Copyright (c) 1982, 1986, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * @(#)time.h 8.2 (Berkeley) 7/10/94 + */ + +#ifndef _SYS_TIME_H_ +#define _SYS_TIME_H_ + +/* Operations on timespecs. */ +#ifndef timespecisset +#define timespecisset(tsp) ((tsp)->tv_sec || (tsp)->tv_nsec) +#endif +#ifndef timespeccmp +#define timespeccmp(tsp, usp, cmp) \ + (((tsp)->tv_sec == (usp)->tv_sec) ? \ + ((tsp)->tv_nsec cmp (usp)->tv_nsec) : \ + ((tsp)->tv_sec cmp (usp)->tv_sec)) +#endif +#ifndef timespecadd +#define timespecadd(tsp, usp, vsp) \ + do { \ + (vsp)->tv_sec = (tsp)->tv_sec + (usp)->tv_sec; \ + (vsp)->tv_nsec = (tsp)->tv_nsec + (usp)->tv_nsec; \ + if ((vsp)->tv_nsec >= 1000000000L) { \ + (vsp)->tv_sec++; \ + (vsp)->tv_nsec -= 1000000000L; \ + } \ + } while (0) +#endif + +#endif /* !_SYS_TIME_H_ */ diff --git a/libopenbsd/sys-tree.h b/libopenbsd/sys-tree.h new file mode 100644 index 0000000..6700e14 --- /dev/null +++ b/libopenbsd/sys-tree.h @@ -0,0 +1,754 @@ +/* $OpenBSD: tree.h,v 1.13 2011/07/09 00:19:45 pirofti Exp $ */ +/* + * Copyright 2002 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* OPENBSD ORIGINAL: sys/sys/tree.h */ + +#ifdef NO_ATTRIBUTE_ON_RETURN_TYPE +# define __attribute__(x) +#endif + +#ifndef _SYS_TREE_H_ +#define _SYS_TREE_H_ + +/* + * This file defines data structures for different types of trees: + * splay trees and red-black trees. + * + * A splay tree is a self-organizing data structure. Every operation + * on the tree causes a splay to happen. The splay moves the requested + * node to the root of the tree and partly rebalances it. + * + * This has the benefit that request locality causes faster lookups as + * the requested nodes move to the top of the tree. On the other hand, + * every lookup causes memory writes. + * + * The Balance Theorem bounds the total access time for m operations + * and n inserts on an initially empty tree as O((m + n)lg n). The + * amortized cost for a sequence of m accesses to a splay tree is O(lg n); + * + * A red-black tree is a binary search tree with the node color as an + * extra attribute. It fulfills a set of conditions: + * - every search path from the root to a leaf consists of the + * same number of black nodes, + * - each red node (except for the root) has a black parent, + * - each leaf node is black. + * + * Every operation on a red-black tree is bounded as O(lg n). + * The maximum height of a red-black tree is 2lg (n+1). + */ + +#define SPLAY_HEAD(name, type) \ +struct name { \ + struct type *sph_root; /* root of the tree */ \ +} + +#define SPLAY_INITIALIZER(root) \ + { NULL } + +#define SPLAY_INIT(root) do { \ + (root)->sph_root = NULL; \ +} while (0) + +#define SPLAY_ENTRY(type) \ +struct { \ + struct type *spe_left; /* left element */ \ + struct type *spe_right; /* right element */ \ +} + +#define SPLAY_LEFT(elm, field) (elm)->field.spe_left +#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right +#define SPLAY_ROOT(head) (head)->sph_root +#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL) + +/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */ +#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (0) + +#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + (head)->sph_root = tmp; \ +} while (0) + +#define SPLAY_LINKLEFT(head, tmp, field) do { \ + SPLAY_LEFT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \ +} while (0) + +#define SPLAY_LINKRIGHT(head, tmp, field) do { \ + SPLAY_RIGHT(tmp, field) = (head)->sph_root; \ + tmp = (head)->sph_root; \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \ +} while (0) + +#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \ + SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \ + SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \ + SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \ +} while (0) + +/* Generates prototypes and inline functions */ + +#define SPLAY_PROTOTYPE(name, type, field, cmp) \ +void name##_SPLAY(struct name *, struct type *); \ +void name##_SPLAY_MINMAX(struct name *, int); \ +struct type *name##_SPLAY_INSERT(struct name *, struct type *); \ +struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \ + \ +/* Finds the node with the same key as elm */ \ +static __inline struct type * \ +name##_SPLAY_FIND(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) \ + return(NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) \ + return (head->sph_root); \ + return (NULL); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_NEXT(struct name *head, struct type *elm) \ +{ \ + name##_SPLAY(head, elm); \ + if (SPLAY_RIGHT(elm, field) != NULL) { \ + elm = SPLAY_RIGHT(elm, field); \ + while (SPLAY_LEFT(elm, field) != NULL) { \ + elm = SPLAY_LEFT(elm, field); \ + } \ + } else \ + elm = NULL; \ + return (elm); \ +} \ + \ +static __inline struct type * \ +name##_SPLAY_MIN_MAX(struct name *head, int val) \ +{ \ + name##_SPLAY_MINMAX(head, val); \ + return (SPLAY_ROOT(head)); \ +} + +/* Main splay operation. + * Moves node close to the key of elm to top + */ +#define SPLAY_GENERATE(name, type, field, cmp) \ +struct type * \ +name##_SPLAY_INSERT(struct name *head, struct type *elm) \ +{ \ + if (SPLAY_EMPTY(head)) { \ + SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \ + } else { \ + int __comp; \ + name##_SPLAY(head, elm); \ + __comp = (cmp)(elm, (head)->sph_root); \ + if(__comp < 0) { \ + SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\ + SPLAY_RIGHT(elm, field) = (head)->sph_root; \ + SPLAY_LEFT((head)->sph_root, field) = NULL; \ + } else if (__comp > 0) { \ + SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\ + SPLAY_LEFT(elm, field) = (head)->sph_root; \ + SPLAY_RIGHT((head)->sph_root, field) = NULL; \ + } else \ + return ((head)->sph_root); \ + } \ + (head)->sph_root = (elm); \ + return (NULL); \ +} \ + \ +struct type * \ +name##_SPLAY_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *__tmp; \ + if (SPLAY_EMPTY(head)) \ + return (NULL); \ + name##_SPLAY(head, elm); \ + if ((cmp)(elm, (head)->sph_root) == 0) { \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \ + (head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\ + } else { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + (head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\ + name##_SPLAY(head, elm); \ + SPLAY_RIGHT((head)->sph_root, field) = __tmp; \ + } \ + return (elm); \ + } \ + return (NULL); \ +} \ + \ +void \ +name##_SPLAY(struct name *head, struct type *elm) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ + int __comp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while ((__comp = (cmp)(elm, (head)->sph_root))) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if ((cmp)(elm, __tmp) > 0){ \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} \ + \ +/* Splay with either the minimum or the maximum element \ + * Used to find minimum or maximum element in tree. \ + */ \ +void name##_SPLAY_MINMAX(struct name *head, int __comp) \ +{ \ + struct type __node, *__left, *__right, *__tmp; \ +\ + SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\ + __left = __right = &__node; \ +\ + while (1) { \ + if (__comp < 0) { \ + __tmp = SPLAY_LEFT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp < 0){ \ + SPLAY_ROTATE_RIGHT(head, __tmp, field); \ + if (SPLAY_LEFT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKLEFT(head, __right, field); \ + } else if (__comp > 0) { \ + __tmp = SPLAY_RIGHT((head)->sph_root, field); \ + if (__tmp == NULL) \ + break; \ + if (__comp > 0) { \ + SPLAY_ROTATE_LEFT(head, __tmp, field); \ + if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\ + break; \ + } \ + SPLAY_LINKRIGHT(head, __left, field); \ + } \ + } \ + SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \ +} + +#define SPLAY_NEGINF -1 +#define SPLAY_INF 1 + +#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y) +#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y) +#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y) +#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y) +#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF)) +#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \ + : name##_SPLAY_MIN_MAX(x, SPLAY_INF)) + +#define SPLAY_FOREACH(x, name, head) \ + for ((x) = SPLAY_MIN(name, head); \ + (x) != NULL; \ + (x) = SPLAY_NEXT(name, head, x)) + +/* Macros that define a red-black tree */ +#define RB_HEAD(name, type) \ +struct name { \ + struct type *rbh_root; /* root of the tree */ \ +} + +#define RB_INITIALIZER(root) \ + { NULL } + +#define RB_INIT(root) do { \ + (root)->rbh_root = NULL; \ +} while (0) + +#define RB_BLACK 0 +#define RB_RED 1 +#define RB_ENTRY(type) \ +struct { \ + struct type *rbe_left; /* left element */ \ + struct type *rbe_right; /* right element */ \ + struct type *rbe_parent; /* parent element */ \ + int rbe_color; /* node color */ \ +} + +#define RB_LEFT(elm, field) (elm)->field.rbe_left +#define RB_RIGHT(elm, field) (elm)->field.rbe_right +#define RB_PARENT(elm, field) (elm)->field.rbe_parent +#define RB_COLOR(elm, field) (elm)->field.rbe_color +#define RB_ROOT(head) (head)->rbh_root +#define RB_EMPTY(head) (RB_ROOT(head) == NULL) + +#define RB_SET(elm, parent, field) do { \ + RB_PARENT(elm, field) = parent; \ + RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \ + RB_COLOR(elm, field) = RB_RED; \ +} while (0) + +#define RB_SET_BLACKRED(black, red, field) do { \ + RB_COLOR(black, field) = RB_BLACK; \ + RB_COLOR(red, field) = RB_RED; \ +} while (0) + +#ifndef RB_AUGMENT +#define RB_AUGMENT(x) do {} while (0) +#endif + +#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \ + (tmp) = RB_RIGHT(elm, field); \ + if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field))) { \ + RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_LEFT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (0) + +#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \ + (tmp) = RB_LEFT(elm, field); \ + if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field))) { \ + RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \ + } \ + RB_AUGMENT(elm); \ + if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field))) { \ + if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \ + RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \ + else \ + RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \ + } else \ + (head)->rbh_root = (tmp); \ + RB_RIGHT(tmp, field) = (elm); \ + RB_PARENT(elm, field) = (tmp); \ + RB_AUGMENT(tmp); \ + if ((RB_PARENT(tmp, field))) \ + RB_AUGMENT(RB_PARENT(tmp, field)); \ +} while (0) + +/* Generates prototypes and inline functions */ +#define RB_PROTOTYPE(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp,) +#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \ + RB_PROTOTYPE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) +#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \ +attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \ +attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\ +attr struct type *name##_RB_REMOVE(struct name *, struct type *); \ +attr struct type *name##_RB_INSERT(struct name *, struct type *); \ +attr struct type *name##_RB_FIND(struct name *, struct type *); \ +attr struct type *name##_RB_NFIND(struct name *, struct type *); \ +attr struct type *name##_RB_NEXT(struct type *); \ +attr struct type *name##_RB_PREV(struct type *); \ +attr struct type *name##_RB_MINMAX(struct name *, int); \ + \ + +/* Main rb operation. + * Moves node close to the key of elm to top + */ +#define RB_GENERATE(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp,) +#define RB_GENERATE_STATIC(name, type, field, cmp) \ + RB_GENERATE_INTERNAL(name, type, field, cmp, __attribute__((__unused__)) static) +#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \ +attr void \ +name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \ +{ \ + struct type *parent, *gparent, *tmp; \ + while ((parent = RB_PARENT(elm, field)) && \ + RB_COLOR(parent, field) == RB_RED) { \ + gparent = RB_PARENT(parent, field); \ + if (parent == RB_LEFT(gparent, field)) { \ + tmp = RB_RIGHT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_RIGHT(parent, field) == elm) { \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_RIGHT(head, gparent, tmp, field); \ + } else { \ + tmp = RB_LEFT(gparent, field); \ + if (tmp && RB_COLOR(tmp, field) == RB_RED) { \ + RB_COLOR(tmp, field) = RB_BLACK; \ + RB_SET_BLACKRED(parent, gparent, field);\ + elm = gparent; \ + continue; \ + } \ + if (RB_LEFT(parent, field) == elm) { \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = parent; \ + parent = elm; \ + elm = tmp; \ + } \ + RB_SET_BLACKRED(parent, gparent, field); \ + RB_ROTATE_LEFT(head, gparent, tmp, field); \ + } \ + } \ + RB_COLOR(head->rbh_root, field) = RB_BLACK; \ +} \ + \ +attr void \ +name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \ +{ \ + struct type *tmp; \ + while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \ + elm != RB_ROOT(head)) { \ + if (RB_LEFT(parent, field) == elm) { \ + tmp = RB_RIGHT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\ + struct type *oleft; \ + if ((oleft = RB_LEFT(tmp, field)))\ + RB_COLOR(oleft, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_RIGHT(head, tmp, oleft, field);\ + tmp = RB_RIGHT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_RIGHT(tmp, field)) \ + RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_LEFT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } else { \ + tmp = RB_LEFT(parent, field); \ + if (RB_COLOR(tmp, field) == RB_RED) { \ + RB_SET_BLACKRED(tmp, parent, field); \ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + if ((RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\ + (RB_RIGHT(tmp, field) == NULL || \ + RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\ + RB_COLOR(tmp, field) = RB_RED; \ + elm = parent; \ + parent = RB_PARENT(elm, field); \ + } else { \ + if (RB_LEFT(tmp, field) == NULL || \ + RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\ + struct type *oright; \ + if ((oright = RB_RIGHT(tmp, field)))\ + RB_COLOR(oright, field) = RB_BLACK;\ + RB_COLOR(tmp, field) = RB_RED; \ + RB_ROTATE_LEFT(head, tmp, oright, field);\ + tmp = RB_LEFT(parent, field); \ + } \ + RB_COLOR(tmp, field) = RB_COLOR(parent, field);\ + RB_COLOR(parent, field) = RB_BLACK; \ + if (RB_LEFT(tmp, field)) \ + RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\ + RB_ROTATE_RIGHT(head, parent, tmp, field);\ + elm = RB_ROOT(head); \ + break; \ + } \ + } \ + } \ + if (elm) \ + RB_COLOR(elm, field) = RB_BLACK; \ +} \ + \ +attr struct type * \ +name##_RB_REMOVE(struct name *head, struct type *elm) \ +{ \ + struct type *child, *parent, *old = elm; \ + int color; \ + if (RB_LEFT(elm, field) == NULL) \ + child = RB_RIGHT(elm, field); \ + else if (RB_RIGHT(elm, field) == NULL) \ + child = RB_LEFT(elm, field); \ + else { \ + struct type *left; \ + elm = RB_RIGHT(elm, field); \ + while ((left = RB_LEFT(elm, field))) \ + elm = left; \ + child = RB_RIGHT(elm, field); \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ + if (RB_PARENT(elm, field) == old) \ + parent = elm; \ + (elm)->field = (old)->field; \ + if (RB_PARENT(old, field)) { \ + if (RB_LEFT(RB_PARENT(old, field), field) == old)\ + RB_LEFT(RB_PARENT(old, field), field) = elm;\ + else \ + RB_RIGHT(RB_PARENT(old, field), field) = elm;\ + RB_AUGMENT(RB_PARENT(old, field)); \ + } else \ + RB_ROOT(head) = elm; \ + RB_PARENT(RB_LEFT(old, field), field) = elm; \ + if (RB_RIGHT(old, field)) \ + RB_PARENT(RB_RIGHT(old, field), field) = elm; \ + if (parent) { \ + left = parent; \ + do { \ + RB_AUGMENT(left); \ + } while ((left = RB_PARENT(left, field))); \ + } \ + goto color; \ + } \ + parent = RB_PARENT(elm, field); \ + color = RB_COLOR(elm, field); \ + if (child) \ + RB_PARENT(child, field) = parent; \ + if (parent) { \ + if (RB_LEFT(parent, field) == elm) \ + RB_LEFT(parent, field) = child; \ + else \ + RB_RIGHT(parent, field) = child; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = child; \ +color: \ + if (color == RB_BLACK) \ + name##_RB_REMOVE_COLOR(head, parent, child); \ + return (old); \ +} \ + \ +/* Inserts a node into the RB tree */ \ +attr struct type * \ +name##_RB_INSERT(struct name *head, struct type *elm) \ +{ \ + struct type *tmp; \ + struct type *parent = NULL; \ + int comp = 0; \ + tmp = RB_ROOT(head); \ + while (tmp) { \ + parent = tmp; \ + comp = (cmp)(elm, parent); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + RB_SET(elm, parent, field); \ + if (parent != NULL) { \ + if (comp < 0) \ + RB_LEFT(parent, field) = elm; \ + else \ + RB_RIGHT(parent, field) = elm; \ + RB_AUGMENT(parent); \ + } else \ + RB_ROOT(head) = elm; \ + name##_RB_INSERT_COLOR(head, elm); \ + return (NULL); \ +} \ + \ +/* Finds the node with the same key as elm */ \ +attr struct type * \ +name##_RB_FIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) \ + tmp = RB_LEFT(tmp, field); \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (NULL); \ +} \ + \ +/* Finds the first node greater than or equal to the search key */ \ +attr struct type * \ +name##_RB_NFIND(struct name *head, struct type *elm) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *res = NULL; \ + int comp; \ + while (tmp) { \ + comp = cmp(elm, tmp); \ + if (comp < 0) { \ + res = tmp; \ + tmp = RB_LEFT(tmp, field); \ + } \ + else if (comp > 0) \ + tmp = RB_RIGHT(tmp, field); \ + else \ + return (tmp); \ + } \ + return (res); \ +} \ + \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_NEXT(struct type *elm) \ +{ \ + if (RB_RIGHT(elm, field)) { \ + elm = RB_RIGHT(elm, field); \ + while (RB_LEFT(elm, field)) \ + elm = RB_LEFT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} \ + \ +/* ARGSUSED */ \ +attr struct type * \ +name##_RB_PREV(struct type *elm) \ +{ \ + if (RB_LEFT(elm, field)) { \ + elm = RB_LEFT(elm, field); \ + while (RB_RIGHT(elm, field)) \ + elm = RB_RIGHT(elm, field); \ + } else { \ + if (RB_PARENT(elm, field) && \ + (elm == RB_RIGHT(RB_PARENT(elm, field), field))) \ + elm = RB_PARENT(elm, field); \ + else { \ + while (RB_PARENT(elm, field) && \ + (elm == RB_LEFT(RB_PARENT(elm, field), field)))\ + elm = RB_PARENT(elm, field); \ + elm = RB_PARENT(elm, field); \ + } \ + } \ + return (elm); \ +} \ + \ +attr struct type * \ +name##_RB_MINMAX(struct name *head, int val) \ +{ \ + struct type *tmp = RB_ROOT(head); \ + struct type *parent = NULL; \ + while (tmp) { \ + parent = tmp; \ + if (val < 0) \ + tmp = RB_LEFT(tmp, field); \ + else \ + tmp = RB_RIGHT(tmp, field); \ + } \ + return (parent); \ +} + +#define RB_NEGINF -1 +#define RB_INF 1 + +#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y) +#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y) +#define RB_FIND(name, x, y) name##_RB_FIND(x, y) +#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y) +#define RB_NEXT(name, x, y) name##_RB_NEXT(y) +#define RB_PREV(name, x, y) name##_RB_PREV(y) +#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF) +#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF) + +#define RB_FOREACH(x, name, head) \ + for ((x) = RB_MIN(name, head); \ + (x) != NULL; \ + (x) = name##_RB_NEXT(x)) + +#define RB_FOREACH_SAFE(x, name, head, y) \ + for ((x) = RB_MIN(name, head); \ + ((x) != NULL) && ((y) = name##_RB_NEXT(x), 1); \ + (x) = (y)) + +#define RB_FOREACH_REVERSE(x, name, head) \ + for ((x) = RB_MAX(name, head); \ + (x) != NULL; \ + (x) = name##_RB_PREV(x)) + +#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \ + for ((x) = RB_MAX(name, head); \ + ((x) != NULL) && ((y) = name##_RB_PREV(x), 1); \ + (x) = (y)) + +#endif /* _SYS_TREE_H_ */ diff --git a/libopenbsd/verrc.c b/libopenbsd/verrc.c new file mode 100644 index 0000000..c27320d --- /dev/null +++ b/libopenbsd/verrc.c @@ -0,0 +1,51 @@ +/* $OpenBSD: verrc.c,v 1.3 2016/03/13 18:34:20 guenther Exp $ */ +/*- + * Copyright (c) 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "openbsd.h" + +void __dead +verrc(int eval, int code, const char *fmt, va_list ap) +{ + (void)fprintf(stderr, "%s: ", getprogname()); + if (fmt != NULL) { + (void)vfprintf(stderr, fmt, ap); + (void)fprintf(stderr, ": "); + } + (void)fprintf(stderr, "%s\n", strerror(code)); + exit(eval); +} diff --git a/pam.c b/pam.c new file mode 100644 index 0000000..fa483b8 --- /dev/null +++ b/pam.c @@ -0,0 +1,348 @@ +/* + * Copyright (c) 2015 Nathan Holstein + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#ifdef HAVE_READPASSPHRASE +# include +#else +# include "sys-readpassphrase.h" +#endif +#include +#include +#include +#include +#include +#include + +#include + +#include "openbsd.h" +#include "doas.h" + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif + +#define PAM_SERVICE_NAME "doas" + +static pam_handle_t *pamh = NULL; +static char doas_prompt[128]; +static sig_atomic_t volatile caught_signal = 0; + +static void +catchsig(int sig) +{ + caught_signal = sig; +} + +static char * +pamprompt(const char *msg, int echo_on, int *ret) +{ + const char *prompt; + char *pass, buf[PAM_MAX_RESP_SIZE]; + int flags = RPP_REQUIRE_TTY | (echo_on ? RPP_ECHO_ON : RPP_ECHO_OFF); + + /* overwrite default prompt if it matches "Password:[ ]" */ + if (strncmp(msg,"Password:", 9) == 0 && + (msg[9] == '\0' || (msg[9] == ' ' && msg[10] == '\0'))) + prompt = doas_prompt; + else + prompt = msg; + + pass = readpassphrase(prompt, buf, sizeof(buf), flags); + if (!pass) + *ret = PAM_CONV_ERR; + else if (!(pass = strdup(pass))) + *ret = PAM_BUF_ERR; + else + *ret = PAM_SUCCESS; + + explicit_bzero(buf, sizeof(buf)); + return pass; +} + +static int +pamconv(int nmsgs, const struct pam_message **msgs, + struct pam_response **rsps, __UNUSED void *ptr) +{ + struct pam_response *rsp; + int i, style; + int ret; + + if (!(rsp = calloc(nmsgs, sizeof(struct pam_response)))) + err(1, "could not allocate pam_response"); + + for (i = 0; i < nmsgs; i++) { + switch (style = msgs[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + rsp[i].resp = pamprompt(msgs[i]->msg, style == PAM_PROMPT_ECHO_ON, &ret); + if (ret != PAM_SUCCESS) + goto fail; + break; + + case PAM_ERROR_MSG: + case PAM_TEXT_INFO: + if (fprintf(stderr, "%s\n", msgs[i]->msg) < 0) + goto fail; + break; + + default: + errx(1, "invalid PAM msg_style %d", style); + } + } + + *rsps = rsp; + rsp = NULL; + + return PAM_SUCCESS; + +fail: + /* overwrite and free response buffers */ + for (i = 0; i < nmsgs; i++) { + if (rsp[i].resp == NULL) + continue; + switch (msgs[i]->msg_style) { + case PAM_PROMPT_ECHO_OFF: + case PAM_PROMPT_ECHO_ON: + explicit_bzero(rsp[i].resp, strlen(rsp[i].resp)); + free(rsp[i].resp); + } + rsp[i].resp = NULL; + } + free(rsp); + + return PAM_CONV_ERR; +} + +void +pamcleanup(int ret, int sess, int cred) +{ + if (sess) { + ret = pam_close_session(pamh, 0); + if (ret != PAM_SUCCESS) + errx(1, "pam_close_session: %s", pam_strerror(pamh, ret)); + } + if (cred) { + ret = pam_setcred(pamh, PAM_DELETE_CRED | PAM_SILENT); + if (ret != PAM_SUCCESS) + warn("pam_setcred(?, PAM_DELETE_CRED | PAM_SILENT): %s", + pam_strerror(pamh, ret)); + } + pam_end(pamh, ret); +} + +void +watchsession(pid_t child, int sess, int cred) +{ + sigset_t sigs; + struct sigaction act, oldact; + int status = 1; + + /* block signals */ + sigfillset(&sigs); + if (sigprocmask(SIG_BLOCK, &sigs, NULL)) { + warn("failed to block signals"); + caught_signal = 1; + goto close; + } + + /* setup signal handler */ + act.sa_handler = catchsig; + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + + /* unblock SIGTERM and SIGALRM to catch them */ + sigemptyset(&sigs); + if (sigaddset(&sigs, SIGTERM) || + sigaddset(&sigs, SIGALRM) || + sigaddset(&sigs, SIGTSTP) || + sigaction(SIGTERM, &act, &oldact) || + sigprocmask(SIG_UNBLOCK, &sigs, NULL)) { + warn("failed to set signal handler"); + caught_signal = 1; + goto close; + } + + /* wait for child to be terminated */ + if (waitpid(child, &status, 0) != -1) { + if (WIFSIGNALED(status)) { + fprintf(stderr, "%s%s\n", strsignal(WTERMSIG(status)), + WCOREDUMP(status) ? " (core dumped)" : ""); + status = WTERMSIG(status) + 128; + } else + status = WEXITSTATUS(status); + } + else if (caught_signal) + status = caught_signal + 128; + else + status = 1; + +close: + if (caught_signal && child != (pid_t)-1) { + fprintf(stderr, "\nSession terminated, killing shell\n"); + kill(child, SIGTERM); + } + + pamcleanup(PAM_SUCCESS, sess, cred); + + if (caught_signal) { + if (child != (pid_t)-1) { + /* kill child */ + sleep(2); + kill(child, SIGKILL); + fprintf(stderr, " ...killed.\n"); + } + + /* unblock cached signal and resend */ + sigaction(SIGTERM, &oldact, NULL); + if (caught_signal != SIGTERM) + caught_signal = SIGKILL; + kill(getpid(), caught_signal); + } + + exit(status); +} + +void +pamauth(const char *user, const char *myname, int interactive, int nopass, int persist) +{ + static const struct pam_conv conv = { + .conv = pamconv, + .appdata_ptr = NULL, + }; + const char *ttydev; + pid_t child; + int ret, sess = 0, cred = 0; + +#ifdef USE_TIMESTAMP + int fd = -1; + int valid = 0; +#else + (void) persist; +#endif + + if (!user || !myname) + errx(1, "Authentication failed"); + + ret = pam_start(PAM_SERVICE_NAME, myname, &conv, &pamh); + if (ret != PAM_SUCCESS) + errx(1, "pam_start(\"%s\", \"%s\", ?, ?): failed", + PAM_SERVICE_NAME, myname); + + ret = pam_set_item(pamh, PAM_RUSER, myname); + if (ret != PAM_SUCCESS) + warn("pam_set_item(?, PAM_RUSER, \"%s\"): %s", + pam_strerror(pamh, ret), myname); + + if (isatty(0) && (ttydev = ttyname(0)) != NULL) { + if (strncmp(ttydev, "/dev/", 5) == 0) + ttydev += 5; + + ret = pam_set_item(pamh, PAM_TTY, ttydev); + if (ret != PAM_SUCCESS) + warn("pam_set_item(?, PAM_TTY, \"%s\"): %s", + ttydev, pam_strerror(pamh, ret)); + } + + +#ifdef USE_TIMESTAMP + if (persist) + fd = timestamp_open(&valid, 5 * 60); + if (fd != -1 && valid == 1) + nopass = 1; +#endif + + if (!nopass) { + if (!interactive) + errx(1, "Authentication required"); + + /* doas style prompt for pam */ + char host[HOST_NAME_MAX + 1]; + if (gethostname(host, sizeof(host))) + snprintf(host, sizeof(host), "?"); + snprintf(doas_prompt, sizeof(doas_prompt), + "\rdoas (%.32s@%.32s) password: ", myname, host); + + /* authenticate */ + ret = pam_authenticate(pamh, 0); + if (ret != PAM_SUCCESS) { + pamcleanup(ret, sess, cred); + syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed auth for %s", myname); + errx(1, "Authentication failed"); + } + } + + + ret = pam_acct_mgmt(pamh, 0); + if (ret == PAM_NEW_AUTHTOK_REQD) + ret = pam_chauthtok(pamh, PAM_CHANGE_EXPIRED_AUTHTOK); + + /* account not vaild or changing the auth token failed */ + if (ret != PAM_SUCCESS) { + pamcleanup(ret, sess, cred); + syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed auth for %s", myname); + errx(1, "Authentication failed"); + } + + /* set PAM_USER to the user we want to be */ + ret = pam_set_item(pamh, PAM_USER, user); + if (ret != PAM_SUCCESS) + warn("pam_set_item(?, PAM_USER, \"%s\"): %s", user, + pam_strerror(pamh, ret)); + + ret = pam_setcred(pamh, PAM_REINITIALIZE_CRED); + if (ret != PAM_SUCCESS) + warn("pam_setcred(?, PAM_REINITIALIZE_CRED): %s", pam_strerror(pamh, ret)); + else + cred = 1; + + /* open session */ + ret = pam_open_session(pamh, 0); + if (ret != PAM_SUCCESS) + errx(1, "pam_open_session: %s", pam_strerror(pamh, ret)); + sess = 1; + + if ((child = fork()) == -1) { + pamcleanup(PAM_ABORT, sess, cred); + err(1, "fork"); + } + + /* return as child */ + if (child == 0) { +#ifdef USE_TIMESTAMP + if (fd != -1) + close(fd); +#endif + return; + } + +#ifdef USE_TIMESTAMP + if (fd != -1) { + timestamp_set(fd, 5 * 60); + close(fd); + } +#endif + watchsession(child, sess, cred); +} diff --git a/pam.d__doas b/pam.d__doas deleted file mode 100644 index 87551fb..0000000 --- a/pam.d__doas +++ /dev/null @@ -1,5 +0,0 @@ -# sudo: auth account password session -auth required pam_opendirectory.so -account required pam_permit.so -password required pam_deny.so -session required pam_permit.so diff --git a/parse.y b/parse.y index 0307b0f..388c2a5 100644 --- a/parse.y +++ b/parse.y @@ -16,6 +16,8 @@ */ %{ +#include "config.h" + #include #include #include @@ -39,6 +41,7 @@ typedef struct { const char **cmdargs; const char **envlist; }; + const char **strlist; const char *str; }; int lineno; @@ -49,17 +52,30 @@ typedef struct { FILE *yyfp; struct rule **rules; -int nrules, maxrules; +size_t nrules; +static size_t maxrules; + int parse_errors = 0; -void yyerror(const char *, ...); -int yylex(void); -int yyparse(void); +static void yyerror(const char *, ...); +static int yylex(void); + +static size_t +arraylen(const char **arr) +{ + size_t cnt = 0; + + while (*arr) { + cnt++; + arr++; + } + return cnt; +} %} %token TPERMIT TDENY TAS TCMD TARGS -%token TNOPASS TKEEPENV +%token TNOPASS TNOLOG TPERSIST TKEEPENV TSETENV %token TSTRING %% @@ -84,12 +100,12 @@ rule: action ident target cmd { r->cmdargs = $4.cmdargs; if (nrules == maxrules) { if (maxrules == 0) - maxrules = 63; - else - maxrules *= 2; - if (!(rules = reallocarray(rules, maxrules, - sizeof(*rules)))) + maxrules = 32; + rules = reallocarray(rules, maxrules, + 2 * sizeof(*rules)); + if (!rules) errx(1, "can't allocate rules"); + maxrules *= 2; } rules[nrules++] = r; } ; @@ -100,6 +116,8 @@ action: TPERMIT options { $$.envlist = $2.envlist; } | TDENY { $$.action = DENY; + $$.options = 0; + $$.envlist = NULL; } ; options: /* none */ { @@ -108,9 +126,13 @@ options: /* none */ { } | options option { $$.options = $1.options | $2.options; $$.envlist = $1.envlist; + if (($$.options & (NOPASS|PERSIST)) == (NOPASS|PERSIST)) { + yyerror("can't combine nopass and persist"); + YYERROR; + } if ($2.envlist) { if ($$.envlist) { - yyerror("can't have two keepenv sections"); + yyerror("can't have two setenv sections"); YYERROR; } else $$.envlist = $2.envlist; @@ -119,24 +141,31 @@ options: /* none */ { option: TNOPASS { $$.options = NOPASS; $$.envlist = NULL; + } | TNOLOG { + $$.options = NOLOG; + $$.envlist = NULL; + } | TPERSIST { + $$.options = PERSIST; + $$.envlist = NULL; } | TKEEPENV { $$.options = KEEPENV; $$.envlist = NULL; - } | TKEEPENV '{' envlist '}' { - $$.options = KEEPENV; - $$.envlist = $3.envlist; + } | TSETENV '{' strlist '}' { + $$.options = 0; + $$.envlist = $3.strlist; } ; -envlist: /* empty */ { - $$.envlist = NULL; - } | envlist TSTRING { - int nenv = arraylen($1.envlist); - if (!($$.envlist = reallocarray($1.envlist, nenv + 2, +strlist: /* empty */ { + if (!($$.strlist = calloc(1, sizeof(char *)))) + errx(1, "can't allocate strlist"); + } | strlist TSTRING { + int nstr = arraylen($1.strlist); + if (!($$.strlist = reallocarray($1.strlist, nstr + 2, sizeof(char *)))) - errx(1, "can't allocate envlist"); - $$.envlist[nenv] = $2.str; - $$.envlist[nenv + 1] = NULL; - } + errx(1, "can't allocate strlist"); + $$.strlist[nstr] = $2.str; + $$.strlist[nstr + 1] = NULL; + } ; ident: TSTRING { @@ -159,19 +188,8 @@ cmd: /* optional */ { args: /* empty */ { $$.cmdargs = NULL; - } | TARGS argslist { - $$.cmdargs = $2.cmdargs; - } ; - -argslist: /* empty */ { - $$.cmdargs = NULL; - } | argslist TSTRING { - int nargs = arraylen($1.cmdargs); - if (!($$.cmdargs = reallocarray($1.cmdargs, nargs + 2, - sizeof(char *)))) - errx(1, "can't allocate args"); - $$.cmdargs[nargs] = $2.str; - $$.cmdargs[nargs + 1] = NULL; + } | TARGS strlist { + $$.cmdargs = $2.strlist; } ; %% @@ -181,6 +199,7 @@ yyerror(const char *fmt, ...) { va_list va; + fprintf(stderr, "doas: "); va_start(va, fmt); vfprintf(stderr, fmt, va); va_end(va); @@ -188,7 +207,7 @@ yyerror(const char *fmt, ...) parse_errors++; } -struct keyword { +static struct keyword { const char *word; int token; } keywords[] = { @@ -198,7 +217,10 @@ struct keyword { { "cmd", TCMD }, { "args", TARGS }, { "nopass", TNOPASS }, + { "nolog", TNOLOG }, + { "persist", TPERSIST }, { "keepenv", TKEEPENV }, + { "setenv", TSETENV }, }; int @@ -206,6 +228,7 @@ yylex(void) { char buf[1024], *ebuf, *p, *str; int c, quotes = 0, escape = 0, qpos = -1, nonkw = 0; + size_t i; p = buf; ebuf = buf + sizeof(buf); @@ -228,12 +251,12 @@ repeat: /* skip comments; NUL is allowed; no continuation */ while ((c = getc(yyfp)) != '\n') if (c == EOF) - return 0; + goto eof; yylval.colno = 0; yylval.lineno++; return c; case EOF: - return 0; + goto eof; } /* parsing next word */ @@ -256,6 +279,8 @@ repeat: if (escape) { nonkw = 1; escape = 0; + yylval.colno = 0; + yylval.lineno++; continue; } goto eow; @@ -287,8 +312,10 @@ repeat: } } *p++ = c; - if (p == ebuf) + if (p == ebuf) { yyerror("too long line"); + p = buf; + } escape = 0; } @@ -303,19 +330,23 @@ eow: * the main loop. */ if (c == EOF) - return 0; + goto eof; else if (qpos == -1) /* accept, e.g., empty args: cmd foo args "" */ goto repeat; } if (!nonkw) { - size_t i; for (i = 0; i < sizeof(keywords) / sizeof(keywords[0]); i++) { if (strcmp(buf, keywords[i].word) == 0) return keywords[i].token; } } if ((str = strdup(buf)) == NULL) - err(1, "strdup"); + err(1, "%s", __func__); yylval.str = str; return TSTRING; + +eof: + if (ferror(yyfp)) + yyerror("input error reading config"); + return 0; } diff --git a/shadow.c b/shadow.c new file mode 100644 index 0000000..2569b58 --- /dev/null +++ b/shadow.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2020 Duncan Overbruck + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +#if HAVE_CRYPT_H +# include +#endif +#include +#include +#include +#include +#ifdef HAVE_READPASSPHRASE +# include +#else +# include "sys-readpassphrase.h" +#endif +#include +#include +#include +#include +#include + +#include "openbsd.h" +#include "doas.h" + +#ifndef HOST_NAME_MAX +#define HOST_NAME_MAX _POSIX_HOST_NAME_MAX +#endif + +void +shadowauth(const char *myname, int persist) +{ + const char *hash; + char *encrypted; + struct passwd *pw; + char *challenge, *response, rbuf[1024], cbuf[128]; + +#ifdef USE_TIMESTAMP + int fd = -1; + int valid = 0; + + if (persist) + fd = timestamp_open(&valid, 5 * 60); + if (fd != -1 && valid == 1) + goto good; +#else + (void) persist; +#endif + + if ((pw = getpwnam(myname)) == NULL) + err(1, "getpwnam"); + + hash = pw->pw_passwd; + if (hash[0] == 'x' && hash[1] == '\0') { + struct spwd *sp; + if ((sp = getspnam(myname)) == NULL) + errx(1, "Authentication failed"); + hash = sp->sp_pwdp; + } else if (hash[0] != '*') { + errx(1, "Authentication failed"); + } + + char host[HOST_NAME_MAX + 1]; + if (gethostname(host, sizeof(host))) + snprintf(host, sizeof(host), "?"); + snprintf(cbuf, sizeof(cbuf), + "\rdoas (%.32s@%.32s) password: ", myname, host); + challenge = cbuf; + + response = readpassphrase(challenge, rbuf, sizeof(rbuf), RPP_REQUIRE_TTY); + if (response == NULL && errno == ENOTTY) { + syslog(LOG_AUTHPRIV | LOG_NOTICE, + "tty required for %s", myname); + errx(1, "a tty is required"); + } + if (response == NULL) + err(1, "readpassphrase"); + if ((encrypted = crypt(response, hash)) == NULL) { + explicit_bzero(rbuf, sizeof(rbuf)); + errx(1, "Authentication failed"); + } + explicit_bzero(rbuf, sizeof(rbuf)); + if (strcmp(encrypted, hash) != 0) { + syslog(LOG_AUTHPRIV | LOG_NOTICE, "failed auth for %s", myname); + errx(1, "Authentication failed"); + } + +#ifdef USE_TIMESTAMP +good: + if (fd != -1) { + timestamp_set(fd, 5 * 60); + close(fd); + } +#endif +} diff --git a/timestamp.c b/timestamp.c new file mode 100644 index 0000000..bca260d --- /dev/null +++ b/timestamp.c @@ -0,0 +1,315 @@ +/* + * Copyright (c) 2020 Duncan Overbruck + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "config.h" + +/* + * 1) Timestamp files and directories + * + * Timestamp files MUST NOT be accessible to users other than root, + * this includes the name, metadata and the content of timestamp files + * and directories. + * + * Symlinks can be used to create, manipulate or delete wrong files + * and directories. The Implementation MUST reject any symlinks for + * timestamp files or directories. + * + * To avoid race conditions the implementation MUST use the same + * file descriptor for permission checks and do read or write + * write operations after the permission checks. + * + * The timestamp files MUST be opened with openat(2) using the + * timestamp directory file descriptor. Permissions of the directory + * MUST be checked before opening the timestamp file descriptor. + * + * 2) Clock sources for timestamps + * + * Timestamp files MUST NOT rely on only one clock source, using the + * wall clock would allow to reset the clock to an earlier point in + * time to reuse a timestamp. + * + * The timestamp MUST consist of multiple clocks and MUST reject the + * timestamp if there is a change to any clock because there is no way + * to differentiate between malicious and legitimate clock changes. + * + * 3) Timestamp lifetime + * + * The implementation MUST NOT use the user controlled stdin, stdout + * and stderr file descriptors to determine the controlling terminal. + * On linux the /proc/$pid/stat file MUST be used to get the terminal + * number. + * + * There is no reliable way to determine the lifetime of a tty/pty. + * The start time of the session leader MUST be used as part of the + * timestamp to determine if the tty is still the same. + * If the start time of the session leader changed the timestamp MUST + * be rejected. + * + */ + +#include +#include +#include + +#if !defined(timespecisset) || \ + !defined(timespeccmp) || \ + !defined(timespecadd) +# include "sys-time.h" +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "openbsd.h" +#include "doas.h" + +#ifndef TIMESTAMP_DIR +# define TIMESTAMP_DIR "/run/doas" +#endif + +#if defined(TIMESTAMP_TMPFS) && defined(__linux__) +# ifndef TMPFS_MAGIC +# define TMPFS_MAGIC 0x01021994 +# endif +#endif + +#ifdef __linux__ +/* Use tty_nr from /proc/self/stat instead of using + * ttyname(3), stdin, stdout and stderr are user + * controllable and would allow to reuse timestamps + * from another writable terminal. + * See https://www.sudo.ws/alerts/tty_tickets.html + */ +static int +proc_info(pid_t pid, int *ttynr, unsigned long long *starttime) +{ + char path[128]; + char buf[1024]; + char *p, *saveptr, *ep; + const char *errstr; + int fd, n; + + p = buf; + + n = snprintf(path, sizeof path, "/proc/%d/stat", pid); + if (n < 0 || n >= (int)sizeof path) + return -1; + + if ((fd = open(path, O_RDONLY|O_NOFOLLOW)) == -1) { + warn("failed to open: %s", path); + return -1; + } + + while ((n = read(fd, p, buf + (sizeof buf - 1) - p)) != 0) { + if (n == -1) { + if (errno == EAGAIN || errno == EINTR) + continue; + warn("read: %s", path); + close(fd); + return -1; + } + p += n; + if (p >= buf + (sizeof buf - 1)) + break; + } + close(fd); + + /* error if it contains NULL bytes */ + if (n != 0 || memchr(buf, '\0', p - buf - 1) != NULL) { + warn("NUL in: %s", path); + return -1; + } + + *p = '\0'; + + /* Get the 7th field, 5 fields after the last ')', + * (2th field) because the 5th field 'comm' can include + * spaces and closing paranthesis too. + * See https://www.sudo.ws/alerts/linux_tty.html + */ + if ((p = strrchr(buf, ')')) == NULL) + return -1; + + n = 2; + for ((p = strtok_r(p, " ", &saveptr)); p; + (p = strtok_r(NULL, " ", &saveptr))) { + switch (n++) { + case 7: + *ttynr = strtonum(p, INT_MIN, INT_MAX, &errstr); + if (errstr) + return -1; + break; + case 22: + errno = 0; + *starttime = strtoull(p, &ep, 10); + if (p == ep || + (errno == ERANGE && *starttime == ULLONG_MAX)) + return -1; + return 0; + } + } + + return -1; +} +#else +#error "proc_info not implemented" +#endif + +static int +timestamp_path(char *buf, size_t len) +{ + pid_t ppid, sid; + unsigned long long starttime; + int n, ttynr; + + ppid = getppid(); + if ((sid = getsid(0)) == -1) + return -1; + if (proc_info(ppid, &ttynr, &starttime) == -1) + return -1; + n = snprintf(buf, len, TIMESTAMP_DIR "/%d-%d-%d-%llu-%d", + ppid, sid, ttynr, starttime, getuid()); + if (n < 0 || n >= (int)len) + return -1; + return 0; +} + +int +timestamp_set(int fd, int secs) +{ + struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 }; + + if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 || + clock_gettime(CLOCK_REALTIME, &ts[1]) == -1) + return -1; + + timespecadd(&ts[0], &timeout, &ts[0]); + timespecadd(&ts[1], &timeout, &ts[1]); + return futimens(fd, ts); +} + +/* + * Returns 1 if the timestamp is valid, 0 if its invalid + */ +static int +timestamp_check(int fd, int secs) +{ + struct timespec ts[2], timeout = { .tv_sec = secs, .tv_nsec = 0 }; + struct stat st; + + if (fstat(fd, &st) == -1) + err(1, "fstat"); + if (st.st_uid != 0 || st.st_gid != getgid() || st.st_mode != (S_IFREG | 0000)) + errx(1, "timestamp uid, gid or mode wrong"); + + /* this timestamp was created but never set, invalid but no error */ + if (!timespecisset(&st.st_atim) || !timespecisset(&st.st_mtim)) + return 0; + + if (clock_gettime(CLOCK_BOOTTIME, &ts[0]) == -1 || + clock_gettime(CLOCK_REALTIME, &ts[1]) == -1) { + warn("clock_gettime"); + return 0; + } + + /* check if timestamp is too old */ + if (timespeccmp(&st.st_atim, &ts[0], <) || + timespeccmp(&st.st_mtim, &ts[1], <)) + return 0; + + /* check if timestamp is too far in the future */ + timespecadd(&ts[0], &timeout, &ts[0]); + timespecadd(&ts[1], &timeout, &ts[1]); + if (timespeccmp(&st.st_atim, &ts[0], >) || + timespeccmp(&st.st_mtim, &ts[1], >)) { + warnx("timestamp too far in the future"); + return 0; + } + + return 1; +} + +int +timestamp_open(int *valid, int secs) +{ + struct timespec ts[2] = {0}; + struct stat st; + int fd; + char path[256]; + int serrno = 0; + + *valid = 0; + + if (stat(TIMESTAMP_DIR, &st) == -1) { + if (errno != ENOENT) + return -1; + if (mkdir(TIMESTAMP_DIR, 0700) == -1) + return -1; + } else if (st.st_uid != 0 || st.st_mode != (S_IFDIR | 0700)) { + return -1; + } + + if (timestamp_path(path, sizeof path) == -1) + return -1; + + fd = open(path, O_RDONLY|O_NOFOLLOW); + if (fd == -1) { + char tmp[256]; + int n; + + if (errno != ENOENT) + err(1, "open: %s", path); + + n = snprintf(tmp, sizeof tmp, TIMESTAMP_DIR "/.tmp-%d", getpid()); + if (n < 0 || n >= (int)sizeof tmp) + return -1; + + fd = open(tmp, O_RDONLY|O_CREAT|O_EXCL|O_NOFOLLOW, 0000); + if (fd == -1) + return -1; + if (futimens(fd, ts) == -1 || rename(tmp, path) == -1) { + serrno = errno; + close(fd); + unlink(tmp); + errno = serrno; + return -1; + } + } else { + *valid = timestamp_check(fd, secs); + } + return fd; +} + +int +timestamp_clear() +{ + char path[256]; + + if (timestamp_path(path, sizeof path) == -1) + return -1; + if (unlink(path) == -1 && errno != ENOENT) + return -1; + return 0; +}