Page MenuHome GnuPG

GPG_TTY needs to be defaulted in more places than currently
Closed, WontfixPublic

Description

You guys had the right idea in sm/misc.c around line 57. If GPG_TTY is not set, and we
can't get a ttyname off of fd 0, then use "/dev/tty", which is a magic character-special
device that always refers to the current process's controlling terminal.

That logic (that was already in there, thanks to you guys) works great! It just needs to
be copied to the two *other* places in the code that do the same kind of thing.

To see this problem in action, make sure you do not have GPG_TTY set ("unsetenv GPG_TTY")
and run:

touch a_file
gpg -a --clearsign < a_file > out_file

You will notice that it gets confused and fails to pop up the ncurses-based screen for
entering your passphrase.

Enclosed is a patch. Thanks!!

Details

Version
2.0.19

Event Timeline

Ack! Updated patch. Silly mistake in the first one.

That does not work.

For example: The GPG process may map /dev/tty to /dev/pts/4. Then it
passes the string "/dev/tty" via gpg-agent to pinentry. Pinentry is
called by gpg-agent but gpg-agent was started on different tty. Thus
for gpg-agent /dev/tty may map to /dev/pts/2. The pinentry will now
pop up at /dev/pts/2 - it is very likely that no terminal is attached
to it and thus you won't even see a pinentry on some other tty.

Agreed, the fallback we currently have does not work either in this
case. Printing a warning if GPG_TTY is not set would probably be the
better alternative.

Hmmmm.

Would it work to open /dev/tty, and then call ttyname on *that*? Rather than
calling ttyname on stdin always?

I really dislike the solution of "the user must set $GPG_TTY". That is broken,
period. If I'm not making use of any advanced functionality like the agent,
please don't penalize me (as a user) for the fact that such advanced
functionality *exists*.

I want the simple case -- i.e. I'm logged in, and I run gpg on a single tty --
to Just Work, without me having to set any environment variables to make it
work.

Another thought would be that ttyname(2) is possibly somewhat more likely to
give a useful result than either ttyname(0) or ttyname(1). That is, assuming
that people redirect stdin and stdout all the time, but rarely redirect stderr.

I'm just tossing out ideas here. My gut reaction is still "just use /dev/tty",
but I'm hoping that if I toss out some ideas that maybe one of them will be
helpful. :-)

Consider the case of GPGME. All standard descriptors are not connected to a tty.
I don't know a way to get the actual terminal name in a portable way. Thus we
need to rely on the shell to give use the name of the tty and pass it via an envvar.

In your case you may want to use gpg-agent’s --keep-tty option.

Looks like calling ttyname() on a freshly open()ed "/dev/tty" works, at least on
FreeBSD:

cat ttyname.c

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char **argv)
{
int fd = open("/dev/tty", O_RDWR, 0);
char *s = ttyname(fd);
printf("%s\n", s ? s : "NULL");
return 0;
}

gcc -o ttyname ttyname.c
./ttyname

/dev/pts/9

./ttyname < /dev/null >& /tmp/foo
cat /tmp/foo

/dev/pts/9

Notice that even though the program's stdin was /dev/null, and the program's
stdout and stderr were both going to a file (I use tcsh, hence the >& syntax),
and yet it still managed to figure out what the terminal was.

If I come up with a modified patch that opens /dev/tty, calls ttyname on *that*,
and gives *that* tty name to pinentry, will you consider it?

Thanks!!

(btw, I don't use the agent at all... my usage of gpg is very vanilla, just the
plain way of using it on the command line that has worked ever since gpg1, but
is now broken in gpg2)

Ugh. That trick doesn't work on Solaris, it looks like.

The basic place I'm trying to get to is... in the simple case... a user logs in,
and isn't using gpg-agent, gpgme, or anything like that... and just types:

some_command | gpg -a --clearsign > some_file

that it will work.

It seems *to me at least*, like defaulting to the literal filename "/dev/tty",
as in my patch, at least *does no harm*.

Maybe it doesn't solve the gpg-agent case or the gpgme case 100%. But at least
it makes the simple case work. And people can always override it by setting
GPG_TTY, if they need to.

Make sense?

It does not work on glibc based systems either. Actually the correct
way would be to use ctermid(3) but that has the same problem as
ttyname - it even returns a fixed string without trying to find the
tty in /dev/ or /proc.

Pinentry actually defaults to the default tty if no GPG_TTY has been
passed to it from gpg-agent. Here is the code from the curses
pinentry:

/* Open the desired terminal if necessary.  */
if (tty_name)
  {
    ttyfi = fopen (tty_name, "r");
    if (!ttyfi)

return -1;

ttyfo = fopen (tty_name, "w");
if (!ttyfo)

{

	  int err = errno;
	  fclose (ttyfi);
	  errno = err;
	  return -1;

}

    screen = newterm (tty_type, ttyfo, ttyfi);
    set_term (screen);
  }
else
  {
    if (!init_screen)

{

	  init_screen = 1;
	  initscr ();

}

else

clear ();

    }

TTY_NAME has been set via an Assuan option which should have come from
GPG_TTY. If this has not been set (or any of the --ttyname options
used), Pinentry uses init_scr. The problem is that gpg-agent and thus
pinentry usually has no controlling terminal and thus there is no default tty.

It works for you because you started gpg-agent on demand. That is
something to be avoid because it won't be able to cache the
passphrase then.

I have not checked whether your patch may harm. However, I remember
that we had quite some problems calling pinentry from a background
process and the way we do it today works in almost all cases -
assuming your system has been properly configured.

Yes, I saw the if (tty_name) in pinentry when I was looking through all of that
stuff. The problem for me is NOT that pinentry has no controlling terminal,
because I *am* starting the agent, as you say, on demand.

The problem for me is that pinentry has inherited file descriptor 0 from gpg,
and it is *not* a tty, it is the input file that I am asking gpg to process.

So no, the if (tty_name) thing doesn't really work too well if you are feeding
gpg something on its standard input, AND if you are starting gpg-agent on
demand.

To restate: If you are starting the agent on demand, AND if you are feeding gpg
data on standard input, then initscr() will NOT do the right thing. At least on
FreeBSD. Are you saying that on your OS, initscr() will, internally to itself,
open "/dev/tty"?

If that's the case for you, then it's not the case for me on FreeBSD. The
initscr() on FreeBSD doesn't do any magic, it just uses fds 0 and 1. Hence the
fact that I am trying to get it to open /dev/tty in that case.

Without having a controlling tty you can't get the name of the
controlling tty. That is why we need other ways to tell the
background process (i.e. gpg-agent) which tty a pinentry shall use.

It doesn't matter who calls the agent; if he has a tty in some
settings, that is not a fact we can rely upon. For example in 2.1
there will be no on-demand starting and stopping anymore; instead the
agent is started just once (you can even compile 2.0.19 with this
behaviour).

Your options are:

  • Set GPG_TTY for each new TTY.
  • Pass --ttyname=$(tty) on the GPG commandline.
  • Start the agent in advance and use --keep-tty --tyyname=$(tty) to lock the Pinentry to the tty the agent has been started.

BTW, it is common practice to dup fd 0 to /dev/null. Thus it does not
help to use ttyname (0) instead of ttyname (1) as the default.

OK, here's what I hear you saying: Even if my patch would do the right thing in the common case
for 2.0.19, when 2.1 comes along it will stop helping.

I agree with you that *if you are using a long-lived agent*, then the patch I had proposed is not
sufficient. I had been discounting that case as "not the common case"; now I realize it is going
to be the common case soon.

I think, at this point, we're going to have to consider using file descriptor passing (SCM_RIGHTS)
from the gpg to the agent.

It *will* be the case that, even if someone has redirected stdin/stdout in the gpg process, that
the gpg process will be able to open its "/dev/tty" and get a useful file descriptor. I agree with
you that it can't (at least not portably) work backwards from that to find the *name* of its tty,
but it can at least open /dev/tty, itself.

If gpg then passes that open file descriptor across the unix-domain socket to the agent (at least
I assume unix-domain sockets are used for gpg/agent communication), then the agent will have a
copy of *that* file descriptor.

Can you point me to where the agent receives the set of data that makes up one "request" or "work
unit"? I can try to make a new patch that uses file-descriptor passing.

To reiterate: making the user *in the common case* set an environment variable is not acceptable.
Environment variables are a nice thing to be able to set to change behavior from the default; but
if the user is happy with the default behavior they should not have to set any environment
variables in order to use a piece of software. If I had to have one environment variable setting
for every program I used regularly, my .cshrc would be *huge*!

Thanks!

Your solution will not work either. It still depends that a standard fd is
connected to the tty. This is not the case

  foo | gpg 2>gpg.log | bar

is a common pattern. I don't see why you have such a problem to set a variable
for each new terminal. If you don't like GPG_TTY, you may contact the Open
Group to define a new standard variable to POSIX. FWIW, GPG_TTY is used for
more than a decade.

Even in that case:

foo | gpg 2>gpg.log | bar

if the main gpg process opens "/dev/tty", then it will still get the user's
terminal. /dev/tty is strong magic -- it will work even if fds 0, 1, and 2 are
pointing elsewhere.

And if it then passes that file descriptor to an agent (I assume you're aware of
the ability to pass open file descriptors across unix-domain sockets, so that the
target process actually receives a copy of the same file descriptor) then the agent
will have the user's terminal also.

I reiterate my offer to help implement something like this, if you can point me to
the right place in the code (i.e. where *exactly* are these requests to the agent
that require user interaction happening? Where would be the best place to put file-
descriptor passing code?)

So you want to open /dev/tty (which gpg does anyway if needed; see
common/ttyio.c) and pass that to the agent so that the agent may pass
it on to Pinentry if he needs it. That may work.

However, I don't like it because you claim a resource of the tty and
send it to a different process to be used only if needed. With our
current system we use this resource only if we really needs it.

Although libassuan implements descriptor passing, it can't be used
with Pinentry, because that one uses a simple pipe and not a socket.
Yes, we could change that too, but then you can't use a shell script
instead of Pinentry anymore.

marcus claimed this task.
marcus added a subscriber: marcus.

werner said he doesn't like the proposed solution, so this is a wontfix.