connect: tell server that the client understands v1

Teach the connection logic to tell a serve that it understands protocol v1. This is done in 2 different ways for the builtin transports, both of which ultimately set 'GIT_PROTOCOL' to 'version=1' on the server. 1. git:// A normal request to git-daemon is structured as "command path/to/repo\0host=..\0" and due to a bug introduced in 49ba83fb6 (Add virtualization support to git-daemon, 2006-09-19) we aren't able to place any extra arguments (separated by NULs) besides the host otherwise the parsing of those arguments would enter an infinite loop. This bug was fixed in 73bb33a94 (daemon: Strictly parse the "extra arg" part of the command, 2009-06-04) but a check was put in place to disallow extra arguments so that new clients wouldn't trigger this bug in older servers. In order to get around this limitation git-daemon was taught to recognize additional request arguments hidden behind a second NUL byte. Requests can then be structured like: "command path/to/repo\0host=..\0\0version=1\0key=value\0". git-daemon can then parse out the extra arguments and set 'GIT_PROTOCOL' accordingly. By placing these extra arguments behind a second NUL byte we can skirt around both the infinite loop bug in 49ba83fb6 (Add virtualization support to git-daemon, 2006-09-19) as well as the explicit disallowing of extra arguments introduced in 73bb33a94 (daemon: Strictly parse the "extra arg" part of the command, 2009-06-04) because both of these versions of git-daemon check for a single NUL byte after the host argument before terminating the argument parsing. 2. ssh://, file:// Set 'GIT_PROTOCOL' environment variable with the desired protocol version. With the file:// transport, 'GIT_PROTOCOL' can be set explicitly in the locally running git-upload-pack or git-receive-pack processes. With the ssh:// transport and OpenSSH compliant ssh programs, 'GIT_PROTOCOL' can be sent across ssh by using '-o SendEnv=GIT_PROTOCOL' and having the server whitelist this environment variable. Signed-off-by: Brandon Williams <bmwill@google.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Brandon Williams committed Oct 16, 2017 at 10:55 UTC 0c2f0d27038f2aa98082d8af35c53b2ec7e9ad92
2 files changed +255 -5
connect.c
+32 -5
@@ -871,6 +871,7 @@ struct child_process *git_connect(int fd[2], const char *url,
871 printf("Diag: path=%s\n", path ? path : "NULL");
872 conn = NULL;
873 } else if (protocol == PROTO_GIT) {
874 + struct strbuf request = STRBUF_INIT;
875 /*
876 * Set up virtual host information based on where we will
877 * connect, unless the user has overridden us in
@@ -898,13 +899,25 @@ struct child_process *git_connect(int fd[2], const char *url,
899 * Note: Do not add any other headers here! Doing so
900 * will cause older git-daemon servers to crash.
901 */
901 - packet_write_fmt(fd[1],
902 - "%s %s%chost=%s%c",
903 - prog, path, 0,
904 - target_host, 0);
902 + strbuf_addf(&request,
903 + "%s %s%chost=%s%c",
904 + prog, path, 0,
905 + target_host, 0);
906 +
907 + /* If using a new version put that stuff here after a second null byte */
908 + if (get_protocol_version_config() > 0) {
909 + strbuf_addch(&request, '\0');
910 + strbuf_addf(&request, "version=%d%c",
911 + get_protocol_version_config(), '\0');
912 + }
913 +
914 + packet_write(fd[1], request.buf, request.len);
915 +
916 free(target_host);
917 + strbuf_release(&request);
918 } else {
919 struct strbuf cmd = STRBUF_INIT;
920 + const char *const *var;
921
922 conn = xmalloc(sizeof(*conn));
923 child_process_init(conn);
@@ -917,7 +930,9 @@ struct child_process *git_connect(int fd[2], const char *url,
930 sq_quote_buf(&cmd, path);
931
932 /* remove repo-local variables from the environment */
920 - conn->env = local_repo_env;
933 + for (var = local_repo_env; *var; var++)
934 + argv_array_push(&conn->env_array, *var);
935 +
936 conn->use_shell = 1;
937 conn->in = conn->out = -1;
938 if (protocol == PROTO_SSH) {
@@ -971,6 +986,14 @@ struct child_process *git_connect(int fd[2], const char *url,
986 }
987
988 argv_array_push(&conn->args, ssh);
989 +
990 + if (get_protocol_version_config() > 0) {
991 + argv_array_push(&conn->args, "-o");
992 + argv_array_push(&conn->args, "SendEnv=" GIT_PROTOCOL_ENVIRONMENT);
993 + argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
994 + get_protocol_version_config());
995 + }
996 +
997 if (flags & CONNECT_IPV4)
998 argv_array_push(&conn->args, "-4");
999 else if (flags & CONNECT_IPV6)
@@ -985,6 +1008,10 @@ struct child_process *git_connect(int fd[2], const char *url,
1008 argv_array_push(&conn->args, ssh_host);
1009 } else {
1010 transport_check_allowed("file");
1011 + if (get_protocol_version_config() > 0) {
1012 + argv_array_pushf(&conn->env_array, GIT_PROTOCOL_ENVIRONMENT "=version=%d",
1013 + get_protocol_version_config());
1014 + }
1015 }
1016 argv_array_push(&conn->args, cmd.buf);
1017
t/t5700-protocol-v1.sh new
+223
@@ -0,0 +1,223 @@
1 +#!/bin/sh
2 +
3 +test_description='test git wire-protocol transition'
4 +
5 +TEST_NO_CREATE_REPO=1
6 +
7 +. ./test-lib.sh
8 +
9 +# Test protocol v1 with 'git://' transport
10 +#
11 +. "$TEST_DIRECTORY"/lib-git-daemon.sh
12 +start_git_daemon --export-all --enable=receive-pack
13 +daemon_parent=$GIT_DAEMON_DOCUMENT_ROOT_PATH/parent
14 +
15 +test_expect_success 'create repo to be served by git-daemon' '
16 + git init "$daemon_parent" &&
17 + test_commit -C "$daemon_parent" one
18 +'
19 +
20 +test_expect_success 'clone with git:// using protocol v1' '
21 + GIT_TRACE_PACKET=1 git -c protocol.version=1 \
22 + clone "$GIT_DAEMON_URL/parent" daemon_child 2>log &&
23 +
24 + git -C daemon_child log -1 --format=%s >actual &&
25 + git -C "$daemon_parent" log -1 --format=%s >expect &&
26 + test_cmp expect actual &&
27 +
28 + # Client requested to use protocol v1
29 + grep "clone> .*\\\0\\\0version=1\\\0$" log &&
30 + # Server responded using protocol v1
31 + grep "clone< version 1" log
32 +'
33 +
34 +test_expect_success 'fetch with git:// using protocol v1' '
35 + test_commit -C "$daemon_parent" two &&
36 +
37 + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
38 + fetch 2>log &&
39 +
40 + git -C daemon_child log -1 --format=%s origin/master >actual &&
41 + git -C "$daemon_parent" log -1 --format=%s >expect &&
42 + test_cmp expect actual &&
43 +
44 + # Client requested to use protocol v1
45 + grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
46 + # Server responded using protocol v1
47 + grep "fetch< version 1" log
48 +'
49 +
50 +test_expect_success 'pull with git:// using protocol v1' '
51 + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
52 + pull 2>log &&
53 +
54 + git -C daemon_child log -1 --format=%s >actual &&
55 + git -C "$daemon_parent" log -1 --format=%s >expect &&
56 + test_cmp expect actual &&
57 +
58 + # Client requested to use protocol v1
59 + grep "fetch> .*\\\0\\\0version=1\\\0$" log &&
60 + # Server responded using protocol v1
61 + grep "fetch< version 1" log
62 +'
63 +
64 +test_expect_success 'push with git:// using protocol v1' '
65 + test_commit -C daemon_child three &&
66 +
67 + # Push to another branch, as the target repository has the
68 + # master branch checked out and we cannot push into it.
69 + GIT_TRACE_PACKET=1 git -C daemon_child -c protocol.version=1 \
70 + push origin HEAD:client_branch 2>log &&
71 +
72 + git -C daemon_child log -1 --format=%s >actual &&
73 + git -C "$daemon_parent" log -1 --format=%s client_branch >expect &&
74 + test_cmp expect actual &&
75 +
76 + # Client requested to use protocol v1
77 + grep "push> .*\\\0\\\0version=1\\\0$" log &&
78 + # Server responded using protocol v1
79 + grep "push< version 1" log
80 +'
81 +
82 +stop_git_daemon
83 +
84 +# Test protocol v1 with 'file://' transport
85 +#
86 +test_expect_success 'create repo to be served by file:// transport' '
87 + git init file_parent &&
88 + test_commit -C file_parent one
89 +'
90 +
91 +test_expect_success 'clone with file:// using protocol v1' '
92 + GIT_TRACE_PACKET=1 git -c protocol.version=1 \
93 + clone "file://$(pwd)/file_parent" file_child 2>log &&
94 +
95 + git -C file_child log -1 --format=%s >actual &&
96 + git -C file_parent log -1 --format=%s >expect &&
97 + test_cmp expect actual &&
98 +
99 + # Server responded using protocol v1
100 + grep "clone< version 1" log
101 +'
102 +
103 +test_expect_success 'fetch with file:// using protocol v1' '
104 + test_commit -C file_parent two &&
105 +
106 + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
107 + fetch 2>log &&
108 +
109 + git -C file_child log -1 --format=%s origin/master >actual &&
110 + git -C file_parent log -1 --format=%s >expect &&
111 + test_cmp expect actual &&
112 +
113 + # Server responded using protocol v1
114 + grep "fetch< version 1" log
115 +'
116 +
117 +test_expect_success 'pull with file:// using protocol v1' '
118 + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
119 + pull 2>log &&
120 +
121 + git -C file_child log -1 --format=%s >actual &&
122 + git -C file_parent log -1 --format=%s >expect &&
123 + test_cmp expect actual &&
124 +
125 + # Server responded using protocol v1
126 + grep "fetch< version 1" log
127 +'
128 +
129 +test_expect_success 'push with file:// using protocol v1' '
130 + test_commit -C file_child three &&
131 +
132 + # Push to another branch, as the target repository has the
133 + # master branch checked out and we cannot push into it.
134 + GIT_TRACE_PACKET=1 git -C file_child -c protocol.version=1 \
135 + push origin HEAD:client_branch 2>log &&
136 +
137 + git -C file_child log -1 --format=%s >actual &&
138 + git -C file_parent log -1 --format=%s client_branch >expect &&
139 + test_cmp expect actual &&
140 +
141 + # Server responded using protocol v1
142 + grep "push< version 1" log
143 +'
144 +
145 +# Test protocol v1 with 'ssh://' transport
146 +#
147 +test_expect_success 'setup ssh wrapper' '
148 + GIT_SSH="$GIT_BUILD_DIR/t/helper/test-fake-ssh" &&
149 + export GIT_SSH &&
150 + export TRASH_DIRECTORY &&
151 + >"$TRASH_DIRECTORY"/ssh-output
152 +'
153 +
154 +expect_ssh () {
155 + test_when_finished '(cd "$TRASH_DIRECTORY" && rm -f ssh-expect && >ssh-output)' &&
156 + echo "ssh: -o SendEnv=GIT_PROTOCOL myhost $1 '$PWD/ssh_parent'" >"$TRASH_DIRECTORY/ssh-expect" &&
157 + (cd "$TRASH_DIRECTORY" && test_cmp ssh-expect ssh-output)
158 +}
159 +
160 +test_expect_success 'create repo to be served by ssh:// transport' '
161 + git init ssh_parent &&
162 + test_commit -C ssh_parent one
163 +'
164 +
165 +test_expect_success 'clone with ssh:// using protocol v1' '
166 + GIT_TRACE_PACKET=1 git -c protocol.version=1 \
167 + clone "ssh://myhost:$(pwd)/ssh_parent" ssh_child 2>log &&
168 + expect_ssh git-upload-pack &&
169 +
170 + git -C ssh_child log -1 --format=%s >actual &&
171 + git -C ssh_parent log -1 --format=%s >expect &&
172 + test_cmp expect actual &&
173 +
174 + # Server responded using protocol v1
175 + grep "clone< version 1" log
176 +'
177 +
178 +test_expect_success 'fetch with ssh:// using protocol v1' '
179 + test_commit -C ssh_parent two &&
180 +
181 + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
182 + fetch 2>log &&
183 + expect_ssh git-upload-pack &&
184 +
185 + git -C ssh_child log -1 --format=%s origin/master >actual &&
186 + git -C ssh_parent log -1 --format=%s >expect &&
187 + test_cmp expect actual &&
188 +
189 + # Server responded using protocol v1
190 + grep "fetch< version 1" log
191 +'
192 +
193 +test_expect_success 'pull with ssh:// using protocol v1' '
194 + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
195 + pull 2>log &&
196 + expect_ssh git-upload-pack &&
197 +
198 + git -C ssh_child log -1 --format=%s >actual &&
199 + git -C ssh_parent log -1 --format=%s >expect &&
200 + test_cmp expect actual &&
201 +
202 + # Server responded using protocol v1
203 + grep "fetch< version 1" log
204 +'
205 +
206 +test_expect_success 'push with ssh:// using protocol v1' '
207 + test_commit -C ssh_child three &&
208 +
209 + # Push to another branch, as the target repository has the
210 + # master branch checked out and we cannot push into it.
211 + GIT_TRACE_PACKET=1 git -C ssh_child -c protocol.version=1 \
212 + push origin HEAD:client_branch 2>log &&
213 + expect_ssh git-receive-pack &&
214 +
215 + git -C ssh_child log -1 --format=%s >actual &&
216 + git -C ssh_parent log -1 --format=%s client_branch >expect &&
217 + test_cmp expect actual &&
218 +
219 + # Server responded using protocol v1
220 + grep "push< version 1" log
221 +'
222 +
223 +test_done