diff --git a/Documentation/config.txt b/Documentation/config.txt index e3f5bc3396d0c7..7f14ed2e9fa620 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -7,7 +7,9 @@ the Git commands' behavior. The files `.git/config` and optionally repository are used to store the configuration for that repository, and `$HOME/.gitconfig` is used to store a per-user configuration as fallback values for the `.git/config` file. The file `/etc/gitconfig` -can be used to store a system-wide default configuration. +can be used to store a system-wide default configuration. On Windows, +configuration can also be stored in `C:\ProgramData\Git\config`; This +file will be used also by libgit2-based software. The configuration variables are used by both the Git plumbing and the porcelains. The variables are divided into sections, wherein diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index ff9310f9588a4a..db5d80c171f811 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -272,8 +272,16 @@ FILES If not set explicitly with `--file`, there are four files where 'git config' will search for configuration options: +$PROGRAMDATA/Git/config:: + (Windows-only) System-wide configuration file shared with other Git + implementations. Typically `$PROGRAMDATA` points to `C:\ProgramData`. + $(prefix)/etc/gitconfig:: System-wide configuration file. + (Windows-only) This file contains only the settings which are + specific for this installation of Git for Windows and which should + not be shared with other Git implementations like JGit, libgit2. + `--system` will select this file. $XDG_CONFIG_HOME/git/config:: Second user-specific configuration file. If $XDG_CONFIG_HOME is not set diff --git a/Documentation/git.txt b/Documentation/git.txt index 9b82564d1aa9c0..11e032c0d877fa 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -588,7 +588,8 @@ for further details. `GIT_CONFIG_NOSYSTEM`:: Whether to skip reading settings from the system-wide - `$(prefix)/etc/gitconfig` file. This environment variable can + `$(prefix)/etc/gitconfig` file (and on Windows, also from the + `%PROGRAMDATA%\Git\config` file). This environment variable can be used along with `$HOME` and `$XDG_CONFIG_HOME` to create a predictable environment for a picky script, or you can set it temporarily to avoid using a buggy `/etc/gitconfig` file while diff --git a/compat/mingw.c b/compat/mingw.c index 738f0a826a51b8..08129c14dd5154 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -2,6 +2,8 @@ #include "win32.h" #include #include +#include +#include #include "../strbuf.h" #include "../run-command.h" #include "../cache.h" @@ -2541,3 +2543,120 @@ int uname(struct utsname *buf) "%u", (v >> 16) & 0x7fff); return 0; } + +/* + * Verify that the file in question is owned by an administrator or system + * account, or at least by the current user. + * + * This function returns 1 if successful, 0 if the file is not owned by any of + * these, or -1 on error. + */ +static int validate_system_file_ownership(const char *path) +{ + WCHAR wpath[MAX_PATH]; + PSID owner_sid = NULL; + PSECURITY_DESCRIPTOR descriptor = NULL; + HANDLE token; + TOKEN_USER* info = NULL; + DWORD err, len; + int ret; + + if (xutftowcs_path(wpath, path) < 0) + return -1; + + err = GetNamedSecurityInfoW(wpath, SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | + DACL_SECURITY_INFORMATION, + &owner_sid, NULL, NULL, NULL, &descriptor); + + /* if the file does not exist, it does not have a valid owner */ + if (err == ERROR_FILE_NOT_FOUND || err == ERROR_PATH_NOT_FOUND) { + ret = 0; + owner_sid = NULL; + goto finish_validation; + } + + if (err != ERROR_SUCCESS) { + ret = error(_("failed to validate '%s' (%ld)"), path, err); + owner_sid = NULL; + goto finish_validation; + } + + if (!IsValidSid(owner_sid)) { + ret = error(_("invalid owner: '%s'"), path); + goto finish_validation; + } + + if (IsWellKnownSid(owner_sid, WinBuiltinAdministratorsSid) || + IsWellKnownSid(owner_sid, WinLocalSystemSid)) { + ret = 1; + goto finish_validation; + } + + /* Obtain current user's SID */ + if (OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &token) && + !GetTokenInformation(token, TokenUser, NULL, 0, &len)) { + info = xmalloc((size_t)len); + if (!GetTokenInformation(token, TokenUser, info, len, &len)) + FREE_AND_NULL(info); + } + + if (!info) + ret = 0; + else { + ret = EqualSid(owner_sid, info->User.Sid) ? 1 : 0; + free(info); + } + +finish_validation: + if (!ret && owner_sid) { +#define MAX_NAME_OR_DOMAIN 256 + wchar_t owner_name[MAX_NAME_OR_DOMAIN]; + wchar_t owner_domain[MAX_NAME_OR_DOMAIN]; + wchar_t *p = NULL; + DWORD size = MAX_NAME_OR_DOMAIN; + SID_NAME_USE type; + char name[3 * MAX_NAME_OR_DOMAIN + 1]; + + if (!LookupAccountSidW(NULL, owner_sid, owner_name, &size, + owner_domain, &size, &type) || + xwcstoutf(name, owner_name, ARRAY_SIZE(name)) < 0) { + if (!ConvertSidToStringSidW(owner_sid, &p)) + strlcpy(name, "(unknown)", ARRAY_SIZE(name)); + else { + if (xwcstoutf(name, p, ARRAY_SIZE(name)) < 0) + strlcpy(name, "(some user)", + ARRAY_SIZE(name)); + LocalFree(p); + } + } + + warning(_("'%s' has a dubious owner: '%s'.\n" + "For security reasons, it is therefore ignored.\n" + "To fix this, please transfer ownership to an " + "admininstrator."), + path, name); + } + + if (descriptor) + LocalFree(descriptor); + + return ret; +} + +const char *program_data_config(void) +{ + static struct strbuf path = STRBUF_INIT; + static unsigned initialized; + + if (!initialized) { + const char *env = mingw_getenv("PROGRAMDATA"); + if (env) { + strbuf_addf(&path, "%s/Git/config", env); + if (validate_system_file_ownership(path.buf) != 1) + strbuf_setlen(&path, 0); + } + initialized = 1; + } + return *path.buf ? path.buf : NULL; +} diff --git a/compat/mingw.h b/compat/mingw.h index a03e40e6e2a6cb..d243ebefe82cbe 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -445,6 +445,8 @@ static inline void convert_slashes(char *path) #define PATH_SEP ';' extern char *mingw_query_user_email(void); #define query_user_email mingw_query_user_email +extern const char *program_data_config(void); +#define git_program_data_config program_data_config #if !defined(__MINGW64_VERSION_MAJOR) && (!defined(_MSC_VER) || _MSC_VER < 1800) #define PRIuMAX "I64u" #define PRId64 "I64d" diff --git a/config.c b/config.c index 3900e4947be92b..5b04a0cc350087 100644 --- a/config.c +++ b/config.c @@ -1711,11 +1711,16 @@ static int do_git_config_sequence(const struct config_options *opts, repo_config = NULL; current_parsing_scope = CONFIG_SCOPE_SYSTEM; - if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, - opts->system_gently ? - ACCESS_EACCES_OK : 0)) - ret += git_config_from_file(fn, git_etc_gitconfig(), - data); + if (git_config_system()) { + int flags = opts->system_gently ? ACCESS_EACCES_OK : 0; + const char *program_data = git_program_data_config(); + const char *etc = git_etc_gitconfig(); + + if (program_data && !access_or_die(program_data, R_OK, flags)) + ret += git_config_from_file(fn, program_data, data); + if (!access_or_die(etc, R_OK, flags)) + ret += git_config_from_file(fn, etc, data); + } current_parsing_scope = CONFIG_SCOPE_GLOBAL; if (xdg_config && !access_or_die(xdg_config, R_OK, ACCESS_EACCES_OK)) diff --git a/git-compat-util.h b/git-compat-util.h index 83be89de0aac7c..ecc8d15c2c6a90 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -421,6 +421,10 @@ static inline char *git_find_last_dir_sep(const char *path) #endif #endif +#ifndef git_program_data_config +#define git_program_data_config() NULL +#endif + #if defined(__HP_cc) && (__HP_cc >= 61000) #define NORETURN __attribute__((noreturn)) #define NORETURN_PTR