~ed.so/duplicity/backend_fixes

« back to all changes in this revision

Viewing changes to duplicity/backends/sshbackend.py

  • Committer: Kenneth Loafman
  • Author(s): edso
  • Date: 2012-03-08 14:47:34 UTC
  • mfrom: (844.3.2 trunk)
  • Revision ID: kenneth@loafman.com-20120308144734-v8t96obroo1iicrt
Merged in lp:~ed.so/duplicity/0.6-ssh_config

add ssh_config support (/etc/ssh/ssh_config + ~/.ssh/config) to paramiko sshbackend
@Ken: would you please announce that sshbackend is paramiko based native python now in the Changelog for the next release? 
this was missing in 0.6.18's Changelog

Show diffs side-by-side

added added

removed removed

Lines of Context:
3
3
# Copyright 2002 Ben Escoto <ben@emerose.org>
4
4
# Copyright 2007 Kenneth Loafman <kenneth@loafman.com>
5
5
# Copyright 2011 Alexander Zangerl <az@snafu.priv.at>
 
6
# Copyright 2012 edso (ssh_config added)
6
7
#
7
8
# $Id: sshbackend.py,v 1.2 2011/12/31 04:44:12 az Exp $
8
9
#
62
63
    def __init__(self, parsed_url):
63
64
        duplicity.backend.Backend.__init__(self, parsed_url)
64
65
 
65
 
        # host string could be [user@]hostname
66
 
        if parsed_url.username:
67
 
            username=parsed_url.username
68
 
        else:
69
 
            username=getpass.getuser()
70
 
 
71
66
        if parsed_url.path:
72
67
            # remove first leading '/'
73
68
            self.remote_dir = re.sub(r'^/', r'', parsed_url.path, 1)
74
69
        else:
75
70
            self.remote_dir = '.'
76
71
 
77
 
 
78
 
        # set up password
79
 
        if globals.ssh_askpass:
80
 
            password = self.get_password()
81
 
        else:
82
 
            if parsed_url.password:
83
 
                password = parsed_url.password
84
 
            else:
85
 
                password = None
86
72
        self.client = paramiko.SSHClient()
87
73
        # load known_hosts files
88
74
        # paramiko is very picky wrt format and bails out on any problem...
96
82
        except Exception, e:
97
83
            raise BackendException("could not load ~/.ssh/known_hosts, maybe corrupt?")
98
84
 
99
 
        # alternative ssh private key?
100
 
        keyfilename=None
 
85
        """ the next block reorganizes all host parameters into a
 
86
        dictionary like SSHConfig does. this dictionary 'self.config' 
 
87
        becomes the authorative source for these values from here on.
 
88
        rationale is that it is easiest to deal wrt overwriting multiple 
 
89
        values from ssh_config file. (ede 03/2012)
 
90
        """
 
91
        self.config={'hostname':parsed_url.hostname}
 
92
        # get system host config entries
 
93
        self.config.update(self.gethostconfig('/etc/ssh/ssh_config',parsed_url.hostname))
 
94
        # update with user's config file
 
95
        self.config.update(self.gethostconfig('~/.ssh/config',parsed_url.hostname))
 
96
        # update with url values
 
97
        ## username from url
 
98
        if parsed_url.username:
 
99
            self.config.update({'user':parsed_url.username})
 
100
        ## username from input
 
101
        if not 'user' in self.config:
 
102
            self.config.update({'user':getpass.getuser()})
 
103
        ## port from url
 
104
        if parsed_url.port:
 
105
            self.config.update({'port':parsed_url.port})
 
106
        ## ensure there is deafult 22 or an int value
 
107
        if 'port' in self.config:
 
108
            self.config.update({'port':int(self.config['port'])})
 
109
        else:
 
110
            self.config.update({'port':22})
 
111
        ## alternative ssh private key, identity file
101
112
        m=re.search("-oidentityfile=(\S+)",globals.ssh_options,re.I)
102
113
        if (m!=None):
103
114
            keyfilename=m.group(1)
104
 
 
105
 
        if parsed_url.port:
106
 
            portnumber=parsed_url.port
 
115
            self.config['identityfile'] = keyfilename
 
116
        ## ensure ~ is expanded and identity exists in dictionary
 
117
        if 'identityfile' in self.config:
 
118
            self.config['identityfile'] = os.path.expanduser(
 
119
                                            self.config['identityfile'])
107
120
        else:
108
 
            portnumber=22
 
121
            self.config['identityfile'] = None
 
122
 
 
123
        # get password, enable prompt if askpass is set
 
124
        self.use_getpass = globals.ssh_askpass
 
125
        ## set url values for beautiful login prompt
 
126
        parsed_url.username = self.config['user']
 
127
        parsed_url.hostname = self.config['hostname']
 
128
        password = self.get_password()
 
129
 
109
130
        try:
110
 
            self.client.connect(hostname=parsed_url.hostname, port=portnumber,
111
 
                                username=username, password=password,
112
 
                                allow_agent=True, look_for_keys=True,
113
 
                                key_filename=keyfilename)
 
131
            self.client.connect(hostname=self.config['hostname'], 
 
132
                                port=self.config['port'], 
 
133
                                username=self.config['user'], 
 
134
                                password=password,
 
135
                                allow_agent=True, 
 
136
                                look_for_keys=True,
 
137
                                key_filename=self.config['identityfile'])
114
138
        except Exception, e:
115
 
            raise BackendException("ssh connection to %s:%d failed: %s" % (parsed_url.hostname,portnumber,e))
 
139
            raise BackendException("ssh connection to %s@%s:%d failed: %s" % (
 
140
                                    self.config['user'],
 
141
                                    self.config['hostname'],
 
142
                                    self.config['port'],e))
116
143
        self.client.get_transport().set_keepalive((int)(globals.timeout / 2))
117
144
 
118
145
        # scp or sftp?
279
306
            raise BackendException("%sfailed(%d): %s" % (errorprefix,res,chan.recv_stderr(4096)))
280
307
        return output
281
308
 
 
309
    def gethostconfig(self, file, host):
 
310
        file = os.path.expanduser(file)
 
311
        if not os.path.isfile(file):
 
312
            return {}
 
313
        
 
314
        sshconfig = paramiko.SSHConfig()
 
315
        try:
 
316
            sshconfig.parse(open(file))
 
317
        except Exception, e:
 
318
            raise BackendException("could not load '%s', maybe corrupt?" % (file))
 
319
        
 
320
        return sshconfig.lookup(host)
 
321
 
 
322
 
 
323
 
282
324
duplicity.backend.register_backend("sftp", SftpBackend)
283
325
duplicity.backend.register_backend("scp", SftpBackend)
284
326
duplicity.backend.register_backend("ssh", SftpBackend)