threads / discuss / 24546

[PATCHv4 1/3] Allow creation of arbitrary git-shell commands

Subject: [PATCHv4 1/3] Allow creation of arbitrary git-shell commands

## tl;dr

7 messages between Jul 28, 2010 and Jul 29, 2010.

replies: 6people: 2as markdown or json

Greg Brockman· Jul 28, 2010, 07:43 UTC · lore

[PATCHv4 0/3] Updated patch series for providing mechanism to list available repositories

This revised patch series should address all comments given thus far. The specific changes made between v3 and v4 are documented in the patch emails.

Thanks,
Greg
Greg Brockman· Jul 28, 2010, 07:43 UTC · re: Greg Brockman · lore

This provides a mechanism for the server to expose custom functionality to clients. My particular use case is that I would like a way of discovering all repositories available for cloning. A client that clones via

  git clone user@example.com
can invoke a command by
  ssh user@example.com $command
Signed-off-by: Greg Brockman <gdb@mit.edu>
---
 shell.c |   43 +++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 41 insertions(+), 2 deletions(-)

This patch differs from its v3 version by the addition of a 'cd_to_homedir' method and a call to it prior to running any commands in git-shell-commands. This ensures that all commands are run out of $HOME/git-shell-commands.

diff --git a/shell.c b/shell.c
index e4864e0..88b29aa 100644
--- a/shell.c
+++ b/shell.c
@@ -3,6 +3,8 @@
 #include "exec_cmd.h"
 #include "strbuf.h"
 
+#define COMMAND_DIR "git-shell-commands"
+
 static int do_generic_cmd(const char *me, char *arg)
 {
 	const char *my_argv[4];
@@ -33,6 +35,28 @@ 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) {
+	struct passwd *pw = getpwuid(geteuid());
+	if (pw == NULL)
+		die("could not retrieve user's passwd entry");
+	if (chdir(pw->pw_dir) == -1)
+		die("could not chdir to user's home directory");
+}
 
 static struct commands {
 	const char *name;
@@ -48,6 +72,7 @@ static struct commands {
 int main(int argc, char **argv)
 {
 	char *prog;
+	const char **user_argv;
 	struct commands *cmd;
 	int devnull_fd;
 
@@ -76,7 +101,7 @@ int main(int argc, char **argv)
 	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 +124,19 @@ int main(int argc, char **argv)
 		}
 		exit(cmd->exec(cmd->name, arg));
 	}
-	die("unrecognized command '%s'", prog);
+
+	cd_to_homedir();
+	if (split_cmdline(prog, &user_argv) != -1) {
+		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'", argv[2]);
+	}
 }
-- 
1.7.0.4
Greg Brockman· Jul 29, 2010, 00:31 UTC · re: Greg Brockman · lore

Re: [PATCHv4 1/3] Allow creation of arbitrary git-shell commands

This provides a mechanism for the server to expose custom functionality to clients. My particular use case is that I would like a way of discovering all repositories available for cloning. A client that clones via

  git clone user@example.com
can invoke a command by
  ssh user@example.com $command
Signed-off-by: Greg Brockman <gdb@mit.edu>
---
 shell.c |   44 ++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 42 insertions(+), 2 deletions(-)

As discussed on-list, it should be fine to trust the value of HOME. This patch differs from the previous only in the implementation of cd_to_homedir.

diff --git a/shell.c b/shell.c
index e4864e0..1e6077d 100644
--- a/shell.c
+++ b/shell.c
@@ -3,6 +3,8 @@
 #include "exec_cmd.h"
 #include "strbuf.h"

+#define COMMAND_DIR "git-shell-commands"
+
 static int do_generic_cmd(const char *me, char *arg)
 {
 	const char *my_argv[4];
@@ -33,6 +35,29 @@ 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)
+{
+	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 struct commands {
 	const char *name;
@@ -48,6 +73,7 @@ static struct commands {
 int main(int argc, char **argv)
 {
 	char *prog;
+	const char **user_argv;
 	struct commands *cmd;
 	int devnull_fd;

@@ -76,7 +102,7 @@ int main(int argc, char **argv)
 	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 +125,19 @@ int main(int argc, char **argv)
 		}
 		exit(cmd->exec(cmd->name, arg));
 	}
-	die("unrecognized command '%s'", prog);
+
+	cd_to_homedir();
+	if (split_cmdline(prog, &user_argv) != -1) {
+		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'", argv[2]);
+	}
 }
-- 
1.7.0.4
Greg Brockman· Jul 29, 2010, 00:37 UTC · re: Greg Brockman · lore

Re: [PATCHv4 1/3] Allow creation of arbitrary git-shell commands

Err, actually it would probably be a good idea to s/char *home = getenv("HOME");/const char *home = getenv("HOME");/

Greg
On Wed, Jul 28, 2010 at 5:31 PM, Greg Brockman <gdb@mit.edu> wrote:
Show 104 quoted lines
> This provides a mechanism for the server to expose custom
> functionality to clients.  My particular use case is that I would like
> a way of discovering all repositories available for cloning.  A
> client that clones via
>
>  git clone user@example.com
>
> can invoke a command by
>
>  ssh user@example.com $command
>
> Signed-off-by: Greg Brockman <gdb@mit.edu>
> ---
>  shell.c |   44 ++++++++++++++++++++++++++++++++++++++++++--
>  1 files changed, 42 insertions(+), 2 deletions(-)
>
> As discussed on-list, it should be fine to trust the value of HOME.
> This patch differs from the previous only in the implementation of
> cd_to_homedir.
>
> diff --git a/shell.c b/shell.c
> index e4864e0..1e6077d 100644
> --- a/shell.c
> +++ b/shell.c
> @@ -3,6 +3,8 @@
>  #include "exec_cmd.h"
>  #include "strbuf.h"
>
> +#define COMMAND_DIR "git-shell-commands"
> +
>  static int do_generic_cmd(const char *me, char *arg)
>  {
>        const char *my_argv[4];
> @@ -33,6 +35,29 @@ 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)
> +{
> +       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 struct commands {
>        const char *name;
> @@ -48,6 +73,7 @@ static struct commands {
>  int main(int argc, char **argv)
>  {
>        char *prog;
> +       const char **user_argv;
>        struct commands *cmd;
>        int devnull_fd;
>
> @@ -76,7 +102,7 @@ int main(int argc, char **argv)
>        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 +125,19 @@ int main(int argc, char **argv)
>                }
>                exit(cmd->exec(cmd->name, arg));
>        }
> -       die("unrecognized command '%s'", prog);
> +
> +       cd_to_homedir();
> +       if (split_cmdline(prog, &user_argv) != -1) {
> +               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'", argv[2]);
> +       }
>  }
> --
> 1.7.0.4
>
Greg Brockman· Jul 28, 2010, 07:43 UTC · re: Greg Brockman · lore

[PATCHv4 2/3] Add interactive mode to git-shell for user-friendliness

Signed-off-by: Greg Brockman <gdb@mit.edu>
---
 shell.c |   76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 70 insertions(+), 6 deletions(-)

This patch differs from v3 by the addition of a 'cd_to_homedir' call prior to spawning the shell.

diff --git a/shell.c b/shell.c
index 88b29aa..a40b839 100644
--- a/shell.c
+++ b/shell.c
@@ -2,8 +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)
 {
@@ -58,6 +60,56 @@ static void cd_to_homedir(void) {
 		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;
+		const char **argv;
+		int code;
+
+		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);
+		if (split_cmdline(rawargs, &argv) == -1) {
+			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;
 	int (*exec)(const char *me, char *arg);
@@ -91,15 +143,27 @@ 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--;
-
+	}
 	/*
-	 * We do not accept anything but "-c" followed by "cmd arg",
-	 * where "cmd" is a very limited subset of git commands.
+	 * Allow the user to run an interactive shell
 	 */
-	else if (argc != 3 || strcmp(argv[1], "-c"))
-		die("What do you think I am? A shell?");
+	else if (argc == 1) {
+		cd_to_homedir();
+		if (access(COMMAND_DIR, R_OK | X_OK) == -1)
+			die("Sorry, the interactive git-shell is not enabled");
+		run_shell();
+		exit(0);
+	}
+	/*
+	 * 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
+	 */
+	else if (argc != 3 || strcmp(argv[1], "-c")) {
+		die("Run with no arguments or with -c cmd");
+	}
 
 	prog = xstrdup(argv[2]);
 	if (!strncmp(prog, "git", 3) && isspace(prog[3]))
-- 
1.7.0.4
Greg Brockman· Jul 28, 2010, 07:43 UTC · re: Greg Brockman · lore

[PATCHv4 3/3] Add sample commands for git-shell

Provide a 'list' command to view available bare repositories ending in .git and a 'help command to display usage. Also add documentation in a README

Signed-off-by: Greg Brockman <gdb@mit.edu>
---
 contrib/git-shell-commands/README |   18 ++++++++++++++++++
 contrib/git-shell-commands/help   |   16 ++++++++++++++++
 contrib/git-shell-commands/list   |   10 ++++++++++
 3 files changed, 44 insertions(+), 0 deletions(-)
 create mode 100644 contrib/git-shell-commands/README
 create mode 100755 contrib/git-shell-commands/help
 create mode 100755 contrib/git-shell-commands/list
This differs from v3 in that:
* the 'find' command in 'list' discards its stderr.  This is useful for
  avoiding noise from inaccessible directories or non-git repositories with
  name matching '*.git'.
* the README does not mention that commands will be run from $(pwd)/git-shell-commands,
  because they will now be run from $HOME/git-shell-commands.
diff --git a/contrib/git-shell-commands/README b/contrib/git-shell-commands/README
new file mode 100644
index 0000000..438463b
--- /dev/null
+++ b/contrib/git-shell-commands/README
@@ -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.
diff --git a/contrib/git-shell-commands/help b/contrib/git-shell-commands/help
new file mode 100755
index 0000000..a43fcd6
--- /dev/null
+++ b/contrib/git-shell-commands/help
@@ -0,0 +1,16 @@
+#!/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
diff --git a/contrib/git-shell-commands/list b/contrib/git-shell-commands/list
new file mode 100755
index 0000000..ffc5ac4
--- /dev/null
+++ b/contrib/git-shell-commands/list
@@ -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
-- 
1.7.0.4
Junio C Hamano· Jul 28, 2010, 17:23 UTC · re: Greg Brockman · lore

Re: [PATCHv4 0/3] Updated patch series for providing mechanism to list available repositories

Greg Brockman <gdb@MIT.EDU> writes:
> This revised patch series should address all comments given thus far.
> The specific changes made between v3 and v4 are documented in the
> patch emails.
Thanks, all; will queue.

← back to recent threads