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' % |