Git fork
at reftables-rust 231 lines 5.6 kB view raw
1#include "git-compat-util.h" 2#include "quote.h" 3#include "exec-cmd.h" 4#include "strbuf.h" 5#include "run-command.h" 6#include "alias.h" 7 8#define COMMAND_DIR "git-shell-commands" 9#define HELP_COMMAND COMMAND_DIR "/help" 10#define NOLOGIN_COMMAND COMMAND_DIR "/no-interactive-login" 11 12static int do_generic_cmd(const char *me, char *arg) 13{ 14 const char *my_argv[4]; 15 16 setup_path(); 17 if (!arg || !(arg = sq_dequote(arg)) || *arg == '-') 18 die("bad argument"); 19 if (!skip_prefix(me, "git-", &me)) 20 die("bad command"); 21 22 my_argv[0] = me; 23 my_argv[1] = arg; 24 my_argv[2] = NULL; 25 26 return execv_git_cmd(my_argv); 27} 28 29static int is_valid_cmd_name(const char *cmd) 30{ 31 /* Test command contains no . or / characters */ 32 return cmd[strcspn(cmd, "./")] == '\0'; 33} 34 35static char *make_cmd(const char *prog) 36{ 37 return xstrfmt("%s/%s", COMMAND_DIR, prog); 38} 39 40static void cd_to_homedir(void) 41{ 42 const char *home = getenv("HOME"); 43 if (!home) 44 die("could not determine user's home directory; HOME is unset"); 45 if (chdir(home) == -1) 46 die("could not chdir to user's home directory"); 47} 48 49#define MAX_INTERACTIVE_COMMAND (4*1024*1024) 50 51static void run_shell(void) 52{ 53 int done = 0; 54 struct child_process help_cmd = CHILD_PROCESS_INIT; 55 56 if (!access(NOLOGIN_COMMAND, F_OK)) { 57 /* Interactive login disabled. */ 58 struct child_process nologin_cmd = CHILD_PROCESS_INIT; 59 int status; 60 61 strvec_push(&nologin_cmd.args, NOLOGIN_COMMAND); 62 status = run_command(&nologin_cmd); 63 if (status < 0) 64 exit(127); 65 exit(status); 66 } 67 68 /* Print help if enabled */ 69 help_cmd.silent_exec_failure = 1; 70 strvec_push(&help_cmd.args, HELP_COMMAND); 71 run_command(&help_cmd); 72 73 do { 74 const char *prog; 75 char *full_cmd; 76 char *rawargs; 77 size_t len; 78 char *split_args; 79 const char **argv; 80 int code; 81 int count; 82 83 fprintf(stderr, "git> "); 84 85 /* 86 * Avoid using a strbuf or git_read_line_interactively() here. 87 * We don't want to allocate arbitrary amounts of memory on 88 * behalf of a possibly untrusted client, and we're subject to 89 * OS limits on command length anyway. 90 */ 91 fflush(stdout); 92 rawargs = xmalloc(MAX_INTERACTIVE_COMMAND); 93 if (!fgets(rawargs, MAX_INTERACTIVE_COMMAND, stdin)) { 94 fprintf(stderr, "\n"); 95 free(rawargs); 96 break; 97 } 98 len = strlen(rawargs); 99 100 /* 101 * If we truncated due to our input buffer size, reject the 102 * command. That's better than running bogus input, and 103 * there's a good chance it's just malicious garbage anyway. 104 */ 105 if (len >= MAX_INTERACTIVE_COMMAND - 1) 106 die("invalid command format: input too long"); 107 108 if (len > 0 && rawargs[len - 1] == '\n') { 109 if (--len > 0 && rawargs[len - 1] == '\r') 110 --len; 111 rawargs[len] = '\0'; 112 } 113 114 split_args = xstrdup(rawargs); 115 count = split_cmdline(split_args, &argv); 116 if (count < 0) { 117 fprintf(stderr, "invalid command format '%s': %s\n", rawargs, 118 split_cmdline_strerror(count)); 119 free(split_args); 120 free(rawargs); 121 continue; 122 } 123 124 prog = argv[0]; 125 if (!strcmp(prog, "")) { 126 } else if (!strcmp(prog, "quit") || !strcmp(prog, "logout") || 127 !strcmp(prog, "exit") || !strcmp(prog, "bye")) { 128 done = 1; 129 } else if (is_valid_cmd_name(prog)) { 130 struct child_process cmd = CHILD_PROCESS_INIT; 131 132 full_cmd = make_cmd(prog); 133 argv[0] = full_cmd; 134 cmd.silent_exec_failure = 1; 135 strvec_pushv(&cmd.args, argv); 136 code = run_command(&cmd); 137 if (code == -1 && errno == ENOENT) { 138 fprintf(stderr, "unrecognized command '%s'\n", prog); 139 } 140 free(full_cmd); 141 } else { 142 fprintf(stderr, "invalid command format '%s'\n", prog); 143 } 144 145 free(argv); 146 free(split_args); 147 free(rawargs); 148 } while (!done); 149} 150 151static struct commands { 152 const char *name; 153 int (*exec)(const char *me, char *arg); 154} cmd_list[] = { 155 { "git-receive-pack", do_generic_cmd }, 156 { "git-upload-pack", do_generic_cmd }, 157 { "git-upload-archive", do_generic_cmd }, 158 { NULL }, 159}; 160 161int cmd_main(int argc, const char **argv) 162{ 163 char *prog; 164 const char **user_argv; 165 struct commands *cmd; 166 int count; 167 168 /* 169 * Special hack to pretend to be a CVS server 170 */ 171 if (argc == 2 && !strcmp(argv[1], "cvs server")) { 172 argv--; 173 } else if (argc == 1) { 174 /* Allow the user to run an interactive shell */ 175 cd_to_homedir(); 176 if (access(COMMAND_DIR, R_OK | X_OK) == -1) { 177 die("Interactive git shell is not enabled.\n" 178 "hint: ~/" COMMAND_DIR " should exist " 179 "and have read and execute access."); 180 } 181 run_shell(); 182 exit(0); 183 } else if (argc != 3 || strcmp(argv[1], "-c")) { 184 /* 185 * We do not accept any other modes except "-c" followed by 186 * "cmd arg", where "cmd" is a very limited subset of git 187 * commands or a command in the COMMAND_DIR 188 */ 189 die("Run with no arguments or with -c cmd"); 190 } 191 192 prog = xstrdup(argv[2]); 193 if (!strncmp(prog, "git", 3) && isspace(prog[3])) 194 /* Accept "git foo" as if the caller said "git-foo". */ 195 prog[3] = '-'; 196 197 for (cmd = cmd_list ; cmd->name ; cmd++) { 198 int len = strlen(cmd->name); 199 char *arg; 200 if (strncmp(cmd->name, prog, len)) 201 continue; 202 arg = NULL; 203 switch (prog[len]) { 204 case '\0': 205 arg = NULL; 206 break; 207 case ' ': 208 arg = prog + len + 1; 209 break; 210 default: 211 continue; 212 } 213 return cmd->exec(cmd->name, arg); 214 } 215 216 cd_to_homedir(); 217 count = split_cmdline(prog, &user_argv); 218 if (count >= 0) { 219 if (is_valid_cmd_name(user_argv[0])) { 220 char *cmd = make_cmd(user_argv[0]); 221 execv(cmd, (char *const *) user_argv); 222 } 223 free(prog); 224 free(user_argv); 225 die("unrecognized command '%s'", argv[2]); 226 } else { 227 free(prog); 228 die("invalid command format '%s': %s", argv[2], 229 split_cmdline_strerror(count)); 230 } 231}