Skip to content

Windows: add a configuration location to be shared among different Git implementations #148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Documentation/config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 8 additions & 0 deletions Documentation/git-config.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion Documentation/git.txt
Original file line number Diff line number Diff line change
Expand Up @@ -566,7 +566,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
Expand Down
119 changes: 119 additions & 0 deletions compat/mingw.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
#include "win32.h"
#include <conio.h>
#include <wchar.h>
#include <aclapi.h>
#include <sddl.h>
#include "../strbuf.h"
#include "../run-command.h"
#include "../cache.h"
Expand Down Expand Up @@ -2450,3 +2452,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;
}
2 changes: 2 additions & 0 deletions compat/mingw.h
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,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"
Expand Down
15 changes: 10 additions & 5 deletions config.c
Original file line number Diff line number Diff line change
Expand Up @@ -1676,11 +1676,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))
Expand Down
4 changes: 4 additions & 0 deletions git-compat-util.h
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,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
Expand Down