Manual 2to3 fixups.
[cumulus.git] / python / cumulus / store / sftp.py
1 # Cumulus: Efficient Filesystem Backup to the Cloud
2 # Copyright (C) 2010 The Cumulus Developers
3 # See the AUTHORS file for a list of contributors.
4 #
5 # This program is free software; you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation; either version 2 of the License, or
8 # (at your option) any later version.
9 #
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 # GNU General Public License for more details.
14 #
15 # You should have received a copy of the GNU General Public License along
16 # with this program; if not, write to the Free Software Foundation, Inc.,
17 # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18
19 #needed for python 2.5
20
21 from __future__ import division, print_function, unicode_literals
22
23 from paramiko import Transport, SFTPClient, RSAKey, DSSKey
24 from paramiko.config import SSHConfig
25 import paramiko.util
26 from cumulus.store import Store, type_patterns, NotFoundError
27 import os, os.path
28 import getpass
29 import re
30 import sys
31
32
33 class SSHHostConfig(dict):
34     def __init__(self, hostname, user = None, filename = None):
35         #set defaults
36         if filename == None:
37             filename = os.path.expanduser('~/.ssh/config')
38
39         #read config file
40         ssh_config = SSHConfig()
41         with open(filename) as config_file:
42             ssh_config.parse(config_file)
43
44         self.update(ssh_config.lookup(hostname))
45
46         self.defaults={'port': 22, 'user': getpass.getuser(), 'hostname': hostname, 'hostkeyalias': hostname}
47
48         if user != None:
49             self['user'] = user
50
51     def __getitem__(self, key):
52         if key in self:
53             return dict.__getitem__(self,key)
54         elif key == 'hostkeyalias' and 'hostname' in self:
55             return dict.__getitem__(self,'hostname')
56         else:
57             return self.defaults[key]
58
59
60 class SFTPStore(Store):
61     """implements the sftp:// storage backend
62
63         configuration via openssh/sftp style urls and
64         .ssh/config files
65
66         does not support password authentication or password
67         protected authentication keys"""
68     def __init__(self, url, **kw):
69         if self.netloc.find('@') != -1:
70             user, self.netloc = self.netloc.split('@')
71         else:
72             user = None
73
74         self.config = SSHHostConfig(self.netloc, user)
75
76         host_keys = paramiko.util.load_host_keys(os.path.expanduser('~/.ssh/known_hosts'))
77         try:
78             self.hostkey = list(host_keys[self.config['hostkeyalias']].values())[0]
79         except:
80             print(str(self.config))
81             raise
82
83
84         if('identityfile' in self.config):
85             key_file = os.path.expanduser(self.config['identityfile'])
86             #not really nice but i don't see a cleaner way atm...
87             try:
88                 self.auth_key = RSAKey (key_file)
89             except SSHException as e:
90                 if e.message == 'Unable to parse file':
91                     self.auth_key = DSAKey (key_file)
92                 else:
93                     raise
94         else:
95             filename = os.path.expanduser('~/.ssh/id_rsa')
96             if os.path.exists(filename):
97                 self.auth_key = RSAKey(filename)
98             else:
99                 filename = os.path.expanduser('~/.ssh/id_dsa')
100                 if (os.path.exists(filename)):
101                     self.auth_key = DSSKey (filename)
102
103         self.__connect()
104
105     def __connect(self):
106         self.t = Transport((self.config['hostname'], self.config['port']))
107         self.t.connect(username = self.config['user'], pkey = self.auth_key)
108         self.client = SFTPClient.from_transport(self.t)
109         self.client.chdir(self.path)
110
111     def __build_fn(self, name):
112         return "%s/%s" % (self.path,  name)
113
114     def list(self, type):
115         return list(filter(type_patterns[type].match, self.client.listdir(self.path)))
116
117     def get(self, type, name):
118         return self.client.open(self.__build_fn(name), mode = 'rb')
119
120     def put(self, type, name, fp):
121         remote_file = self.client.open(self.__build_fn(name), mode = 'wb')
122         buf = fp.read(4096)
123         while (len(buf) > 0):
124             remote_file.write(buf)
125             buf = fp.read(4096)
126         remote_file.close()
127
128     def delete(self, type, name):
129         self.client.remove(self.__build_fn(name))
130
131     def stat(self, type, name):
132         try:
133             stat = self.client.stat(self.__build_fn(name))
134             return {'size': stat.st_size}
135         except IOError:
136             raise NotFoundError
137
138     def close(self):
139         """connection has to be explicitly closed, otherwise
140             it will hold the process running idefinitly"""
141         self.client.close()
142         self.t.close()
143
144 Store = SFTPStore