mirror of
https://github.com/git/git.git
synced 2025-03-23 02:16:31 +00:00
Merge branch 'gb/shell-ext'
* gb/shell-ext: shell: Display errors from improperly-formatted command lines shell: Rewrite documentation and improve error message Add sample commands for git-shell Add interactive mode to git-shell for user-friendliness Allow creation of arbitrary git-shell commands
This commit is contained in:
commit
ae893e097e
@ -3,24 +3,30 @@ git-shell(1)
|
||||
|
||||
NAME
|
||||
----
|
||||
git-shell - Restricted login shell for GIT-only SSH access
|
||||
git-shell - Restricted login shell for Git-only SSH access
|
||||
|
||||
|
||||
SYNOPSIS
|
||||
--------
|
||||
'$(git --exec-path)/git-shell' -c <command> <argument>
|
||||
'git shell' [-c <command> <argument>]
|
||||
|
||||
DESCRIPTION
|
||||
-----------
|
||||
This is meant to be used as a login shell for SSH accounts you want
|
||||
to restrict to GIT pull/push access only. It permits execution only
|
||||
of server-side GIT commands implementing the pull/push functionality.
|
||||
The commands can be executed only by the '-c' option; the shell is not
|
||||
interactive.
|
||||
|
||||
Currently, only four commands are permitted to be called, 'git-receive-pack'
|
||||
'git-upload-pack' and 'git-upload-archive' with a single required argument, or
|
||||
'cvs server' (to invoke 'git-cvsserver').
|
||||
A login shell for SSH accounts to provide restricted Git access. When
|
||||
'-c' is given, the program executes <command> non-interactively;
|
||||
<command> can be one of 'git receive-pack', 'git upload-pack', 'git
|
||||
upload-archive', 'cvs server', or a command in COMMAND_DIR. The shell
|
||||
is started in interactive mode when no arguments are given; in this
|
||||
case, COMMAND_DIR must exist, and any of the executables in it can be
|
||||
invoked.
|
||||
|
||||
'cvs server' is a special command which executes git-cvsserver.
|
||||
|
||||
COMMAND_DIR is the path "$HOME/git-shell-commands". The user must have
|
||||
read and execute permissions to the directory in order to execute the
|
||||
programs in it. The programs are executed with a cwd of $HOME, and
|
||||
<argument> is parsed as a command-line string.
|
||||
|
||||
Author
|
||||
------
|
||||
|
18
contrib/git-shell-commands/README
Normal file
18
contrib/git-shell-commands/README
Normal file
@ -0,0 +1,18 @@
|
||||
Sample programs callable through git-shell. Place a directory named
|
||||
'git-shell-commands' in the home directory of a user whose shell is
|
||||
git-shell. Then anyone logging in as that user will be able to run
|
||||
executables in the 'git-shell-commands' directory.
|
||||
|
||||
Provided commands:
|
||||
|
||||
help: Prints out the names of available commands. When run
|
||||
interactively, git-shell will automatically run 'help' on startup,
|
||||
provided it exists.
|
||||
|
||||
list: Displays any bare repository whose name ends with ".git" under
|
||||
user's home directory. No other git repositories are visible,
|
||||
although they might be clonable through git-shell. 'list' is designed
|
||||
to minimize the number of calls to git that must be made in finding
|
||||
available repositories; if your setup has additional repositories that
|
||||
should be user-discoverable, you may wish to modify 'list'
|
||||
accordingly.
|
18
contrib/git-shell-commands/help
Executable file
18
contrib/git-shell-commands/help
Executable file
@ -0,0 +1,18 @@
|
||||
#!/bin/sh
|
||||
|
||||
if tty -s
|
||||
then
|
||||
echo "Run 'help' for help, or 'exit' to leave. Available commands:"
|
||||
else
|
||||
echo "Run 'help' for help. Available commands:"
|
||||
fi
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
for cmd in *
|
||||
do
|
||||
case "$cmd" in
|
||||
help) ;;
|
||||
*) [ -f "$cmd" ] && [ -x "$cmd" ] && echo "$cmd" ;;
|
||||
esac
|
||||
done
|
10
contrib/git-shell-commands/list
Executable file
10
contrib/git-shell-commands/list
Executable file
@ -0,0 +1,10 @@
|
||||
#!/bin/sh
|
||||
|
||||
print_if_bare_repo='
|
||||
if "$(git --git-dir="$1" rev-parse --is-bare-repository)" = true
|
||||
then
|
||||
printf "%s\n" "${1#./}"
|
||||
fi
|
||||
'
|
||||
|
||||
find -type d -name "*.git" -exec sh -c "$print_if_bare_repo" -- \{} \; -prune 2>/dev/null
|
133
shell.c
133
shell.c
@ -2,6 +2,10 @@
|
||||
#include "quote.h"
|
||||
#include "exec_cmd.h"
|
||||
#include "strbuf.h"
|
||||
#include "run-command.h"
|
||||
|
||||
#define COMMAND_DIR "git-shell-commands"
|
||||
#define HELP_COMMAND COMMAND_DIR "/help"
|
||||
|
||||
static int do_generic_cmd(const char *me, char *arg)
|
||||
{
|
||||
@ -33,6 +37,86 @@ static int do_cvs_cmd(const char *me, char *arg)
|
||||
return execv_git_cmd(cvsserver_argv);
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
char *prefix = xmalloc((strlen(prog) + strlen(COMMAND_DIR) + 2));
|
||||
strcpy(prefix, COMMAND_DIR);
|
||||
strcat(prefix, "/");
|
||||
strcat(prefix, prog);
|
||||
return prefix;
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
static void run_shell(void)
|
||||
{
|
||||
int done = 0;
|
||||
static const char *help_argv[] = { HELP_COMMAND, NULL };
|
||||
/* 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;
|
||||
char *split_args;
|
||||
const char **argv;
|
||||
int code;
|
||||
int count;
|
||||
|
||||
fprintf(stderr, "git> ");
|
||||
if (strbuf_getline(&line, stdin, '\n') == EOF) {
|
||||
fprintf(stderr, "\n");
|
||||
strbuf_release(&line);
|
||||
break;
|
||||
}
|
||||
strbuf_trim(&line);
|
||||
rawargs = strbuf_detach(&line, NULL);
|
||||
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);
|
||||
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);
|
||||
}
|
||||
|
||||
static struct commands {
|
||||
const char *name;
|
||||
@ -48,8 +132,10 @@ static struct commands {
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
char *prog;
|
||||
const char **user_argv;
|
||||
struct commands *cmd;
|
||||
int devnull_fd;
|
||||
int count;
|
||||
|
||||
/*
|
||||
* Always open file descriptors 0/1/2 to avoid clobbering files
|
||||
@ -66,17 +152,28 @@ int main(int argc, char **argv)
|
||||
/*
|
||||
* Special hack to pretend to be a CVS server
|
||||
*/
|
||||
if (argc == 2 && !strcmp(argv[1], "cvs server"))
|
||||
if (argc == 2 && !strcmp(argv[1], "cvs server")) {
|
||||
argv--;
|
||||
} else if (argc == 1) {
|
||||
/* Allow the user to run an interactive shell */
|
||||
cd_to_homedir();
|
||||
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.");
|
||||
}
|
||||
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");
|
||||
}
|
||||
|
||||
/*
|
||||
* We do not accept anything but "-c" followed by "cmd arg",
|
||||
* where "cmd" is a very limited subset of git commands.
|
||||
*/
|
||||
else if (argc != 3 || strcmp(argv[1], "-c"))
|
||||
die("What do you think I am? A shell?");
|
||||
|
||||
prog = argv[2];
|
||||
prog = xstrdup(argv[2]);
|
||||
if (!strncmp(prog, "git", 3) && isspace(prog[3]))
|
||||
/* Accept "git foo" as if the caller said "git-foo". */
|
||||
prog[3] = '-';
|
||||
@ -99,5 +196,21 @@ int main(int argc, char **argv)
|
||||
}
|
||||
exit(cmd->exec(cmd->name, arg));
|
||||
}
|
||||
die("unrecognized command '%s'", prog);
|
||||
|
||||
cd_to_homedir();
|
||||
count = split_cmdline(prog, &user_argv);
|
||||
if (count >= 0) {
|
||||
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);
|
||||
die("invalid command format '%s': %s", argv[2],
|
||||
split_cmdline_strerror(count));
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user