4 # Sometimes the first click is lost (presumably it's used to give
5 # focus to virt-viewer or similar) so we do that now rather than
6 # having an important click lost. The point we click should be
7 # somewhere where no clickable elements generally reside.
8 @screen.click_point(@screen.w, @screen.h/2)
11 def activate_filesystem_shares
12 # XXX-9p: First of all, filesystem shares cannot be mounted while we
13 # do a snapshot save+restore, so unmounting+remounting them seems
14 # like a good idea. However, the 9p modules get into a broken state
15 # during the save+restore, so we also would like to unload+reload
16 # them, but loading of 9pnet_virtio fails after a restore with
17 # "probe of virtio2 failed with error -2" (in dmesg) which makes the
18 # shares unavailable. Hence we leave this code commented for now.
19 #for mod in ["9pnet_virtio", "9p"] do
20 # @vm.execute("modprobe #{mod}")
23 @vm.list_shares.each do |share|
24 @vm.execute("mkdir -p #{share}")
25 @vm.execute("mount -t 9p -o trans=virtio #{share} #{share}")
29 def deactivate_filesystem_shares
30 @vm.list_shares.each do |share|
31 @vm.execute("umount #{share}")
34 # XXX-9p: See XXX-9p above
35 #for mod in ["9p", "9pnet_virtio"] do
36 # @vm.execute("modprobe -r #{mod}")
40 def restore_background
41 @vm.restore_snapshot($background_snapshot)
42 @vm.wait_until_remote_shell_is_up
45 # XXX-9p: See XXX-9p above
46 #activate_filesystem_shares
48 # The guest's Tor's circuits' states are likely to get out of sync
49 # with the other relays, so we ensure that we have fresh circuits.
50 # Time jumps and incorrect clocks also confuses Tor in many ways.
52 if @vm.execute("service tor status").success?
53 @vm.execute("service tor stop")
54 @vm.execute("rm -f /var/log/tor/log")
55 @vm.execute("killall vidalia")
56 @vm.host_to_guest_time_sync
57 @vm.execute("service tor start")
58 wait_until_tor_is_working
59 @vm.spawn("/usr/local/sbin/restart-vidalia")
64 Given /^a computer$/ do
66 @vm = VM.new($vm_xml_path, $x_display)
69 Given /^the computer has (\d+) ([[:alpha:]]+) of RAM$/ do |size, unit|
70 next if @skip_steps_while_restoring_background
71 @vm.set_ram_size(size, unit)
74 Given /^the computer is set to boot from the Tails DVD$/ do
75 next if @skip_steps_while_restoring_background
76 @vm.set_cdrom_boot($tails_iso)
79 Given /^the computer is set to boot from (.+?) drive "(.+?)"$/ do |type, name|
80 next if @skip_steps_while_restoring_background
81 @vm.set_disk_boot(name, type.downcase)
84 Given /^I plug ([[:alpha:]]+) drive "([^"]+)"$/ do |bus, name|
85 next if @skip_steps_while_restoring_background
86 @vm.plug_drive(name, bus.downcase)
88 step "drive \"#{name}\" is detected by Tails"
92 Then /^drive "([^"]+)" is detected by Tails$/ do |name|
93 next if @skip_steps_while_restoring_background
95 try_for(10, :msg => "Drive '#{name}' is not detected by Tails") {
96 @vm.disk_detected?(name)
99 STDERR.puts "Cannot tell if drive '#{name}' is detected by Tails: " +
100 "Tails is not running"
104 Given /^the network is plugged$/ do
105 next if @skip_steps_while_restoring_background
109 Given /^the network is unplugged$/ do
110 next if @skip_steps_while_restoring_background
114 Given /^I capture all network traffic$/ do
115 # Note: We don't want skip this particular stpe if
116 # @skip_steps_while_restoring_background is set since it starts
117 # something external to the VM state.
118 @sniffer = Sniffer.new("TestSniffer", @vm.net.bridge_name)
122 Given /^I set Tails to boot with options "([^"]*)"$/ do |options|
123 next if @skip_steps_while_restoring_background
124 @boot_options = options
127 When /^I start the computer$/ do
128 next if @skip_steps_while_restoring_background
129 assert(!@vm.is_running?,
130 "Trying to start a VM that is already running")
135 Given /^I start Tails from DVD(| with network unplugged) and I login$/ do |network_unplugged|
136 # we don't @skip_steps_while_restoring_background as we're only running
137 # other steps, that are taking care of it *if* they have to
138 step "the computer is set to boot from the Tails DVD"
139 if network_unplugged.empty?
140 step "the network is plugged"
142 step "the network is unplugged"
144 step "I start the computer"
145 step "the computer boots Tails"
146 step "I log in to a new session"
147 step "Tails seems to have booted normally"
148 if network_unplugged.empty?
150 step "all notifications have disappeared"
151 step "available upgrades have been checked"
153 step "all notifications have disappeared"
157 Given /^I start Tails from (.+?) drive "(.+?)"(| with network unplugged) and I login(| with(| read-only) persistence password "([^"]+)")$/ do |drive_type, drive_name, network_unplugged, persistence_on, persistence_ro, persistence_pwd|
158 # we don't @skip_steps_while_restoring_background as we're only running
159 # other steps, that are taking care of it *if* they have to
160 step "the computer is set to boot from #{drive_type} drive \"#{drive_name}\""
161 if network_unplugged.empty?
162 step "the network is plugged"
164 step "the network is unplugged"
166 step "I start the computer"
167 step "the computer boots Tails"
168 if ! persistence_on.empty?
169 assert(! persistence_pwd.empty?, "A password must be provided when enabling persistence")
170 if persistence_ro.empty?
171 step "I enable persistence with password \"#{persistence_pwd}\""
173 step "I enable read-only persistence with password \"#{persistence_pwd}\""
176 step "I log in to a new session"
177 step "Tails seems to have booted normally"
178 if network_unplugged.empty?
180 step "all notifications have disappeared"
181 step "available upgrades have been checked"
183 step "all notifications have disappeared"
187 When /^I power off the computer$/ do
188 next if @skip_steps_while_restoring_background
189 assert(@vm.is_running?,
190 "Trying to power off an already powered off VM")
194 When /^I cold reboot the computer$/ do
195 next if @skip_steps_while_restoring_background
196 step "I power off the computer"
197 step "I start the computer"
200 When /^I destroy the computer$/ do
201 next if @skip_steps_while_restoring_background
205 Given /^the computer (re)?boots Tails$/ do |reboot|
206 next if @skip_steps_while_restoring_background
210 assert(!reboot, "Testing of reboot with UEFI enabled is not implemented")
211 bootsplash = 'TailsBootSplashUEFI.png'
212 bootsplash_tab_msg = 'TailsBootSplashTabMsgUEFI.png'
216 bootsplash = 'TailsBootSplashPostReset.png'
217 bootsplash_tab_msg = 'TailsBootSplashTabMsgPostReset.png'
220 bootsplash = 'TailsBootSplash.png'
221 bootsplash_tab_msg = 'TailsBootSplashTabMsg.png'
226 @screen.wait(bootsplash, boot_timeout)
227 @screen.wait(bootsplash_tab_msg, 10)
228 @screen.type(Sikuli::Key.TAB)
229 @screen.waitVanish(bootsplash_tab_msg, 1)
231 @screen.type(" autotest_never_use_this_option #{@boot_options}" +
233 @screen.wait('TailsGreeter.png', 30*60)
234 @vm.wait_until_remote_shell_is_up
235 activate_filesystem_shares
238 Given /^I log in to a new session$/ do
239 next if @skip_steps_while_restoring_background
240 @screen.wait_and_click('TailsGreeterLoginButton.png', 10)
243 Given /^I enable more Tails Greeter options$/ do
244 next if @skip_steps_while_restoring_background
245 match = @screen.find('TailsGreeterMoreOptions.png')
246 @screen.click(match.getCenter.offset(match.w/2, match.h*2))
247 @screen.wait_and_click('TailsGreeterForward.png', 10)
248 @screen.wait('TailsGreeterLoginButton.png', 20)
251 Given /^I set sudo password "([^"]*)"$/ do |password|
252 @sudo_password = password
253 next if @skip_steps_while_restoring_background
254 @screen.wait("TailsGreeterAdminPassword.png", 20)
255 @screen.type(@sudo_password)
256 @screen.type(Sikuli::Key.TAB)
257 @screen.type(@sudo_password)
260 Given /^Tails Greeter has dealt with the sudo password$/ do
261 next if @skip_steps_while_restoring_background
262 f1 = "/etc/sudoers.d/tails-greeter"
263 f2 = "#{f1}-no-password-lecture"
265 @vm.execute("test -e '#{f1}' -o -e '#{f2}'").success?
269 Given /^GNOME has started$/ do
270 next if @skip_steps_while_restoring_background
273 desktop_started_picture = 'WindowsStartButton.png'
275 desktop_started_picture = 'GnomeApplicationsMenu.png'
277 @screen.wait(desktop_started_picture, 180)
280 Then /^Tails seems to have booted normally$/ do
281 next if @skip_steps_while_restoring_background
282 step "GNOME has started"
285 Given /^Tor is ready$/ do
286 next if @skip_steps_while_restoring_background
287 @screen.wait("GnomeTorIsReady.png", 300)
288 @screen.waitVanish("GnomeTorIsReady.png", 15)
290 # Having seen the "Tor is ready" notification implies that Tor has
291 # built a circuit, but let's check it directly to be on the safe side.
292 step "Tor has built a circuit"
294 step "the time has synced"
297 Given /^Tor has built a circuit$/ do
298 next if @skip_steps_while_restoring_background
299 wait_until_tor_is_working
302 Given /^the time has synced$/ do
303 next if @skip_steps_while_restoring_background
304 ["/var/run/tordate/done", "/var/run/htpdate/success"].each do |file|
305 try_for(300) { @vm.execute("test -e #{file}").success? }
309 Given /^available upgrades have been checked$/ do
310 next if @skip_steps_while_restoring_background
312 @vm.execute("test -e '/var/run/tails-upgrader/checked_upgrades'").success?
316 Given /^the Tor Browser has started$/ do
317 next if @skip_steps_while_restoring_background
320 tor_browser_picture = "WindowsTorBrowserWindow.png"
322 tor_browser_picture = "TorBrowserWindow.png"
325 @screen.wait(tor_browser_picture, 60)
328 Given /^the Tor Browser has started and loaded the startup page$/ do
329 next if @skip_steps_while_restoring_background
330 step "the Tor Browser has started"
331 @screen.wait("TorBrowserStartupPage.png", 120)
334 Given /^the Tor Browser has started in offline mode$/ do
335 next if @skip_steps_while_restoring_background
336 @screen.wait("TorBrowserOffline.png", 60)
339 Given /^I add a bookmark to eff.org in the Tor Browser$/ do
340 next if @skip_steps_while_restoring_background
341 url = "https://www.eff.org"
342 step "I open the address \"#{url}\" in the Tor Browser"
343 @screen.wait("TorBrowserOffline.png", 5)
344 @screen.type("d", Sikuli::KeyModifier.CTRL)
345 @screen.wait("TorBrowserBookmarkPrompt.png", 10)
346 @screen.type(url + Sikuli::Key.ENTER)
349 Given /^the Tor Browser has a bookmark to eff.org$/ do
350 next if @skip_steps_while_restoring_background
351 @screen.type("b", Sikuli::KeyModifier.ALT)
352 @screen.wait("TorBrowserEFFBookmark.png", 10)
355 Given /^all notifications have disappeared$/ do
356 next if @skip_steps_while_restoring_background
359 notification_picture = "WindowsNotificationX.png"
361 notification_picture = "GnomeNotificationX.png"
363 @screen.waitVanish(notification_picture, 60)
366 Given /^I save the state so the background can be restored next scenario$/ do
367 if @skip_steps_while_restoring_background
368 assert(File.size?($background_snapshot),
369 "We have been skipping steps but there is no snapshot to restore")
371 # To be sure we run the feature from scratch we remove any
372 # leftover snapshot that wasn't removed.
373 if File.exist?($background_snapshot)
374 File.delete($background_snapshot)
376 # Workaround: when libvirt takes ownership of the snapshot it may
377 # become unwritable for the user running this script so it cannot
378 # be removed during clean up.
379 FileUtils.touch($background_snapshot)
380 FileUtils.chmod(0666, $background_snapshot)
382 # Snapshots cannot be saved while filesystem shares are mounted
383 # XXX-9p: See XXX-9p above.
384 #deactivate_filesystem_shares
386 @vm.save_snapshot($background_snapshot)
389 # Now we stop skipping steps from the snapshot restore.
390 @skip_steps_while_restoring_background = false
393 Then /^I see "([^"]*)" after at most (\d+) seconds$/ do |image, time|
394 next if @skip_steps_while_restoring_background
395 @screen.wait(image, time.to_i)
398 Then /^all Internet traffic has only flowed through Tor$/ do
399 next if @skip_steps_while_restoring_background
400 leaks = FirewallLeakCheck.new(@sniffer.pcap_file, get_tor_relays)
402 if !leaks.ipv4_tcp_leaks.empty?
403 puts "The following IPv4 TCP non-Tor Internet hosts were contacted:"
404 puts leaks.ipv4_tcp_leaks.join("\n")
407 if !leaks.ipv4_nontcp_leaks.empty?
408 puts "The following IPv4 non-TCP Internet hosts were contacted:"
409 puts leaks.ipv4_nontcp_leaks.join("\n")
412 if !leaks.ipv6_leaks.empty?
413 puts "The following IPv6 Internet hosts were contacted:"
414 puts leaks.ipv6_leaks.join("\n")
417 if !leaks.nonip_leaks.empty?
418 puts "Some non-IP packets were sent\n"
421 raise "There were network leaks!"
425 Given /^I enter the sudo password in the gksu prompt$/ do
426 next if @skip_steps_while_restoring_background
427 @screen.wait('GksuAuthPrompt.png', 60)
428 sleep 1 # wait for weird fade-in to unblock the "Ok" button
429 @screen.type(@sudo_password)
430 @screen.type(Sikuli::Key.ENTER)
431 @screen.waitVanish('GksuAuthPrompt.png', 10)
434 Given /^I enter the sudo password in the pkexec prompt$/ do
435 next if @skip_steps_while_restoring_background
436 step "I enter the \"#{@sudo_password}\" password in the pkexec prompt"
439 def deal_with_polkit_prompt (image, password)
440 @screen.wait(image, 60)
441 sleep 1 # wait for weird fade-in to unblock the "Ok" button
442 @screen.type(password)
443 @screen.type(Sikuli::Key.ENTER)
444 @screen.waitVanish(image, 10)
447 Given /^I enter the "([^"]*)" password in the pkexec prompt$/ do |password|
448 next if @skip_steps_while_restoring_background
449 deal_with_polkit_prompt('PolicyKitAuthPrompt.png', password)
452 Given /^process "([^"]+)" is running$/ do |process|
453 next if @skip_steps_while_restoring_background
454 assert(@vm.has_process?(process),
455 "Process '#{process}' is not running")
458 Given /^process "([^"]+)" is running within (\d+) seconds$/ do |process, time|
459 next if @skip_steps_while_restoring_background
460 try_for(time.to_i, :msg => "Process '#{process}' is not running after " +
461 "waiting for #{time} seconds") do
462 @vm.has_process?(process)
466 Given /^process "([^"]+)" is not running$/ do |process|
467 next if @skip_steps_while_restoring_background
468 assert(!@vm.has_process?(process),
469 "Process '#{process}' is running")
472 Given /^I kill the process "([^"]+)"$/ do |process|
473 next if @skip_steps_while_restoring_background
474 @vm.execute("killall #{process}")
475 try_for(10, :msg => "Process '#{process}' could not be killed") {
476 !@vm.has_process?(process)
480 Then /^Tails eventually shuts down$/ do
481 next if @skip_steps_while_restoring_background
482 nr_gibs_of_ram = (detected_ram_in_MiB.to_f/(2**10)).ceil
483 timeout = nr_gibs_of_ram*5*60
484 try_for(timeout, :msg => "VM is still running after #{timeout} seconds") do
489 Then /^Tails eventually restarts$/ do
490 next if @skip_steps_while_restoring_background
491 nr_gibs_of_ram = (detected_ram_in_MiB.to_f/(2**10)).ceil
492 @screen.wait('TailsBootSplashPostReset.png', nr_gibs_of_ram*5*60)
495 Given /^I shutdown Tails and wait for the computer to power off$/ do
496 next if @skip_steps_while_restoring_background
497 @vm.execute("poweroff")
498 step 'Tails eventually shuts down'
501 When /^I request a shutdown using the emergency shutdown applet$/ do
502 next if @skip_steps_while_restoring_background
504 @screen.wait_and_click('TailsEmergencyShutdownButton.png', 10)
506 @screen.wait_and_click('TailsEmergencyShutdownHalt.png', 10)
509 When /^I warm reboot the computer$/ do
510 next if @skip_steps_while_restoring_background
511 @vm.execute("reboot")
514 When /^I request a reboot using the emergency shutdown applet$/ do
515 next if @skip_steps_while_restoring_background
517 @screen.wait_and_click('TailsEmergencyShutdownButton.png', 10)
519 @screen.wait_and_click('TailsEmergencyShutdownReboot.png', 10)
522 Given /^package "([^"]+)" is installed$/ do |package|
523 next if @skip_steps_while_restoring_background
524 assert(@vm.execute("dpkg -s '#{package}' 2>/dev/null | grep -qs '^Status:.*installed$'").success?,
525 "Package '#{package}' is not installed")
528 When /^I start the Tor Browser$/ do
529 next if @skip_steps_while_restoring_background
530 step 'I start "TorBrowser" via the GNOME "Internet" applications menu'
533 When /^I start the Tor Browser in offline mode$/ do
534 next if @skip_steps_while_restoring_background
535 step "I start the Tor Browser"
538 @screen.wait_and_click("WindowsTorBrowserOfflinePrompt.png", 10)
539 @screen.click("WindowsTorBrowserOfflinePromptStart.png")
541 @screen.wait_and_click("TorBrowserOfflinePrompt.png", 10)
542 @screen.click("TorBrowserOfflinePromptStart.png")
546 def xul_app_shared_lib_check(pid, chroot)
547 expected_absent_tbb_libs = ['libnssdbm3.so']
549 unwanted_native_libs = []
550 tbb_libs = @vm.execute_successfully(
551 ". /usr/local/lib/tails-shell-library/tor-browser.sh; " +
552 "ls -1 #{chroot}${TBB_INSTALL}/*.so"
554 firefox_pmap_info = @vm.execute("pmap #{pid}").stdout
555 for lib in tbb_libs do
556 lib_name = File.basename lib
557 if not /\W#{lib}$/.match firefox_pmap_info
558 absent_tbb_libs << lib_name
560 native_libs = @vm.execute_successfully(
561 "find /usr/lib /lib -name \"#{lib_name}\""
563 for native_lib in native_libs do
564 if /\W#{native_lib}$"/.match firefox_pmap_info
565 unwanted_native_libs << lib_name
569 absent_tbb_libs -= expected_absent_tbb_libs
570 assert(absent_tbb_libs.empty? && unwanted_native_libs.empty?,
571 "The loaded shared libraries for the firefox process are not the " +
572 "way we expect them.\n" +
573 "Expected TBB libs that are absent: #{absent_tbb_libs}\n" +
574 "Native libs that we don't want: #{unwanted_native_libs}")
577 Then /^(.*) uses all expected TBB shared libraries$/ do |application|
578 next if @skip_steps_while_restoring_background
579 binary = @vm.execute_successfully(
580 '. /usr/local/lib/tails-shell-library/tor-browser.sh; ' +
581 'echo ${TBB_INSTALL}/firefox'
584 when "the Tor Browser"
586 cmd_regex = "#{binary} .* -profile /home/#{user}/\.tor-browser/profile\.default"
588 when "the Unsafe Browser"
590 cmd_regex = "#{binary} .* -profile /home/#{user}/\.tor-browser/profile\.default"
591 chroot = "/var/lib/unsafe-browser/chroot"
593 user = "tor-launcher"
594 cmd_regex = "#{binary} -app /home/#{user}/\.tor-launcher/tor-launcher-standalone/application\.ini"
597 raise "Invalid browser or XUL application: #{application}"
599 pid = @vm.execute_successfully("pgrep --uid #{user} --full --exact '#{cmd_regex}'").stdout.chomp
600 assert(/\A\d+\z/.match(pid), "It seems like #{application} is not running")
601 xul_app_shared_lib_check(pid, chroot)
604 Given /^I add a wired DHCP NetworkManager connection called "([^"]+)"$/ do |con_name|
605 next if @skip_steps_while_restoring_background
612 uuid=bbc60668-1be0-11e4-a9c6-2f1ce0e75bf1
622 con_content.split("\n").each do |line|
623 @vm.execute("echo '#{line}' >> /tmp/NM.#{con_name}")
625 @vm.execute("install -m 0600 '/tmp/NM.#{con_name}' '/etc/NetworkManager/system-connections/#{con_name}'")
627 nm_con_list = @vm.execute("nmcli --terse --fields NAME con list").stdout
628 nm_con_list.split("\n").include? "#{con_name}"
632 Given /^I switch to the "([^"]+)" NetworkManager connection$/ do |con_name|
633 next if @skip_steps_while_restoring_background
634 @vm.execute("nmcli con up id #{con_name}")
636 @vm.execute("nmcli --terse --fields NAME,STATE con status").stdout.chomp == "#{con_name}:activated"
640 When /^I start and focus GNOME Terminal$/ do
641 next if @skip_steps_while_restoring_background
642 step 'I start "Terminal" via the GNOME "Accessories" applications menu'
643 @screen.wait_and_click('GnomeTerminalWindow.png', 20)
646 When /^I run "([^"]+)" in GNOME Terminal$/ do |command|
647 next if @skip_steps_while_restoring_background
648 step "I start and focus GNOME Terminal"
649 @screen.type(command + Sikuli::Key.ENTER)
652 When /^the file "([^"]+)" exists$/ do |file|
653 next if @skip_steps_while_restoring_background
654 assert(@vm.file_exist?(file))
657 When /^I copy "([^"]+)" to "([^"]+)" as user "([^"]+)"$/ do |source, destination, user|
658 next if @skip_steps_while_restoring_background
659 c = @vm.execute("cp \"#{source}\" \"#{destination}\"", $live_user)
660 assert(c.success?, "Failed to copy file:\n#{c.stdout}\n#{c.stderr}")
663 Given /^the USB drive "([^"]+)" contains Tails with persistence configured and password "([^"]+)"$/ do |drive, password|
665 step "I start Tails from DVD with network unplugged and I login"
666 step "I create a new 4 GiB USB drive named \"#{drive}\""
667 step "I plug USB drive \"#{drive}\""
668 step "I \"Clone & Install\" Tails to USB drive \"#{drive}\""
669 step "there is no persistence partition on USB drive \"#{drive}\""
670 step "I shutdown Tails and wait for the computer to power off"
672 step "I start Tails from USB drive \"#{drive}\" with network unplugged and I login"
673 step "I create a persistent partition with password \"#{password}\""
674 step "a Tails persistence partition with password \"#{password}\" exists on USB drive \"#{drive}\""
675 step "I shutdown Tails and wait for the computer to power off"
678 Given /^I start "([^"]+)" via the GNOME "([^"]+)" applications menu$/ do |app, submenu|
679 next if @skip_steps_while_restoring_background
686 @screen.wait_and_click(prefix + "ApplicationsMenu.png", 10)
687 @screen.wait_and_hover(prefix + "Applications" + submenu + ".png", 40)
688 @screen.wait_and_click(prefix + "Applications" + app + ".png", 40)
691 Given /^I start "([^"]+)" via the GNOME "([^"]+)"\/"([^"]+)" applications menu$/ do |app, submenu, subsubmenu|
692 next if @skip_steps_while_restoring_background
699 @screen.wait_and_click(prefix + "ApplicationsMenu.png", 10)
700 @screen.wait_and_hover(prefix + "Applications" + submenu + ".png", 20)
701 @screen.wait_and_hover(prefix + "Applications" + subsubmenu + ".png", 20)
702 @screen.wait_and_click(prefix + "Applications" + app + ".png", 20)