##// END OF EJS Templates
wireprotoserver: access headers through parsed request...
Gregory Szorc -
r36862:14f70c44 default
parent child Browse files
Show More
@@ -1,669 +1,668 b''
1 1 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
2 2 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 3 #
4 4 # This software may be used and distributed according to the terms of the
5 5 # GNU General Public License version 2 or any later version.
6 6
7 7 from __future__ import absolute_import
8 8
9 9 import contextlib
10 10 import struct
11 11 import sys
12 12 import threading
13 13
14 14 from .i18n import _
15 15 from . import (
16 16 encoding,
17 17 error,
18 18 hook,
19 19 pycompat,
20 20 util,
21 21 wireproto,
22 22 wireprototypes,
23 23 )
24 24
25 25 stringio = util.stringio
26 26
27 27 urlerr = util.urlerr
28 28 urlreq = util.urlreq
29 29
30 30 HTTP_OK = 200
31 31
32 32 HGTYPE = 'application/mercurial-0.1'
33 33 HGTYPE2 = 'application/mercurial-0.2'
34 34 HGERRTYPE = 'application/hg-error'
35 35
36 36 SSHV1 = wireprototypes.SSHV1
37 37 SSHV2 = wireprototypes.SSHV2
38 38
39 def decodevaluefromheaders(wsgireq, headerprefix):
39 def decodevaluefromheaders(req, headerprefix):
40 40 """Decode a long value from multiple HTTP request headers.
41 41
42 42 Returns the value as a bytes, not a str.
43 43 """
44 44 chunks = []
45 45 i = 1
46 prefix = headerprefix.upper().replace(r'-', r'_')
47 46 while True:
48 v = wsgireq.env.get(r'HTTP_%s_%d' % (prefix, i))
47 v = req.headers.get(b'%s-%d' % (headerprefix, i))
49 48 if v is None:
50 49 break
51 50 chunks.append(pycompat.bytesurl(v))
52 51 i += 1
53 52
54 53 return ''.join(chunks)
55 54
56 55 class httpv1protocolhandler(wireprototypes.baseprotocolhandler):
57 def __init__(self, wsgireq, ui, checkperm):
56 def __init__(self, wsgireq, req, ui, checkperm):
58 57 self._wsgireq = wsgireq
58 self._req = req
59 59 self._ui = ui
60 60 self._checkperm = checkperm
61 61
62 62 @property
63 63 def name(self):
64 64 return 'http-v1'
65 65
66 66 def getargs(self, args):
67 67 knownargs = self._args()
68 68 data = {}
69 69 keys = args.split()
70 70 for k in keys:
71 71 if k == '*':
72 72 star = {}
73 73 for key in knownargs.keys():
74 74 if key != 'cmd' and key not in keys:
75 75 star[key] = knownargs[key][0]
76 76 data['*'] = star
77 77 else:
78 78 data[k] = knownargs[k][0]
79 79 return [data[k] for k in keys]
80 80
81 81 def _args(self):
82 82 args = util.rapply(pycompat.bytesurl, self._wsgireq.form.copy())
83 postlen = int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0))
83 postlen = int(self._req.headers.get(b'X-HgArgs-Post', 0))
84 84 if postlen:
85 85 args.update(urlreq.parseqs(
86 86 self._wsgireq.read(postlen), keep_blank_values=True))
87 87 return args
88 88
89 argvalue = decodevaluefromheaders(self._wsgireq, r'X-HgArg')
89 argvalue = decodevaluefromheaders(self._req, b'X-HgArg')
90 90 args.update(urlreq.parseqs(argvalue, keep_blank_values=True))
91 91 return args
92 92
93 93 def forwardpayload(self, fp):
94 if r'HTTP_CONTENT_LENGTH' in self._wsgireq.env:
95 length = int(self._wsgireq.env[r'HTTP_CONTENT_LENGTH'])
94 if b'Content-Length' in self._req.headers:
95 length = int(self._req.headers[b'Content-Length'])
96 96 else:
97 97 length = int(self._wsgireq.env[r'CONTENT_LENGTH'])
98 98 # If httppostargs is used, we need to read Content-Length
99 99 # minus the amount that was consumed by args.
100 length -= int(self._wsgireq.env.get(r'HTTP_X_HGARGS_POST', 0))
100 length -= int(self._req.headers.get(b'X-HgArgs-Post', 0))
101 101 for s in util.filechunkiter(self._wsgireq, limit=length):
102 102 fp.write(s)
103 103
104 104 @contextlib.contextmanager
105 105 def mayberedirectstdio(self):
106 106 oldout = self._ui.fout
107 107 olderr = self._ui.ferr
108 108
109 109 out = util.stringio()
110 110
111 111 try:
112 112 self._ui.fout = out
113 113 self._ui.ferr = out
114 114 yield out
115 115 finally:
116 116 self._ui.fout = oldout
117 117 self._ui.ferr = olderr
118 118
119 119 def client(self):
120 120 return 'remote:%s:%s:%s' % (
121 121 self._wsgireq.env.get('wsgi.url_scheme') or 'http',
122 122 urlreq.quote(self._wsgireq.env.get('REMOTE_HOST', '')),
123 123 urlreq.quote(self._wsgireq.env.get('REMOTE_USER', '')))
124 124
125 125 def addcapabilities(self, repo, caps):
126 126 caps.append('httpheader=%d' %
127 127 repo.ui.configint('server', 'maxhttpheaderlen'))
128 128 if repo.ui.configbool('experimental', 'httppostargs'):
129 129 caps.append('httppostargs')
130 130
131 131 # FUTURE advertise 0.2rx once support is implemented
132 132 # FUTURE advertise minrx and mintx after consulting config option
133 133 caps.append('httpmediatype=0.1rx,0.1tx,0.2tx')
134 134
135 135 compengines = wireproto.supportedcompengines(repo.ui, util.SERVERROLE)
136 136 if compengines:
137 137 comptypes = ','.join(urlreq.quote(e.wireprotosupport().name)
138 138 for e in compengines)
139 139 caps.append('compression=%s' % comptypes)
140 140
141 141 return caps
142 142
143 143 def checkperm(self, perm):
144 144 return self._checkperm(perm)
145 145
146 146 # This method exists mostly so that extensions like remotefilelog can
147 147 # disable a kludgey legacy method only over http. As of early 2018,
148 148 # there are no other known users, so with any luck we can discard this
149 149 # hook if remotefilelog becomes a first-party extension.
150 150 def iscmd(cmd):
151 151 return cmd in wireproto.commands
152 152
153 153 def handlewsgirequest(rctx, wsgireq, req, checkperm):
154 154 """Possibly process a wire protocol request.
155 155
156 156 If the current request is a wire protocol request, the request is
157 157 processed by this function.
158 158
159 159 ``wsgireq`` is a ``wsgirequest`` instance.
160 160 ``req`` is a ``parsedrequest`` instance.
161 161
162 162 Returns a 2-tuple of (bool, response) where the 1st element indicates
163 163 whether the request was handled and the 2nd element is a return
164 164 value for a WSGI application (often a generator of bytes).
165 165 """
166 166 # Avoid cycle involving hg module.
167 167 from .hgweb import common as hgwebcommon
168 168
169 169 repo = rctx.repo
170 170
171 171 # HTTP version 1 wire protocol requests are denoted by a "cmd" query
172 172 # string parameter. If it isn't present, this isn't a wire protocol
173 173 # request.
174 174 if 'cmd' not in req.querystringdict:
175 175 return False, None
176 176
177 177 cmd = req.querystringdict['cmd'][0]
178 178
179 179 # The "cmd" request parameter is used by both the wire protocol and hgweb.
180 180 # While not all wire protocol commands are available for all transports,
181 181 # if we see a "cmd" value that resembles a known wire protocol command, we
182 182 # route it to a protocol handler. This is better than routing possible
183 183 # wire protocol requests to hgweb because it prevents hgweb from using
184 184 # known wire protocol commands and it is less confusing for machine
185 185 # clients.
186 186 if not iscmd(cmd):
187 187 return False, None
188 188
189 189 # The "cmd" query string argument is only valid on the root path of the
190 190 # repo. e.g. ``/?cmd=foo``, ``/repo?cmd=foo``. URL paths within the repo
191 191 # like ``/blah?cmd=foo`` are not allowed. So don't recognize the request
192 192 # in this case. We send an HTTP 404 for backwards compatibility reasons.
193 193 if req.dispatchpath:
194 194 res = _handlehttperror(
195 195 hgwebcommon.ErrorResponse(hgwebcommon.HTTP_NOT_FOUND), wsgireq,
196 cmd)
196 req, cmd)
197 197
198 198 return True, res
199 199
200 proto = httpv1protocolhandler(wsgireq, repo.ui,
200 proto = httpv1protocolhandler(wsgireq, req, repo.ui,
201 201 lambda perm: checkperm(rctx, wsgireq, perm))
202 202
203 203 # The permissions checker should be the only thing that can raise an
204 204 # ErrorResponse. It is kind of a layer violation to catch an hgweb
205 205 # exception here. So consider refactoring into a exception type that
206 206 # is associated with the wire protocol.
207 207 try:
208 res = _callhttp(repo, wsgireq, proto, cmd)
208 res = _callhttp(repo, wsgireq, req, proto, cmd)
209 209 except hgwebcommon.ErrorResponse as e:
210 res = _handlehttperror(e, wsgireq, cmd)
210 res = _handlehttperror(e, wsgireq, req, cmd)
211 211
212 212 return True, res
213 213
214 def _httpresponsetype(ui, wsgireq, prefer_uncompressed):
214 def _httpresponsetype(ui, req, prefer_uncompressed):
215 215 """Determine the appropriate response type and compression settings.
216 216
217 217 Returns a tuple of (mediatype, compengine, engineopts).
218 218 """
219 219 # Determine the response media type and compression engine based
220 220 # on the request parameters.
221 protocaps = decodevaluefromheaders(wsgireq, r'X-HgProto').split(' ')
221 protocaps = decodevaluefromheaders(req, 'X-HgProto').split(' ')
222 222
223 223 if '0.2' in protocaps:
224 224 # All clients are expected to support uncompressed data.
225 225 if prefer_uncompressed:
226 226 return HGTYPE2, util._noopengine(), {}
227 227
228 228 # Default as defined by wire protocol spec.
229 229 compformats = ['zlib', 'none']
230 230 for cap in protocaps:
231 231 if cap.startswith('comp='):
232 232 compformats = cap[5:].split(',')
233 233 break
234 234
235 235 # Now find an agreed upon compression format.
236 236 for engine in wireproto.supportedcompengines(ui, util.SERVERROLE):
237 237 if engine.wireprotosupport().name in compformats:
238 238 opts = {}
239 239 level = ui.configint('server', '%slevel' % engine.name())
240 240 if level is not None:
241 241 opts['level'] = level
242 242
243 243 return HGTYPE2, engine, opts
244 244
245 245 # No mutually supported compression format. Fall back to the
246 246 # legacy protocol.
247 247
248 248 # Don't allow untrusted settings because disabling compression or
249 249 # setting a very high compression level could lead to flooding
250 250 # the server's network or CPU.
251 251 opts = {'level': ui.configint('server', 'zliblevel')}
252 252 return HGTYPE, util.compengines['zlib'], opts
253 253
254 def _callhttp(repo, wsgireq, proto, cmd):
254 def _callhttp(repo, wsgireq, req, proto, cmd):
255 255 def genversion2(gen, engine, engineopts):
256 256 # application/mercurial-0.2 always sends a payload header
257 257 # identifying the compression engine.
258 258 name = engine.wireprotosupport().name
259 259 assert 0 < len(name) < 256
260 260 yield struct.pack('B', len(name))
261 261 yield name
262 262
263 263 for chunk in gen:
264 264 yield chunk
265 265
266 266 if not wireproto.commands.commandavailable(cmd, proto):
267 267 wsgireq.respond(HTTP_OK, HGERRTYPE,
268 268 body=_('requested wire protocol command is not '
269 269 'available over HTTP'))
270 270 return []
271 271
272 272 proto.checkperm(wireproto.commands[cmd].permission)
273 273
274 274 rsp = wireproto.dispatch(repo, proto, cmd)
275 275
276 276 if isinstance(rsp, bytes):
277 277 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
278 278 return []
279 279 elif isinstance(rsp, wireprototypes.bytesresponse):
280 280 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp.data)
281 281 return []
282 282 elif isinstance(rsp, wireprototypes.streamreslegacy):
283 283 gen = rsp.gen
284 284 wsgireq.respond(HTTP_OK, HGTYPE)
285 285 return gen
286 286 elif isinstance(rsp, wireprototypes.streamres):
287 287 gen = rsp.gen
288 288
289 289 # This code for compression should not be streamres specific. It
290 290 # is here because we only compress streamres at the moment.
291 291 mediatype, engine, engineopts = _httpresponsetype(
292 repo.ui, wsgireq, rsp.prefer_uncompressed)
292 repo.ui, req, rsp.prefer_uncompressed)
293 293 gen = engine.compressstream(gen, engineopts)
294 294
295 295 if mediatype == HGTYPE2:
296 296 gen = genversion2(gen, engine, engineopts)
297 297
298 298 wsgireq.respond(HTTP_OK, mediatype)
299 299 return gen
300 300 elif isinstance(rsp, wireprototypes.pushres):
301 301 rsp = '%d\n%s' % (rsp.res, rsp.output)
302 302 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
303 303 return []
304 304 elif isinstance(rsp, wireprototypes.pusherr):
305 305 # This is the httplib workaround documented in _handlehttperror().
306 306 wsgireq.drain()
307 307
308 308 rsp = '0\n%s\n' % rsp.res
309 309 wsgireq.respond(HTTP_OK, HGTYPE, body=rsp)
310 310 return []
311 311 elif isinstance(rsp, wireprototypes.ooberror):
312 312 rsp = rsp.message
313 313 wsgireq.respond(HTTP_OK, HGERRTYPE, body=rsp)
314 314 return []
315 315 raise error.ProgrammingError('hgweb.protocol internal failure', rsp)
316 316
317 def _handlehttperror(e, wsgireq, cmd):
317 def _handlehttperror(e, wsgireq, req, cmd):
318 318 """Called when an ErrorResponse is raised during HTTP request processing."""
319 319
320 320 # Clients using Python's httplib are stateful: the HTTP client
321 321 # won't process an HTTP response until all request data is
322 322 # sent to the server. The intent of this code is to ensure
323 323 # we always read HTTP request data from the client, thus
324 324 # ensuring httplib transitions to a state that allows it to read
325 325 # the HTTP response. In other words, it helps prevent deadlocks
326 326 # on clients using httplib.
327 327
328 328 if (wsgireq.env[r'REQUEST_METHOD'] == r'POST' and
329 329 # But not if Expect: 100-continue is being used.
330 (wsgireq.env.get('HTTP_EXPECT',
331 '').lower() != '100-continue')):
330 (req.headers.get('Expect', '').lower() != '100-continue')):
332 331 wsgireq.drain()
333 332 else:
334 333 wsgireq.headers.append((r'Connection', r'Close'))
335 334
336 335 # TODO This response body assumes the failed command was
337 336 # "unbundle." That assumption is not always valid.
338 337 wsgireq.respond(e, HGTYPE, body='0\n%s\n' % pycompat.bytestr(e))
339 338
340 339 return ''
341 340
342 341 def _sshv1respondbytes(fout, value):
343 342 """Send a bytes response for protocol version 1."""
344 343 fout.write('%d\n' % len(value))
345 344 fout.write(value)
346 345 fout.flush()
347 346
348 347 def _sshv1respondstream(fout, source):
349 348 write = fout.write
350 349 for chunk in source.gen:
351 350 write(chunk)
352 351 fout.flush()
353 352
354 353 def _sshv1respondooberror(fout, ferr, rsp):
355 354 ferr.write(b'%s\n-\n' % rsp)
356 355 ferr.flush()
357 356 fout.write(b'\n')
358 357 fout.flush()
359 358
360 359 class sshv1protocolhandler(wireprototypes.baseprotocolhandler):
361 360 """Handler for requests services via version 1 of SSH protocol."""
362 361 def __init__(self, ui, fin, fout):
363 362 self._ui = ui
364 363 self._fin = fin
365 364 self._fout = fout
366 365
367 366 @property
368 367 def name(self):
369 368 return wireprototypes.SSHV1
370 369
371 370 def getargs(self, args):
372 371 data = {}
373 372 keys = args.split()
374 373 for n in xrange(len(keys)):
375 374 argline = self._fin.readline()[:-1]
376 375 arg, l = argline.split()
377 376 if arg not in keys:
378 377 raise error.Abort(_("unexpected parameter %r") % arg)
379 378 if arg == '*':
380 379 star = {}
381 380 for k in xrange(int(l)):
382 381 argline = self._fin.readline()[:-1]
383 382 arg, l = argline.split()
384 383 val = self._fin.read(int(l))
385 384 star[arg] = val
386 385 data['*'] = star
387 386 else:
388 387 val = self._fin.read(int(l))
389 388 data[arg] = val
390 389 return [data[k] for k in keys]
391 390
392 391 def forwardpayload(self, fpout):
393 392 # We initially send an empty response. This tells the client it is
394 393 # OK to start sending data. If a client sees any other response, it
395 394 # interprets it as an error.
396 395 _sshv1respondbytes(self._fout, b'')
397 396
398 397 # The file is in the form:
399 398 #
400 399 # <chunk size>\n<chunk>
401 400 # ...
402 401 # 0\n
403 402 count = int(self._fin.readline())
404 403 while count:
405 404 fpout.write(self._fin.read(count))
406 405 count = int(self._fin.readline())
407 406
408 407 @contextlib.contextmanager
409 408 def mayberedirectstdio(self):
410 409 yield None
411 410
412 411 def client(self):
413 412 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
414 413 return 'remote:ssh:' + client
415 414
416 415 def addcapabilities(self, repo, caps):
417 416 return caps
418 417
419 418 def checkperm(self, perm):
420 419 pass
421 420
422 421 class sshv2protocolhandler(sshv1protocolhandler):
423 422 """Protocol handler for version 2 of the SSH protocol."""
424 423
425 424 @property
426 425 def name(self):
427 426 return wireprototypes.SSHV2
428 427
429 428 def _runsshserver(ui, repo, fin, fout, ev):
430 429 # This function operates like a state machine of sorts. The following
431 430 # states are defined:
432 431 #
433 432 # protov1-serving
434 433 # Server is in protocol version 1 serving mode. Commands arrive on
435 434 # new lines. These commands are processed in this state, one command
436 435 # after the other.
437 436 #
438 437 # protov2-serving
439 438 # Server is in protocol version 2 serving mode.
440 439 #
441 440 # upgrade-initial
442 441 # The server is going to process an upgrade request.
443 442 #
444 443 # upgrade-v2-filter-legacy-handshake
445 444 # The protocol is being upgraded to version 2. The server is expecting
446 445 # the legacy handshake from version 1.
447 446 #
448 447 # upgrade-v2-finish
449 448 # The upgrade to version 2 of the protocol is imminent.
450 449 #
451 450 # shutdown
452 451 # The server is shutting down, possibly in reaction to a client event.
453 452 #
454 453 # And here are their transitions:
455 454 #
456 455 # protov1-serving -> shutdown
457 456 # When server receives an empty request or encounters another
458 457 # error.
459 458 #
460 459 # protov1-serving -> upgrade-initial
461 460 # An upgrade request line was seen.
462 461 #
463 462 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
464 463 # Upgrade to version 2 in progress. Server is expecting to
465 464 # process a legacy handshake.
466 465 #
467 466 # upgrade-v2-filter-legacy-handshake -> shutdown
468 467 # Client did not fulfill upgrade handshake requirements.
469 468 #
470 469 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
471 470 # Client fulfilled version 2 upgrade requirements. Finishing that
472 471 # upgrade.
473 472 #
474 473 # upgrade-v2-finish -> protov2-serving
475 474 # Protocol upgrade to version 2 complete. Server can now speak protocol
476 475 # version 2.
477 476 #
478 477 # protov2-serving -> protov1-serving
479 478 # Ths happens by default since protocol version 2 is the same as
480 479 # version 1 except for the handshake.
481 480
482 481 state = 'protov1-serving'
483 482 proto = sshv1protocolhandler(ui, fin, fout)
484 483 protoswitched = False
485 484
486 485 while not ev.is_set():
487 486 if state == 'protov1-serving':
488 487 # Commands are issued on new lines.
489 488 request = fin.readline()[:-1]
490 489
491 490 # Empty lines signal to terminate the connection.
492 491 if not request:
493 492 state = 'shutdown'
494 493 continue
495 494
496 495 # It looks like a protocol upgrade request. Transition state to
497 496 # handle it.
498 497 if request.startswith(b'upgrade '):
499 498 if protoswitched:
500 499 _sshv1respondooberror(fout, ui.ferr,
501 500 b'cannot upgrade protocols multiple '
502 501 b'times')
503 502 state = 'shutdown'
504 503 continue
505 504
506 505 state = 'upgrade-initial'
507 506 continue
508 507
509 508 available = wireproto.commands.commandavailable(request, proto)
510 509
511 510 # This command isn't available. Send an empty response and go
512 511 # back to waiting for a new command.
513 512 if not available:
514 513 _sshv1respondbytes(fout, b'')
515 514 continue
516 515
517 516 rsp = wireproto.dispatch(repo, proto, request)
518 517
519 518 if isinstance(rsp, bytes):
520 519 _sshv1respondbytes(fout, rsp)
521 520 elif isinstance(rsp, wireprototypes.bytesresponse):
522 521 _sshv1respondbytes(fout, rsp.data)
523 522 elif isinstance(rsp, wireprototypes.streamres):
524 523 _sshv1respondstream(fout, rsp)
525 524 elif isinstance(rsp, wireprototypes.streamreslegacy):
526 525 _sshv1respondstream(fout, rsp)
527 526 elif isinstance(rsp, wireprototypes.pushres):
528 527 _sshv1respondbytes(fout, b'')
529 528 _sshv1respondbytes(fout, b'%d' % rsp.res)
530 529 elif isinstance(rsp, wireprototypes.pusherr):
531 530 _sshv1respondbytes(fout, rsp.res)
532 531 elif isinstance(rsp, wireprototypes.ooberror):
533 532 _sshv1respondooberror(fout, ui.ferr, rsp.message)
534 533 else:
535 534 raise error.ProgrammingError('unhandled response type from '
536 535 'wire protocol command: %s' % rsp)
537 536
538 537 # For now, protocol version 2 serving just goes back to version 1.
539 538 elif state == 'protov2-serving':
540 539 state = 'protov1-serving'
541 540 continue
542 541
543 542 elif state == 'upgrade-initial':
544 543 # We should never transition into this state if we've switched
545 544 # protocols.
546 545 assert not protoswitched
547 546 assert proto.name == wireprototypes.SSHV1
548 547
549 548 # Expected: upgrade <token> <capabilities>
550 549 # If we get something else, the request is malformed. It could be
551 550 # from a future client that has altered the upgrade line content.
552 551 # We treat this as an unknown command.
553 552 try:
554 553 token, caps = request.split(b' ')[1:]
555 554 except ValueError:
556 555 _sshv1respondbytes(fout, b'')
557 556 state = 'protov1-serving'
558 557 continue
559 558
560 559 # Send empty response if we don't support upgrading protocols.
561 560 if not ui.configbool('experimental', 'sshserver.support-v2'):
562 561 _sshv1respondbytes(fout, b'')
563 562 state = 'protov1-serving'
564 563 continue
565 564
566 565 try:
567 566 caps = urlreq.parseqs(caps)
568 567 except ValueError:
569 568 _sshv1respondbytes(fout, b'')
570 569 state = 'protov1-serving'
571 570 continue
572 571
573 572 # We don't see an upgrade request to protocol version 2. Ignore
574 573 # the upgrade request.
575 574 wantedprotos = caps.get(b'proto', [b''])[0]
576 575 if SSHV2 not in wantedprotos:
577 576 _sshv1respondbytes(fout, b'')
578 577 state = 'protov1-serving'
579 578 continue
580 579
581 580 # It looks like we can honor this upgrade request to protocol 2.
582 581 # Filter the rest of the handshake protocol request lines.
583 582 state = 'upgrade-v2-filter-legacy-handshake'
584 583 continue
585 584
586 585 elif state == 'upgrade-v2-filter-legacy-handshake':
587 586 # Client should have sent legacy handshake after an ``upgrade``
588 587 # request. Expected lines:
589 588 #
590 589 # hello
591 590 # between
592 591 # pairs 81
593 592 # 0000...-0000...
594 593
595 594 ok = True
596 595 for line in (b'hello', b'between', b'pairs 81'):
597 596 request = fin.readline()[:-1]
598 597
599 598 if request != line:
600 599 _sshv1respondooberror(fout, ui.ferr,
601 600 b'malformed handshake protocol: '
602 601 b'missing %s' % line)
603 602 ok = False
604 603 state = 'shutdown'
605 604 break
606 605
607 606 if not ok:
608 607 continue
609 608
610 609 request = fin.read(81)
611 610 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
612 611 _sshv1respondooberror(fout, ui.ferr,
613 612 b'malformed handshake protocol: '
614 613 b'missing between argument value')
615 614 state = 'shutdown'
616 615 continue
617 616
618 617 state = 'upgrade-v2-finish'
619 618 continue
620 619
621 620 elif state == 'upgrade-v2-finish':
622 621 # Send the upgrade response.
623 622 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
624 623 servercaps = wireproto.capabilities(repo, proto)
625 624 rsp = b'capabilities: %s' % servercaps.data
626 625 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
627 626 fout.flush()
628 627
629 628 proto = sshv2protocolhandler(ui, fin, fout)
630 629 protoswitched = True
631 630
632 631 state = 'protov2-serving'
633 632 continue
634 633
635 634 elif state == 'shutdown':
636 635 break
637 636
638 637 else:
639 638 raise error.ProgrammingError('unhandled ssh server state: %s' %
640 639 state)
641 640
642 641 class sshserver(object):
643 642 def __init__(self, ui, repo, logfh=None):
644 643 self._ui = ui
645 644 self._repo = repo
646 645 self._fin = ui.fin
647 646 self._fout = ui.fout
648 647
649 648 # Log write I/O to stdout and stderr if configured.
650 649 if logfh:
651 650 self._fout = util.makeloggingfileobject(
652 651 logfh, self._fout, 'o', logdata=True)
653 652 ui.ferr = util.makeloggingfileobject(
654 653 logfh, ui.ferr, 'e', logdata=True)
655 654
656 655 hook.redirect(True)
657 656 ui.fout = repo.ui.fout = ui.ferr
658 657
659 658 # Prevent insertion/deletion of CRs
660 659 util.setbinary(self._fin)
661 660 util.setbinary(self._fout)
662 661
663 662 def serve_forever(self):
664 663 self.serveuntil(threading.Event())
665 664 sys.exit(0)
666 665
667 666 def serveuntil(self, ev):
668 667 """Serve until a threading.Event is set."""
669 668 _runsshserver(self._ui, self._repo, self._fin, self._fout, ev)
General Comments 0
You need to be logged in to leave comments. Login now