New option chdir (cd)

This commit is contained in:
Gerhard Rieger 2023-10-02 07:56:51 +02:00
parent e5cbf2feeb
commit 6125ed4e4e
11 changed files with 301 additions and 62 deletions

View file

@ -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.

View file

@ -1 +1 @@
"1.7.4.5+20230721"
"1.7.4.5+"

View file

@ -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=<filename>))) dit(bf(tt(cd=<filename>)))
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=<mode>)))
Sets the umask of the process to <mode> [link(mode_t)(TYPE_MODE_T)] before
opening the address. Useful when file system entries are created or a shell

176
test.sh
View file

@ -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 </dev/null)
echo "Original pwd: $opwd" >>$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 </dev/null)
echo "Original pwd: $opwd" >>$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.

View file

@ -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;
}

View file

@ -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 */

View file

@ -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_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(<ptr>, 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;
}

View file

@ -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) */

View file

@ -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 */

View file

@ -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

View file

@ -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 */