Merge branch 'jk/credentials'
* jk/credentials: t: add test harness for external credential helpers credentials: add "store" helper strbuf: add strbuf_add*_urlencode Makefile: unix sockets may not available on some platforms credentials: add "cache" helper docs: end-user documentation for the credential subsystem credential: make relevance of http path configurable credential: add credential.*.username credential: apply helper config http: use credential API to get passwords credential: add function for parsing url components introduce credentials API t5550: fix typo test-lib: add test_config_global variant Conflicts: strbuf.c
This commit is contained in:
		
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@ -30,6 +30,9 @@
 | 
			
		||||
/git-commit-tree
 | 
			
		||||
/git-config
 | 
			
		||||
/git-count-objects
 | 
			
		||||
/git-credential-cache
 | 
			
		||||
/git-credential-cache--daemon
 | 
			
		||||
/git-credential-store
 | 
			
		||||
/git-cvsexportcommit
 | 
			
		||||
/git-cvsimport
 | 
			
		||||
/git-cvsserver
 | 
			
		||||
@ -167,6 +170,7 @@
 | 
			
		||||
/gitweb/static/gitweb.js
 | 
			
		||||
/gitweb/static/gitweb.min.*
 | 
			
		||||
/test-chmtime
 | 
			
		||||
/test-credential
 | 
			
		||||
/test-ctype
 | 
			
		||||
/test-date
 | 
			
		||||
/test-delta
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ MAN5_TXT=gitattributes.txt gitignore.txt gitmodules.txt githooks.txt \
 | 
			
		||||
MAN7_TXT=gitcli.txt gittutorial.txt gittutorial-2.txt \
 | 
			
		||||
	gitcvs-migration.txt gitcore-tutorial.txt gitglossary.txt \
 | 
			
		||||
	gitdiffcore.txt gitnamespaces.txt gitrevisions.txt gitworkflows.txt
 | 
			
		||||
MAN7_TXT += gitcredentials.txt
 | 
			
		||||
 | 
			
		||||
MAN_TXT = $(MAN1_TXT) $(MAN5_TXT) $(MAN7_TXT)
 | 
			
		||||
MAN_XML=$(patsubst %.txt,%.xml,$(MAN_TXT))
 | 
			
		||||
 | 
			
		||||
@ -834,6 +834,29 @@ commit.template::
 | 
			
		||||
	"{tilde}/" is expanded to the value of `$HOME` and "{tilde}user/" to the
 | 
			
		||||
	specified user's home directory.
 | 
			
		||||
 | 
			
		||||
credential.helper::
 | 
			
		||||
	Specify an external helper to be called when a username or
 | 
			
		||||
	password credential is needed; the helper may consult external
 | 
			
		||||
	storage to avoid prompting the user for the credentials. See
 | 
			
		||||
	linkgit:gitcredentials[7] for details.
 | 
			
		||||
 | 
			
		||||
credential.useHttpPath::
 | 
			
		||||
	When acquiring credentials, consider the "path" component of an http
 | 
			
		||||
	or https URL to be important. Defaults to false. See
 | 
			
		||||
	linkgit:gitcredentials[7] for more information.
 | 
			
		||||
 | 
			
		||||
credential.username::
 | 
			
		||||
	If no username is set for a network authentication, use this username
 | 
			
		||||
	by default. See credential.<context>.* below, and
 | 
			
		||||
	linkgit:gitcredentials[7].
 | 
			
		||||
 | 
			
		||||
credential.<url>.*::
 | 
			
		||||
	Any of the credential.* options above can be applied selectively to
 | 
			
		||||
	some credentials. For example "credential.https://example.com.username"
 | 
			
		||||
	would set the default username only for https connections to
 | 
			
		||||
	example.com. See linkgit:gitcredentials[7] for details on how URLs are
 | 
			
		||||
	matched.
 | 
			
		||||
 | 
			
		||||
include::diff-config.txt[]
 | 
			
		||||
 | 
			
		||||
difftool.<tool>.path::
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										26
									
								
								Documentation/git-credential-cache--daemon.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								Documentation/git-credential-cache--daemon.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,26 @@
 | 
			
		||||
git-credential-cache--daemon(1)
 | 
			
		||||
===============================
 | 
			
		||||
 | 
			
		||||
NAME
 | 
			
		||||
----
 | 
			
		||||
git-credential-cache--daemon - temporarily store user credentials in memory
 | 
			
		||||
 | 
			
		||||
SYNOPSIS
 | 
			
		||||
--------
 | 
			
		||||
[verse]
 | 
			
		||||
git credential-cache--daemon <socket>
 | 
			
		||||
 | 
			
		||||
DESCRIPTION
 | 
			
		||||
-----------
 | 
			
		||||
 | 
			
		||||
NOTE: You probably don't want to invoke this command yourself; it is
 | 
			
		||||
started automatically when you use linkgit:git-credential-cache[1].
 | 
			
		||||
 | 
			
		||||
This command listens on the Unix domain socket specified by `<socket>`
 | 
			
		||||
for `git-credential-cache` clients. Clients may store and retrieve
 | 
			
		||||
credentials. Each credential is held for a timeout specified by the
 | 
			
		||||
client; once no credentials are held, the daemon exits.
 | 
			
		||||
 | 
			
		||||
GIT
 | 
			
		||||
---
 | 
			
		||||
Part of the linkgit:git[1] suite
 | 
			
		||||
							
								
								
									
										77
									
								
								Documentation/git-credential-cache.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								Documentation/git-credential-cache.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,77 @@
 | 
			
		||||
git-credential-cache(1)
 | 
			
		||||
=======================
 | 
			
		||||
 | 
			
		||||
NAME
 | 
			
		||||
----
 | 
			
		||||
git-credential-cache - helper to temporarily store passwords in memory
 | 
			
		||||
 | 
			
		||||
SYNOPSIS
 | 
			
		||||
--------
 | 
			
		||||
-----------------------------
 | 
			
		||||
git config credential.helper 'cache [options]'
 | 
			
		||||
-----------------------------
 | 
			
		||||
 | 
			
		||||
DESCRIPTION
 | 
			
		||||
-----------
 | 
			
		||||
 | 
			
		||||
This command caches credentials in memory for use by future git
 | 
			
		||||
programs. The stored credentials never touch the disk, and are forgotten
 | 
			
		||||
after a configurable timeout.  The cache is accessible over a Unix
 | 
			
		||||
domain socket, restricted to the current user by filesystem permissions.
 | 
			
		||||
 | 
			
		||||
You probably don't want to invoke this command directly; it is meant to
 | 
			
		||||
be used as a credential helper by other parts of git. See
 | 
			
		||||
linkgit:gitcredentials[7] or `EXAMPLES` below.
 | 
			
		||||
 | 
			
		||||
OPTIONS
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
--timeout <seconds>::
 | 
			
		||||
 | 
			
		||||
	Number of seconds to cache credentials (default: 900).
 | 
			
		||||
 | 
			
		||||
--socket <path>::
 | 
			
		||||
 | 
			
		||||
	Use `<path>` to contact a running cache daemon (or start a new
 | 
			
		||||
	cache daemon if one is not started). Defaults to
 | 
			
		||||
	`~/.git-credential-cache/socket`. If your home directory is on a
 | 
			
		||||
	network-mounted filesystem, you may need to change this to a
 | 
			
		||||
	local filesystem.
 | 
			
		||||
 | 
			
		||||
CONTROLLING THE DAEMON
 | 
			
		||||
----------------------
 | 
			
		||||
 | 
			
		||||
If you would like the daemon to exit early, forgetting all cached
 | 
			
		||||
credentials before their timeout, you can issue an `exit` action:
 | 
			
		||||
 | 
			
		||||
--------------------------------------
 | 
			
		||||
git credential-cache exit
 | 
			
		||||
--------------------------------------
 | 
			
		||||
 | 
			
		||||
EXAMPLES
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
The point of this helper is to reduce the number of times you must type
 | 
			
		||||
your username or password. For example:
 | 
			
		||||
 | 
			
		||||
------------------------------------
 | 
			
		||||
$ git config credential.helper cache
 | 
			
		||||
$ git push http://example.com/repo.git
 | 
			
		||||
Username: <type your username>
 | 
			
		||||
Password: <type your password>
 | 
			
		||||
 | 
			
		||||
[work for 5 more minutes]
 | 
			
		||||
$ git push http://example.com/repo.git
 | 
			
		||||
[your credentials are used automatically]
 | 
			
		||||
------------------------------------
 | 
			
		||||
 | 
			
		||||
You can provide options via the credential.helper configuration
 | 
			
		||||
variable (this example drops the cache time to 5 minutes):
 | 
			
		||||
 | 
			
		||||
-------------------------------------------------------
 | 
			
		||||
$ git config credential.helper 'cache --timeout=300'
 | 
			
		||||
-------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
GIT
 | 
			
		||||
---
 | 
			
		||||
Part of the linkgit:git[1] suite
 | 
			
		||||
							
								
								
									
										75
									
								
								Documentation/git-credential-store.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								Documentation/git-credential-store.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,75 @@
 | 
			
		||||
git-credential-store(1)
 | 
			
		||||
=======================
 | 
			
		||||
 | 
			
		||||
NAME
 | 
			
		||||
----
 | 
			
		||||
git-credential-store - helper to store credentials on disk
 | 
			
		||||
 | 
			
		||||
SYNOPSIS
 | 
			
		||||
--------
 | 
			
		||||
-------------------
 | 
			
		||||
git config credential.helper 'store [options]'
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
DESCRIPTION
 | 
			
		||||
-----------
 | 
			
		||||
 | 
			
		||||
NOTE: Using this helper will store your passwords unencrypted on disk,
 | 
			
		||||
protected only by filesystem permissions. If this is not an acceptable
 | 
			
		||||
security tradeoff, try linkgit:git-credential-cache[1], or find a helper
 | 
			
		||||
that integrates with secure storage provided by your operating system.
 | 
			
		||||
 | 
			
		||||
This command stores credentials indefinitely on disk for use by future
 | 
			
		||||
git programs.
 | 
			
		||||
 | 
			
		||||
You probably don't want to invoke this command directly; it is meant to
 | 
			
		||||
be used as a credential helper by other parts of git. See
 | 
			
		||||
linkgit:gitcredentials[7] or `EXAMPLES` below.
 | 
			
		||||
 | 
			
		||||
OPTIONS
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
--store=<path>::
 | 
			
		||||
 | 
			
		||||
	Use `<path>` to store credentials. The file will have its
 | 
			
		||||
	filesystem permissions set to prevent other users on the system
 | 
			
		||||
	from reading it, but will not be encrypted or otherwise
 | 
			
		||||
	protected. Defaults to `~/.git-credentials`.
 | 
			
		||||
 | 
			
		||||
EXAMPLES
 | 
			
		||||
--------
 | 
			
		||||
 | 
			
		||||
The point of this helper is to reduce the number of times you must type
 | 
			
		||||
your username or password. For example:
 | 
			
		||||
 | 
			
		||||
------------------------------------------
 | 
			
		||||
$ git config credential.helper store
 | 
			
		||||
$ git push http://example.com/repo.git
 | 
			
		||||
Username: <type your username>
 | 
			
		||||
Password: <type your password>
 | 
			
		||||
 | 
			
		||||
[several days later]
 | 
			
		||||
$ git push http://example.com/repo.git
 | 
			
		||||
[your credentials are used automatically]
 | 
			
		||||
------------------------------------------
 | 
			
		||||
 | 
			
		||||
STORAGE FORMAT
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
The `.git-credentials` file is stored in plaintext. Each credential is
 | 
			
		||||
stored on its own line as a URL like:
 | 
			
		||||
 | 
			
		||||
------------------------------
 | 
			
		||||
https://user:pass@example.com
 | 
			
		||||
------------------------------
 | 
			
		||||
 | 
			
		||||
When git needs authentication for a particular URL context,
 | 
			
		||||
credential-store will consider that context a pattern to match against
 | 
			
		||||
each entry in the credentials file.  If the protocol, hostname, and
 | 
			
		||||
username (if we already have one) match, then the password is returned
 | 
			
		||||
to git. See the discussion of configuration in linkgit:gitcredentials[7]
 | 
			
		||||
for more information.
 | 
			
		||||
 | 
			
		||||
GIT
 | 
			
		||||
---
 | 
			
		||||
Part of the linkgit:git[1] suite
 | 
			
		||||
							
								
								
									
										183
									
								
								Documentation/gitcredentials.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										183
									
								
								Documentation/gitcredentials.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,183 @@
 | 
			
		||||
gitcredentials(7)
 | 
			
		||||
=================
 | 
			
		||||
 | 
			
		||||
NAME
 | 
			
		||||
----
 | 
			
		||||
gitcredentials - providing usernames and passwords to git
 | 
			
		||||
 | 
			
		||||
SYNOPSIS
 | 
			
		||||
--------
 | 
			
		||||
------------------
 | 
			
		||||
git config credential.https://example.com.username myusername
 | 
			
		||||
git config credential.helper "$helper $options"
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
DESCRIPTION
 | 
			
		||||
-----------
 | 
			
		||||
 | 
			
		||||
Git will sometimes need credentials from the user in order to perform
 | 
			
		||||
operations; for example, it may need to ask for a username and password
 | 
			
		||||
in order to access a remote repository over HTTP. This manual describes
 | 
			
		||||
the mechanisms git uses to request these credentials, as well as some
 | 
			
		||||
features to avoid inputting these credentials repeatedly.
 | 
			
		||||
 | 
			
		||||
REQUESTING CREDENTIALS
 | 
			
		||||
----------------------
 | 
			
		||||
 | 
			
		||||
Without any credential helpers defined, git will try the following
 | 
			
		||||
strategies to ask the user for usernames and passwords:
 | 
			
		||||
 | 
			
		||||
1. If the `GIT_ASKPASS` environment variable is set, the program
 | 
			
		||||
   specified by the variable is invoked. A suitable prompt is provided
 | 
			
		||||
   to the program on the command line, and the user's input is read
 | 
			
		||||
   from its standard output.
 | 
			
		||||
 | 
			
		||||
2. Otherwise, if the `core.askpass` configuration variable is set, its
 | 
			
		||||
   value is used as above.
 | 
			
		||||
 | 
			
		||||
3. Otherwise, if the `SSH_ASKPASS` environment variable is set, its
 | 
			
		||||
   value is used as above.
 | 
			
		||||
 | 
			
		||||
4. Otherwise, the user is prompted on the terminal.
 | 
			
		||||
 | 
			
		||||
AVOIDING REPETITION
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
It can be cumbersome to input the same credentials over and over.  Git
 | 
			
		||||
provides two methods to reduce this annoyance:
 | 
			
		||||
 | 
			
		||||
1. Static configuration of usernames for a given authentication context.
 | 
			
		||||
 | 
			
		||||
2. Credential helpers to cache or store passwords, or to interact with
 | 
			
		||||
   a system password wallet or keychain.
 | 
			
		||||
 | 
			
		||||
The first is simple and appropriate if you do not have secure storage available
 | 
			
		||||
for a password. It is generally configured by adding this to your config:
 | 
			
		||||
 | 
			
		||||
---------------------------------------
 | 
			
		||||
[credential "https://example.com"]
 | 
			
		||||
	username = me
 | 
			
		||||
---------------------------------------
 | 
			
		||||
 | 
			
		||||
Credential helpers, on the other hand, are external programs from which git can
 | 
			
		||||
request both usernames and passwords; they typically interface with secure
 | 
			
		||||
storage provided by the OS or other programs.
 | 
			
		||||
 | 
			
		||||
To use a helper, you must first select one to use. Git currently
 | 
			
		||||
includes the following helpers:
 | 
			
		||||
 | 
			
		||||
cache::
 | 
			
		||||
 | 
			
		||||
	Cache credentials in memory for a short period of time. See
 | 
			
		||||
	linkgit:git-credential-cache[1] for details.
 | 
			
		||||
 | 
			
		||||
store::
 | 
			
		||||
 | 
			
		||||
	Store credentials indefinitely on disk. See
 | 
			
		||||
	linkgit:git-credential-store[1] for details.
 | 
			
		||||
 | 
			
		||||
You may also have third-party helpers installed; search for
 | 
			
		||||
`credential-*` in the output of `git help -a`, and consult the
 | 
			
		||||
documentation of individual helpers.  Once you have selected a helper,
 | 
			
		||||
you can tell git to use it by putting its name into the
 | 
			
		||||
credential.helper variable.
 | 
			
		||||
 | 
			
		||||
1. Find a helper.
 | 
			
		||||
+
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
$ git help -a | grep credential-
 | 
			
		||||
credential-foo
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
 | 
			
		||||
2. Read its description.
 | 
			
		||||
+
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
$ git help credential-foo
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
 | 
			
		||||
3. Tell git to use it.
 | 
			
		||||
+
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
$ git config --global credential.helper foo
 | 
			
		||||
-------------------------------------------
 | 
			
		||||
 | 
			
		||||
If there are multiple instances of the `credential.helper` configuration
 | 
			
		||||
variable, each helper will be tried in turn, and may provide a username,
 | 
			
		||||
password, or nothing. Once git has acquired both a username and a
 | 
			
		||||
password, no more helpers will be tried.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CREDENTIAL CONTEXTS
 | 
			
		||||
-------------------
 | 
			
		||||
 | 
			
		||||
Git considers each credential to have a context defined by a URL. This context
 | 
			
		||||
is used to look up context-specific configuration, and is passed to any
 | 
			
		||||
helpers, which may use it as an index into secure storage.
 | 
			
		||||
 | 
			
		||||
For instance, imagine we are accessing `https://example.com/foo.git`. When git
 | 
			
		||||
looks into a config file to see if a section matches this context, it will
 | 
			
		||||
consider the two a match if the context is a more-specific subset of the
 | 
			
		||||
pattern in the config file. For example, if you have this in your config file:
 | 
			
		||||
 | 
			
		||||
--------------------------------------
 | 
			
		||||
[credential "https://example.com"]
 | 
			
		||||
	username = foo
 | 
			
		||||
--------------------------------------
 | 
			
		||||
 | 
			
		||||
then we will match: both protocols are the same, both hosts are the same, and
 | 
			
		||||
the "pattern" URL does not care about the path component at all. However, this
 | 
			
		||||
context would not match:
 | 
			
		||||
 | 
			
		||||
--------------------------------------
 | 
			
		||||
[credential "https://kernel.org"]
 | 
			
		||||
	username = foo
 | 
			
		||||
--------------------------------------
 | 
			
		||||
 | 
			
		||||
because the hostnames differ. Nor would it match `foo.example.com`; git
 | 
			
		||||
compares hostnames exactly, without considering whether two hosts are part of
 | 
			
		||||
the same domain. Likewise, a config entry for `http://example.com` would not
 | 
			
		||||
match: git compares the protocols exactly.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CONFIGURATION OPTIONS
 | 
			
		||||
---------------------
 | 
			
		||||
 | 
			
		||||
Options for a credential context can be configured either in
 | 
			
		||||
`credential.\*` (which applies to all credentials), or
 | 
			
		||||
`credential.<url>.\*`, where <url> matches the context as described
 | 
			
		||||
above.
 | 
			
		||||
 | 
			
		||||
The following options are available in either location:
 | 
			
		||||
 | 
			
		||||
helper::
 | 
			
		||||
 | 
			
		||||
	The name of an external credential helper, and any associated options.
 | 
			
		||||
	If the helper name is not an absolute path, then the string `git
 | 
			
		||||
	credential-` is prepended. The resulting string is executed by the
 | 
			
		||||
	shell (so, for example, setting this to `foo --option=bar` will execute
 | 
			
		||||
	`git credential-foo --option=bar` via the shell. See the manual of
 | 
			
		||||
	specific helpers for examples of their use.
 | 
			
		||||
 | 
			
		||||
username::
 | 
			
		||||
 | 
			
		||||
	A default username, if one is not provided in the URL.
 | 
			
		||||
 | 
			
		||||
useHttpPath::
 | 
			
		||||
 | 
			
		||||
	By default, git does not consider the "path" component of an http URL
 | 
			
		||||
	to be worth matching via external helpers. This means that a credential
 | 
			
		||||
	stored for `https://example.com/foo.git` will also be used for
 | 
			
		||||
	`https://example.com/bar.git`. If you do want to distinguish these
 | 
			
		||||
	cases, set this option to `true`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
CUSTOM HELPERS
 | 
			
		||||
--------------
 | 
			
		||||
 | 
			
		||||
You can write your own custom helpers to interface with any system in
 | 
			
		||||
which you keep credentials. See the documentation for git's
 | 
			
		||||
link:technical/api-credentials.html[credentials API] for details.
 | 
			
		||||
 | 
			
		||||
GIT
 | 
			
		||||
---
 | 
			
		||||
Part of the linkgit:git[1] suite
 | 
			
		||||
							
								
								
									
										245
									
								
								Documentation/technical/api-credentials.txt
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										245
									
								
								Documentation/technical/api-credentials.txt
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,245 @@
 | 
			
		||||
credentials API
 | 
			
		||||
===============
 | 
			
		||||
 | 
			
		||||
The credentials API provides an abstracted way of gathering username and
 | 
			
		||||
password credentials from the user (even though credentials in the wider
 | 
			
		||||
world can take many forms, in this document the word "credential" always
 | 
			
		||||
refers to a username and password pair).
 | 
			
		||||
 | 
			
		||||
Data Structures
 | 
			
		||||
---------------
 | 
			
		||||
 | 
			
		||||
`struct credential`::
 | 
			
		||||
 | 
			
		||||
	This struct represents a single username/password combination
 | 
			
		||||
	along with any associated context. All string fields should be
 | 
			
		||||
	heap-allocated (or NULL if they are not known or not applicable).
 | 
			
		||||
	The meaning of the individual context fields is the same as
 | 
			
		||||
	their counterparts in the helper protocol; see the section below
 | 
			
		||||
	for a description of each field.
 | 
			
		||||
+
 | 
			
		||||
The `helpers` member of the struct is a `string_list` of helpers.  Each
 | 
			
		||||
string specifies an external helper which will be run, in order, to
 | 
			
		||||
either acquire or store credentials. See the section on credential
 | 
			
		||||
helpers below.
 | 
			
		||||
+
 | 
			
		||||
This struct should always be initialized with `CREDENTIAL_INIT` or
 | 
			
		||||
`credential_init`.
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Functions
 | 
			
		||||
---------
 | 
			
		||||
 | 
			
		||||
`credential_init`::
 | 
			
		||||
 | 
			
		||||
	Initialize a credential structure, setting all fields to empty.
 | 
			
		||||
 | 
			
		||||
`credential_clear`::
 | 
			
		||||
 | 
			
		||||
	Free any resources associated with the credential structure,
 | 
			
		||||
	returning it to a pristine initialized state.
 | 
			
		||||
 | 
			
		||||
`credential_fill`::
 | 
			
		||||
 | 
			
		||||
	Instruct the credential subsystem to fill the username and
 | 
			
		||||
	password fields of the passed credential struct by first
 | 
			
		||||
	consulting helpers, then asking the user. After this function
 | 
			
		||||
	returns, the username and password fields of the credential are
 | 
			
		||||
	guaranteed to be non-NULL. If an error occurs, the function will
 | 
			
		||||
	die().
 | 
			
		||||
 | 
			
		||||
`credential_reject`::
 | 
			
		||||
 | 
			
		||||
	Inform the credential subsystem that the provided credentials
 | 
			
		||||
	have been rejected. This will cause the credential subsystem to
 | 
			
		||||
	notify any helpers of the rejection (which allows them, for
 | 
			
		||||
	example, to purge the invalid credentials from storage).  It
 | 
			
		||||
	will also free() the username and password fields of the
 | 
			
		||||
	credential and set them to NULL (readying the credential for
 | 
			
		||||
	another call to `credential_fill`). Any errors from helpers are
 | 
			
		||||
	ignored.
 | 
			
		||||
 | 
			
		||||
`credential_approve`::
 | 
			
		||||
 | 
			
		||||
	Inform the credential subsystem that the provided credentials
 | 
			
		||||
	were successfully used for authentication.  This will cause the
 | 
			
		||||
	credential subsystem to notify any helpers of the approval, so
 | 
			
		||||
	that they may store the result to be used again.  Any errors
 | 
			
		||||
	from helpers are ignored.
 | 
			
		||||
 | 
			
		||||
`credential_from_url`::
 | 
			
		||||
 | 
			
		||||
	Parse a URL into broken-down credential fields.
 | 
			
		||||
 | 
			
		||||
Example
 | 
			
		||||
-------
 | 
			
		||||
 | 
			
		||||
The example below shows how the functions of the credential API could be
 | 
			
		||||
used to login to a fictitious "foo" service on a remote host:
 | 
			
		||||
 | 
			
		||||
-----------------------------------------------------------------------
 | 
			
		||||
int foo_login(struct foo_connection *f)
 | 
			
		||||
{
 | 
			
		||||
	int status;
 | 
			
		||||
	/*
 | 
			
		||||
	 * Create a credential with some context; we don't yet know the
 | 
			
		||||
	 * username or password.
 | 
			
		||||
	 */
 | 
			
		||||
 | 
			
		||||
	struct credential c = CREDENTIAL_INIT;
 | 
			
		||||
	c.protocol = xstrdup("foo");
 | 
			
		||||
	c.host = xstrdup(f->hostname);
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Fill in the username and password fields by contacting
 | 
			
		||||
	 * helpers and/or asking the user. The function will die if it
 | 
			
		||||
	 * fails.
 | 
			
		||||
	 */
 | 
			
		||||
	credential_fill(&c);
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Otherwise, we have a username and password. Try to use it.
 | 
			
		||||
	 */
 | 
			
		||||
	status = send_foo_login(f, c.username, c.password);
 | 
			
		||||
	switch (status) {
 | 
			
		||||
	case FOO_OK:
 | 
			
		||||
		/* It worked. Store the credential for later use. */
 | 
			
		||||
		credential_accept(&c);
 | 
			
		||||
		break;
 | 
			
		||||
	case FOO_BAD_LOGIN:
 | 
			
		||||
		/* Erase the credential from storage so we don't try it
 | 
			
		||||
		 * again. */
 | 
			
		||||
		credential_reject(&c);
 | 
			
		||||
		break;
 | 
			
		||||
	default:
 | 
			
		||||
		/*
 | 
			
		||||
		 * Some other error occured. We don't know if the
 | 
			
		||||
		 * credential is good or bad, so report nothing to the
 | 
			
		||||
		 * credential subsystem.
 | 
			
		||||
		 */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/* Free any associated resources. */
 | 
			
		||||
	credential_clear(&c);
 | 
			
		||||
 | 
			
		||||
	return status;
 | 
			
		||||
}
 | 
			
		||||
-----------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
Credential Helpers
 | 
			
		||||
------------------
 | 
			
		||||
 | 
			
		||||
Credential helpers are programs executed by git to fetch or save
 | 
			
		||||
credentials from and to long-term storage (where "long-term" is simply
 | 
			
		||||
longer than a single git process; e.g., credentials may be stored
 | 
			
		||||
in-memory for a few minutes, or indefinitely on disk).
 | 
			
		||||
 | 
			
		||||
Each helper is specified by a single string. The string is transformed
 | 
			
		||||
by git into a command to be executed using these rules:
 | 
			
		||||
 | 
			
		||||
  1. If the helper string begins with "!", it is considered a shell
 | 
			
		||||
     snippet, and everything after the "!" becomes the command.
 | 
			
		||||
 | 
			
		||||
  2. Otherwise, if the helper string begins with an absolute path, the
 | 
			
		||||
     verbatim helper string becomes the command.
 | 
			
		||||
 | 
			
		||||
  3. Otherwise, the string "git credential-" is prepended to the helper
 | 
			
		||||
     string, and the result becomes the command.
 | 
			
		||||
 | 
			
		||||
The resulting command then has an "operation" argument appended to it
 | 
			
		||||
(see below for details), and the result is executed by the shell.
 | 
			
		||||
 | 
			
		||||
Here are some example specifications:
 | 
			
		||||
 | 
			
		||||
----------------------------------------------------
 | 
			
		||||
# run "git credential-foo"
 | 
			
		||||
foo
 | 
			
		||||
 | 
			
		||||
# same as above, but pass an argument to the helper
 | 
			
		||||
foo --bar=baz
 | 
			
		||||
 | 
			
		||||
# the arguments are parsed by the shell, so use shell
 | 
			
		||||
# quoting if necessary
 | 
			
		||||
foo --bar="whitespace arg"
 | 
			
		||||
 | 
			
		||||
# you can also use an absolute path, which will not use the git wrapper
 | 
			
		||||
/path/to/my/helper --with-arguments
 | 
			
		||||
 | 
			
		||||
# or you can specify your own shell snippet
 | 
			
		||||
!f() { echo "password=`cat $HOME/.secret`"; }; f
 | 
			
		||||
----------------------------------------------------
 | 
			
		||||
 | 
			
		||||
Generally speaking, rule (3) above is the simplest for users to specify.
 | 
			
		||||
Authors of credential helpers should make an effort to assist their
 | 
			
		||||
users by naming their program "git-credential-$NAME", and putting it in
 | 
			
		||||
the $PATH or $GIT_EXEC_PATH during installation, which will allow a user
 | 
			
		||||
to enable it with `git config credential.helper $NAME`.
 | 
			
		||||
 | 
			
		||||
When a helper is executed, it will have one "operation" argument
 | 
			
		||||
appended to its command line, which is one of:
 | 
			
		||||
 | 
			
		||||
`get`::
 | 
			
		||||
 | 
			
		||||
	Return a matching credential, if any exists.
 | 
			
		||||
 | 
			
		||||
`store`::
 | 
			
		||||
 | 
			
		||||
	Store the credential, if applicable to the helper.
 | 
			
		||||
 | 
			
		||||
`erase`::
 | 
			
		||||
 | 
			
		||||
	Remove a matching credential, if any, from the helper's storage.
 | 
			
		||||
 | 
			
		||||
The details of the credential will be provided on the helper's stdin
 | 
			
		||||
stream. The credential is split into a set of named attributes.
 | 
			
		||||
Attributes are provided to the helper, one per line. Each attribute is
 | 
			
		||||
specified by a key-value pair, separated by an `=` (equals) sign,
 | 
			
		||||
followed by a newline. The key may contain any bytes except `=`,
 | 
			
		||||
newline, or NUL. The value may contain any bytes except newline or NUL.
 | 
			
		||||
In both cases, all bytes are treated as-is (i.e., there is no quoting,
 | 
			
		||||
and one cannot transmit a value with newline or NUL in it). The list of
 | 
			
		||||
attributes is terminated by a blank line or end-of-file.
 | 
			
		||||
 | 
			
		||||
Git will send the following attributes (but may not send all of
 | 
			
		||||
them for a given credential; for example, a `host` attribute makes no
 | 
			
		||||
sense when dealing with a non-network protocol):
 | 
			
		||||
 | 
			
		||||
`protocol`::
 | 
			
		||||
 | 
			
		||||
	The protocol over which the credential will be used (e.g.,
 | 
			
		||||
	`https`).
 | 
			
		||||
 | 
			
		||||
`host`::
 | 
			
		||||
 | 
			
		||||
	The remote hostname for a network credential.
 | 
			
		||||
 | 
			
		||||
`path`::
 | 
			
		||||
 | 
			
		||||
	The path with which the credential will be used. E.g., for
 | 
			
		||||
	accessing a remote https repository, this will be the
 | 
			
		||||
	repository's path on the server.
 | 
			
		||||
 | 
			
		||||
`username`::
 | 
			
		||||
 | 
			
		||||
	The credential's username, if we already have one (e.g., from a
 | 
			
		||||
	URL, from the user, or from a previously run helper).
 | 
			
		||||
 | 
			
		||||
`password`::
 | 
			
		||||
 | 
			
		||||
	The credential's password, if we are asking it to be stored.
 | 
			
		||||
 | 
			
		||||
For a `get` operation, the helper should produce a list of attributes
 | 
			
		||||
on stdout in the same format. A helper is free to produce a subset, or
 | 
			
		||||
even no values at all if it has nothing useful to provide. Any provided
 | 
			
		||||
attributes will overwrite those already known about by git.
 | 
			
		||||
 | 
			
		||||
For a `store` or `erase` operation, the helper's output is ignored.
 | 
			
		||||
If it fails to perform the requested operation, it may complain to
 | 
			
		||||
stderr to inform the user. If it does not support the requested
 | 
			
		||||
operation (e.g., a read-only store), it should silently ignore the
 | 
			
		||||
request.
 | 
			
		||||
 | 
			
		||||
If a helper receives any other operation, it should silently ignore the
 | 
			
		||||
request. This leaves room for future operations to be added (older
 | 
			
		||||
helpers will just ignore the new requests).
 | 
			
		||||
							
								
								
									
										15
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								Makefile
									
									
									
									
									
								
							@ -143,6 +143,8 @@ all::
 | 
			
		||||
#
 | 
			
		||||
# Define NO_IPV6 if you lack IPv6 support and getaddrinfo().
 | 
			
		||||
#
 | 
			
		||||
# Define NO_UNIX_SOCKETS if your system does not offer unix sockets.
 | 
			
		||||
#
 | 
			
		||||
# Define NO_SOCKADDR_STORAGE if your platform does not have struct
 | 
			
		||||
# sockaddr_storage.
 | 
			
		||||
#
 | 
			
		||||
@ -427,10 +429,12 @@ PROGRAM_OBJS += show-index.o
 | 
			
		||||
PROGRAM_OBJS += upload-pack.o
 | 
			
		||||
PROGRAM_OBJS += http-backend.o
 | 
			
		||||
PROGRAM_OBJS += sh-i18n--envsubst.o
 | 
			
		||||
PROGRAM_OBJS += credential-store.o
 | 
			
		||||
 | 
			
		||||
PROGRAMS += $(patsubst %.o,git-%$X,$(PROGRAM_OBJS))
 | 
			
		||||
 | 
			
		||||
TEST_PROGRAMS_NEED_X += test-chmtime
 | 
			
		||||
TEST_PROGRAMS_NEED_X += test-credential
 | 
			
		||||
TEST_PROGRAMS_NEED_X += test-ctype
 | 
			
		||||
TEST_PROGRAMS_NEED_X += test-date
 | 
			
		||||
TEST_PROGRAMS_NEED_X += test-delta
 | 
			
		||||
@ -526,6 +530,7 @@ LIB_H += compat/win32/poll.h
 | 
			
		||||
LIB_H += compat/win32/dirent.h
 | 
			
		||||
LIB_H += connected.h
 | 
			
		||||
LIB_H += convert.h
 | 
			
		||||
LIB_H += credential.h
 | 
			
		||||
LIB_H += csum-file.h
 | 
			
		||||
LIB_H += decorate.h
 | 
			
		||||
LIB_H += delta.h
 | 
			
		||||
@ -613,6 +618,7 @@ LIB_OBJS += connect.o
 | 
			
		||||
LIB_OBJS += connected.o
 | 
			
		||||
LIB_OBJS += convert.o
 | 
			
		||||
LIB_OBJS += copy.o
 | 
			
		||||
LIB_OBJS += credential.o
 | 
			
		||||
LIB_OBJS += csum-file.o
 | 
			
		||||
LIB_OBJS += ctype.o
 | 
			
		||||
LIB_OBJS += date.o
 | 
			
		||||
@ -1103,6 +1109,7 @@ ifeq ($(uname_S),Windows)
 | 
			
		||||
	NO_SYS_POLL_H = YesPlease
 | 
			
		||||
	NO_SYMLINK_HEAD = YesPlease
 | 
			
		||||
	NO_IPV6 = YesPlease
 | 
			
		||||
	NO_UNIX_SOCKETS = YesPlease
 | 
			
		||||
	NO_SETENV = YesPlease
 | 
			
		||||
	NO_UNSETENV = YesPlease
 | 
			
		||||
	NO_STRCASESTR = YesPlease
 | 
			
		||||
@ -1196,6 +1203,7 @@ ifneq (,$(findstring MINGW,$(uname_S)))
 | 
			
		||||
	NO_LIBGEN_H = YesPlease
 | 
			
		||||
	NO_SYS_POLL_H = YesPlease
 | 
			
		||||
	NO_SYMLINK_HEAD = YesPlease
 | 
			
		||||
	NO_UNIX_SOCKETS = YesPlease
 | 
			
		||||
	NO_SETENV = YesPlease
 | 
			
		||||
	NO_UNSETENV = YesPlease
 | 
			
		||||
	NO_STRCASESTR = YesPlease
 | 
			
		||||
@ -1573,6 +1581,12 @@ ifdef NO_INET_PTON
 | 
			
		||||
	LIB_OBJS += compat/inet_pton.o
 | 
			
		||||
	BASIC_CFLAGS += -DNO_INET_PTON
 | 
			
		||||
endif
 | 
			
		||||
ifndef NO_UNIX_SOCKETS
 | 
			
		||||
	LIB_OBJS += unix-socket.o
 | 
			
		||||
	LIB_H += unix-socket.h
 | 
			
		||||
	PROGRAM_OBJS += credential-cache.o
 | 
			
		||||
	PROGRAM_OBJS += credential-cache--daemon.o
 | 
			
		||||
endif
 | 
			
		||||
 | 
			
		||||
ifdef NO_ICONV
 | 
			
		||||
	BASIC_CFLAGS += -DNO_ICONV
 | 
			
		||||
@ -2208,6 +2222,7 @@ ifdef GIT_TEST_CMP_USE_COPIED_CONTEXT
 | 
			
		||||
	@echo GIT_TEST_CMP_USE_COPIED_CONTEXT=YesPlease >>$@
 | 
			
		||||
endif
 | 
			
		||||
	@echo GETTEXT_POISON=\''$(subst ','\'',$(subst ','\'',$(GETTEXT_POISON)))'\' >>$@
 | 
			
		||||
	@echo NO_UNIX_SOCKETS=\''$(subst ','\'',$(subst ','\'',$(NO_UNIX_SOCKETS)))'\' >>$@
 | 
			
		||||
 | 
			
		||||
### Detect Tck/Tk interpreter path changes
 | 
			
		||||
ifndef NO_TCLTK
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										269
									
								
								credential-cache--daemon.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										269
									
								
								credential-cache--daemon.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,269 @@
 | 
			
		||||
#include "cache.h"
 | 
			
		||||
#include "credential.h"
 | 
			
		||||
#include "unix-socket.h"
 | 
			
		||||
#include "sigchain.h"
 | 
			
		||||
 | 
			
		||||
static const char *socket_path;
 | 
			
		||||
 | 
			
		||||
static void cleanup_socket(void)
 | 
			
		||||
{
 | 
			
		||||
	if (socket_path)
 | 
			
		||||
		unlink(socket_path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void cleanup_socket_on_signal(int sig)
 | 
			
		||||
{
 | 
			
		||||
	cleanup_socket();
 | 
			
		||||
	sigchain_pop(sig);
 | 
			
		||||
	raise(sig);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct credential_cache_entry {
 | 
			
		||||
	struct credential item;
 | 
			
		||||
	unsigned long expiration;
 | 
			
		||||
};
 | 
			
		||||
static struct credential_cache_entry *entries;
 | 
			
		||||
static int entries_nr;
 | 
			
		||||
static int entries_alloc;
 | 
			
		||||
 | 
			
		||||
static void cache_credential(struct credential *c, int timeout)
 | 
			
		||||
{
 | 
			
		||||
	struct credential_cache_entry *e;
 | 
			
		||||
 | 
			
		||||
	ALLOC_GROW(entries, entries_nr + 1, entries_alloc);
 | 
			
		||||
	e = &entries[entries_nr++];
 | 
			
		||||
 | 
			
		||||
	/* take ownership of pointers */
 | 
			
		||||
	memcpy(&e->item, c, sizeof(*c));
 | 
			
		||||
	memset(c, 0, sizeof(*c));
 | 
			
		||||
	e->expiration = time(NULL) + timeout;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static struct credential_cache_entry *lookup_credential(const struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
	for (i = 0; i < entries_nr; i++) {
 | 
			
		||||
		struct credential *e = &entries[i].item;
 | 
			
		||||
		if (credential_match(c, e))
 | 
			
		||||
			return &entries[i];
 | 
			
		||||
	}
 | 
			
		||||
	return NULL;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void remove_credential(const struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	struct credential_cache_entry *e;
 | 
			
		||||
 | 
			
		||||
	e = lookup_credential(c);
 | 
			
		||||
	if (e)
 | 
			
		||||
		e->expiration = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int check_expirations(void)
 | 
			
		||||
{
 | 
			
		||||
	static unsigned long wait_for_entry_until;
 | 
			
		||||
	int i = 0;
 | 
			
		||||
	unsigned long now = time(NULL);
 | 
			
		||||
	unsigned long next = (unsigned long)-1;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Initially give the client 30 seconds to actually contact us
 | 
			
		||||
	 * and store a credential before we decide there's no point in
 | 
			
		||||
	 * keeping the daemon around.
 | 
			
		||||
	 */
 | 
			
		||||
	if (!wait_for_entry_until)
 | 
			
		||||
		wait_for_entry_until = now + 30;
 | 
			
		||||
 | 
			
		||||
	while (i < entries_nr) {
 | 
			
		||||
		if (entries[i].expiration <= now) {
 | 
			
		||||
			entries_nr--;
 | 
			
		||||
			credential_clear(&entries[i].item);
 | 
			
		||||
			if (i != entries_nr)
 | 
			
		||||
				memcpy(&entries[i], &entries[entries_nr], sizeof(*entries));
 | 
			
		||||
			/*
 | 
			
		||||
			 * Stick around 30 seconds in case a new credential
 | 
			
		||||
			 * shows up (e.g., because we just removed a failed
 | 
			
		||||
			 * one, and we will soon get the correct one).
 | 
			
		||||
			 */
 | 
			
		||||
			wait_for_entry_until = now + 30;
 | 
			
		||||
		}
 | 
			
		||||
		else {
 | 
			
		||||
			if (entries[i].expiration < next)
 | 
			
		||||
				next = entries[i].expiration;
 | 
			
		||||
			i++;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!entries_nr) {
 | 
			
		||||
		if (wait_for_entry_until <= now)
 | 
			
		||||
			return 0;
 | 
			
		||||
		next = wait_for_entry_until;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return next - now;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int read_request(FILE *fh, struct credential *c,
 | 
			
		||||
			struct strbuf *action, int *timeout) {
 | 
			
		||||
	static struct strbuf item = STRBUF_INIT;
 | 
			
		||||
	const char *p;
 | 
			
		||||
 | 
			
		||||
	strbuf_getline(&item, fh, '\n');
 | 
			
		||||
	p = skip_prefix(item.buf, "action=");
 | 
			
		||||
	if (!p)
 | 
			
		||||
		return error("client sent bogus action line: %s", item.buf);
 | 
			
		||||
	strbuf_addstr(action, p);
 | 
			
		||||
 | 
			
		||||
	strbuf_getline(&item, fh, '\n');
 | 
			
		||||
	p = skip_prefix(item.buf, "timeout=");
 | 
			
		||||
	if (!p)
 | 
			
		||||
		return error("client sent bogus timeout line: %s", item.buf);
 | 
			
		||||
	*timeout = atoi(p);
 | 
			
		||||
 | 
			
		||||
	if (credential_read(c, fh) < 0)
 | 
			
		||||
		return -1;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void serve_one_client(FILE *in, FILE *out)
 | 
			
		||||
{
 | 
			
		||||
	struct credential c = CREDENTIAL_INIT;
 | 
			
		||||
	struct strbuf action = STRBUF_INIT;
 | 
			
		||||
	int timeout = -1;
 | 
			
		||||
 | 
			
		||||
	if (read_request(in, &c, &action, &timeout) < 0)
 | 
			
		||||
		/* ignore error */ ;
 | 
			
		||||
	else if (!strcmp(action.buf, "get")) {
 | 
			
		||||
		struct credential_cache_entry *e = lookup_credential(&c);
 | 
			
		||||
		if (e) {
 | 
			
		||||
			fprintf(out, "username=%s\n", e->item.username);
 | 
			
		||||
			fprintf(out, "password=%s\n", e->item.password);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else if (!strcmp(action.buf, "exit"))
 | 
			
		||||
		exit(0);
 | 
			
		||||
	else if (!strcmp(action.buf, "erase"))
 | 
			
		||||
		remove_credential(&c);
 | 
			
		||||
	else if (!strcmp(action.buf, "store")) {
 | 
			
		||||
		if (timeout < 0)
 | 
			
		||||
			warning("cache client didn't specify a timeout");
 | 
			
		||||
		else if (!c.username || !c.password)
 | 
			
		||||
			warning("cache client gave us a partial credential");
 | 
			
		||||
		else {
 | 
			
		||||
			remove_credential(&c);
 | 
			
		||||
			cache_credential(&c, timeout);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	else
 | 
			
		||||
		warning("cache client sent unknown action: %s", action.buf);
 | 
			
		||||
 | 
			
		||||
	credential_clear(&c);
 | 
			
		||||
	strbuf_release(&action);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int serve_cache_loop(int fd)
 | 
			
		||||
{
 | 
			
		||||
	struct pollfd pfd;
 | 
			
		||||
	unsigned long wakeup;
 | 
			
		||||
 | 
			
		||||
	wakeup = check_expirations();
 | 
			
		||||
	if (!wakeup)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	pfd.fd = fd;
 | 
			
		||||
	pfd.events = POLLIN;
 | 
			
		||||
	if (poll(&pfd, 1, 1000 * wakeup) < 0) {
 | 
			
		||||
		if (errno != EINTR)
 | 
			
		||||
			die_errno("poll failed");
 | 
			
		||||
		return 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (pfd.revents & POLLIN) {
 | 
			
		||||
		int client, client2;
 | 
			
		||||
		FILE *in, *out;
 | 
			
		||||
 | 
			
		||||
		client = accept(fd, NULL, NULL);
 | 
			
		||||
		if (client < 0) {
 | 
			
		||||
			warning("accept failed: %s", strerror(errno));
 | 
			
		||||
			return 1;
 | 
			
		||||
		}
 | 
			
		||||
		client2 = dup(client);
 | 
			
		||||
		if (client2 < 0) {
 | 
			
		||||
			warning("dup failed: %s", strerror(errno));
 | 
			
		||||
			close(client);
 | 
			
		||||
			return 1;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		in = xfdopen(client, "r");
 | 
			
		||||
		out = xfdopen(client2, "w");
 | 
			
		||||
		serve_one_client(in, out);
 | 
			
		||||
		fclose(in);
 | 
			
		||||
		fclose(out);
 | 
			
		||||
	}
 | 
			
		||||
	return 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void serve_cache(const char *socket_path)
 | 
			
		||||
{
 | 
			
		||||
	int fd;
 | 
			
		||||
 | 
			
		||||
	fd = unix_stream_listen(socket_path);
 | 
			
		||||
	if (fd < 0)
 | 
			
		||||
		die_errno("unable to bind to '%s'", socket_path);
 | 
			
		||||
 | 
			
		||||
	printf("ok\n");
 | 
			
		||||
	fclose(stdout);
 | 
			
		||||
 | 
			
		||||
	while (serve_cache_loop(fd))
 | 
			
		||||
		; /* nothing */
 | 
			
		||||
 | 
			
		||||
	close(fd);
 | 
			
		||||
	unlink(socket_path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static const char permissions_advice[] =
 | 
			
		||||
"The permissions on your socket directory are too loose; other\n"
 | 
			
		||||
"users may be able to read your cached credentials. Consider running:\n"
 | 
			
		||||
"\n"
 | 
			
		||||
"	chmod 0700 %s";
 | 
			
		||||
static void check_socket_directory(const char *path)
 | 
			
		||||
{
 | 
			
		||||
	struct stat st;
 | 
			
		||||
	char *path_copy = xstrdup(path);
 | 
			
		||||
	char *dir = dirname(path_copy);
 | 
			
		||||
 | 
			
		||||
	if (!stat(dir, &st)) {
 | 
			
		||||
		if (st.st_mode & 077)
 | 
			
		||||
			die(permissions_advice, dir);
 | 
			
		||||
		free(path_copy);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * We must be sure to create the directory with the correct mode,
 | 
			
		||||
	 * not just chmod it after the fact; otherwise, there is a race
 | 
			
		||||
	 * condition in which somebody can chdir to it, sleep, then try to open
 | 
			
		||||
	 * our protected socket.
 | 
			
		||||
	 */
 | 
			
		||||
	if (safe_create_leading_directories_const(dir) < 0)
 | 
			
		||||
		die_errno("unable to create directories for '%s'", dir);
 | 
			
		||||
	if (mkdir(dir, 0700) < 0)
 | 
			
		||||
		die_errno("unable to mkdir '%s'", dir);
 | 
			
		||||
	free(path_copy);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, const char **argv)
 | 
			
		||||
{
 | 
			
		||||
	socket_path = argv[1];
 | 
			
		||||
 | 
			
		||||
	if (!socket_path)
 | 
			
		||||
		die("usage: git-credential-cache--daemon <socket_path>");
 | 
			
		||||
	check_socket_directory(socket_path);
 | 
			
		||||
 | 
			
		||||
	atexit(cleanup_socket);
 | 
			
		||||
	sigchain_push_common(cleanup_socket_on_signal);
 | 
			
		||||
 | 
			
		||||
	serve_cache(socket_path);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										120
									
								
								credential-cache.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								credential-cache.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,120 @@
 | 
			
		||||
#include "cache.h"
 | 
			
		||||
#include "credential.h"
 | 
			
		||||
#include "string-list.h"
 | 
			
		||||
#include "parse-options.h"
 | 
			
		||||
#include "unix-socket.h"
 | 
			
		||||
#include "run-command.h"
 | 
			
		||||
 | 
			
		||||
#define FLAG_SPAWN 0x1
 | 
			
		||||
#define FLAG_RELAY 0x2
 | 
			
		||||
 | 
			
		||||
static int send_request(const char *socket, const struct strbuf *out)
 | 
			
		||||
{
 | 
			
		||||
	int got_data = 0;
 | 
			
		||||
	int fd = unix_stream_connect(socket);
 | 
			
		||||
 | 
			
		||||
	if (fd < 0)
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
	if (write_in_full(fd, out->buf, out->len) < 0)
 | 
			
		||||
		die_errno("unable to write to cache daemon");
 | 
			
		||||
	shutdown(fd, SHUT_WR);
 | 
			
		||||
 | 
			
		||||
	while (1) {
 | 
			
		||||
		char in[1024];
 | 
			
		||||
		int r;
 | 
			
		||||
 | 
			
		||||
		r = read_in_full(fd, in, sizeof(in));
 | 
			
		||||
		if (r == 0)
 | 
			
		||||
			break;
 | 
			
		||||
		if (r < 0)
 | 
			
		||||
			die_errno("read error from cache daemon");
 | 
			
		||||
		write_or_die(1, in, r);
 | 
			
		||||
		got_data = 1;
 | 
			
		||||
	}
 | 
			
		||||
	return got_data;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void spawn_daemon(const char *socket)
 | 
			
		||||
{
 | 
			
		||||
	struct child_process daemon;
 | 
			
		||||
	const char *argv[] = { NULL, NULL, NULL };
 | 
			
		||||
	char buf[128];
 | 
			
		||||
	int r;
 | 
			
		||||
 | 
			
		||||
	memset(&daemon, 0, sizeof(daemon));
 | 
			
		||||
	argv[0] = "git-credential-cache--daemon";
 | 
			
		||||
	argv[1] = socket;
 | 
			
		||||
	daemon.argv = argv;
 | 
			
		||||
	daemon.no_stdin = 1;
 | 
			
		||||
	daemon.out = -1;
 | 
			
		||||
 | 
			
		||||
	if (start_command(&daemon))
 | 
			
		||||
		die_errno("unable to start cache daemon");
 | 
			
		||||
	r = read_in_full(daemon.out, buf, sizeof(buf));
 | 
			
		||||
	if (r < 0)
 | 
			
		||||
		die_errno("unable to read result code from cache daemon");
 | 
			
		||||
	if (r != 3 || memcmp(buf, "ok\n", 3))
 | 
			
		||||
		die("cache daemon did not start: %.*s", r, buf);
 | 
			
		||||
	close(daemon.out);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void do_cache(const char *socket, const char *action, int timeout,
 | 
			
		||||
		     int flags)
 | 
			
		||||
{
 | 
			
		||||
	struct strbuf buf = STRBUF_INIT;
 | 
			
		||||
 | 
			
		||||
	strbuf_addf(&buf, "action=%s\n", action);
 | 
			
		||||
	strbuf_addf(&buf, "timeout=%d\n", timeout);
 | 
			
		||||
	if (flags & FLAG_RELAY) {
 | 
			
		||||
		if (strbuf_read(&buf, 0, 0) < 0)
 | 
			
		||||
			die_errno("unable to relay credential");
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!send_request(socket, &buf))
 | 
			
		||||
		return;
 | 
			
		||||
	if (flags & FLAG_SPAWN) {
 | 
			
		||||
		spawn_daemon(socket);
 | 
			
		||||
		send_request(socket, &buf);
 | 
			
		||||
	}
 | 
			
		||||
	strbuf_release(&buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, const char **argv)
 | 
			
		||||
{
 | 
			
		||||
	char *socket_path = NULL;
 | 
			
		||||
	int timeout = 900;
 | 
			
		||||
	const char *op;
 | 
			
		||||
	const char * const usage[] = {
 | 
			
		||||
		"git credential-cache [options] <action>",
 | 
			
		||||
		NULL
 | 
			
		||||
	};
 | 
			
		||||
	struct option options[] = {
 | 
			
		||||
		OPT_INTEGER(0, "timeout", &timeout,
 | 
			
		||||
			    "number of seconds to cache credentials"),
 | 
			
		||||
		OPT_STRING(0, "socket", &socket_path, "path",
 | 
			
		||||
			   "path of cache-daemon socket"),
 | 
			
		||||
		OPT_END()
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	argc = parse_options(argc, argv, NULL, options, usage, 0);
 | 
			
		||||
	if (!argc)
 | 
			
		||||
		usage_with_options(usage, options);
 | 
			
		||||
	op = argv[0];
 | 
			
		||||
 | 
			
		||||
	if (!socket_path)
 | 
			
		||||
		socket_path = expand_user_path("~/.git-credential-cache/socket");
 | 
			
		||||
	if (!socket_path)
 | 
			
		||||
		die("unable to find a suitable socket path; use --socket");
 | 
			
		||||
 | 
			
		||||
	if (!strcmp(op, "exit"))
 | 
			
		||||
		do_cache(socket_path, op, timeout, 0);
 | 
			
		||||
	else if (!strcmp(op, "get") || !strcmp(op, "erase"))
 | 
			
		||||
		do_cache(socket_path, op, timeout, FLAG_RELAY);
 | 
			
		||||
	else if (!strcmp(op, "store"))
 | 
			
		||||
		do_cache(socket_path, op, timeout, FLAG_RELAY|FLAG_SPAWN);
 | 
			
		||||
	else
 | 
			
		||||
		; /* ignore unknown operation */
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										157
									
								
								credential-store.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								credential-store.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,157 @@
 | 
			
		||||
#include "cache.h"
 | 
			
		||||
#include "credential.h"
 | 
			
		||||
#include "string-list.h"
 | 
			
		||||
#include "parse-options.h"
 | 
			
		||||
 | 
			
		||||
static struct lock_file credential_lock;
 | 
			
		||||
 | 
			
		||||
static void parse_credential_file(const char *fn,
 | 
			
		||||
				  struct credential *c,
 | 
			
		||||
				  void (*match_cb)(struct credential *),
 | 
			
		||||
				  void (*other_cb)(struct strbuf *))
 | 
			
		||||
{
 | 
			
		||||
	FILE *fh;
 | 
			
		||||
	struct strbuf line = STRBUF_INIT;
 | 
			
		||||
	struct credential entry = CREDENTIAL_INIT;
 | 
			
		||||
 | 
			
		||||
	fh = fopen(fn, "r");
 | 
			
		||||
	if (!fh) {
 | 
			
		||||
		if (errno != ENOENT)
 | 
			
		||||
			die_errno("unable to open %s", fn);
 | 
			
		||||
		return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	while (strbuf_getline(&line, fh, '\n') != EOF) {
 | 
			
		||||
		credential_from_url(&entry, line.buf);
 | 
			
		||||
		if (entry.username && entry.password &&
 | 
			
		||||
		    credential_match(c, &entry)) {
 | 
			
		||||
			if (match_cb) {
 | 
			
		||||
				match_cb(&entry);
 | 
			
		||||
				break;
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
		else if (other_cb)
 | 
			
		||||
			other_cb(&line);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	credential_clear(&entry);
 | 
			
		||||
	strbuf_release(&line);
 | 
			
		||||
	fclose(fh);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void print_entry(struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	printf("username=%s\n", c->username);
 | 
			
		||||
	printf("password=%s\n", c->password);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void print_line(struct strbuf *buf)
 | 
			
		||||
{
 | 
			
		||||
	strbuf_addch(buf, '\n');
 | 
			
		||||
	write_or_die(credential_lock.fd, buf->buf, buf->len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void rewrite_credential_file(const char *fn, struct credential *c,
 | 
			
		||||
				    struct strbuf *extra)
 | 
			
		||||
{
 | 
			
		||||
	if (hold_lock_file_for_update(&credential_lock, fn, 0) < 0)
 | 
			
		||||
		die_errno("unable to get credential storage lock");
 | 
			
		||||
	if (extra)
 | 
			
		||||
		print_line(extra);
 | 
			
		||||
	parse_credential_file(fn, c, NULL, print_line);
 | 
			
		||||
	if (commit_lock_file(&credential_lock) < 0)
 | 
			
		||||
		die_errno("unable to commit credential store");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void store_credential(const char *fn, struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	struct strbuf buf = STRBUF_INIT;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Sanity check that what we are storing is actually sensible.
 | 
			
		||||
	 * In particular, we can't make a URL without a protocol field.
 | 
			
		||||
	 * Without either a host or pathname (depending on the scheme),
 | 
			
		||||
	 * we have no primary key. And without a username and password,
 | 
			
		||||
	 * we are not actually storing a credential.
 | 
			
		||||
	 */
 | 
			
		||||
	if (!c->protocol || !(c->host || c->path) ||
 | 
			
		||||
	    !c->username || !c->password)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	strbuf_addf(&buf, "%s://", c->protocol);
 | 
			
		||||
	strbuf_addstr_urlencode(&buf, c->username, 1);
 | 
			
		||||
	strbuf_addch(&buf, ':');
 | 
			
		||||
	strbuf_addstr_urlencode(&buf, c->password, 1);
 | 
			
		||||
	strbuf_addch(&buf, '@');
 | 
			
		||||
	if (c->host)
 | 
			
		||||
		strbuf_addstr_urlencode(&buf, c->host, 1);
 | 
			
		||||
	if (c->path) {
 | 
			
		||||
		strbuf_addch(&buf, '/');
 | 
			
		||||
		strbuf_addstr_urlencode(&buf, c->path, 0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	rewrite_credential_file(fn, c, &buf);
 | 
			
		||||
	strbuf_release(&buf);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void remove_credential(const char *fn, struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	/*
 | 
			
		||||
	 * Sanity check that we actually have something to match
 | 
			
		||||
	 * against. The input we get is a restrictive pattern,
 | 
			
		||||
	 * so technically a blank credential means "erase everything".
 | 
			
		||||
	 * But it is too easy to accidentally send this, since it is equivalent
 | 
			
		||||
	 * to empty input. So explicitly disallow it, and require that the
 | 
			
		||||
	 * pattern have some actual content to match.
 | 
			
		||||
	 */
 | 
			
		||||
	if (c->protocol || c->host || c->path || c->username)
 | 
			
		||||
		rewrite_credential_file(fn, c, NULL);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int lookup_credential(const char *fn, struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	parse_credential_file(fn, c, print_entry, NULL);
 | 
			
		||||
	return c->username && c->password;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int main(int argc, const char **argv)
 | 
			
		||||
{
 | 
			
		||||
	const char * const usage[] = {
 | 
			
		||||
		"git credential-store [options] <action>",
 | 
			
		||||
		NULL
 | 
			
		||||
	};
 | 
			
		||||
	const char *op;
 | 
			
		||||
	struct credential c = CREDENTIAL_INIT;
 | 
			
		||||
	char *file = NULL;
 | 
			
		||||
	struct option options[] = {
 | 
			
		||||
		OPT_STRING(0, "file", &file, "path",
 | 
			
		||||
			   "fetch and store credentials in <path>"),
 | 
			
		||||
		OPT_END()
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	umask(077);
 | 
			
		||||
 | 
			
		||||
	argc = parse_options(argc, argv, NULL, options, usage, 0);
 | 
			
		||||
	if (argc != 1)
 | 
			
		||||
		usage_with_options(usage, options);
 | 
			
		||||
	op = argv[0];
 | 
			
		||||
 | 
			
		||||
	if (!file)
 | 
			
		||||
		file = expand_user_path("~/.git-credentials");
 | 
			
		||||
	if (!file)
 | 
			
		||||
		die("unable to set up default path; use --file");
 | 
			
		||||
 | 
			
		||||
	if (credential_read(&c, stdin) < 0)
 | 
			
		||||
		die("unable to read credential");
 | 
			
		||||
 | 
			
		||||
	if (!strcmp(op, "get"))
 | 
			
		||||
		lookup_credential(file, &c);
 | 
			
		||||
	else if (!strcmp(op, "erase"))
 | 
			
		||||
		remove_credential(file, &c);
 | 
			
		||||
	else if (!strcmp(op, "store"))
 | 
			
		||||
		store_credential(file, &c);
 | 
			
		||||
	else
 | 
			
		||||
		; /* Ignore unknown operation. */
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										365
									
								
								credential.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										365
									
								
								credential.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,365 @@
 | 
			
		||||
#include "cache.h"
 | 
			
		||||
#include "credential.h"
 | 
			
		||||
#include "string-list.h"
 | 
			
		||||
#include "run-command.h"
 | 
			
		||||
#include "url.h"
 | 
			
		||||
 | 
			
		||||
void credential_init(struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	memset(c, 0, sizeof(*c));
 | 
			
		||||
	c->helpers.strdup_strings = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void credential_clear(struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	free(c->protocol);
 | 
			
		||||
	free(c->host);
 | 
			
		||||
	free(c->path);
 | 
			
		||||
	free(c->username);
 | 
			
		||||
	free(c->password);
 | 
			
		||||
	string_list_clear(&c->helpers, 0);
 | 
			
		||||
 | 
			
		||||
	credential_init(c);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int credential_match(const struct credential *want,
 | 
			
		||||
		     const struct credential *have)
 | 
			
		||||
{
 | 
			
		||||
#define CHECK(x) (!want->x || (have->x && !strcmp(want->x, have->x)))
 | 
			
		||||
	return CHECK(protocol) &&
 | 
			
		||||
	       CHECK(host) &&
 | 
			
		||||
	       CHECK(path) &&
 | 
			
		||||
	       CHECK(username);
 | 
			
		||||
#undef CHECK
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int credential_config_callback(const char *var, const char *value,
 | 
			
		||||
				      void *data)
 | 
			
		||||
{
 | 
			
		||||
	struct credential *c = data;
 | 
			
		||||
	const char *key, *dot;
 | 
			
		||||
 | 
			
		||||
	key = skip_prefix(var, "credential.");
 | 
			
		||||
	if (!key)
 | 
			
		||||
		return 0;
 | 
			
		||||
 | 
			
		||||
	if (!value)
 | 
			
		||||
		return config_error_nonbool(var);
 | 
			
		||||
 | 
			
		||||
	dot = strrchr(key, '.');
 | 
			
		||||
	if (dot) {
 | 
			
		||||
		struct credential want = CREDENTIAL_INIT;
 | 
			
		||||
		char *url = xmemdupz(key, dot - key);
 | 
			
		||||
		int matched;
 | 
			
		||||
 | 
			
		||||
		credential_from_url(&want, url);
 | 
			
		||||
		matched = credential_match(&want, c);
 | 
			
		||||
 | 
			
		||||
		credential_clear(&want);
 | 
			
		||||
		free(url);
 | 
			
		||||
 | 
			
		||||
		if (!matched)
 | 
			
		||||
			return 0;
 | 
			
		||||
		key = dot + 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (!strcmp(key, "helper"))
 | 
			
		||||
		string_list_append(&c->helpers, value);
 | 
			
		||||
	else if (!strcmp(key, "username")) {
 | 
			
		||||
		if (!c->username)
 | 
			
		||||
			c->username = xstrdup(value);
 | 
			
		||||
	}
 | 
			
		||||
	else if (!strcmp(key, "usehttppath"))
 | 
			
		||||
		c->use_http_path = git_config_bool(var, value);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int proto_is_http(const char *s)
 | 
			
		||||
{
 | 
			
		||||
	if (!s)
 | 
			
		||||
		return 0;
 | 
			
		||||
	return !strcmp(s, "https") || !strcmp(s, "http");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void credential_apply_config(struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	if (c->configured)
 | 
			
		||||
		return;
 | 
			
		||||
	git_config(credential_config_callback, c);
 | 
			
		||||
	c->configured = 1;
 | 
			
		||||
 | 
			
		||||
	if (!c->use_http_path && proto_is_http(c->protocol)) {
 | 
			
		||||
		free(c->path);
 | 
			
		||||
		c->path = NULL;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void credential_describe(struct credential *c, struct strbuf *out)
 | 
			
		||||
{
 | 
			
		||||
	if (!c->protocol)
 | 
			
		||||
		return;
 | 
			
		||||
	strbuf_addf(out, "%s://", c->protocol);
 | 
			
		||||
	if (c->username && *c->username)
 | 
			
		||||
		strbuf_addf(out, "%s@", c->username);
 | 
			
		||||
	if (c->host)
 | 
			
		||||
		strbuf_addstr(out, c->host);
 | 
			
		||||
	if (c->path)
 | 
			
		||||
		strbuf_addf(out, "/%s", c->path);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static char *credential_ask_one(const char *what, struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	struct strbuf desc = STRBUF_INIT;
 | 
			
		||||
	struct strbuf prompt = STRBUF_INIT;
 | 
			
		||||
	char *r;
 | 
			
		||||
 | 
			
		||||
	credential_describe(c, &desc);
 | 
			
		||||
	if (desc.len)
 | 
			
		||||
		strbuf_addf(&prompt, "%s for '%s': ", what, desc.buf);
 | 
			
		||||
	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);
 | 
			
		||||
 | 
			
		||||
	strbuf_release(&desc);
 | 
			
		||||
	strbuf_release(&prompt);
 | 
			
		||||
	return xstrdup(r);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void credential_getpass(struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	if (!c->username)
 | 
			
		||||
		c->username = credential_ask_one("Username", c);
 | 
			
		||||
	if (!c->password)
 | 
			
		||||
		c->password = credential_ask_one("Password", c);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int credential_read(struct credential *c, FILE *fp)
 | 
			
		||||
{
 | 
			
		||||
	struct strbuf line = STRBUF_INIT;
 | 
			
		||||
 | 
			
		||||
	while (strbuf_getline(&line, fp, '\n') != EOF) {
 | 
			
		||||
		char *key = line.buf;
 | 
			
		||||
		char *value = strchr(key, '=');
 | 
			
		||||
 | 
			
		||||
		if (!line.len)
 | 
			
		||||
			break;
 | 
			
		||||
 | 
			
		||||
		if (!value) {
 | 
			
		||||
			warning("invalid credential line: %s", key);
 | 
			
		||||
			strbuf_release(&line);
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
		*value++ = '\0';
 | 
			
		||||
 | 
			
		||||
		if (!strcmp(key, "username")) {
 | 
			
		||||
			free(c->username);
 | 
			
		||||
			c->username = xstrdup(value);
 | 
			
		||||
		} else if (!strcmp(key, "password")) {
 | 
			
		||||
			free(c->password);
 | 
			
		||||
			c->password = xstrdup(value);
 | 
			
		||||
		} else if (!strcmp(key, "protocol")) {
 | 
			
		||||
			free(c->protocol);
 | 
			
		||||
			c->protocol = xstrdup(value);
 | 
			
		||||
		} else if (!strcmp(key, "host")) {
 | 
			
		||||
			free(c->host);
 | 
			
		||||
			c->host = xstrdup(value);
 | 
			
		||||
		} else if (!strcmp(key, "path")) {
 | 
			
		||||
			free(c->path);
 | 
			
		||||
			c->path = xstrdup(value);
 | 
			
		||||
		}
 | 
			
		||||
		/*
 | 
			
		||||
		 * Ignore other lines; we don't know what they mean, but
 | 
			
		||||
		 * this future-proofs us when later versions of git do
 | 
			
		||||
		 * learn new lines, and the helpers are updated to match.
 | 
			
		||||
		 */
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	strbuf_release(&line);
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void credential_write_item(FILE *fp, const char *key, const char *value)
 | 
			
		||||
{
 | 
			
		||||
	if (!value)
 | 
			
		||||
		return;
 | 
			
		||||
	fprintf(fp, "%s=%s\n", key, value);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void credential_write(const struct credential *c, FILE *fp)
 | 
			
		||||
{
 | 
			
		||||
	credential_write_item(fp, "protocol", c->protocol);
 | 
			
		||||
	credential_write_item(fp, "host", c->host);
 | 
			
		||||
	credential_write_item(fp, "path", c->path);
 | 
			
		||||
	credential_write_item(fp, "username", c->username);
 | 
			
		||||
	credential_write_item(fp, "password", c->password);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int run_credential_helper(struct credential *c,
 | 
			
		||||
				 const char *cmd,
 | 
			
		||||
				 int want_output)
 | 
			
		||||
{
 | 
			
		||||
	struct child_process helper;
 | 
			
		||||
	const char *argv[] = { NULL, NULL };
 | 
			
		||||
	FILE *fp;
 | 
			
		||||
 | 
			
		||||
	memset(&helper, 0, sizeof(helper));
 | 
			
		||||
	argv[0] = cmd;
 | 
			
		||||
	helper.argv = argv;
 | 
			
		||||
	helper.use_shell = 1;
 | 
			
		||||
	helper.in = -1;
 | 
			
		||||
	if (want_output)
 | 
			
		||||
		helper.out = -1;
 | 
			
		||||
	else
 | 
			
		||||
		helper.no_stdout = 1;
 | 
			
		||||
 | 
			
		||||
	if (start_command(&helper) < 0)
 | 
			
		||||
		return -1;
 | 
			
		||||
 | 
			
		||||
	fp = xfdopen(helper.in, "w");
 | 
			
		||||
	credential_write(c, fp);
 | 
			
		||||
	fclose(fp);
 | 
			
		||||
 | 
			
		||||
	if (want_output) {
 | 
			
		||||
		int r;
 | 
			
		||||
		fp = xfdopen(helper.out, "r");
 | 
			
		||||
		r = credential_read(c, fp);
 | 
			
		||||
		fclose(fp);
 | 
			
		||||
		if (r < 0) {
 | 
			
		||||
			finish_command(&helper);
 | 
			
		||||
			return -1;
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (finish_command(&helper))
 | 
			
		||||
		return -1;
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int credential_do(struct credential *c, const char *helper,
 | 
			
		||||
			 const char *operation)
 | 
			
		||||
{
 | 
			
		||||
	struct strbuf cmd = STRBUF_INIT;
 | 
			
		||||
	int r;
 | 
			
		||||
 | 
			
		||||
	if (helper[0] == '!')
 | 
			
		||||
		strbuf_addstr(&cmd, helper + 1);
 | 
			
		||||
	else if (is_absolute_path(helper))
 | 
			
		||||
		strbuf_addstr(&cmd, helper);
 | 
			
		||||
	else
 | 
			
		||||
		strbuf_addf(&cmd, "git credential-%s", helper);
 | 
			
		||||
 | 
			
		||||
	strbuf_addf(&cmd, " %s", operation);
 | 
			
		||||
	r = run_credential_helper(c, cmd.buf, !strcmp(operation, "get"));
 | 
			
		||||
 | 
			
		||||
	strbuf_release(&cmd);
 | 
			
		||||
	return r;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void credential_fill(struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	if (c->username && c->password)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	credential_apply_config(c);
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < c->helpers.nr; i++) {
 | 
			
		||||
		credential_do(c, c->helpers.items[i].string, "get");
 | 
			
		||||
		if (c->username && c->password)
 | 
			
		||||
			return;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	credential_getpass(c);
 | 
			
		||||
	if (!c->username && !c->password)
 | 
			
		||||
		die("unable to get password from user");
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void credential_approve(struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	if (c->approved)
 | 
			
		||||
		return;
 | 
			
		||||
	if (!c->username || !c->password)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	credential_apply_config(c);
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < c->helpers.nr; i++)
 | 
			
		||||
		credential_do(c, c->helpers.items[i].string, "store");
 | 
			
		||||
	c->approved = 1;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void credential_reject(struct credential *c)
 | 
			
		||||
{
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	credential_apply_config(c);
 | 
			
		||||
 | 
			
		||||
	for (i = 0; i < c->helpers.nr; i++)
 | 
			
		||||
		credential_do(c, c->helpers.items[i].string, "erase");
 | 
			
		||||
 | 
			
		||||
	free(c->username);
 | 
			
		||||
	c->username = NULL;
 | 
			
		||||
	free(c->password);
 | 
			
		||||
	c->password = NULL;
 | 
			
		||||
	c->approved = 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void credential_from_url(struct credential *c, const char *url)
 | 
			
		||||
{
 | 
			
		||||
	const char *at, *colon, *cp, *slash, *host, *proto_end;
 | 
			
		||||
 | 
			
		||||
	credential_clear(c);
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Match one of:
 | 
			
		||||
	 *   (1) proto://<host>/...
 | 
			
		||||
	 *   (2) proto://<user>@<host>/...
 | 
			
		||||
	 *   (3) proto://<user>:<pass>@<host>/...
 | 
			
		||||
	 */
 | 
			
		||||
	proto_end = strstr(url, "://");
 | 
			
		||||
	if (!proto_end)
 | 
			
		||||
		return;
 | 
			
		||||
	cp = proto_end + 3;
 | 
			
		||||
	at = strchr(cp, '@');
 | 
			
		||||
	colon = strchr(cp, ':');
 | 
			
		||||
	slash = strchrnul(cp, '/');
 | 
			
		||||
 | 
			
		||||
	if (!at || slash <= at) {
 | 
			
		||||
		/* Case (1) */
 | 
			
		||||
		host = cp;
 | 
			
		||||
	}
 | 
			
		||||
	else if (!colon || at <= colon) {
 | 
			
		||||
		/* Case (2) */
 | 
			
		||||
		c->username = url_decode_mem(cp, at - cp);
 | 
			
		||||
		host = at + 1;
 | 
			
		||||
	} else {
 | 
			
		||||
		/* Case (3) */
 | 
			
		||||
		c->username = url_decode_mem(cp, colon - cp);
 | 
			
		||||
		c->password = url_decode_mem(colon + 1, at - (colon + 1));
 | 
			
		||||
		host = at + 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (proto_end - url > 0)
 | 
			
		||||
		c->protocol = xmemdupz(url, proto_end - url);
 | 
			
		||||
	if (slash - host > 0)
 | 
			
		||||
		c->host = url_decode_mem(host, slash - host);
 | 
			
		||||
	/* Trim leading and trailing slashes from path */
 | 
			
		||||
	while (*slash == '/')
 | 
			
		||||
		slash++;
 | 
			
		||||
	if (*slash) {
 | 
			
		||||
		char *p;
 | 
			
		||||
		c->path = url_decode(slash);
 | 
			
		||||
		p = c->path + strlen(c->path) - 1;
 | 
			
		||||
		while (p > c->path && *p == '/')
 | 
			
		||||
			*p-- = '\0';
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								credential.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								credential.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,33 @@
 | 
			
		||||
#ifndef CREDENTIAL_H
 | 
			
		||||
#define CREDENTIAL_H
 | 
			
		||||
 | 
			
		||||
#include "string-list.h"
 | 
			
		||||
 | 
			
		||||
struct credential {
 | 
			
		||||
	struct string_list helpers;
 | 
			
		||||
	unsigned approved:1,
 | 
			
		||||
		 configured:1,
 | 
			
		||||
		 use_http_path:1;
 | 
			
		||||
 | 
			
		||||
	char *username;
 | 
			
		||||
	char *password;
 | 
			
		||||
	char *protocol;
 | 
			
		||||
	char *host;
 | 
			
		||||
	char *path;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
#define CREDENTIAL_INIT { STRING_LIST_INIT_DUP }
 | 
			
		||||
 | 
			
		||||
void credential_init(struct credential *);
 | 
			
		||||
void credential_clear(struct credential *);
 | 
			
		||||
 | 
			
		||||
void credential_fill(struct credential *);
 | 
			
		||||
void credential_approve(struct credential *);
 | 
			
		||||
void credential_reject(struct credential *);
 | 
			
		||||
 | 
			
		||||
int credential_read(struct credential *, FILE *);
 | 
			
		||||
void credential_from_url(struct credential *, const char *url);
 | 
			
		||||
int credential_match(const struct credential *have,
 | 
			
		||||
		     const struct credential *want);
 | 
			
		||||
 | 
			
		||||
#endif /* CREDENTIAL_H */
 | 
			
		||||
@ -135,6 +135,7 @@
 | 
			
		||||
#include <arpa/inet.h>
 | 
			
		||||
#include <netdb.h>
 | 
			
		||||
#include <pwd.h>
 | 
			
		||||
#include <sys/un.h>
 | 
			
		||||
#ifndef NO_INTTYPES_H
 | 
			
		||||
#include <inttypes.h>
 | 
			
		||||
#else
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										111
									
								
								http.c
									
									
									
									
									
								
							
							
						
						
									
										111
									
								
								http.c
									
									
									
									
									
								
							@ -3,6 +3,7 @@
 | 
			
		||||
#include "sideband.h"
 | 
			
		||||
#include "run-command.h"
 | 
			
		||||
#include "url.h"
 | 
			
		||||
#include "credential.h"
 | 
			
		||||
 | 
			
		||||
int active_requests;
 | 
			
		||||
int http_is_verbose;
 | 
			
		||||
@ -41,7 +42,7 @@ static long curl_low_speed_time = -1;
 | 
			
		||||
static int curl_ftp_no_epsv;
 | 
			
		||||
static const char *curl_http_proxy;
 | 
			
		||||
static const char *curl_cookie_file;
 | 
			
		||||
static char *user_name, *user_pass, *description;
 | 
			
		||||
static struct credential http_auth = CREDENTIAL_INIT;
 | 
			
		||||
static const char *user_agent;
 | 
			
		||||
 | 
			
		||||
#if LIBCURL_VERSION_NUM >= 0x071700
 | 
			
		||||
@ -52,7 +53,7 @@ static const char *user_agent;
 | 
			
		||||
#define CURLOPT_KEYPASSWD CURLOPT_SSLCERTPASSWD
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static char *ssl_cert_password;
 | 
			
		||||
static struct credential cert_auth = CREDENTIAL_INIT;
 | 
			
		||||
static int ssl_cert_password_required;
 | 
			
		||||
 | 
			
		||||
static struct curl_slist *pragma_header;
 | 
			
		||||
@ -136,27 +137,6 @@ static void process_curl_messages(void)
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
static char *git_getpass_with_description(const char *what, const char *desc)
 | 
			
		||||
{
 | 
			
		||||
	struct strbuf prompt = STRBUF_INIT;
 | 
			
		||||
	char *r;
 | 
			
		||||
 | 
			
		||||
	if (desc)
 | 
			
		||||
		strbuf_addf(&prompt, "%s for '%s': ", what, desc);
 | 
			
		||||
	else
 | 
			
		||||
		strbuf_addf(&prompt, "%s: ", what);
 | 
			
		||||
	/*
 | 
			
		||||
	 * NEEDSWORK: 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);
 | 
			
		||||
 | 
			
		||||
	strbuf_release(&prompt);
 | 
			
		||||
	return xstrdup(r);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int http_options(const char *var, const char *value, void *cb)
 | 
			
		||||
{
 | 
			
		||||
	if (!strcmp("http.sslverify", var)) {
 | 
			
		||||
@ -229,11 +209,11 @@ static int http_options(const char *var, const char *value, void *cb)
 | 
			
		||||
 | 
			
		||||
static void init_curl_http_auth(CURL *result)
 | 
			
		||||
{
 | 
			
		||||
	if (user_name) {
 | 
			
		||||
	if (http_auth.username) {
 | 
			
		||||
		struct strbuf up = STRBUF_INIT;
 | 
			
		||||
		if (!user_pass)
 | 
			
		||||
			user_pass = xstrdup(git_getpass_with_description("Password", description));
 | 
			
		||||
		strbuf_addf(&up, "%s:%s", user_name, user_pass);
 | 
			
		||||
		credential_fill(&http_auth);
 | 
			
		||||
		strbuf_addf(&up, "%s:%s",
 | 
			
		||||
			    http_auth.username, http_auth.password);
 | 
			
		||||
		curl_easy_setopt(result, CURLOPT_USERPWD,
 | 
			
		||||
				 strbuf_detach(&up, NULL));
 | 
			
		||||
	}
 | 
			
		||||
@ -241,18 +221,14 @@ static void init_curl_http_auth(CURL *result)
 | 
			
		||||
 | 
			
		||||
static int has_cert_password(void)
 | 
			
		||||
{
 | 
			
		||||
	if (ssl_cert_password != NULL)
 | 
			
		||||
		return 1;
 | 
			
		||||
	if (ssl_cert == NULL || ssl_cert_password_required != 1)
 | 
			
		||||
		return 0;
 | 
			
		||||
	/* Only prompt the user once. */
 | 
			
		||||
	ssl_cert_password_required = -1;
 | 
			
		||||
	ssl_cert_password = git_getpass_with_description("Certificate Password", description);
 | 
			
		||||
	if (ssl_cert_password != NULL) {
 | 
			
		||||
		ssl_cert_password = xstrdup(ssl_cert_password);
 | 
			
		||||
	if (!cert_auth.password) {
 | 
			
		||||
		cert_auth.protocol = xstrdup("cert");
 | 
			
		||||
		cert_auth.path = xstrdup(ssl_cert);
 | 
			
		||||
		credential_fill(&cert_auth);
 | 
			
		||||
	}
 | 
			
		||||
	return 1;
 | 
			
		||||
	} else
 | 
			
		||||
		return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static CURL *get_curl_handle(void)
 | 
			
		||||
@ -279,7 +255,7 @@ static CURL *get_curl_handle(void)
 | 
			
		||||
	if (ssl_cert != NULL)
 | 
			
		||||
		curl_easy_setopt(result, CURLOPT_SSLCERT, ssl_cert);
 | 
			
		||||
	if (has_cert_password())
 | 
			
		||||
		curl_easy_setopt(result, CURLOPT_KEYPASSWD, ssl_cert_password);
 | 
			
		||||
		curl_easy_setopt(result, CURLOPT_KEYPASSWD, cert_auth.password);
 | 
			
		||||
#if LIBCURL_VERSION_NUM >= 0x070903
 | 
			
		||||
	if (ssl_key != NULL)
 | 
			
		||||
		curl_easy_setopt(result, CURLOPT_SSLKEY, ssl_key);
 | 
			
		||||
@ -321,42 +297,6 @@ static CURL *get_curl_handle(void)
 | 
			
		||||
	return result;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void http_auth_init(const char *url)
 | 
			
		||||
{
 | 
			
		||||
	const char *at, *colon, *cp, *slash, *host;
 | 
			
		||||
 | 
			
		||||
	cp = strstr(url, "://");
 | 
			
		||||
	if (!cp)
 | 
			
		||||
		return;
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	 * Ok, the URL looks like "proto://something".  Which one?
 | 
			
		||||
	 * "proto://<user>:<pass>@<host>/...",
 | 
			
		||||
	 * "proto://<user>@<host>/...", or just
 | 
			
		||||
	 * "proto://<host>/..."?
 | 
			
		||||
	 */
 | 
			
		||||
	cp += 3;
 | 
			
		||||
	at = strchr(cp, '@');
 | 
			
		||||
	colon = strchr(cp, ':');
 | 
			
		||||
	slash = strchrnul(cp, '/');
 | 
			
		||||
	if (!at || slash <= at) {
 | 
			
		||||
		/* No credentials, but we may have to ask for some later */
 | 
			
		||||
		host = cp;
 | 
			
		||||
	}
 | 
			
		||||
	else if (!colon || at <= colon) {
 | 
			
		||||
		/* Only username */
 | 
			
		||||
		user_name = url_decode_mem(cp, at - cp);
 | 
			
		||||
		user_pass = NULL;
 | 
			
		||||
		host = at + 1;
 | 
			
		||||
	} else {
 | 
			
		||||
		user_name = url_decode_mem(cp, colon - cp);
 | 
			
		||||
		user_pass = url_decode_mem(colon + 1, at - (colon + 1));
 | 
			
		||||
		host = at + 1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	description = url_decode_mem(host, slash - host);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void set_from_env(const char **var, const char *envname)
 | 
			
		||||
{
 | 
			
		||||
	const char *val = getenv(envname);
 | 
			
		||||
@ -429,7 +369,7 @@ void http_init(struct remote *remote, const char *url)
 | 
			
		||||
		curl_ftp_no_epsv = 1;
 | 
			
		||||
 | 
			
		||||
	if (url) {
 | 
			
		||||
		http_auth_init(url);
 | 
			
		||||
		credential_from_url(&http_auth, url);
 | 
			
		||||
		if (!ssl_cert_password_required &&
 | 
			
		||||
		    getenv("GIT_SSL_CERT_PASSWORD_PROTECTED") &&
 | 
			
		||||
		    !prefixcmp(url, "https://"))
 | 
			
		||||
@ -478,10 +418,10 @@ void http_cleanup(void)
 | 
			
		||||
		curl_http_proxy = NULL;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (ssl_cert_password != NULL) {
 | 
			
		||||
		memset(ssl_cert_password, 0, strlen(ssl_cert_password));
 | 
			
		||||
		free(ssl_cert_password);
 | 
			
		||||
		ssl_cert_password = NULL;
 | 
			
		||||
	if (cert_auth.password != NULL) {
 | 
			
		||||
		memset(cert_auth.password, 0, strlen(cert_auth.password));
 | 
			
		||||
		free(cert_auth.password);
 | 
			
		||||
		cert_auth.password = NULL;
 | 
			
		||||
	}
 | 
			
		||||
	ssl_cert_password_required = 0;
 | 
			
		||||
}
 | 
			
		||||
@ -837,17 +777,11 @@ static int http_request(const char *url, void *result, int target, int options)
 | 
			
		||||
		else if (missing_target(&results))
 | 
			
		||||
			ret = HTTP_MISSING_TARGET;
 | 
			
		||||
		else if (results.http_code == 401) {
 | 
			
		||||
			if (user_name && user_pass) {
 | 
			
		||||
			if (http_auth.username && http_auth.password) {
 | 
			
		||||
				credential_reject(&http_auth);
 | 
			
		||||
				ret = HTTP_NOAUTH;
 | 
			
		||||
			} else {
 | 
			
		||||
				/*
 | 
			
		||||
				 * git_getpass is needed here because its very likely stdin/stdout are
 | 
			
		||||
				 * pipes to our parent process.  So we instead need to use /dev/tty,
 | 
			
		||||
				 * but that is non-portable.  Using git_getpass() can at least be stubbed
 | 
			
		||||
				 * on other platforms with a different implementation if/when necessary.
 | 
			
		||||
				 */
 | 
			
		||||
				if (!user_name)
 | 
			
		||||
					user_name = xstrdup(git_getpass_with_description("Username", description));
 | 
			
		||||
				credential_fill(&http_auth);
 | 
			
		||||
				init_curl_http_auth(slot->curl);
 | 
			
		||||
				ret = HTTP_REAUTH;
 | 
			
		||||
			}
 | 
			
		||||
@ -866,6 +800,9 @@ static int http_request(const char *url, void *result, int target, int options)
 | 
			
		||||
	curl_slist_free_all(headers);
 | 
			
		||||
	strbuf_release(&buf);
 | 
			
		||||
 | 
			
		||||
	if (ret == HTTP_OK)
 | 
			
		||||
		credential_approve(&http_auth);
 | 
			
		||||
 | 
			
		||||
	return ret;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										37
									
								
								strbuf.c
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								strbuf.c
									
									
									
									
									
								
							@ -411,3 +411,40 @@ void strbuf_add_lines(struct strbuf *out, const char *prefix,
 | 
			
		||||
	}
 | 
			
		||||
	strbuf_complete_line(out);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int is_rfc3986_reserved(char ch)
 | 
			
		||||
{
 | 
			
		||||
	switch (ch) {
 | 
			
		||||
		case '!': case '*': case '\'': case '(': case ')': case ';':
 | 
			
		||||
		case ':': case '@': case '&': case '=': case '+': case '$':
 | 
			
		||||
		case ',': case '/': case '?': case '#': case '[': case ']':
 | 
			
		||||
			return 1;
 | 
			
		||||
	}
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static int is_rfc3986_unreserved(char ch)
 | 
			
		||||
{
 | 
			
		||||
	return isalnum(ch) ||
 | 
			
		||||
		ch == '-' || ch == '_' || ch == '.' || ch == '~';
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void strbuf_add_urlencode(struct strbuf *sb, const char *s, size_t len,
 | 
			
		||||
			  int reserved)
 | 
			
		||||
{
 | 
			
		||||
	strbuf_grow(sb, len);
 | 
			
		||||
	while (len--) {
 | 
			
		||||
		char ch = *s++;
 | 
			
		||||
		if (is_rfc3986_unreserved(ch) ||
 | 
			
		||||
		    (!reserved && is_rfc3986_reserved(ch)))
 | 
			
		||||
			strbuf_addch(sb, ch);
 | 
			
		||||
		else
 | 
			
		||||
			strbuf_addf(sb, "%%%02x", ch);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void strbuf_addstr_urlencode(struct strbuf *sb, const char *s,
 | 
			
		||||
			     int reserved)
 | 
			
		||||
{
 | 
			
		||||
	strbuf_add_urlencode(sb, s, strlen(s), reserved);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										5
									
								
								strbuf.h
									
									
									
									
									
								
							
							
						
						
									
										5
									
								
								strbuf.h
									
									
									
									
									
								
							@ -123,4 +123,9 @@ extern int launch_editor(const char *path, struct strbuf *buffer, const char *co
 | 
			
		||||
extern int strbuf_branchname(struct strbuf *sb, const char *name);
 | 
			
		||||
extern int strbuf_check_branch_ref(struct strbuf *sb, const char *name);
 | 
			
		||||
 | 
			
		||||
extern void strbuf_add_urlencode(struct strbuf *, const char *, size_t,
 | 
			
		||||
				 int reserved);
 | 
			
		||||
extern void strbuf_addstr_urlencode(struct strbuf *, const char *,
 | 
			
		||||
				    int reserved);
 | 
			
		||||
 | 
			
		||||
#endif /* STRBUF_H */
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										254
									
								
								t/lib-credential.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										254
									
								
								t/lib-credential.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,254 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
# Try a set of credential helpers; the expected stdin,
 | 
			
		||||
# stdout and stderr should be provided on stdin,
 | 
			
		||||
# separated by "--".
 | 
			
		||||
check() {
 | 
			
		||||
	read_chunk >stdin &&
 | 
			
		||||
	read_chunk >expect-stdout &&
 | 
			
		||||
	read_chunk >expect-stderr &&
 | 
			
		||||
	test-credential "$@" <stdin >stdout 2>stderr &&
 | 
			
		||||
	test_cmp expect-stdout stdout &&
 | 
			
		||||
	test_cmp expect-stderr stderr
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
read_chunk() {
 | 
			
		||||
	while read line; do
 | 
			
		||||
		case "$line" in
 | 
			
		||||
		--) break ;;
 | 
			
		||||
		*) echo "$line" ;;
 | 
			
		||||
		esac
 | 
			
		||||
	done
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Clear any residual data from previous tests. We only
 | 
			
		||||
# need this when testing third-party helpers which read and
 | 
			
		||||
# write outside of our trash-directory sandbox.
 | 
			
		||||
#
 | 
			
		||||
# Don't bother checking for success here, as it is
 | 
			
		||||
# outside the scope of tests and represents a best effort to
 | 
			
		||||
# clean up after ourselves.
 | 
			
		||||
helper_test_clean() {
 | 
			
		||||
	reject $1 https example.com store-user
 | 
			
		||||
	reject $1 https example.com user1
 | 
			
		||||
	reject $1 https example.com user2
 | 
			
		||||
	reject $1 http path.tld user
 | 
			
		||||
	reject $1 https timeout.tld user
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
reject() {
 | 
			
		||||
	(
 | 
			
		||||
		echo protocol=$2
 | 
			
		||||
		echo host=$3
 | 
			
		||||
		echo username=$4
 | 
			
		||||
	) | test-credential reject $1
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
helper_test() {
 | 
			
		||||
	HELPER=$1
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) has no existing data" '
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		--
 | 
			
		||||
		username=askpass-username
 | 
			
		||||
		password=askpass-password
 | 
			
		||||
		--
 | 
			
		||||
		askpass: Username for '\''https://example.com'\'':
 | 
			
		||||
		askpass: Password for '\''https://askpass-username@example.com'\'':
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) stores password" '
 | 
			
		||||
		check approve $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=store-user
 | 
			
		||||
		password=store-pass
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) can retrieve password" '
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		--
 | 
			
		||||
		username=store-user
 | 
			
		||||
		password=store-pass
 | 
			
		||||
		--
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) requires matching protocol" '
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=http
 | 
			
		||||
		host=example.com
 | 
			
		||||
		--
 | 
			
		||||
		username=askpass-username
 | 
			
		||||
		password=askpass-password
 | 
			
		||||
		--
 | 
			
		||||
		askpass: Username for '\''http://example.com'\'':
 | 
			
		||||
		askpass: Password for '\''http://askpass-username@example.com'\'':
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) requires matching host" '
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=other.tld
 | 
			
		||||
		--
 | 
			
		||||
		username=askpass-username
 | 
			
		||||
		password=askpass-password
 | 
			
		||||
		--
 | 
			
		||||
		askpass: Username for '\''https://other.tld'\'':
 | 
			
		||||
		askpass: Password for '\''https://askpass-username@other.tld'\'':
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) requires matching username" '
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=other
 | 
			
		||||
		--
 | 
			
		||||
		username=other
 | 
			
		||||
		password=askpass-password
 | 
			
		||||
		--
 | 
			
		||||
		askpass: Password for '\''https://other@example.com'\'':
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) requires matching path" '
 | 
			
		||||
		test_config credential.usehttppath true &&
 | 
			
		||||
		check approve $HELPER <<-\EOF &&
 | 
			
		||||
		protocol=http
 | 
			
		||||
		host=path.tld
 | 
			
		||||
		path=foo.git
 | 
			
		||||
		username=user
 | 
			
		||||
		password=pass
 | 
			
		||||
		EOF
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=http
 | 
			
		||||
		host=path.tld
 | 
			
		||||
		path=bar.git
 | 
			
		||||
		--
 | 
			
		||||
		username=askpass-username
 | 
			
		||||
		password=askpass-password
 | 
			
		||||
		--
 | 
			
		||||
		askpass: Username for '\''http://path.tld/bar.git'\'':
 | 
			
		||||
		askpass: Password for '\''http://askpass-username@path.tld/bar.git'\'':
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) can forget host" '
 | 
			
		||||
		check reject $HELPER <<-\EOF &&
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		EOF
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		--
 | 
			
		||||
		username=askpass-username
 | 
			
		||||
		password=askpass-password
 | 
			
		||||
		--
 | 
			
		||||
		askpass: Username for '\''https://example.com'\'':
 | 
			
		||||
		askpass: Password for '\''https://askpass-username@example.com'\'':
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) can store multiple users" '
 | 
			
		||||
		check approve $HELPER <<-\EOF &&
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=user1
 | 
			
		||||
		password=pass1
 | 
			
		||||
		EOF
 | 
			
		||||
		check approve $HELPER <<-\EOF &&
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=user2
 | 
			
		||||
		password=pass2
 | 
			
		||||
		EOF
 | 
			
		||||
		check fill $HELPER <<-\EOF &&
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=user1
 | 
			
		||||
		--
 | 
			
		||||
		username=user1
 | 
			
		||||
		password=pass1
 | 
			
		||||
		EOF
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=user2
 | 
			
		||||
		--
 | 
			
		||||
		username=user2
 | 
			
		||||
		password=pass2
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) can forget user" '
 | 
			
		||||
		check reject $HELPER <<-\EOF &&
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=user1
 | 
			
		||||
		EOF
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=user1
 | 
			
		||||
		--
 | 
			
		||||
		username=user1
 | 
			
		||||
		password=askpass-password
 | 
			
		||||
		--
 | 
			
		||||
		askpass: Password for '\''https://user1@example.com'\'':
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) remembers other user" '
 | 
			
		||||
		check fill $HELPER <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=example.com
 | 
			
		||||
		username=user2
 | 
			
		||||
		--
 | 
			
		||||
		username=user2
 | 
			
		||||
		password=pass2
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
helper_test_timeout() {
 | 
			
		||||
	HELPER="$*"
 | 
			
		||||
 | 
			
		||||
	test_expect_success "helper ($HELPER) times out" '
 | 
			
		||||
		check approve "$HELPER" <<-\EOF &&
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=timeout.tld
 | 
			
		||||
		username=user
 | 
			
		||||
		password=pass
 | 
			
		||||
		EOF
 | 
			
		||||
		sleep 2 &&
 | 
			
		||||
		check fill "$HELPER" <<-\EOF
 | 
			
		||||
		protocol=https
 | 
			
		||||
		host=timeout.tld
 | 
			
		||||
		--
 | 
			
		||||
		username=askpass-username
 | 
			
		||||
		password=askpass-password
 | 
			
		||||
		--
 | 
			
		||||
		askpass: Username for '\''https://timeout.tld'\'':
 | 
			
		||||
		askpass: Password for '\''https://askpass-username@timeout.tld'\'':
 | 
			
		||||
		EOF
 | 
			
		||||
	'
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
cat >askpass <<\EOF
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
echo >&2 askpass: $*
 | 
			
		||||
what=`echo $1 | cut -d" " -f1 | tr A-Z a-z | tr -cd a-z`
 | 
			
		||||
echo "askpass-$what"
 | 
			
		||||
EOF
 | 
			
		||||
chmod +x askpass
 | 
			
		||||
GIT_ASKPASS="$PWD/askpass"
 | 
			
		||||
export GIT_ASKPASS
 | 
			
		||||
							
								
								
									
										279
									
								
								t/t0300-credentials.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										279
									
								
								t/t0300-credentials.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,279 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
test_description='basic credential helper tests'
 | 
			
		||||
. ./test-lib.sh
 | 
			
		||||
. "$TEST_DIRECTORY"/lib-credential.sh
 | 
			
		||||
 | 
			
		||||
test_expect_success 'setup helper scripts' '
 | 
			
		||||
	cat >dump <<-\EOF &&
 | 
			
		||||
	whoami=`echo $0 | sed s/.*git-credential-//`
 | 
			
		||||
	echo >&2 "$whoami: $*"
 | 
			
		||||
	while IFS== read key value; do
 | 
			
		||||
		echo >&2 "$whoami: $key=$value"
 | 
			
		||||
		eval "$key=$value"
 | 
			
		||||
	done
 | 
			
		||||
	EOF
 | 
			
		||||
 | 
			
		||||
	cat >git-credential-useless <<-\EOF &&
 | 
			
		||||
	#!/bin/sh
 | 
			
		||||
	. ./dump
 | 
			
		||||
	exit 0
 | 
			
		||||
	EOF
 | 
			
		||||
	chmod +x git-credential-useless &&
 | 
			
		||||
 | 
			
		||||
	cat >git-credential-verbatim <<-\EOF &&
 | 
			
		||||
	#!/bin/sh
 | 
			
		||||
	user=$1; shift
 | 
			
		||||
	pass=$1; shift
 | 
			
		||||
	. ./dump
 | 
			
		||||
	test -z "$user" || echo username=$user
 | 
			
		||||
	test -z "$pass" || echo password=$pass
 | 
			
		||||
	EOF
 | 
			
		||||
	chmod +x git-credential-verbatim &&
 | 
			
		||||
 | 
			
		||||
	PATH="$PWD:$PATH"
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'credential_fill invokes helper' '
 | 
			
		||||
	check fill "verbatim foo bar" <<-\EOF
 | 
			
		||||
	--
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=bar
 | 
			
		||||
	--
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'credential_fill invokes multiple helpers' '
 | 
			
		||||
	check fill useless "verbatim foo bar" <<-\EOF
 | 
			
		||||
	--
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=bar
 | 
			
		||||
	--
 | 
			
		||||
	useless: get
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'credential_fill stops when we get a full response' '
 | 
			
		||||
	check fill "verbatim one two" "verbatim three four" <<-\EOF
 | 
			
		||||
	--
 | 
			
		||||
	username=one
 | 
			
		||||
	password=two
 | 
			
		||||
	--
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'credential_fill continues through partial response' '
 | 
			
		||||
	check fill "verbatim one \"\"" "verbatim two three" <<-\EOF
 | 
			
		||||
	--
 | 
			
		||||
	username=two
 | 
			
		||||
	password=three
 | 
			
		||||
	--
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	verbatim: username=one
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'credential_fill passes along metadata' '
 | 
			
		||||
	check fill "verbatim one two" <<-\EOF
 | 
			
		||||
	protocol=ftp
 | 
			
		||||
	host=example.com
 | 
			
		||||
	path=foo.git
 | 
			
		||||
	--
 | 
			
		||||
	username=one
 | 
			
		||||
	password=two
 | 
			
		||||
	--
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	verbatim: protocol=ftp
 | 
			
		||||
	verbatim: host=example.com
 | 
			
		||||
	verbatim: path=foo.git
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'credential_approve calls all helpers' '
 | 
			
		||||
	check approve useless "verbatim one two" <<-\EOF
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=bar
 | 
			
		||||
	--
 | 
			
		||||
	--
 | 
			
		||||
	useless: store
 | 
			
		||||
	useless: username=foo
 | 
			
		||||
	useless: password=bar
 | 
			
		||||
	verbatim: store
 | 
			
		||||
	verbatim: username=foo
 | 
			
		||||
	verbatim: password=bar
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'do not bother storing password-less credential' '
 | 
			
		||||
	check approve useless <<-\EOF
 | 
			
		||||
	username=foo
 | 
			
		||||
	--
 | 
			
		||||
	--
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
test_expect_success 'credential_reject calls all helpers' '
 | 
			
		||||
	check reject useless "verbatim one two" <<-\EOF
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=bar
 | 
			
		||||
	--
 | 
			
		||||
	--
 | 
			
		||||
	useless: erase
 | 
			
		||||
	useless: username=foo
 | 
			
		||||
	useless: password=bar
 | 
			
		||||
	verbatim: erase
 | 
			
		||||
	verbatim: username=foo
 | 
			
		||||
	verbatim: password=bar
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'usernames can be preserved' '
 | 
			
		||||
	check fill "verbatim \"\" three" <<-\EOF
 | 
			
		||||
	username=one
 | 
			
		||||
	--
 | 
			
		||||
	username=one
 | 
			
		||||
	password=three
 | 
			
		||||
	--
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	verbatim: username=one
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'usernames can be overridden' '
 | 
			
		||||
	check fill "verbatim two three" <<-\EOF
 | 
			
		||||
	username=one
 | 
			
		||||
	--
 | 
			
		||||
	username=two
 | 
			
		||||
	password=three
 | 
			
		||||
	--
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	verbatim: username=one
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'do not bother completing already-full credential' '
 | 
			
		||||
	check fill "verbatim three four" <<-\EOF
 | 
			
		||||
	username=one
 | 
			
		||||
	password=two
 | 
			
		||||
	--
 | 
			
		||||
	username=one
 | 
			
		||||
	password=two
 | 
			
		||||
	--
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
# We can't test the basic terminal password prompt here because
 | 
			
		||||
# getpass() tries too hard to find the real terminal. But if our
 | 
			
		||||
# askpass helper is run, we know the internal getpass is working.
 | 
			
		||||
test_expect_success 'empty helper list falls back to internal getpass' '
 | 
			
		||||
	check fill <<-\EOF
 | 
			
		||||
	--
 | 
			
		||||
	username=askpass-username
 | 
			
		||||
	password=askpass-password
 | 
			
		||||
	--
 | 
			
		||||
	askpass: Username:
 | 
			
		||||
	askpass: Password:
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'internal getpass does not ask for known username' '
 | 
			
		||||
	check fill <<-\EOF
 | 
			
		||||
	username=foo
 | 
			
		||||
	--
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=askpass-password
 | 
			
		||||
	--
 | 
			
		||||
	askpass: Password:
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
HELPER="!f() {
 | 
			
		||||
		cat >/dev/null
 | 
			
		||||
		echo username=foo
 | 
			
		||||
		echo password=bar
 | 
			
		||||
	}; f"
 | 
			
		||||
test_expect_success 'respect configured credentials' '
 | 
			
		||||
	test_config credential.helper "$HELPER" &&
 | 
			
		||||
	check fill <<-\EOF
 | 
			
		||||
	--
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=bar
 | 
			
		||||
	--
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'match configured credential' '
 | 
			
		||||
	test_config credential.https://example.com.helper "$HELPER" &&
 | 
			
		||||
	check fill <<-\EOF
 | 
			
		||||
	protocol=https
 | 
			
		||||
	host=example.com
 | 
			
		||||
	path=repo.git
 | 
			
		||||
	--
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=bar
 | 
			
		||||
	--
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'do not match configured credential' '
 | 
			
		||||
	test_config credential.https://foo.helper "$HELPER" &&
 | 
			
		||||
	check fill <<-\EOF
 | 
			
		||||
	protocol=https
 | 
			
		||||
	host=bar
 | 
			
		||||
	--
 | 
			
		||||
	username=askpass-username
 | 
			
		||||
	password=askpass-password
 | 
			
		||||
	--
 | 
			
		||||
	askpass: Username for '\''https://bar'\'':
 | 
			
		||||
	askpass: Password for '\''https://askpass-username@bar'\'':
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'pull username from config' '
 | 
			
		||||
	test_config credential.https://example.com.username foo &&
 | 
			
		||||
	check fill <<-\EOF
 | 
			
		||||
	protocol=https
 | 
			
		||||
	host=example.com
 | 
			
		||||
	--
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=askpass-password
 | 
			
		||||
	--
 | 
			
		||||
	askpass: Password for '\''https://foo@example.com'\'':
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'http paths can be part of context' '
 | 
			
		||||
	check fill "verbatim foo bar" <<-\EOF &&
 | 
			
		||||
	protocol=https
 | 
			
		||||
	host=example.com
 | 
			
		||||
	path=foo.git
 | 
			
		||||
	--
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=bar
 | 
			
		||||
	--
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	verbatim: protocol=https
 | 
			
		||||
	verbatim: host=example.com
 | 
			
		||||
	EOF
 | 
			
		||||
	test_config credential.https://example.com.useHttpPath true &&
 | 
			
		||||
	check fill "verbatim foo bar" <<-\EOF
 | 
			
		||||
	protocol=https
 | 
			
		||||
	host=example.com
 | 
			
		||||
	path=foo.git
 | 
			
		||||
	--
 | 
			
		||||
	username=foo
 | 
			
		||||
	password=bar
 | 
			
		||||
	--
 | 
			
		||||
	verbatim: get
 | 
			
		||||
	verbatim: protocol=https
 | 
			
		||||
	verbatim: host=example.com
 | 
			
		||||
	verbatim: path=foo.git
 | 
			
		||||
	EOF
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_done
 | 
			
		||||
							
								
								
									
										23
									
								
								t/t0301-credential-cache.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										23
									
								
								t/t0301-credential-cache.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,23 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
test_description='credential-cache tests'
 | 
			
		||||
. ./test-lib.sh
 | 
			
		||||
. "$TEST_DIRECTORY"/lib-credential.sh
 | 
			
		||||
 | 
			
		||||
test -z "$NO_UNIX_SOCKETS" || {
 | 
			
		||||
	skip_all='skipping credential-cache tests, unix sockets not available'
 | 
			
		||||
	test_done
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# don't leave a stale daemon running
 | 
			
		||||
trap 'code=$?; git credential-cache exit; (exit $code); die' EXIT
 | 
			
		||||
 | 
			
		||||
helper_test cache
 | 
			
		||||
helper_test_timeout cache --timeout=1
 | 
			
		||||
 | 
			
		||||
# we can't rely on our "trap" above working after test_done,
 | 
			
		||||
# as test_done will delete the trash directory containing
 | 
			
		||||
# our socket, leaving us with no way to access the daemon.
 | 
			
		||||
git credential-cache exit
 | 
			
		||||
 | 
			
		||||
test_done
 | 
			
		||||
							
								
								
									
										9
									
								
								t/t0302-credential-store.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										9
									
								
								t/t0302-credential-store.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,9 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
test_description='credential-store tests'
 | 
			
		||||
. ./test-lib.sh
 | 
			
		||||
. "$TEST_DIRECTORY"/lib-credential.sh
 | 
			
		||||
 | 
			
		||||
helper_test store
 | 
			
		||||
 | 
			
		||||
test_done
 | 
			
		||||
							
								
								
									
										39
									
								
								t/t0303-credential-external.sh
									
									
									
									
									
										Executable file
									
								
							
							
						
						
									
										39
									
								
								t/t0303-credential-external.sh
									
									
									
									
									
										Executable file
									
								
							@ -0,0 +1,39 @@
 | 
			
		||||
#!/bin/sh
 | 
			
		||||
 | 
			
		||||
test_description='external credential helper tests'
 | 
			
		||||
. ./test-lib.sh
 | 
			
		||||
. "$TEST_DIRECTORY"/lib-credential.sh
 | 
			
		||||
 | 
			
		||||
pre_test() {
 | 
			
		||||
	test -z "$GIT_TEST_CREDENTIAL_HELPER_SETUP" ||
 | 
			
		||||
	eval "$GIT_TEST_CREDENTIAL_HELPER_SETUP"
 | 
			
		||||
 | 
			
		||||
	# clean before the test in case there is cruft left
 | 
			
		||||
	# over from a previous run that would impact results
 | 
			
		||||
	helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
post_test() {
 | 
			
		||||
	# clean afterwards so that we are good citizens
 | 
			
		||||
	# and don't leave cruft in the helper's storage, which
 | 
			
		||||
	# might be long-term system storage
 | 
			
		||||
	helper_test_clean "$GIT_TEST_CREDENTIAL_HELPER"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
if test -z "$GIT_TEST_CREDENTIAL_HELPER"; then
 | 
			
		||||
	say "# skipping external helper tests (set GIT_TEST_CREDENTIAL_HELPER)"
 | 
			
		||||
else
 | 
			
		||||
	pre_test
 | 
			
		||||
	helper_test "$GIT_TEST_CREDENTIAL_HELPER"
 | 
			
		||||
	post_test
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
if test -z "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"; then
 | 
			
		||||
	say "# skipping external helper timeout tests"
 | 
			
		||||
else
 | 
			
		||||
	pre_test
 | 
			
		||||
	helper_test_timeout "$GIT_TEST_CREDENTIAL_HELPER_TIMEOUT"
 | 
			
		||||
	post_test
 | 
			
		||||
fi
 | 
			
		||||
 | 
			
		||||
test_done
 | 
			
		||||
@ -49,40 +49,84 @@ test_expect_success 'setup askpass helpers' '
 | 
			
		||||
	EOF
 | 
			
		||||
	chmod +x askpass &&
 | 
			
		||||
	GIT_ASKPASS="$PWD/askpass" &&
 | 
			
		||||
	export GIT_ASKPASS &&
 | 
			
		||||
	>askpass-expect-none &&
 | 
			
		||||
	echo "askpass: Password for '\''$HTTPD_DEST'\'': " >askpass-expect-pass &&
 | 
			
		||||
	{ echo "askpass: Username for '\''$HTTPD_DEST'\'': " &&
 | 
			
		||||
	  cat askpass-expect-pass
 | 
			
		||||
	} >askpass-expect-both
 | 
			
		||||
	export GIT_ASKPASS
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
expect_askpass() {
 | 
			
		||||
	dest=$HTTPD_DEST
 | 
			
		||||
	{
 | 
			
		||||
		case "$1" in
 | 
			
		||||
		none)
 | 
			
		||||
			;;
 | 
			
		||||
		pass)
 | 
			
		||||
			echo "askpass: Password for 'http://$2@$dest': "
 | 
			
		||||
			;;
 | 
			
		||||
		both)
 | 
			
		||||
			echo "askpass: Username for 'http://$dest': "
 | 
			
		||||
			echo "askpass: Password for 'http://$2@$dest': "
 | 
			
		||||
			;;
 | 
			
		||||
		*)
 | 
			
		||||
			false
 | 
			
		||||
			;;
 | 
			
		||||
		esac
 | 
			
		||||
	} >askpass-expect &&
 | 
			
		||||
	test_cmp askpass-expect askpass-query
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test_expect_success 'cloning password-protected repository can fail' '
 | 
			
		||||
	>askpass-query &&
 | 
			
		||||
	echo wrong >askpass-response &&
 | 
			
		||||
	test_must_fail git clone "$HTTPD_URL/auth/repo.git" clone-auth-fail &&
 | 
			
		||||
	test_cmp askpass-expect-both askpass-query
 | 
			
		||||
	expect_askpass both wrong
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'http auth can use user/pass in URL' '
 | 
			
		||||
	>askpass-query &&
 | 
			
		||||
	echo wrong >askpass-reponse &&
 | 
			
		||||
	echo wrong >askpass-response &&
 | 
			
		||||
	git clone "$HTTPD_URL_USER_PASS/auth/repo.git" clone-auth-none &&
 | 
			
		||||
	test_cmp askpass-expect-none askpass-query
 | 
			
		||||
	expect_askpass none
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'http auth can use just user in URL' '
 | 
			
		||||
	>askpass-query &&
 | 
			
		||||
	echo user@host >askpass-response &&
 | 
			
		||||
	git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-pass &&
 | 
			
		||||
	test_cmp askpass-expect-pass askpass-query
 | 
			
		||||
	expect_askpass pass user@host
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'http auth can request both user and pass' '
 | 
			
		||||
	>askpass-query &&
 | 
			
		||||
	echo user@host >askpass-response &&
 | 
			
		||||
	git clone "$HTTPD_URL/auth/repo.git" clone-auth-both &&
 | 
			
		||||
	test_cmp askpass-expect-both askpass-query
 | 
			
		||||
	expect_askpass both user@host
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'http auth respects credential helper config' '
 | 
			
		||||
	test_config_global credential.helper "!f() {
 | 
			
		||||
		cat >/dev/null
 | 
			
		||||
		echo username=user@host
 | 
			
		||||
		echo password=user@host
 | 
			
		||||
	}; f" &&
 | 
			
		||||
	>askpass-query &&
 | 
			
		||||
	echo wrong >askpass-response &&
 | 
			
		||||
	git clone "$HTTPD_URL/auth/repo.git" clone-auth-helper &&
 | 
			
		||||
	expect_askpass none
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'http auth can get username from config' '
 | 
			
		||||
	test_config_global "credential.$HTTPD_URL.username" user@host &&
 | 
			
		||||
	>askpass-query &&
 | 
			
		||||
	echo user@host >askpass-response &&
 | 
			
		||||
	git clone "$HTTPD_URL/auth/repo.git" clone-auth-user &&
 | 
			
		||||
	expect_askpass pass user@host
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'configured username does not override URL' '
 | 
			
		||||
	test_config_global "credential.$HTTPD_URL.username" wrong &&
 | 
			
		||||
	>askpass-query &&
 | 
			
		||||
	echo user@host >askpass-response &&
 | 
			
		||||
	git clone "$HTTPD_URL_USER/auth/repo.git" clone-auth-user2 &&
 | 
			
		||||
	expect_askpass pass user@host
 | 
			
		||||
'
 | 
			
		||||
 | 
			
		||||
test_expect_success 'fetch changes via http' '
 | 
			
		||||
 | 
			
		||||
@ -379,6 +379,11 @@ test_config () {
 | 
			
		||||
	git config "$@"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
test_config_global () {
 | 
			
		||||
	test_when_finished "test_unconfig --global '$1'" &&
 | 
			
		||||
	git config --global "$@"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
# Use test_set_prereq to tell that a particular prerequisite is available.
 | 
			
		||||
# The prerequisite can later be checked for in two ways:
 | 
			
		||||
#
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										38
									
								
								test-credential.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								test-credential.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
#include "cache.h"
 | 
			
		||||
#include "credential.h"
 | 
			
		||||
#include "string-list.h"
 | 
			
		||||
 | 
			
		||||
static const char usage_msg[] =
 | 
			
		||||
"test-credential <fill|approve|reject> [helper...]";
 | 
			
		||||
 | 
			
		||||
int main(int argc, const char **argv)
 | 
			
		||||
{
 | 
			
		||||
	const char *op;
 | 
			
		||||
	struct credential c = CREDENTIAL_INIT;
 | 
			
		||||
	int i;
 | 
			
		||||
 | 
			
		||||
	op = argv[1];
 | 
			
		||||
	if (!op)
 | 
			
		||||
		usage(usage_msg);
 | 
			
		||||
	for (i = 2; i < argc; i++)
 | 
			
		||||
		string_list_append(&c.helpers, argv[i]);
 | 
			
		||||
 | 
			
		||||
	if (credential_read(&c, stdin) < 0)
 | 
			
		||||
		die("unable to read credential from stdin");
 | 
			
		||||
 | 
			
		||||
	if (!strcmp(op, "fill")) {
 | 
			
		||||
		credential_fill(&c);
 | 
			
		||||
		if (c.username)
 | 
			
		||||
			printf("username=%s\n", c.username);
 | 
			
		||||
		if (c.password)
 | 
			
		||||
			printf("password=%s\n", c.password);
 | 
			
		||||
	}
 | 
			
		||||
	else if (!strcmp(op, "approve"))
 | 
			
		||||
		credential_approve(&c);
 | 
			
		||||
	else if (!strcmp(op, "reject"))
 | 
			
		||||
		credential_reject(&c);
 | 
			
		||||
	else
 | 
			
		||||
		usage(usage_msg);
 | 
			
		||||
 | 
			
		||||
	return 0;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										56
									
								
								unix-socket.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								unix-socket.c
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
#include "cache.h"
 | 
			
		||||
#include "unix-socket.h"
 | 
			
		||||
 | 
			
		||||
static int unix_stream_socket(void)
 | 
			
		||||
{
 | 
			
		||||
	int fd = socket(AF_UNIX, SOCK_STREAM, 0);
 | 
			
		||||
	if (fd < 0)
 | 
			
		||||
		die_errno("unable to create socket");
 | 
			
		||||
	return fd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
static void unix_sockaddr_init(struct sockaddr_un *sa, const char *path)
 | 
			
		||||
{
 | 
			
		||||
	int size = strlen(path) + 1;
 | 
			
		||||
	if (size > sizeof(sa->sun_path))
 | 
			
		||||
		die("socket path is too long to fit in sockaddr");
 | 
			
		||||
	memset(sa, 0, sizeof(*sa));
 | 
			
		||||
	sa->sun_family = AF_UNIX;
 | 
			
		||||
	memcpy(sa->sun_path, path, size);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int unix_stream_connect(const char *path)
 | 
			
		||||
{
 | 
			
		||||
	int fd;
 | 
			
		||||
	struct sockaddr_un sa;
 | 
			
		||||
 | 
			
		||||
	unix_sockaddr_init(&sa, path);
 | 
			
		||||
	fd = unix_stream_socket();
 | 
			
		||||
	if (connect(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
 | 
			
		||||
		close(fd);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
	return fd;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
int unix_stream_listen(const char *path)
 | 
			
		||||
{
 | 
			
		||||
	int fd;
 | 
			
		||||
	struct sockaddr_un sa;
 | 
			
		||||
 | 
			
		||||
	unix_sockaddr_init(&sa, path);
 | 
			
		||||
	fd = unix_stream_socket();
 | 
			
		||||
 | 
			
		||||
	unlink(path);
 | 
			
		||||
	if (bind(fd, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
 | 
			
		||||
		close(fd);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (listen(fd, 5) < 0) {
 | 
			
		||||
		close(fd);
 | 
			
		||||
		return -1;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return fd;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								unix-socket.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								unix-socket.h
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
#ifndef UNIX_SOCKET_H
 | 
			
		||||
#define UNIX_SOCKET_H
 | 
			
		||||
 | 
			
		||||
int unix_stream_connect(const char *path);
 | 
			
		||||
int unix_stream_listen(const char *path);
 | 
			
		||||
 | 
			
		||||
#endif /* UNIX_SOCKET_H */
 | 
			
		||||
		Reference in New Issue
	
	Block a user