24
24
# have the same syntax. Also these strings will be executed by the
25
25
# shell, so shouldn't have strange characters in them.
27
from future_builtins import map
32
33
import duplicity.backend
33
34
from duplicity import globals
34
35
from duplicity import log
35
from duplicity.errors import * #@UnusedWildImport
36
from duplicity.errors import BackendException
37
38
class SSHPExpectBackend(duplicity.backend.Backend):
38
"""This backend copies files using scp. List not supported"""
39
"""This backend copies files using scp. List not supported. Filenames
40
should not need any quoting or this will break."""
39
41
def __init__(self, parsed_url):
40
42
"""scpBackend initializer"""
41
43
duplicity.backend.Backend.__init__(self, parsed_url)
76
78
def run_scp_command(self, commandline):
77
79
""" Run an scp command, responding to password prompts """
79
for n in range(1, globals.num_retries+1):
82
time.sleep(self.retry_delay)
83
log.Info("Running '%s' (attempt #%d)" % (commandline, n))
84
child = pexpect.spawn(commandline, timeout = None)
85
if globals.ssh_askpass:
90
if state == "authorizing":
91
match = child.expect([pexpect.EOF,
92
"(?i)timeout, server not responding",
93
"(?i)pass(word|phrase .*):",
94
"(?i)permission denied",
96
log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
98
log.Warn("Failed to authenticate")
101
log.Warn("Timeout waiting to authenticate")
104
child.sendline(self.password)
107
log.Warn("Invalid SSH password")
110
log.Warn("Remote host authentication failed (missing known_hosts entry?)")
112
elif state == "copying":
113
match = child.expect([pexpect.EOF,
114
"(?i)timeout, server not responding",
118
log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
122
log.Warn("Timeout waiting for response")
127
log.Warn("Remote host authentication failed (missing known_hosts entry?)")
129
elif state == "stalled":
130
match = child.expect([pexpect.EOF,
131
"(?i)timeout, server not responding",
133
log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
137
log.Warn("Stalled for too long, aborted copy")
141
child.close(force = True)
142
if child.exitstatus == 0:
144
log.Warn("Running '%s' failed (attempt #%d)" % (commandline, n))
145
log.Warn("Giving up trying to execute '%s' after %d attempts" % (commandline, globals.num_retries))
146
raise BackendException("Error running '%s'" % commandline)
81
log.Info("Running '%s'" % commandline)
82
child = pexpect.spawn(commandline, timeout = None)
83
if globals.ssh_askpass:
88
if state == "authorizing":
89
match = child.expect([pexpect.EOF,
90
"(?i)timeout, server not responding",
91
"(?i)pass(word|phrase .*):",
92
"(?i)permission denied",
94
log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
96
log.Warn("Failed to authenticate")
99
log.Warn("Timeout waiting to authenticate")
102
child.sendline(self.password)
105
log.Warn("Invalid SSH password")
108
log.Warn("Remote host authentication failed (missing known_hosts entry?)")
110
elif state == "copying":
111
match = child.expect([pexpect.EOF,
112
"(?i)timeout, server not responding",
116
log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
120
log.Warn("Timeout waiting for response")
125
log.Warn("Remote host authentication failed (missing known_hosts entry?)")
127
elif state == "stalled":
128
match = child.expect([pexpect.EOF,
129
"(?i)timeout, server not responding",
131
log.Debug("State = %s, Before = '%s'" % (state, child.before.strip()))
135
log.Warn("Stalled for too long, aborted copy")
139
child.close(force = True)
140
if child.exitstatus != 0:
141
raise BackendException("Error running '%s'" % commandline)
148
143
def run_sftp_command(self, commandline, commands):
149
144
""" Run an sftp command, responding to password prompts, passing commands from list """
160
155
"Couldn't delete file",
161
156
"open(.*): Failure"]
162
157
max_response_len = max([len(p) for p in responses[1:]])
163
for n in range(1, globals.num_retries+1):
166
time.sleep(self.retry_delay)
167
log.Info("Running '%s' (attempt #%d)" % (commandline, n))
168
child = pexpect.spawn(commandline, timeout = None, maxread=maxread)
173
match = child.expect(responses,
174
searchwindowsize=maxread+max_response_len)
175
log.Debug("State = sftp, Before = '%s'" % (child.before.strip()))
179
msg = "Timeout waiting for response"
182
if cmdloc < len(commands):
183
command = commands[cmdloc]
184
log.Info("sftp command: '%s'" % (command,))
185
child.sendline(command)
189
child.sendline(command)
193
child.sendline(self.password)
195
raise BackendException("Invalid SSH password.")
197
if not child.before.strip().startswith("mkdir"):
198
msg = "Permission denied"
201
msg = "Host key authenticity could not be verified (missing known_hosts entry?)"
204
if not child.before.strip().startswith("rm"):
205
msg = "Remote file or directory does not exist in command='%s'" % (commandline,)
208
if not child.before.strip().startswith("Removing"):
209
msg = "Could not delete file in command='%s'" % (commandline,)
158
log.Info("Running '%s'" % (commandline))
159
child = pexpect.spawn(commandline, timeout = None, maxread=maxread)
164
match = child.expect(responses,
165
searchwindowsize=maxread+max_response_len)
166
log.Debug("State = sftp, Before = '%s'" % (child.before.strip()))
170
msg = "Timeout waiting for response"
173
if cmdloc < len(commands):
174
command = commands[cmdloc]
175
log.Info("sftp command: '%s'" % (command,))
176
child.sendline(command)
180
child.sendline(command)
184
child.sendline(self.password)
186
raise BackendException("Invalid SSH password.")
188
if not child.before.strip().startswith("mkdir"):
189
msg = "Permission denied"
192
msg = "Host key authenticity could not be verified (missing known_hosts entry?)"
195
if not child.before.strip().startswith("rm"):
196
msg = "Remote file or directory does not exist in command='%s'" % (commandline,)
199
if not child.before.strip().startswith("Removing"):
212
200
msg = "Could not delete file in command='%s'" % (commandline,)
215
msg = "Could not open file in command='%s'" % (commandline,)
217
child.close(force = True)
218
if child.exitstatus == 0:
220
log.Warn("Running '%s' with commands:\n %s\n failed (attempt #%d): %s" % (commandline, "\n ".join(commands), n, msg))
221
raise BackendException("Giving up trying to execute '%s' with commands:\n %s\n after %d attempts" % (commandline, "\n ".join(commands), globals.num_retries))
203
msg = "Could not delete file in command='%s'" % (commandline,)
206
msg = "Could not open file in command='%s'" % (commandline,)
208
child.close(force = True)
209
if child.exitstatus == 0:
212
raise BackendException("Error running '%s': %s" % (commandline, msg))
223
def put(self, source_path, remote_filename = None):
214
def _put(self, source_path, remote_filename):
224
215
if globals.use_scp:
225
self.put_scp(source_path, remote_filename = remote_filename)
216
self.put_scp(source_path, remote_filename)
227
self.put_sftp(source_path, remote_filename = remote_filename)
218
self.put_sftp(source_path, remote_filename)
229
def put_sftp(self, source_path, remote_filename = None):
230
"""Use sftp to copy source_dir/filename to remote computer"""
231
if not remote_filename:
232
remote_filename = source_path.get_filename()
220
def put_sftp(self, source_path, remote_filename):
233
221
commands = ["put \"%s\" \"%s.%s.part\"" %
234
222
(source_path.name, self.remote_prefix, remote_filename),
235
223
"rename \"%s.%s.part\" \"%s%s\"" %
239
227
self.host_string))
240
228
self.run_sftp_command(commandline, commands)
242
def put_scp(self, source_path, remote_filename = None):
243
"""Use scp to copy source_dir/filename to remote computer"""
244
if not remote_filename:
245
remote_filename = source_path.get_filename()
230
def put_scp(self, source_path, remote_filename):
246
231
commandline = "%s %s %s %s:%s%s" % \
247
232
(self.scp_command, globals.ssh_options, source_path.name, self.host_string,
248
233
self.remote_prefix, remote_filename)
249
234
self.run_scp_command(commandline)
251
def get(self, remote_filename, local_path):
236
def _get(self, remote_filename, local_path):
252
237
if globals.use_scp:
253
238
self.get_scp(remote_filename, local_path)
255
240
self.get_sftp(remote_filename, local_path)
257
242
def get_sftp(self, remote_filename, local_path):
258
"""Use sftp to get a remote file"""
259
243
commands = ["get \"%s%s\" \"%s\"" %
260
244
(self.remote_prefix, remote_filename, local_path.name)]
261
245
commandline = ("%s %s %s" % (self.sftp_command,
262
246
globals.ssh_options,
263
247
self.host_string))
264
248
self.run_sftp_command(commandline, commands)
266
if not local_path.exists():
267
raise BackendException("File %s not found locally after get "
268
"from backend" % local_path.name)
270
250
def get_scp(self, remote_filename, local_path):
271
"""Use scp to get a remote file"""
272
251
commandline = "%s %s %s:%s%s %s" % \
273
252
(self.scp_command, globals.ssh_options, self.host_string, self.remote_prefix,
274
253
remote_filename, local_path.name)
275
254
self.run_scp_command(commandline)
277
if not local_path.exists():
278
raise BackendException("File %s not found locally after get "
279
"from backend" % local_path.name)
283
List files available for scp
285
Note that this command can get confused when dealing with
286
files with newlines in them, as the embedded newlines cannot
287
be distinguished from the file boundaries.
257
# Note that this command can get confused when dealing with
258
# files with newlines in them, as the embedded newlines cannot
259
# be distinguished from the file boundaries.
289
260
dirs = self.remote_dir.split(os.sep)
290
261
if len(dirs) > 0:
305
276
return [x for x in map(string.strip, l) if x]
307
def delete(self, filename_list):
309
Runs sftp rm to delete files. Files must not require quoting.
278
def _delete(self, filename):
311
279
commands = ["cd \"%s\"" % (self.remote_dir,)]
312
for fn in filename_list:
313
commands.append("rm \"%s\"" % fn)
280
commands.append("rm \"%s\"" % filename)
314
281
commandline = ("%s %s %s" % (self.sftp_command, globals.ssh_options, self.host_string))
315
282
self.run_sftp_command(commandline, commands)
317
duplicity.backend.register_backend("ssh", SSHPExpectBackend)
318
duplicity.backend.register_backend("scp", SSHPExpectBackend)
319
duplicity.backend.register_backend("sftp", SSHPExpectBackend)