Merge branch 'jk/git-prompt'

* jk/git-prompt:
  contrib: add credential helper for OS X Keychain
  Makefile: OS X has /dev/tty
  Makefile: linux has /dev/tty
  credential: use git_prompt instead of git_getpass
  prompt: use git_terminal_prompt
  add generic terminal prompt function
  refactor git_getpass into generic prompt function
  move git_getpass to its own source file
  imap-send: don't check return value of git_getpass
  imap-send: avoid buffer overflow

Conflicts:
	Makefile
This commit is contained in:
Junio C Hamano 2011-12-22 11:27:23 -08:00
commit ded408fd20
12 changed files with 374 additions and 60 deletions

View File

@ -245,6 +245,9 @@ all::
# #
# Define NO_REGEX if you have no or inferior regex support in your C library. # 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 # 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. In a GETTEXT_POISON build, you can turn all strings marked
# for translation into gibberish by setting the GIT_GETTEXT_POISON variable # for translation into gibberish by setting the GIT_GETTEXT_POISON variable
@ -543,6 +546,7 @@ LIB_H += compat/bswap.h
LIB_H += compat/cygwin.h LIB_H += compat/cygwin.h
LIB_H += compat/mingw.h LIB_H += compat/mingw.h
LIB_H += compat/obstack.h LIB_H += compat/obstack.h
LIB_H += compat/terminal.h
LIB_H += compat/win32/pthread.h LIB_H += compat/win32/pthread.h
LIB_H += compat/win32/syslog.h LIB_H += compat/win32/syslog.h
LIB_H += compat/win32/poll.h LIB_H += compat/win32/poll.h
@ -585,6 +589,7 @@ LIB_H += parse-options.h
LIB_H += patch-ids.h LIB_H += patch-ids.h
LIB_H += pkt-line.h LIB_H += pkt-line.h
LIB_H += progress.h LIB_H += progress.h
LIB_H += prompt.h
LIB_H += quote.h LIB_H += quote.h
LIB_H += reflog-walk.h LIB_H += reflog-walk.h
LIB_H += refs.h LIB_H += refs.h
@ -632,6 +637,7 @@ LIB_OBJS += color.o
LIB_OBJS += combine-diff.o LIB_OBJS += combine-diff.o
LIB_OBJS += commit.o LIB_OBJS += commit.o
LIB_OBJS += compat/obstack.o LIB_OBJS += compat/obstack.o
LIB_OBJS += compat/terminal.o
LIB_OBJS += config.o LIB_OBJS += config.o
LIB_OBJS += connect.o LIB_OBJS += connect.o
LIB_OBJS += connected.o LIB_OBJS += connected.o
@ -694,6 +700,7 @@ LIB_OBJS += pkt-line.o
LIB_OBJS += preload-index.o LIB_OBJS += preload-index.o
LIB_OBJS += pretty.o LIB_OBJS += pretty.o
LIB_OBJS += progress.o LIB_OBJS += progress.o
LIB_OBJS += prompt.o
LIB_OBJS += quote.o LIB_OBJS += quote.o
LIB_OBJS += reachable.o LIB_OBJS += reachable.o
LIB_OBJS += read-cache.o LIB_OBJS += read-cache.o
@ -856,6 +863,7 @@ ifeq ($(uname_S),Linux)
NO_MKSTEMPS = YesPlease NO_MKSTEMPS = YesPlease
HAVE_PATHS_H = YesPlease HAVE_PATHS_H = YesPlease
LIBC_CONTAINS_LIBINTL = YesPlease LIBC_CONTAINS_LIBINTL = YesPlease
HAVE_DEV_TTY = YesPlease
endif endif
ifeq ($(uname_S),GNU/kFreeBSD) ifeq ($(uname_S),GNU/kFreeBSD)
NO_STRLCPY = YesPlease NO_STRLCPY = YesPlease
@ -917,6 +925,7 @@ ifeq ($(uname_S),Darwin)
endif endif
NO_MEMMEM = YesPlease NO_MEMMEM = YesPlease
USE_ST_TIMESPEC = YesPlease USE_ST_TIMESPEC = YesPlease
HAVE_DEV_TTY = YesPlease
endif endif
ifeq ($(uname_S),SunOS) ifeq ($(uname_S),SunOS)
NEEDS_SOCKET = YesPlease NEEDS_SOCKET = YesPlease
@ -1685,6 +1694,10 @@ ifdef HAVE_LIBCHARSET_H
BASIC_CFLAGS += -DHAVE_LIBCHARSET_H BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
endif endif
ifdef HAVE_DEV_TTY
BASIC_CFLAGS += -DHAVE_DEV_TTY
endif
ifdef DIR_HAS_BSD_GROUP_SEMANTICS ifdef DIR_HAS_BSD_GROUP_SEMANTICS
COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
endif endif

View File

@ -1028,7 +1028,6 @@ struct ref {
extern struct ref *find_ref_by_name(const struct ref *list, const char *name); extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
#define CONNECT_VERBOSE (1u << 0) #define CONNECT_VERBOSE (1u << 0)
extern char *git_getpass(const char *prompt);
extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags); extern struct child_process *git_connect(int fd[2], const char *url, const char *prog, int flags);
extern int finish_connect(struct child_process *conn); extern int finish_connect(struct child_process *conn);
extern int git_connection_is_socket(struct child_process *conn); extern int git_connection_is_socket(struct child_process *conn);

81
compat/terminal.c Normal file
View File

@ -0,0 +1,81 @@
#include "git-compat-util.h"
#include "compat/terminal.h"
#include "sigchain.h"
#include "strbuf.h"
#ifdef HAVE_DEV_TTY
static int term_fd = -1;
static struct termios old_term;
static void restore_term(void)
{
if (term_fd < 0)
return;
tcsetattr(term_fd, TCSAFLUSH, &old_term);
term_fd = -1;
}
static void restore_term_on_signal(int sig)
{
restore_term();
sigchain_pop(sig);
raise(sig);
}
char *git_terminal_prompt(const char *prompt, int echo)
{
static struct strbuf buf = STRBUF_INIT;
int r;
FILE *fh;
fh = fopen("/dev/tty", "w+");
if (!fh)
return NULL;
if (!echo) {
struct termios t;
if (tcgetattr(fileno(fh), &t) < 0) {
fclose(fh);
return NULL;
}
old_term = t;
term_fd = fileno(fh);
sigchain_push_common(restore_term_on_signal);
t.c_lflag &= ~ECHO;
if (tcsetattr(fileno(fh), TCSAFLUSH, &t) < 0) {
term_fd = -1;
fclose(fh);
return NULL;
}
}
fputs(prompt, fh);
fflush(fh);
r = strbuf_getline(&buf, fh, '\n');
if (!echo) {
putc('\n', fh);
fflush(fh);
}
restore_term();
fclose(fh);
if (r == EOF)
return NULL;
return buf.buf;
}
#else
char *git_terminal_prompt(const char *prompt, int echo)
{
return getpass(prompt);
}
#endif

6
compat/terminal.h Normal file
View File

@ -0,0 +1,6 @@
#ifndef COMPAT_TERMINAL_H
#define COMPAT_TERMINAL_H
char *git_terminal_prompt(const char *prompt, int echo);
#endif /* COMPAT_TERMINAL_H */

View File

@ -608,47 +608,3 @@ int finish_connect(struct child_process *conn)
free(conn); free(conn);
return code; return code;
} }
char *git_getpass(const char *prompt)
{
const char *askpass;
struct child_process pass;
const char *args[3];
static struct strbuf buffer = 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");
return result;
}
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"));
return buffer.buf;
}

View File

@ -0,0 +1 @@
git-credential-osxkeychain

View File

@ -0,0 +1,14 @@
all:: git-credential-osxkeychain
CC = gcc
RM = rm -f
CFLAGS = -g -Wall
git-credential-osxkeychain: git-credential-osxkeychain.o
$(CC) -o $@ $< -Wl,-framework -Wl,Security
git-credential-osxkeychain.o: git-credential-osxkeychain.c
$(CC) -c $(CFLAGS) $<
clean:
$(RM) git-credential-osxkeychain git-credential-osxkeychain.o

View File

@ -0,0 +1,173 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <Security/Security.h>
static SecProtocolType protocol;
static char *host;
static char *path;
static char *username;
static char *password;
static UInt16 port;
static void die(const char *err, ...)
{
char msg[4096];
va_list params;
va_start(params, err);
vsnprintf(msg, sizeof(msg), err, params);
fprintf(stderr, "%s\n", msg);
va_end(params);
exit(1);
}
static void *xstrdup(const char *s1)
{
void *ret = strdup(s1);
if (!ret)
die("Out of memory");
return ret;
}
#define KEYCHAIN_ITEM(x) (x ? strlen(x) : 0), x
#define KEYCHAIN_ARGS \
NULL, /* default keychain */ \
KEYCHAIN_ITEM(host), \
0, NULL, /* account domain */ \
KEYCHAIN_ITEM(username), \
KEYCHAIN_ITEM(path), \
port, \
protocol, \
kSecAuthenticationTypeDefault
static void write_item(const char *what, const char *buf, int len)
{
printf("%s=", what);
fwrite(buf, 1, len, stdout);
putchar('\n');
}
static void find_username_in_item(SecKeychainItemRef item)
{
SecKeychainAttributeList list;
SecKeychainAttribute attr;
list.count = 1;
list.attr = &attr;
attr.tag = kSecAccountItemAttr;
if (SecKeychainItemCopyContent(item, NULL, &list, NULL, NULL))
return;
write_item("username", attr.data, attr.length);
SecKeychainItemFreeContent(&list, NULL);
}
static void find_internet_password(void)
{
void *buf;
UInt32 len;
SecKeychainItemRef item;
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, &len, &buf, &item))
return;
write_item("password", buf, len);
if (!username)
find_username_in_item(item);
SecKeychainItemFreeContent(NULL, buf);
}
static void delete_internet_password(void)
{
SecKeychainItemRef item;
/*
* Require at least a protocol and host for removal, which is what git
* will give us; if you want to do something more fancy, use the
* Keychain manager.
*/
if (!protocol || !host)
return;
if (SecKeychainFindInternetPassword(KEYCHAIN_ARGS, 0, NULL, &item))
return;
SecKeychainItemDelete(item);
}
static void add_internet_password(void)
{
/* Only store complete credentials */
if (!protocol || !host || !username || !password)
return;
if (SecKeychainAddInternetPassword(
KEYCHAIN_ARGS,
KEYCHAIN_ITEM(password),
NULL))
return;
}
static void read_credential(void)
{
char buf[1024];
while (fgets(buf, sizeof(buf), stdin)) {
char *v;
if (!strcmp(buf, "\n"))
break;
buf[strlen(buf)-1] = '\0';
v = strchr(buf, '=');
if (!v)
die("bad input: %s", buf);
*v++ = '\0';
if (!strcmp(buf, "protocol")) {
if (!strcmp(v, "https"))
protocol = kSecProtocolTypeHTTPS;
else if (!strcmp(v, "http"))
protocol = kSecProtocolTypeHTTP;
else /* we don't yet handle other protocols */
exit(0);
}
else if (!strcmp(buf, "host")) {
char *colon = strchr(v, ':');
if (colon) {
*colon++ = '\0';
port = atoi(colon);
}
host = xstrdup(v);
}
else if (!strcmp(buf, "path"))
path = xstrdup(v);
else if (!strcmp(buf, "username"))
username = xstrdup(v);
else if (!strcmp(buf, "password"))
password = xstrdup(v);
}
}
int main(int argc, const char **argv)
{
const char *usage =
"Usage: git credential-osxkeychain <get|store|erase>";
if (!argv[1])
die(usage);
read_credential();
if (!strcmp(argv[1], "get"))
find_internet_password();
else if (!strcmp(argv[1], "store"))
add_internet_password();
else if (!strcmp(argv[1], "erase"))
delete_internet_password();
/* otherwise, ignore unknown action */
return 0;
}

View File

@ -3,6 +3,7 @@
#include "string-list.h" #include "string-list.h"
#include "run-command.h" #include "run-command.h"
#include "url.h" #include "url.h"
#include "prompt.h"
void credential_init(struct credential *c) void credential_init(struct credential *c)
{ {
@ -108,7 +109,8 @@ static void credential_describe(struct credential *c, struct strbuf *out)
strbuf_addf(out, "/%s", c->path); strbuf_addf(out, "/%s", c->path);
} }
static char *credential_ask_one(const char *what, struct credential *c) static char *credential_ask_one(const char *what, struct credential *c,
int flags)
{ {
struct strbuf desc = STRBUF_INIT; struct strbuf desc = STRBUF_INIT;
struct strbuf prompt = STRBUF_INIT; struct strbuf prompt = STRBUF_INIT;
@ -120,11 +122,7 @@ static char *credential_ask_one(const char *what, struct credential *c)
else else
strbuf_addf(&prompt, "%s: ", what); strbuf_addf(&prompt, "%s: ", what);
/* FIXME: for usernames, we should do something less magical that r = git_prompt(prompt.buf, flags);
* actually echoes the characters. However, we need to read from
* /dev/tty and not stdio, which is not portable (but getpass will do
* it for us). http.c uses the same workaround. */
r = git_getpass(prompt.buf);
strbuf_release(&desc); strbuf_release(&desc);
strbuf_release(&prompt); strbuf_release(&prompt);
@ -134,9 +132,11 @@ static char *credential_ask_one(const char *what, struct credential *c)
static void credential_getpass(struct credential *c) static void credential_getpass(struct credential *c)
{ {
if (!c->username) if (!c->username)
c->username = credential_ask_one("Username", c); c->username = credential_ask_one("Username", c,
PROMPT_ASKPASS|PROMPT_ECHO);
if (!c->password) if (!c->password)
c->password = credential_ask_one("Password", c); c->password = credential_ask_one("Password", c,
PROMPT_ASKPASS);
} }
int credential_read(struct credential *c, FILE *fp) int credential_read(struct credential *c, FILE *fp)

View File

@ -25,6 +25,7 @@
#include "cache.h" #include "cache.h"
#include "exec_cmd.h" #include "exec_cmd.h"
#include "run-command.h" #include "run-command.h"
#include "prompt.h"
#ifdef NO_OPENSSL #ifdef NO_OPENSSL
typedef void *SSL; typedef void *SSL;
#else #else
@ -1208,13 +1209,10 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
goto bail; goto bail;
} }
if (!srvc->pass) { if (!srvc->pass) {
char prompt[80]; struct strbuf prompt = STRBUF_INIT;
sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host); strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host);
arg = git_getpass(prompt); arg = git_getpass(prompt.buf);
if (!arg) { strbuf_release(&prompt);
perror("getpass");
exit(1);
}
if (!*arg) { if (!*arg) {
fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host); fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
goto bail; goto bail;

63
prompt.c Normal file
View File

@ -0,0 +1,63 @@
#include "cache.h"
#include "run-command.h"
#include "strbuf.h"
#include "prompt.h"
#include "compat/terminal.h"
static char *do_askpass(const char *cmd, const char *prompt)
{
struct child_process pass;
const char *args[3];
static struct strbuf buffer = STRBUF_INIT;
args[0] = cmd;
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 get '%s' from %s\n", prompt, cmd);
close(pass.out);
if (finish_command(&pass))
exit(1);
strbuf_setlen(&buffer, strcspn(buffer.buf, "\r\n"));
return buffer.buf;
}
char *git_prompt(const char *prompt, int flags)
{
char *r;
if (flags & PROMPT_ASKPASS) {
const char *askpass;
askpass = getenv("GIT_ASKPASS");
if (!askpass)
askpass = askpass_program;
if (!askpass)
askpass = getenv("SSH_ASKPASS");
if (askpass && *askpass)
return do_askpass(askpass, prompt);
}
r = git_terminal_prompt(prompt, flags & PROMPT_ECHO);
if (!r)
die_errno("could not read '%s'", prompt);
return r;
}
char *git_getpass(const char *prompt)
{
return git_prompt(prompt, PROMPT_ASKPASS);
}

10
prompt.h Normal file
View File

@ -0,0 +1,10 @@
#ifndef PROMPT_H
#define PROMPT_H
#define PROMPT_ASKPASS (1<<0)
#define PROMPT_ECHO (1<<1)
char *git_prompt(const char *prompt, int flags);
char *git_getpass(const char *prompt);
#endif /* PROMPT_H */