160 def badresponse(): |
162 def badresponse(): |
161 msg = _('no suitable response from remote hg') |
163 msg = _('no suitable response from remote hg') |
162 hint = ui.config('ui', 'ssherrorhint') |
164 hint = ui.config('ui', 'ssherrorhint') |
163 raise error.RepoError(msg, hint=hint) |
165 raise error.RepoError(msg, hint=hint) |
164 |
166 |
165 # The handshake consists of sending 2 wire protocol commands: |
167 # The handshake consists of sending wire protocol commands in reverse |
166 # ``hello`` and ``between``. |
168 # order of protocol implementation and then sniffing for a response |
167 # |
169 # to one of them. |
168 # The ``hello`` command (which was introduced in Mercurial 0.9.1) |
170 # |
169 # instructs the server to advertise its capabilities. |
171 # Those commands (from oldest to newest) are: |
170 # |
172 # |
171 # The ``between`` command (which has existed in all Mercurial servers |
173 # ``between`` |
172 # for as long as SSH support has existed), asks for the set of revisions |
174 # Asks for the set of revisions between a pair of revisions. Command |
173 # between a pair of revisions. |
175 # present in all Mercurial server implementations. |
|
176 # |
|
177 # ``hello`` |
|
178 # Instructs the server to advertise its capabilities. Introduced in |
|
179 # Mercurial 0.9.1. |
|
180 # |
|
181 # ``upgrade`` |
|
182 # Requests upgrade from default transport protocol version 1 to |
|
183 # a newer version. Introduced in Mercurial 4.6 as an experimental |
|
184 # feature. |
174 # |
185 # |
175 # The ``between`` command is issued with a request for the null |
186 # The ``between`` command is issued with a request for the null |
176 # range. If the remote is a Mercurial server, this request will |
187 # range. If the remote is a Mercurial server, this request will |
177 # generate a specific response: ``1\n\n``. This represents the |
188 # generate a specific response: ``1\n\n``. This represents the |
178 # wire protocol encoded value for ``\n``. We look for ``1\n\n`` |
189 # wire protocol encoded value for ``\n``. We look for ``1\n\n`` |
184 # value. If the server doesn't support ``hello`` (which should be |
195 # value. If the server doesn't support ``hello`` (which should be |
185 # rare), that line will be ``0\n``. Otherwise, the value will contain |
196 # rare), that line will be ``0\n``. Otherwise, the value will contain |
186 # RFC 822 like lines. Of these, the ``capabilities:`` line contains |
197 # RFC 822 like lines. Of these, the ``capabilities:`` line contains |
187 # the capabilities of the server. |
198 # the capabilities of the server. |
188 # |
199 # |
|
200 # The ``upgrade`` command isn't really a command in the traditional |
|
201 # sense of version 1 of the transport because it isn't using the |
|
202 # proper mechanism for formatting insteads: instead, it just encodes |
|
203 # arguments on the line, delimited by spaces. |
|
204 # |
|
205 # The ``upgrade`` line looks like ``upgrade <token> <capabilities>``. |
|
206 # If the server doesn't support protocol upgrades, it will reply to |
|
207 # this line with ``0\n``. Otherwise, it emits an |
|
208 # ``upgraded <token> <protocol>`` line to both stdout and stderr. |
|
209 # Content immediately following this line describes additional |
|
210 # protocol and server state. |
|
211 # |
189 # In addition to the responses to our command requests, the server |
212 # In addition to the responses to our command requests, the server |
190 # may emit "banner" output on stdout. SSH servers are allowed to |
213 # may emit "banner" output on stdout. SSH servers are allowed to |
191 # print messages to stdout on login. Issuing commands on connection |
214 # print messages to stdout on login. Issuing commands on connection |
192 # allows us to flush this banner output from the server by scanning |
215 # allows us to flush this banner output from the server by scanning |
193 # for output to our well-known ``between`` command. Of course, if |
216 # for output to our well-known ``between`` command. Of course, if |
194 # the banner contains ``1\n\n``, this will throw off our detection. |
217 # the banner contains ``1\n\n``, this will throw off our detection. |
195 |
218 |
196 requestlog = ui.configbool('devel', 'debug.peer-request') |
219 requestlog = ui.configbool('devel', 'debug.peer-request') |
|
220 |
|
221 # Generate a random token to help identify responses to version 2 |
|
222 # upgrade request. |
|
223 token = bytes(uuid.uuid4()) |
|
224 upgradecaps = [ |
|
225 ('proto', wireprotoserver.SSHV2), |
|
226 ] |
|
227 upgradecaps = util.urlreq.urlencode(upgradecaps) |
197 |
228 |
198 try: |
229 try: |
199 pairsarg = '%s-%s' % ('0' * 40, '0' * 40) |
230 pairsarg = '%s-%s' % ('0' * 40, '0' * 40) |
200 handshake = [ |
231 handshake = [ |
201 'hello\n', |
232 'hello\n', |
202 'between\n', |
233 'between\n', |
203 'pairs %d\n' % len(pairsarg), |
234 'pairs %d\n' % len(pairsarg), |
204 pairsarg, |
235 pairsarg, |
205 ] |
236 ] |
206 |
237 |
|
238 # Request upgrade to version 2 if configured. |
|
239 if ui.configbool('experimental', 'sshpeer.advertise-v2'): |
|
240 ui.debug('sending upgrade request: %s %s\n' % (token, upgradecaps)) |
|
241 handshake.insert(0, 'upgrade %s %s\n' % (token, upgradecaps)) |
|
242 |
207 if requestlog: |
243 if requestlog: |
208 ui.debug('devel-peer-request: hello\n') |
244 ui.debug('devel-peer-request: hello\n') |
209 ui.debug('sending hello command\n') |
245 ui.debug('sending hello command\n') |
210 if requestlog: |
246 if requestlog: |
211 ui.debug('devel-peer-request: between\n') |
247 ui.debug('devel-peer-request: between\n') |
215 stdin.write(''.join(handshake)) |
251 stdin.write(''.join(handshake)) |
216 stdin.flush() |
252 stdin.flush() |
217 except IOError: |
253 except IOError: |
218 badresponse() |
254 badresponse() |
219 |
255 |
|
256 # Assume version 1 of wire protocol by default. |
|
257 protoname = wireprotoserver.SSHV1 |
|
258 reupgraded = re.compile(b'^upgraded %s (.*)$' % re.escape(token)) |
|
259 |
220 lines = ['', 'dummy'] |
260 lines = ['', 'dummy'] |
221 max_noise = 500 |
261 max_noise = 500 |
222 while lines[-1] and max_noise: |
262 while lines[-1] and max_noise: |
223 try: |
263 try: |
224 l = stdout.readline() |
264 l = stdout.readline() |
225 _forwardoutput(ui, stderr) |
265 _forwardoutput(ui, stderr) |
|
266 |
|
267 # Look for reply to protocol upgrade request. It has a token |
|
268 # in it, so there should be no false positives. |
|
269 m = reupgraded.match(l) |
|
270 if m: |
|
271 protoname = m.group(1) |
|
272 ui.debug('protocol upgraded to %s\n' % protoname) |
|
273 # If an upgrade was handled, the ``hello`` and ``between`` |
|
274 # requests are ignored. The next output belongs to the |
|
275 # protocol, so stop scanning lines. |
|
276 break |
|
277 |
|
278 # Otherwise it could be a banner, ``0\n`` response if server |
|
279 # doesn't support upgrade. |
|
280 |
226 if lines[-1] == '1\n' and l == '\n': |
281 if lines[-1] == '1\n' and l == '\n': |
227 break |
282 break |
228 if l: |
283 if l: |
229 ui.debug('remote: ', l) |
284 ui.debug('remote: ', l) |
230 lines.append(l) |
285 lines.append(l) |
233 badresponse() |
288 badresponse() |
234 else: |
289 else: |
235 badresponse() |
290 badresponse() |
236 |
291 |
237 caps = set() |
292 caps = set() |
238 for l in reversed(lines): |
293 |
239 # Look for response to ``hello`` command. Scan from the back so |
294 # For version 1, we should see a ``capabilities`` line in response to the |
240 # we don't misinterpret banner output as the command reply. |
295 # ``hello`` command. |
241 if l.startswith('capabilities:'): |
296 if protoname == wireprotoserver.SSHV1: |
242 caps.update(l[:-1].split(':')[1].split()) |
297 for l in reversed(lines): |
243 break |
298 # Look for response to ``hello`` command. Scan from the back so |
244 |
299 # we don't misinterpret banner output as the command reply. |
245 # Error if we couldn't find a response to ``hello``. This could |
300 if l.startswith('capabilities:'): |
246 # mean: |
301 caps.update(l[:-1].split(':')[1].split()) |
|
302 break |
|
303 elif protoname == wireprotoserver.SSHV2: |
|
304 # We see a line with number of bytes to follow and then a value |
|
305 # looking like ``capabilities: *``. |
|
306 line = stdout.readline() |
|
307 try: |
|
308 valuelen = int(line) |
|
309 except ValueError: |
|
310 badresponse() |
|
311 |
|
312 capsline = stdout.read(valuelen) |
|
313 if not capsline.startswith('capabilities: '): |
|
314 badresponse() |
|
315 |
|
316 caps.update(capsline.split(':')[1].split()) |
|
317 # Trailing newline. |
|
318 stdout.read(1) |
|
319 |
|
320 # Error if we couldn't find capabilities, this means: |
247 # |
321 # |
248 # 1. Remote isn't a Mercurial server |
322 # 1. Remote isn't a Mercurial server |
249 # 2. Remote is a <0.9.1 Mercurial server |
323 # 2. Remote is a <0.9.1 Mercurial server |
250 # 3. Remote is a future Mercurial server that dropped ``hello`` |
324 # 3. Remote is a future Mercurial server that dropped ``hello`` |
251 # support. |
325 # and other attempted handshake mechanisms. |
252 if not caps: |
326 if not caps: |
253 badresponse() |
327 badresponse() |
254 |
328 |
255 return caps |
329 return caps |
256 |
330 |