mercurial/wireprotoserver.py
changeset 36215 464bedc0fdb4
parent 36214 3b3a987bbbaa
child 36222 6ba5b03f3645
equal deleted inserted replaced
36214:3b3a987bbbaa 36215:464bedc0fdb4
   407 
   407 
   408     def client(self):
   408     def client(self):
   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':
   418             # Commands are issued on new lines.
   474             # Commands are issued on new lines.
   419             request = fin.readline()[:-1]
   475             request = fin.readline()[:-1]
   420 
   476 
   421             # Empty lines signal to terminate the connection.
   477             # Empty lines signal to terminate the connection.
   422             if not request:
   478             if not request:
   423                 state = 'shutdown'
   479                 state = 'shutdown'
       
   480                 continue
       
   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'
   424                 continue
   493                 continue
   425 
   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
   450                 _sshv1respondooberror(fout, ui.ferr, rsp.message)
   519                 _sshv1respondooberror(fout, ui.ferr, rsp.message)
   451             else:
   520             else:
   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 
   458         else:
   624         else:
   459             raise error.ProgrammingError('unhandled ssh server state: %s' %
   625             raise error.ProgrammingError('unhandled ssh server state: %s' %