Mailing List Archive

[PATCH] better stdin handling for controlling processes
NetworkManager-vpnc has been using vpnc with --non-inter for a long time,
but this precludes handling requests for new passwords if the originally
given one is wrong. Now that we'd like to do that, controlling vpnc in
interactive mode brings up a few problems.

There are two issues controlling vpnc as a child process:

1) vpnc's config file processing logic uses EOF to determine when to stop
processing the config input, but if stdin is actually a pipe from a controlling
process, EOF only happens if the pipe is closed. Which means the controlling
process can't respond to any interactive requests for information. So we need
to add some other mechanism to indicate that config processing is done that
does not rely on closing stdin to indicate this.

getline() only returns on EOF (which has the problems described above) or
when it encounters sufficient newline characters; unfortunately this precludes
using getline() to handle single bytes. Switch to fgetc() and build up the
line ourselves so that we can recognize a custom EOF character, 0x1A (Ctl-Z).

2) getpass() tries to use /dev/tty by default, which requests input directly
from the user, which prevents a controlling process from writing the input
to stdin. Add a config option to always listen for input on stdin and
implement a replacement for getpass() that always reads from stdin.

One thing that would be nice is some indication of whether vpnc has this
patch included or not, so that NM-vpnc could alter its behavior automatically.
Any thoughts on that?

Dan
Re: [PATCH] better stdin handling for controlling processes [ In reply to ]
Hi Dan,

in principle I agree on your patch, but I have few points to check with you.


On Sat, Jun 29, 2013 at 1:45 AM, Dan Williams <dcbw@redhat.com> wrote:

> ...
>
> There are two issues controlling vpnc as a child process:
>
> 1) vpnc's config file processing logic uses EOF ... Switch to fgetc() ...
>
> 2) getpass() tries to use /dev/tty by default ...
>

Two separate patches would help review :-)


One thing that would be nice is some indication of whether vpnc has this
> patch included or not, so that NM-vpnc could alter its behavior
> automatically.
> Any thoughts on that?
>

The version of vpnc includes the svn revision number.
Would it be enough to detect if this patch is present?
Depending on your method to clone and build, you could loose svn revision
number.
If you don't use svn, clone the repository with git-svn. It keeps the right
revision from svn.

Could even be time to create a new version of vpnc!


#include <unistd.h>
#include <string.h>
#include <errno.h>
+#include <termios.h>

Is this fine on non Linux targets?
I was quickly googling to find an implementation of getpass(), and found
this
http://cpansearch.perl.org/src/RAM/kit-2.0.38/des/getpass.c
It supports also Windows (not the case for vpnc), but in general it uses a
more complex mechanism.
Would be good to check widely.

+ /* Standard getpass() tries to open the TTY first so if the caller
+ * always wants to use stdin, we can't use getpass().
+ */
+ if (!always_stdin)
+ return getpass(prompt);

"man getpass" reports that "This function is obsolete. Do not use it."
I think this is the right time to drop it.


+ static char *buf;
+ static size_t bufsize;
...
+ nread = getline(&buf, &bufsize, stdin);

You want get rid of getline(), but now you introduce it again!

Here I would like to explicitly set buf to NULL to show what I'm doing.
Not mandatory, but helpful for code review.

Then, code above introduces a bug.
Once getline() is call the first time, it allocates the buffer. Fine!
But if called a second time, there is no way to return a password longer
that the text entered previously.
You can see that this function can be called in different contexts, as below

case CONFIG_IPSEC_SECRET:
case CONFIG_XAUTH_PASSWORD:
- s = strdup(getpass(""));
+ s = strdup(vpnc_getpass("", !!config[CONFIG
_INTERACTIVE_STDIN]));


At last:

+ CONFIG_INTERACTIVE_STDIN, 0, 1,
+ "--interactive-stdin",
+ "InteractiveStdin",
+ NULL,
+ "Always look for interactive input on stdin instead of the
TTY",
+ NULL
+ }, {

Can be autodetected?
if (isatty(fileno(stdin)))
printf("stdin is a tty\n");
else
printf("stdin is a pipe or a file\n");

Best Regards,
Antonio Borneo
Re: [PATCH] better stdin handling for controlling processes [ In reply to ]
On Sat, 2013-06-29 at 10:16 +0800, Antonio Borneo wrote:
> Hi Dan,
>
> in principle I agree on your patch, but I have few points to check with you.
>
>
> On Sat, Jun 29, 2013 at 1:45 AM, Dan Williams <dcbw@redhat.com> wrote:
>
> > ...
> >
> > There are two issues controlling vpnc as a child process:
> >
> > 1) vpnc's config file processing logic uses EOF ... Switch to fgetc() ...
> >
> > 2) getpass() tries to use /dev/tty by default ...
> >
>
> Two separate patches would help review :-)

I will do this.

>
> One thing that would be nice is some indication of whether vpnc has this
> > patch included or not, so that NM-vpnc could alter its behavior
> > automatically.
> > Any thoughts on that?
> >
>
> The version of vpnc includes the svn revision number.
> Would it be enough to detect if this patch is present?
> Depending on your method to clone and build, you could loose svn revision
> number.
> If you don't use svn, clone the repository with git-svn. It keeps the right
> revision from svn.

Ideally I could detect this at runtime; after the patch is committed
could we update the vpnc version so that "vpnc --version" could be used
to detect that the patch is applied?

> Could even be time to create a new version of vpnc!

I agree :)

> #include <unistd.h>
> #include <string.h>
> #include <errno.h>
> +#include <termios.h>
>
> Is this fine on non Linux targets?

It may not be. The changes in this patch are really only necessary when
stdin is being used, so perhaps we could just drop the termios stuff
completely with stdin? The only reason I added it was that I was
attempting to duplicate the functionality of the libc getpass() function
to minimize regressions.

> I was quickly googling to find an implementation of getpass(), and found
> this
> http://cpansearch.perl.org/src/RAM/kit-2.0.38/des/getpass.c
> It supports also Windows (not the case for vpnc), but in general it uses a
> more complex mechanism.
> Would be good to check widely.

Or avoid the problem entirely by not doing any termios; we could define
the --interactive-stdin option to mean that vpnc expects a non-tty and
would never do TTY operations on the file descriptor, which would be
fine with me.

> + /* Standard getpass() tries to open the TTY first so if the caller
> + * always wants to use stdin, we can't use getpass().
> + */
> + if (!always_stdin)
> + return getpass(prompt);
>
> "man getpass" reports that "This function is obsolete. Do not use it."
> I think this is the right time to drop it.

Yeah, though we could do this with another patch later? It's somewhat
orthogonal to the current patchset if we redefine --interactive-stdin to
ignore TTY stuff. Which means my patches would also be simpler. And
less risk of regressions.

>
> + static char *buf;
> + static size_t bufsize;
> ...
> + nread = getline(&buf, &bufsize, stdin);
>
> You want get rid of getline(), but now you introduce it again!

True.

> Here I would like to explicitly set buf to NULL to show what I'm doing.
> Not mandatory, but helpful for code review.

True and I typically do this. Technically though since it's static data
the system will initialize it to NULL anyway.

> Then, code above introduces a bug.
> Once getline() is call the first time, it allocates the buffer. Fine!
> But if called a second time, there is no way to return a password longer
> that the text entered previously.

Good point. And realloc() semantics are tricky to get correct though,
so if you're happy with me making vpnc_getpass() return an allocated
value that callers can free, it would be simpler.

> You can see that this function can be called in different contexts, as below
>
> case CONFIG_IPSEC_SECRET:
> case CONFIG_XAUTH_PASSWORD:
> - s = strdup(getpass(""));
> + s = strdup(vpnc_getpass("", !!config[CONFIG
> _INTERACTIVE_STDIN]));
>
>
> At last:
>
> + CONFIG_INTERACTIVE_STDIN, 0, 1,
> + "--interactive-stdin",
> + "InteractiveStdin",
> + NULL,
> + "Always look for interactive input on stdin instead of the
> TTY",
> + NULL
> + }, {
>
> Can be autodetected?
> if (isatty(fileno(stdin)))
> printf("stdin is a tty\n");
> else
> printf("stdin is a pipe or a file\n");

Autodetection isn't the issue here, it's the behavior of getpass(). A
controlling program may want to write interactive answers to vpnc
directly, and that means it wants to use stdin. But getpass()
unconditionally tries to open /dev/tty no matter what. So if we
continue to use getpass() or if we continue to default to the existing
behavior (which I think we should do) then we still need some switch
indicating that the calling process wants to explicitly use stdin
instead of a tty.

I'm not sure how we could keep the existing behavior by default (which
is exactly what you want when you run vpnc manually from the command
line) and also allow input from a controlling process, without using
another switch.

So here's what I'll do for the next patchset:

1) split into two patches
2) redefine --interactive-stdin to imply "never use a tty"
3) fix up vpnc_getpass() to return an allocated value

Sound good?

Dan


_______________________________________________
vpnc-devel mailing list
vpnc-devel@unix-ag.uni-kl.de
https://lists.unix-ag.uni-kl.de/mailman/listinfo/vpnc-devel
http://www.unix-ag.uni-kl.de/~massar/vpnc/
Re: [PATCH] better stdin handling for controlling processes [ In reply to ]
On Thu, 2013-07-11 at 12:38 -0500, Dan Williams wrote:
> On Sat, 2013-06-29 at 10:16 +0800, Antonio Borneo wrote:
> > Hi Dan,
> >
> > in principle I agree on your patch, but I have few points to check with you.
> >
> >
> > On Sat, Jun 29, 2013 at 1:45 AM, Dan Williams <dcbw@redhat.com> wrote:
> >
> > > ...
> > >
> > > There are two issues controlling vpnc as a child process:
> > >
> > > 1) vpnc's config file processing logic uses EOF ... Switch to fgetc() ...
> > >
> > > 2) getpass() tries to use /dev/tty by default ...
> > >
> >
> > Two separate patches would help review :-)
>
> I will do this.
>
> >
> > One thing that would be nice is some indication of whether vpnc has this
> > > patch included or not, so that NM-vpnc could alter its behavior
> > > automatically.
> > > Any thoughts on that?
> > >
> >
> > The version of vpnc includes the svn revision number.
> > Would it be enough to detect if this patch is present?
> > Depending on your method to clone and build, you could loose svn revision
> > number.
> > If you don't use svn, clone the repository with git-svn. It keeps the right
> > revision from svn.
>
> Ideally I could detect this at runtime; after the patch is committed
> could we update the vpnc version so that "vpnc --version" could be used
> to detect that the patch is applied?
>
> > Could even be time to create a new version of vpnc!
>
> I agree :)
>
> > #include <unistd.h>
> > #include <string.h>
> > #include <errno.h>
> > +#include <termios.h>
> >
> > Is this fine on non Linux targets?
>
> It may not be. The changes in this patch are really only necessary when
> stdin is being used, so perhaps we could just drop the termios stuff
> completely with stdin? The only reason I added it was that I was
> attempting to duplicate the functionality of the libc getpass() function
> to minimize regressions.
>
> > I was quickly googling to find an implementation of getpass(), and found
> > this
> > http://cpansearch.perl.org/src/RAM/kit-2.0.38/des/getpass.c
> > It supports also Windows (not the case for vpnc), but in general it uses a
> > more complex mechanism.
> > Would be good to check widely.
>
> Or avoid the problem entirely by not doing any termios; we could define
> the --interactive-stdin option to mean that vpnc expects a non-tty and
> would never do TTY operations on the file descriptor, which would be
> fine with me.
>
> > + /* Standard getpass() tries to open the TTY first so if the caller
> > + * always wants to use stdin, we can't use getpass().
> > + */
> > + if (!always_stdin)
> > + return getpass(prompt);
> >
> > "man getpass" reports that "This function is obsolete. Do not use it."
> > I think this is the right time to drop it.
>
> Yeah, though we could do this with another patch later? It's somewhat
> orthogonal to the current patchset if we redefine --interactive-stdin to
> ignore TTY stuff. Which means my patches would also be simpler. And
> less risk of regressions.
>
> >
> > + static char *buf;
> > + static size_t bufsize;
> > ...
> > + nread = getline(&buf, &bufsize, stdin);
> >
> > You want get rid of getline(), but now you introduce it again!
>
> True.
>
> > Here I would like to explicitly set buf to NULL to show what I'm doing.
> > Not mandatory, but helpful for code review.
>
> True and I typically do this. Technically though since it's static data
> the system will initialize it to NULL anyway.
>
> > Then, code above introduces a bug.
> > Once getline() is call the first time, it allocates the buffer. Fine!
> > But if called a second time, there is no way to return a password longer
> > that the text entered previously.
>
> Good point. And realloc() semantics are tricky to get correct though,
> so if you're happy with me making vpnc_getpass() return an allocated
> value that callers can free, it would be simpler.
>
> > You can see that this function can be called in different contexts, as below
> >
> > case CONFIG_IPSEC_SECRET:
> > case CONFIG_XAUTH_PASSWORD:
> > - s = strdup(getpass(""));
> > + s = strdup(vpnc_getpass("", !!config[CONFIG
> > _INTERACTIVE_STDIN]));
> >
> >
> > At last:
> >
> > + CONFIG_INTERACTIVE_STDIN, 0, 1,
> > + "--interactive-stdin",
> > + "InteractiveStdin",
> > + NULL,
> > + "Always look for interactive input on stdin instead of the
> > TTY",
> > + NULL
> > + }, {
> >
> > Can be autodetected?
> > if (isatty(fileno(stdin)))
> > printf("stdin is a tty\n");
> > else
> > printf("stdin is a pipe or a file\n");
>
> Autodetection isn't the issue here, it's the behavior of getpass(). A
> controlling program may want to write interactive answers to vpnc
> directly, and that means it wants to use stdin. But getpass()
> unconditionally tries to open /dev/tty no matter what. So if we
> continue to use getpass() or if we continue to default to the existing
> behavior (which I think we should do) then we still need some switch
> indicating that the calling process wants to explicitly use stdin
> instead of a tty.
>
> I'm not sure how we could keep the existing behavior by default (which
> is exactly what you want when you run vpnc manually from the command
> line) and also allow input from a controlling process, without using
> another switch.
>
> So here's what I'll do for the next patchset:
>
> 1) split into two patches
> 2) redefine --interactive-stdin to imply "never use a tty"
> 3) fix up vpnc_getpass() to return an allocated value
>
> Sound good?

Also, vpnc appears to only accept '\n' as the newline, is it OK if I
make this more explicit in the getline() reimplementation? (eg, treat
\r like any other character)

Dan

_______________________________________________
vpnc-devel mailing list
vpnc-devel@unix-ag.uni-kl.de
https://lists.unix-ag.uni-kl.de/mailman/listinfo/vpnc-devel
http://www.unix-ag.uni-kl.de/~massar/vpnc/