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:
@ -3,24 +3,30 @@ git-shell(1)
|
|||||||
|
|
||||||
NAME
|
NAME
|
||||||
----
|
----
|
||||||
git-shell - Restricted login shell for GIT-only SSH access
|
git-shell - Restricted login shell for Git-only SSH access
|
||||||
|
|
||||||
|
|
||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
'$(git --exec-path)/git-shell' -c <command> <argument>
|
'git shell' [-c <command> <argument>]
|
||||||
|
|
||||||
DESCRIPTION
|
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'
|
A login shell for SSH accounts to provide restricted Git access. When
|
||||||
'git-upload-pack' and 'git-upload-archive' with a single required argument, or
|
'-c' is given, the program executes <command> non-interactively;
|
||||||
'cvs server' (to invoke 'git-cvsserver').
|
<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
|
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 "quote.h"
|
||||||
#include "exec_cmd.h"
|
#include "exec_cmd.h"
|
||||||
#include "strbuf.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)
|
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);
|
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 {
|
static struct commands {
|
||||||
const char *name;
|
const char *name;
|
||||||
@ -48,8 +132,10 @@ static struct commands {
|
|||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
char *prog;
|
char *prog;
|
||||||
|
const char **user_argv;
|
||||||
struct commands *cmd;
|
struct commands *cmd;
|
||||||
int devnull_fd;
|
int devnull_fd;
|
||||||
|
int count;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Always open file descriptors 0/1/2 to avoid clobbering files
|
* 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
|
* 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--;
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
prog = xstrdup(argv[2]);
|
||||||
* 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];
|
|
||||||
if (!strncmp(prog, "git", 3) && isspace(prog[3]))
|
if (!strncmp(prog, "git", 3) && isspace(prog[3]))
|
||||||
/* Accept "git foo" as if the caller said "git-foo". */
|
/* Accept "git foo" as if the caller said "git-foo". */
|
||||||
prog[3] = '-';
|
prog[3] = '-';
|
||||||
@ -99,5 +196,21 @@ int main(int argc, char **argv)
|
|||||||
}
|
}
|
||||||
exit(cmd->exec(cmd->name, arg));
|
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));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user