codecraftai-protocol-ssh 1.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,170 @@
1
+ """
2
+ SSH Client Implementation
3
+ """
4
+ import socket
5
+ import logging
6
+ from ccai.protocols.ssh.sshtransport import SshTransport
7
+ from ccai.protocols.ssh.sshconnection import SshConnection
8
+ from ccai.protocols.ssh.sshsftp import SshSFTPClient
9
+ from ccai.protocols.ssh.sshauth import SshAuthAgent
10
+
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+ class AutoAddPolicy:
15
+ """
16
+ Host-key policy that trusts and records any host key not already known.
17
+ Convenient for first-use / test environments; not for untrusted networks.
18
+ """
19
+ auto_add = True
20
+
21
+
22
+ class RejectPolicy:
23
+ """
24
+ Host-key policy that rejects any host key that is not already known.
25
+ """
26
+ auto_add = False
27
+
28
+
29
+ class SshClient:
30
+ """
31
+ High-level SSH Client.
32
+ Mimics paramiko.SSHClient interaction where possible.
33
+ """
34
+ def __init__(self):
35
+ self.transport = None
36
+ self.connection = None
37
+ self._policy = None # Missing host key policy
38
+ self._host_keys = {} # hostname -> set of trusted host-key blobs
39
+
40
+ def set_missing_host_key_policy(self, policy):
41
+ self._policy = policy
42
+ return
43
+
44
+ def load_system_host_keys(self, filename=None):
45
+ # Loading persisted known_hosts is not yet implemented; trust is
46
+ # governed by the configured missing-host-key policy and any keys
47
+ # added via add_host_key.
48
+ return
49
+
50
+ def add_host_key(self, hostname, key_blob):
51
+ """
52
+ Record a trusted host-key blob for a host.
53
+ """
54
+ known = self._host_keys.get(hostname)
55
+ if known is None:
56
+ known = set()
57
+ self._host_keys[hostname] = known
58
+ known.add(bytes(key_blob))
59
+ return
60
+
61
+ def _verify_host_key(self, hostname, key_type, key_blob):
62
+ """
63
+ Host-key trust verifier handed to the transport. Returns True only if
64
+ the key is already trusted for the host, or the policy auto-adds it.
65
+ """
66
+ blob = bytes(key_blob)
67
+ known = self._host_keys.get(hostname)
68
+ if known is not None and blob in known:
69
+ rtnval = True
70
+ return rtnval
71
+
72
+ auto_add = getattr(self._policy, "auto_add", False)
73
+ if auto_add is True:
74
+ self.add_host_key(hostname, blob)
75
+ logger.warning("Auto-added untrusted host key for %s (%s)", hostname, key_type)
76
+ rtnval = True
77
+ return rtnval
78
+
79
+ logger.error("Rejecting unknown host key for %s (%s)", hostname, key_type)
80
+ rtnval = False
81
+ return rtnval
82
+
83
+ def connect(self, hostname, port=22, username=None, password=None, pkey=None,
84
+ key_filename=None, timeout=None, allow_agent=True, look_for_keys=True,
85
+ compress=False, sock=None, gss_auth=False, gss_kex=False,
86
+ gss_deleg_creds=False, gss_host=None, banner_timeout=None,
87
+ auth_timeout=None, channel_timeout=None, disabled_algorithms=None):
88
+
89
+ # 1. Create Socket if not provided
90
+ if sock is None:
91
+ sock = socket.create_connection((hostname, port), timeout=timeout)
92
+
93
+ # 2. Start Transport and wire host-key trust before the handshake runs.
94
+ self.transport = SshTransport(sock)
95
+ self.transport.hostname = hostname
96
+ self.transport.host_key_verifier = self._verify_host_key
97
+ self.transport.connect() # sends version + starts KEX
98
+
99
+ # 3. Wait for key exchange to complete (both directions encrypted) before
100
+ # sending any authentication traffic.
101
+ kex_timeout = auth_timeout if auth_timeout is not None else 30
102
+ completed = self.transport.kex_complete.wait(timeout=kex_timeout)
103
+ if self.transport.kex_error is not None:
104
+ kex_error = self.transport.kex_error
105
+ self.close()
106
+ raise kex_error
107
+ if completed is not True:
108
+ self.close()
109
+ raise TimeoutError("SSH key exchange did not complete")
110
+ if self.transport.active is not True:
111
+ self.close()
112
+ raise ConnectionError("SSH transport closed during key exchange")
113
+
114
+ # 4. Authenticate. Register the auth agent so the transport delivers
115
+ # SERVICE_ACCEPT / USERAUTH_* responses to it.
116
+ auth_agent = SshAuthAgent(self.transport)
117
+ self.transport.register_handler(auth_agent.dispatch_message)
118
+
119
+ try:
120
+ if password:
121
+ auth_agent.authenticate_password(username, password)
122
+ else:
123
+ # 'none' probes for the list of allowed methods.
124
+ auth_agent.authenticate_none(username)
125
+ except Exception as e:
126
+ self.close()
127
+ raise e
128
+
129
+ # 5. Start the connection layer and route subsequent packets to it.
130
+ self.connection = SshConnection(self.transport)
131
+ if channel_timeout is not None:
132
+ # As in paramiko, 'channel_timeout' bounds how long a channel open waits for the peer to confirm it.
133
+ self.connection.channel_timeout = channel_timeout
134
+ self.transport.register_handler(self.connection.dispatch_message)
135
+ return
136
+
137
+ def get_transport(self):
138
+ return self.transport
139
+
140
+ def open_sftp(self):
141
+ """
142
+ Open SFTP Client.
143
+ """
144
+ chan = self.connection.open_channel("session")
145
+ rtnval = SshSFTPClient(chan)
146
+ return rtnval
147
+
148
+ def exec_command(self, command, bufsize=-1, timeout=None, get_pty=False, environment=None):
149
+ """
150
+ Execute a command.
151
+ Returns (stdin, stdout, stderr).
152
+ """
153
+ chan = self.connection.open_channel("session")
154
+ if get_pty:
155
+ chan.request_pty()
156
+ chan.request_exec(command)
157
+
158
+ rtnval = (None, chan, None)
159
+ return rtnval
160
+
161
+ def invoke_shell(self, term='vt100', width=80, height=24, width_pixels=0, height_pixels=0, environment=None):
162
+ chan = self.connection.open_channel("session")
163
+ chan.request_pty(term=term, width=width, height=height)
164
+ chan.request_shell()
165
+ return chan
166
+
167
+ def close(self):
168
+ if self.transport:
169
+ self.transport.close()
170
+ return