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: Makefilemaint
						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); | ||||
|  |  | |||
|  | @ -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 | ||||
|  | @ -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; | ||||
| } | ||||
|  |  | |||
|  | @ -0,0 +1 @@ | |||
| git-credential-osxkeychain | ||||
|  | @ -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 | ||||
|  | @ -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; | ||||
|  |  | |||
|  | @ -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
	
	 Junio C Hamano
						Junio C Hamano