From f152c55584d8b8a4e1ab22d758fd48a0e2f7d24f Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Fri, 21 Jul 2023 07:10:38 +0200 Subject: [PATCH] New option netns for network namespace setting --- CHANGES | 4 + Makefile.in | 4 +- config.h.in | 7 ++ configure.ac | 24 ++++ doc/socat.yo | 63 +++++++++-- procan.c | 11 ++ socat.c | 5 + sycls.c | 21 ++++ sycls.h | 2 + sysincludes.h | 3 + test.sh | 210 +++++++++++++++++++++++++++++++++- xio-exec.c | 3 +- xio-ipapp.c | 3 +- xio-namespaces.c | 73 ++++++++++++ xio-namespaces.h | 18 +++ xio-pipe.c | 2 +- xio-process.c | 1 - xio-progcall.c | 12 +- xio-socket.c | 1 + xio-streams.c | 2 +- xio-system.c | 2 +- xio-tun.c | 1 + xiomodes.h | 1 + xioopen.c | 37 +++++- xioopts.c | 286 +++++++++++++++++++++++++++++++---------------- xioopts.h | 9 +- 26 files changed, 686 insertions(+), 119 deletions(-) create mode 100644 xio-namespaces.c create mode 100644 xio-namespaces.h diff --git a/CHANGES b/CHANGES index df5107e..10e5c00 100644 --- a/CHANGES +++ b/CHANGES @@ -107,6 +107,10 @@ Features: DNS resolver Options (res-*) are now set for the complete open phase of the address, not per getaddrinfo() invocation. + Added the netns option that tries to open an address in the given + network namespace. + Tests: NETNS NETNS_EXEC + Corrections: When a sub process (EXEC, SYSTEM) terminated with exit code other than 0, its last sent data might have been lost depending on timing of read/ diff --git a/Makefile.in b/Makefile.in index aa17fa2..a54a954 100644 --- a/Makefile.in +++ b/Makefile.in @@ -51,7 +51,7 @@ XIOSRCS = xioinitialize.c xiohelp.c xioparam.c xiodiag.c xioopen.c xioopts.c \ xio-sctp.c xio-rawip.c \ xio-socks.c xio-proxy.c xio-udp.c \ xio-progcall.c xio-exec.c xio-system.c xio-termios.c xio-readline.c \ - xio-pty.c xio-openssl.c xio-streams.c\ + xio-pty.c xio-openssl.c xio-streams.c xio-namespaces.c \ xio-ascii.c xiolockfile.c xio-tcpwrap.c xio-fs.c xio-tun.c XIOOBJS = $(XIOSRCS:.c=.o) UTLSRCS = error.c dalan.c procan.c procan-cdefs.c hostan.c fdname.c sysutils.c utils.c nestlex.c vsnprintf_r.c snprinterr.c @FILAN@ sycls.c @SSLCLS@ @@ -69,7 +69,7 @@ HFILES = sycls.h sslcls.h error.h dalan.h procan.h filan.h hostan.h sysincludes. xio-ipapp.h xio-tcp.h xio-udp.h xio-sctp.h \ xio-socks.h xio-proxy.h xio-progcall.h xio-exec.h \ xio-system.h xio-termios.h xio-readline.h \ - xio-pty.h xio-openssl.h xio-streams.h \ + xio-pty.h xio-openssl.h xio-streams.h xio-namespaces.h \ xio-ascii.h xiolockfile.h xio-tcpwrap.h xio-fs.h xio-tun.h diff --git a/config.h.in b/config.h.in index 44e8f19..a87bf21 100644 --- a/config.h.in +++ b/config.h.in @@ -285,6 +285,9 @@ /* Define if you have the header file. */ #undef HAVE_LINUX_VM_SOCKETS_H +/* Define if you have the header file. */ +#undef HAVE_SCHED_H + /* Define if you have the header file. */ #undef HAVE_LINUX_IF_PACKET_H @@ -439,6 +442,9 @@ /* define if your struct ip has ip_hl; otherwise assume ip_vhl */ #undef HAVE_STRUCT_IP_IP_HL +/* Define if you have the setns function */ +#undef HAVE_SETNS + /* Define if you have the setenv function */ #undef HAVE_SETENV @@ -700,6 +706,7 @@ #undef WITH_SOCKS4 #undef WITH_SOCKS4A #undef WITH_VSOCK +#undef WITH_NAMESPACES #undef WITH_PROXY #undef WITH_EXEC #undef WITH_SYSTEM diff --git a/configure.ac b/configure.ac index 1388d59..bf04260 100644 --- a/configure.ac +++ b/configure.ac @@ -404,6 +404,30 @@ if test "$WITH_VSOCK"; then AC_DEFINE(WITH_VSOCK) fi +AC_ARG_ENABLE(namespaces, [ --disable-namespaces disable Linux namespaces support], + [case "$enableval" in + no) TRY_NAMESPACES= ;; + *) TRY_NAMESPACES=1 ;; + esac], + [TRY_NAMESPACES=1 ]) +if test "TRY_NAMESPACES"; then + AC_TRY_LINK([#include ], + [int x=setns(0,0);], + [], + [TRY_NAMESPACES=failed]) +fi +AC_MSG_CHECKING(whether to include Linux namespaces support) +if test "$TRY_NAMESPACES" = 1; then + AC_MSG_RESULT(YES) + AC_DEFINE(WITH_NAMESPACES) + AC_DEFINE(HAVE_SCHED_H) + AC_DEFINE(HAVE_SETNS) +elif test "$TRY_NAMESPACES" = yes; then + AC_MSG_RESULT(NO (failed)) +else + AC_MSG_RESULT(NO) +fi + AC_MSG_CHECKING(whether to include listen support) AC_ARG_ENABLE(listen, [ --disable-listen disable listen support], [case "$enableval" in diff --git a/doc/socat.yo b/doc/socat.yo index 808e161..b28e55f 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -341,7 +341,8 @@ label(ADDRESS_EXEC)dit(bf(tt(EXEC:))) link(pipes)(OPTION_PIPES), link(login)(OPTION_LOGIN), link(sigint)(OPTION_SIGINT), - link(sigquit)(OPTION_SIGQUIT)nl() + link(sigquit)(OPTION_SIGQUIT), + link(netns)(OPTION_NETNS)nl() See also: link(SYSTEM)(ADDRESS_SYSTEM) label(ADDRESS_FD)dit(bf(tt(FD:))) Uses the file descriptor link()(TYPE_FDNUM). It must already exist as @@ -999,7 +1000,8 @@ label(ADDRESS_SYSTEM)dit(bf(tt(SYSTEM:))) link(setsid)(OPTION_SETSID), link(pipes)(OPTION_PIPES), link(sigint)(OPTION_SIGINT), - link(sigquit)(OPTION_SIGQUIT)nl() + link(sigquit)(OPTION_SIGQUIT), + link(netns)(OPTION_NETNS)nl() See also: link(EXEC)(ADDRESS_EXEC) label(ADDRESS_TCP_CONNECT)dit(bf(tt(TCP::))) Connects to [link(TCP service)(TYPE_TCP_SERVICE)] on @@ -1008,17 +1010,18 @@ label(ADDRESS_TCP_CONNECT)dit(bf(tt(TCP::))) link(pf)(OPTION_PROTOCOL_FAMILY).nl() Option groups: link(FD)(GROUP_FD),link(SOCKET)(GROUP_SOCKET),link(IP4)(GROUP_IP4),link(IP6)(GROUP_IP6),link(TCP)(GROUP_TCP),link(RETRY)(GROUP_RETRY) nl() Useful options: + link(connect-timeout)(OPTION_CONNECT_TIMEOUT), + link(retry)(OPTION_RETRY), + link(sourceport)(OPTION_SOURCEPORT), + link(netns)(OPTION_NETNS), link(crnl)(OPTION_CRNL), link(bind)(OPTION_BIND), link(pf)(OPTION_PROTOCOL_FAMILY), - link(connect-timeout)(OPTION_CONNECT_TIMEOUT), link(tos)(OPTION_TOS), link(mtudiscover)(OPTION_MTUDISCOVER), link(mss)(OPTION_MSS), link(nodelay)(OPTION_TCP_NODELAY), link(nonblock)(OPTION_NONBLOCK), - link(sourceport)(OPTION_SOURCEPORT), - link(retry)(OPTION_RETRY), link(readbytes)(OPTION_READBYTES)nl() See also: link(TCP4)(ADDRESS_TCP4_CONNECT), @@ -1090,7 +1093,8 @@ label(ADDRESS_TUN)dit(bf(tt(TUN[:/]))) link(tun-device)(OPTION_TUN_DEVICE), link(tun-name)(OPTION_TUN_NAME), link(tun-type)(OPTION_TUN_TYPE), - link(iff-no-pi)(OPTION_IFF_NO_PI) nl() + link(iff-no-pi)(OPTION_IFF_NO_PI), + link(netns)(OPTION_NETNS)nl() See also: link(ip-recv)(ADDRESS_IP_RECV) label(ADDRESS_UDP_CONNECT)dit(bf(tt(UDP::))) @@ -1429,7 +1433,9 @@ dit(bf(tt(ABSTRACT-CLIENT:))) unixdomain() address space. To achieve this the socket address strings are prefixed with "\0" internally. This feature is available (only?) on Linux. Option groups are the same as with the related UNIX addresses, except that - the ABSTRACT addresses are not member of the NAMED group. + the ABSTRACT addresses are not member of the NAMED group.nl() + Useful options: + link(netns)(OPTION_NETNS) enddit() @@ -1878,6 +1884,12 @@ label(OPTION_SETPGID)dit(bf(tt(setpgid=))) process group. label(OPTION_SETSID)dit(bf(tt(setsid))) Makes the process the leader of a new session (link(example)(EXAMPLE_OPTION_SETSID)). +label(OPTION_NETNS)dit(bf(tt(netns=))) + Before opening the address it tries to switch to the named network namespace. + After opening the address it switches back to the previous namespace. + (link(Example with TCP forwarder)(EXAMPLE_OPTION_NETNS), + link(example with virtual network connection)(EXAMPLE_TUN_NETNS).nl() + Only on Linux; requires root; use option tt(--experimental).nl() enddit() startdit()enddit()nl() @@ -3609,6 +3621,43 @@ to a modemserver via ssh where another socat instance links it to file(/dev/ttyS0). +label(EXAMPLE_OPTION_NETNS) +mancommand(\.LP) +mancommand(\.nf) +mancommand(\fBsudo socat --experimental \\) +mancommand(\.RS) +mancommand(\fBTCP4-LISTEN:8000,reuseaddr,fork,netns=namespace1 \\ + TCP4-CONNECT:server2:8000\fP) +mancommand(\.RE) +mancommand(\.fi) + +htmlcommand(
sudo socat --experimental \ + TCP4-LISTEN:8000,reuseaddr,fork,netns=namespace1 \ + TCP4-CONNECT:server2:8000
) + +creates a listener in the given network namespace that accepts TCP connections +on port 8000 and forwards them to server2. + + +label(EXAMPLE_TUN_NETNS) +mancommand(\.LP) +mancommand(\.nf) +mancommand(\fBsudo socat --experimental \\) +mancommand(\.RS) +mancommand(\fBTUN:192.168.2.1/24,up \\ + TUN:192.168.2.2/24,up,netns=namespace2\fP) +mancommand(\.RE) +mancommand(\.fi) + +htmlcommand(
sudo socat --experimental \ + TUN:192.168.2.1/24,up \ + TUN:192.168.2.2/24,up,netns=namespace2
) + +creates two virtual network interfaces, one in default namespace, the other one +in namespace2, and forwards packets between them, acting as a virtual +network connection. + + label(EXAMPLE_PROXY_CONNECT) mancommand(\.LP) mancommand(\.nf) diff --git a/procan.c b/procan.c index 0265c2b..8f08aa0 100644 --- a/procan.c +++ b/procan.c @@ -13,6 +13,7 @@ #include "error.h" #include "sycls.h" #include "sysutils.h" +#include "sched.h" #include "filan.h" #include @@ -163,6 +164,16 @@ int procan(FILE *outfile) { #endif } + /* Name spaces */ + { + char path[PATH_MAX]; + char link[PATH_MAX]; + snprintf(path, sizeof(path)-1, "/proc/"F_pid"/ns/net", getpid()); + if (readlink(path, link, sizeof(link)-1) >= 0) { + fprintf(outfile, "Network namespace: %s", link); + } + } + /* file descriptors */ /* what was this for?? */ diff --git a/socat.c b/socat.c index c61fc21..6f25707 100644 --- a/socat.c +++ b/socat.c @@ -610,6 +610,11 @@ void socat_version(FILE *fd) { #else fputs(" #undef WITH_VSOCK\n", fd); #endif +#ifdef WITH_NAMESPACES + fprintf(fd, " #define WITH_NAMESPACES %d\n", WITH_NAMESPACES); +#else + fputs(" #undef WITH_NAMESPACES\n", fd); +#endif #ifdef WITH_PROXY fprintf(fd, " #define WITH_PROXY %d\n", WITH_PROXY); #else diff --git a/sycls.c b/sycls.c index e6cd8db..1e258c0 100644 --- a/sycls.c +++ b/sycls.c @@ -1744,6 +1744,27 @@ void Unsetenv(const char *name) { #endif +#if WITH_NAMESPACES +int Setns( + int fd, + int nstype) +{ + int _errno; + int result; + Debug2("setns(%d, 0x%x)", fd, nstype); + result = setns(fd, nstype); + if (result < 0) { + _errno = errno; + Debug2("setns() -> %d (errno=%d)", result, errno); + errno = _errno; + return result; + } + Debug1("setns() -> %d", result); + return result; +} +#endif /* WITH_NAMESPACES */ + + #if WITH_READLINE char *Readline(const char *prompt) { diff --git a/sycls.h b/sycls.h index a9dc883..6b64d27 100644 --- a/sycls.h +++ b/sycls.h @@ -167,6 +167,7 @@ void Abort(void); int Mkstemp(char *template); int Setenv(const char *name, const char *value, int overwrite); void Unsetenv(const char *name); +int Setns(int fd, int nstype); #endif /* WITH_SYCLS */ #if WITH_SYCLS @@ -271,6 +272,7 @@ void Add_history(const char *string); #define Mkstemp(t) mkstemp(t) #define Setenv(n,v,o) setenv(n,v,o) #define Unsetenv(n) unsetenv(n) +#define Setns(f,n) setns(f,n) #define Readline(p) readline(p) #define Using_history() using_history() diff --git a/sysincludes.h b/sysincludes.h index 01acdb3..8445ccb 100644 --- a/sysincludes.h +++ b/sysincludes.h @@ -177,6 +177,9 @@ #if HAVE_LINUX_EXT2_FS_H #include /* Linux ext2 filesystem definitions */ #endif +#if WITH_NAMESPACES && HAVE_SCHED_H +#include +#endif #if WITH_READLINE # if HAVE_READLINE_READLINE_H #include diff --git a/test.sh b/test.sh index ecd1b22..0fea81b 100755 --- a/test.sh +++ b/test.sh @@ -1671,7 +1671,7 @@ NAME=SYSTEMPIPES case "$TESTS" in *%$N%*|*%functions%*|*%system%*|*%pipes%*|*%$NAME%*) TEST="$NAME: simple echo via system() of cat with pipes" -testecho "$N" "$TEST" "" "sYSTEM:$CAT,pipes" "$opts" +testecho "$N" "$TEST" "" "SYSTEM:$CAT,pipes" "$opts" esac N=$((N+1)) @@ -16338,6 +16338,214 @@ esac N=$((N+1)) +# Test the netns (net namespace) feature +NAME=NETNS +case "$TESTS" in +*%$N%*|*%functions%*|*%root%*|*%namespace%*|*%netns%*|*%socket%*|*%$NAME%*) +ns=socat-$$-$N +TEST="$NAME: option netns (net namespace $ns)" +# Start a simple echo server with option netns on localhost of a net namespace; +# use a client process with option netns to send data to the net namespace +# net server and check the reply. +if ! eval $NUMCOND; then :; +elif [ "$UNAME" != Linux ]; then + $PRINTF "test $F_n $TEST... ${YELLOW}Only on Linux${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif [ $(id -u) -ne 0 -a "$withroot" -eq 0 ]; then + $PRINTF "test $F_n $TEST... ${YELLOW}Must be root${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! $(type ip >/dev/null 2>&1); then + $PRINTF "test $F_n $TEST... ${YELLOW}ip program not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! F=$(testfeats IP4 TCP LISTEN NAMESPACES); then + $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! A=$(testaddrs STDIO TCP-LISTEN TCP EXEC); then + $PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! o=$(testoptions netns) >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +newport tcp4 # or whatever proto, or drop this line +CMD0="$TRACE $SOCAT $opts --experimental TCP4-LISTEN:$PORT,netns=$ns EXEC:'od -c'" +CMD1="$TRACE $SOCAT $opts --experimental - TCP4:127.0.0.1:$PORT,netns=$ns" +printf "test $F_n $TEST... " $N +ip netns del $ns 2>/dev/null # make sure it does not exist +ip netns add $ns +ip netns exec $ns ip -4 addr add dev lo 127.0.0.1/8 +ip netns exec $ns ip link set lo up +eval "$CMD0" >/dev/null 2>"${te}0" & +pid0=$! +relsleep 1 # if no matching wait*port function +echo "$da" |$CMD1 >"${tf}1" 2>"${te}1" +rc1=$? +kill $pid0 2>/dev/null; wait +ip netns del $ns +if [ $rc1 -ne 0 ]; then + $PRINTF "$FAILED (client failed)\n" + echo "ip netns del $ns" + echo "ip netns add $ns" + echo "ip netns exec $ns ip -4 addr add dev lo 127.0.0.1/8" + echo "ip netns exec $ns ip link set lo up" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + echo "ip netns del $ns" + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" +elif echo "$da" |od -c |diff - ${tf}1 >"$tdiff"; then + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then + echo "ip netns del $ns" + echo "ip netns add $ns" + echo "ip netns exec $ns ip -4 addr add dev lo 127.0.0.1/8" + echo "ip netns exec $ns ip link set lo up" + fi + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD1"; fi + if [ "$DEBUG" ]; then cat "${te}1" >&2; fi + if [ "$VERBOSE" ]; then echo "ip netns del $ns"; fi + numOK=$((numOK+1)) +else + $PRINTF "$FAILED (bad output)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + diff: + cat "$tdiff" + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + +# Test the netns (net namespace) feature with EXEC and reset +NAME=NETNS_EXEC +case "$TESTS" in +*%$N%*|*%functions%*|*%root%*|*%namespace%*|*%netns%*|*%socket%*|*%$NAME%*) +ns=socat-$$-test$N +TEST="$NAME: option netns with EXEC (net namespace $ns)" +# Start a simple server with option netns on localhost of a net namespace that +# stores data it receives; +# use a middle process that EXECs a socat client that connects to the server on +# the net namespace; then it listens on default namespace. +# With a third command line connect and send data to the middle process. +# When the data received by the server is correct the test succeeded. +if ! eval $NUMCOND; then :; +elif [ "$UNAME" != Linux ]; then + $PRINTF "test $F_n $TEST... ${YELLOW}Only on Linux${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif [ $(id -u) -ne 0 -a "$withroot" -eq 0 ]; then + $PRINTF "test $F_n $TEST... ${YELLOW}Must be root${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! $(type ip >/dev/null 2>&1); then + $PRINTF "test $F_n $TEST... ${YELLOW}ip program not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! F=$(testfeats IP4 ABSTRACT_UNIXSOCKET UDP LISTEN NAMESPACES STDIO); then + $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! A=$(testaddrs ABSTRACT-RECV ABSTRACT-SENDTO CREATE EXEC UDP4-RECV STDIO UDP4); then + $PRINTF "test $F_n $TEST... ${YELLOW}Address $a not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! o=$(testoptions netns) >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! runsip4 >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}IPv4 not available${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else +tf="$td/test$N.stdout" +te="$td/test$N.stderr" +tdiff="$td/test$N.diff" +da="test$N $(date) $RANDOM" +newport udp4 +CMD0="$TRACE $SOCAT $opts --experimental -u -T 1 ABSTRACT-RECV:test$N,netns=$ns CREAT:${tf}0" +CMD1="$TRACE $SOCAT $opts --experimental -U -T 1 EXEC:\"$SOCAT STDIO ABSTRACT-SENDTO\:test$N\",netns=$ns UDP4-RECV:$PORT" +CMD2="$TRACE $SOCAT $opts -u STDIO UDP4:127.0.0.1:$PORT" +printf "test $F_n $TEST... " $N +ip netns del $ns 2>/dev/null # make sure it does not exist +ip netns add $ns +#ip netns exec $ns ip -4 addr add dev lo 127.0.0.1/8 +#ip netns exec $ns ip link set lo up +eval "$CMD0" >/dev/null 2>"${te}0" & +pid0=$! +relsleep 1 # if no matching wait*port function +eval "$CMD1" 2>${te}1 & +pid1=$! +relsleep 1 +echo "$da" |$CMD2 >"${tf}2" 2>"${te}2" +rc1=$? +kill $pid0 $pid1 2>/dev/null; wait +ip netns del $ns +if [ $rc1 -ne 0 ]; then + $PRINTF "$FAILED (client failed)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + echo "$CMD2" + cat "${te}2" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" +elif echo "$da" |diff - ${tf}0 >"$tdiff" 2>/dev/null; then + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD1"; fi + if [ "$DEBUG" ]; then cat "${te}1" >&2; fi + if [ "$VERBOSE" ]; then echo "$CMD2"; fi + if [ "$DEBUG" ]; then cat "${te}2" >&2; fi + numOK=$((numOK+1)) +else + $PRINTF "$FAILED (bad output)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "$CMD1" + cat "${te}1" >&2 + echo "$CMD2" + cat "${te}2" >&2 + echo diff: + cat "$tdiff" + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" +fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + # end of common tests ################################################################################## diff --git a/xio-exec.c b/xio-exec.c index 56c2929..89be2a1 100644 --- a/xio-exec.c +++ b/xio-exec.c @@ -111,9 +111,10 @@ static int xioopen_exec(int argc, const char *argv[], struct opt *opts, Exit(1); } + dropopts(opts, PH_PASTEXEC); if ((numleft = leftopts(opts)) > 0) { - Error1("%d option(s) could not be used", numleft); showleft(opts); + Error1("INTERNAL: %d option(s) remained unused", numleft); return STAT_NORETRY; } diff --git a/xio-ipapp.c b/xio-ipapp.c index 68693ed..44ec8c1 100644 --- a/xio-ipapp.c +++ b/xio-ipapp.c @@ -46,7 +46,8 @@ int xioopen_ipapp_connect(int argc, const char *argv[], struct opt *opts, xfd->howtoend = END_SHUTDOWN; - if (applyopts_single(xfd, opts, PH_INIT) < 0) return -1; + if (applyopts_single(xfd, opts, PH_INIT) < 0) + return -1; applyopts(-1, opts, PH_INIT); retropt_bool(opts, OPT_FORK, &dofork); diff --git a/xio-namespaces.c b/xio-namespaces.c new file mode 100644 index 0000000..1c2bc3d --- /dev/null +++ b/xio-namespaces.c @@ -0,0 +1,73 @@ +/* Source: xio-namespaces.c */ +/* Copyright Gerhard Rieger and contributors (see file CHANGES) */ +/* Published under the GNU General Public License V.2, see file COPYING */ + +/* This file contains Linux namespace related code */ + +#include "xiosysincludes.h" +#include "xioopen.h" + +#include "xio-namespaces.h" + +#if WITH_NAMESPACES + +const struct optdesc opt_set_netns = { "netns", NULL, OPT_SET_NETNS, GROUP_PROCESS, PH_INIT, TYPE_STRING, OFUNC_SET_NAMESPACE, 0, 0, 0 }; + + +/* Set the given namespace. Requires root or the appropriate CAP_*- + Returns 0 on success, or -1 on error. */ +int xio_set_namespace( + const char *nstype, + const char *nsname) +{ + char nspath[PATH_MAX]; + int nsfd; + int rc; + + if (!xioparms.experimental) { + Error1("option \"%s\" requires use of --experimental", nstype); + } + + snprintf(nspath, sizeof(nspath)-1, "/run/%s/%s", nstype, nsname); + Info1("switching to net namespace \"%s\"", nsname); + nsfd = Open(nspath, O_RDONLY|O_CLOEXEC, 000); + if (nsfd < 0) { + Error2("open(%s, O_RDONLY|O_CLOEXEC): %s", nspath, strerror(errno)); + return -1; + } + rc = Setns(nsfd, CLONE_NEWNET); + if (rc < 0) { + Error2("setns(%d, CLONE_NEWNET): %s", nsfd, strerror(errno)); + Close(nsfd); + } + Close(nsfd); + return 0; +} + +/* Sets the given namespace to that of process 1, this is assumed to be the + systems default. + Returns 0 on success, or -1 on error. */ +int xio_reset_namespace( + const char *nstype) +{ + char nspath[PATH_MAX]; + int nsfd; + int rc; + + snprintf(nspath, sizeof(nspath)-1, "/proc/1/ns/%s", nstype); + Info("switching back to default namespace"); + nsfd = Open(nspath, O_RDONLY|O_CLOEXEC, 000); + if (nsfd < 0) { + Error2("open(%s, O_RDONLY|O_CLOEXEC): %s", nspath, strerror(errno)); + return -1; + } + rc = Setns(nsfd, CLONE_NEWNET); + if (rc < 0) { + Error2("setns(%d, CLONE_NEWNET): %s", nsfd, strerror(errno)); + Close(nsfd); + } + Close(nsfd); + return 0; +} + +#endif /* WITH_NAMESPACES */ diff --git a/xio-namespaces.h b/xio-namespaces.h new file mode 100644 index 0000000..8aed47a --- /dev/null +++ b/xio-namespaces.h @@ -0,0 +1,18 @@ +/* Source: xio-namespaces.h */ +/* Copyright Gerhard Rieger and contributors (see file CHANGES) */ +/* Published under the GNU General Public License V.2, see file COPYING */ + +#ifndef __xio_namespaces_h_included +#define __xio_namespaces_h_included 1 + +#if WITH_NAMESPACES + +extern const struct optdesc opt_set_netns; +extern const struct optdesc opt_reset_netns; + +extern int xio_set_namespace(const char *nstype, const char *nsname); +extern int xio_reset_namespace(const char *nstype); + +#endif /* WITH_NAMESPACES */ + +#endif /* __xio_namespaces_h_included */ diff --git a/xio-pipe.c b/xio-pipe.c index c860fb8..c4c8ceb 100644 --- a/xio-pipe.c +++ b/xio-pipe.c @@ -66,8 +66,8 @@ static int xioopen_fifo_unnamed(xiofile_t *sock, struct opt *opts) { } if ((numleft = leftopts(opts)) > 0) { - Error1("%d option(s) could not be used", numleft); showleft(opts); + Error1("INTERNAL: %d option(s) remained unused", numleft); } Notice("writing to and reading from unnamed pipe"); return 0; diff --git a/xio-process.c b/xio-process.c index b43a480..5820895 100644 --- a/xio-process.c +++ b/xio-process.c @@ -70,4 +70,3 @@ int _xioopen_setdelayeduser(void) { } return 0; } - diff --git a/xio-progcall.c b/xio-progcall.c index eef8be0..337db1f 100644 --- a/xio-progcall.c +++ b/xio-progcall.c @@ -374,11 +374,7 @@ int _xioopen_foxec(int xioflags, /* XIO_RDONLY etc. */ /* this for parent, was after fork */ fd->fd = sv[0]; applyopts(fd->fd, popts, PH_FD); - applyopts(fd->fd, popts, PH_LATE); - if (applyopts_single(fd, popts, PH_LATE) < 0) return -1; } - /*0 if ((optpr = copyopts(*copts, GROUP_PROCESS)) == NULL) - return -1;*/ retropt_bool(*copts, OPT_STDERR, &withstderr); xiosetchilddied(); /* set SIGCHLD handler */ @@ -390,7 +386,8 @@ int _xioopen_foxec(int xioflags, /* XIO_RDONLY etc. */ return -1; } } - if (!withfork || pid == 0) { /* child */ + if (!withfork || pid == 0) { /* in single process, or in child */ + applyopts_optgroup(-1, *copts, PH_PRESOCKET, PH_PRESOCKET, GROUP_PROCESS); if (withfork) { Close(trigger[0]); /* in child: not needed here */ /* The child should have default handling for SIGCHLD. */ @@ -593,9 +590,12 @@ int _xioopen_foxec(int xioflags, /* XIO_RDONLY etc. */ if (applyopts_single(fd, popts, PH_LATE) < 0) return -1; applyopts_signal(fd, popts); + applyopts(-1, popts, PH_LATE); + applyopts(-1, popts, PH_LATE2); + applyopts(-1, popts, PH_PASTEXEC); if ((numleft = leftopts(popts)) > 0) { - Error1("%d option(s) could not be used", numleft); showleft(popts); + Error1("INTERNAL: %d option(s) remained unused", numleft); return STAT_NORETRY; } diff --git a/xio-socket.c b/xio-socket.c index a90cc99..81b827a 100644 --- a/xio-socket.c +++ b/xio-socket.c @@ -2024,6 +2024,7 @@ xiosocket(struct opt *opts, int pf, int socktype, int proto, int msglevel) { retropt_int(opts, OPT_SO_TYPE, &socktype); retropt_int(opts, OPT_SO_PROTOTYPE, &proto); + applyopts(-1, opts, PH_PRESOCKET); result = Socket(pf, socktype, proto); if (result < 0) { int _errno = errno; diff --git a/xio-streams.c b/xio-streams.c index 06be390..298959c 100644 --- a/xio-streams.c +++ b/xio-streams.c @@ -53,7 +53,7 @@ void dummy(void) { if (Ioctl(fd, I_PUSH, opt->value.u_string) < 0) { Warn3("ioctl(%d, I_PUSH, \"%s\"): %s", fd, opt->value.u_string, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #endif #if 0 diff --git a/xio-system.c b/xio-system.c index 7f859d0..fe42473 100644 --- a/xio-system.c +++ b/xio-system.c @@ -50,8 +50,8 @@ static int xioopen_system(int argc, const char *argv[], struct opt *opts, } if ((numleft = leftopts(opts)) > 0) { - Error1("%d option(s) could not be used", numleft); showleft(opts); + Error1("INTERNAL: %d option(s) remained unused", numleft); return STAT_NORETRY; } diff --git a/xio-tun.c b/xio-tun.c index 7fd096d..718272b 100644 --- a/xio-tun.c +++ b/xio-tun.c @@ -79,6 +79,7 @@ static int xioopen_tun(int argc, const char *argv[], struct opt *opts, int xiofl /*========================= the tunnel interface =========================*/ Notice("creating tunnel network interface"); + applyopts_optgroup(-1, opts, PH_PRESOCKET, PH_PRESOCKET, GROUP_PROCESS); if ((result = _xioopen_open(tundevice, rw, opts)) < 0) return result; xfd->stream.fd = result; diff --git a/xiomodes.h b/xiomodes.h index 4d0b303..7a20547 100644 --- a/xiomodes.h +++ b/xiomodes.h @@ -34,6 +34,7 @@ #include "xio-proxy.h" #include "xio-vsock.h" #endif /* _WITH_SOCKET */ +#include "xio-namespaces.h" #include "xio-progcall.h" #include "xio-exec.h" #include "xio-system.h" diff --git a/xioopen.c b/xioopen.c index 855dd90..1a87ac8 100644 --- a/xioopen.c +++ b/xioopen.c @@ -592,14 +592,38 @@ int xioopen_single(xiofile_t *xfd, int xioflags) { const struct addrdesc *addrdesc; int result; /* Values to be saved until xioopen() is finished */ - int do_res; #if HAVE_RESOLV_H + int do_res; struct __res_state save_res; #endif /* HAVE_RESOLV_H */ +#if WITH_NAMESPACES + char *temp_netns; + int save_netfd = -1; +#endif + int rc; if (applyopts_single(sfd, sfd->opts, PH_OFFSET) < 0) return -1; +#if WITH_NAMESPACES + if (retropt_string(sfd->opts, OPT_SET_NETNS, &temp_netns) >= 0) { + char nspath[PATH_MAX]; + + snprintf(nspath, sizeof(nspath)-1, "/proc/"F_pid"/ns/net", + Getpid()); + save_netfd = Open(nspath, O_RDONLY|O_CLOEXEC, 000); + if (save_netfd < 0) { + Error2("open(%s, O_RDONLY|O_CLOEXEC): %s", nspath, strerror(errno)); + return -1; + } + + rc = xio_set_namespace("netns", temp_netns); + free(temp_netns); + if (rc < 0) + return -1; + } +#endif /* WITH_NAMESPACES */ + #if HAVE_RESOLV_H if ((do_res = xio_res_init(sfd, &save_res)) < 0) return STAT_NORETRY; @@ -627,6 +651,17 @@ int xioopen_single(xiofile_t *xfd, int xioflags) { xio_res_init(sfd, &save_res); #endif /* HAVE_RESOLV_H */ +#if WITH_NAMESPACES + if (save_netfd >= 0) { + rc = Setns(save_netfd, CLONE_NEWNET); + if (rc < 0) { + Error2("setns(%d, CLONE_NEWNET): %s", save_netfd, strerror(errno)); + Close(save_netfd); + return STAT_NORETRY; + } + } +#endif /* WITH_NAMESPACES */ + return result; } diff --git a/xioopts.c b/xioopts.c index 68eb5b5..f31bbaa 100644 --- a/xioopts.c +++ b/xioopts.c @@ -1011,6 +1011,9 @@ const struct optname optionnames[] = { IF_ANY ("ndelay", &opt_o_ndelay) #else IF_ANY ("ndelay", &opt_nonblock) +#endif +#if WITH_NAMESPACES + IF_ANY ("netns", &opt_set_netns) #endif IF_NAMED ("new", &opt_unlink_early) #ifdef NLDLY @@ -2766,7 +2769,7 @@ int leftopts(const struct opt *opts) { if (!opts) return 0; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR) { ++num; } ++opt; @@ -2779,7 +2782,7 @@ int showleft(const struct opt *opts) { const struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR) { Warn1("showleft(): option \"%s\" not inquired", opt->desc->defname); } ++opt; @@ -2852,7 +2855,8 @@ int retropt(struct opt *opts, int optcode, union integral *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { *result = opt->value; opt->desc = ODESC_DONE; return 0; @@ -2867,7 +2871,8 @@ static struct opt *xio_findopt(struct opt *opts, int optcode) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { return opt; } ++opt; @@ -2895,7 +2900,8 @@ int retropt_bool(struct opt *opts, int optcode, bool *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { *result = opt->value.u_bool; opt->desc = ODESC_DONE; return 0; @@ -2914,7 +2920,8 @@ int retropt_short(struct opt *opts, int optcode, short *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { *result = opt->value.u_short; opt->desc = ODESC_DONE; return 0; @@ -2933,7 +2940,8 @@ int retropt_ushort(struct opt *opts, int optcode, unsigned short *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { *result = opt->value.u_ushort; opt->desc = ODESC_DONE; return 0; @@ -2951,7 +2959,8 @@ int retropt_int(struct opt *opts, int optcode, int *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { char *rest; switch (opt->desc->type) { case TYPE_INT: *result = opt->value.u_int; break; @@ -3012,7 +3021,8 @@ int retropt_uint(struct opt *opts, int optcode, unsigned int *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { *result = opt->value.u_uint; opt->desc = ODESC_DONE; return 0; @@ -3030,7 +3040,8 @@ int retropt_long(struct opt *opts, int optcode, long *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { *result = opt->value.u_long; opt->desc = ODESC_DONE; return 0; @@ -3048,7 +3059,8 @@ int retropt_ulong(struct opt *opts, int optcode, unsigned long *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { *result = opt->value.u_ulong; opt->desc = ODESC_DONE; return 0; @@ -3066,7 +3078,8 @@ int retropt_flag(struct opt *opts, int optcode, flags_t *result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { if (opt->value.u_bool) { *result |= opt->desc->major; } else { @@ -3092,7 +3105,8 @@ int retropt_string(struct opt *opts, int optcode, char **result) { struct opt *opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->optcode == optcode) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->optcode == optcode) { if (opt->value.u_string == NULL) { *result = NULL; } else if ((*result = strdup(opt->value.u_string)) == NULL) { @@ -3231,30 +3245,28 @@ int retropt_bind(struct opt *opts, #endif /* _WITH_SOCKET */ -/* applies to fd all options belonging to phase */ -/* note: not all options can be applied this way (e.g. OFUNC_SPEC with PH_OPEN) - implemented are: OFUNC_FCNTL, OFUNC_SOCKOPT (probably not all types), - OFUNC_TERMIOS_FLAG, OFUNC_TERMIOS_PATTERN, and some OFUNC_SPEC */ -int applyopts(int fd, struct opt *opts, enum e_phase phase) { - struct opt *opt; - - opt = opts; while (opt && opt->desc != ODESC_END) { - if (opt->desc == ODESC_DONE || - (phase != PH_ALL && opt->desc->phase != phase)) { - ++opt; continue; } +/* Applies to FD all options belonging to phase */ +/* Note: not all options can be applied this way */ +int applyopt( + int fd, + struct opt *opt) +{ + if (opt->desc == ODESC_DONE || opt->desc == ODESC_ERROR) + return 0; if (opt->desc->func == OFUNC_SEEK32) { if (Lseek(fd, opt->value.u_off, opt->desc->major) < 0) { Error4("lseek(%d, "F_off", %d): %s", fd, opt->value.u_off, opt->desc->major, strerror(errno)); + return -1; } #if HAVE_LSEEK64 } else if (opt->desc->func == OFUNC_SEEK64) { - /*! this depends on off64_t atomic type */ if (Lseek64(fd, opt->value.u_off64, opt->desc->major) < 0) { Error4("lseek64(%d, "F_off64", %d): %s", fd, opt->value.u_off64, opt->desc->major, strerror(errno)); + return -1; } #endif /* HAVE_LSEEK64 */ @@ -3265,7 +3277,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if ((flag = Fcntl(fd, opt->desc->major-1)) < 0) { Error3("fcntl(%d, %d): %s", fd, opt->desc->major, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } else { if (opt->value.u_bool) { flag |= opt->desc->minor; @@ -3275,7 +3287,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Fcntl_l(fd, opt->desc->major, flag) < 0) { Error4("fcntl(%d, %d, %d): %s", fd, opt->desc->major, flag, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } @@ -3283,7 +3295,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Ioctl(fd, opt->desc->major, (void *)&opt->value) < 0) { Error4("ioctl(%d, 0x%x, %p): %s", fd, opt->desc->major, (void *)&opt->value, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } else if (opt->desc->func == OFUNC_IOCTL_MASK_LONG) { @@ -3295,14 +3307,14 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Ioctl(fd, getreq, (void *)&val) < 0) { Error4("ioctl(%d, 0x%x, %p): %s", fd, opt->desc->major, (void *)&val, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } val &= ~mask; if (opt->value.u_bool) val |= mask; if (Ioctl(fd, setreq, (void *)&val) < 0) { Error4("ioctl(%d, 0x%x, %p): %s", fd, opt->desc->major, (void *)&val, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } else if (opt->desc->func == OFUNC_IOCTL_GENERIC) { @@ -3311,40 +3323,41 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Ioctl(fd, opt->value.u_int, NULL) < 0) { Error3("ioctl(%d, 0x%x, NULL): %s", fd, opt->value.u_int, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_INT_INT: if (Ioctl_int(fd, opt->value.u_int, opt->value2.u_int) < 0) { Error4("ioctl(%d, 0x%x, 0x%x): %s", fd, opt->value.u_int, opt->value2.u_int, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_INT_INTP: if (Ioctl(fd, opt->value.u_int, (void *)&opt->value2.u_int) < 0) { Error4("ioctl(%d, 0x%x, %p): %s", fd, opt->value.u_int, (void *)&opt->value2.u_int, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_INT_BIN: if (Ioctl(fd, opt->value.u_int, (void *)opt->value2.u_bin.b_data) < 0) { Error4("ioctl(%d, 0x%x, %p): %s", fd, opt->value.u_int, (void *)opt->value2.u_bin.b_data, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_INT_STRING: if (Ioctl(fd, opt->value.u_int, (void *)opt->value2.u_string) < 0) { Error4("ioctl(%d, 0x%x, %p): %s", fd, opt->value.u_int, (void *)opt->value2.u_string, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; default: Error1("ioctl() data type %d not implemented", opt->desc->type); + return -1; } #if _WITH_SOCKET @@ -3361,7 +3374,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error6("setsockopt(%d, %d, %d, {%d,%d}, "F_Zu, fd, opt->desc->major, opt->desc->minor, lingstru.l_onoff, lingstru.l_linger, sizeof(lingstru)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #endif /* HAVE_STRUCT_LINGER */ } else { @@ -3374,7 +3387,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { fd, opt->desc->major, opt->desc->minor, opt->value.u_bin.b_data, opt->value.u_bin.b_len, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_BOOL: @@ -3385,7 +3398,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { opt->desc->major, opt->desc->minor, opt->value.u_bool, sizeof(opt->value.u_bool), strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_BYTE: @@ -3394,7 +3407,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error6("setsockopt(%d, %d, %d, {%u}, "F_Zu"): %s", fd, opt->desc->major, opt->desc->minor, opt->value.u_byte, sizeof(uint8_t), strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_INT: @@ -3403,7 +3416,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error6("setsockopt(%d, %d, %d, {%d}, "F_Zu"): %s", fd, opt->desc->major, opt->desc->minor, opt->value.u_int, sizeof(int), strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_INT_NULL: @@ -3413,7 +3426,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error6("setsockopt(%d, %d, %d, {%d}, "F_Zu"): %s", fd, opt->desc->major, opt->desc->minor, opt->value.u_int, sizeof(int), strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_LONG: @@ -3422,7 +3435,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error6("setsockopt(%d, %d, %d, {%ld}, "F_Zu"): %s", fd, opt->desc->major, opt->desc->minor, opt->value.u_long, sizeof(long), strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_STRING: @@ -3433,7 +3446,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { fd, opt->desc->major, opt->desc->minor, opt->value.u_string, strlen(opt->value.u_string)+1, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_UINT: @@ -3443,7 +3456,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { fd, opt->desc->major, opt->desc->minor, opt->value.u_uint, sizeof(unsigned int), strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case TYPE_TIMEVAL: @@ -3453,7 +3466,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { fd, opt->desc->major, opt->desc->minor, opt->value.u_timeval.tv_sec, opt->value.u_timeval.tv_usec, sizeof(struct timeval), strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; #if HAVE_STRUCT_LINGER @@ -3468,7 +3481,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { fd, opt->desc->major, opt->desc->minor, lingstru.l_onoff, lingstru.l_linger, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } break; @@ -3476,13 +3489,13 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { #if defined(HAVE_STRUCT_IP_MREQ) || defined (HAVE_STRUCT_IP_MREQN) case TYPE_IP_MREQN: /* handled in applyopts_single */ - ++opt; continue; + break; #endif /* defined(HAVE_STRUCT_IP_MREQ) || defined (HAVE_STRUCT_IP_MREQN) */ #if defined(HAVE_STRUCT_GROUP_SOURCE_REQ) case TYPE_GROUP_SOURCE_REQ: /* handled in applyopts_single */ - ++opt; continue; + break; #endif /* defined(HAVE_STRUCT_GROUP_SOURCE_REQ) */ /*! still many types missing; implement on demand */ @@ -3494,6 +3507,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { fd, opt->desc->major, opt->desc->minor, *(uint32_t *)&opt->value.u_ip4addr, sizeof(opt->value.u_ip4addr), strerror(errno)); + return -1; } break; #endif /* defined(WITH_IP4) */ @@ -3505,7 +3519,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Warn1("applyopts(): type %d not implemented", opt->desc->type); #endif - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } @@ -3521,7 +3535,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error6("getsockopt(%d, %d, %d, %p, {"F_socklen"}): %s", fd, opt->desc->major, opt->desc->minor, data, oldlen, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } memcpy(&data[oldlen], opt->value.u_bin.b_data, MIN(opt->value.u_bin.b_len, sizeof(data)-oldlen)); @@ -3532,7 +3546,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error6("setsockopt(%d, %d, %d, %p, %d): %s", fd, opt->desc->major, opt->desc->minor, data, newlen, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; default: @@ -3548,6 +3562,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error6("setsockopt(%d, %d, %d, {%d}, "F_Zu"): %s", fd, opt->value.u_int, opt->value2.u_int, opt->value3.u_int, sizeof(int), strerror(errno)); + return -1; } break; case TYPE_INT_INT_BIN: @@ -3556,6 +3571,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { Error5("setsockopt(%d, %d, %d, {...}, "F_Zu"): %s", fd, opt->value.u_int, opt->value2.u_int, opt->value3.u_bin.b_len, strerror(errno)); + return -1; } break; case TYPE_INT_INT_STRING: @@ -3566,11 +3582,13 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { fd, opt->value.u_int, opt->value2.u_int, opt->value3.u_string, strlen(opt->value3.u_string)+1, strerror(errno)); + return -1; } break; default: Error1("setsockopt() data type %d not implemented", opt->desc->type); + return -1; } #endif /* _WITH_SOCKET */ @@ -3579,7 +3597,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Flock(fd, opt->desc->major) < 0) { Error3("flock(%d, %d): %s", fd, opt->desc->major, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #endif /* defined(HAVE_FLOCK) */ @@ -3591,7 +3609,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Fchown(fd, opt->value.u_uidt, -1) < 0) { Error3("fchown(%d, "F_uid", -1): %s", fd, opt->value.u_uidt, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case OPT_GROUP: @@ -3599,7 +3617,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Fchown(fd, -1, opt->value.u_gidt) < 0) { Error3("fchown(%d, -1, "F_gid"): %s", fd, opt->value.u_gidt, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case OPT_PERM: @@ -3607,14 +3625,14 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Fchmod(fd, opt->value.u_modet) < 0) { Error3("fchmod(%d, %u): %s", fd, opt->value.u_modet, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case OPT_FTRUNCATE32: if (Ftruncate(fd, opt->value.u_off) < 0) { Error3("ftruncate(%d, "F_off"): %s", fd, opt->value.u_off, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; #if HAVE_FTRUNCATE64 @@ -3622,7 +3640,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Ftruncate64(fd, opt->value.u_off64) < 0) { Error3("ftruncate64(%d, "F_off64"): %s", fd, opt->value.u_off64, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #endif /* HAVE_FTRUNCATE64 */ break; @@ -3639,7 +3657,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { l.l_pid = 0; /* hope this uses our current process */ if (Fcntl_lock(fd, opt->desc->major, &l) < 0) { Error3("fcntl(%d, %d, {type=F_WRLCK,whence=SEEK_SET,start=0,len=LONG_MAX,pid=0}): %s", fd, opt->desc->major, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } break; @@ -3648,7 +3666,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Setuid(opt->value.u_uidt) < 0) { Error2("setuid("F_uid"): %s", opt->value.u_uidt, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case OPT_SETGID_EARLY: @@ -3656,7 +3674,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Setgid(opt->value.u_gidt) < 0) { Error2("setgid("F_gid"): %s", opt->value.u_gidt, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } break; case OPT_SUBSTUSER_EARLY: @@ -3666,36 +3684,44 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if ((pwd = getpwuid(opt->value.u_uidt)) == NULL) { Error1("getpwuid("F_uid"): no such user", opt->value.u_uidt); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } if (Initgroups(pwd->pw_name, pwd->pw_gid) < 0) { Error3("initgroups(%s, "F_gid"): %s", pwd->pw_name, pwd->pw_gid, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } if (Setgid(pwd->pw_gid) < 0) { Error2("setgid("F_gid"): %s", pwd->pw_gid, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } if (Setuid(opt->value.u_uidt) < 0) { Error2("setuid("F_uid"): %s", opt->value.u_uidt, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #if 1 - if (setenv("USER", pwd->pw_name, 1) < 0) + if (setenv("USER", pwd->pw_name, 1) < 0) { Error1("setenv(\"USER\", \"%s\", 1): insufficient space", pwd->pw_name); - if (setenv("LOGNAME", pwd->pw_name, 1) < 0) + return -1; + } + if (setenv("LOGNAME", pwd->pw_name, 1) < 0) { Error1("setenv(\"LOGNAME\", \"%s\", 1): insufficient space", pwd->pw_name); - if (setenv("HOME", pwd->pw_dir, 1) < 0) + return -1; + } + if (setenv("HOME", pwd->pw_dir, 1) < 0) { Error1("setenv(\"HOME\", \"%s\", 1): insufficient space", pwd->pw_dir); - if (setenv("SHELL", pwd->pw_shell, 1) < 0) + return -1; + } + if (setenv("SHELL", pwd->pw_shell, 1) < 0) { Error1("setenv(\"SHELL\", \"%s\", 1): insufficient space", pwd->pw_shell); + return -1; + } #endif } break; @@ -3707,24 +3733,24 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if ((pwd = getpwuid(opt->value.u_uidt)) == NULL) { Error1("getpwuid("F_uid"): no such user", opt->value.u_uidt); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } delayeduser_uid = opt->value.u_uidt; delayeduser_gid = pwd->pw_gid; if ((delayeduser_name = strdup(pwd->pw_name)) == NULL) { Error1("strdup("F_Zu"): out of memory", strlen(pwd->pw_name)+1); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } if ((delayeduser_dir = strdup(pwd->pw_dir)) == NULL) { Error1("strdup("F_Zu"): out of memory", strlen(pwd->pw_dir)+1); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } if ((delayeduser_shell = strdup(pwd->pw_shell)) == NULL) { Error1("strdup("F_Zu"): out of memory", strlen(pwd->pw_shell)+1); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } /* function to get all supplementary groups of user */ delayeduser_ngids = sizeof(delayeduser_gids)/sizeof(gid_t); @@ -3739,10 +3765,11 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (Chroot(opt->value.u_string) < 0) { Error2("chroot(\"%s\"): %s", opt->value.u_string, strerror(errno)); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } if (Chdir("/") < 0) { Error1("chdir(\"/\"): %s", strerror(errno)); + return -1; } break; case OPT_SETSID: @@ -3754,6 +3781,7 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { } else { if (Setsid() < 0) { Error1("setsid(): %s", strerror(errno)); + return -1; } } } @@ -3805,15 +3833,15 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { break; #endif /* _WITH_INTERFACE */ - default: Error1("applyopts(): option \"%s\" not implemented", + default: Error1("INTERNAL: applyopts(): option \"%s\" not implemented", opt->desc->defname); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #if WITH_TERMIOS } else if (opt->desc->func == OFUNC_TERMIOS_FLAG) { if (xiotermiosflag_applyopt(fd, opt) < 0) { - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } else if (opt->desc->func == OFUNC_TERMIOS_VALUE) { @@ -3821,33 +3849,33 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { (opt->value.u_uint << opt->desc->arg3)) { Error2("option %s: invalid value %u", opt->desc->defname, opt->value.u_uint); - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } if (xiotermios_value(fd, opt->desc->major, opt->desc->minor, (opt->value.u_uint << opt->desc->arg3) & opt->desc->minor) < 0) { - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } else if (opt->desc->func == OFUNC_TERMIOS_PATTERN) { if (xiotermios_value(fd, opt->desc->major, opt->desc->arg3, opt->desc->minor) < 0) { - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } } else if (opt->desc->func == OFUNC_TERMIOS_CHAR) { if (xiotermios_char(fd, opt->desc->major, opt->value.u_byte) < 0) { - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #ifdef HAVE_TERMIOS_ISPEED } else if (opt->desc->func == OFUNC_TERMIOS_SPEED) { if (xiotermios_speed(fd, opt->desc->major, opt->value.u_uint) < 0) { - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #endif /* HAVE_TERMIOS_ISPEED */ } else if (opt->desc->func == OFUNC_TERMIOS_SPEC) { if (xiotermios_spec(fd, opt->desc->optcode) < 0) { - opt->desc = ODESC_ERROR; ++opt; continue; + return -1; } #endif /* WITH_TERMIOS */ @@ -3864,14 +3892,30 @@ int applyopts(int fd, struct opt *opts, enum e_phase phase) { if (opt->desc->func != OFUNC_EXT && opt->desc->func != OFUNC_SIGNAL) { Error1("applyopts(): internal error: option \"%s\" does not apply", opt->desc->defname); - opt->desc = ODESC_ERROR; - ++opt; - continue; + return -1; } - ++opt; - continue; + return 0; } opt->desc = ODESC_DONE; + return 0; +} + +/* Note: not all options can be applied this way (e.g. OFUNC_SPEC with PH_OPEN) + implemented are: OFUNC_FCNTL, OFUNC_SOCKOPT (probably not all types), + OFUNC_TERMIOS_FLAG, OFUNC_TERMIOS_PATTERN, and some OFUNC_SPEC */ +int applyopts(int fd, struct opt *opts, enum e_phase phase) +{ + struct opt *opt; + int rc; + + opt = opts; + while (opt && opt->desc != ODESC_END) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + (phase == PH_ALL || phase == opt->desc->phase)) { + rc = applyopt(fd, opt); + if (rc < 0) + opt->desc = ODESC_ERROR; + } ++opt; } @@ -3898,6 +3942,56 @@ int applyopts2(int fd, struct opt *opts, unsigned int from, unsigned int to) { return 0; } +/* Apply and consume all options of type FLAG and group. + Return 0 when everything went right, or -1 if an error occurred. */ +int applyopts_optgroup( + int fd, + struct opt *opts, + unsigned int from, /* -1: from first phase, not in order */ + unsigned int to, /* -1: to last phase, not in order */ + groups_t groups) +{ + struct opt *opt = opts; + unsigned int i; + + if (opts == NULL) + return 0; + + /* Just apply all opts matching from/to phases, in their stored order */ + if (from < 0 || to < 0) { + if (from < 0) + from = 0; + if (to < 0) + to = UINT_MAX; + while (opt->desc != ODESC_END) { + if (opt->desc == ODESC_DONE || opt->desc == ODESC_ERROR) + continue; + if ((opt->desc->group & groups) == 0) + continue; + if (opt->desc->phase < from || + opt->desc->phase > to) + continue; + applyopt(fd, opt); + /* Dont check rc: on error is should not come here */ + + ++opt; + } + } + + /* Just apply all opts from, to phase, in their phases order */ + for (i = from; i <= to; ++i) { + while (opt->desc != ODESC_END) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + (opt->desc->group & groups) && opt->desc->phase == i) { + applyopt(fd, opt); + /* Dont check rc: on error is should not come here */ + } + ++opt; + } + } + return 0; +} + /* apply and consume all options of type FLAG and group. Return 0 if everything went right, or -1 if an error occurred. */ int applyopts_flags(struct opt *opts, groups_t group, flags_t *result) { @@ -3906,7 +4000,7 @@ int applyopts_flags(struct opt *opts, groups_t group, flags_t *result) { if (!opts) return 0; while (opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && (opt->desc->group & group)) { if (opt->desc->func == OFUNC_FLAG) { if (opt->value.u_bool) { @@ -3927,7 +4021,6 @@ int applyopts_flags(struct opt *opts, groups_t group, flags_t *result) { } - /* set the FD_CLOEXEC fcntl if the options do not set it to 0 */ int applyopts_cloexec(int fd, struct opt *opts) { bool docloexec = 1; @@ -4002,7 +4095,7 @@ int applyopts_offset(struct single *xfd, struct opt *opts) { struct opt *opt; opt = opts; while (opt->desc != ODESC_END) { - if ((opt->desc == ODESC_DONE) || + if ((opt->desc == ODESC_DONE || opt->desc == ODESC_ERROR) || opt->desc->func != OFUNC_OFFSET) { ++opt; continue; } @@ -4022,7 +4115,7 @@ int applyopts_single(struct single *xfd, struct opt *opts, enum e_phase phase) { if (!opts) return 0; opt = opts; while (opt->desc != ODESC_END) { - if ((opt->desc == ODESC_DONE) || + if ((opt->desc == ODESC_DONE || opt->desc == ODESC_ERROR) || (opt->desc->phase != phase && phase != PH_ALL)) { /* option not handled in this function */ ++opt; continue; @@ -4193,7 +4286,8 @@ int applyopts_signal(struct single *xfd, struct opt *opts) { if (!opts) return 0; opt = opts; while (opt->desc != ODESC_END) { - if (opt->desc == ODESC_DONE || opt->desc->func != OFUNC_SIGNAL) { + if (opt->desc == ODESC_DONE || opt->desc == ODESC_ERROR || + opt->desc->func != OFUNC_SIGNAL) { ++opt; continue; } @@ -4223,10 +4317,13 @@ int _xio_openlate(struct single *fd, struct opt *opts) { if ((result = applyopts(fd->fd, opts, PH_LATE2)) < 0) { return result; } + if ((result = applyopts(fd->fd, opts, PH_PASTEXEC)) < 0) { + return result; + } if ((numleft = leftopts(opts)) > 0) { showleft(opts); - Error1("%d option(s) could not be used", numleft); + Error1("INTERNAL: %d option(s) remained unused", numleft); return -1; } return 0; @@ -4240,7 +4337,8 @@ int dropopts(struct opt *opts, unsigned int phase) { return 0; } opt = opts; while (opt && opt->desc != ODESC_END) { - if (opt->desc != ODESC_DONE && opt->desc->phase == phase) { + if (opt->desc != ODESC_DONE && opt->desc != ODESC_ERROR && + opt->desc->phase == phase) { Debug1("ignoring option \"%s\"", opt->desc->defname); opt->desc = ODESC_DONE; } diff --git a/xioopts.h b/xioopts.h index 96d323c..c3f1359 100644 --- a/xioopts.h +++ b/xioopts.h @@ -121,6 +121,8 @@ enum e_func { # define ENABLE_OFUNC # include "xio-streams.h" /* push a POSIX STREAMS module */ # undef ENABLE_OFUNC + OFUNC_SET_NAMESPACE, /* set/change Linux namespace */ + OFUNC_RESET_NAMESPACE, /* set Linux namespace back to default */ } ; /* for simpler handling of option-to-connection-type relations we define @@ -610,6 +612,7 @@ enum e_optcode { OPT_RANGE, /* restrict client socket address */ OPT_RAW, /* termios */ OPT_READBYTES, + OPT_RESET_NETNS, /* reset net namespace - not an option, just op! */ OPT_RES_AAONLY, /* resolver(3) */ OPT_RES_DEBUG, /* resolver(3) */ OPT_RES_DEFNAMES, /* resolver(3) */ @@ -641,6 +644,7 @@ enum e_optcode { OPT_SETSOCKOPT_STRING, OPT_SETUID, OPT_SETUID_EARLY, + OPT_SET_NETNS, /* set net namespace */ OPT_SHUT_CLOSE, OPT_SHUT_DOWN, OPT_SHUT_NONE, @@ -924,6 +928,7 @@ enum e_phase { PH_LATE2, /* FD is ready, dropping privileges */ PH_PREEXEC, /* before exec() or system() */ PH_EXEC, /* during exec() or system() */ + PH_PASTEXEC, /* only reached on addresses that do NOT exec() */ PH_SPEC /* specific to situation, not fix */ } ; @@ -961,8 +966,8 @@ extern int retropt_string(struct opt *opts, int optcode, char **result); extern int retropt_timespec(struct opt *opts, int optcode, struct timespec *result); extern int retropt_bind(struct opt *opts, int af, int socktype, int ipproto, struct sockaddr *sa, socklen_t *salen, int feats, const int ai_flags[2]); extern int applyopts(int fd, struct opt *opts, enum e_phase phase); -extern int applyopts2(int fd, struct opt *opts, unsigned int from, - unsigned int to); +extern int applyopts2(int fd, struct opt *opts, unsigned int from, unsigned int to); +extern int applyopts_optgroup(int fd, struct opt *opts, unsigned int from, unsigned int to, groups_t groups); extern int applyopts_flags(struct opt *opts, groups_t group, flags_t *result); extern int applyopts_cloexec(int fd, struct opt *opts); extern int applyopts_early(const char *path, struct opt *opts);