summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--README1
-rw-r--r--paramiko/__init__.py5
-rw-r--r--paramiko/config.py105
-rw-r--r--paramiko/util.py86
-rw-r--r--tests/test_util.py9
5 files changed, 121 insertions, 85 deletions
diff --git a/README b/README
index 84384d5e..63ec6f03 100644
--- a/README
+++ b/README
@@ -269,7 +269,6 @@ v1.0 JIGGLYPUFF
* [sigh] release a fork of pycrypto with the speed improvements
--- BEFORE 1.6: ---
-* pull out util.parse_* (2 funcs) into a separate class
* try making bzr use SSHClient
* host-based auth (yuck!)
diff --git a/paramiko/__init__.py b/paramiko/__init__.py
index e9d504a4..a3769859 100644
--- a/paramiko/__init__.py
+++ b/paramiko/__init__.py
@@ -86,6 +86,7 @@ from file import BufferedFile
from agent import Agent, AgentKey
from pkey import PKey
from hostkeys import HostKeys
+from config import SSHConfig
# fix module names for epydoc
for x in (Transport, SecurityOptions, Channel, SFTPServer, SSHException,
@@ -94,7 +95,8 @@ for x in (Transport, SecurityOptions, Channel, SFTPServer, SSHException,
SFTP, SFTPClient, SFTPServer, Message, Packetizer, SFTPAttributes,
SFTPHandle, SFTPServerInterface, BufferedFile, Agent, AgentKey,
PKey, BaseSFTP, SFTPFile, ServerInterface, HostKeys, SSHClient,
- MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, ChannelException):
+ MissingHostKeyPolicy, AutoAddPolicy, RejectPolicy, ChannelException,
+ SSHConfig):
x.__module__ = 'paramiko'
from common import AUTH_SUCCESSFUL, AUTH_PARTIALLY_SUCCESSFUL, AUTH_FAILED, \
@@ -133,4 +135,5 @@ __all__ = [ 'Transport',
'Agent',
'AgentKey',
'HostKeys',
+ 'SSHConfig',
'util' ]
diff --git a/paramiko/config.py b/paramiko/config.py
new file mode 100644
index 00000000..01a6eb55
--- /dev/null
+++ b/paramiko/config.py
@@ -0,0 +1,105 @@
+# Copyright (C) 2006 Robey Pointer <robey@lag.net>
+#
+# This file is part of paramiko.
+#
+# Paramiko is free software; you can redistribute it and/or modify it under the
+# terms of the GNU Lesser General Public License as published by the Free
+# Software Foundation; either version 2.1 of the License, or (at your option)
+# any later version.
+#
+# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
+# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
+# details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
+# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
+
+"""
+L{SSHConfig}.
+"""
+
+import fnmatch
+
+
+class SSHConfig (object):
+ """
+ Representation of config information as stored in the format used by
+ OpenSSH. Queries can be made via L{lookup}. The format is described in
+ OpenSSH's C{ssh_config} man page. This class is provided primarily as a
+ convenience to posix users (since the OpenSSH format is a de-facto
+ standard on posix) but should work fine on Windows too.
+
+ @since: 1.6
+ """
+
+ def __init__(self):
+ """
+ Create a new OpenSSH config object.
+ """
+ self._config = [ { 'host': '*' } ]
+
+ def parse(self, file_obj):
+ """
+ Read an OpenSSH config from the given file object.
+
+ @param file_obj: a file-like object to read the config file from
+ @type file_obj: file
+ """
+ config = self._config[0]
+ for line in file_obj:
+ line = line.rstrip('\n').lstrip()
+ if (line == '') or (line[0] == '#'):
+ continue
+ if '=' in line:
+ key, value = line.split('=', 1)
+ key = key.strip().lower()
+ else:
+ # find first whitespace, and split there
+ i = 0
+ while (i < len(line)) and not line[i].isspace():
+ i += 1
+ if i == len(line):
+ raise Exception('Unparsable line: %r' % line)
+ key = line[:i].lower()
+ value = line[i:].lstrip()
+
+ if key == 'host':
+ # do we have a pre-existing host config to append to?
+ matches = [c for c in self._config if c['host'] == value]
+ if len(matches) > 0:
+ config = matches[0]
+ else:
+ config = { 'host': value }
+ self._config.append(config)
+ else:
+ config[key] = value
+
+ def lookup(self, hostname):
+ """
+ Return a dict of config options for a given hostname.
+
+ The host-matching rules of OpenSSH's C{ssh_config} man page are used,
+ which means that all configuration options from matching host
+ specifications are merged, with more specific hostmasks taking
+ precedence. In other words, if C{"Port"} is set under C{"Host *"}
+ and also C{"Host *.example.com"}, and the lookup is for
+ C{"ssh.example.com"}, then the port entry for C{"Host *.example.com"}
+ will win out.
+
+ The keys in the returned dict are all normalized to lowercase (look for
+ C{"port"}, not C{"Port"}. No other processing is done to the keys or
+ values.
+
+ @param hostname: the hostname to lookup
+ @type hostname: str
+ """
+ matches = [x for x in self._config if fnmatch.fnmatch(hostname, x['host'])]
+ # sort in order of shortest match (usually '*') to longest
+ matches.sort(lambda x,y: cmp(len(x['host']), len(y['host'])))
+ ret = {}
+ for m in matches:
+ ret.update(m)
+ del ret['host']
+ return ret
diff --git a/paramiko/util.py b/paramiko/util.py
index f39ff293..a8fcf6ea 100644
--- a/paramiko/util.py
+++ b/paramiko/util.py
@@ -22,13 +22,13 @@ Useful functions used by the rest of paramiko.
from __future__ import generators
-import fnmatch
import sys
import struct
import traceback
import threading
from paramiko.common import *
+from paramiko.config import SSHConfig
# Change by RogerB - python < 2.3 doesn't have enumerate so we implement it
@@ -201,89 +201,17 @@ def load_host_keys(filename):
def parse_ssh_config(file_obj):
"""
- Parse a config file of the format used by OpenSSH, and return an object
- that can be used to make queries to L{lookup_ssh_host_config}. The
- format is described in OpenSSH's C{ssh_config} man page. This method is
- provided primarily as a convenience to posix users (since the OpenSSH
- format is a de-facto standard on posix) but should work fine on Windows
- too.
-
- The return value is currently a list of dictionaries, each containing
- host-specific configuration, but this is considered an implementation
- detail and may be subject to change in later versions.
-
- @param file_obj: a file-like object to read the config file from
- @type file_obj: file
- @return: opaque configuration object
- @rtype: object
-
- @since: 1.5.2
+ Provided only as a backward-compatible wrapper around L{SSHConfig}.
"""
- ret = []
- config = { 'host': '*' }
- ret.append(config)
-
- for line in file_obj:
- line = line.rstrip('\n').lstrip()
- if (line == '') or (line[0] == '#'):
- continue
- if '=' in line:
- key, value = line.split('=', 1)
- key = key.strip().lower()
- else:
- # find first whitespace, and split there
- i = 0
- while (i < len(line)) and not line[i].isspace():
- i += 1
- if i == len(line):
- raise Exception('Unparsable line: %r' % line)
- key = line[:i].lower()
- value = line[i:].lstrip()
-
- if key == 'host':
- # do we have a pre-existing host config to append to?
- matches = [c for c in ret if c['host'] == value]
- if len(matches) > 0:
- config = matches[0]
- else:
- config = { 'host': value }
- ret.append(config)
- else:
- config[key] = value
-
- return ret
+ config = SSHConfig()
+ config.parse(file_obj)
+ return config
def lookup_ssh_host_config(hostname, config):
"""
- Return a dict of config options for a given hostname. The C{config} object
- must come from L{parse_ssh_config}.
-
- The host-matching rules of OpenSSH's C{ssh_config} man page are used, which
- means that all configuration options from matching host specifications are
- merged, with more specific hostmasks taking precedence. In other words, if
- C{"Port"} is set under C{"Host *"} and also C{"Host *.example.com"}, and
- the lookup is for C{"ssh.example.com"}, then the port entry for
- C{"Host *.example.com"} will win out.
-
- The keys in the returned dict are all normalized to lowercase (look for
- C{"port"}, not C{"Port"}. No other processing is done to the keys or
- values.
-
- @param hostname: the hostname to lookup
- @type hostname: str
- @param config: the config object to search
- @type config: object
-
- @since: 1.5.2
+ Provided only as a backward-compatible wrapper around L{SSHConfig}.
"""
- matches = [x for x in config if fnmatch.fnmatch(hostname, x['host'])]
- # sort in order of shortest match (usually '*') to longest
- matches.sort(lambda x,y: cmp(len(x['host']), len(y['host'])))
- ret = {}
- for m in matches:
- ret.update(m)
- del ret['host']
- return ret
+ return config.lookup(hostname)
def mod_inverse(x, m):
# it's crazy how small python can make this function.
diff --git a/tests/test_util.py b/tests/test_util.py
index 83852b06..c99c1c91 100644
--- a/tests/test_util.py
+++ b/tests/test_util.py
@@ -66,10 +66,11 @@ class UtilTest (unittest.TestCase):
global test_config_file
f = cStringIO.StringIO(test_config_file)
config = paramiko.util.parse_ssh_config(f)
- self.assertEquals(config, [ {'identityfile': '~/.ssh/id_rsa', 'host': '*', 'user': 'robey',
- 'crazy': 'something dumb '},
- {'host': '*.example.com', 'user': 'bjork', 'port': '3333'},
- {'host': 'spoo.example.com', 'crazy': 'something else'}])
+ self.assertEquals(config._config,
+ [ {'identityfile': '~/.ssh/id_rsa', 'host': '*', 'user': 'robey',
+ 'crazy': 'something dumb '},
+ {'host': '*.example.com', 'user': 'bjork', 'port': '3333'},
+ {'host': 'spoo.example.com', 'crazy': 'something else'}])
def test_2_host_config(self):
global test_config_file