##// END OF EJS Templates
wireprotoserver: handle SSH protocol version 2 upgrade requests...
Gregory Szorc -
r36233:464bedc0 default
parent child Browse files
Show More
@@ -556,6 +556,9 b" coreconfigitem('experimental', 'revlogv2"
556 coreconfigitem('experimental', 'single-head-per-branch',
556 coreconfigitem('experimental', 'single-head-per-branch',
557 default=False,
557 default=False,
558 )
558 )
559 coreconfigitem('experimental', 'sshserver.support-v2',
560 default=False,
561 )
559 coreconfigitem('experimental', 'spacemovesdown',
562 coreconfigitem('experimental', 'spacemovesdown',
560 default=False,
563 default=False,
561 )
564 )
@@ -409,9 +409,65 b' class sshv1protocolhandler(baseprotocolh'
409 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
409 client = encoding.environ.get('SSH_CLIENT', '').split(' ', 1)[0]
410 return 'remote:ssh:' + client
410 return 'remote:ssh:' + client
411
411
412 class sshv2protocolhandler(sshv1protocolhandler):
413 """Protocol handler for version 2 of the SSH protocol."""
414
412 def _runsshserver(ui, repo, fin, fout):
415 def _runsshserver(ui, repo, fin, fout):
416 # This function operates like a state machine of sorts. The following
417 # states are defined:
418 #
419 # protov1-serving
420 # Server is in protocol version 1 serving mode. Commands arrive on
421 # new lines. These commands are processed in this state, one command
422 # after the other.
423 #
424 # protov2-serving
425 # Server is in protocol version 2 serving mode.
426 #
427 # upgrade-initial
428 # The server is going to process an upgrade request.
429 #
430 # upgrade-v2-filter-legacy-handshake
431 # The protocol is being upgraded to version 2. The server is expecting
432 # the legacy handshake from version 1.
433 #
434 # upgrade-v2-finish
435 # The upgrade to version 2 of the protocol is imminent.
436 #
437 # shutdown
438 # The server is shutting down, possibly in reaction to a client event.
439 #
440 # And here are their transitions:
441 #
442 # protov1-serving -> shutdown
443 # When server receives an empty request or encounters another
444 # error.
445 #
446 # protov1-serving -> upgrade-initial
447 # An upgrade request line was seen.
448 #
449 # upgrade-initial -> upgrade-v2-filter-legacy-handshake
450 # Upgrade to version 2 in progress. Server is expecting to
451 # process a legacy handshake.
452 #
453 # upgrade-v2-filter-legacy-handshake -> shutdown
454 # Client did not fulfill upgrade handshake requirements.
455 #
456 # upgrade-v2-filter-legacy-handshake -> upgrade-v2-finish
457 # Client fulfilled version 2 upgrade requirements. Finishing that
458 # upgrade.
459 #
460 # upgrade-v2-finish -> protov2-serving
461 # Protocol upgrade to version 2 complete. Server can now speak protocol
462 # version 2.
463 #
464 # protov2-serving -> protov1-serving
465 # Ths happens by default since protocol version 2 is the same as
466 # version 1 except for the handshake.
467
413 state = 'protov1-serving'
468 state = 'protov1-serving'
414 proto = sshv1protocolhandler(ui, fin, fout)
469 proto = sshv1protocolhandler(ui, fin, fout)
470 protoswitched = False
415
471
416 while True:
472 while True:
417 if state == 'protov1-serving':
473 if state == 'protov1-serving':
@@ -423,6 +479,19 b' def _runsshserver(ui, repo, fin, fout):'
423 state = 'shutdown'
479 state = 'shutdown'
424 continue
480 continue
425
481
482 # It looks like a protocol upgrade request. Transition state to
483 # handle it.
484 if request.startswith(b'upgrade '):
485 if protoswitched:
486 _sshv1respondooberror(fout, ui.ferr,
487 b'cannot upgrade protocols multiple '
488 b'times')
489 state = 'shutdown'
490 continue
491
492 state = 'upgrade-initial'
493 continue
494
426 available = wireproto.commands.commandavailable(request, proto)
495 available = wireproto.commands.commandavailable(request, proto)
427
496
428 # This command isn't available. Send an empty response and go
497 # This command isn't available. Send an empty response and go
@@ -452,6 +521,103 b' def _runsshserver(ui, repo, fin, fout):'
452 raise error.ProgrammingError('unhandled response type from '
521 raise error.ProgrammingError('unhandled response type from '
453 'wire protocol command: %s' % rsp)
522 'wire protocol command: %s' % rsp)
454
523
524 # For now, protocol version 2 serving just goes back to version 1.
525 elif state == 'protov2-serving':
526 state = 'protov1-serving'
527 continue
528
529 elif state == 'upgrade-initial':
530 # We should never transition into this state if we've switched
531 # protocols.
532 assert not protoswitched
533 assert proto.name == SSHV1
534
535 # Expected: upgrade <token> <capabilities>
536 # If we get something else, the request is malformed. It could be
537 # from a future client that has altered the upgrade line content.
538 # We treat this as an unknown command.
539 try:
540 token, caps = request.split(b' ')[1:]
541 except ValueError:
542 _sshv1respondbytes(fout, b'')
543 state = 'protov1-serving'
544 continue
545
546 # Send empty response if we don't support upgrading protocols.
547 if not ui.configbool('experimental', 'sshserver.support-v2'):
548 _sshv1respondbytes(fout, b'')
549 state = 'protov1-serving'
550 continue
551
552 try:
553 caps = urlreq.parseqs(caps)
554 except ValueError:
555 _sshv1respondbytes(fout, b'')
556 state = 'protov1-serving'
557 continue
558
559 # We don't see an upgrade request to protocol version 2. Ignore
560 # the upgrade request.
561 wantedprotos = caps.get(b'proto', [b''])[0]
562 if SSHV2 not in wantedprotos:
563 _sshv1respondbytes(fout, b'')
564 state = 'protov1-serving'
565 continue
566
567 # It looks like we can honor this upgrade request to protocol 2.
568 # Filter the rest of the handshake protocol request lines.
569 state = 'upgrade-v2-filter-legacy-handshake'
570 continue
571
572 elif state == 'upgrade-v2-filter-legacy-handshake':
573 # Client should have sent legacy handshake after an ``upgrade``
574 # request. Expected lines:
575 #
576 # hello
577 # between
578 # pairs 81
579 # 0000...-0000...
580
581 ok = True
582 for line in (b'hello', b'between', b'pairs 81'):
583 request = fin.readline()[:-1]
584
585 if request != line:
586 _sshv1respondooberror(fout, ui.ferr,
587 b'malformed handshake protocol: '
588 b'missing %s' % line)
589 ok = False
590 state = 'shutdown'
591 break
592
593 if not ok:
594 continue
595
596 request = fin.read(81)
597 if request != b'%s-%s' % (b'0' * 40, b'0' * 40):
598 _sshv1respondooberror(fout, ui.ferr,
599 b'malformed handshake protocol: '
600 b'missing between argument value')
601 state = 'shutdown'
602 continue
603
604 state = 'upgrade-v2-finish'
605 continue
606
607 elif state == 'upgrade-v2-finish':
608 # Send the upgrade response.
609 fout.write(b'upgraded %s %s\n' % (token, SSHV2))
610 servercaps = wireproto.capabilities(repo, proto)
611 rsp = b'capabilities: %s' % servercaps.data
612 fout.write(b'%d\n%s\n' % (len(rsp), rsp))
613 fout.flush()
614
615 proto = sshv2protocolhandler(ui, fin, fout)
616 protoswitched = True
617
618 state = 'protov2-serving'
619 continue
620
455 elif state == 'shutdown':
621 elif state == 'shutdown':
456 break
622 break
457
623
@@ -55,37 +55,6 b' class prehelloserver(wireprotoserver.ssh'
55
55
56 super(prehelloserver, self).serve_forever()
56 super(prehelloserver, self).serve_forever()
57
57
58 class upgradev2server(wireprotoserver.sshserver):
59 """Tests behavior for clients that issue upgrade to version 2."""
60 def serve_forever(self):
61 name = wireprotoserver.SSHV2
62 l = self._fin.readline()
63 assert l.startswith(b'upgrade ')
64 token, caps = l[:-1].split(b' ')[1:]
65 assert caps == b'proto=%s' % name
66
67 # Filter hello and between requests.
68 l = self._fin.readline()
69 assert l == b'hello\n'
70 l = self._fin.readline()
71 assert l == b'between\n'
72 l = self._fin.readline()
73 assert l == b'pairs 81\n'
74 self._fin.read(81)
75
76 # Send the upgrade response.
77 proto = wireprotoserver.sshv1protocolhandler(self._ui, self._fin,
78 self._fout)
79 self._fout.write(b'upgraded %s %s\n' % (token, name))
80 servercaps = wireproto.capabilities(self._repo, proto)
81 rsp = b'capabilities: %s' % servercaps.data
82 self._fout.write(b'%d\n' % len(rsp))
83 self._fout.write(rsp)
84 self._fout.write(b'\n')
85 self._fout.flush()
86
87 super(upgradev2server, self).serve_forever()
88
89 def performhandshake(orig, ui, stdin, stdout, stderr):
58 def performhandshake(orig, ui, stdin, stdout, stderr):
90 """Wrapped version of sshpeer._performhandshake to send extra commands."""
59 """Wrapped version of sshpeer._performhandshake to send extra commands."""
91 mode = ui.config(b'sshpeer', b'handshake-mode')
60 mode = ui.config(b'sshpeer', b'handshake-mode')
@@ -118,8 +87,6 b' def extsetup(ui):'
118 wireprotoserver.sshserver = bannerserver
87 wireprotoserver.sshserver = bannerserver
119 elif servermode == b'no-hello':
88 elif servermode == b'no-hello':
120 wireprotoserver.sshserver = prehelloserver
89 wireprotoserver.sshserver = prehelloserver
121 elif servermode == b'upgradev2':
122 wireprotoserver.sshserver = upgradev2server
123 elif servermode:
90 elif servermode:
124 raise error.ProgrammingError(b'unknown server mode: %s' % servermode)
91 raise error.ProgrammingError(b'unknown server mode: %s' % servermode)
125
92
@@ -453,9 +453,17 b' Send an upgrade request to a server that'
453 local: no
453 local: no
454 pushable: yes
454 pushable: yes
455
455
456 Enable version 2 support on server. We need to do this in hgrc because we can't
457 use --config with `hg serve --stdio`.
458
459 $ cat >> server/.hg/hgrc << EOF
460 > [experimental]
461 > sshserver.support-v2 = true
462 > EOF
463
456 Send an upgrade request to a server that supports upgrade
464 Send an upgrade request to a server that supports upgrade
457
465
458 $ SSHSERVERMODE=upgradev2 hg -R server serve --stdio << EOF
466 $ hg -R server serve --stdio << EOF
459 > upgrade this-is-some-token proto=exp-ssh-v2-0001
467 > upgrade this-is-some-token proto=exp-ssh-v2-0001
460 > hello
468 > hello
461 > between
469 > between
@@ -466,7 +474,7 b' Send an upgrade request to a server that'
466 383
474 383
467 capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
475 capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
468
476
469 $ SSHSERVERMODE=upgradev2 hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server
477 $ hg --config experimental.sshpeer.advertise-v2=true --debug debugpeer ssh://user@dummy/server
470 running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !)
478 running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !)
471 running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !)
479 running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !)
472 sending upgrade request: * proto=exp-ssh-v2-0001 (glob)
480 sending upgrade request: * proto=exp-ssh-v2-0001 (glob)
@@ -482,7 +490,7 b' Send an upgrade request to a server that'
482
490
483 Verify the peer has capabilities
491 Verify the peer has capabilities
484
492
485 $ SSHSERVERMODE=upgradev2 hg --config experimental.sshpeer.advertise-v2=true --debug debugcapabilities ssh://user@dummy/server
493 $ hg --config experimental.sshpeer.advertise-v2=true --debug debugcapabilities ssh://user@dummy/server
486 running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !)
494 running * "*/tests/dummyssh" 'user@dummy' 'hg -R server serve --stdio' (glob) (no-windows !)
487 running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !)
495 running * "*\tests/dummyssh" "user@dummy" "hg -R server serve --stdio" (glob) (windows !)
488 sending upgrade request: * proto=exp-ssh-v2-0001 (glob)
496 sending upgrade request: * proto=exp-ssh-v2-0001 (glob)
@@ -527,3 +535,96 b' Verify the peer has capabilities'
527 remote-changegroup
535 remote-changegroup
528 http
536 http
529 https
537 https
538
539 Command after upgrade to version 2 is processed
540
541 $ hg -R server serve --stdio << EOF
542 > upgrade this-is-some-token proto=exp-ssh-v2-0001
543 > hello
544 > between
545 > pairs 81
546 > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000hello
547 > EOF
548 upgraded this-is-some-token exp-ssh-v2-0001
549 383
550 capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
551 384
552 capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
553
554 Multiple upgrades is not allowed
555
556 $ hg -R server serve --stdio << EOF
557 > upgrade this-is-some-token proto=exp-ssh-v2-0001
558 > hello
559 > between
560 > pairs 81
561 > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000upgrade another-token proto=irrelevant
562 > hello
563 > EOF
564 upgraded this-is-some-token exp-ssh-v2-0001
565 383
566 capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
567 cannot upgrade protocols multiple times
568 -
569
570
571 Malformed upgrade request line (not exactly 3 space delimited tokens)
572
573 $ hg -R server serve --stdio << EOF
574 > upgrade
575 > EOF
576 0
577
578 $ hg -R server serve --stdio << EOF
579 > upgrade token
580 > EOF
581 0
582
583 $ hg -R server serve --stdio << EOF
584 > upgrade token foo=bar extra-token
585 > EOF
586 0
587
588 Upgrade request to unsupported protocol is ignored
589
590 $ hg -R server serve --stdio << EOF
591 > upgrade this-is-some-token proto=unknown1,unknown2
592 > hello
593 > between
594 > pairs 81
595 > 0000000000000000000000000000000000000000-0000000000000000000000000000000000000000
596 > EOF
597 0
598 384
599 capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch streamreqs=generaldelta,revlogv1 $USUAL_BUNDLE2_CAPS_SERVER$ unbundle=HG10GZ,HG10BZ,HG10UN
600 1
601
602
603 Upgrade request must be followed by hello + between
604
605 $ hg -R server serve --stdio << EOF
606 > upgrade token proto=exp-ssh-v2-0001
607 > invalid
608 > EOF
609 malformed handshake protocol: missing hello
610 -
611
612
613 $ hg -R server serve --stdio << EOF
614 > upgrade token proto=exp-ssh-v2-0001
615 > hello
616 > invalid
617 > EOF
618 malformed handshake protocol: missing between
619 -
620
621
622 $ hg -R server serve --stdio << EOF
623 > upgrade token proto=exp-ssh-v2-0001
624 > hello
625 > between
626 > invalid
627 > EOF
628 malformed handshake protocol: missing pairs 81
629 -
630
General Comments 0
You need to be logged in to leave comments. Login now