From 6125ed4e4eb293dc3e2dee71dbe7982deb9e76d6 Mon Sep 17 00:00:00 2001 From: Gerhard Rieger Date: Mon, 2 Oct 2023 07:56:51 +0200 Subject: [PATCH] New option chdir (cd) --- CHANGES | 4 ++ VERSION | 2 +- doc/socat.yo | 4 ++ test.sh | 176 +++++++++++++++++++++++++++++++++++++++++++++-- xio-namespaces.c | 60 ++++++++++++---- xio-namespaces.h | 3 +- xiolayer.c | 37 +++++++++- xiolayer.h | 4 +- xioopen.c | 70 +++++++++---------- xioopts.c | 2 + xioopts.h | 1 + 11 files changed, 301 insertions(+), 62 deletions(-) diff --git a/CHANGES b/CHANGES index bb22299..21e9a39 100644 --- a/CHANGES +++ b/CHANGES @@ -143,6 +143,10 @@ Features: Added option res-nsaddr that overrides /etc/resolv.conf nameserver address based on an undocumented resolver feature. + New option chdir changes the working directory of the address to the + given path, only during the open stage. + Tests: CHDIR_ON_CREATE CHDIR_ON_SYSTEM + Option umask now applies only during opening of its very address, not for the lifetime of the process; the original umask is restored afterwards. diff --git a/VERSION b/VERSION index f09dcc3..ceaf471 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -"1.7.4.5+20230721" +"1.7.4.5+" diff --git a/doc/socat.yo b/doc/socat.yo index ad5e1b8..572d4a1 100644 --- a/doc/socat.yo +++ b/doc/socat.yo @@ -1990,6 +1990,10 @@ These options may be applied to all address types. They change some process properties that are restored after opening the address. startdit() +label(OPTION_CHDIR)dit(bf(tt(chdir=))) dit(bf(tt(cd=))) + Changes the working directory. After opening the address the master process + changes back to the original working directory. Sub processes inherit the + temporary setting. label(OPTION_UMASK)dit(bf(tt(umask=))) Sets the umask of the process to [link(mode_t)(TYPE_MODE_T)] before opening the address. Useful when file system entries are created or a shell diff --git a/test.sh b/test.sh index b9ff555..6f79bb8 100755 --- a/test.sh +++ b/test.sh @@ -1283,7 +1283,7 @@ waitsctp4port () { [ "$timeout" ] || timeout=5 while [ $timeout -gt 0 ]; do case "$UNAME" in - Linux) if false && [ "$SS" ]; then + Linux) if [ "$SS" ]; then l=$($SS -4 -n 2>/dev/null |grep "^sctp.*LISTEN .*:$port\>") else l=$(netstat -n -a |grep '^sctp .*[0-9*]:'$port' .* LISTEN') @@ -7851,7 +7851,8 @@ if ! eval $NUMCOND; then :; else tf="$td/test$N.stout" te="$td/test$N.stderr" -CMD="$TRACE $SOCAT $opts -d -d /dev/null pty,end-close" +# -t must be longer than 0.1 on OpenBSD +CMD="$TRACE $SOCAT $opts -d -d -t 0.5 /dev/null pty,end-close" printf "test $F_n $TEST... " $N # AIX reports the pty writeable for select() only when its slave side has been # opened, therefore we run this process in background and check its NOTICE @@ -7860,7 +7861,7 @@ printf "test $F_n $TEST... " $N waitfile "${te}" psleep 0.1 PTY=$(grep "N PTY is " $te |sed 's/.*N PTY is //') -[ -e "$PTY" ] && cat $PTY >/dev/null +[ -e "$PTY" ] && cat $PTY >/dev/null 2>/dev/null rc=$(cat "$td/test$N.rc0") if [ "$rc" = 0 ]; then $PRINTF "$OK\n" @@ -17355,6 +17356,171 @@ else listFAIL="$listFAIL $N" namesFAIL="$namesFAIL $NAME" fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + + +# Some of the following tests need absolute path of Socat +case "$SOCAT" in + /*) absSOCAT="$SOCAT" ;; + *) absSOCAT="$PWD/$SOCAT" ;; +esac + +# Test the chdir option, in particular if chdir with the first address +# (CREATE) does not affect pwd of second address, i.e. original pwd is +# recovered +NAME=CHDIR_ON_CREATE +case "$TESTS" in +*%$N%*|*%functions%*|*%creat%*|*%system%*|*%chdir%*|*%$NAME%*) +TEST="$NAME: restore of pwd after CREAT with chdir option" +# Run Socat with first address CREAT with modified chdir, +# and second address SYSTEM (shell) with pwd command +# Check if the file is created with modified pwd but shell has original pwd +if ! eval $NUMCOND; then :; +elif ! F=$(testfeats CREAT SYSTEM); then + $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not configured in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! A=$(testaddrs - CREAT SYSTEM); then + $PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! o=$(testoptions chdir) >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else + tf="$td/test$N.stdout" + te="$td/test$N.stderr" + tc="test$N.creat" + tdd="test$N.d" + tdiff="$td/test$N.diff" + tdebug="$td/test$N.debug" + opwd=$(pwd) + CMD0="$TRACE $absSOCAT $opts -U CREAT:$tc,chdir=$td SYSTEM:pwd" + printf "test $F_n $TEST... " $N + mkdir "$td/$tdd" + pushd "$td/$tdd" >/dev/null + $CMD0 >/dev/null 2>"${te}0" + rc0=$? + popd >/dev/null + tpwd=$(find $td -name $tc -print); tpwd=${tpwd%/*} + pwd2=$(cat $tpwd/$tc >$tdebug + echo "Temporary pwd: $tpwd" >>$tdebug + echo "Addr2 pwd: $pwd2" >>$tdebug + if [ "$rc0" -ne 0 ]; then + $PRINTF "$FAILED\n" + echo "$CMD0 &" + cat "${te}0" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + elif [ "$tpwd" != "$td" ]; then + $PRINTF "$FAILED (chdir failed)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + elif ! echo "$pwd2" |diff "$td/$tc" - >$tdiff; then + $PRINTF "$FAILED (bad pwd2)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "// diff:" >&2 + cat "$tdiff" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + else + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + numOK=$((numOK+1)) + fi +fi # NUMCOND + ;; +esac +N=$((N+1)) + +# Test the chdir option, in particular if chdir with first address +# (SHELL) does not affect pwd of second address, i.e. original pwd is +# recovered +NAME=CHDIR_ON_SHELL +case "$TESTS" in +*%$N%*|*%functions%*|*%shell%*|*%system%*|*%chdir%*|*%$NAME%*) +TEST="$NAME: restore of pwd after SYSTEM with chdir option" +# Run Socat with first address SYSTEM:"cat >file" with chdir, +# and second address SYSTEM (shell) with pwd command. +# Check if the file is created with modified pwd but shell has original pwd +if ! eval $NUMCOND; then :; +elif ! F=$(testfeats SHELL SYSTEM); then + $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not configured in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! A=$(testaddrs SHELL SYSTEM); then + $PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +elif ! o=$(testoptions chdir) >/dev/null; then + $PRINTF "test $F_n $TEST... ${YELLOW}Option $o not available in $SOCAT${NORMAL}\n" $N + numCANT=$((numCANT+1)) + listCANT="$listCANT $N" +else + tf="$td/test$N.stdout" + te="$td/test$N.stderr" + tc="test$N.creat" + tdd="test$N.d" + tdiff="$td/test$N.diff" + tdebug="$td/test$N.debug" + opwd=$(pwd) + CMD0="$TRACE $absSOCAT $opts -U SHELL:\"cat\ >$tc\",chdir=$td SYSTEM:pwd" + printf "test $F_n $TEST... " $N + mkdir "$td/$tdd" + pushd "$td/$tdd" >/dev/null + eval "$CMD0" >/dev/null 2>"${te}0" + rc0=$? + popd >/dev/null + tpwd=$(find $td -name $tc -print); tpwd=${tpwd%/*} + pwd2=$(cat $tpwd/$tc >$tdebug + echo "Temporary pwd: $tpwd" >>$tdebug + echo "Addr2 pwd: $pwd2" >>$tdebug + if [ "$rc0" -ne 0 ]; then + $PRINTF "$FAILED\n" + echo "$CMD0 &" + cat "${te}0" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + elif [ "$tpwd" != "$td" ]; then + $PRINTF "$FAILED (chdir failed)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + elif ! echo "$pwd2" |diff "$td/$tc" - >$tdiff; then + $PRINTF "$FAILED (bad pwd)\n" + echo "$CMD0 &" + cat "${te}0" >&2 + echo "// diff:" >&2 + cat "$tdiff" >&2 + numFAIL=$((numFAIL+1)) + listFAIL="$listFAIL $N" + namesFAIL="$namesFAIL $NAME" + else + $PRINTF "$OK\n" + if [ "$VERBOSE" ]; then echo "$CMD0 &"; fi + if [ "$DEBUG" ]; then cat "${te}0" >&2; fi + numOK=$((numOK+1)) + fi +fi # NUMCOND + ;; +esac +N=$((N+1)) # Test the modified umask option, in particular if umask with first address @@ -17372,7 +17538,7 @@ elif ! F=$(testfeats CREAT SYSTEM); then $PRINTF "test $F_n $TEST... ${YELLOW}Feature $F not configured in $SOCAT${NORMAL}\n" $N numCANT=$((numCANT+1)) listCANT="$listCANT $N" -elif ! A=$(testaddrs - CREAT SYSTEM); then +elif ! A=$(testaddrs CREAT SYSTEM); then $PRINTF "test $F_n $TEST... ${YELLOW}Address $A not available in $SOCAT${NORMAL}\n" $N numCANT=$((numCANT+1)) listCANT="$listCANT $N" @@ -17441,7 +17607,7 @@ N=$((N+1)) # recovered NAME=UMASK_ON_SYSTEM case "$TESTS" in -*%$N%*|*%functions%*|*%shell%*|*%umask%*|*%socket%*|*%$NAME%*) +*%$N%*|*%functions%*|*%shell%*|*%system%*|*%umask%*|*%socket%*|*%$NAME%*) TEST="$NAME: test restore after SHELL with umask option" # Run Socat with first address SHELL:"cat >file" with modified umask, # and second address SYSTEM (shell) with umask command. diff --git a/xio-namespaces.c b/xio-namespaces.c index 1c2bc3d..b44ee43 100644 --- a/xio-namespaces.c +++ b/xio-namespaces.c @@ -44,29 +44,61 @@ int xio_set_namespace( return 0; } +int xio_apply_namespace( + struct opt *opts) +{ + int old_netfd; + char *netns_name; + char old_nspath[PATH_MAX]; + int rc; + + if (retropt_string(opts, OPT_SET_NETNS, &netns_name) < 0) + return 0; + + /* Get path describing current namespace */ + snprintf(old_nspath, sizeof(old_nspath)-1, "/proc/"F_pid"/ns/net", + Getpid()); + + /* Get a file descriptor to current ns for later reset */ + old_netfd = Open(old_nspath, O_RDONLY|O_CLOEXEC, 000); + if (old_netfd < 0) { + Error2("open(%s, O_RDONLY|O_CLOEXEC): %s", + old_nspath, strerror(errno)); + free(netns_name); + return -1; + } + if (old_netfd == 0) { + /* 0 means not netns option, oops */ + Error1("%s(): INTERNAL", __func__); + free(netns_name); + Close(old_netfd); + return -1; + } + rc = xio_set_namespace("netns", netns_name); + free(netns_name); + if (rc < 0) { + Close(old_netfd); + return -1; + } + + return old_netfd; +} + /* 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) + int saved_netfd) { - 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); + rc = Setns(saved_netfd, CLONE_NEWNET); if (rc < 0) { - Error2("setns(%d, CLONE_NEWNET): %s", nsfd, strerror(errno)); - Close(nsfd); + Error2("xio_reset_namespace(%d): %s", saved_netfd, strerror(errno)); + Close(saved_netfd); + return STAT_NORETRY; } - Close(nsfd); + Close(saved_netfd); return 0; } diff --git a/xio-namespaces.h b/xio-namespaces.h index 8aed47a..0b5d4bf 100644 --- a/xio-namespaces.h +++ b/xio-namespaces.h @@ -11,7 +11,8 @@ 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); +extern int xio_apply_namespace(struct opt *opts); +extern int xio_reset_namespace(int saved_netfd); #endif /* WITH_NAMESPACES */ diff --git a/xiolayer.c b/xiolayer.c index 6838e17..81c88e9 100644 --- a/xiolayer.c +++ b/xiolayer.c @@ -24,4 +24,39 @@ const struct optdesc opt_intervall = { "interval", NULL, OPT_INTERVALL, GROUP_R const struct optdesc opt_retry = { "retry", NULL, OPT_RETRY, GROUP_RETRY, PH_INIT, TYPE_UINT, OFUNC_EXT, XIO_OFFSETOF(retry), XIO_SIZEOF(retry) }; #endif -const struct optdesc opt_umask = { "umask", NULL, OPT_UMASK, GROUP_ADDR, PH_INIT, TYPE_MODET, OFUNC_SPEC }; +const struct optdesc opt_chdir = { "chdir", "cd", OPT_CHDIR, GROUP_ADDR, PH_INIT, TYPE_FILENAME, OFUNC_SPEC }; +const struct optdesc opt_umask = { "umask", NULL, OPT_UMASK, GROUP_ADDR, PH_INIT, TYPE_MODET, OFUNC_SPEC }; + + +int xio_chdir( + struct opt* opts, + char **orig_dir) +{ + char *tmp_dir = NULL; + + if (retropt_string(opts, OPT_CHDIR, &tmp_dir) < 0) + return 0; + + if ((*orig_dir = Malloc(PATH_MAX)) == NULL) { + free(tmp_dir); + return -1; + } + + if (getcwd(*orig_dir, PATH_MAX) == NULL) { + Error1("getcwd(, PATH_MAX): %s", strerror(errno)); + free(*orig_dir); + free(tmp_dir); + return -1; + } + *orig_dir = Realloc(*orig_dir, strlen(*orig_dir+1)); + + if (Chdir(tmp_dir) < 0) { + Error2("chdir(\"%s\"): %s", tmp_dir, strerror(errno)); + free(*orig_dir); + free(tmp_dir); + return -1; + } + + free(tmp_dir); + return 1; +} diff --git a/xiolayer.h b/xiolayer.h index 22a52ad..a877e26 100644 --- a/xiolayer.h +++ b/xiolayer.h @@ -15,7 +15,9 @@ extern const struct optdesc opt_escape; extern const struct optdesc opt_forever; extern const struct optdesc opt_intervall; extern const struct optdesc opt_retry; +extern const struct optdesc opt_chdir; extern const struct optdesc opt_umask; -extern const struct optdesc opt_un_umask; + +extern int xio_chdir(struct opt* opts, char **orig_dir); #endif /* !defined(__xiolayer_h_included) */ diff --git a/xioopen.c b/xioopen.c index 0477824..3ffa375 100644 --- a/xioopen.c +++ b/xioopen.c @@ -622,6 +622,7 @@ int xioopen_single(xiofile_t *xfd, int xioflags) { const struct addrdesc *addrdesc; const char *modetext[4] = { "none", "read-only", "write-only", "read-write" } ; /* Values to be saved until xioopen() is finished */ + char *orig_dir = NULL; bool have_umask = false; mode_t orig_umask, tmp_umask; int result; @@ -631,42 +632,10 @@ int xioopen_single(xiofile_t *xfd, int xioflags) { struct __res_state save_res; #endif /* WITH_RESOLVE && HAVE_RESOLV_H */ #if WITH_NAMESPACES - char *temp_netns; int save_netfd = -1; #endif - int rc; - - /* Apply "temporary" process properties, save value for later restore */ - - 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 WITH_RESOLVE && HAVE_RESOLV_H - if ((do_res = xio_res_init(sfd, &save_res)) < 0) - return STAT_NORETRY; -#endif /* WITH_RESOLVE && HAVE_RESOLV_H */ addrdesc = xfd->stream.addr; - /* Check if address supports required data directions */ if (((xioflags+1)&XIO_ACCMODE) & ~(addrdesc->directions)) { Warn2("address is opened in %s mode but only supports %s", modetext[(xioflags+1)&XIO_ACCMODE], modetext[addrdesc->directions]); } @@ -682,12 +651,31 @@ int xioopen_single(xiofile_t *xfd, int xioflags) { xfd->stream.flags &= (~XIO_ACCMODE); xfd->stream.flags |= (xioflags & XIO_ACCMODE); + /* Apply "temporary" process properties, save value for later restore */ + + if (applyopts_single(sfd, sfd->opts, PH_OFFSET) < 0) + return -1; + +#if WITH_NAMESPACES + if ((save_netfd = xio_apply_namespace(sfd->opts)) < 0) + return -1; +#endif /* WITH_NAMESPACES */ + +#if HAVE_RESOLV_H + if ((do_res = xio_res_init(sfd, &save_res)) < 0) + return STAT_NORETRY; +#endif /* HAVE_RESOLV_H */ + + if (xio_chdir(sfd->opts, &orig_dir) < 0) + return STAT_NORETRY; + if (retropt_mode(xfd->stream.opts, OPT_UMASK, &tmp_umask) >= 0) { Info1("changing umask to 0%3o", tmp_umask); orig_umask = Umask(tmp_umask); have_umask = true; } + /* Call the specific xioopen function */ result = (*addrdesc->func)(xfd->stream.argc, xfd->stream.argv, xfd->stream.opts, xioflags, xfd, addrdesc); @@ -698,19 +686,23 @@ int xioopen_single(xiofile_t *xfd, int xioflags) { Umask(orig_umask); } + if (orig_dir != NULL) { + if (Chdir(orig_dir) < 0) { + Error2("chdir(\"%s\"): %s", orig_dir, strerror(errno)); + free(orig_dir); + return STAT_NORETRY; + } + free(orig_dir); + } + #if WITH_RESOLVE && HAVE_RESOLV_H if (do_res) xio_res_restore(&save_res); #endif /* WITH_RESOLVE && 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; - } + if (save_netfd > 0) { + xio_reset_namespace(save_netfd); } #endif /* WITH_NAMESPACES */ diff --git a/xioopts.c b/xioopts.c index b326211..466fa92 100644 --- a/xioopts.c +++ b/xioopts.c @@ -314,9 +314,11 @@ const struct optname optionnames[] = { IF_ANY ("bytes", &opt_readbytes) IF_OPENSSL("cafile", &opt_openssl_cafile) IF_OPENSSL("capath", &opt_openssl_capath) + IF_ANY ("cd", &opt_chdir) IF_OPENSSL("cert", &opt_openssl_certificate) IF_OPENSSL("certificate", &opt_openssl_certificate) IF_TERMIOS("cfmakeraw", &opt_termios_cfmakeraw) + IF_ANY ("chdir", &opt_chdir) #if WITH_LISTEN IF_ANY ("children-shutup", &opt_children_shutup) #endif diff --git a/xioopts.h b/xioopts.h index 00153ea..1140f82 100644 --- a/xioopts.h +++ b/xioopts.h @@ -263,6 +263,7 @@ enum e_optcode { # endif OPT_BSDLY, /* termios.c_oflag */ #endif + OPT_CHDIR, /* change working directory */ OPT_CHILDREN_SHUTUP, OPT_CHROOT, /* chroot() past file system access */ OPT_CHROOT_EARLY, /* chroot() before file system access */