blob: 3062b4c5819b467d47e012ffbd8643dc9611b0be [file] [log] [blame]
Chip Bolingf5af85d2019-02-12 15:36:17 -06001# Copyright 2017-present Adtran, Inc.
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import structlog
16from twisted.internet.defer import Deferred, succeed
17from twisted.internet.protocol import Factory, Protocol
18from twisted.conch.client.knownhosts import ConsoleUI, KnownHostsFile
19from twisted.conch.endpoints import SSHCommandClientEndpoint
20from twisted.internet import reactor
21
22log = structlog.get_logger()
23_open = open
24
25
26class RCmd(object):
27 """
28 Execute a one-time remote command via SSH
29 """
30 def __init__(self, host, username, password,
31 command,
32 port=None,
33 keys=None,
34 known_hosts=None,
35 agent=None):
36 self.reactor = reactor
37 self.host = host
38 self.port = port
39 self.username = username
40 self.password = password
41 self.keys = keys
42 # self.knownHosts = known_hosts
43 self.knownHosts = known_hosts
44 self.agent = agent
45 self.command = command
46 self.ui = RCmd.FixedResponseUI(True)
47
48 class NoiseProtocol(Protocol):
49 def __init__(self):
50 self.finished = Deferred()
51 self.strings = ["bif", "pow", "zot"]
52
53 def connectionMade(self):
54 log.debug('connection-made')
55 self._send_noise()
56
57 def _send_noise(self):
58 if self.strings:
59 self.transport.write(self.strings.pop(0) + "\n")
60 else:
61 self.transport.loseConnection()
62
63 def dataReceived(self, data):
64 log.debug('rx', data=data)
65 if self.finished is not None and not self.finished.called:
66 self.finished.callback(data)
67 self._send_noise()
68
69 def connectionLost(self, reason):
70 log.debug('connection-lost')
71 if not self.finished.called:
72 self.finished.callback(reason)
73
74 class PermissiveKnownHosts(KnownHostsFile):
75 def verifyHostKey(self, ui, hostname, ip, key):
76 log.debug('verifyHostKey')
77 return True
78
79 class FixedResponseUI(ConsoleUI):
80 def __init__(self, result):
81 super(RCmd.FixedResponseUI, self).__init__(lambda: _open("/dev/null",
82 "r+b",
83 buffering=0))
84 self.result = result
85
86 def prompt(self, _):
87 log.debug('prompt')
88 return succeed(True)
89
90 def warn(self, text):
91 log.debug('warn')
92 pass
93
94 def _endpoint_for_command(self, command):
95 return SSHCommandClientEndpoint.newConnection(
96 self.reactor, command, self.username, self.host,
97 port=self.port,
98 password=self.password,
99 keys=self.keys,
100 agentEndpoint=self.agent,
101 knownHosts=self.knownHosts,
102 ui=self.ui
103 )
104
105 def execute(self):
106 endpoint = self._endpoint_for_command(self.command)
107 factory = Factory()
108 factory.protocol = RCmd.NoiseProtocol
109
110 d = endpoint.connect(factory)
111 d.addCallback(lambda proto: proto.finished)
112 return d