From 50d0158fbba5c4cd04184bb757bf43a84c290405 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:40:45 -0500 Subject: imap-send: avoid buffer overflow 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 Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index e1ad1a4..4c1e897 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1209,9 +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); + 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) { perror("getpass"); exit(1); -- cgit v0.10.2-6-g49f6 From 6c597aeba1e0fc369e64b1033515b0e39544cbe1 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:40:49 -0500 Subject: imap-send: don't check return value of git_getpass 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 Signed-off-by: Junio C Hamano diff --git a/imap-send.c b/imap-send.c index 4c1e897..227253e 100644 --- a/imap-send.c +++ b/imap-send.c @@ -1213,10 +1213,6 @@ static struct store *imap_open_store(struct imap_server_conf *srvc) strbuf_addf(&prompt, "Password (%s@%s): ", srvc->user, srvc->host); arg = git_getpass(prompt.buf); strbuf_release(&prompt); - if (!arg) { - perror("getpass"); - exit(1); - } if (!*arg) { fprintf(stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host); goto bail; -- cgit v0.10.2-6-g49f6 From d3c58b83aee2007ca76dc5d1242c09b6f7989c76 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:40:54 -0500 Subject: move git_getpass to its own source file 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 Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 2222633..d20d0de 100644 --- a/Makefile +++ b/Makefile @@ -563,6 +563,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 @@ -669,6 +670,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 diff --git a/cache.h b/cache.h index 2e6ad36..f320c98 100644 --- a/cache.h +++ b/cache.h @@ -1024,7 +1024,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); diff --git a/connect.c b/connect.c index 51990fa..519e527 100644 --- a/connect.c +++ b/connect.c @@ -619,47 +619,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; -} diff --git a/credential.c b/credential.c index a17eafe..fbb7231 100644 --- a/credential.c +++ b/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) { diff --git a/imap-send.c b/imap-send.c index 227253e..43588e8 100644 --- a/imap-send.c +++ b/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 diff --git a/prompt.c b/prompt.c new file mode 100644 index 0000000..42a1c9f --- /dev/null +++ b/prompt.c @@ -0,0 +1,48 @@ +#include "cache.h" +#include "run-command.h" +#include "strbuf.h" +#include "prompt.h" + +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; +} diff --git a/prompt.h b/prompt.h new file mode 100644 index 0000000..0fd7bd9 --- /dev/null +++ b/prompt.h @@ -0,0 +1,6 @@ +#ifndef PROMPT_H +#define PROMPT_H + +char *git_getpass(const char *prompt); + +#endif /* PROMPT_H */ -- cgit v0.10.2-6-g49f6 From 1cb0134f3414be187cc3eb98e9740aeeb07dcb16 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:40:57 -0500 Subject: refactor git_getpass into generic prompt function 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 Signed-off-by: Junio C Hamano diff --git a/prompt.c b/prompt.c index 42a1c9f..2002644 100644 --- a/prompt.c +++ b/prompt.c @@ -3,26 +3,13 @@ #include "strbuf.h" #include "prompt.h" -char *git_getpass(const char *prompt) +static char *do_askpass(const char *cmd, 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[0] = cmd; args[1] = prompt; args[2] = NULL; @@ -35,7 +22,7 @@ char *git_getpass(const char *prompt) 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 @@ char *git_getpass(const char *prompt) 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 = getpass(prompt); + if (!r) + die_errno("could not read '%s'", prompt); + return r; +} + +char *git_getpass(const char *prompt) +{ + return git_prompt(prompt, PROMPT_ASKPASS); +} diff --git a/prompt.h b/prompt.h index 0fd7bd9..9ab85a7 100644 --- a/prompt.h +++ b/prompt.h @@ -1,6 +1,9 @@ #ifndef PROMPT_H #define PROMPT_H +#define PROMPT_ASKPASS (1<<0) + +char *git_prompt(const char *prompt, int flags); char *git_getpass(const char *prompt); #endif /* PROMPT_H */ -- cgit v0.10.2-6-g49f6 From 21aeafceda2382d26bfa73a98ba45a937d65d77a Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:41:01 -0500 Subject: add generic terminal prompt function 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. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index d20d0de..7c370ea 100644 --- a/Makefile +++ b/Makefile @@ -229,6 +229,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 @@ -523,6 +526,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 @@ -610,6 +614,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 @@ -1646,6 +1651,10 @@ ifdef HAVE_PATHS_H BASIC_CFLAGS += -DHAVE_PATHS_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 diff --git a/compat/terminal.c b/compat/terminal.c new file mode 100644 index 0000000..6d16c8f --- /dev/null +++ b/compat/terminal.c @@ -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 diff --git a/compat/terminal.h b/compat/terminal.h new file mode 100644 index 0000000..97db7cd --- /dev/null +++ b/compat/terminal.h @@ -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 */ -- cgit v0.10.2-6-g49f6 From a50902590e703878e888fd8a33ec5a22d5347481 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:41:08 -0500 Subject: prompt: use git_terminal_prompt 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 Signed-off-by: Junio C Hamano diff --git a/prompt.c b/prompt.c index 2002644..72ab9de 100644 --- a/prompt.c +++ b/prompt.c @@ -2,6 +2,7 @@ #include "run-command.h" #include "strbuf.h" #include "prompt.h" +#include "compat/terminal.h" static char *do_askpass(const char *cmd, const char *prompt) { @@ -50,7 +51,7 @@ char *git_prompt(const char *prompt, int flags) return do_askpass(askpass, prompt); } - r = getpass(prompt); + r = git_terminal_prompt(prompt, flags & PROMPT_ECHO); if (!r) die_errno("could not read '%s'", prompt); return r; diff --git a/prompt.h b/prompt.h index 9ab85a7..04f321a 100644 --- a/prompt.h +++ b/prompt.h @@ -2,6 +2,7 @@ #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); -- cgit v0.10.2-6-g49f6 From ce77aa4813c8a3980283c0a1e760f51f3a405154 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:41:23 -0500 Subject: credential: use git_prompt instead of git_getpass 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 Signed-off-by: Junio C Hamano diff --git a/credential.c b/credential.c index fbb7231..62d1c56 100644 --- a/credential.c +++ b/credential.c @@ -109,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; @@ -121,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); @@ -135,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) -- cgit v0.10.2-6-g49f6 From 9b4b894601a484bec4132f0201c55a7a0d29eab3 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:41:27 -0500 Subject: Makefile: linux has /dev/tty Therefore we can turn on our custom prompt function instead of relying on getpass. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index 7c370ea..a09da74 100644 --- a/Makefile +++ b/Makefile @@ -837,6 +837,7 @@ ifeq ($(uname_S),Linux) NO_STRLCPY = YesPlease NO_MKSTEMPS = YesPlease HAVE_PATHS_H = YesPlease + HAVE_DEV_TTY = YesPlease endif ifeq ($(uname_S),GNU/kFreeBSD) NO_STRLCPY = YesPlease -- cgit v0.10.2-6-g49f6 From 3f3a9701aeb1743a3eaedec8791afba67419961c Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:41:30 -0500 Subject: Makefile: OS X has /dev/tty We can use our enhanced getpass(). Tested by me. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/Makefile b/Makefile index a09da74..4449cc8 100644 --- a/Makefile +++ b/Makefile @@ -898,6 +898,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 -- cgit v0.10.2-6-g49f6 From 34961d30dae69b00a8a5aabd568fb87f376ebb87 Mon Sep 17 00:00:00 2001 From: Jeff King Date: Sat, 10 Dec 2011 05:53:14 -0500 Subject: contrib: add credential helper for OS X Keychain With this installed in your $PATH, you can store git-over-http passwords in your keychain by doing: git config credential.helper osxkeychain The code is based in large part on the work of Jay Soffian, who wrote the helper originally for the initial, unpublished version of the credential helper protocol. This version will pass t0303 if you do: GIT_TEST_CREDENTIAL_HELPER=osxkeychain \ GIT_TEST_CREDENTIAL_HELPER_SETUP="export HOME=$HOME" \ ./t0303-credential-external.sh The "HOME" setup is unfortunately necessary. The test scripts set HOME to the trash directory, but this causes the keychain API to complain. Signed-off-by: Jeff King Signed-off-by: Junio C Hamano diff --git a/contrib/credential/osxkeychain/.gitignore b/contrib/credential/osxkeychain/.gitignore new file mode 100644 index 0000000..6c5b702 --- /dev/null +++ b/contrib/credential/osxkeychain/.gitignore @@ -0,0 +1 @@ +git-credential-osxkeychain diff --git a/contrib/credential/osxkeychain/Makefile b/contrib/credential/osxkeychain/Makefile new file mode 100644 index 0000000..75c07f8 --- /dev/null +++ b/contrib/credential/osxkeychain/Makefile @@ -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 diff --git a/contrib/credential/osxkeychain/git-credential-osxkeychain.c b/contrib/credential/osxkeychain/git-credential-osxkeychain.c new file mode 100644 index 0000000..6beed12 --- /dev/null +++ b/contrib/credential/osxkeychain/git-credential-osxkeychain.c @@ -0,0 +1,173 @@ +#include +#include +#include +#include + +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 "; + + 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; +} -- cgit v0.10.2-6-g49f6