22#include "quote.h"
33#include "exec_cmd.h"
44#include "strbuf.h"
5+ #include "run-command.h"
6+
7+ #define COMMAND_DIR "git-shell-commands"
8+ #define HELP_COMMAND COMMAND_DIR "/help"
59
610static int do_generic_cmd (const char * me , char * arg )
711{
@@ -33,6 +37,86 @@ static int do_cvs_cmd(const char *me, char *arg)
3337 return execv_git_cmd (cvsserver_argv );
3438}
3539
40+ static int is_valid_cmd_name (const char * cmd )
41+ {
42+ /* Test command contains no . or / characters */
43+ return cmd [strcspn (cmd , "./" )] == '\0' ;
44+ }
45+
46+ static char * make_cmd (const char * prog )
47+ {
48+ char * prefix = xmalloc ((strlen (prog ) + strlen (COMMAND_DIR ) + 2 ));
49+ strcpy (prefix , COMMAND_DIR );
50+ strcat (prefix , "/" );
51+ strcat (prefix , prog );
52+ return prefix ;
53+ }
54+
55+ static void cd_to_homedir (void )
56+ {
57+ const char * home = getenv ("HOME" );
58+ if (!home )
59+ die ("could not determine user's home directory; HOME is unset" );
60+ if (chdir (home ) == -1 )
61+ die ("could not chdir to user's home directory" );
62+ }
63+
64+ static void run_shell (void )
65+ {
66+ int done = 0 ;
67+ static const char * help_argv [] = { HELP_COMMAND , NULL };
68+ /* Print help if enabled */
69+ run_command_v_opt (help_argv , RUN_SILENT_EXEC_FAILURE );
70+
71+ do {
72+ struct strbuf line = STRBUF_INIT ;
73+ const char * prog ;
74+ char * full_cmd ;
75+ char * rawargs ;
76+ char * split_args ;
77+ const char * * argv ;
78+ int code ;
79+ int count ;
80+
81+ fprintf (stderr , "git> " );
82+ if (strbuf_getline (& line , stdin , '\n' ) == EOF ) {
83+ fprintf (stderr , "\n" );
84+ strbuf_release (& line );
85+ break ;
86+ }
87+ strbuf_trim (& line );
88+ rawargs = strbuf_detach (& line , NULL );
89+ split_args = xstrdup (rawargs );
90+ count = split_cmdline (split_args , & argv );
91+ if (count < 0 ) {
92+ fprintf (stderr , "invalid command format '%s': %s\n" , rawargs ,
93+ split_cmdline_strerror (count ));
94+ free (split_args );
95+ free (rawargs );
96+ continue ;
97+ }
98+
99+ prog = argv [0 ];
100+ if (!strcmp (prog , "" )) {
101+ } else if (!strcmp (prog , "quit" ) || !strcmp (prog , "logout" ) ||
102+ !strcmp (prog , "exit" ) || !strcmp (prog , "bye" )) {
103+ done = 1 ;
104+ } else if (is_valid_cmd_name (prog )) {
105+ full_cmd = make_cmd (prog );
106+ argv [0 ] = full_cmd ;
107+ code = run_command_v_opt (argv , RUN_SILENT_EXEC_FAILURE );
108+ if (code == -1 && errno == ENOENT ) {
109+ fprintf (stderr , "unrecognized command '%s'\n" , prog );
110+ }
111+ free (full_cmd );
112+ } else {
113+ fprintf (stderr , "invalid command format '%s'\n" , prog );
114+ }
115+
116+ free (argv );
117+ free (rawargs );
118+ } while (!done );
119+ }
36120
37121static struct commands {
38122 const char * name ;
@@ -48,8 +132,10 @@ static struct commands {
48132int main (int argc , char * * argv )
49133{
50134 char * prog ;
135+ const char * * user_argv ;
51136 struct commands * cmd ;
52137 int devnull_fd ;
138+ int count ;
53139
54140 /*
55141 * Always open file descriptors 0/1/2 to avoid clobbering files
@@ -66,17 +152,28 @@ int main(int argc, char **argv)
66152 /*
67153 * Special hack to pretend to be a CVS server
68154 */
69- if (argc == 2 && !strcmp (argv [1 ], "cvs server" ))
155+ if (argc == 2 && !strcmp (argv [1 ], "cvs server" )) {
70156 argv -- ;
157+ } else if (argc == 1 ) {
158+ /* Allow the user to run an interactive shell */
159+ cd_to_homedir ();
160+ if (access (COMMAND_DIR , R_OK | X_OK ) == -1 ) {
161+ die ("Interactive git shell is not enabled.\n"
162+ "hint: ~/" COMMAND_DIR " should exist "
163+ "and have read and execute access." );
164+ }
165+ run_shell ();
166+ exit (0 );
167+ } else if (argc != 3 || strcmp (argv [1 ], "-c" )) {
168+ /*
169+ * We do not accept any other modes except "-c" followed by
170+ * "cmd arg", where "cmd" is a very limited subset of git
171+ * commands or a command in the COMMAND_DIR
172+ */
173+ die ("Run with no arguments or with -c cmd" );
174+ }
71175
72- /*
73- * We do not accept anything but "-c" followed by "cmd arg",
74- * where "cmd" is a very limited subset of git commands.
75- */
76- else if (argc != 3 || strcmp (argv [1 ], "-c" ))
77- die ("What do you think I am? A shell?" );
78-
79- prog = argv [2 ];
176+ prog = xstrdup (argv [2 ]);
80177 if (!strncmp (prog , "git" , 3 ) && isspace (prog [3 ]))
81178 /* Accept "git foo" as if the caller said "git-foo". */
82179 prog [3 ] = '-' ;
@@ -99,5 +196,21 @@ int main(int argc, char **argv)
99196 }
100197 exit (cmd -> exec (cmd -> name , arg ));
101198 }
102- die ("unrecognized command '%s'" , prog );
199+
200+ cd_to_homedir ();
201+ count = split_cmdline (prog , & user_argv );
202+ if (count >= 0 ) {
203+ if (is_valid_cmd_name (user_argv [0 ])) {
204+ prog = make_cmd (user_argv [0 ]);
205+ user_argv [0 ] = prog ;
206+ execv (user_argv [0 ], (char * const * ) user_argv );
207+ }
208+ free (prog );
209+ free (user_argv );
210+ die ("unrecognized command '%s'" , argv [2 ]);
211+ } else {
212+ free (prog );
213+ die ("invalid command format '%s': %s" , argv [2 ],
214+ split_cmdline_strerror (count ));
215+ }
103216}
0 commit comments