shell.con commit Allow creation of arbitrary git-shell commands (2dbc887)
   1#include "cache.h"
   2#include "quote.h"
   3#include "exec_cmd.h"
   4#include "strbuf.h"
   5
   6#define COMMAND_DIR "git-shell-commands"
   7
   8static int do_generic_cmd(const char *me, char *arg)
   9{
  10        const char *my_argv[4];
  11
  12        setup_path();
  13        if (!arg || !(arg = sq_dequote(arg)))
  14                die("bad argument");
  15        if (prefixcmp(me, "git-"))
  16                die("bad command");
  17
  18        my_argv[0] = me + 4;
  19        my_argv[1] = arg;
  20        my_argv[2] = NULL;
  21
  22        return execv_git_cmd(my_argv);
  23}
  24
  25static int do_cvs_cmd(const char *me, char *arg)
  26{
  27        const char *cvsserver_argv[3] = {
  28                "cvsserver", "server", NULL
  29        };
  30
  31        if (!arg || strcmp(arg, "server"))
  32                die("git-cvsserver only handles server: %s", arg);
  33
  34        setup_path();
  35        return execv_git_cmd(cvsserver_argv);
  36}
  37
  38static int is_valid_cmd_name(const char *cmd)
  39{
  40        /* Test command contains no . or / characters */
  41        return cmd[strcspn(cmd, "./")] == '\0';
  42}
  43
  44static char *make_cmd(const char *prog)
  45{
  46        char *prefix = xmalloc((strlen(prog) + strlen(COMMAND_DIR) + 2));
  47        strcpy(prefix, COMMAND_DIR);
  48        strcat(prefix, "/");
  49        strcat(prefix, prog);
  50        return prefix;
  51}
  52
  53static void cd_to_homedir(void)
  54{
  55        const char *home = getenv("HOME");
  56        if (!home)
  57                die("could not determine user's home directory; HOME is unset");
  58        if (chdir(home) == -1)
  59                die("could not chdir to user's home directory");
  60}
  61
  62static struct commands {
  63        const char *name;
  64        int (*exec)(const char *me, char *arg);
  65} cmd_list[] = {
  66        { "git-receive-pack", do_generic_cmd },
  67        { "git-upload-pack", do_generic_cmd },
  68        { "git-upload-archive", do_generic_cmd },
  69        { "cvs", do_cvs_cmd },
  70        { NULL },
  71};
  72
  73int main(int argc, char **argv)
  74{
  75        char *prog;
  76        const char **user_argv;
  77        struct commands *cmd;
  78        int devnull_fd;
  79
  80        /*
  81         * Always open file descriptors 0/1/2 to avoid clobbering files
  82         * in die().  It also avoids not messing up when the pipes are
  83         * dup'ed onto stdin/stdout/stderr in the child processes we spawn.
  84         */
  85        devnull_fd = open("/dev/null", O_RDWR);
  86        while (devnull_fd >= 0 && devnull_fd <= 2)
  87                devnull_fd = dup(devnull_fd);
  88        if (devnull_fd == -1)
  89                die_errno("opening /dev/null failed");
  90        close (devnull_fd);
  91
  92        /*
  93         * Special hack to pretend to be a CVS server
  94         */
  95        if (argc == 2 && !strcmp(argv[1], "cvs server"))
  96                argv--;
  97
  98        /*
  99         * We do not accept anything but "-c" followed by "cmd arg",
 100         * where "cmd" is a very limited subset of git commands.
 101         */
 102        else if (argc != 3 || strcmp(argv[1], "-c"))
 103                die("What do you think I am? A shell?");
 104
 105        prog = xstrdup(argv[2]);
 106        if (!strncmp(prog, "git", 3) && isspace(prog[3]))
 107                /* Accept "git foo" as if the caller said "git-foo". */
 108                prog[3] = '-';
 109
 110        for (cmd = cmd_list ; cmd->name ; cmd++) {
 111                int len = strlen(cmd->name);
 112                char *arg;
 113                if (strncmp(cmd->name, prog, len))
 114                        continue;
 115                arg = NULL;
 116                switch (prog[len]) {
 117                case '\0':
 118                        arg = NULL;
 119                        break;
 120                case ' ':
 121                        arg = prog + len + 1;
 122                        break;
 123                default:
 124                        continue;
 125                }
 126                exit(cmd->exec(cmd->name, arg));
 127        }
 128
 129        cd_to_homedir();
 130        if (split_cmdline(prog, &user_argv) != -1) {
 131                if (is_valid_cmd_name(user_argv[0])) {
 132                        prog = make_cmd(user_argv[0]);
 133                        user_argv[0] = prog;
 134                        execv(user_argv[0], (char *const *) user_argv);
 135                }
 136                free(prog);
 137                free(user_argv);
 138                die("unrecognized command '%s'", argv[2]);
 139        } else {
 140                free(prog);
 141                die("invalid command format '%s'", argv[2]);
 142        }
 143}