Package paramiko :: Module agent
[frames] | no frames]

Source Code for Module paramiko.agent

  1  # Copyright (C) 2003-2007  John Rochester <john@jrochester.org> 
  2  # 
  3  # This file is part of paramiko. 
  4  # 
  5  # Paramiko is free software; you can redistribute it and/or modify it under the 
  6  # terms of the GNU Lesser General Public License as published by the Free 
  7  # Software Foundation; either version 2.1 of the License, or (at your option) 
  8  # any later version. 
  9  # 
 10  # Paramiko is distributed in the hope that it will be useful, but WITHOUT ANY 
 11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
 13  # details. 
 14  # 
 15  # You should have received a copy of the GNU Lesser General Public License 
 16  # along with Paramiko; if not, write to the Free Software Foundation, Inc., 
 17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. 
 18   
 19  """ 
 20  SSH Agent interface 
 21  """ 
 22   
 23  import os 
 24  import socket 
 25  import struct 
 26  import sys 
 27  import threading 
 28  import time 
 29  import tempfile 
 30  import stat 
 31  from select import select 
 32  from paramiko.common import asbytes, io_sleep 
 33  from paramiko.py3compat import byte_chr 
 34   
 35  from paramiko.ssh_exception import SSHException 
 36  from paramiko.message import Message 
 37  from paramiko.pkey import PKey 
 38  from paramiko.util import retry_on_signal 
 39   
 40  cSSH2_AGENTC_REQUEST_IDENTITIES = byte_chr(11) 
 41  SSH2_AGENT_IDENTITIES_ANSWER = 12 
 42  cSSH2_AGENTC_SIGN_REQUEST = byte_chr(13) 
 43  SSH2_AGENT_SIGN_RESPONSE = 14 
 44   
 45   
46 -class AgentSSH(object):
47 - def __init__(self):
48 self._conn = None 49 self._keys = ()
50
51 - def get_keys(self):
52 """ 53 Return the list of keys available through the SSH agent, if any. If 54 no SSH agent was running (or it couldn't be contacted), an empty list 55 will be returned. 56 57 :return: 58 a tuple of `.AgentKey` objects representing keys available on the 59 SSH agent 60 """ 61 return self._keys
62
63 - def _connect(self, conn):
64 self._conn = conn 65 ptype, result = self._send_message(cSSH2_AGENTC_REQUEST_IDENTITIES) 66 if ptype != SSH2_AGENT_IDENTITIES_ANSWER: 67 raise SSHException('could not get keys from ssh-agent') 68 keys = [] 69 for i in range(result.get_int()): 70 keys.append(AgentKey(self, result.get_binary())) 71 result.get_string() 72 self._keys = tuple(keys)
73
74 - def _close(self):
75 #self._conn.close() 76 self._conn = None 77 self._keys = ()
78
79 - def _send_message(self, msg):
80 msg = asbytes(msg) 81 self._conn.send(struct.pack('>I', len(msg)) + msg) 82 l = self._read_all(4) 83 msg = Message(self._read_all(struct.unpack('>I', l)[0])) 84 return ord(msg.get_byte()), msg
85
86 - def _read_all(self, wanted):
87 result = self._conn.recv(wanted) 88 while len(result) < wanted: 89 if len(result) == 0: 90 raise SSHException('lost ssh-agent') 91 extra = self._conn.recv(wanted - len(result)) 92 if len(extra) == 0: 93 raise SSHException('lost ssh-agent') 94 result += extra 95 return result
96 97
98 -class AgentProxyThread(threading.Thread):
99 """ 100 Class in charge of communication between two channels. 101 """
102 - def __init__(self, agent):
103 threading.Thread.__init__(self, target=self.run) 104 self._agent = agent 105 self._exit = False
106
107 - def run(self):
108 try: 109 (r, addr) = self.get_connection() 110 self.__inr = r 111 self.__addr = addr 112 self._agent.connect() 113 self._communicate() 114 except: 115 #XXX Not sure what to do here ... raise or pass ? 116 raise
117
118 - def _communicate(self):
119 import fcntl 120 oldflags = fcntl.fcntl(self.__inr, fcntl.F_GETFL) 121 fcntl.fcntl(self.__inr, fcntl.F_SETFL, oldflags | os.O_NONBLOCK) 122 while not self._exit: 123 events = select([self._agent._conn, self.__inr], [], [], 0.5) 124 for fd in events[0]: 125 if self._agent._conn == fd: 126 data = self._agent._conn.recv(512) 127 if len(data) != 0: 128 self.__inr.send(data) 129 else: 130 self._close() 131 break 132 elif self.__inr == fd: 133 data = self.__inr.recv(512) 134 if len(data) != 0: 135 self._agent._conn.send(data) 136 else: 137 self._close() 138 break 139 time.sleep(io_sleep)
140
141 - def _close(self):
142 self._exit = True 143 self.__inr.close() 144 self._agent._conn.close()
145 146
147 -class AgentLocalProxy(AgentProxyThread):
148 """ 149 Class to be used when wanting to ask a local SSH Agent being 150 asked from a remote fake agent (so use a unix socket for ex.) 151 """
152 - def __init__(self, agent):
154
155 - def get_connection(self):
156 """ 157 Return a pair of socket object and string address. 158 159 May block! 160 """ 161 conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 162 try: 163 conn.bind(self._agent._get_filename()) 164 conn.listen(1) 165 (r, addr) = conn.accept() 166 return r, addr 167 except: 168 raise
169 170
171 -class AgentRemoteProxy(AgentProxyThread):
172 """ 173 Class to be used when wanting to ask a remote SSH Agent 174 """
175 - def __init__(self, agent, chan):
176 AgentProxyThread.__init__(self, agent) 177 self.__chan = chan
178
179 - def get_connection(self):
180 return self.__chan, None
181 182
183 -class AgentClientProxy(object):
184 """ 185 Class proxying request as a client: 186 187 #. client ask for a request_forward_agent() 188 #. server creates a proxy and a fake SSH Agent 189 #. server ask for establishing a connection when needed, 190 calling the forward_agent_handler at client side. 191 #. the forward_agent_handler launch a thread for connecting 192 the remote fake agent and the local agent 193 #. Communication occurs ... 194 """
195 - def __init__(self, chanRemote):
196 self._conn = None 197 self.__chanR = chanRemote 198 self.thread = AgentRemoteProxy(self, chanRemote) 199 self.thread.start()
200
201 - def __del__(self):
202 self.close()
203
204 - def connect(self):
205 """ 206 Method automatically called by ``AgentProxyThread.run``. 207 """ 208 if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'): 209 conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 210 try: 211 retry_on_signal(lambda: conn.connect(os.environ['SSH_AUTH_SOCK'])) 212 except: 213 # probably a dangling env var: the ssh agent is gone 214 return 215 elif sys.platform == 'win32': 216 import paramiko.win_pageant as win_pageant 217 if win_pageant.can_talk_to_agent(): 218 conn = win_pageant.PageantConnection() 219 else: 220 return 221 else: 222 # no agent support 223 return 224 self._conn = conn
225
226 - def close(self):
227 """ 228 Close the current connection and terminate the agent 229 Should be called manually 230 """ 231 if hasattr(self, "thread"): 232 self.thread._exit = True 233 self.thread.join(1000) 234 if self._conn is not None: 235 self._conn.close()
236 237
238 -class AgentServerProxy(AgentSSH):
239 """ 240 :param .Transport t: Transport used for SSH Agent communication forwarding 241 242 :raises SSHException: mostly if we lost the agent 243 """
244 - def __init__(self, t):
245 AgentSSH.__init__(self) 246 self.__t = t 247 self._dir = tempfile.mkdtemp('sshproxy') 248 os.chmod(self._dir, stat.S_IRWXU) 249 self._file = self._dir + '/sshproxy.ssh' 250 self.thread = AgentLocalProxy(self) 251 self.thread.start()
252
253 - def __del__(self):
254 self.close()
255
256 - def connect(self):
257 conn_sock = self.__t.open_forward_agent_channel() 258 if conn_sock is None: 259 raise SSHException('lost ssh-agent') 260 conn_sock.set_name('auth-agent') 261 self._connect(conn_sock)
262
263 - def close(self):
264 """ 265 Terminate the agent, clean the files, close connections 266 Should be called manually 267 """ 268 os.remove(self._file) 269 os.rmdir(self._dir) 270 self.thread._exit = True 271 self.thread.join(1000) 272 self._close()
273
274 - def get_env(self):
275 """ 276 Helper for the environnement under unix 277 278 :return: 279 a dict containing the ``SSH_AUTH_SOCK`` environnement variables 280 """ 281 return {'SSH_AUTH_SOCK': self._get_filename()}
282
283 - def _get_filename(self):
284 return self._file
285 286
287 -class AgentRequestHandler(object):
288 - def __init__(self, chanClient):
289 self._conn = None 290 self.__chanC = chanClient 291 chanClient.request_forward_agent(self._forward_agent_handler) 292 self.__clientProxys = []
293
294 - def _forward_agent_handler(self, chanRemote):
295 self.__clientProxys.append(AgentClientProxy(chanRemote))
296
297 - def __del__(self):
298 self.close()
299
300 - def close(self):
301 for p in self.__clientProxys: 302 p.close()
303 304
305 -class Agent(AgentSSH):
306 """ 307 Client interface for using private keys from an SSH agent running on the 308 local machine. If an SSH agent is running, this class can be used to 309 connect to it and retreive `.PKey` objects which can be used when 310 attempting to authenticate to remote SSH servers. 311 312 Upon initialization, a session with the local machine's SSH agent is 313 opened, if one is running. If no agent is running, initialization will 314 succeed, but `get_keys` will return an empty tuple. 315 316 :raises SSHException: 317 if an SSH agent is found, but speaks an incompatible protocol 318 """
319 - def __init__(self):
320 AgentSSH.__init__(self) 321 322 if ('SSH_AUTH_SOCK' in os.environ) and (sys.platform != 'win32'): 323 conn = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 324 try: 325 conn.connect(os.environ['SSH_AUTH_SOCK']) 326 except: 327 # probably a dangling env var: the ssh agent is gone 328 return 329 elif sys.platform == 'win32': 330 from . import win_pageant 331 if win_pageant.can_talk_to_agent(): 332 conn = win_pageant.PageantConnection() 333 else: 334 return 335 else: 336 # no agent support 337 return 338 self._connect(conn)
339
340 - def close(self):
341 """ 342 Close the SSH agent connection. 343 """ 344 self._close()
345 346
347 -class AgentKey(PKey):
348 """ 349 Private key held in a local SSH agent. This type of key can be used for 350 authenticating to a remote server (signing). Most other key operations 351 work as expected. 352 """
353 - def __init__(self, agent, blob):
354 self.agent = agent 355 self.blob = blob 356 self.name = Message(blob).get_text()
357
358 - def asbytes(self):
359 return self.blob
360
361 - def __str__(self):
362 return self.asbytes()
363
364 - def get_name(self):
365 return self.name
366
367 - def sign_ssh_data(self, data):
368 msg = Message() 369 msg.add_byte(cSSH2_AGENTC_SIGN_REQUEST) 370 msg.add_string(self.blob) 371 msg.add_string(data) 372 msg.add_int(0) 373 ptype, result = self.agent._send_message(msg) 374 if ptype != SSH2_AGENT_SIGN_RESPONSE: 375 raise SSHException('key cannot be used for signing') 376 return result.get_binary()
377