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:
commit
ded408fd20
13
Makefile
13
Makefile
@ -245,6 +245,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
|
||||
@ -543,6 +546,7 @@ LIB_H += compat/bswap.h
|
||||
LIB_H += compat/cygwin.h
|
||||
LIB_H += compat/mingw.h
|
||||
LIB_H += compat/obstack.h
|
||||
LIB_H += compat/terminal.h
|
||||
LIB_H += compat/win32/pthread.h
|
||||
LIB_H += compat/win32/syslog.h
|
||||
LIB_H += compat/win32/poll.h
|
||||
@ -585,6 +589,7 @@ LIB_H += parse-options.h
|
||||
LIB_H += patch-ids.h
|
||||
LIB_H += pkt-line.h
|
||||
LIB_H += progress.h
|
||||
LIB_H += prompt.h
|
||||
LIB_H += quote.h
|
||||
LIB_H += reflog-walk.h
|
||||
LIB_H += refs.h
|
||||
@ -632,6 +637,7 @@ LIB_OBJS += color.o
|
||||
LIB_OBJS += combine-diff.o
|
||||
LIB_OBJS += commit.o
|
||||
LIB_OBJS += compat/obstack.o
|
||||
LIB_OBJS += compat/terminal.o
|
||||
LIB_OBJS += config.o
|
||||
LIB_OBJS += connect.o
|
||||
LIB_OBJS += connected.o
|
||||
@ -694,6 +700,7 @@ LIB_OBJS += pkt-line.o
|
||||
LIB_OBJS += preload-index.o
|
||||
LIB_OBJS += pretty.o
|
||||
LIB_OBJS += progress.o
|
||||
LIB_OBJS += prompt.o
|
||||
LIB_OBJS += quote.o
|
||||
LIB_OBJS += reachable.o
|
||||
LIB_OBJS += read-cache.o
|
||||
@ -856,6 +863,7 @@ ifeq ($(uname_S),Linux)
|
||||
NO_MKSTEMPS = YesPlease
|
||||
HAVE_PATHS_H = YesPlease
|
||||
LIBC_CONTAINS_LIBINTL = YesPlease
|
||||
HAVE_DEV_TTY = YesPlease
|
||||
endif
|
||||
ifeq ($(uname_S),GNU/kFreeBSD)
|
||||
NO_STRLCPY = YesPlease
|
||||
@ -917,6 +925,7 @@ ifeq ($(uname_S),Darwin)
|
||||
endif
|
||||
NO_MEMMEM = YesPlease
|
||||
USE_ST_TIMESPEC = YesPlease
|
||||
HAVE_DEV_TTY = YesPlease
|
||||
endif
|
||||
ifeq ($(uname_S),SunOS)
|
||||
NEEDS_SOCKET = YesPlease
|
||||
@ -1685,6 +1694,10 @@ ifdef HAVE_LIBCHARSET_H
|
||||
BASIC_CFLAGS += -DHAVE_LIBCHARSET_H
|
||||
endif
|
||||
|
||||
ifdef HAVE_DEV_TTY
|
||||
BASIC_CFLAGS += -DHAVE_DEV_TTY
|
||||
endif
|
||||
|
||||
ifdef DIR_HAS_BSD_GROUP_SEMANTICS
|
||||
COMPAT_CFLAGS += -DDIR_HAS_BSD_GROUP_SEMANTICS
|
||||
endif
|
||||
|
1
cache.h
1
cache.h
@ -1028,7 +1028,6 @@ struct ref {
|
||||
extern struct ref *find_ref_by_name(const struct ref *list, const char *name);
|
||||
|
||||
#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 int finish_connect(struct child_process *conn);
|
||||
extern int git_connection_is_socket(struct child_process *conn);
|
||||
|
81
compat/terminal.c
Normal file
81
compat/terminal.c
Normal 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
6
compat/terminal.h
Normal 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 */
|
44
connect.c
44
connect.c
@ -608,47 +608,3 @@ int finish_connect(struct child_process *conn)
|
||||
free(conn);
|
||||
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;
|
||||
}
|
||||
|
1
contrib/credential/osxkeychain/.gitignore
vendored
Normal file
1
contrib/credential/osxkeychain/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
git-credential-osxkeychain
|
14
contrib/credential/osxkeychain/Makefile
Normal file
14
contrib/credential/osxkeychain/Makefile
Normal 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
|
173
contrib/credential/osxkeychain/git-credential-osxkeychain.c
Normal file
173
contrib/credential/osxkeychain/git-credential-osxkeychain.c
Normal 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;
|
||||
}
|
16
credential.c
16
credential.c
@ -3,6 +3,7 @@
|
||||
#include "string-list.h"
|
||||
#include "run-command.h"
|
||||
#include "url.h"
|
||||
#include "prompt.h"
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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 prompt = STRBUF_INIT;
|
||||
@ -120,11 +122,7 @@ static char *credential_ask_one(const char *what, struct credential *c)
|
||||
else
|
||||
strbuf_addf(&prompt, "%s: ", what);
|
||||
|
||||
/* FIXME: for usernames, we should do something less magical that
|
||||
* 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);
|
||||
r = git_prompt(prompt.buf, flags);
|
||||
|
||||
strbuf_release(&desc);
|
||||
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)
|
||||
{
|
||||
if (!c->username)
|
||||
c->username = credential_ask_one("Username", c);
|
||||
c->username = credential_ask_one("Username", c,
|
||||
PROMPT_ASKPASS|PROMPT_ECHO);
|
||||
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)
|
||||
|
12
imap-send.c
12
imap-send.c
@ -25,6 +25,7 @@
|
||||
#include "cache.h"
|
||||
#include "exec_cmd.h"
|
||||
#include "run-command.h"
|
||||
#include "prompt.h"
|
||||
#ifdef NO_OPENSSL
|
||||
typedef void *SSL;
|
||||
#else
|
||||
@ -1208,13 +1209,10 @@ static struct store *imap_open_store(struct imap_server_conf *srvc)
|
||||
goto bail;
|
||||
}
|
||||
if (!srvc->pass) {
|
||||
char prompt[80];
|
||||
sprintf(prompt, "Password (%s@%s): ", srvc->user, srvc->host);
|
||||
arg = git_getpass(prompt);
|
||||
if (!arg) {
|
||||
perror("getpass");
|
||||
exit(1);
|
||||
}
|
||||
struct strbuf prompt = STRBUF_INIT;
|
||||
strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host);
|
||||
arg = git_getpass(prompt.buf);
|
||||
strbuf_release(&prompt);
|
||||
if (!*arg) {
|
||||
fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host);
|
||||
goto bail;
|
||||
|
63
prompt.c
Normal file
63
prompt.c
Normal 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);
|
||||
}
|
Loading…
Reference in New Issue
Block a user