Code

Allow creation of arbitrary git-shell commands
[git.git] / shell.c
1 #include "cache.h"
2 #include "quote.h"
3 #include "exec_cmd.h"
4 #include "strbuf.h"
6 #define COMMAND_DIR "git-shell-commands"
8 static int do_generic_cmd(const char *me, char *arg)
9 {
10         const char *my_argv[4];
12         setup_path();
13         if (!arg || !(arg = sq_dequote(arg)))
14                 die("bad argument");
15         if (prefixcmp(me, "git-"))
16                 die("bad command");
18         my_argv[0] = me + 4;
19         my_argv[1] = arg;
20         my_argv[2] = NULL;
22         return execv_git_cmd(my_argv);
23 }
25 static int do_cvs_cmd(const char *me, char *arg)
26 {
27         const char *cvsserver_argv[3] = {
28                 "cvsserver", "server", NULL
29         };
31         if (!arg || strcmp(arg, "server"))
32                 die("git-cvsserver only handles server: %s", arg);
34         setup_path();
35         return execv_git_cmd(cvsserver_argv);
36 }
38 static int is_valid_cmd_name(const char *cmd)
39 {
40         /* Test command contains no . or / characters */
41         return cmd[strcspn(cmd, "./")] == '\0';
42 }
44 static 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 }
53 static 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 }
62 static 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 };
73 int main(int argc, char **argv)
74 {
75         char *prog;
76         const char **user_argv;
77         struct commands *cmd;
78         int devnull_fd;
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);
92         /*
93          * Special hack to pretend to be a CVS server
94          */
95         if (argc == 2 && !strcmp(argv[1], "cvs server"))
96                 argv--;
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?");
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] = '-';
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         }
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         }