Add 'wormhole ssh-add' and 'wormhole ssh-send' commands
This commit is contained in:
		
							parent
							
								
									026c8fd093
								
							
						
					
					
						commit
						069b76485b
					
				|  | @ -30,6 +30,7 @@ class Config(object): | ||||||
|         self.cwd = os.getcwd() |         self.cwd = os.getcwd() | ||||||
|         self.stdout = stdout |         self.stdout = stdout | ||||||
|         self.stderr = stderr |         self.stderr = stderr | ||||||
|  |         self.tor = False  # XXX? | ||||||
| 
 | 
 | ||||||
| def _compose(*decorators): | def _compose(*decorators): | ||||||
|     def decorate(f): |     def decorate(f): | ||||||
|  | @ -217,3 +218,43 @@ def receive(cfg, code, **kwargs): | ||||||
|         cfg.code = None |         cfg.code = None | ||||||
| 
 | 
 | ||||||
|     return go(cmd_receive.receive, cfg) |     return go(cmd_receive.receive, cfg) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @wormhole.command(name="ssh-add") | ||||||
|  | @click.option( | ||||||
|  |     "-c", "--code-length", default=2, | ||||||
|  |     metavar="NUMWORDS", | ||||||
|  |     help="length of code (in bytes/words)", | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     "--auth-file", "-f", | ||||||
|  |     default=expanduser('~/.ssh/authorized_keys'), | ||||||
|  |     type=click.Path(exists=False), | ||||||
|  | ) | ||||||
|  | @click.pass_context | ||||||
|  | def ssh_add(ctx, code_length, auth_file): | ||||||
|  |     from . import cmd_ssh | ||||||
|  |     ctx.obj.code_length = code_length | ||||||
|  |     ctx.obj.auth_file = auth_file | ||||||
|  |     return go(cmd_ssh.add, ctx.obj) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @wormhole.command(name="ssh-send") | ||||||
|  | @click.argument( | ||||||
|  |     "code", nargs=1, required=True, | ||||||
|  | ) | ||||||
|  | @click.option( | ||||||
|  |     "--yes", "-y", is_flag=True, | ||||||
|  |     help="Skip confirmation prompt to send key", | ||||||
|  | ) | ||||||
|  | @click.pass_obj | ||||||
|  | def ssh_send(cfg, code, yes): | ||||||
|  |     from . import cmd_ssh | ||||||
|  |     kind, keyid, pubkey = cmd_ssh.find_public_key() | ||||||
|  |     print("Sending public key type='{}' keyid='{}'".format(kind, keyid)) | ||||||
|  |     if yes is not True: | ||||||
|  |         click.confirm("Really send public key '{}' ?".format(keyid), abort=True) | ||||||
|  |     cfg.public_key = (kind, keyid, pubkey) | ||||||
|  |     cfg.code = code | ||||||
|  | 
 | ||||||
|  |     return go(cmd_ssh.send, cfg) | ||||||
|  |  | ||||||
							
								
								
									
										72
									
								
								src/wormhole/cli/cmd_ssh.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/wormhole/cli/cmd_ssh.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,72 @@ | ||||||
|  | from __future__ import print_function | ||||||
|  | 
 | ||||||
|  | from os.path import expanduser, exists | ||||||
|  | from twisted.internet.defer import inlineCallbacks | ||||||
|  | from twisted.internet import reactor | ||||||
|  | 
 | ||||||
|  | from .. import xfer_util | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | def find_public_key(): | ||||||
|  |     """ | ||||||
|  |     This looks for an appropriate SSH key to send, possibly querying | ||||||
|  |     the user in the meantime. | ||||||
|  | 
 | ||||||
|  |     Returns a 3-tuple: kind, keyid, pubkey_data | ||||||
|  |     """ | ||||||
|  | 
 | ||||||
|  |     # XXX FIXME don't blindly just send this one... | ||||||
|  |     with open(expanduser('~/.ssh/id_rsa.pub'), 'r') as f: | ||||||
|  |         pubkey = f.read() | ||||||
|  |     parts = pubkey.strip().split() | ||||||
|  |     kind = parts[0] | ||||||
|  |     keyid = 'unknown' if len(parts) <= 2 else parts[2] | ||||||
|  | 
 | ||||||
|  |     return kind, keyid, pubkey | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @inlineCallbacks | ||||||
|  | def send(cfg, reactor=reactor): | ||||||
|  |     yield xfer_util.send( | ||||||
|  |         reactor, | ||||||
|  |         u"lothar.com/wormhole/ssh-add", | ||||||
|  |         cfg.relay_url, | ||||||
|  |         data=cfg.public_key[2], | ||||||
|  |         code=cfg.code, | ||||||
|  |         use_tor=cfg.tor, | ||||||
|  |     ) | ||||||
|  |     print("Key sent.") | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @inlineCallbacks | ||||||
|  | def add(cfg, reactor=reactor): | ||||||
|  | 
 | ||||||
|  |     def on_code_created(code): | ||||||
|  |         print("Now tell the other user to run:") | ||||||
|  |         print() | ||||||
|  |         print("wormhole ssh-send {}".format(code)) | ||||||
|  |         print() | ||||||
|  | 
 | ||||||
|  |     pubkey = yield xfer_util.receive( | ||||||
|  |         reactor, | ||||||
|  |         u"lothar.com/wormhole/ssh-add", | ||||||
|  |         cfg.relay_url, | ||||||
|  |         None,  # allocate a code for us | ||||||
|  |         use_tor=cfg.tor, | ||||||
|  |         on_code=on_code_created, | ||||||
|  |     ) | ||||||
|  | 
 | ||||||
|  |     parts = pubkey.split() | ||||||
|  |     kind = parts[0] | ||||||
|  |     keyid = 'unknown' if len(parts) <= 2 else parts[2] | ||||||
|  | 
 | ||||||
|  |     path = cfg.auth_file | ||||||
|  |     if path == '-': | ||||||
|  |         print(pubkey.strip()) | ||||||
|  |     else: | ||||||
|  |         if not exists(path): | ||||||
|  |             print("Note: '{}' not found; will be created".format(path)) | ||||||
|  |         with open(path, 'a') as f: | ||||||
|  |             f.write('{}\n'.format(pubkey.strip())) | ||||||
|  |         print("Appended key type='{kind}' id='{key_id}' to '{auth_file}'".format( | ||||||
|  |             kind=kind, key_id=keyid, auth_file=path)) | ||||||
							
								
								
									
										101
									
								
								src/wormhole/xfer_util.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/wormhole/xfer_util.py
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,101 @@ | ||||||
|  | import json | ||||||
|  | from twisted.internet.defer import inlineCallbacks, returnValue | ||||||
|  | 
 | ||||||
|  | from .wormhole import wormhole | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @inlineCallbacks | ||||||
|  | def receive(reactor, appid, relay_url, code, use_tor=None, on_code=None): | ||||||
|  |     """ | ||||||
|  |     This is a convenience API which returns a Deferred that callbacks | ||||||
|  |     with a single chunk of data from another wormhole (and then closes | ||||||
|  |     the wormhole). Under the hood, it's just using an instance | ||||||
|  |     returned from :func:`wormhole.wormhole`. This is similar to the | ||||||
|  |     `wormhole receive` command. | ||||||
|  | 
 | ||||||
|  |     :param unicode appid: our application ID | ||||||
|  | 
 | ||||||
|  |     :param unicode relay_url: the relay URL to use | ||||||
|  | 
 | ||||||
|  |     :param unicode code: a pre-existing code to use, or None | ||||||
|  | 
 | ||||||
|  |     :param bool use_tor: True if we should use Tor, False to not use it (None for default) | ||||||
|  | 
 | ||||||
|  |     :param on_code: if not None, this is called when we have a code (even if you passed in one explicitly) | ||||||
|  |     :type on_code: single-argument callable | ||||||
|  |     """ | ||||||
|  |     wh = wormhole(appid, relay_url, reactor, use_tor) | ||||||
|  |     if code is None: | ||||||
|  |         code = yield wh.get_code() | ||||||
|  |     else: | ||||||
|  |         wh.set_code(code) | ||||||
|  |     # we'll call this no matter what, even if you passed in a code -- | ||||||
|  |     # maybe it should be only in the 'if' block above? | ||||||
|  |     if on_code: | ||||||
|  |         on_code(code) | ||||||
|  |     data = yield wh.get() | ||||||
|  |     data = json.loads(data) | ||||||
|  |     offer = data.get('offer', None) | ||||||
|  |     if not offer: | ||||||
|  |         raise Exception( | ||||||
|  |             "Do not understand response: {}".format(data) | ||||||
|  |         ) | ||||||
|  |     msg = None | ||||||
|  |     if 'message' in offer: | ||||||
|  |         msg = offer['message'] | ||||||
|  |         wh.send(json.dumps({"answer": {"message_ack": "ok"}})) | ||||||
|  | 
 | ||||||
|  |     else: | ||||||
|  |         raise Exception( | ||||||
|  |             "Unknown offer type: {}".format(offer.keys()) | ||||||
|  |         ) | ||||||
|  | 
 | ||||||
|  |     yield wh.close() | ||||||
|  |     returnValue(msg) | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|  | @inlineCallbacks | ||||||
|  | def send(reactor, appid, relay_url, data, code, use_tor=None, on_code=None): | ||||||
|  |     """ | ||||||
|  |     This is a convenience API which returns a Deferred that callbacks | ||||||
|  |     after a single chunk of data has been sent to another | ||||||
|  |     wormhole. Under the hood, it's just using an instance returned | ||||||
|  |     from :func:`wormhole.wormhole`. This is similar to the `wormhole | ||||||
|  |     send` command. | ||||||
|  | 
 | ||||||
|  |     :param unicode appid: the application ID | ||||||
|  | 
 | ||||||
|  |     :param unicode relay_url: the relay URL to use | ||||||
|  | 
 | ||||||
|  |     :param unicode code: a pre-existing code to use, or None | ||||||
|  | 
 | ||||||
|  |     :param bool use_tor: True if we should use Tor, False to not use it (None for default) | ||||||
|  | 
 | ||||||
|  |     :param on_code: if not None, this is called when we have a code (even if you passed in one explicitly) | ||||||
|  |     :type on_code: single-argument callable | ||||||
|  |     """ | ||||||
|  |     wh = wormhole(appid, relay_url, reactor, use_tor) | ||||||
|  |     if code is None: | ||||||
|  |         code = yield wh.get_code() | ||||||
|  |     else: | ||||||
|  |         wh.set_code(code) | ||||||
|  |     if on_code: | ||||||
|  |         on_code(code) | ||||||
|  | 
 | ||||||
|  |     wh.send( | ||||||
|  |         json.dumps({ | ||||||
|  |             "offer": { | ||||||
|  |                 "message": data | ||||||
|  |             } | ||||||
|  |         }) | ||||||
|  |     ) | ||||||
|  |     data = yield wh.get() | ||||||
|  |     data = json.loads(data) | ||||||
|  |     answer = data.get('answer', None) | ||||||
|  |     yield wh.close() | ||||||
|  |     if answer: | ||||||
|  |         returnValue(None) | ||||||
|  |     else: | ||||||
|  |         raise Exception( | ||||||
|  |             "Unknown answer: {}".format(data) | ||||||
|  |         ) | ||||||
		Loading…
	
		Reference in New Issue
	
	Block a user