2 ["/home/#{$live_user}/.claws-mail",
3 "/home/#{$live_user}/.gconf/system/networking/connections",
4 "/home/#{$live_user}/.gnome2/keyrings",
5 "/home/#{$live_user}/.gnupg",
6 "/home/#{$live_user}/.mozilla/firefox/bookmarks",
7 "/home/#{$live_user}/.purple",
8 "/home/#{$live_user}/.ssh",
9 "/home/#{$live_user}/Persistent",
10 "/var/cache/apt/archives",
14 def persistent_volumes_mountpoints
15 @vm.execute("ls -1 /live/persistence/*_unlocked/").stdout.chomp.split
18 Given /^I create a new (\d+) ([[:alpha:]]+) USB drive named "([^"]+)"$/ do |size, unit, name|
19 next if @skip_steps_while_restoring_background
20 @vm.storage.create_new_disk(name, {:size => size, :unit => unit})
23 Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
24 next if @skip_steps_while_restoring_background
25 @vm.storage.clone_to_new_disk(from, to)
28 Given /^I unplug USB drive "([^"]+)"$/ do |name|
29 next if @skip_steps_while_restoring_background
30 @vm.unplug_drive(name)
33 Given /^the computer is set to boot from the old Tails DVD$/ do
34 next if @skip_steps_while_restoring_background
35 @vm.set_cdrom_boot($old_tails_iso)
38 class ISOHybridUpgradeNotSupported < StandardError
41 def usb_install_helper(name)
42 @screen.wait('USBCreateLiveUSB.png', 10)
44 # Here we'd like to select USB drive using #{name}, but Sikuli's
45 # OCR seems to be too unreliable.
46 # @screen.wait('USBTargetDevice.png', 10)
47 # match = @screen.find('USBTargetDevice.png')
49 # region_y = match.y + match.height
50 # region_w = match.width*3
51 # region_h = match.height*2
52 # ocr = Sikuli::Region.new(region_x, region_y, region_w, region_h).text
54 # # Unfortunately this results in almost garbage, like "|]dev/sdm"
55 # # when it should be /dev/sda1
57 @screen.wait_and_click('USBCreateLiveUSB.png', 10)
59 if @screen.find("USBSuggestsInstall.png")
60 raise ISOHybridUpgradeNotSupported
62 rescue Sikuli::ImageNotFound
63 # we didn't get the warning, so we can proceed with the install
66 @screen.wait_and_click('USBCreateLiveUSBNext.png', 10)
68 @screen.wait('USBInstallationComplete.png', 60*60)
69 @screen.type(Sikuli::KEY_RETURN)
70 @screen.type(Sikuli::KEY_F4, Sikuli::KEY_ALT)
73 When /^I "Clone & Install" Tails to USB drive "([^"]+)"$/ do |name|
74 next if @skip_steps_while_restoring_background
75 step "I run \"liveusb-creator-launcher\""
76 @screen.wait_and_click('USBCloneAndInstall.png', 30)
77 usb_install_helper(name)
80 When /^I "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
81 next if @skip_steps_while_restoring_background
82 step "I run \"liveusb-creator-launcher\""
83 @screen.wait_and_click('USBCloneAndUpgrade.png', 30)
84 usb_install_helper(name)
87 When /^I try a "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
88 next if @skip_steps_while_restoring_background
90 step "I \"Clone & Upgrade\" Tails to USB drive \"#{name}\""
91 rescue ISOHybridUpgradeNotSupported
92 # this is what we expect
94 raise "The USB installer should not succeed"
98 When /^I am suggested to do a "Clone & Upgrade"$/ do
99 next if @skip_steps_while_restoring_background
100 @screen.find("USBSuggestsInstall.png")
103 def shared_iso_dir_on_guest
107 Given /^I setup a filesystem share containing the Tails ISO$/ do
108 next if @skip_steps_while_restoring_background
109 @vm.add_share(File.dirname($tails_iso), shared_iso_dir_on_guest)
112 When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
113 next if @skip_steps_while_restoring_background
114 step "I run \"liveusb-creator-launcher\""
115 @screen.wait_and_click('USBUpgradeFromISO.png', 10)
116 @screen.wait('USBUseLiveSystemISO.png', 10)
117 match = @screen.find('USBUseLiveSystemISO.png')
118 pos_x = match.x + match.width/2
119 pos_y = match.y + match.height*2
120 @screen.click(pos_x, pos_y)
121 @screen.wait('USBSelectISO.png', 10)
122 @screen.wait_and_click('GnomeFileDiagTypeFilename.png', 10)
123 iso = "#{shared_iso_dir_on_guest}/#{File.basename($tails_iso)}"
124 @screen.type(iso + Sikuli::KEY_RETURN)
125 usb_install_helper(name)
128 Given /^I enable all persistence presets$/ do
129 next if @skip_steps_while_restoring_background
130 @screen.wait('PersistenceWizardPresets.png', 20)
131 # Mark first non-default persistence preset
133 # Check all non-default persistence presets
137 @screen.wait_and_click('PersistenceWizardSave.png', 10)
138 @screen.wait('PersistenceWizardDone.png', 20)
139 @screen.type(Sikuli::KEY_F4, Sikuli::KEY_ALT)
142 Given /^I create a persistent partition with password "([^"]+)"$/ do |pwd|
143 next if @skip_steps_while_restoring_background
144 step "I run \"tails-persistence-setup\""
145 @screen.wait('PersistenceWizardWindow.png', 20)
146 @screen.wait('PersistenceWizardStart.png', 20)
147 @screen.type(pwd + "\t" + pwd + Sikuli::KEY_RETURN)
148 @screen.wait('PersistenceWizardPresets.png', 300)
149 step "I enable all persistence presets"
152 def check_part_integrity(name, dev, usage, type, scheme, label)
153 info = @vm.execute("udisks --show-info #{dev}").stdout
154 info_split = info.split("\n partition:\n")
155 dev_info = info_split[0]
156 part_info = info_split[1]
157 assert(dev_info.match("^ usage: +#{usage}$"),
158 "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'")
159 assert(dev_info.match("^ type: +#{type}$"),
160 "Unexpected device field 'type' on USB drive '#{name}', '#{dev}'")
161 assert(part_info.match("^ scheme: +#{scheme}$"),
162 "Unexpected partition scheme on USB drive '#{name}', '#{dev}'")
163 assert(part_info.match("^ label: +#{label}$"),
164 "Unexpected partition label on USB drive '#{name}', '#{dev}'")
167 Then /^Tails is installed on USB drive "([^"]+)"$/ do |name|
168 next if @skip_steps_while_restoring_background
169 dev = @vm.disk_dev(name) + "1"
170 check_part_integrity(name, dev, "filesystem", "vfat", "gpt", "Tails")
172 old_root = "/lib/live/mount/medium"
173 new_root = "/mnt/new"
174 @vm.execute("mkdir -p #{new_root}")
175 @vm.execute("mount #{dev} #{new_root}")
177 c = @vm.execute("diff -qr '#{old_root}/live' '#{new_root}/live'")
179 "USB drive '#{name}' has differences in /live:\n#{c.stdout}")
181 loader = boot_device_type == "usb" ? "syslinux" : "isolinux"
182 syslinux_files = @vm.execute("ls -1 #{new_root}/syslinux").stdout.chomp.split
183 # We deal with these files separately
184 ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.sys"]
185 for f in syslinux_files - ignores do
186 c = @vm.execute("diff -q '#{old_root}/#{loader}/#{f}' " +
187 "'#{new_root}/syslinux/#{f}'")
188 assert(c.success?, "USB drive '#{name}' has differences in " +
192 # The main .cfg is named differently vs isolinux
193 c = @vm.execute("diff -q '#{old_root}/#{loader}/#{loader}.cfg' " +
194 "'#{new_root}/syslinux/syslinux.cfg'")
195 assert(c.success?, "USB drive '#{name}' has differences in " +
196 "'/syslinux/syslinux.cfg'")
198 # We have to account for the different path vs isolinux
199 old_exithelp = @vm.execute("cat '#{old_root}/#{loader}/exithelp.cfg'").stdout
200 new_exithelp = @vm.execute("cat '#{new_root}/syslinux/exithelp.cfg'").stdout
201 new_exithelp_undiffed = new_exithelp.sub("kernel /syslinux/vesamenu.c32",
202 "kernel /#{loader}/vesamenu.c32")
203 assert(new_exithelp_undiffed == old_exithelp,
204 "USB drive '#{name}' has unexpected differences in " +
205 "'/syslinux/exithelp.cfg'")
207 @vm.execute("umount #{new_root}")
211 Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name|
212 next if @skip_steps_while_restoring_background
213 data_part_dev = @vm.disk_dev(name) + "2"
214 assert(!@vm.execute("test -b #{data_part_dev}").success?,
215 "USB drive #{name} has a partition '#{data_part_dev}'")
218 Then /^a Tails persistence partition with password "([^"]+)" exists on USB drive "([^"]+)"$/ do |pwd, name|
219 next if @skip_steps_while_restoring_background
220 dev = @vm.disk_dev(name) + "2"
221 check_part_integrity(name, dev, "crypto", "crypto_LUKS", "gpt", "TailsData")
223 c = @vm.execute("echo #{pwd} | cryptsetup luksOpen #{dev} #{name}")
224 assert(c.success?, "Couldn't open LUKS device '#{dev}' on drive '#{name}'")
225 luks_dev = "/dev/mapper/#{name}"
227 # Adapting check_part_integrity() seems like a bad idea so here goes
228 info = @vm.execute("udisks --show-info #{luks_dev}").stdout
229 assert info.match("^ cleartext luks device:$")
230 assert info.match("^ usage: +filesystem$")
231 assert info.match("^ type: +ext[34]$")
232 assert info.match("^ label: +TailsData$")
234 mount_dir = "/mnt/#{name}"
235 @vm.execute("mkdir -p #{mount_dir}")
236 c = @vm.execute("mount #{luks_dev} #{mount_dir}")
238 "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
240 @vm.execute("umount #{mount_dir}")
242 @vm.execute("cryptsetup luksClose #{name}")
245 Given /^I enable persistence with password "([^"]+)"$/ do |pwd|
246 next if @skip_steps_while_restoring_background
247 match = @screen.find('TailsGreeterPersistence.png')
248 pos_x = match.x + match.width/2
249 # height*2 may seem odd, but we want to click the button below the
250 # match. This may even work accross different screen resolutions.
251 pos_y = match.y + match.height*2
252 @screen.click(pos_x, pos_y)
253 @screen.wait('TailsGreeterPersistencePassphrase.png', 10)
254 match = @screen.find('TailsGreeterPersistencePassphrase.png')
255 pos_x = match.x + match.width*2
256 pos_y = match.y + match.height/2
257 @screen.click(pos_x, pos_y)
261 Given /^persistence is not enabled$/ do
262 next if @skip_steps_while_restoring_background
263 data_part_dev = boot_device + "2"
264 assert(!@vm.execute("grep -q '^#{data_part_dev} ' /proc/mounts").success?,
265 "Partition '#{data_part_dev}' from the boot device is mounted")
268 Given /^I enable read-only persistence with password "([^"]+)"$/ do |pwd|
269 step "I enable persistence with password \"#{pwd}\""
270 next if @skip_steps_while_restoring_background
271 @screen.wait_and_click('TailsGreeterPersistenceReadOnly.png', 10)
274 Given /^persistence has been enabled$/ do
275 next if @skip_steps_while_restoring_background
276 try_for(120, :msg => "Some persistent dir was not mounted") {
277 mount = @vm.execute("mount").stdout.chomp
278 persistent_dirs.each do |dir|
279 if ! mount.include? "on #{dir} "
280 raise "persistent dir #{dir} missing"
287 # Approach borrowed from
288 # config/chroot_local_includes/lib/live/config/998-permissions
289 boot_dev_id = @vm.execute("udevadm info --device-id-of-file=/lib/live/mount/medium").stdout.chomp
290 boot_dev = @vm.execute("readlink -f /dev/block/'#{boot_dev_id}'").stdout.chomp
295 # Approach borrowed from
296 # config/chroot_local_includes/lib/live/config/998-permissions
297 boot_dev_info = @vm.execute("udevadm info --query=property --name='#{boot_device}'").stdout.chomp
298 boot_dev_type = (boot_dev_info.split("\n").select { |x| x.start_with? "ID_BUS=" })[0].split("=")[1]
302 Then /^Tails is running from USB drive "([^"]+)"$/ do |name|
303 next if @skip_steps_while_restoring_background
304 assert(boot_device_type == "usb",
305 "Got device type '#{boot_device_type}' while expecting 'usb'")
306 actual_dev = boot_device
307 # The boot partition differs between a "normal" install using the
308 # USB installer and isohybrid installations
309 expected_dev_normal = @vm.disk_dev(name) + "1"
310 expected_dev_isohybrid = @vm.disk_dev(name) + "4"
311 assert(actual_dev == expected_dev_normal ||
312 actual_dev == expected_dev_isohybrid,
313 "We are running from device #{actual_dev}, but for USB drive " +
314 "'#{name}' we expected to run from either device " +
315 "#{expected_dev_normal} (when installed via the USB installer) " +
316 "or #{expected_dev_normal} (when installed from an isohybrid)")
319 Then /^the boot device has safe access rights$/ do
320 next if @skip_steps_while_restoring_background
322 super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "")
323 devs = @vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split
324 assert(devs.size > 0, "Could not determine boot device")
325 all_users = @vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split
326 all_users_with_groups = all_users.collect do |user|
327 groups = @vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ")
331 dev_owner = @vm.execute("stat -c %U #{dev}").stdout.chomp
332 dev_group = @vm.execute("stat -c %G #{dev}").stdout.chomp
333 dev_perms = @vm.execute("stat -c %a #{dev}").stdout.chomp
334 assert(dev_owner == "root",
335 "Boot device '#{dev}' owned by user '#{dev_owner}', expected 'root'")
336 assert(dev_group == "disk" || dev_group == "root",
337 "Boot device '#{dev}' owned by group '#{dev_group}', expected " +
339 assert(dev_perms == "660",
340 "Boot device '#{dev}' has permissions '#{dev_perms}', expected '660'")
341 for user, groups in all_users_with_groups do
342 next if user == "root"
343 assert(!(groups.include?(dev_group)),
344 "Unprivileged user '#{user}' is in group '#{dev_group}' which " +
345 "owns boot device '#{dev}'")
350 Then /^persistent filesystems have safe access rights$/ do
351 persistent_volumes_mountpoints.each do |mountpoint|
352 fs_owner = @vm.execute("stat -c %U #{mountpoint}").stdout.chomp
353 fs_group = @vm.execute("stat -c %G #{mountpoint}").stdout.chomp
354 fs_perms = @vm.execute("stat -c %a #{mountpoint}").stdout.chomp
355 assert(fs_owner == "root",
356 "Persistent filesystem '#{mountpoint}' owned by user '#{fs_owner}', expected 'root'")
357 assert(fs_group == "amnesia",
358 "Persistent filesystem '#{mountpoint}' owned by group '#{fs_group}', expected 'amnesia'")
359 assert(fs_perms == '1777',
360 "Persistent filesystem '#{mountpoint}' has permissions '#{fs_perms}', expected '1777'")
364 Then /^persistence configuration files have safe access rights$/ do
365 persistent_volumes_mountpoints.each do |mountpoint|
366 assert(@vm.execute("test -e #{mountpoint}/persistence.conf").success?,
367 "#{mountpoint}/persistence.conf does not exist, while it should")
368 assert(@vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?,
369 "#{mountpoint}/live-persistence.conf does exist, while it should not")
371 "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf"
372 ).stdout.chomp.split.each do |f|
373 file_owner = @vm.execute("stat -c %U '#{f}'").stdout.chomp
374 file_group = @vm.execute("stat -c %G '#{f}'").stdout.chomp
375 file_perms = @vm.execute("stat -c %a '#{f}'").stdout.chomp
376 assert(file_owner = "tails-persistence-setup",
377 "'#{f}' is owned by user '#{file_owner}', expected 'tails-persistence-setup'")
378 assert(file_group = "tails-persistence-setup",
379 "'#{f}' is owned by group '#{file_group}', expected 'tails-persistence-setup'")
380 assert(file_perms = "600",
381 "'#{f}' has permissions '#{file_perms}', expected '600'")
386 When /^I write some files expected to persist$/ do
387 next if @skip_steps_while_restoring_background
388 persistent_dirs.each do |dir|
389 owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
390 assert(@vm.execute("touch #{dir}/XXX_persist", user=owner).success?,
391 "Could not create file in persistent directory #{dir}")
395 When /^I remove some files expected to persist$/ do
396 next if @skip_steps_while_restoring_background
397 persistent_dirs.each do |dir|
398 assert(@vm.execute("rm #{dir}/XXX_persist").success?,
399 "Could not remove file in persistent directory #{dir}")
403 When /^I write some files not expected to persist$/ do
404 next if @skip_steps_while_restoring_background
405 persistent_dirs.each do |dir|
406 owner = @vm.execute("stat -c %U #{dir}").stdout.chomp
407 assert(@vm.execute("touch #{dir}/XXX_gone", user=owner).success?,
408 "Could not create file in persistent directory #{dir}")
412 Then /^the expected persistent files are present in the filesystem$/ do
413 next if @skip_steps_while_restoring_background
414 persistent_dirs.each do |dir|
415 assert(@vm.execute("test -e #{dir}/XXX_persist").success?,
416 "Could not find expected file in persistent directory #{dir}")
417 assert(!@vm.execute("test -e #{dir}/XXX_gone").success?,
418 "Found file that should not have persisted in persistent directory #{dir}")
422 Then /^only the expected files should persist on USB drive "([^"]+)"$/ do |name|
423 next if @skip_steps_while_restoring_background
425 step "the computer is set to boot from USB drive \"#{name}\""
426 step "the network is unplugged"
427 step "I start the computer"
428 step "the computer boots Tails"
429 step "I enable read-only persistence with password \"asdf\""
430 step "I log in to a new session"
431 step "persistence has been enabled"
432 step "GNOME has started"
433 step "I have closed all annoying notifications"
434 step "the expected persistent files are present in the filesystem"
435 step "I completely shutdown Tails"
438 When /^I delete the persistent partition$/ do
439 next if @skip_steps_while_restoring_background
440 step "I run \"tails-persistence-setup --step delete\""
441 @screen.wait("PersistenceWizardWindow.png", 20)
442 @screen.wait("PersistenceWizardDeletionStart.png", 20)
444 @screen.wait("PersistenceWizardDone.png", 120)