From: Jeff King <hidden> Date: 2016-06-15 22:52:35
This is a re-roll of the earlier "echo usernames as they are typed"
series here:
http://article.gmane.org/gmane.comp.version-control.git/185970
It turns out that getpass() isn't even really good at getting passwords.
See patch 5/7 for details. This series replaces it entirely if you set
HAVE_DEV_TTY (otherwise, we continue to fallback to getpass()).
[1/7]: imap-send: avoid buffer overflow
[2/7]: imap-send: don't check return value of git_getpass
[3/7]: move git_getpass to its own source file
[4/7]: refactor git_getpass into generic prompt function
[5/7]: add generic terminal prompt function
[6/7]: prompt: use git_terminal_prompt
[7/7]: credential: use git_prompt instead of git_getpass
It should be applied on top of the jk/credentials topic.
-Peff
From: Jeff King <hidden> Date: 2016-06-15 22:52:35
We format the password prompt in an 80-character static
buffer. It contains the remote host and username, so it's
unlikely to overflow (or be exploitable by a remote
attacker), but there's no reason not to be careful and use
a strbuf.
Signed-off-by: Jeff King <redacted>
---
Just something I noticed while doing the cleanup in the next patch.
imap-send.c | 7 ++++---
1 files changed, 4 insertions(+), 3 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 22:52:35
git_getpass will always die() if we weren't able to get
input, so there's no point looking for NULL.
Signed-off-by: Jeff King <redacted>
---
Not a bug, but just useless code I noticed.
imap-send.c | 4 ----
1 files changed, 0 insertions(+), 4 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 22:52:35
This is currently in connect.c, but really has nothing to
do with the git protocol itself. Let's make a new source
file all about prompting the user, which will make it
cleaner to refactor.
Signed-off-by: Jeff King <redacted>
---
Same as patch 1 from the previous series.
Makefile | 2 ++
cache.h | 1 -
connect.c | 44 --------------------------------------------
credential.c | 1 +
imap-send.c | 1 +
prompt.c | 48 ++++++++++++++++++++++++++++++++++++++++++++++++
prompt.h | 6 ++++++
7 files changed, 58 insertions(+), 45 deletions(-)
create mode 100644 prompt.c
create mode 100644 prompt.h
@@ -619,47 +619,3 @@ int finish_connect(struct child_process *conn)free(conn);returncode;}--char*git_getpass(constchar*prompt)-{-constchar*askpass;-structchild_processpass;-constchar*args[3];-staticstructstrbufbuffer=STRBUF_INIT;--askpass=getenv("GIT_ASKPASS");-if(!askpass)-askpass=askpass_program;-if(!askpass)-askpass=getenv("SSH_ASKPASS");-if(!askpass||!(*askpass)){-char*result=getpass(prompt);-if(!result)-die_errno("Could not read password");-returnresult;-}--args[0]=askpass;-args[1]=prompt;-args[2]=NULL;--memset(&pass,0,sizeof(pass));-pass.argv=args;-pass.out=-1;--if(start_command(&pass))-exit(1);--strbuf_reset(&buffer);-if(strbuf_read(&buffer,pass.out,20)<0)-die("failed to read password from %s\n",askpass);--close(pass.out);--if(finish_command(&pass))-exit(1);--strbuf_setlen(&buffer,strcspn(buffer.buf,"\r\n"));--returnbuffer.buf;-}
@@ -0,0 +1,48 @@+#include"cache.h"+#include"run-command.h"+#include"strbuf.h"+#include"prompt.h"++char*git_getpass(constchar*prompt)+{+constchar*askpass;+structchild_processpass;+constchar*args[3];+staticstructstrbufbuffer=STRBUF_INIT;++askpass=getenv("GIT_ASKPASS");+if(!askpass)+askpass=askpass_program;+if(!askpass)+askpass=getenv("SSH_ASKPASS");+if(!askpass||!(*askpass)){+char*result=getpass(prompt);+if(!result)+die_errno("Could not read password");+returnresult;+}++args[0]=askpass;+args[1]=prompt;+args[2]=NULL;++memset(&pass,0,sizeof(pass));+pass.argv=args;+pass.out=-1;++if(start_command(&pass))+exit(1);++strbuf_reset(&buffer);+if(strbuf_read(&buffer,pass.out,20)<0)+die("failed to read password from %s\n",askpass);++close(pass.out);++if(finish_command(&pass))+exit(1);++strbuf_setlen(&buffer,strcspn(buffer.buf,"\r\n"));++returnbuffer.buf;+}
From: Jeff King <hidden> Date: 2016-06-15 22:52:35
This will allow callers to specify more options (e.g.,
leaving echo on). The original git_getpass becomes a slim
wrapper around the new function.
Signed-off-by: Jeff King <redacted>
---
Similar to patch 2 from the previous series. Two big differences:
1. The first series accidentally dropped the "die if we don't get a
password" behavior during the refactor, but we want to keep it.
2. The first series had a special "name" parameter just for generating
error messages. This drops it in the name of simplicity, so error
messages have gone from (assuming you don't have a tty):
Could not read password: No such device or address
to:
Could not read 'Username for 'https://example.com': ': No such
device or address
which is verbose, yes, but contains a little more useful
information. The formatting is rather unfortunate, but I don't think
it's worth worrying too much about.
prompt.c | 46 ++++++++++++++++++++++++++++++----------------
prompt.h | 3 +++
2 files changed, 33 insertions(+), 16 deletions(-)
@@ -3,26 +3,13 @@#include"strbuf.h"#include"prompt.h"-char*git_getpass(constchar*prompt)+staticchar*do_askpass(constchar*cmd,constchar*prompt){-constchar*askpass;structchild_processpass;constchar*args[3];staticstructstrbufbuffer=STRBUF_INIT;-askpass=getenv("GIT_ASKPASS");-if(!askpass)-askpass=askpass_program;-if(!askpass)-askpass=getenv("SSH_ASKPASS");-if(!askpass||!(*askpass)){-char*result=getpass(prompt);-if(!result)-die_errno("Could not read password");-returnresult;-}--args[0]=askpass;+args[0]=cmd;args[1]=prompt;args[2]=NULL;
@@ -35,7 +22,7 @@strbuf_reset(&buffer);if(strbuf_read(&buffer,pass.out,20)<0)-die("failed to read password from %s\n",askpass);+die("failed to get '%s' from %s\n",prompt,cmd);close(pass.out);
@@ -46,3 +33,30 @@returnbuffer.buf;}++char*git_prompt(constchar*prompt,intflags)+{+char*r;++if(flags&PROMPT_ASKPASS){+constchar*askpass;++askpass=getenv("GIT_ASKPASS");+if(!askpass)+askpass=askpass_program;+if(!askpass)+askpass=getenv("SSH_ASKPASS");+if(askpass&&*askpass)+returndo_askpass(askpass,prompt);+}++r=getpass(prompt);+if(!r)+die_errno("could not read '%s'",prompt);+returnr;+}++char*git_getpass(constchar*prompt)+{+returngit_prompt(prompt,PROMPT_ASKPASS);+}
From: Jeff King <hidden> Date: 2016-06-15 22:52:35
When we need to prompt the user for input interactively, we
want to access their terminal directly. We can't rely on
stdio because it may be connected to pipes or files, rather
than the terminal. Instead, we use "getpass()", because it
abstracts the idea of prompting and reading from the
terminal. However, it has some problems:
1. It never echoes the typed characters, which makes it OK
for passwords but annoying for other input (like usernames).
2. Some implementations of getpass() have an extremely
small input buffer (e.g., Solaris 8 is reported to
support only 8 characters).
3. Some implementations of getpass() will fall back to
reading from stdin (e.g., glibc). We explicitly don't
want this, because our stdin may be connected to a pipe
speaking a particular protocol, and reading will
disrupt the protocol flow (e.g., the remote-curl
helper).
4. Some implementations of getpass() turn off signals, so
that hitting "^C" on the terminal does not break out of
the password prompt. This can be a mild annoyance.
Instead, let's provide an abstract "git_terminal_prompt"
function that addresses these concerns. This patch includes
an implementation based on /dev/tty, enabled by setting
HAVE_DEV_TTY. The fallback is to use getpass() as before.
For now, only Linux enables this by default. People on other
/dev/tty-enabled systems can submit patches to turn it on
once they have tested it.
Signed-off-by: Jeff King <redacted>
---
This is the interesting one. I suspect most Unixes will want to use
this, but I was conservative about enabling it. msysgit can just drop
its own function into compat/terminal.c, and the existing custom
getpass() implementation can just go away.
Makefile | 10 ++++++
compat/terminal.c | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++
compat/terminal.h | 6 ++++
3 files changed, 97 insertions(+), 0 deletions(-)
create mode 100644 compat/terminal.c
create mode 100644 compat/terminal.h
@@ -227,6 +227,9 @@ all::## Define NO_REGEX if you have no or inferior regex support in your C library.#+# Define HAVE_DEV_TTY if your system can open /dev/tty to interact with the+# user.+## Define GETTEXT_POISON if you are debugging the choice of strings marked# for translation. In a GETTEXT_POISON build, you can turn all strings marked# for translation into gibberish by setting the GIT_GETTEXT_POISON variable
From: Jeff King <hidden> Date: 2016-06-15 22:52:35
Our custom implementation of git_terminal_prompt has many
advantages over regular getpass(), as described in the prior
commit.
This also lets us implement a PROMPT_ECHO flag for callers
who want it.
Signed-off-by: Jeff King <redacted>
---
prompt.c | 3 ++-
prompt.h | 1 +
2 files changed, 3 insertions(+), 1 deletions(-)
From: Jeff King <hidden> Date: 2016-06-15 22:52:35
We use git_getpass to retrieve the username and password
from the terminal. However, git_getpass will not echo the
username as the user types. We can fix this by using the
more generic git_prompt, which underlies git_getpass but
lets us specify an "echo" option.
Signed-off-by: Jeff King <redacted>
---
credential.c | 15 +++++++--------
1 files changed, 7 insertions(+), 8 deletions(-)
@@ -121,11 +122,7 @@ static void credential_describe(struct credential *c, struct strbuf *out)elsestrbuf_addf(&prompt,"%s: ",what);-/* FIXME: for usernames, we should do something less magical that-*actuallyechoesthecharacters.However,weneedtoreadfrom-*/dev/ttyandnotstdio,whichisnotportable(butgetpasswilldo-*itforus).http.cusesthesameworkaround.*/-r=git_getpass(prompt.buf);+r=git_prompt(prompt.buf,flags);strbuf_release(&desc);strbuf_release(&prompt);
Here you use HAVE_DEV_TTY (by the way, I wonder if it could be
automatically detected by ./configure script)...
[...]
...and here you have NO_DEV_TTY
Whoops. Thanks for catching. I converted it to NO_DEV_TTY, which would
turn this code on by default (because I think _most_ platforms we use
are going to want this), but then I decided to go the conservative route
and let platforms opt into it.
And of course since my platform is the one that enables it, I didn't
notice during my testing.
I'll fix it for the next re-roll.
-Peff
From: Junio C Hamano <hidden> Date: 2016-06-15 22:52:36
Jeff King [off-list ref] writes:
2. The first series had a special "name" parameter just for generating
error messages. This drops it in the name of simplicity, so error
messages have gone from (assuming you don't have a tty):
Could not read password: No such device or address
to:
Could not read 'Username for 'https://example.com': ': No such
device or address
which is verbose, yes, but contains a little more useful
information. The formatting is rather unfortunate,...
It also would be unpleasant to i18n it, I suspect.
+ r = getpass(prompt);
+ if (!r)
+ die_errno("could not read '%s'", prompt);
Taking advantage of the "prompt-string"-ness of the message, this might be
a cuter workaround:
fatal: Password: <<could not be read>>
But I do not think it matters that much. Let's queue what you have, and
work out these details in-tree.