2005-10-24 05:30:45 +08:00
|
|
|
#include "cache.h"
|
|
|
|
#include "quote.h"
|
2006-01-11 10:12:17 +08:00
|
|
|
#include "exec_cmd.h"
|
2007-10-09 22:33:25 +08:00
|
|
|
#include "strbuf.h"
|
2010-07-28 15:43:03 +08:00
|
|
|
#include "run-command.h"
|
2005-10-24 05:30:45 +08:00
|
|
|
|
2010-07-29 08:31:01 +08:00
|
|
|
#define COMMAND_DIR "git-shell-commands"
|
2010-07-28 15:43:03 +08:00
|
|
|
#define HELP_COMMAND COMMAND_DIR "/help"
|
shell: new no-interactive-login command to print a custom message
If I disable git-shell's interactive mode by removing the
~/git-shell-commands directory, attempts to ssh in to the service
produce a message intended for the administrator:
$ ssh git@myserver
fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access.
$
That is helpful for the new admin who is wondering "What? Why isn't
the git-shell I just set up working?", but once the site setup is
complete, it would be better to give the user a friendly hint that she
is on the right track, like GitHub does.
Hi <username>! You've successfully authenticated, but
GitHub does not provide shell access.
An appropriate greeting might even include more complex dynamic
information, like gitolite's list of repositories the user has access
to. Add support for a ~/git-shell-commands/no-interactive-login
command that generates an arbitrary greeting. When the user tries to
log in:
* If the file ~/git-shell-commands/no-interactive-login exists,
run no-interactive-login to let the server say what it likes,
then hang up.
* Otherwise, if ~/git-shell-commands/ is present, start an
interactive read-eval-print loop.
* Otherwise, print the usual configuration hint and hang up.
Reported-by: Ethan Reesor <firelizzard@gmail.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Improved-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-03-10 06:00:11 +08:00
|
|
|
#define NOLOGIN_COMMAND COMMAND_DIR "/no-interactive-login"
|
2010-07-29 08:31:01 +08:00
|
|
|
|
2005-10-24 05:30:45 +08:00
|
|
|
static int do_generic_cmd(const char *me, char *arg)
|
|
|
|
{
|
|
|
|
const char *my_argv[4];
|
|
|
|
|
2008-07-22 03:19:52 +08:00
|
|
|
setup_path();
|
shell: disallow repo names beginning with dash
When a remote server uses git-shell, the client side will
connect to it like:
ssh server "git-upload-pack 'foo.git'"
and we literally exec ("git-upload-pack", "foo.git"). In
early versions of upload-pack and receive-pack, we took a
repository argument and nothing else. But over time they
learned to accept dashed options. If the user passes a
repository name that starts with a dash, the results are
confusing at best (we complain of a bogus option instead of
a non-existent repository) and malicious at worst (the user
can start an interactive pager via "--help").
We could pass "--" to the sub-process to make sure the
user's argument is interpreted as a branch name. I.e.:
git-upload-pack -- -foo.git
But adding "--" automatically would make us inconsistent
with a normal shell (i.e., when git-shell is not in use),
where "-foo.git" would still be an error. For that case, the
client would have to specify the "--", but they can't do so
reliably, as existing versions of git-shell do not allow
more than a single argument.
The simplest thing is to simply disallow "-" at the start of
the repo name argument. This hasn't worked either with or
without git-shell since version 1.0.0, and nobody has
complained.
Note that this patch just applies to do_generic_cmd(), which
runs upload-pack, receive-pack, and upload-archive. There
are two other types of commands that git-shell runs:
- do_cvs_cmd(), but this already restricts the argument to
be the literal string "server"
- admin-provided commands in the git-shell-commands
directory. We'll pass along arbitrary arguments there,
so these commands could have similar problems. But these
commands might actually understand dashed arguments, so
we cannot just block them here. It's up to the writer of
the commands to make sure they are safe. With great
power comes great responsibility.
Reported-by: Timo Schmid <tschmid@ernw.de>
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2017-04-29 20:36:44 +08:00
|
|
|
if (!arg || !(arg = sq_dequote(arg)) || *arg == '-')
|
2005-10-24 05:30:45 +08:00
|
|
|
die("bad argument");
|
2013-12-01 04:55:40 +08:00
|
|
|
if (!starts_with(me, "git-"))
|
2006-01-11 10:12:17 +08:00
|
|
|
die("bad command");
|
2005-10-24 05:30:45 +08:00
|
|
|
|
2006-01-11 10:12:17 +08:00
|
|
|
my_argv[0] = me + 4;
|
2005-10-24 05:30:45 +08:00
|
|
|
my_argv[1] = arg;
|
|
|
|
my_argv[2] = NULL;
|
|
|
|
|
2006-03-05 18:47:29 +08:00
|
|
|
return execv_git_cmd(my_argv);
|
2005-10-24 05:30:45 +08:00
|
|
|
}
|
|
|
|
|
2010-07-29 08:31:01 +08:00
|
|
|
static int is_valid_cmd_name(const char *cmd)
|
|
|
|
{
|
|
|
|
/* Test command contains no . or / characters */
|
|
|
|
return cmd[strcspn(cmd, "./")] == '\0';
|
|
|
|
}
|
|
|
|
|
|
|
|
static char *make_cmd(const char *prog)
|
|
|
|
{
|
2014-06-20 05:26:56 +08:00
|
|
|
return xstrfmt("%s/%s", COMMAND_DIR, prog);
|
2010-07-29 08:31:01 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void cd_to_homedir(void)
|
|
|
|
{
|
|
|
|
const char *home = getenv("HOME");
|
|
|
|
if (!home)
|
|
|
|
die("could not determine user's home directory; HOME is unset");
|
|
|
|
if (chdir(home) == -1)
|
|
|
|
die("could not chdir to user's home directory");
|
|
|
|
}
|
2007-10-09 22:33:25 +08:00
|
|
|
|
2010-07-28 15:43:03 +08:00
|
|
|
static void run_shell(void)
|
|
|
|
{
|
|
|
|
int done = 0;
|
|
|
|
static const char *help_argv[] = { HELP_COMMAND, NULL };
|
shell: new no-interactive-login command to print a custom message
If I disable git-shell's interactive mode by removing the
~/git-shell-commands directory, attempts to ssh in to the service
produce a message intended for the administrator:
$ ssh git@myserver
fatal: Interactive git shell is not enabled.
hint: ~/git-shell-commands should exist and have read and execute access.
$
That is helpful for the new admin who is wondering "What? Why isn't
the git-shell I just set up working?", but once the site setup is
complete, it would be better to give the user a friendly hint that she
is on the right track, like GitHub does.
Hi <username>! You've successfully authenticated, but
GitHub does not provide shell access.
An appropriate greeting might even include more complex dynamic
information, like gitolite's list of repositories the user has access
to. Add support for a ~/git-shell-commands/no-interactive-login
command that generates an arbitrary greeting. When the user tries to
log in:
* If the file ~/git-shell-commands/no-interactive-login exists,
run no-interactive-login to let the server say what it likes,
then hang up.
* Otherwise, if ~/git-shell-commands/ is present, start an
interactive read-eval-print loop.
* Otherwise, print the usual configuration hint and hang up.
Reported-by: Ethan Reesor <firelizzard@gmail.com>
Signed-off-by: Jonathan Nieder <jrnieder@gmail.com>
Improved-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2013-03-10 06:00:11 +08:00
|
|
|
|
|
|
|
if (!access(NOLOGIN_COMMAND, F_OK)) {
|
|
|
|
/* Interactive login disabled. */
|
|
|
|
const char *argv[] = { NOLOGIN_COMMAND, NULL };
|
|
|
|
int status;
|
|
|
|
|
|
|
|
status = run_command_v_opt(argv, 0);
|
|
|
|
if (status < 0)
|
|
|
|
exit(127);
|
|
|
|
exit(status);
|
|
|
|
}
|
|
|
|
|
2010-07-28 15:43:03 +08:00
|
|
|
/* Print help if enabled */
|
|
|
|
run_command_v_opt(help_argv, RUN_SILENT_EXEC_FAILURE);
|
|
|
|
|
|
|
|
do {
|
|
|
|
struct strbuf line = STRBUF_INIT;
|
|
|
|
const char *prog;
|
|
|
|
char *full_cmd;
|
|
|
|
char *rawargs;
|
2010-08-27 13:36:13 +08:00
|
|
|
char *split_args;
|
2010-07-28 15:43:03 +08:00
|
|
|
const char **argv;
|
|
|
|
int code;
|
2010-08-27 13:36:13 +08:00
|
|
|
int count;
|
2010-07-28 15:43:03 +08:00
|
|
|
|
|
|
|
fprintf(stderr, "git> ");
|
2016-01-14 07:31:17 +08:00
|
|
|
if (strbuf_getline_lf(&line, stdin) == EOF) {
|
2010-07-28 15:43:03 +08:00
|
|
|
fprintf(stderr, "\n");
|
|
|
|
strbuf_release(&line);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
strbuf_trim(&line);
|
|
|
|
rawargs = strbuf_detach(&line, NULL);
|
2010-08-27 13:36:13 +08:00
|
|
|
split_args = xstrdup(rawargs);
|
|
|
|
count = split_cmdline(split_args, &argv);
|
|
|
|
if (count < 0) {
|
|
|
|
fprintf(stderr, "invalid command format '%s': %s\n", rawargs,
|
|
|
|
split_cmdline_strerror(count));
|
|
|
|
free(split_args);
|
2010-07-28 15:43:03 +08:00
|
|
|
free(rawargs);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
prog = argv[0];
|
|
|
|
if (!strcmp(prog, "")) {
|
|
|
|
} else if (!strcmp(prog, "quit") || !strcmp(prog, "logout") ||
|
|
|
|
!strcmp(prog, "exit") || !strcmp(prog, "bye")) {
|
|
|
|
done = 1;
|
|
|
|
} else if (is_valid_cmd_name(prog)) {
|
|
|
|
full_cmd = make_cmd(prog);
|
|
|
|
argv[0] = full_cmd;
|
|
|
|
code = run_command_v_opt(argv, RUN_SILENT_EXEC_FAILURE);
|
|
|
|
if (code == -1 && errno == ENOENT) {
|
|
|
|
fprintf(stderr, "unrecognized command '%s'\n", prog);
|
|
|
|
}
|
|
|
|
free(full_cmd);
|
|
|
|
} else {
|
|
|
|
fprintf(stderr, "invalid command format '%s'\n", prog);
|
|
|
|
}
|
|
|
|
|
|
|
|
free(argv);
|
|
|
|
free(rawargs);
|
|
|
|
} while (!done);
|
|
|
|
}
|
|
|
|
|
2005-10-24 05:30:45 +08:00
|
|
|
static struct commands {
|
|
|
|
const char *name;
|
|
|
|
int (*exec)(const char *me, char *arg);
|
|
|
|
} cmd_list[] = {
|
|
|
|
{ "git-receive-pack", do_generic_cmd },
|
|
|
|
{ "git-upload-pack", do_generic_cmd },
|
2009-04-10 03:58:52 +08:00
|
|
|
{ "git-upload-archive", do_generic_cmd },
|
2005-10-24 05:30:45 +08:00
|
|
|
{ NULL },
|
|
|
|
};
|
|
|
|
|
add an extra level of indirection to main()
There are certain startup tasks that we expect every git
process to do. In some cases this is just to improve the
quality of the program (e.g., setting up gettext()). In
others it is a requirement for using certain functions in
libgit.a (e.g., system_path() expects that you have called
git_extract_argv0_path()).
Most commands are builtins and are covered by the git.c
version of main(). However, there are still a few external
commands that use their own main(). Each of these has to
remember to include the correct startup sequence, and we are
not always consistent.
Rather than just fix the inconsistencies, let's make this
harder to get wrong by providing a common main() that can
run this standard startup.
We basically have two options to do this:
- the compat/mingw.h file already does something like this by
adding a #define that replaces the definition of main with a
wrapper that calls mingw_startup().
The upside is that the code in each program doesn't need
to be changed at all; it's rewritten on the fly by the
preprocessor.
The downside is that it may make debugging of the startup
sequence a bit more confusing, as the preprocessor is
quietly inserting new code.
- the builtin functions are all of the form cmd_foo(),
and git.c's main() calls them.
This is much more explicit, which may make things more
obvious to somebody reading the code. It's also more
flexible (because of course we have to figure out _which_
cmd_foo() to call).
The downside is that each of the builtins must define
cmd_foo(), instead of just main().
This patch chooses the latter option, preferring the more
explicit approach, even though it is more invasive. We
introduce a new file common-main.c, with the "real" main. It
expects to call cmd_main() from whatever other objects it is
linked against.
We link common-main.o against anything that links against
libgit.a, since we know that such programs will need to do
this setup. Note that common-main.o can't actually go inside
libgit.a, as the linker would not pick up its main()
function automatically (it has no callers).
The rest of the patch is just adjusting all of the various
external programs (mostly in t/helper) to use cmd_main().
I've provided a global declaration for cmd_main(), which
means that all of the programs also need to match its
signature. In particular, many functions need to switch to
"const char **" instead of "char **" for argv. This effect
ripples out to a few other variables and functions, as well.
This makes the patch even more invasive, but the end result
is much better. We should be treating argv strings as const
anyway, and now all programs conform to the same signature
(which also matches the way builtins are defined).
Signed-off-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
2016-07-01 13:58:58 +08:00
|
|
|
int cmd_main(int argc, const char **argv)
|
2005-10-24 05:30:45 +08:00
|
|
|
{
|
|
|
|
char *prog;
|
2010-07-29 08:31:01 +08:00
|
|
|
const char **user_argv;
|
2005-10-24 05:30:45 +08:00
|
|
|
struct commands *cmd;
|
2010-08-27 13:36:13 +08:00
|
|
|
int count;
|
2008-08-27 23:20:35 +08:00
|
|
|
|
2007-12-02 14:16:19 +08:00
|
|
|
/*
|
|
|
|
* Special hack to pretend to be a CVS server
|
|
|
|
*/
|
2010-07-28 15:43:03 +08:00
|
|
|
if (argc == 2 && !strcmp(argv[1], "cvs server")) {
|
2007-10-09 22:33:25 +08:00
|
|
|
argv--;
|
2010-07-28 15:43:03 +08:00
|
|
|
} else if (argc == 1) {
|
|
|
|
/* Allow the user to run an interactive shell */
|
|
|
|
cd_to_homedir();
|
2010-08-24 13:36:51 +08:00
|
|
|
if (access(COMMAND_DIR, R_OK | X_OK) == -1) {
|
|
|
|
die("Interactive git shell is not enabled.\n"
|
|
|
|
"hint: ~/" COMMAND_DIR " should exist "
|
|
|
|
"and have read and execute access.");
|
|
|
|
}
|
2010-07-28 15:43:03 +08:00
|
|
|
run_shell();
|
|
|
|
exit(0);
|
|
|
|
} else if (argc != 3 || strcmp(argv[1], "-c")) {
|
|
|
|
/*
|
|
|
|
* We do not accept any other modes except "-c" followed by
|
|
|
|
* "cmd arg", where "cmd" is a very limited subset of git
|
|
|
|
* commands or a command in the COMMAND_DIR
|
|
|
|
*/
|
|
|
|
die("Run with no arguments or with -c cmd");
|
|
|
|
}
|
2005-10-24 05:30:45 +08:00
|
|
|
|
2010-07-29 08:31:01 +08:00
|
|
|
prog = xstrdup(argv[2]);
|
2007-12-02 14:16:19 +08:00
|
|
|
if (!strncmp(prog, "git", 3) && isspace(prog[3]))
|
|
|
|
/* Accept "git foo" as if the caller said "git-foo". */
|
|
|
|
prog[3] = '-';
|
|
|
|
|
2005-10-24 05:30:45 +08:00
|
|
|
for (cmd = cmd_list ; cmd->name ; cmd++) {
|
|
|
|
int len = strlen(cmd->name);
|
|
|
|
char *arg;
|
|
|
|
if (strncmp(cmd->name, prog, len))
|
|
|
|
continue;
|
|
|
|
arg = NULL;
|
|
|
|
switch (prog[len]) {
|
|
|
|
case '\0':
|
|
|
|
arg = NULL;
|
|
|
|
break;
|
|
|
|
case ' ':
|
|
|
|
arg = prog + len + 1;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
exit(cmd->exec(cmd->name, arg));
|
|
|
|
}
|
2010-07-29 08:31:01 +08:00
|
|
|
|
|
|
|
cd_to_homedir();
|
2010-08-27 13:36:13 +08:00
|
|
|
count = split_cmdline(prog, &user_argv);
|
|
|
|
if (count >= 0) {
|
2010-07-29 08:31:01 +08:00
|
|
|
if (is_valid_cmd_name(user_argv[0])) {
|
|
|
|
prog = make_cmd(user_argv[0]);
|
|
|
|
user_argv[0] = prog;
|
|
|
|
execv(user_argv[0], (char *const *) user_argv);
|
|
|
|
}
|
|
|
|
free(prog);
|
|
|
|
free(user_argv);
|
|
|
|
die("unrecognized command '%s'", argv[2]);
|
|
|
|
} else {
|
|
|
|
free(prog);
|
2010-08-27 13:36:13 +08:00
|
|
|
die("invalid command format '%s': %s", argv[2],
|
|
|
|
split_cmdline_strerror(count));
|
2010-07-29 08:31:01 +08:00
|
|
|
}
|
2005-10-24 05:30:45 +08:00
|
|
|
}
|