Merge lp:~bit-team/backintime/password into lp:backintime/1.0
- password
- Merge into release-1.0
Proposed by
Germar
Status: | Merged |
---|---|
Merged at revision: | 836 |
Proposed branch: | lp:~bit-team/backintime/password |
Merge into: | lp:backintime/1.0 |
Diff against target: |
2372 lines (+1492/-76) 31 files modified
CHANGES (+1/-0) common/Makefile.template (+5/-0) common/askpass.py (+49/-0) common/backintime (+5/-1) common/backintime-askpass (+27/-0) common/backintime.desktop (+8/-0) common/backintime.py (+35/-0) common/config.py (+88/-1) common/configfile.py (+6/-4) common/debian_specific/conffiles (+1/-0) common/debian_specific/control (+1/-1) common/debian_specific/rules (+4/-0) common/dummytools.py (+5/-3) common/makedeb-src.sh (+7/-0) common/man/C/backintime.1 (+5/-3) common/mount.py (+25/-14) common/password.py (+446/-0) common/password_ipc.py (+112/-0) common/sshtools.py (+63/-3) common/tools.py (+106/-2) gnome/backintime-gnome (+4/-1) gnome/debian_specific/control (+1/-1) gnome/messagebox.py (+20/-2) gnome/settingsdialog.glade (+183/-25) gnome/settingsdialog.py (+95/-4) gnome/textinputdialog.glade (+14/-1) kde4/app.py (+3/-3) kde4/debian_specific/control (+1/-1) kde4/messagebox.py (+49/-0) kde4/settingsdialog.py (+122/-6) makedeb.sh (+1/-0) |
To merge this branch: | bzr merge lp:~bit-team/backintime/password |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Dan | Approve | ||
Review via email: mp+152305@code.launchpad.net |
Commit message
Description of the change
Add ability to store passwords in Backintime. There are three different modes:
-store password in system keyring
-store password in system keyring and cache it as soon as the user log in first time so it can be used during cronjobs if the user is logged out
- ask user for Password
To post a comment you must log in.
Revision history for this message
Dan (danleweb) : | # |
review:
Approve
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === modified file 'CHANGES' |
2 | --- CHANGES 2013-03-04 21:16:20 +0000 |
3 | +++ CHANGES 2013-03-07 23:23:23 +0000 |
4 | @@ -1,6 +1,7 @@ |
5 | Back In Time |
6 | |
7 | Version 1.0.22 |
8 | +* Add Password storage mode ssh |
9 | * Add "Full rsync mode" (can be faster but ...) |
10 | * Fix bug: "Restore to..." failed due to spaces in directory name (https://bugs.launchpad.net/backintime/+bug/1096319) |
11 | * Fix bug: host not found in known_hosts if port != 22 (https://bugs.launchpad.net/backintime/+bug/1130356) |
12 | |
13 | === modified file 'common/Makefile.template' |
14 | --- common/Makefile.template 2012-11-02 17:34:44 +0000 |
15 | +++ common/Makefile.template 2013-03-07 23:23:23 +0000 |
16 | @@ -38,4 +38,9 @@ |
17 | #install application |
18 | install -d $(DEST)/bin |
19 | install backintime $(DEST)/bin |
20 | + install backintime-askpass $(DEST)/bin |
21 | + |
22 | + #install autostart |
23 | + install -d $(DEST)/../etc/xdg/autostart |
24 | + install --mode=644 backintime.desktop $(DEST)/../etc/xdg/autostart |
25 | |
26 | |
27 | === added file 'common/askpass.py' |
28 | --- common/askpass.py 1970-01-01 00:00:00 +0000 |
29 | +++ common/askpass.py 2013-03-07 23:23:23 +0000 |
30 | @@ -0,0 +1,49 @@ |
31 | +#!/usr/bin/env python |
32 | +# Copyright (c) 2012-2013 Germar Reitze |
33 | +# |
34 | +# This program is free software; you can redistribute it and/or modify |
35 | +# it under the terms of the GNU General Public License as published by |
36 | +# the Free Software Foundation; either version 2 of the License, or |
37 | +# (at your option) any later version. |
38 | +# |
39 | +# This program is distributed in the hope that it will be useful, |
40 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
41 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
42 | +# GNU General Public License for more details. |
43 | +# |
44 | +# You should have received a copy of the GNU General Public License along |
45 | +# with this program; if not, write to the Free Software Foundation, Inc., |
46 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
47 | + |
48 | +import os |
49 | +import sys |
50 | +import base64 |
51 | + |
52 | +import password |
53 | +import password_ipc |
54 | +import tools |
55 | +import config |
56 | + |
57 | +if __name__ == '__main__': |
58 | + """ |
59 | + return password. |
60 | + """ |
61 | + cfg = config.Config() |
62 | + tools.load_env(cfg) |
63 | + try: |
64 | + profile_id = os.environ['ASKPASS_PROFILE_ID'] |
65 | + mode = os.environ['ASKPASS_MODE'] |
66 | + except KeyError: |
67 | + sys.exit(1) |
68 | + try: |
69 | + temp_file = os.environ['ASKPASS_TEMP'] |
70 | + except KeyError: |
71 | + #normal mode, get password from module password |
72 | + pw = password.Password(cfg) |
73 | + print(pw.get_password(None, profile_id, mode)) |
74 | + else: |
75 | + #temp mode |
76 | + fifo = password_ipc.FIFO(temp_file) |
77 | + pw_base64 = fifo.read(5) |
78 | + if pw_base64: |
79 | + print(base64.decodestring(pw_base64)) |
80 | \ No newline at end of file |
81 | |
82 | === modified file 'common/backintime' |
83 | --- common/backintime 2009-11-02 13:52:00 +0000 |
84 | +++ common/backintime 2013-03-07 23:23:23 +0000 |
85 | @@ -23,5 +23,9 @@ |
86 | APP_PATH="/usr/share/backintime/common" |
87 | fi |
88 | |
89 | -python $APP_PATH/backintime.py "$@" |
90 | +#starting a new ssh-agent all the time is just a workaround for |
91 | +#https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/841672 |
92 | +#normally this should only be necessary if run as cronjob |
93 | +#and the user is not logged in |
94 | +ssh-agent python $APP_PATH/backintime.py "$@" |
95 | |
96 | |
97 | === added file 'common/backintime-askpass' |
98 | --- common/backintime-askpass 1970-01-01 00:00:00 +0000 |
99 | +++ common/backintime-askpass 2013-03-07 23:23:23 +0000 |
100 | @@ -0,0 +1,27 @@ |
101 | +#!/bin/sh |
102 | + |
103 | +# Back In Time |
104 | +# Copyright (C) 2008-2009 Oprea Dan, Bart de Koning, Richard Bailey |
105 | +# |
106 | +# This program is free software; you can redistribute it and/or modify |
107 | +# it under the terms of the GNU General Public License as published by |
108 | +# the Free Software Foundation; either version 2 of the License, or |
109 | +# (at your option) any later version. |
110 | +# |
111 | +# This program is distributed in the hope that it will be useful, |
112 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
113 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
114 | +# GNU General Public License for more details. |
115 | +# |
116 | +# You should have received a copy of the GNU General Public License along |
117 | +# with this program; if not, write to the Free Software Foundation, Inc., |
118 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
119 | + |
120 | +if [ -f askpass.py ]; then |
121 | + APP_PATH="." |
122 | +else |
123 | + APP_PATH="/usr/share/backintime/common" |
124 | +fi |
125 | + |
126 | +python $APP_PATH/askpass.py "$@" |
127 | + |
128 | |
129 | === added file 'common/backintime.desktop' |
130 | --- common/backintime.desktop 1970-01-01 00:00:00 +0000 |
131 | +++ common/backintime.desktop 2013-03-07 23:23:23 +0000 |
132 | @@ -0,0 +1,8 @@ |
133 | +[Desktop Entry] |
134 | +Version=1.0 |
135 | +Name=Backintime Password Cache |
136 | +Exec=backintime --pw-cache start |
137 | +Comment=Cache passwords for non-interactive Backintime cronjobs |
138 | +Icon=gtk-save |
139 | +Terminal=false |
140 | +Type=Application |
141 | |
142 | === modified file 'common/backintime.py' |
143 | --- common/backintime.py 2012-10-21 13:35:30 +0000 |
144 | +++ common/backintime.py 2013-03-07 23:23:23 +0000 |
145 | @@ -28,6 +28,7 @@ |
146 | import tools |
147 | import sshtools |
148 | import mount |
149 | +import password |
150 | |
151 | _=gettext.gettext |
152 | |
153 | @@ -46,6 +47,7 @@ |
154 | |
155 | def take_snapshot( cfg, force = True ): |
156 | logger.openlog() |
157 | + tools.load_env(cfg) |
158 | _mount(cfg) |
159 | snapshots.Snapshots( cfg ).take_snapshot( force ) |
160 | _umount(cfg) |
161 | @@ -105,6 +107,8 @@ |
162 | print '\tUnmount the profile.' |
163 | print '--benchmark-cipher [file-size]' |
164 | print '\tShow a benchmark of all ciphers for ssh transfer (and exit)' |
165 | + print '--pw-cache [start|stop|restart|reload|status]' |
166 | + print '\tControl Password Cache for non-interactive cronjobs' |
167 | print '-v | --version' |
168 | print '\tShow version (and exit)' |
169 | print '--license' |
170 | @@ -251,6 +255,37 @@ |
171 | print('ssh is not configured !') |
172 | sys.exit(0) |
173 | |
174 | + if arg == '--pw-cache': |
175 | + if not cfg.is_configured(): |
176 | + print "The application is not configured !" |
177 | + sys.exit(0) |
178 | + else: |
179 | + daemon = password.Password_Cache(cfg) |
180 | + try: |
181 | + if sys.argv[index + 1].startswith('-'): |
182 | + daemon.run() |
183 | + elif 'start' == sys.argv[index + 1]: |
184 | + daemon.start() |
185 | + elif 'stop' == sys.argv[index + 1]: |
186 | + daemon.stop() |
187 | + elif 'restart' == sys.argv[index + 1]: |
188 | + daemon.restart() |
189 | + elif 'reload' == sys.argv[index + 1]: |
190 | + daemon.reload() |
191 | + elif 'status' == sys.argv[index + 1]: |
192 | + print 'Backintime Password Cache:', |
193 | + if daemon.status(): |
194 | + print 'running' |
195 | + else: |
196 | + print 'not running' |
197 | + else: |
198 | + print "Unknown command" |
199 | + print "usage: %s %s start|stop|restart|reload|status" % (sys.argv[0], sys.argv[index]) |
200 | + sys.exit(2) |
201 | + except IndexError: |
202 | + daemon.run() |
203 | + sys.exit(0) |
204 | + |
205 | if arg == '--snapshots' or arg == '-s': |
206 | continue |
207 | |
208 | |
209 | === modified file 'common/config.py' |
210 | --- common/config.py 2013-03-04 21:16:20 +0000 |
211 | +++ common/config.py 2013-03-07 23:23:23 +0000 |
212 | @@ -29,6 +29,7 @@ |
213 | import mount |
214 | import sshtools |
215 | ##import dummytools |
216 | +import password |
217 | |
218 | _=gettext.gettext |
219 | |
220 | @@ -91,9 +92,11 @@ |
221 | SNAPSHOT_MODES = { |
222 | #mode : (<mounttools>, _('ComboBox Text') ), |
223 | 'local' : (None, _('Local') ), |
224 | - 'ssh' : (sshtools.SSH, _('SSH (without password)') ) |
225 | + 'ssh' : (sshtools.SSH, _('SSH') ) |
226 | ## 'dummy' : (dummytools.Dummy, _('Dummy') ) |
227 | } |
228 | + |
229 | + SNAPSHOT_MODES_NEED_PASSWORD = ['ssh', 'dummy'] |
230 | |
231 | MOUNT_ROOT = '/tmp/backintime' |
232 | |
233 | @@ -219,6 +222,7 @@ |
234 | self.save() |
235 | |
236 | self.current_hash_id = 'local' |
237 | + self.pw = password.Password(self) |
238 | |
239 | def save( self ): |
240 | configfile.ConfigFile.save( self, self._LOCAL_CONFIG_PATH ) |
241 | @@ -449,6 +453,26 @@ |
242 | if len(path) == 0: |
243 | path = './' |
244 | return (host, port, user, path) |
245 | + |
246 | + def get_ssh_private_key_file(self, profile_id = None): |
247 | + ssh = self.get_ssh_private_key_folder() |
248 | + default = '' |
249 | + for file in ['id_dsa', 'id_rsa', 'identity']: |
250 | + private_key = os.path.join(ssh, file) |
251 | + if os.path.isfile(private_key): |
252 | + default = private_key |
253 | + break |
254 | + file = self.get_profile_str_value( 'snapshots.ssh.private_key_file', default, profile_id ) |
255 | + if len(file) == 0: |
256 | + return default |
257 | + else: |
258 | + return file |
259 | + |
260 | + def get_ssh_private_key_folder(self): |
261 | + return os.path.join(os.path.expanduser('~'), '.ssh') |
262 | + |
263 | + def set_ssh_private_key_file( self, value, profile_id = None ): |
264 | + self.set_profile_str_value( 'snapshots.ssh.private_key_file', value, profile_id ) |
265 | |
266 | ## def get_dummy_host( self, profile_id = None ): |
267 | ## return self.get_profile_str_value( 'snapshots.dummy.host', '', profile_id ) |
268 | @@ -468,6 +492,57 @@ |
269 | ## def set_dummy_user( self, value, profile_id = None ): |
270 | ## self.set_profile_str_value( 'snapshots.dummy.user', value, profile_id ) |
271 | |
272 | + def get_password_save( self, profile_id = None, mode = None ): |
273 | + if mode is None: |
274 | + mode = self.get_snapshots_mode(profile_id) |
275 | + return self.get_profile_bool_value( 'snapshots.%s.password.save' % mode, True, profile_id ) |
276 | + |
277 | + def set_password_save( self, value, profile_id = None, mode = None ): |
278 | + if mode is None: |
279 | + mode = self.get_snapshots_mode(profile_id) |
280 | + self.set_profile_bool_value( 'snapshots.%s.password.save' % mode, value, profile_id ) |
281 | + |
282 | + def get_password_use_cache( self, profile_id = None, mode = None ): |
283 | + if mode is None: |
284 | + mode = self.get_snapshots_mode(profile_id) |
285 | + default = not tools.check_home_encrypt() |
286 | + return self.get_profile_bool_value( 'snapshots.%s.password.use_cache' % mode, default, profile_id ) |
287 | + |
288 | + def set_password_use_cache( self, value, profile_id = None, mode = None ): |
289 | + if mode is None: |
290 | + mode = self.get_snapshots_mode(profile_id) |
291 | + self.set_profile_bool_value( 'snapshots.%s.password.use_cache' % mode, value, profile_id ) |
292 | + |
293 | + def get_password( self, parent = None, profile_id = None, mode = None, only_from_keyring = False ): |
294 | + if profile_id is None: |
295 | + profile_id = self.get_current_profile() |
296 | + if mode is None: |
297 | + mode = self.get_snapshots_mode(profile_id) |
298 | + return self.pw.get_password(parent, profile_id, mode, only_from_keyring) |
299 | + |
300 | + def set_password( self, password, profile_id = None, mode = None ): |
301 | + if profile_id is None: |
302 | + profile_id = self.get_current_profile() |
303 | + if mode is None: |
304 | + mode = self.get_snapshots_mode(profile_id) |
305 | + self.pw.set_password(password, profile_id, mode) |
306 | + |
307 | + def get_keyring_backend(self): |
308 | + return self.get_str_value('keyring.backend', '') |
309 | + |
310 | + def set_keyring_backend(self, value): |
311 | + self.set_str_value('keyring.backend', value) |
312 | + |
313 | + def get_keyring_service_name( self, profile_id = None, mode = None ): |
314 | + if mode is None: |
315 | + mode = self.get_snapshots_mode(profile_id) |
316 | + return 'backintime/%s' % mode |
317 | + |
318 | + def get_keyring_user_name( self, profile_id = None ): |
319 | + if profile_id is None: |
320 | + profile_id = self.get_current_profile() |
321 | + return 'profile_id_%s' % profile_id |
322 | + |
323 | def get_auto_host_user_profile( self, profile_id = None ): |
324 | return self.get_profile_bool_value( 'snapshots.path.auto', True, profile_id ) |
325 | |
326 | @@ -891,6 +966,18 @@ |
327 | def get_take_snapshot_user_callback( self, profile_id = None ): |
328 | return os.path.join( self._LOCAL_CONFIG_FOLDER, "user-callback" ) |
329 | |
330 | + def get_password_cache_folder( self ): |
331 | + return os.path.join( self._LOCAL_DATA_FOLDER, "password_cache" ) |
332 | + |
333 | + def get_password_cache_pid( self ): |
334 | + return os.path.join( self.get_password_cache_folder(), "PID" ) |
335 | + |
336 | + def get_password_cache_fifo( self ): |
337 | + return os.path.join( self.get_password_cache_folder(), "FIFO" ) |
338 | + |
339 | + def get_cron_env_file( self ): |
340 | + return os.path.join( self._LOCAL_DATA_FOLDER, "cron_env" ) |
341 | + |
342 | def get_license( self ): |
343 | return tools.read_file( os.path.join( self.get_doc_path(), 'LICENSE' ), '' ) |
344 | |
345 | |
346 | === modified file 'common/configfile.py' |
347 | --- common/configfile.py 2012-10-12 07:09:14 +0000 |
348 | +++ common/configfile.py 2013-03-07 23:23:23 +0000 |
349 | @@ -61,11 +61,11 @@ |
350 | except: |
351 | pass |
352 | |
353 | - def load( self, filename ): |
354 | + def load( self, filename, **kwargs ): |
355 | self.dict = {} |
356 | - self.append( filename ) |
357 | + self.append( filename, **kwargs ) |
358 | |
359 | - def append( self, filename ): |
360 | + def append( self, filename, maxsplit = -1 ): |
361 | lines = [] |
362 | |
363 | try: |
364 | @@ -76,7 +76,7 @@ |
365 | pass |
366 | |
367 | for line in lines: |
368 | - items = line.split( '=' ) |
369 | + items = line.split( '=', maxsplit ) |
370 | if len( items ) == 2: |
371 | self.dict[ items[ 0 ] ] = items[ 1 ][ : -1 ] |
372 | |
373 | @@ -137,6 +137,8 @@ |
374 | for key in remove_keys: |
375 | del self.dict[ key ] |
376 | |
377 | + def get_keys(self): |
378 | + return self.dict.keys() |
379 | |
380 | class ConfigFileWithProfiles( ConfigFile ): |
381 | def __init__( self, default_profile_name = '' ): |
382 | |
383 | === added file 'common/debian_specific/conffiles' |
384 | --- common/debian_specific/conffiles 1970-01-01 00:00:00 +0000 |
385 | +++ common/debian_specific/conffiles 2013-03-07 23:23:23 +0000 |
386 | @@ -0,0 +1,1 @@ |
387 | +/etc/xdg/autostart/backintime.desktop |
388 | |
389 | === modified file 'common/debian_specific/control' |
390 | --- common/debian_specific/control 2013-01-01 20:26:37 +0000 |
391 | +++ common/debian_specific/control 2013-03-07 23:23:23 +0000 |
392 | @@ -6,7 +6,7 @@ |
393 | Maintainer: BIT Team <bit-team@lists.launchpad.net> |
394 | Homepage: http://backintime.le-web.org |
395 | Architecture: all |
396 | -Depends: python, rsync, cron, openssh-client, sshfs |
397 | +Depends: python, rsync, cron, openssh-client, sshfs, python-keyring |
398 | Recommends: powermgmt-base |
399 | Conflicts: backintime |
400 | Replaces: backintime |
401 | |
402 | === modified file 'common/debian_specific/rules' |
403 | --- common/debian_specific/rules 2010-03-01 13:22:45 +0000 |
404 | +++ common/debian_specific/rules 2013-03-07 23:23:23 +0000 |
405 | @@ -38,6 +38,10 @@ |
406 | #install application |
407 | #dh_installdirs /usr/bin |
408 | dh_install backintime /usr/bin |
409 | + dh_install backintime-askpass /usr/bin |
410 | + |
411 | + #install autostart |
412 | + dh_install backintime.desktop /etc/xdg/autostart |
413 | |
414 | #install langs |
415 | [INSTALL_LANGS] |
416 | |
417 | === modified file 'common/dummytools.py' |
418 | --- common/dummytools.py 2012-10-23 19:26:20 +0000 |
419 | +++ common/dummytools.py 2013-03-07 23:23:23 +0000 |
420 | @@ -1,4 +1,4 @@ |
421 | -# Copyright (c) 2012 Germar Reitze |
422 | +# Copyright (c) 2012-2013 Germar Reitze |
423 | # |
424 | # This program is free software; you can redistribute it and/or modify |
425 | # it under the terms of the GNU General Public License as published by |
426 | @@ -35,17 +35,18 @@ |
427 | be used exactly like they were in this class. |
428 | Methodes from MountControl also can be overriden in here if you need |
429 | something different.""" |
430 | - def __init__(self, cfg = None, profile_id = None, hash_id = None, tmp_mount = False, **kwargs): |
431 | + def __init__(self, cfg = None, profile_id = None, hash_id = None, tmp_mount = False, parent = None **kwargs): |
432 | self.config = cfg |
433 | if self.config is None: |
434 | self.config = config.Config() |
435 | |
436 | self.profile_id = profile_id |
437 | - if not self.profile_id: |
438 | + if self.profile_id is None: |
439 | self.profile_id = self.config.get_current_profile() |
440 | |
441 | self.tmp_mount = tmp_mount |
442 | self.hash_id = hash_id |
443 | + self.parent = parent |
444 | |
445 | #init MountControl |
446 | mount.MountControl.__init__(self) |
447 | @@ -62,6 +63,7 @@ |
448 | self.setattr_kwargs('user', self.config.get_dummy_user(self.profile_id), **kwargs) |
449 | self.setattr_kwargs('host', self.config.get_dummy_host(self.profile_id), **kwargs) |
450 | self.setattr_kwargs('port', self.config.get_dummy_port(self.profile_id), **kwargs) |
451 | + self.setattr_kwargs('password', self.config.get_password(parent, self.profile_id), store = False, **kwargs) |
452 | |
453 | self.set_default_args() |
454 | |
455 | |
456 | === modified file 'common/makedeb-src.sh' |
457 | --- common/makedeb-src.sh 2012-11-17 13:45:00 +0000 |
458 | +++ common/makedeb-src.sh 2013-03-07 23:23:23 +0000 |
459 | @@ -17,6 +17,10 @@ |
460 | |
461 | #app |
462 | cp backintime $DEST/ |
463 | +cp backintime-askpass $DEST/ |
464 | + |
465 | +#autostart |
466 | +cp backintime.desktop $DEST/ |
467 | |
468 | #python files |
469 | cp *.py $DEST/ |
470 | @@ -44,6 +48,9 @@ |
471 | #debian: rules |
472 | cp debian_specific/rules $DEST/debian |
473 | |
474 | +#debian: conffiles |
475 | +cp debian_specific/conffiles $DEST/debian |
476 | + |
477 | #add languages to rules |
478 | for langfile in `ls po/*.po`; do |
479 | lang=`echo $langfile | cut -d/ -f2 | cut -d. -f1` |
480 | |
481 | === modified file 'common/man/C/backintime.1' |
482 | --- common/man/C/backintime.1 2013-01-01 20:26:37 +0000 |
483 | +++ common/man/C/backintime.1 2013-03-07 23:23:23 +0000 |
484 | @@ -83,9 +83,11 @@ |
485 | To prepare your user account for ssh-mode you have to add the user to group 'fuse' by typing 'sudo adduser <USER> fuse' in terminal. To apply changes you have to logout and login again. |
486 | .PP |
487 | Next you have to create a password-less login to the remote host (for further informations look at http://www.debian-administration.org/articles/152). |
488 | -Type in terminal 'ssh-keygen -t rsa' hit enter for default path and keep the passphrase empty. |
489 | -.PP |
490 | -Finally type 'ssh-copy-id -i ~/.ssh/id_rsa.pub <REMOTE_USER>@<HOST>' and enter your password on remote host. |
491 | +Type in terminal 'ssh-keygen -t dsa' hit enter for default path and enter a passphrase for the private key. |
492 | +.PP |
493 | +Finally type 'ssh-copy-id -i ~/.ssh/id_dsa.pub <REMOTE_USER>@<HOST>' and enter your password on remote host. |
494 | +.PP |
495 | +In Settingsdialog you need to set the host and remote user. If you enter a relative path (no leading / ) it will start from remote users homedir. The password has to be the passphrase for your private key. |
496 | .PP |
497 | .B Cipher |
498 | (the algorithm used to encrypt the data during transfer) |
499 | |
500 | === modified file 'common/mount.py' |
501 | --- common/mount.py 2012-10-23 19:26:20 +0000 |
502 | +++ common/mount.py 2013-03-07 23:23:23 +0000 |
503 | @@ -1,4 +1,4 @@ |
504 | -# Copyright (c) 2012 Germar Reitze |
505 | +# Copyright (c) 2012-2013 Germar Reitze |
506 | # |
507 | # This program is free software; you can redistribute it and/or modify |
508 | # it under the terms of the GNU General Public License as published by |
509 | @@ -24,6 +24,7 @@ |
510 | |
511 | import config |
512 | import logger |
513 | +import tools |
514 | |
515 | _=gettext.gettext |
516 | |
517 | @@ -34,7 +35,7 @@ |
518 | pass |
519 | |
520 | class Mount(object): |
521 | - def __init__(self, cfg = None, profile_id = None, tmp_mount = False): |
522 | + def __init__(self, cfg = None, profile_id = None, tmp_mount = False, parent = None): |
523 | self.config = cfg |
524 | if self.config is None: |
525 | self.config = config.Config() |
526 | @@ -44,6 +45,7 @@ |
527 | self.profile_id = self.config.get_current_profile() |
528 | |
529 | self.tmp_mount = tmp_mount |
530 | + self.parent = parent |
531 | |
532 | def mount(self, mode = None, check = True, **kwargs): |
533 | if mode is None: |
534 | @@ -56,7 +58,9 @@ |
535 | while True: |
536 | try: |
537 | mounttools = self.config.SNAPSHOT_MODES[mode][0] |
538 | - tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs) |
539 | + tools = mounttools(cfg = self.config, profile_id = self.profile_id, |
540 | + tmp_mount = self.tmp_mount, mode = mode, |
541 | + parent = self.parent, **kwargs) |
542 | return tools.mount(check = check) |
543 | except HashCollision as ex: |
544 | logger.warning(str(ex)) |
545 | @@ -79,7 +83,9 @@ |
546 | kwargs = json.loads(data_string) |
547 | mode = kwargs.pop('mode') |
548 | mounttools = self.config.SNAPSHOT_MODES[mode][0] |
549 | - tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, hash_id = hash_id, **kwargs) |
550 | + tools = mounttools(cfg = self.config, profile_id = self.profile_id, |
551 | + tmp_mount = self.tmp_mount, mode = mode, |
552 | + hash_id = hash_id, parent = self.parent, **kwargs) |
553 | tools.umount() |
554 | |
555 | def pre_mount_check(self, mode = None, first_run = False, **kwargs): |
556 | @@ -93,7 +99,9 @@ |
557 | return True |
558 | else: |
559 | mounttools = self.config.SNAPSHOT_MODES[mode][0] |
560 | - tools = mounttools(cfg = self.config, profile_id = self.profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs) |
561 | + tools = mounttools(cfg = self.config, profile_id = self.profile_id, |
562 | + tmp_mount = self.tmp_mount, mode = mode, |
563 | + parent = self.parent, **kwargs) |
564 | return tools.pre_mount_check(first_run) |
565 | |
566 | def remount(self, new_profile_id, mode = None, hash_id = None, **kwargs): |
567 | @@ -117,7 +125,9 @@ |
568 | return self.mount(mode = mode, **kwargs) |
569 | |
570 | mounttools = self.config.SNAPSHOT_MODES[mode][0] |
571 | - tools = mounttools(cfg = self.config, profile_id = new_profile_id, tmp_mount = self.tmp_mount, mode = mode, **kwargs) |
572 | + tools = mounttools(cfg = self.config, profile_id = new_profile_id, |
573 | + tmp_mount = self.tmp_mount, mode = mode, |
574 | + parent = self.parent, **kwargs) |
575 | if tools.compare_remount(hash_id): |
576 | #profiles uses the same settings. just swap the symlinks |
577 | tools.remove_symlink(profile_id = self.profile_id) |
578 | @@ -214,14 +224,12 @@ |
579 | |
580 | def is_mounted(self): |
581 | """return True if path is is already mounted""" |
582 | - try: |
583 | - subprocess.check_call(['mountpoint', self.mountpoint], stdout=open(os.devnull, 'w')) |
584 | - except subprocess.CalledProcessError: |
585 | + if tools.check_mountpoint(self.mountpoint): |
586 | + return True |
587 | + else: |
588 | if len(os.listdir(self.mountpoint)) > 0: |
589 | raise MountException( _('mountpoint %s not empty.') % self.mountpoint) |
590 | return False |
591 | - else: |
592 | - return True |
593 | |
594 | def create_mountstructure(self): |
595 | """ folder structure in /tmp/backintime/<user>/: |
596 | @@ -331,7 +339,7 @@ |
597 | os.remove(os.path.join(self.mount_user_path, symlink)) |
598 | return False |
599 | |
600 | - def setattr_kwargs(self, arg, default, **kwargs): |
601 | + def setattr_kwargs(self, arg, default, store = True, **kwargs): |
602 | """if kwargs[arg] exist set self.<arg> to kwargs[arg] |
603 | else set self.<arg> to default which should be the value from config""" |
604 | if arg in kwargs: |
605 | @@ -339,8 +347,9 @@ |
606 | else: |
607 | value = default |
608 | setattr(self, arg, value) |
609 | - #make dictionary with all used args for umount |
610 | - self.all_kwargs[arg] = value |
611 | + if store: |
612 | + #make dictionary with all used args for umount |
613 | + self.all_kwargs[arg] = value |
614 | |
615 | def write_umount_info(self): |
616 | """dump dictionary self.all_kwargs to umount_info file""" |
617 | @@ -393,6 +402,8 @@ |
618 | src = mountpoint |
619 | else: |
620 | src = os.path.join(mountpoint, self.symlink_subfolder) |
621 | + if os.path.exists(dst): |
622 | + os.remove(dst) |
623 | os.symlink(src, dst) |
624 | |
625 | def remove_symlink(self, profile_id = None, tmp_mount = None): |
626 | |
627 | === added file 'common/password.py' |
628 | --- common/password.py 1970-01-01 00:00:00 +0000 |
629 | +++ common/password.py 2013-03-07 23:23:23 +0000 |
630 | @@ -0,0 +1,446 @@ |
631 | +# Copyright (c) 2012-2013 Germar Reitze |
632 | +# |
633 | +# This program is free software; you can redistribute it and/or modify |
634 | +# it under the terms of the GNU General Public License as published by |
635 | +# the Free Software Foundation; either version 2 of the License, or |
636 | +# (at your option) any later version. |
637 | +# |
638 | +# This program is distributed in the hope that it will be useful, |
639 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
640 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
641 | +# GNU General Public License for more details. |
642 | +# |
643 | +# You should have received a copy of the GNU General Public License along |
644 | +# with this program; if not, write to the Free Software Foundation, Inc., |
645 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
646 | + |
647 | +import sys |
648 | +import os |
649 | +import time |
650 | +import atexit |
651 | +import signal |
652 | +import base64 |
653 | +import subprocess |
654 | +import gettext |
655 | +import keyring |
656 | + |
657 | +import config |
658 | +import configfile |
659 | +import tools |
660 | +import password_ipc |
661 | + |
662 | +_=gettext.gettext |
663 | + |
664 | +class Daemon: |
665 | + """ |
666 | + A generic daemon class. |
667 | + |
668 | + Usage: subclass the Daemon class and override the run() method |
669 | + |
670 | + Daemon Copyright by Sander Marechal |
671 | + License CC BY-SA 3.0 |
672 | + http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ |
673 | + """ |
674 | + def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/stdout', stderr='/dev/null'): |
675 | + self.stdin = stdin |
676 | + self.stdout = stdout |
677 | + self.stderr = stderr |
678 | + self.pidfile = pidfile |
679 | + |
680 | + def daemonize(self): |
681 | + """ |
682 | + do the UNIX double-fork magic, see Stevens' "Advanced |
683 | + Programming in the UNIX Environment" for details (ISBN 0201563177) |
684 | + http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 |
685 | + """ |
686 | + try: |
687 | + pid = os.fork() |
688 | + if pid > 0: |
689 | + # exit first parent |
690 | + sys.exit(0) |
691 | + except OSError, e: |
692 | + sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) |
693 | + sys.exit(1) |
694 | + |
695 | + # decouple from parent environment |
696 | + os.chdir("/") |
697 | + os.setsid() |
698 | + os.umask(0) |
699 | + |
700 | + # do second fork |
701 | + try: |
702 | + pid = os.fork() |
703 | + if pid > 0: |
704 | + # exit from second parent |
705 | + sys.exit(0) |
706 | + except OSError, e: |
707 | + sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) |
708 | + sys.exit(1) |
709 | + |
710 | + # redirect standard file descriptors |
711 | + sys.stdout.flush() |
712 | + sys.stderr.flush() |
713 | + si = file(self.stdin, 'r') |
714 | + so = file(self.stdout, 'a+') |
715 | + se = file(self.stderr, 'a+', 0) |
716 | + os.dup2(si.fileno(), sys.stdin.fileno()) |
717 | + os.dup2(so.fileno(), sys.stdout.fileno()) |
718 | + os.dup2(se.fileno(), sys.stderr.fileno()) |
719 | + |
720 | + # write pidfile |
721 | + sys.stdout.write('write pidfile\n') |
722 | + atexit.register(self.delpid) |
723 | + signal.signal(signal.SIGTERM, self._cleanup_handler) |
724 | + pid = str(os.getpid()) |
725 | + with open(self.pidfile, 'w+') as pidfile: |
726 | + pidfile.write("%s\n" % pid) |
727 | + os.chmod(self.pidfile, 0600) |
728 | + |
729 | + def _cleanup_handler(self, signum, frame): |
730 | + self.fifo.delfifo() |
731 | + self.delpid() |
732 | + sys.exit(0) |
733 | + |
734 | + def delpid(self): |
735 | + try: |
736 | + os.remove(self.pidfile) |
737 | + except: |
738 | + pass |
739 | + |
740 | + def start(self): |
741 | + """ |
742 | + Start the daemon |
743 | + """ |
744 | + # Check for a pidfile to see if the daemon already runs |
745 | + try: |
746 | + pf = file(self.pidfile,'r') |
747 | + pid = int(pf.read().strip()) |
748 | + pf.close() |
749 | + except IOError: |
750 | + pid = None |
751 | + |
752 | + if pid: |
753 | + if tools.is_process_alive(pid): |
754 | + message = "pidfile %s already exist. Daemon already running?\n" |
755 | + sys.stderr.write(message % self.pidfile) |
756 | + sys.exit(1) |
757 | + else: |
758 | + self.delpid() |
759 | + |
760 | + # Start the daemon |
761 | + self.daemonize() |
762 | + self.run() |
763 | + |
764 | + def stop(self): |
765 | + """ |
766 | + Stop the daemon |
767 | + """ |
768 | + # Get the pid from the pidfile |
769 | + try: |
770 | + pf = file(self.pidfile,'r') |
771 | + pid = int(pf.read().strip()) |
772 | + pf.close() |
773 | + except IOError: |
774 | + pid = None |
775 | + |
776 | + if not pid: |
777 | + message = "pidfile %s does not exist. Daemon not running?\n" |
778 | + sys.stderr.write(message % self.pidfile) |
779 | + return # not an error in a restart |
780 | + |
781 | + # Try killing the daemon process |
782 | + try: |
783 | + while 1: |
784 | + os.kill(pid, signal.SIGTERM) |
785 | + time.sleep(0.1) |
786 | + except OSError, err: |
787 | + if err.errno == 3: |
788 | + if os.path.exists(self.pidfile): |
789 | + os.remove(self.pidfile) |
790 | + else: |
791 | + print err.strerror |
792 | + sys.exit(1) |
793 | + |
794 | + def restart(self): |
795 | + """ |
796 | + Restart the daemon |
797 | + """ |
798 | + self.stop() |
799 | + self.start() |
800 | + |
801 | + def reload(self): |
802 | + """ |
803 | + send SIGHUP signal to process |
804 | + """ |
805 | + # Get the pid from the pidfile |
806 | + try: |
807 | + pf = file(self.pidfile,'r') |
808 | + pid = int(pf.read().strip()) |
809 | + pf.close() |
810 | + except IOError: |
811 | + pid = None |
812 | + |
813 | + if not pid: |
814 | + message = "pidfile %s does not exist. Daemon not running?\n" |
815 | + sys.stderr.write(message % self.pidfile) |
816 | + return |
817 | + |
818 | + # Try killing the daemon process |
819 | + try: |
820 | + os.kill(pid, signal.SIGHUP) |
821 | + except OSError, err: |
822 | + if err.errno == 3: |
823 | + if os.path.exists(self.pidfile): |
824 | + os.remove(self.pidfile) |
825 | + else: |
826 | + sys.stderr.write(err.strerror) |
827 | + sys.exit(1) |
828 | + |
829 | + def status(self): |
830 | + """ |
831 | + return status |
832 | + """ |
833 | + # Get the pid from the pidfile |
834 | + try: |
835 | + pf = file(self.pidfile,'r') |
836 | + pid = int(pf.read().strip()) |
837 | + pf.close() |
838 | + except IOError: |
839 | + pid = None |
840 | + |
841 | + if not pid: |
842 | + return False |
843 | + |
844 | + # Try killing the daemon process |
845 | + try: |
846 | + os.kill(pid, 0) |
847 | + except OSError, err: |
848 | + if err.errno == 3: |
849 | + if os.path.exists(self.pidfile): |
850 | + os.remove(self.pidfile) |
851 | + return False |
852 | + else: |
853 | + sys.stderr.write(err.strerror) |
854 | + return False |
855 | + return True |
856 | + |
857 | + def run(self): |
858 | + """ |
859 | + You should override this method when you subclass Daemon. It will be called after the process has been |
860 | + daemonized by start() or restart(). |
861 | + """ |
862 | + pass |
863 | + |
864 | +class Password_Cache(Daemon): |
865 | + """ |
866 | + Password_Cache get started on User login. It provides passwords for |
867 | + BIT cronjobs because keyring is not available when the User is not |
868 | + logged in. Does not start if there is no password to cache |
869 | + (e.g. no profile allows to cache). |
870 | + """ |
871 | + def __init__(self, cfg = None, *args, **kwargs): |
872 | + self.config = cfg |
873 | + if self.config is None: |
874 | + self.config = config.Config() |
875 | + pw_cache_path = self.config.get_password_cache_folder() |
876 | + if not os.path.isdir(pw_cache_path): |
877 | + os.mkdir(pw_cache_path, 0700) |
878 | + else: |
879 | + os.chmod(pw_cache_path, 0700) |
880 | + Daemon.__init__(self, self.config.get_password_cache_pid(), *args, **kwargs) |
881 | + self.db = {} |
882 | + self.fifo = password_ipc.FIFO(self.config.get_password_cache_fifo()) |
883 | + |
884 | + if self.config.get_keyring_backend() == 'kde': |
885 | + keyring.set_keyring(keyring.backend.KDEKWallet()) |
886 | + else: |
887 | + keyring.set_keyring(keyring.backend.GnomeKeyring()) |
888 | + |
889 | + def run(self): |
890 | + """ |
891 | + wait for password request on FIFO and answer with password |
892 | + from self.db through FIFO. |
893 | + """ |
894 | + tools.save_env(self.config) |
895 | + if tools.check_home_encrypt(): |
896 | + sys.stdout.write('Home is encrypt. Doesn\'t make sense to cache passwords. Quit.') |
897 | + sys.exit(0) |
898 | + self._collect_passwords() |
899 | + if len(self.db) == 0: |
900 | + sys.stdout.write('Nothing to cache. Quit.') |
901 | + sys.exit(0) |
902 | + self.fifo.create() |
903 | + atexit.register(self.fifo.delfifo) |
904 | + signal.signal(signal.SIGHUP, self._reload_handler) |
905 | + while True: |
906 | + try: |
907 | + request = self.fifo.read() |
908 | + request = request.split('\n')[0] |
909 | + if request in self.db.keys(): |
910 | + answer = self.db[request] |
911 | + else: |
912 | + answer = '' |
913 | + self.fifo.write(answer, 5) |
914 | + except IOError as e: |
915 | + sys.stderr.write('Error in writing answer to FIFO: %s\n' % e.strerror) |
916 | + except KeyboardInterrupt: |
917 | + print('Quit.') |
918 | + break |
919 | + except tools.Timeout: |
920 | + sys.stderr.write('FIFO timeout\n') |
921 | + except StandardError as e: |
922 | + sys.stderr.write('ERROR: %s\n' % str(e)) |
923 | + |
924 | + def _reload_handler(self, signum, frame): |
925 | + """ |
926 | + reload passwords during runtime. |
927 | + """ |
928 | + sys.stdout.write('Reloading\n') |
929 | + del(self.db) |
930 | + self.db = {} |
931 | + self._collect_passwords() |
932 | + |
933 | + def _collect_passwords(self): |
934 | + """ |
935 | + search all profiles in config and collect passwords from keyring. |
936 | + """ |
937 | + profiles = self.config.get_profiles() |
938 | + for profile_id in profiles: |
939 | + mode = self.config.get_snapshots_mode(profile_id) |
940 | + if mode in self.config.SNAPSHOT_MODES_NEED_PASSWORD: |
941 | + if self.config.get_password_save(profile_id): |
942 | + if self.config.get_password_use_cache(profile_id): |
943 | + service_name = self.config.get_keyring_service_name(profile_id, mode) |
944 | + user_name = self.config.get_keyring_user_name(profile_id) |
945 | + |
946 | + password = keyring.get_password(service_name, user_name) |
947 | + if password is None: |
948 | + continue |
949 | + #add some snakeoil |
950 | + pw_base64 = base64.encodestring(password) |
951 | + self.db[profile_id] = pw_base64 |
952 | + |
953 | +class Password(object): |
954 | + """ |
955 | + provide passwords for BIT either from keyring, Password_Cache or |
956 | + by asking User. |
957 | + """ |
958 | + def __init__(self, cfg = None): |
959 | + self.config = cfg |
960 | + if self.config is None: |
961 | + self.config = config.Config() |
962 | + self.pw_cache = Password_Cache(self.config) |
963 | + self.fifo = password_ipc.FIFO(self.config.get_password_cache_fifo()) |
964 | + self.db = {} |
965 | + |
966 | + if self.config.get_keyring_backend() == 'kwallet': |
967 | + keyring.set_keyring(keyring.backend.KDEKWallet()) |
968 | + else: |
969 | + keyring.set_keyring(keyring.backend.GnomeKeyring()) |
970 | + |
971 | + def get_password(self, parent, profile_id, mode, only_from_keyring = False): |
972 | + """ |
973 | + based on profile settings return password from keyring, |
974 | + Password_Cache or by asking User. |
975 | + """ |
976 | + if not mode in self.config.SNAPSHOT_MODES_NEED_PASSWORD: |
977 | + return '' |
978 | + try: |
979 | + return self.db[profile_id][mode] |
980 | + except KeyError: |
981 | + pass |
982 | + if self.config.get_password_save(profile_id): |
983 | + if self.config.get_password_use_cache(profile_id) and not only_from_keyring: |
984 | + password = self._get_password_from_pw_cache(profile_id) |
985 | + else: |
986 | + password = self._get_password_from_keyring(profile_id, mode) |
987 | + else: |
988 | + if not only_from_keyring: |
989 | + password = self._get_password_from_user(parent, profile_id, mode) |
990 | + else: |
991 | + password = '' |
992 | + self._set_password_db(profile_id, mode, password) |
993 | + return password |
994 | + |
995 | + def _get_password_from_keyring(self, profile_id, mode): |
996 | + """ |
997 | + get password from system keyring (seahorse). The keyring is only |
998 | + available if User is logged in. |
999 | + """ |
1000 | + service_name = self.config.get_keyring_service_name(profile_id, mode) |
1001 | + user_name = self.config.get_keyring_user_name(profile_id) |
1002 | + return keyring.get_password(service_name, user_name) |
1003 | + |
1004 | + def _get_password_from_pw_cache(self, profile_id): |
1005 | + """ |
1006 | + get password from Password_Cache |
1007 | + """ |
1008 | + if self.pw_cache: |
1009 | + self.fifo.write(profile_id, timeout = 5) |
1010 | + pw_base64 = self.fifo.read(timeout = 5) |
1011 | + return base64.decodestring(pw_base64) |
1012 | + else: |
1013 | + return '' |
1014 | + |
1015 | + def _get_password_from_user(self, parent, profile_id, mode): |
1016 | + """ |
1017 | + ask user for password. This does even work when run as cronjob |
1018 | + and user is logged in. |
1019 | + """ |
1020 | + prompt = _('Enter Password for profile \'%(profile)s\' mode %(mode)s: ') % {'profile': self.config.get_profile_name(profile_id), 'mode': self.config.SNAPSHOT_MODES[mode][1]} |
1021 | + |
1022 | + gnome = os.path.join(self.config.get_app_path(), 'gnome') |
1023 | + kde = os.path.join(self.config.get_app_path(), 'kde4') |
1024 | + for path in (gnome, kde): |
1025 | + if os.path.isdir(path): |
1026 | + sys.path = [path] + sys.path |
1027 | + break |
1028 | + |
1029 | + x_server = tools.check_x_server() |
1030 | + import_successful = False |
1031 | + if x_server: |
1032 | + try: |
1033 | + import messagebox |
1034 | + import_successful = True |
1035 | + except ImportError: |
1036 | + pass |
1037 | + |
1038 | + if not import_successful or not x_server: |
1039 | + import getpass |
1040 | + alarm = tools.Alarm() |
1041 | + alarm.start(300) |
1042 | + try: |
1043 | + password = getpass.getpass(prompt) |
1044 | + alarm.stop() |
1045 | + except tools.Timeout: |
1046 | + password = '' |
1047 | + return password |
1048 | + |
1049 | + password = messagebox.ask_password_dialog(parent, self.config, 'Back in Time', |
1050 | + prompt = prompt, |
1051 | + timeout = 300) |
1052 | + return password |
1053 | + |
1054 | + def _set_password_db(self, profile_id, mode, password): |
1055 | + """ |
1056 | + internal Password cache. Prevent to ask password several times |
1057 | + during runtime. |
1058 | + """ |
1059 | + if not profile_id in self.db.keys(): |
1060 | + self.db[profile_id] = {} |
1061 | + self.db[profile_id][mode] = password |
1062 | + |
1063 | + def set_password(self, password, profile_id, mode): |
1064 | + """ |
1065 | + store password to keyring (seahorse). If caching is allowed |
1066 | + reload Password_Cache |
1067 | + """ |
1068 | + if mode in self.config.SNAPSHOT_MODES_NEED_PASSWORD: |
1069 | + service_name = self.config.get_keyring_service_name(profile_id, mode) |
1070 | + user_name = self.config.get_keyring_user_name(profile_id) |
1071 | + if self.config.get_password_save(profile_id): |
1072 | + keyring.set_password(service_name, user_name, password) |
1073 | + if self.config.get_password_use_cache(profile_id): |
1074 | + if self.pw_cache.status(): |
1075 | + self.pw_cache.reload() |
1076 | + self._set_password_db(profile_id, mode, password) |
1077 | |
1078 | === added file 'common/password_ipc.py' |
1079 | --- common/password_ipc.py 1970-01-01 00:00:00 +0000 |
1080 | +++ common/password_ipc.py 2013-03-07 23:23:23 +0000 |
1081 | @@ -0,0 +1,112 @@ |
1082 | +# Copyright (c) 2012-2013 Germar Reitze |
1083 | +# |
1084 | +# This program is free software; you can redistribute it and/or modify |
1085 | +# it under the terms of the GNU General Public License as published by |
1086 | +# the Free Software Foundation; either version 2 of the License, or |
1087 | +# (at your option) any later version. |
1088 | +# |
1089 | +# This program is distributed in the hope that it will be useful, |
1090 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
1091 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
1092 | +# GNU General Public License for more details. |
1093 | +# |
1094 | +# You should have received a copy of the GNU General Public License along |
1095 | +# with this program; if not, write to the Free Software Foundation, Inc., |
1096 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
1097 | + |
1098 | +import os |
1099 | +import sys |
1100 | +import stat |
1101 | +import signal |
1102 | +import tools |
1103 | +import threading |
1104 | +import base64 |
1105 | + |
1106 | +class FIFO(object): |
1107 | + """ |
1108 | + interprocess-communication with named pipes |
1109 | + """ |
1110 | + def __init__(self, fname): |
1111 | + self.fifo = fname |
1112 | + self.alarm = tools.Alarm() |
1113 | + |
1114 | + def delfifo(self): |
1115 | + """ |
1116 | + remove FIFO |
1117 | + """ |
1118 | + try: |
1119 | + os.remove(self.fifo) |
1120 | + except: |
1121 | + pass |
1122 | + |
1123 | + def create(self): |
1124 | + """ |
1125 | + create the FIFO in a way that only the current user can access it. |
1126 | + """ |
1127 | + if os.path.exists(self.fifo): |
1128 | + self.delfifo() |
1129 | + try: |
1130 | + os.mkfifo(self.fifo, 0600) |
1131 | + except OSError, e: |
1132 | + sys.stderr.write('Failed to create FIFO: %s\n' % e.strerror) |
1133 | + sys.exit(1) |
1134 | + |
1135 | + def read(self, timeout = 0): |
1136 | + """ |
1137 | + read from fifo untill timeout. If timeout is 0 it will wait forever |
1138 | + for input. |
1139 | + """ |
1140 | + #sys.stdout.write('read fifo\n') |
1141 | + if not self.is_fifo(): |
1142 | + sys.stderr.write('%s is not a FIFO\n' % self.fifo) |
1143 | + sys.exit(1) |
1144 | + self.alarm.start(timeout) |
1145 | + with open(self.fifo, 'r') as fifo: |
1146 | + ret = fifo.read() |
1147 | + self.alarm.stop() |
1148 | + return ret |
1149 | + |
1150 | + def write(self, string, timeout = 0): |
1151 | + """ |
1152 | + write to fifo untill timeout. If timeout is 0 it will wait forever |
1153 | + for an other process that will read this. |
1154 | + """ |
1155 | + #sys.stdout.write('write fifo\n') |
1156 | + if not self.is_fifo(): |
1157 | + sys.stderr.write('%s is not a FIFO\n' % self.fifo) |
1158 | + sys.exit(1) |
1159 | + self.alarm.start(timeout) |
1160 | + with open(self.fifo, 'a') as fifo: |
1161 | + fifo.write(string) |
1162 | + self.alarm.stop() |
1163 | + |
1164 | + def is_fifo(self): |
1165 | + """ |
1166 | + make sure file is still a FIFO |
1167 | + """ |
1168 | + try: |
1169 | + return stat.S_ISFIFO(os.stat(self.fifo).st_mode) |
1170 | + except OSError: |
1171 | + return False |
1172 | + |
1173 | +class TempPasswordThread(threading.Thread): |
1174 | + """ |
1175 | + in case BIT is not configured yet provide password through temp FIFO |
1176 | + to backintime-askpass. |
1177 | + """ |
1178 | + def __init__(self, string, temp_file): |
1179 | + threading.Thread.__init__(self) |
1180 | + self.pw_base64 = base64.encodestring(string) |
1181 | + self.fifo = FIFO(temp_file) |
1182 | + |
1183 | + def run(self): |
1184 | + self.fifo.create() |
1185 | + self.fifo.write(self.pw_base64) |
1186 | + self.fifo.delfifo() |
1187 | + |
1188 | + def read(self): |
1189 | + """ |
1190 | + read fifo to end the blocking fifo.write |
1191 | + use only if thread timeout. |
1192 | + """ |
1193 | + self.fifo.read() |
1194 | \ No newline at end of file |
1195 | |
1196 | === modified file 'common/sshtools.py' |
1197 | --- common/sshtools.py 2013-02-20 16:37:48 +0000 |
1198 | +++ common/sshtools.py 2013-03-07 23:23:23 +0000 |
1199 | @@ -1,4 +1,4 @@ |
1200 | -# Copyright (c) 2012 Germar Reitze |
1201 | +# Copyright (c) 2012-2013 Germar Reitze |
1202 | # |
1203 | # This program is free software; you can redistribute it and/or modify |
1204 | # it under the terms of the GNU General Public License as published by |
1205 | @@ -22,11 +22,13 @@ |
1206 | import random |
1207 | import tempfile |
1208 | from time import sleep |
1209 | +import threading |
1210 | |
1211 | import config |
1212 | import mount |
1213 | import logger |
1214 | import tools |
1215 | +import password_ipc |
1216 | |
1217 | _=gettext.gettext |
1218 | |
1219 | @@ -35,17 +37,18 @@ |
1220 | Mount remote path with sshfs. The real take_snapshot process will use |
1221 | rsync over ssh. Other commands run remote over ssh. |
1222 | """ |
1223 | - def __init__(self, cfg = None, profile_id = None, hash_id = None, tmp_mount = False, **kwargs): |
1224 | + def __init__(self, cfg = None, profile_id = None, hash_id = None, tmp_mount = False, parent = None, **kwargs): |
1225 | self.config = cfg |
1226 | if self.config is None: |
1227 | self.config = config.Config() |
1228 | |
1229 | self.profile_id = profile_id |
1230 | - if not self.profile_id: |
1231 | + if self.profile_id is None: |
1232 | self.profile_id = self.config.get_current_profile() |
1233 | |
1234 | self.tmp_mount = tmp_mount |
1235 | self.hash_id = hash_id |
1236 | + self.parent = parent |
1237 | |
1238 | #init MountControl |
1239 | mount.MountControl.__init__(self) |
1240 | @@ -61,6 +64,8 @@ |
1241 | self.setattr_kwargs('port', self.config.get_ssh_port(self.profile_id), **kwargs) |
1242 | self.setattr_kwargs('path', self.config.get_snapshots_path_ssh(self.profile_id), **kwargs) |
1243 | self.setattr_kwargs('cipher', self.config.get_ssh_cipher(self.profile_id), **kwargs) |
1244 | + self.setattr_kwargs('private_key_file', self.config.get_ssh_private_key_file(self.profile_id), **kwargs) |
1245 | + self.setattr_kwargs('password', None, store = False, **kwargs) |
1246 | |
1247 | if len(self.path) == 0: |
1248 | self.path = './' |
1249 | @@ -70,6 +75,8 @@ |
1250 | self.user_host_path = '%s@%s:%s' % (self.user, self.host, self.path) |
1251 | self.log_command = '%s: %s' % (self.mode, self.user_host_path) |
1252 | |
1253 | + self.unlock_ssh_agent() |
1254 | + |
1255 | def _mount(self): |
1256 | """mount the service""" |
1257 | sshfs = ['sshfs', '-p', str(self.port)] |
1258 | @@ -125,6 +132,59 @@ |
1259 | raise MountException( _('Error discription') ) if not""" |
1260 | return True |
1261 | |
1262 | + def unlock_ssh_agent(self): |
1263 | + """using askpass.py to unlock private key in ssh-agent""" |
1264 | + env = os.environ.copy() |
1265 | + env['SSH_ASKPASS'] = 'backintime-askpass' |
1266 | + env['ASKPASS_PROFILE_ID'] = self.profile_id |
1267 | + env['ASKPASS_MODE'] = self.mode |
1268 | + |
1269 | + output = subprocess.Popen(['ssh-add', '-l'], stdout = subprocess.PIPE).communicate()[0] |
1270 | + if not output.find(self.private_key_file) >= 0: |
1271 | + if not self.config.get_password_save(self.profile_id) and not tools.check_x_server(): |
1272 | + #we need to unlink stdin from ssh-add in order to make it |
1273 | + #use our own backintime-askpass. |
1274 | + #But because of this we can NOT use getpass inside backintime-askpass |
1275 | + #if password is not saved and there is no x-server. |
1276 | + #So, let's just keep ssh-add asking for the password in that case. |
1277 | + alarm = tools.Alarm() |
1278 | + alarm.start(10) |
1279 | + try: |
1280 | + proc = subprocess.call(['ssh-add', self.private_key_file]) |
1281 | + alarm.stop() |
1282 | + except tools.Timeout: |
1283 | + pass |
1284 | + else: |
1285 | + if not self.password is None: |
1286 | + #write password directly to temp FIFO |
1287 | + temp_file = os.path.join(tempfile.mkdtemp(), 'FIFO') |
1288 | + env['ASKPASS_TEMP'] = temp_file |
1289 | + thread = password_ipc.TempPasswordThread(self.password, temp_file) |
1290 | + thread.start() |
1291 | + |
1292 | + proc = subprocess.Popen(['ssh-add', self.private_key_file], |
1293 | + stdin=subprocess.PIPE, |
1294 | + stdout=subprocess.PIPE, |
1295 | + stderr=subprocess.PIPE, |
1296 | + env = env, |
1297 | + preexec_fn = os.setsid) |
1298 | + output, error = proc.communicate() |
1299 | + if proc.returncode: |
1300 | + print( _('Failed to unlock SSH private key:\nError: %s') % error) |
1301 | + output = subprocess.Popen(['ssh-add', '-l'], stdout = subprocess.PIPE).communicate()[0] |
1302 | + if not output.find(self.private_key_file) >= 0: |
1303 | + raise mount.MountException( _('Could not unlock ssh private key. Wrong password or password not available for cron.')) |
1304 | + |
1305 | + if not self.password is None: |
1306 | + thread.join(5) |
1307 | + if thread.isAlive(): |
1308 | + #threading does not support signal.alarm |
1309 | + thread.read() |
1310 | + try: |
1311 | + os.rmdir(os.path.dirname(temp_file)) |
1312 | + except OSError: |
1313 | + pass |
1314 | + |
1315 | def check_fuse(self): |
1316 | """check if sshfs is installed and user is part of group fuse""" |
1317 | if not self.pathexists('sshfs'): |
1318 | |
1319 | === modified file 'common/tools.py' |
1320 | --- common/tools.py 2012-10-19 15:47:27 +0000 |
1321 | +++ common/tools.py 2013-03-07 23:23:23 +0000 |
1322 | @@ -22,7 +22,9 @@ |
1323 | import subprocess |
1324 | import hashlib |
1325 | import commands |
1326 | +import signal |
1327 | |
1328 | +import configfile |
1329 | |
1330 | ON_AC = 0 |
1331 | ON_BATTERY = 1 |
1332 | @@ -420,8 +422,73 @@ |
1333 | except ValueError: |
1334 | return False |
1335 | |
1336 | -# |
1337 | -# |
1338 | +def check_mountpoint(path): |
1339 | + '''return True if path is a mountpoint''' |
1340 | + try: |
1341 | + subprocess.check_call(['mountpoint', path], stdout=open(os.devnull, 'w')) |
1342 | + except subprocess.CalledProcessError: |
1343 | + return False |
1344 | + return True |
1345 | + |
1346 | +def check_home_encrypt(): |
1347 | + '''return True if users home is encrypted''' |
1348 | + home = os.path.expanduser('~') |
1349 | + if not check_mountpoint(home): |
1350 | + return False |
1351 | + if check_command('ecryptfs-verify'): |
1352 | + try: |
1353 | + subprocess.check_call(['ecryptfs-verify', '--home'], |
1354 | + stdout=open(os.devnull, 'w'), |
1355 | + stderr=open(os.devnull, 'w')) |
1356 | + except subprocess.CalledProcessError: |
1357 | + pass |
1358 | + else: |
1359 | + return True |
1360 | + if check_command('encfs'): |
1361 | + mount = subprocess.Popen(['mount'], stdout=subprocess.PIPE).communicate()[0] |
1362 | + for line in mount.split('\n'): |
1363 | + line_split = line.split(' ') |
1364 | + if len(line_split) < 5: |
1365 | + continue |
1366 | + if not line_split[2] == home: |
1367 | + continue |
1368 | + if line_split[4] == 'fuse.encfs': |
1369 | + return True |
1370 | + return False |
1371 | + |
1372 | +def load_env(cfg): |
1373 | + env = os.environ.copy() |
1374 | + env_file = configfile.ConfigFile() |
1375 | + env_file.load(cfg.get_cron_env_file(), maxsplit = 1) |
1376 | + for key in env_file.get_keys(): |
1377 | + value = env_file.get_str_value(key) |
1378 | + if not value: |
1379 | + continue |
1380 | + if not key in env.keys(): |
1381 | + os.environ[key] = value |
1382 | + del(env_file) |
1383 | + |
1384 | +def save_env(cfg): |
1385 | + """ |
1386 | + save environ variables to file that are needed by cron |
1387 | + to connect to keyring. This will only work if the user is logged in. |
1388 | + """ |
1389 | + env = os.environ.copy() |
1390 | + env_file = configfile.ConfigFile() |
1391 | + #ubuntu |
1392 | + set_env_key(env, env_file, 'GNOME_KEYRING_CONTROL') |
1393 | + set_env_key(env, env_file, 'DBUS_SESSION_BUS_ADDRESS') |
1394 | + set_env_key(env, env_file, 'DISPLAY') |
1395 | + #debian |
1396 | + set_env_key(env, env_file, 'XAUTHORITY') |
1397 | + |
1398 | + env_file.save(cfg.get_cron_env_file()) |
1399 | + del(env_file) |
1400 | + |
1401 | +def set_env_key(env, env_file, key): |
1402 | + if key in env.keys(): |
1403 | + env_file.set_str_value(key, env[key]) |
1404 | + |
1405 | class UniquenessSet: |
1406 | '''a class to check for uniqueness of snapshots of the same [item]''' |
1407 | |
1408 | @@ -474,3 +541,40 @@ |
1409 | if verb: print " >> skip (it's a duplicate)" |
1410 | return False |
1411 | |
1412 | +class Timeout(Exception): |
1413 | + pass |
1414 | + |
1415 | +class Alarm(object): |
1416 | + """ |
1417 | + Timeout for FIFO. This does not work with threading. |
1418 | + """ |
1419 | + def __init__(self, callback = None): |
1420 | + self.callback = callback |
1421 | + |
1422 | + def start(self, timeout): |
1423 | + """ |
1424 | + start timer |
1425 | + """ |
1426 | + try: |
1427 | + signal.signal(signal.SIGALRM, self.handler) |
1428 | + signal.alarm(timeout) |
1429 | + except ValueError: |
1430 | + pass |
1431 | + |
1432 | + def stop(self): |
1433 | + """ |
1434 | + stop timer before it come to an end |
1435 | + """ |
1436 | + try: |
1437 | + signal.alarm(0) |
1438 | + except: |
1439 | + pass |
1440 | + |
1441 | + def handler(self, signum, frame): |
1442 | + """ |
1443 | + timeout occur. |
1444 | + """ |
1445 | + if self.callback is None: |
1446 | + raise Timeout() |
1447 | + else: |
1448 | + self.callback() |
1449 | \ No newline at end of file |
1450 | |
1451 | === modified file 'gnome/backintime-gnome' |
1452 | --- gnome/backintime-gnome 2009-11-02 13:52:00 +0000 |
1453 | +++ gnome/backintime-gnome 2013-03-07 23:23:23 +0000 |
1454 | @@ -23,5 +23,8 @@ |
1455 | APP_PATH="/usr/share/backintime/gnome" |
1456 | fi |
1457 | |
1458 | -python ${APP_PATH}/app.py "$@" |
1459 | +#starting a new ssh-agent all the time is just a workaround for |
1460 | +#https://bugs.launchpad.net/ubuntu/+source/openssh/+bug/841672 |
1461 | +#normally we don't need to start ssh-agent for backintime-gnome |
1462 | +ssh-agent python ${APP_PATH}/app.py "$@" |
1463 | |
1464 | |
1465 | === modified file 'gnome/debian_specific/control' |
1466 | --- gnome/debian_specific/control 2013-01-09 21:02:50 +0000 |
1467 | +++ gnome/debian_specific/control 2013-03-07 23:23:23 +0000 |
1468 | @@ -5,6 +5,6 @@ |
1469 | Maintainer: BIT Team <bit-team@lists.launchpad.net> |
1470 | Architecture: all |
1471 | Homepage: http://backintime.le-web.org |
1472 | -Depends: x11-utils, python-glade2, python-gnome2, python-notify, meld, gksu, libnotify-bin, backintime-common (>= 1.0.21~), backintime-notify (>= 1.0.21~) |
1473 | +Depends: x11-utils, python-glade2, python-gnome2, python-notify, python-keyring-gnome | python-gnomekeyring, meld, gksu, libnotify-bin, backintime-common (>= 1.0.21~), backintime-notify (>= 1.0.21~) |
1474 | Description: Simple backup system for GNOME Desktop |
1475 | This is a GNOME GUI frontend for backintime-common. |
1476 | |
1477 | === modified file 'gnome/messagebox.py' |
1478 | --- gnome/messagebox.py 2012-10-28 23:11:24 +0000 |
1479 | +++ gnome/messagebox.py 2013-03-07 23:23:23 +0000 |
1480 | @@ -23,6 +23,7 @@ |
1481 | pygtk.require("2.0") |
1482 | import gtk |
1483 | import gettext |
1484 | +import gobject |
1485 | |
1486 | import config |
1487 | |
1488 | @@ -46,7 +47,7 @@ |
1489 | dialog.destroy() |
1490 | return retVal |
1491 | |
1492 | -def text_input_dialog( parent, config, title, default_value = '' ): |
1493 | +def text_input_dialog( parent, config, title, default_value = '', prompt = None, hide_input = False , timeout = None): |
1494 | |
1495 | builder = gtk.Builder() |
1496 | builder.set_translation_domain('backintime') |
1497 | @@ -58,15 +59,27 @@ |
1498 | |
1499 | dialog = builder.get_object( 'TextInputDialog' ) |
1500 | dialog.set_title( title ) |
1501 | - dialog.set_transient_for( parent ) |
1502 | + if not parent is None: |
1503 | + dialog.set_transient_for( parent ) |
1504 | + |
1505 | + prompt_text = builder.get_object('prompt_text') |
1506 | + if not prompt is None: |
1507 | + prompt_text.set_text(prompt) |
1508 | + prompt_text.set_visible(True) |
1509 | |
1510 | edit = builder.get_object( 'edit_text' ) |
1511 | + |
1512 | + if hide_input: |
1513 | + edit.set_visibility(False) |
1514 | |
1515 | if not default_value is None: |
1516 | edit.set_text( default_value ) |
1517 | |
1518 | edit.grab_focus() |
1519 | |
1520 | + if not timeout is None: |
1521 | + gobject.timeout_add(1000 * timeout, dialog.destroy) |
1522 | + |
1523 | text = None |
1524 | if gtk.RESPONSE_OK == dialog.run(): |
1525 | text = edit.get_text() |
1526 | @@ -76,3 +89,8 @@ |
1527 | dialog.destroy() |
1528 | return text |
1529 | |
1530 | +def ask_password_dialog(parent, config, title, prompt, timeout = None): |
1531 | + password = text_input_dialog(parent, config, title, |
1532 | + prompt = prompt, |
1533 | + hide_input = True, timeout = timeout) |
1534 | + return(password) |
1535 | |
1536 | === modified file 'gnome/settingsdialog.glade' |
1537 | --- gnome/settingsdialog.glade 2013-03-04 21:16:20 +0000 |
1538 | +++ gnome/settingsdialog.glade 2013-03-07 23:23:23 +0000 |
1539 | @@ -475,31 +475,6 @@ |
1540 | <property name="position">1</property> |
1541 | </packing> |
1542 | </child> |
1543 | - <child> |
1544 | - <object class="GtkLabel" id="lbl_ssh_cipher"> |
1545 | - <property name="visible">True</property> |
1546 | - <property name="can_focus">False</property> |
1547 | - <property name="xalign">1</property> |
1548 | - <property name="label" translatable="yes">Cipher:</property> |
1549 | - <property name="width_chars">8</property> |
1550 | - </object> |
1551 | - <packing> |
1552 | - <property name="expand">False</property> |
1553 | - <property name="fill">True</property> |
1554 | - <property name="position">2</property> |
1555 | - </packing> |
1556 | - </child> |
1557 | - <child> |
1558 | - <object class="GtkComboBox" id="combo_ssh_cipher"> |
1559 | - <property name="visible">True</property> |
1560 | - <property name="can_focus">False</property> |
1561 | - </object> |
1562 | - <packing> |
1563 | - <property name="expand">True</property> |
1564 | - <property name="fill">True</property> |
1565 | - <property name="position">3</property> |
1566 | - </packing> |
1567 | - </child> |
1568 | </object> |
1569 | <packing> |
1570 | <property name="expand">True</property> |
1571 | @@ -507,6 +482,108 @@ |
1572 | <property name="position">1</property> |
1573 | </packing> |
1574 | </child> |
1575 | + <child> |
1576 | + <object class="GtkExpander" id="expander_ssh"> |
1577 | + <property name="visible">True</property> |
1578 | + <property name="can_focus">True</property> |
1579 | + <child> |
1580 | + <object class="GtkHBox" id="hbox_ssh3"> |
1581 | + <property name="visible">True</property> |
1582 | + <property name="can_focus">False</property> |
1583 | + <property name="spacing">2</property> |
1584 | + <child> |
1585 | + <object class="GtkLabel" id="lbl_ssh_cipher"> |
1586 | + <property name="visible">True</property> |
1587 | + <property name="can_focus">False</property> |
1588 | + <property name="xalign">1</property> |
1589 | + <property name="label" translatable="yes">Cipher:</property> |
1590 | + <property name="width_chars">8</property> |
1591 | + </object> |
1592 | + <packing> |
1593 | + <property name="expand">False</property> |
1594 | + <property name="fill">True</property> |
1595 | + <property name="position">0</property> |
1596 | + </packing> |
1597 | + </child> |
1598 | + <child> |
1599 | + <object class="GtkComboBox" id="combo_ssh_cipher"> |
1600 | + <property name="visible">True</property> |
1601 | + <property name="can_focus">False</property> |
1602 | + </object> |
1603 | + <packing> |
1604 | + <property name="expand">True</property> |
1605 | + <property name="fill">True</property> |
1606 | + <property name="position">1</property> |
1607 | + </packing> |
1608 | + </child> |
1609 | + <child> |
1610 | + <object class="GtkLabel" id="lbl_ssh_private_key_file"> |
1611 | + <property name="visible">True</property> |
1612 | + <property name="can_focus">False</property> |
1613 | + <property name="xalign">1</property> |
1614 | + <property name="label" translatable="yes">Private key:</property> |
1615 | + <property name="width_chars">12</property> |
1616 | + </object> |
1617 | + <packing> |
1618 | + <property name="expand">False</property> |
1619 | + <property name="fill">True</property> |
1620 | + <property name="position">2</property> |
1621 | + </packing> |
1622 | + </child> |
1623 | + <child> |
1624 | + <object class="GtkEntry" id="txt_ssh_private_key_file"> |
1625 | + <property name="visible">True</property> |
1626 | + <property name="can_focus">True</property> |
1627 | + <property name="editable">False</property> |
1628 | + <property name="invisible_char">•</property> |
1629 | + <property name="primary_icon_activatable">False</property> |
1630 | + <property name="secondary_icon_activatable">False</property> |
1631 | + <property name="primary_icon_sensitive">True</property> |
1632 | + <property name="secondary_icon_sensitive">True</property> |
1633 | + </object> |
1634 | + <packing> |
1635 | + <property name="expand">True</property> |
1636 | + <property name="fill">True</property> |
1637 | + <property name="position">3</property> |
1638 | + </packing> |
1639 | + </child> |
1640 | + <child> |
1641 | + <object class="GtkButton" id="btn_ssh_private_key_file"> |
1642 | + <property name="visible">True</property> |
1643 | + <property name="can_focus">True</property> |
1644 | + <property name="receives_default">True</property> |
1645 | + <property name="use_action_appearance">False</property> |
1646 | + <signal name="clicked" handler="on_btn_ssh_private_key_file_clicked" swapped="no"/> |
1647 | + <child> |
1648 | + <object class="GtkImage" id="image_ssh_private_key_file"> |
1649 | + <property name="visible">True</property> |
1650 | + <property name="can_focus">False</property> |
1651 | + <property name="stock">gtk-directory</property> |
1652 | + </object> |
1653 | + </child> |
1654 | + </object> |
1655 | + <packing> |
1656 | + <property name="expand">False</property> |
1657 | + <property name="fill">True</property> |
1658 | + <property name="position">4</property> |
1659 | + </packing> |
1660 | + </child> |
1661 | + </object> |
1662 | + </child> |
1663 | + <child type="label"> |
1664 | + <object class="GtkLabel" id="label_ssh_advanced"> |
1665 | + <property name="visible">True</property> |
1666 | + <property name="can_focus">False</property> |
1667 | + <property name="label" translatable="yes">Advanced SSH Settings</property> |
1668 | + </object> |
1669 | + </child> |
1670 | + </object> |
1671 | + <packing> |
1672 | + <property name="expand">True</property> |
1673 | + <property name="fill">True</property> |
1674 | + <property name="position">2</property> |
1675 | + </packing> |
1676 | + </child> |
1677 | </object> |
1678 | </child> |
1679 | </object> |
1680 | @@ -645,6 +722,87 @@ |
1681 | </packing> |
1682 | </child> |
1683 | <child> |
1684 | + <object class="GtkFrame" id="password"> |
1685 | + <property name="can_focus">False</property> |
1686 | + <property name="label_xalign">0</property> |
1687 | + <property name="shadow_type">none</property> |
1688 | + <child> |
1689 | + <object class="GtkAlignment" id="alignment_password"> |
1690 | + <property name="visible">True</property> |
1691 | + <property name="can_focus">False</property> |
1692 | + <property name="left_padding">12</property> |
1693 | + <child> |
1694 | + <object class="GtkVBox" id="vbox_password"> |
1695 | + <property name="visible">True</property> |
1696 | + <property name="can_focus">False</property> |
1697 | + <property name="spacing">2</property> |
1698 | + <child> |
1699 | + <object class="GtkEntry" id="txt_password"> |
1700 | + <property name="visible">True</property> |
1701 | + <property name="can_focus">True</property> |
1702 | + <property name="invisible_char">•</property> |
1703 | + <property name="primary_icon_activatable">False</property> |
1704 | + <property name="secondary_icon_activatable">False</property> |
1705 | + <property name="primary_icon_sensitive">True</property> |
1706 | + <property name="secondary_icon_sensitive">True</property> |
1707 | + </object> |
1708 | + <packing> |
1709 | + <property name="expand">True</property> |
1710 | + <property name="fill">True</property> |
1711 | + <property name="position">0</property> |
1712 | + </packing> |
1713 | + </child> |
1714 | + <child> |
1715 | + <object class="GtkCheckButton" id="cb_password_save"> |
1716 | + <property name="label" translatable="yes">Save Password to Keyring</property> |
1717 | + <property name="visible">True</property> |
1718 | + <property name="can_focus">True</property> |
1719 | + <property name="receives_default">False</property> |
1720 | + <property name="use_action_appearance">False</property> |
1721 | + <property name="draw_indicator">True</property> |
1722 | + <signal name="toggled" handler="on_cb_password_save_toggled" swapped="no"/> |
1723 | + </object> |
1724 | + <packing> |
1725 | + <property name="expand">False</property> |
1726 | + <property name="fill">True</property> |
1727 | + <property name="position">1</property> |
1728 | + </packing> |
1729 | + </child> |
1730 | + <child> |
1731 | + <object class="GtkCheckButton" id="cb_password_use_cache"> |
1732 | + <property name="label" translatable="yes">Cache Password for Cron (Security issue: root can read password)</property> |
1733 | + <property name="visible">True</property> |
1734 | + <property name="can_focus">True</property> |
1735 | + <property name="receives_default">False</property> |
1736 | + <property name="use_action_appearance">False</property> |
1737 | + <property name="draw_indicator">True</property> |
1738 | + </object> |
1739 | + <packing> |
1740 | + <property name="expand">False</property> |
1741 | + <property name="fill">True</property> |
1742 | + <property name="position">2</property> |
1743 | + </packing> |
1744 | + </child> |
1745 | + </object> |
1746 | + </child> |
1747 | + </object> |
1748 | + </child> |
1749 | + <child type="label"> |
1750 | + <object class="GtkLabel" id="lbl_password"> |
1751 | + <property name="visible">True</property> |
1752 | + <property name="can_focus">False</property> |
1753 | + <property name="label" translatable="yes"><b>Password</b></property> |
1754 | + <property name="use_markup">True</property> |
1755 | + </object> |
1756 | + </child> |
1757 | + </object> |
1758 | + <packing> |
1759 | + <property name="expand">False</property> |
1760 | + <property name="fill">True</property> |
1761 | + <property name="position">850</property> |
1762 | + </packing> |
1763 | + </child> |
1764 | + <child> |
1765 | <object class="GtkFrame" id="advanced"> |
1766 | <property name="visible">True</property> |
1767 | <property name="can_focus">False</property> |
1768 | |
1769 | === modified file 'gnome/settingsdialog.py' |
1770 | --- gnome/settingsdialog.py 2013-03-04 21:16:20 +0000 |
1771 | +++ gnome/settingsdialog.py 2013-03-07 23:23:23 +0000 |
1772 | @@ -26,12 +26,14 @@ |
1773 | import gobject |
1774 | import datetime |
1775 | import gettext |
1776 | +import subprocess |
1777 | +import keyring |
1778 | |
1779 | import config |
1780 | import messagebox |
1781 | import tools |
1782 | import mount |
1783 | - |
1784 | +import password |
1785 | |
1786 | _=gettext.gettext |
1787 | |
1788 | @@ -77,7 +79,9 @@ |
1789 | 'on_btn_where_clicked': self.on_btn_where_clicked, |
1790 | 'on_cb_backup_mode_changed': self.on_cb_backup_mode_changed, |
1791 | 'on_cb_auto_host_user_profile_toggled': self.update_host_user_profile, |
1792 | - 'on_combo_modes_changed': self.on_combo_modes_changed |
1793 | + 'on_combo_modes_changed': self.on_combo_modes_changed, |
1794 | + 'on_cb_password_save_toggled': self.update_password_save, |
1795 | + 'on_btn_ssh_private_key_file_clicked': self.on_btn_ssh_private_key_file_clicked |
1796 | } |
1797 | |
1798 | builder.connect_signals(signals) |
1799 | @@ -139,6 +143,7 @@ |
1800 | self.txt_ssh_port = get('txt_ssh_port') |
1801 | self.txt_ssh_user = get('txt_ssh_user') |
1802 | self.txt_ssh_path = get('txt_ssh_path') |
1803 | + self.txt_private_key_file = get('txt_ssh_private_key_file') |
1804 | |
1805 | self.store_ssh_cipher = gtk.ListStore(str, str) |
1806 | keys = self.config.SSH_CIPHERS.keys() |
1807 | @@ -159,6 +164,13 @@ |
1808 | ## self.txt_dummy_port = get('txt_dummy_port') |
1809 | ## self.txt_dummy_user = get('txt_dummy_user') |
1810 | |
1811 | + #password |
1812 | + self.frame_password = get('password') |
1813 | + self.txt_password = get('txt_password') |
1814 | + self.txt_password.set_visibility(False) |
1815 | + self.cb_password_save = get('cb_password_save') |
1816 | + self.cb_password_use_cache = get('cb_password_use_cache') |
1817 | + |
1818 | #automatic backup mode store |
1819 | self.store_backup_mode = gtk.ListStore( str, int ) |
1820 | map = self.config.AUTOMATIC_BACKUP_MODES |
1821 | @@ -413,6 +425,22 @@ |
1822 | else: |
1823 | fcd.destroy() |
1824 | |
1825 | + def on_btn_ssh_private_key_file_clicked( self, button ): |
1826 | + file = self.txt_private_key_file.get_text() |
1827 | + |
1828 | + fcd = gtk.FileChooserDialog( _('SSH private key'), self.dialog, gtk.FILE_CHOOSER_ACTION_OPEN, (gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL, gtk.STOCK_OPEN, gtk.RESPONSE_OK) ) |
1829 | + if len( file ) > 0: |
1830 | + fcd.set_filename( file ) |
1831 | + else: |
1832 | + fcd.set_filename(self.config.get_ssh_private_key_folder()) |
1833 | + |
1834 | + if fcd.run() == gtk.RESPONSE_OK: |
1835 | + new_file = tools.prepare_path( fcd.get_filename() ) |
1836 | + fcd.destroy() |
1837 | + self.txt_private_key_file.set_text( new_file ) |
1838 | + else: |
1839 | + fcd.destroy() |
1840 | + |
1841 | def on_cb_backup_mode_changed( self, *params ): |
1842 | iter = self.cb_backup_mode.get_active_iter() |
1843 | if iter is None: |
1844 | @@ -459,6 +487,12 @@ |
1845 | self.lbl_profile.set_sensitive( value ) |
1846 | self.txt_profile.set_sensitive( value ) |
1847 | |
1848 | + def update_password_save(self, *params): |
1849 | + value = self.cb_password_save.get_active() |
1850 | + if value and tools.check_home_encrypt(): |
1851 | + value = False |
1852 | + self.cb_password_use_cache.set_sensitive(value) |
1853 | + |
1854 | def on_combo_modes_changed(self, *params): |
1855 | iter = self.combo_modes.get_active_iter() |
1856 | if iter is None: |
1857 | @@ -472,6 +506,10 @@ |
1858 | else: |
1859 | getattr(self, 'mode_%s' % mode).hide() |
1860 | self.mode = active_mode |
1861 | + if active_mode in self.config.SNAPSHOT_MODES_NEED_PASSWORD: |
1862 | + self.frame_password.show() |
1863 | + else: |
1864 | + self.frame_password.hide() |
1865 | |
1866 | def on_combo_profiles_changed( self, *params ): |
1867 | if self.disable_combo_changed: |
1868 | @@ -541,6 +579,7 @@ |
1869 | self.txt_ssh_port.set_text( str(self.config.get_ssh_port( self.profile_id )) ) |
1870 | self.txt_ssh_user.set_text( self.config.get_ssh_user( self.profile_id ) ) |
1871 | self.txt_ssh_path.set_text( self.config.get_snapshots_path_ssh( self.profile_id ) ) |
1872 | + self.txt_private_key_file.set_text( self.config.get_ssh_private_key_file( self.profile_id ) ) |
1873 | #set chipher |
1874 | i = 0 |
1875 | iter = self.store_ssh_cipher.get_iter_first() |
1876 | @@ -557,6 +596,15 @@ |
1877 | ## self.txt_dummy_port.set_text( str(self.config.get_dummy_port( self.profile_id )) ) |
1878 | ## self.txt_dummy_user.set_text( self.config.get_dummy_user( self.profile_id ) ) |
1879 | |
1880 | + #password |
1881 | + password = self.config.get_password( profile_id = self.profile_id, mode = self.mode, only_from_keyring = True ) |
1882 | + if password is None: |
1883 | + password = '' |
1884 | + self.txt_password.set_text(password) |
1885 | + self.cb_password_save.set_active( self.config.get_password_save( self.profile_id, self.mode ) ) |
1886 | + self.cb_password_use_cache.set_active( self.config.get_password_use_cache( self.profile_id, self.mode ) ) |
1887 | + self.update_password_save() |
1888 | + |
1889 | #per directory schedule |
1890 | #self.cb_per_directory_schedule.set_active( self.config.get_per_directory_schedule() ) |
1891 | |
1892 | @@ -749,6 +797,9 @@ |
1893 | self.error_handler( _('Custom Hours can only be a comma seperate list of hours (e.g. 8,12,18,23) or */3 for periodic backups every 3 hours') ) |
1894 | return False |
1895 | |
1896 | + #password |
1897 | + password = self.txt_password.get_text() |
1898 | + |
1899 | mount_kwargs = {} |
1900 | |
1901 | #ssh settings |
1902 | @@ -756,10 +807,18 @@ |
1903 | ssh_port = self.txt_ssh_port.get_text() |
1904 | ssh_user = self.txt_ssh_user.get_text() |
1905 | ssh_path = self.txt_ssh_path.get_text() |
1906 | + ssh_private_key_file = self.txt_private_key_file.get_text() |
1907 | iter = self.combo_ssh_cipher.get_active_iter() |
1908 | ssh_cipher = self.store_ssh_cipher.get_value( iter, 1 ) |
1909 | if mode == 'ssh': |
1910 | - mount_kwargs = { 'host': ssh_host, 'port': int(ssh_port), 'user': ssh_user, 'path': ssh_path, 'cipher': ssh_cipher } |
1911 | + mount_kwargs = {'host': ssh_host, |
1912 | + 'port': int(ssh_port), |
1913 | + 'user': ssh_user, |
1914 | + 'path': ssh_path, |
1915 | + 'cipher': ssh_cipher, |
1916 | + 'private_key_file': ssh_private_key_file, |
1917 | + 'password': password |
1918 | + } |
1919 | |
1920 | ## #dummy settings |
1921 | ## dummy_host = self.txt_dummy_host.get_text() |
1922 | @@ -769,7 +828,11 @@ |
1923 | ## #values must have exactly the same Type (str, int or bool) |
1924 | ## #as they are set in config or you will run into false-positive |
1925 | ## #HashCollision warnings |
1926 | -## mount_kwargs = { 'host': dummy_host, 'port': int(dummy_port), 'user': dummy_user } |
1927 | +## mount_kwargs = {'host': dummy_host, |
1928 | +## 'port': int(dummy_port), |
1929 | +## 'user': dummy_user, |
1930 | +## 'password': password |
1931 | +## } |
1932 | |
1933 | if not self.config.SNAPSHOT_MODES[mode][0] is None: |
1934 | #pre_mount_check |
1935 | @@ -805,12 +868,32 @@ |
1936 | self.config.set_ssh_user(ssh_user, self.profile_id) |
1937 | self.config.set_snapshots_path_ssh(ssh_path, self.profile_id) |
1938 | self.config.set_ssh_cipher(ssh_cipher, self.profile_id) |
1939 | + self.config.set_ssh_private_key_file(ssh_private_key_file, self.profile_id) |
1940 | |
1941 | ## #save dummy |
1942 | ## self.config.set_dummy_host(dummy_host, self.profile_id) |
1943 | ## self.config.set_dummy_port(dummy_port, self.profile_id) |
1944 | ## self.config.set_dummy_user(dummy_user, self.profile_id) |
1945 | |
1946 | + #save password |
1947 | + if self.cb_password_save.get_active(): |
1948 | + if self.config.get_keyring_backend() == '': |
1949 | + self.config.set_keyring_backend('gnomekeyring') |
1950 | + if self.config.get_keyring_backend() == 'kwallet': |
1951 | + if keyring.backend.KDEKWallet().supported() == 1: |
1952 | + keyring.set_keyring(keyring.backend.KDEKWallet()) |
1953 | + else: |
1954 | + self.config.set_keyring_backend('gnomekeyring') |
1955 | + if self.config.get_keyring_backend() == 'gnomekeyring': |
1956 | + if keyring.backend.GnomeKeyring().supported() == 1: |
1957 | + keyring.set_keyring(keyring.backend.GnomeKeyring()) |
1958 | + else: |
1959 | + self.error_handler( _('Can\'t connect to gnomekeyring to save password')) |
1960 | + return False |
1961 | + self.config.set_password_save(self.cb_password_save.get_active(), self.profile_id, mode) |
1962 | + self.config.set_password_use_cache(self.cb_password_use_cache.get_active(), self.profile_id, mode) |
1963 | + self.config.set_password(password, self.profile_id, mode) |
1964 | + |
1965 | #if not msg is None: |
1966 | # messagebox.show_error( self.dialog, self.config, msg ) |
1967 | # return False |
1968 | @@ -1062,5 +1145,13 @@ |
1969 | return False |
1970 | |
1971 | self.config.save() |
1972 | + |
1973 | + #start Password_Cache if not running |
1974 | + daemon = password.Password_Cache(self.config) |
1975 | + if not daemon.status(): |
1976 | + try: |
1977 | + subprocess.check_call(['backintime', '--pw-cache', 'start'], stdout=open(os.devnull, 'w')) |
1978 | + except subprocess.CalledProcessError as e: |
1979 | + messagebox.show_error(self.dialog, self.config, _('start Password Cache failed: %s') % e.strerror) |
1980 | return True |
1981 | |
1982 | |
1983 | === modified file 'gnome/textinputdialog.glade' |
1984 | --- gnome/textinputdialog.glade 2011-11-21 09:27:27 +0000 |
1985 | +++ gnome/textinputdialog.glade 2013-03-07 23:23:23 +0000 |
1986 | @@ -63,6 +63,19 @@ |
1987 | </packing> |
1988 | </child> |
1989 | <child> |
1990 | + <object class="GtkLabel" id="prompt_text"> |
1991 | + <property name="visible">False</property> |
1992 | + <property name="can_focus">False</property> |
1993 | + <property name="xalign">0</property> |
1994 | + <property name="label" translatable="yes">Text</property> |
1995 | + </object> |
1996 | + <packing> |
1997 | + <property name="expand">True</property> |
1998 | + <property name="fill">True</property> |
1999 | + <property name="position">1</property> |
2000 | + </packing> |
2001 | + </child> |
2002 | + <child> |
2003 | <object class="GtkEntry" id="edit_text"> |
2004 | <property name="visible">True</property> |
2005 | <property name="can_focus">True</property> |
2006 | @@ -72,7 +85,7 @@ |
2007 | <packing> |
2008 | <property name="expand">False</property> |
2009 | <property name="fill">True</property> |
2010 | - <property name="position">1</property> |
2011 | + <property name="position">2</property> |
2012 | </packing> |
2013 | </child> |
2014 | </object> |
2015 | |
2016 | === modified file 'kde4/app.py' |
2017 | --- kde4/app.py 2012-10-31 21:45:38 +0000 |
2018 | +++ kde4/app.py 2013-03-07 23:23:23 +0000 |
2019 | @@ -343,7 +343,7 @@ |
2020 | |
2021 | #mount |
2022 | try: |
2023 | - mnt = mount.Mount(cfg = self.config, profile_id = profile_id) |
2024 | + mnt = mount.Mount(cfg = self.config, profile_id = profile_id, parent = self) |
2025 | hash_id = mnt.mount() |
2026 | except mount.MountException as ex: |
2027 | KMessageBox.error( self, QString.fromUtf8( str(ex) ) ) |
2028 | @@ -413,7 +413,7 @@ |
2029 | |
2030 | #umount |
2031 | try: |
2032 | - mnt = mount.Mount(cfg = self.config) |
2033 | + mnt = mount.Mount(cfg = self.config, parent = self) |
2034 | mnt.umount(self.config.current_hash_id) |
2035 | except mount.MountException as ex: |
2036 | KMessageBox.error( self, QString.fromUtf8( str(ex) ) ) |
2037 | @@ -465,7 +465,7 @@ |
2038 | |
2039 | def remount( self, new_profile_id, old_profile_id): |
2040 | try: |
2041 | - mnt = mount.Mount(cfg = self.config, profile_id = old_profile_id) |
2042 | + mnt = mount.Mount(cfg = self.config, profile_id = old_profile_id, parent = self) |
2043 | hash_id = mnt.remount(new_profile_id) |
2044 | except mount.MountException as ex: |
2045 | KMessageBox.error( self, QString.fromUtf8( str(ex) ) ) |
2046 | |
2047 | === modified file 'kde4/debian_specific/control' |
2048 | --- kde4/debian_specific/control 2013-01-09 21:02:50 +0000 |
2049 | +++ kde4/debian_specific/control 2013-03-07 23:23:23 +0000 |
2050 | @@ -7,7 +7,7 @@ |
2051 | Architecture: all |
2052 | Homepage: http://backintime.le-web.org |
2053 | Depends: x11-utils, kdesudo, libnotify-bin, python-kde4 (>= 4.1), backintime-common (>= 1.0.21~), backintime-notify (>= 1.0.21~) |
2054 | -Suggests: kompare |
2055 | +Suggests: kompare, python-keyring-kwallet |
2056 | Conflicts: backintime-kde |
2057 | Replaces: backintime-kde |
2058 | Description: Simple backup system for KDE4 Desktop |
2059 | |
2060 | === added file 'kde4/messagebox.py' |
2061 | --- kde4/messagebox.py 1970-01-01 00:00:00 +0000 |
2062 | +++ kde4/messagebox.py 2013-03-07 23:23:23 +0000 |
2063 | @@ -0,0 +1,49 @@ |
2064 | +# Copyright (c) 2012-2013 Germar Reitze |
2065 | +# |
2066 | +# This program is free software; you can redistribute it and/or modify |
2067 | +# it under the terms of the GNU General Public License as published by |
2068 | +# the Free Software Foundation; either version 2 of the License, or |
2069 | +# (at your option) any later version. |
2070 | +# |
2071 | +# This program is distributed in the hope that it will be useful, |
2072 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
2073 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
2074 | +# GNU General Public License for more details. |
2075 | +# |
2076 | +# You should have received a copy of the GNU General Public License along |
2077 | +# with this program; if not, write to the Free Software Foundation, Inc., |
2078 | +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
2079 | + |
2080 | +import sys |
2081 | +from PyKDE4.kdecore import ki18n, KAboutData, KCmdLineArgs |
2082 | +from PyKDE4.kdeui import KApplication, KPasswordDialog |
2083 | +from PyQt4.QtCore import QString, QTimer, SIGNAL |
2084 | + |
2085 | +import app |
2086 | + |
2087 | +def ask_password_dialog(parent, config, title, prompt, timeout = None): |
2088 | + if parent is None: |
2089 | + kapp, kaboutData = app.create_kapplication( config ) |
2090 | + |
2091 | + dialog = KPasswordDialog() |
2092 | + |
2093 | + timer = QTimer() |
2094 | + if not timeout is None: |
2095 | + dialog.connect(timer, SIGNAL("timeout()"), dialog.close) |
2096 | + timer.setInterval(timeout * 1000) |
2097 | + timer.start() |
2098 | + |
2099 | + dialog.setPrompt( QString.fromUtf8(prompt)) |
2100 | + dialog.show() |
2101 | + KApplication.processEvents() |
2102 | + |
2103 | + if parent is None: |
2104 | + kapp.exec_() |
2105 | + else: |
2106 | + dialog.exec_() |
2107 | + |
2108 | + timer.stop() |
2109 | + password = dialog.password() |
2110 | + del(dialog) |
2111 | + |
2112 | + return(password) |
2113 | |
2114 | === modified file 'kde4/settingsdialog.py' |
2115 | --- kde4/settingsdialog.py 2013-03-04 21:16:20 +0000 |
2116 | +++ kde4/settingsdialog.py 2013-03-07 23:23:23 +0000 |
2117 | @@ -22,6 +22,8 @@ |
2118 | import datetime |
2119 | import gettext |
2120 | import copy |
2121 | +import subprocess |
2122 | +import keyring |
2123 | |
2124 | from PyQt4.QtGui import * |
2125 | from PyQt4.QtCore import * |
2126 | @@ -33,6 +35,7 @@ |
2127 | import tools |
2128 | import kde4tools |
2129 | import mount |
2130 | +import password |
2131 | |
2132 | |
2133 | _=gettext.gettext |
2134 | @@ -150,6 +153,8 @@ |
2135 | vlayout.addLayout( hlayout1 ) |
2136 | hlayout2 = QHBoxLayout() |
2137 | vlayout.addLayout( hlayout2 ) |
2138 | + hlayout3 = QHBoxLayout() |
2139 | + vlayout.addLayout( hlayout3 ) |
2140 | |
2141 | self.lbl_ssh_host = QLabel( QString.fromUtf8( _( 'Host:' ) ), self ) |
2142 | hlayout1.addWidget( self.lbl_ssh_host ) |
2143 | @@ -172,11 +177,21 @@ |
2144 | hlayout2.addWidget( self.txt_ssh_path ) |
2145 | |
2146 | self.lbl_ssh_cipher = QLabel( QString.fromUtf8( _( 'Cipher:' ) ), self ) |
2147 | - hlayout2.addWidget( self.lbl_ssh_cipher ) |
2148 | + hlayout3.addWidget( self.lbl_ssh_cipher ) |
2149 | self.combo_ssh_cipher = KComboBox( self ) |
2150 | - hlayout2.addWidget( self.combo_ssh_cipher ) |
2151 | + hlayout3.addWidget( self.combo_ssh_cipher ) |
2152 | self.fill_combo( self.combo_ssh_cipher, self.config.SSH_CIPHERS ) |
2153 | |
2154 | + self.lbl_ssh_private_key_file = QLabel( QString.fromUtf8( _( 'Private Key:' ) ), self ) |
2155 | + hlayout3.addWidget( self.lbl_ssh_private_key_file ) |
2156 | + self.txt_ssh_private_key_file = KLineEdit( self ) |
2157 | + self.txt_ssh_private_key_file.setReadOnly( True ) |
2158 | + hlayout3.addWidget( self.txt_ssh_private_key_file ) |
2159 | + |
2160 | + self.btn_ssh_private_key_file = KPushButton( KIcon( 'folder' ), '', self ) |
2161 | + hlayout3.addWidget( self.btn_ssh_private_key_file ) |
2162 | + QObject.connect( self.btn_ssh_private_key_file, SIGNAL('clicked()'), self.on_btn_ssh_private_key_file_clicked ) |
2163 | + |
2164 | ## #Dummy |
2165 | ## group_box = QGroupBox( self ) |
2166 | ## self.mode_dummy = group_box |
2167 | @@ -203,12 +218,36 @@ |
2168 | ## self.txt_dummy_user = KLineEdit( self ) |
2169 | ## hlayout.addWidget( self.txt_dummy_user ) |
2170 | |
2171 | + #password |
2172 | + group_box = QGroupBox( self ) |
2173 | + self.frame_password = group_box |
2174 | + group_box.setTitle( QString.fromUtf8( _( 'Password' ) ) ) |
2175 | + layout.addWidget( group_box ) |
2176 | + |
2177 | + vlayout = QVBoxLayout( group_box ) |
2178 | + |
2179 | + self.txt_password = KLineEdit( self ) |
2180 | + self.txt_password.setPasswordMode(True) |
2181 | + vlayout.addWidget( self.txt_password ) |
2182 | + |
2183 | + self.cb_password_save = QCheckBox( QString.fromUtf8( _( 'Save Password to Keyring' ) ), self ) |
2184 | + QObject.connect( self.cb_password_save, SIGNAL('stateChanged(int)'), self.update_password_save ) |
2185 | + vlayout.addWidget( self.cb_password_save ) |
2186 | + |
2187 | + self.cb_password_use_cache = QCheckBox( QString.fromUtf8( _( 'Cache Password for Cron (Security issue: root can read password)' ) ), self ) |
2188 | + vlayout.addWidget( self.cb_password_use_cache ) |
2189 | + |
2190 | + #mode change |
2191 | QObject.connect( self.combo_modes, SIGNAL('currentIndexChanged(int)'), self.on_combo_modes_changed ) |
2192 | self.on_combo_modes_changed() |
2193 | |
2194 | #host, user, profile id |
2195 | - hlayout = QHBoxLayout() |
2196 | - layout.addLayout( hlayout ) |
2197 | + group_box = QGroupBox( self ) |
2198 | + self.frame_advanced = group_box |
2199 | + group_box.setTitle( QString.fromUtf8( _( 'Advanced' ) ) ) |
2200 | + layout.addWidget( group_box ) |
2201 | + |
2202 | + hlayout = QHBoxLayout( group_box ) |
2203 | hlayout.addSpacing( 12 ) |
2204 | |
2205 | vlayout2 = QVBoxLayout() |
2206 | @@ -620,6 +659,12 @@ |
2207 | self.lbl_profile.setEnabled( enabled ) |
2208 | self.txt_profile.setEnabled( enabled ) |
2209 | |
2210 | + def update_password_save( self ): |
2211 | + enabled = self.cb_password_save.isChecked() |
2212 | + if enabled and tools.check_home_encrypt(): |
2213 | + enabled = False |
2214 | + self.cb_password_use_cache.setEnabled( enabled ) |
2215 | + |
2216 | def update_profiles( self ): |
2217 | self.update_profile() |
2218 | current_profile_id = self.config.get_current_profile() |
2219 | @@ -657,12 +702,22 @@ |
2220 | self.txt_ssh_user.setText( QString.fromUtf8( self.config.get_ssh_user() ) ) |
2221 | self.txt_ssh_path.setText( QString.fromUtf8( self.config.get_snapshots_path_ssh() ) ) |
2222 | self.set_combo_value( self.combo_ssh_cipher, self.config.get_ssh_cipher(), type = 'str' ) |
2223 | + self.txt_ssh_private_key_file.setText( QString.fromUtf8( self.config.get_ssh_private_key_file() ) ) |
2224 | |
2225 | ## #dummy |
2226 | ## self.txt_dummy_host.setText( QString.fromUtf8( self.config.get_dummy_host() ) ) |
2227 | ## self.txt_dummy_port.setText( QString.fromUtf8( self.config.get_dummy_port() ) ) |
2228 | ## self.txt_dummy_user.setText( QString.fromUtf8( self.config.get_dummy_user() ) ) |
2229 | |
2230 | + #password |
2231 | + password = self.config.get_password( mode = self.mode, only_from_keyring = True ) |
2232 | + if password is None: |
2233 | + password = '' |
2234 | + self.txt_password.setText( QString.fromUtf8( password ) ) |
2235 | + self.cb_password_save.setChecked( self.config.get_password_save( mode = self.mode ) ) |
2236 | + self.cb_password_use_cache.setChecked( self.config.get_password_use_cache( mode = self.mode ) ) |
2237 | + self.update_password_save() |
2238 | + |
2239 | self.cb_auto_host_user_profile.setChecked( self.config.get_auto_host_user_profile() ) |
2240 | host, user, profile = self.config.get_host_user_profile() |
2241 | self.txt_host.setText( QString.fromUtf8( host ) ) |
2242 | @@ -750,14 +805,25 @@ |
2243 | self.config.set_snapshots_mode( mode ) |
2244 | mount_kwargs = {} |
2245 | |
2246 | + #password |
2247 | + password = str( self.txt_password.text().toUtf8() ) |
2248 | + |
2249 | #ssh |
2250 | ssh_host = str( self.txt_ssh_host.text().toUtf8() ) |
2251 | ssh_port = str( self.txt_ssh_port.text().toUtf8() ) |
2252 | ssh_user = str( self.txt_ssh_user.text().toUtf8() ) |
2253 | ssh_path = str( self.txt_ssh_path.text().toUtf8() ) |
2254 | ssh_cipher = str( self.combo_ssh_cipher.itemData( self.combo_ssh_cipher.currentIndex() ).toString().toUtf8() ) |
2255 | + ssh_private_key_file = str( self.txt_ssh_private_key_file.text().toUtf8() ) |
2256 | if mode == 'ssh': |
2257 | - mount_kwargs = { 'host': ssh_host, 'port': int(ssh_port), 'user': ssh_user, 'path': ssh_path, 'cipher': ssh_cipher } |
2258 | + mount_kwargs = {'host': ssh_host, |
2259 | + 'port': int(ssh_port), |
2260 | + 'user': ssh_user, |
2261 | + 'path': ssh_path, |
2262 | + 'cipher': ssh_cipher, |
2263 | + 'private_key_file': ssh_private_key_file, |
2264 | + 'password': password |
2265 | + } |
2266 | |
2267 | ## #dummy |
2268 | ## dummy_host = str( self.txt_dummy_host.text().toUtf8() ) |
2269 | @@ -767,7 +833,11 @@ |
2270 | ## #values must have exactly the same Type (str, int or bool) |
2271 | ## #as they are set in config or you will run into false-positive |
2272 | ## #HashCollision warnings |
2273 | -## mount_kwargs = { 'host': dummy_host, 'port': int(dummy_port), 'user': dummy_user } |
2274 | +## mount_kwargs = {'host': dummy_host, |
2275 | +## 'port': int(dummy_port), |
2276 | +## 'user': dummy_user, |
2277 | +## 'password': password |
2278 | +## } |
2279 | |
2280 | if not self.config.SNAPSHOT_MODES[mode][0] is None: |
2281 | #pre_mount_check |
2282 | @@ -805,12 +875,34 @@ |
2283 | self.config.set_ssh_user(ssh_user) |
2284 | self.config.set_snapshots_path_ssh(ssh_path) |
2285 | self.config.set_ssh_cipher(ssh_cipher) |
2286 | + self.config.set_ssh_private_key_file(ssh_private_key_file) |
2287 | |
2288 | ## #save dummy |
2289 | ## self.config.set_dummy_host(dummy_host) |
2290 | ## self.config.set_dummy_port(dummy_port) |
2291 | ## self.config.set_dummy_user(dummy_user) |
2292 | |
2293 | + #save password |
2294 | + if self.cb_password_save.isChecked(): |
2295 | + if self.config.get_keyring_backend() == '': |
2296 | + self.config.set_keyring_backend('kwallet') |
2297 | + if self.config.get_keyring_backend() == 'gnomekeyring': |
2298 | + if keyring.backend.GnomeKeyring().supported() == 1: |
2299 | + keyring.set_keyring(keyring.backend.GnomeKeyring()) |
2300 | + else: |
2301 | + self.config.set_keyring_backend('kwallet') |
2302 | + if self.config.get_keyring_backend() == 'kwallet': |
2303 | + if keyring.backend.KDEKWallet().supported() == 1: |
2304 | + keyring.set_keyring(keyring.backend.KDEKWallet()) |
2305 | + else: |
2306 | + #in Kubuntu keyring.backend.KDEKWallet is included in python-keyring |
2307 | + #but Debian uses an extra package for this: python-keyring-kwallet |
2308 | + self.error_handler( _('Can\'t connect to KWallet to save password. On Debian you need to install:\n\'apt-get install python-keyring-kwallet\'')) |
2309 | + return False |
2310 | + self.config.set_password_save(self.cb_password_save.isChecked(), mode = mode) |
2311 | + self.config.set_password_use_cache(self.cb_password_use_cache.isChecked(), mode = mode) |
2312 | + self.config.set_password(password, mode = mode) |
2313 | + |
2314 | #include list |
2315 | include_list = [] |
2316 | for index in xrange( self.list_include.topLevelItemCount() ): |
2317 | @@ -992,6 +1084,15 @@ |
2318 | return False |
2319 | |
2320 | self.config.save() |
2321 | + |
2322 | + #start Password_Cache if not running |
2323 | + daemon = password.Password_Cache(self.config) |
2324 | + if not daemon.status(): |
2325 | + try: |
2326 | + subprocess.check_call(['backintime', '--pw-cache', 'start'], stdout=open(os.devnull, 'w')) |
2327 | + except subprocess.CalledProcessError as e: |
2328 | + self.error_handler( _('start Password Cache failed: %s') % e.strerror) |
2329 | + |
2330 | return True |
2331 | |
2332 | def on_btn_exclude_remove_clicked ( self ): |
2333 | @@ -1085,6 +1186,17 @@ |
2334 | if not self.question_handler( _('Are you sure you want to change snapshots folder ?') ): |
2335 | return |
2336 | self.edit_snapshots_path.setText( QString.fromUtf8( self.config.prepare_path( path ) ) ) |
2337 | + |
2338 | + def on_btn_ssh_private_key_file_clicked( self ): |
2339 | + old_file = str( self.txt_ssh_private_key_file.text().toUtf8() ) |
2340 | + |
2341 | + if len(old_file) > 0: |
2342 | + start_dir = KUrl( self.txt_ssh_private_key_file.text() ) |
2343 | + else: |
2344 | + start_dir = KUrl( self.config.get_ssh_private_key_folder() ) |
2345 | + file = str( KFileDialog.getOpenFileName( start_dir, QString.fromUtf8(''), self, QString.fromUtf8( _( 'SSH private key' ) ) ).toUtf8() ) |
2346 | + if len( file ) > 0 : |
2347 | + self.txt_ssh_private_key_file.setText( QString.fromUtf8( self.config.prepare_path( file ) ) ) |
2348 | |
2349 | def on_combo_modes_changed(self, *params): |
2350 | if len(params) == 0: |
2351 | @@ -1099,6 +1211,10 @@ |
2352 | else: |
2353 | getattr(self, 'mode_%s' % mode).hide() |
2354 | self.mode = active_mode |
2355 | + if active_mode in self.config.SNAPSHOT_MODES_NEED_PASSWORD: |
2356 | + self.frame_password.show() |
2357 | + else: |
2358 | + self.frame_password.hide() |
2359 | |
2360 | def accept( self ): |
2361 | if self.validate(): |
2362 | |
2363 | === modified file 'makedeb.sh' |
2364 | --- makedeb.sh 2012-10-27 14:42:44 +0000 |
2365 | +++ makedeb.sh 2013-03-07 23:23:23 +0000 |
2366 | @@ -21,6 +21,7 @@ |
2367 | echo "Installed-Size: `du -sk tmp | cut -f1`" >> tmp/DEBIAN/control |
2368 | |
2369 | cp $i/debian_specific/postrm tmp/DEBIAN/postrm |
2370 | + [ -e $i/debian_specific/conffiles ] && cp $i/debian_specific/conffiles tmp/DEBIAN/conffiles |
2371 | |
2372 | dpkg --build tmp/ $PKGNAME-${PKGVER}_$PKGARCH.deb |
2373 |