Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 11 Aug 2011 14:34:01 +0200
From:      joris dedieu <joris.dedieu@gmail.com>
To:        freebsd-jail@freebsd.org
Subject:   Re: Jexec and access to tty
Message-ID:  <CAPd55qC1k9SP=k1QO%2B_iSLwkBj0QWRrnqPiVzweZtwBW=UjbEw@mail.gmail.com>
In-Reply-To: <CAPd55qDCpF6%2BNJTKCNUZKEVp3JRnrF8VLKUgFVtLc7fxQODA=w@mail.gmail.com>
References:  <20110809124543.GA22077@psconsult.nl> <CAPd55qBGAkQDxQ6cy7=M%2BxxX%2BHnAoR%2B8_iCS6QMxxSi-iNnniA@mail.gmail.com> <CAPd55qDCpF6%2BNJTKCNUZKEVp3JRnrF8VLKUgFVtLc7fxQODA=w@mail.gmail.com>

next in thread | previous in thread | raw e-mail | index | archive | help
2011/8/11 joris dedieu <joris.dedieu@gmail.com>:
> 2011/8/10 joris dedieu <joris.dedieu@gmail.com>:
>> 2011/8/9 Paul Schenkeveld <freebsd@psconsult.nl>:
>>> Hi,
>>>
>>> There have been several threads about this issue, some people have come
>>> up with work arounds but I think that the issue is more fundamental,
>>> that's why I wanted to start this new thread.
>>>
>>> When using jexec to do interactive work inside an existing jail, people
>>> find out that they no longer have access to their tty device. =A0As a
>>> result, programs requiring input of passwords or passphrases behave
>>> unexpectedly in one of several ways.
>>>
>>> Ssh says "Host key verification failed." and refuses to log in to
>>> another system (unless pubkey authentication is user in combination wit=
h
>>> an agent of course). =A0Some programs fall back to using stdin/stdout
>>> and echo the password as it is typed (the mysql clients are popular
>>> examples).
>>>
>>> Work-arounds that have been suggested are
>>> =A01. Run a sshd inside the jail and log in using ssh
>>> =A02. Start tmux inside the jail so you get a new pseudo tty slave insi=
de
>>> =A0 =A0the jail. =A0People trying screen find that it won't work unlike=
 tmux.
>>> =A03. I tried using 'script -q /dev/null' inside the jail because it is
>>> =A0 =A0part op the base system and it doesn't change your terminal type
>>> =A0 =A0and interpret keyboard input and screen output. =A0I found out t=
hat I
>>> =A0 =A0failed when I resized my window :-(
>>
>> An other way is to use chroot(5) to enter the jail.
>> Maybe chroot /jail/root login -f $USER should be acceptable in some situ=
ations.
>>
>>>
>>> I don't like 1 on a machine with many jails, especially if some of them
>>> share the same IP address (e.g. sometimes I have to run a mail server o=
n
>>> the same IP adress as a webserver but in a distinct jail).
>>>
>>> 2 is not ideal either because tmux emulates a different terminal on
>>> the inside than the terminal on the outside that it runs on.
>>>
>>> 3 is really a kludge and causes problems when you resize your window.
>>>
>>> I thought that I found a solution by rewriting jexec such that it will
>>> open a pseudo tty and does the passing of data between the jailed pts
>>> and the tty from where jexec was started but that's not going to work a=
s
>>> the pseudo tty most be opened by the child process inside the jail but
>>> the parent outside the jail must have access to the master side of the
>>> pseudo tty.
>>>
>>> So far we are still talking about work-arounds. =A0Why not look at the
>>> root cause. =A0Unfortunately I'm not familiar with kernel sources so if
>>> I'm wrong, please forgive me, I write this with the best intentions.
>>>
>>> The root cause of th problem appears to be that pseudo ttys opened
>>> outside a jail are not visible nor accessible inside a jail, pseudo tty=
s
>>> created inside a jail are visible and accessible though.
>>
>> As far as I understand, sys/fs/devfs/devfs_vnops.c uses
>> prison_check(9) too see if an item as been build in the same jail or
>> in a child.
>> The tty exists inside the jail but you can't use it (and so you can't
>> escape the jail).
>>
>>>
>>> Would it be conceivable that by using jexec the controlling tty of jexe=
c
>>> magically becomes visible and accessible inside the jail? =A0Preferrabl=
e
>>> only until jexec dies.
>>
>> I'm not sure. The only way should be =A0to temporary disable the check
>> with a =A0variable. But it will also brake jail security during jexec
>> excution.
>> Using chroot(2) instead of jail(2) should be an option (but it's =A0non
>> trivial to affect jail context for all other subsystems).
>>
>> I think the only right way is to open a new tty while entering the
>> jail (this is what tmux or a jailed ssh does).
>> But it should be difficult to make things like echo ps | jexec 1 sh work=
s.
>
> I gave this way a try and quickly produced a dirty patch. I did not
> yet investigate on the right way, but I already know that I'm wrong on
> several items so sorry for your eyes :)
>
> Basically this patch =A0introduced =A0-t option that forks jexec inside
> the jail, open a new pts and tries to establish a communication with
> the old one.
> It mostly works has expected but breaks some console inputs like
> control commands, completion and so on.
> Before spending more time =A0trying to produce something acceptable,
> please let =A0me know if it should be the expected feature or sounds
> like a nasty workaround.
>
> --- usr.sbin/jexec/jexec.c.orig 2009-08-03 10:13:06.000000000 +0200
> +++ usr.sbin/jexec/jexec.c =A0 =A0 =A02011-08-10 23:33:44.000000000 +0200
> @@ -37,16 +37,19 @@
>
> =A0#include <err.h>
> =A0#include <errno.h>
> +#include <fcntl.h>
> =A0#include <jail.h>
> =A0#include <limits.h>
> =A0#include <login_cap.h>
> =A0#include <stdio.h>
> =A0#include <stdlib.h>
> =A0#include <string.h>
> +#include <termios.h>
> =A0#include <pwd.h>
> =A0#include <unistd.h>
>
> =A0static void =A0 =A0usage(void);
> +static int =A0 =A0 ntty_exec(const char *file, char *const argv[]);
>
> =A0#define GET_USER_INFO do { =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 \
> =A0 =A0 =A0 =A0pwd =3D getpwnam(username); =A0 =A0 =A0 =A0 =A0 =A0 =A0 =
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 \
> @@ -64,6 +67,8 @@
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0err(1, "getgrouplist: %s", username); =A0 =
=A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 \
> =A0} while (0)
>
> +#define MAXREAD 256
> +
> =A0int
> =A0main(int argc, char *argv[])
> =A0{
> @@ -71,17 +76,17 @@
> =A0 =A0 =A0 =A0login_cap_t *lcap =3D NULL;
> =A0 =A0 =A0 =A0struct passwd *pwd =3D NULL;
> =A0 =A0 =A0 =A0gid_t *groups =3D NULL;
> - =A0 =A0 =A0 int ch, ngroups, uflag, Uflag;
> + =A0 =A0 =A0 int ch, ngroups, uflag, Uflag, tflag;
> =A0 =A0 =A0 =A0long ngroups_max;
> =A0 =A0 =A0 =A0char *username;
>
> - =A0 =A0 =A0 ch =3D uflag =3D Uflag =3D 0;
> + =A0 =A0 =A0 ch =3D uflag =3D Uflag =3D tflag =3D 0;
> =A0 =A0 =A0 =A0username =3D NULL;
> =A0 =A0 =A0 =A0ngroups_max =3D sysconf(_SC_NGROUPS_MAX) + 1;
> =A0 =A0 =A0 =A0if ((groups =3D malloc(sizeof(gid_t) * ngroups_max)) =3D=
=3D NULL)
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0err(1, "malloc");
>
> - =A0 =A0 =A0 while ((ch =3D getopt(argc, argv, "nu:U:")) !=3D -1) {
> + =A0 =A0 =A0 while ((ch =3D getopt(argc, argv, "nu:U:t")) !=3D -1) {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0switch (ch) {
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0case 'n':
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0/* Specified name, now unu=
sed */
> @@ -94,6 +99,9 @@
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0username =3D optarg;
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0Uflag =3D 1;
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0break;
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 case 't':
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 tflag =3D 1;
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 break;
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0default:
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0usage();
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0}
> @@ -125,8 +133,14 @@
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0err(1, "setusercontext");
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0login_close(lcap);
> =A0 =A0 =A0 =A0}
> - =A0 =A0 =A0 if (execvp(argv[1], argv + 1) =3D=3D -1)
> - =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "execvp(): %s", argv[1]);
> + =A0 =A0 =A0 if (tflag) {
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 if(ntty_exec(argv[1], argv + 1) =3D=3D -1)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "ntty_exec(): %s", a=
rgv[1]);
> + =A0 =A0 =A0 }
> + =A0 =A0 =A0 else {
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (execvp(argv[1], argv + 1) =3D=3D -1)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "execvp(): %s", argv=
[1]);
> + =A0 =A0 =A0 }
> =A0 =A0 =A0 =A0exit(0);
> =A0}
>
> @@ -138,3 +152,77 @@
> =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0"usage: jexec [-u username | -U username] =
jail command ...");
> =A0 =A0 =A0 =A0exit(1);
> =A0}
> +
> +static int
> +ntty_exec(const char *file, char *const argv[])
> +{
> + =A0 =A0 =A0 int pseudoterm, pseudoterm_fd, res;
> + =A0 =A0 =A0 struct fd_set in_fd;
> + =A0 =A0 =A0 struct termios termset;
> + =A0 =A0 =A0 char input[MAXREAD];
> +
> + =A0 =A0 =A0 if ((pseudoterm =3D posix_openpt(O_RDWR)) =3D=3D -1)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "posix_openpt");
> + =A0 =A0 =A0 if (grantpt(pseudoterm) !=3D 0)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "grantpt");
> + =A0 =A0 =A0 if (unlockpt(pseudoterm) !=3D 0)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "grantpt");
> + =A0 =A0 =A0 if ((pseudoterm_fd =3D open(ptsname(pseudoterm), O_RDWR)) =
=3D=3D -1)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "open");
> +
> + =A0 =A0 =A0 switch (fork()) {
> + =A0 =A0 =A0 case -1 :
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "fork");
> + =A0 =A0 =A0 case 0 =A0:
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 close(pseudoterm);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (tcgetattr(pseudoterm_fd, &termset) =3D=
=3D -1)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "tcgetattr");
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 cfmakeraw(&termset);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 tcsetattr(pseudoterm_fd, TCSANOW, &termset)=
;
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 fclose(stdin);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 fclose(stdout);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 fclose(stderr);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 dup(pseudoterm_fd);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 dup(pseudoterm_fd);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 dup(pseudoterm_fd);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 close(pseudoterm_fd);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 setsid();
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 ioctl(0, TIOCSCTTY, 1);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 return execvp(file, argv);
> + =A0 =A0 =A0 default :
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 close(pseudoterm_fd);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 while(1) {
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 FD_ZERO(&in_fd);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 FD_SET(0, &in_fd);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 FD_SET(pseudoterm, &in_fd);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (select(pseudoterm + 1, =
&in_fd, NULL, NULL,
> NULL) =3D=3D -1)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 err(1, "sel=
ect");
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (FD_ISSET(0, &in_fd)) {
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 /* stdin */
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if ((res =
=3D read(0, input, sizeof(input))) > 0 )
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 write(pseudoterm, input, res);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 else {
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 if (errno =3D=3D ENOENT)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 =A0 =A0 =A0 =A0 return 0;
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 else
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 =A0 =A0 =A0 =A0 err(1, "read");
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 }
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 }
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if (FD_ISSET(pseudoterm, &i=
n_fd)) {
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 if ((res =
=3D read(pseudoterm, input,
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 siz=
eof(input))) > 0 )
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 /* stdout */
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 write(1, input, res);
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 else {
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 if (errno =3D=3D ENOENT)
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 =A0 =A0 =A0 =A0 return 0;
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 else
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 =A0 =A0 =A0 =A0 err(1, "write");
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0=
 =A0 }
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 =A0 }
> + =A0 =A0 =A0 =A0 =A0 =A0 =A0 }
> + =A0 =A0 =A0 }
> +
> + =A0 =A0 =A0 return 0;
> +}
> +
>
> regards
> Joris

Finally I produced a most acceptable patch as almost everything exists
in script(1) sources.  I don't experienced any problems using it.

--- usr.sbin/jexec/jexec.c.orig 2009-08-03 10:13:06.000000000 +0200
+++ usr.sbin/jexec/jexec.c      2011-08-11 14:25:11.000000000 +0200
@@ -37,16 +37,20 @@

 #include <err.h>
 #include <errno.h>
+#include <fcntl.h>
 #include <jail.h>
 #include <limits.h>
+#include <libutil.h>
 #include <login_cap.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <termios.h>
 #include <pwd.h>
 #include <unistd.h>

 static void    usage(void);
+static void    newpty(void);

 #define GET_USER_INFO do {                                             \
        pwd =3D getpwnam(username);                                       \
@@ -71,17 +75,17 @@
        login_cap_t *lcap =3D NULL;
        struct passwd *pwd =3D NULL;
        gid_t *groups =3D NULL;
-       int ch, ngroups, uflag, Uflag;
+       int ch, ngroups, uflag, Uflag, tflag;
        long ngroups_max;
        char *username;

-       ch =3D uflag =3D Uflag =3D 0;
+       ch =3D uflag =3D Uflag =3D tflag =3D 0;
        username =3D NULL;
        ngroups_max =3D sysconf(_SC_NGROUPS_MAX) + 1;
        if ((groups =3D malloc(sizeof(gid_t) * ngroups_max)) =3D=3D NULL)
                err(1, "malloc");

-       while ((ch =3D getopt(argc, argv, "nu:U:")) !=3D -1) {
+       while ((ch =3D getopt(argc, argv, "nu:U:t")) !=3D -1) {
                switch (ch) {
                case 'n':
                        /* Specified name, now unused */
@@ -94,6 +98,9 @@
                        username =3D optarg;
                        Uflag =3D 1;
                        break;
+               case 't':
+                       tflag =3D 1;
+                       break;
                default:
                        usage();
                }
@@ -125,6 +132,8 @@
                        err(1, "setusercontext");
                login_close(lcap);
        }
+       if (tflag)
+               newpty();
        if (execvp(argv[1], argv + 1) =3D=3D -1)
                err(1, "execvp(): %s", argv[1]);
        exit(0);
@@ -135,6 +144,68 @@
 {

        fprintf(stderr, "%s\n",
-               "usage: jexec [-u username | -U username] jail command ..."=
);
+               "usage: jexec [-u username | -U username] [-t] jail
command ...");
        exit(1);
 }
+
+static void
+newpty(void)
+{
+       int master, slave, child, n, cc;
+       fd_set rfd;
+       struct termios tt, rtt;
+       struct winsize win;
+       char obuf[BUFSIZ];
+       char ibuf[BUFSIZ];
+
+       if (tcgetattr(STDIN_FILENO, &tt) =3D=3D -1)
+                err(1, "tcgetattr");
+        if (ioctl(STDIN_FILENO, TIOCGWINSZ, &win) =3D=3D -1)
+                err(1, "ioctl");
+        if (openpty(&master, &slave, NULL, &tt, &win) =3D=3D -1)
+                err(1, "openpty");
+        rtt =3D tt;
+        cfmakeraw(&rtt);
+        rtt.c_lflag &=3D ~ECHO;
+        tcsetattr(STDIN_FILENO, TCSAFLUSH, &rtt);
+
+        child =3D fork();
+        if (child < 0)
+                err(1, "fork");
+        if (child =3D=3D 0) {
+                close(master);
+                login_tty(slave);
+                dup2(slave, STDIN_FILENO);
+                dup2(slave, STDOUT_FILENO);
+                dup2(slave, STDERR_FILENO);
+               close(slave);
+               return;
+       }
+       FD_ZERO(&rfd);
+       for (;;) {
+               FD_SET(master, &rfd);
+                FD_SET(STDIN_FILENO, &rfd);
+                n =3D select(master + 1, &rfd, 0, 0, NULL);
+                if (n < 0 && errno !=3D EINTR)
+                        break;
+                if (n > 0 && FD_ISSET(STDIN_FILENO, &rfd)) {
+                        cc =3D read(STDIN_FILENO, ibuf, BUFSIZ);
+                        if (cc < 0)
+                                break;
+                        if (cc =3D=3D 0)
+                                write(master, ibuf, 0);
+                        if (cc > 0) {
+                                write(master, ibuf, cc);
+                        }
+                }
+                if (n > 0 && FD_ISSET(master, &rfd)) {
+                        cc =3D read(master, obuf, sizeof (obuf));
+                        if (cc <=3D 0)
+                                break;
+                        write(STDOUT_FILENO, obuf, cc);
+                }
+        }
+       tcsetattr(STDIN_FILENO, TCSAFLUSH, &tt);
+       exit(0);
+}
+


>
>>
>> regards
>> Joris
>>
>>>
>>> I understand that this is not trivial but given the number of threads
>>> about this problem, it's a real issue to many people. =A0To me it's wor=
th
>>> some $ or EUR to solve this in a clean way.
>>>
>>> Kind regards,
>>>
>>> Paul Schenkeveld
>>> _______________________________________________
>>> freebsd-jail@freebsd.org mailing list
>>> http://lists.freebsd.org/mailman/listinfo/freebsd-jail
>>> To unsubscribe, send any mail to "freebsd-jail-unsubscribe@freebsd.org"
>>>
>>
>



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?CAPd55qC1k9SP=k1QO%2B_iSLwkBj0QWRrnqPiVzweZtwBW=UjbEw>