summaryrefslogtreecommitdiffstats
path: root/cucumber/features/step_definitions
diff options
context:
space:
mode:
Diffstat (limited to 'cucumber/features/step_definitions')
-rw-r--r--cucumber/features/step_definitions/apt.rb132
-rw-r--r--cucumber/features/step_definitions/browser.rb100
-rw-r--r--cucumber/features/step_definitions/build.rb46
-rw-r--r--cucumber/features/step_definitions/checks.rb124
-rw-r--r--cucumber/features/step_definitions/common_steps.rb579
-rw-r--r--cucumber/features/step_definitions/dhcp.rb34
-rw-r--r--cucumber/features/step_definitions/electrum.rb18
-rw-r--r--cucumber/features/step_definitions/encryption.rb16
-rw-r--r--cucumber/features/step_definitions/firewall_leaks.rb33
-rw-r--r--cucumber/features/step_definitions/git.rb26
-rw-r--r--cucumber/features/step_definitions/icedove.rb94
-rw-r--r--cucumber/features/step_definitions/mac_spoofing.rb87
-rw-r--r--cucumber/features/step_definitions/pidgin.rb126
-rw-r--r--cucumber/features/step_definitions/root_access_control.rb3
-rw-r--r--cucumber/features/step_definitions/snapshots.rb15
-rw-r--r--cucumber/features/step_definitions/ssh.rb52
-rw-r--r--cucumber/features/step_definitions/time_syncing.rb16
-rw-r--r--cucumber/features/step_definitions/tor.rb152
-rw-r--r--cucumber/features/step_definitions/torified_browsing.rb8
-rw-r--r--cucumber/features/step_definitions/torified_gnupg.rb89
-rw-r--r--cucumber/features/step_definitions/torified_misc.rb16
-rw-r--r--cucumber/features/step_definitions/totem.rb23
-rw-r--r--cucumber/features/step_definitions/unsafe_browser.rb45
-rw-r--r--cucumber/features/step_definitions/untrusted_partitions.rb4
-rw-r--r--cucumber/features/step_definitions/usb.rb347
25 files changed, 1261 insertions, 924 deletions
diff --git a/cucumber/features/step_definitions/apt.rb b/cucumber/features/step_definitions/apt.rb
index c69d2598..52ef9f7f 100644
--- a/cucumber/features/step_definitions/apt.rb
+++ b/cucumber/features/step_definitions/apt.rb
@@ -2,55 +2,123 @@ require 'uri'
Given /^the only hosts in APT sources are "([^"]*)"$/ do |hosts_str|
hosts = hosts_str.split(',')
- $vm.file_content("/etc/apt/sources.list /etc/apt/sources.list.d/*").chomp.each_line { |line|
+ apt_sources = $vm.execute_successfully(
+ "cat /etc/apt/sources.list /etc/apt/sources.list.d/*"
+ ).stdout.chomp
+ apt_sources.each_line do |line|
next if ! line.start_with? "deb"
source_host = URI(line.split[1]).host
if !hosts.include?(source_host)
raise "Bad APT source '#{line}'"
end
- }
+ end
+end
+
+Given /^no proposed-updates APT suite is enabled$/ do
+ apt_sources = $vm.execute_successfully(
+ 'cat /etc/apt/sources.list /etc/apt/sources.list.d/*'
+ ).stdout
+ assert_no_match(/\s\S+-proposed-updates\s/, apt_sources)
+end
+
+When /^I configure APT to use non-onion sources$/ do
+ script = <<-EOF
+ use strict;
+ use warnings FATAL => "all";
+ s{vwakviie2ienjx6t[.]onion}{ftp.us.debian.org};
+ s{sgvtcaew4bxjd7ln[.]onion}{security.debian.org};
+ s{sdscoq7snqtznauu[.]onion}{deb.torproject.org};
+ s{jenw7xbd6tf7vfhp[.]onion}{deb.tails.boum.org};
+EOF
+ # VMCommand:s cannot handle newlines, and they're irrelevant in the
+ # above perl script any way
+ script.delete!("\n")
+ $vm.execute_successfully(
+ "perl -pi -E '#{script}' /etc/apt/sources.list /etc/apt/sources.list.d/*"
+ )
end
When /^I update APT using apt$/ do
- Timeout::timeout(30*60) do
- $vm.execute_successfully("echo #{@sudo_password} | " +
- "sudo -S apt update", :user => LIVE_USER)
+ recovery_proc = Proc.new do
+ step 'I kill the process "apt"'
+ $vm.execute('rm -rf /var/lib/apt/lists/*')
+ end
+ retry_tor(recovery_proc) do
+ Timeout::timeout(15*60) do
+ $vm.execute_successfully("echo #{@sudo_password} | " +
+ "sudo -S apt update", :user => LIVE_USER)
+ end
end
end
-Then /^I should be able to install a package using apt$/ do
- package = "cowsay"
- Timeout::timeout(120) do
- $vm.execute_successfully("echo #{@sudo_password} | " +
- "sudo -S apt install #{package}",
- :user => LIVE_USER)
+Then /^I install "(.+)" using apt$/ do |package_name|
+ recovery_proc = Proc.new do
+ step 'I kill the process "apt"'
+ $vm.execute("apt purge #{package_name}")
+ end
+ retry_tor(recovery_proc) do
+ Timeout::timeout(2*60) do
+ $vm.execute_successfully("echo #{@sudo_password} | " +
+ "sudo -S apt install #{package_name}",
+ :user => LIVE_USER)
+ end
end
- step "package \"#{package}\" is installed"
end
-When /^I update APT using Synaptic$/ do
- @screen.click('SynapticReloadButton.png')
- @screen.wait('SynapticReloadPrompt.png', 20)
- @screen.waitVanish('SynapticReloadPrompt.png', 30*60)
+When /^I start Synaptic$/ do
+ step 'I start "Synaptic Package Manager" via GNOME Activities Overview'
+ deal_with_polkit_prompt(@sudo_password)
+ @synaptic = Dogtail::Application.new('synaptic')
+ # The seemingly spurious space is needed because that is how this
+ # frame is named...
+ @synaptic.child(
+ 'Synaptic Package Manager ', roleName: 'frame', recursive: false
+ )
end
-Then /^I should be able to install a package using Synaptic$/ do
- package = "cowsay"
- try_for(60) do
- @screen.wait_and_click('SynapticSearchButton.png', 10)
- @screen.wait_and_click('SynapticSearchWindow.png', 10)
+When /^I update APT using Synaptic$/ do
+ recovery_proc = Proc.new do
+ step 'I kill the process "synaptic"'
+ step "I start Synaptic"
+ end
+ retry_tor(recovery_proc) do
+ @synaptic.button('Reload').click
+ sleep 10 # It might take some time before APT starts downloading
+ try_for(15*60, :msg => "Took too much time to download the APT data") {
+ !$vm.has_process?("/usr/lib/apt/methods/tor+http")
+ }
+ assert_raise(RuntimeError) do
+ @synaptic.child(roleName: 'dialog', recursive: false)
+ .child('Error', roleName: 'icon', retry: false)
+ end
+ if !$vm.has_process?("synaptic")
+ raise "Synaptic process vanished, did it segfault again?"
+ end
end
- @screen.type(package + Sikuli::Key.ENTER)
- @screen.wait_and_double_click('SynapticCowsaySearchResult.png', 20)
- @screen.wait_and_click('SynapticApplyButton.png', 10)
- @screen.wait('SynapticApplyPrompt.png', 60)
- @screen.type(Sikuli::Key.ENTER)
- @screen.wait('SynapticChangesAppliedPrompt.png', 240)
- step "package \"#{package}\" is installed"
end
-When /^I start Synaptic$/ do
- step 'I start "Synaptic" via the GNOME "System" applications menu'
- deal_with_polkit_prompt('PolicyKitAuthPrompt.png', @sudo_password)
- @screen.wait('SynapticReloadButton.png', 30)
+Then /^I install "(.+)" using Synaptic$/ do |package_name|
+ recovery_proc = Proc.new do
+ step 'I kill the process "synaptic"'
+ $vm.execute("apt -y purge #{package_name}")
+ step "I start Synaptic"
+ end
+ retry_tor(recovery_proc) do
+ @synaptic.button('Search').click
+ find_dialog = @synaptic.dialog('Find')
+ find_dialog.child(roleName: 'text').typeText(package_name)
+ find_dialog.button('Search').click
+ package_list = @synaptic.child('Installed Version',
+ roleName: 'table column header').parent
+ package_entry = package_list.child(package_name, roleName: 'table cell')
+ package_entry.doubleClick
+ @synaptic.button('Apply').click
+ apply_prompt = nil
+ try_for(60) { apply_prompt = @synaptic.dialog('Summary'); true }
+ apply_prompt.button('Apply').click
+ try_for(4*60) do
+ @synaptic.child('Changes applied', roleName: 'frame', recursive: false)
+ true
+ end
+ end
end
diff --git a/cucumber/features/step_definitions/browser.rb b/cucumber/features/step_definitions/browser.rb
index 84ef1d35..68d1bca4 100644
--- a/cucumber/features/step_definitions/browser.rb
+++ b/cucumber/features/step_definitions/browser.rb
@@ -1,41 +1,28 @@
-Then /^I see the (Unsafe|I2P) Browser start notification and wait for it to close$/ do |browser_type|
- robust_notification_wait("#{browser_type}BrowserStartNotification.png", 60)
+Then /^the Unsafe Browser has started$/ do
+ @screen.wait("UnsafeBrowserHomepage.png", 360)
end
-Then /^the (Unsafe|I2P) Browser has started$/ do |browser_type|
- case browser_type
- when 'Unsafe'
- @screen.wait("UnsafeBrowserHomepage.png", 360)
- when 'I2P'
- step 'the I2P router console is displayed in I2P Browser'
- end
-end
-
-When /^I start the (Unsafe|I2P) Browser(?: through the GNOME menu)?$/ do |browser_type|
- step "I start \"#{browser_type}Browser\" via the GNOME \"Internet\" applications menu"
+When /^I start the Unsafe Browser(?: through the GNOME menu)?$/ do
+ step "I start \"Unsafe Browser\" via GNOME Activities Overview"
end
-When /^I successfully start the (Unsafe|I2P) Browser$/ do |browser_type|
- step "I start the #{browser_type} Browser"
- step "I see and accept the Unsafe Browser start verification" unless browser_type == 'I2P'
- step "I see the #{browser_type} Browser start notification and wait for it to close"
- step "the #{browser_type} Browser has started"
+When /^I successfully start the Unsafe Browser$/ do
+ step "I start the Unsafe Browser"
+ step "I see and accept the Unsafe Browser start verification"
+ step "I see the \"Starting the Unsafe Browser...\" notification after at most 60 seconds"
+ step "the Unsafe Browser has started"
end
-When /^I close the (?:Unsafe|I2P) Browser$/ do
+When /^I close the Unsafe Browser$/ do
@screen.type("q", Sikuli::KeyModifier.CTRL)
end
-Then /^I see the (Unsafe|I2P) Browser stop notification$/ do |browser_type|
- robust_notification_wait("#{browser_type}BrowserStopNotification.png", 60)
-end
-
def xul_application_info(application)
binary = $vm.execute_successfully(
'echo ${TBB_INSTALL}/firefox', :libs => 'tor-browser'
).stdout.chomp
address_bar_image = "BrowserAddressBar.png"
- unused_tbb_libs = ['libnssdbm3.so']
+ unused_tbb_libs = ['libnssdbm3.so', "libmozavcodec.so", "libmozavutil.so"]
case application
when "Tor Browser"
user = LIVE_USER
@@ -47,11 +34,6 @@ def xul_application_info(application)
cmd_regex = "#{binary} .* -profile /home/#{user}/\.unsafe-browser/profile\.default"
chroot = "/var/lib/unsafe-browser/chroot"
new_tab_button_image = "UnsafeBrowserNewTabButton.png"
- when "I2P Browser"
- user = "i2pbrowser"
- cmd_regex = "#{binary} .* -profile /home/#{user}/\.i2p-browser/profile\.default"
- chroot = "/var/lib/i2p-browser/chroot"
- new_tab_button_image = "I2PBrowserNewTabButton.png"
when "Tor Launcher"
user = "tor-launcher"
# We do not enable AppArmor confinement for the Tor Launcher.
@@ -100,19 +82,41 @@ When /^I open the address "([^"]*)" in the (.*)$/ do |address, browser|
@screen.type('v', Sikuli::KeyModifier.CTRL)
@screen.type(Sikuli::Key.ENTER)
end
- open_address.call
+ recovery_on_failure = Proc.new do
+ @screen.type(Sikuli::Key.ESC)
+ @screen.waitVanish('BrowserReloadButton.png', 3)
+ open_address.call
+ end
if browser == "Tor Browser"
- recovery_on_failure = Proc.new do
- @screen.type(Sikuli::Key.ESC)
- @screen.waitVanish('BrowserReloadButton.png', 3)
- open_address.call
- end
- retry_tor(recovery_on_failure) do
- @screen.wait('BrowserReloadButton.png', 120)
- end
+ retry_method = method(:retry_tor)
+ else
+ retry_method = Proc.new { |p, &b| retry_action(10, recovery_proc: p, &b) }
+ end
+ open_address.call
+ retry_method.call(recovery_on_failure) do
+ @screen.wait('BrowserReloadButton.png', 120)
end
end
+# This step is limited to the Tor Browser due to #7502 since dogtail
+# uses the same interface.
+Then /^"([^"]+)" has loaded in the Tor Browser$/ do |title|
+ if @language == 'German'
+ browser_name = 'Tor-Browser'
+ reload_action = 'Aktuelle Seite neu laden'
+ else
+ browser_name = 'Tor Browser'
+ reload_action = 'Reload current page'
+ end
+ expected_title = "#{title} - #{browser_name}"
+ try_for(60) { @torbrowser.child(expected_title, roleName: 'frame') }
+ # The 'Reload current page' button (graphically shown as a looping
+ # arrow) is only shown when a page has loaded, so once we see the
+ # expected title *and* this button has appeared, then we can be sure
+ # that the page has fully loaded.
+ try_for(60) { @torbrowser.child(reload_action, roleName: 'push button') }
+end
+
Then /^the (.*) has no plugins installed$/ do |browser|
step "I open the address \"about:plugins\" in the #{browser}"
step "I see \"TorBrowserNoPlugins.png\" after at most 30 seconds"
@@ -193,3 +197,23 @@ Then /^the file is saved to the default Tor Browser download directory$/ do
expected_path = "/home/#{LIVE_USER}/Tor Browser/#{@some_file}"
try_for(10) { $vm.file_exist?(expected_path) }
end
+
+When /^I open Tails homepage in the (.+)$/ do |browser|
+ step "I open the address \"https://tails.boum.org\" in the #{browser}"
+end
+
+Then /^Tails homepage loads in the Tor Browser$/ do
+ title = 'Tails - Privacy for anyone anywhere'
+ step "\"#{title}\" has loaded in the Tor Browser"
+end
+
+Then /^Tails homepage loads in the Unsafe Browser$/ do
+ @screen.wait('TailsHomepage.png', 60)
+end
+
+Then /^the Tor Browser shows the "([^"]+)" error$/ do |error|
+ page = @torbrowser.child("Problem loading page", roleName: "document frame")
+ headers = page.children(roleName: "heading")
+ found = headers.any? { |heading| heading.text == error }
+ raise "Could not find the '#{error}' error in the Tor Browser" unless found
+end
diff --git a/cucumber/features/step_definitions/build.rb b/cucumber/features/step_definitions/build.rb
index fd001ff4..e02edc62 100644
--- a/cucumber/features/step_definitions/build.rb
+++ b/cucumber/features/step_definitions/build.rb
@@ -1,4 +1,4 @@
-Given /^Tails ([[:alnum:].]+) has been released$/ do |version|
+Given /^Tails ([[:alnum:]~.]+) has been released$/ do |version|
create_git unless git_exists?
old_branch = current_branch
@@ -17,7 +17,7 @@ tails (#{version}) stable; urgency=low
END_OF_CHANGELOG
end
fatal_system "git commit --quiet debian/changelog -m 'Release #{version}'"
- fatal_system "git tag '#{version}'"
+ fatal_system "git tag '#{version.gsub('~', '-')}'"
if old_branch != 'stable'
fatal_system "git checkout --quiet '#{old_branch}'"
@@ -42,6 +42,31 @@ Given /^the last version mentioned in debian\/changelog is ([[:alnum:]~.]+)$/ do
end
end
+Given /^the last versions mentioned in debian\/changelog are ([[:alnum:]~.]+) and ([[:alnum:]~.]+)$/ do |version_a, version_b|
+ step "the last version mentioned in debian/changelog is #{version_a}"
+ step "the last version mentioned in debian/changelog is #{version_b}"
+end
+
+Given(/^no frozen APT snapshot is encoded in config\/APT_snapshots\.d$/) do
+ ['debian', 'debian-security', 'torproject'].map do |origin|
+ File.open("config/APT_snapshots.d/#{origin}/serial", 'w+') do |serial|
+ serial.write("latest\n")
+ end
+ end
+end
+
+Given(/^frozen APT snapshots are encoded in config\/APT_snapshots\.d$/) do
+ ['debian', 'torproject'].map do |origin|
+ File.open("config/APT_snapshots.d/#{origin}/serial", 'w+') do |serial|
+ serial.write("2016060602\n")
+ end
+ end
+ # We never freeze debian-security
+ File.open("config/APT_snapshots.d/debian-security/serial", 'w+') do |serial|
+ serial.write("latest\n")
+ end
+end
+
Given %r{I am working on the ([[:alnum:]./_-]+) base branch$} do |branch|
create_git unless git_exists?
@@ -54,6 +79,11 @@ Given %r{I am working on the ([[:alnum:]./_-]+) base branch$} do |branch|
end
end
+Given %r{^I checkout the ([[:alnum:]~.-]+) tag$} do |tag|
+ create_git unless git_exists?
+ fatal_system "git checkout --quiet #{tag}"
+end
+
Given %r{I am working on the ([[:alnum:]./_-]+) branch based on ([[:alnum:]./_-]+)$} do |branch, base|
create_git unless git_exists?
@@ -66,12 +96,12 @@ Given %r{I am working on the ([[:alnum:]./_-]+) branch based on ([[:alnum:]./_-]
end
end
-When /^I successfully run ([[:alnum:]-]+)$/ do |command|
+When /^I successfully run "?([[:alnum:] -]+)"?$/ do |command|
@output = `#{File.expand_path("../../../auto/scripts/#{command}", __FILE__)}`
raise StandardError.new("#{command} failed. Exit code: #{$?}") if $? != 0
end
-When /^I run ([[:alnum:]-]+)$/ do |command|
+When /^I run "?([[:alnum:] -]+)"?$/ do |command|
@output = `#{File.expand_path("../../../auto/scripts/#{command}", __FILE__)}`
@exit_code = $?.exitstatus
end
@@ -113,3 +143,11 @@ end
Given(/^the config\/base_branch file is empty$/) do
File.truncate('config/base_branch', 0)
end
+
+Then(/^I should see the ([[:alnum:].-]+) tagged snapshot$/) do |tag|
+ @output.should have_tagged_snapshot(tag)
+end
+
+Then(/^I should see a time\-based snapshot$/) do
+ @output.should have_time_based_snapshot()
+end
diff --git a/cucumber/features/step_definitions/checks.rb b/cucumber/features/step_definitions/checks.rb
index 423b8390..142141a8 100644
--- a/cucumber/features/step_definitions/checks.rb
+++ b/cucumber/features/step_definitions/checks.rb
@@ -35,10 +35,6 @@ Then /^the shipped (?:Debian repository key|OpenPGP key ([A-Z0-9]+)) will be val
end
end
-Then /^I double-click the Report an Error launcher on the desktop$/ do
- @screen.wait_and_double_click('DesktopReportAnError.png', 30)
-end
-
Then /^the live user has been setup by live\-boot$/ do
assert($vm.execute("test -e /var/lib/live/config/user-setup").success?,
"live-boot failed its user-setup")
@@ -69,20 +65,12 @@ Then /^the live user owns its home dir and it has normal permissions$/ do
end
Then /^no unexpected services are listening for network connections$/ do
- netstat_cmd = $vm.execute("netstat -ltupn")
- assert netstat_cmd.success?
- for line in netstat_cmd.stdout.chomp.split("\n") do
+ for line in $vm.execute_successfully("ss -ltupn").stdout.chomp.split("\n") do
splitted = line.split(/[[:blank:]]+/)
proto = splitted[0]
- if proto == "tcp"
- proc_index = 6
- elsif proto == "udp"
- proc_index = 5
- else
- next
- end
- laddr, lport = splitted[3].split(":")
- proc = splitted[proc_index].split("/")[1]
+ next unless ['tcp', 'udp'].include?(proto)
+ laddr, lport = splitted[4].split(":")
+ proc = /users:\(\("([^"]+)"/.match(splitted[6])[1]
# Services listening on loopback is not a threat
if /127(\.[[:digit:]]{1,3}){3}/.match(laddr).nil?
if SERVICES_EXPECTED_ON_ALL_IFACES.include? [proc, laddr, lport] or
@@ -101,61 +89,58 @@ When /^Tails has booted a 64-bit kernel$/ do
"Tails has not booted a 64-bit kernel.")
end
-Then /^there is no screenshot in the live user's Pictures directory$/ do
- pictures_directory = "/home/#{LIVE_USER}/Pictures"
- assert($vm.execute(
- "find '#{pictures_directory}' -name 'Screenshot*.png' -maxdepth 1"
- ).stdout.empty?,
- "Existing screenshots were found in the live user's Pictures directory.")
-end
-
-Then /^a screenshot is saved to the live user's Pictures directory$/ do
- pictures_directory = "/home/#{LIVE_USER}/Pictures"
- try_for(10, :msg=> "No screenshot was created in #{pictures_directory}") do
- !$vm.execute(
- "find '#{pictures_directory}' -name 'Screenshot*.png' -maxdepth 1"
- ).stdout.empty?
- end
-end
-
Then /^the VirtualBox guest modules are available$/ do
assert($vm.execute("modinfo vboxguest").success?,
"The vboxguest module is not available.")
end
-Given /^I setup a filesystem share containing a sample PDF$/ do
- shared_pdf_dir_on_host = "#{$config["TMPDIR"]}/shared_pdf_dir"
- @shared_pdf_dir_on_guest = "/tmp/shared_pdf_dir"
- FileUtils.mkdir_p(shared_pdf_dir_on_host)
- Dir.glob("#{MISC_FILES_DIR}/*.pdf") do |pdf_file|
- FileUtils.cp(pdf_file, shared_pdf_dir_on_host)
- end
- add_after_scenario_hook { FileUtils.rm_r(shared_pdf_dir_on_host) }
- $vm.add_share(shared_pdf_dir_on_host, @shared_pdf_dir_on_guest)
-end
-
Then /^the support documentation page opens in Tor Browser$/ do
- @screen.wait("SupportDocumentation#{@language}.png", 120)
-end
-
-Then /^MAT can clean some sample PDF file$/ do
- for pdf_on_host in Dir.glob("#{MISC_FILES_DIR}/*.pdf") do
- pdf_name = File.basename(pdf_on_host)
- pdf_on_guest = "/home/#{LIVE_USER}/#{pdf_name}"
- step "I copy \"#{@shared_pdf_dir_on_guest}/#{pdf_name}\" to \"#{pdf_on_guest}\" as user \"#{LIVE_USER}\""
- check_before = $vm.execute_successfully("mat --check '#{pdf_on_guest}'",
+ if @language == 'German'
+ expected_title = 'Tails - Hilfe & Support'
+ expected_heading = 'Die Dokumentation durchsuchen'
+ else
+ expected_title = 'Tails - Support'
+ expected_heading = 'Search the documentation'
+ end
+ step "\"#{expected_title}\" has loaded in the Tor Browser"
+ headings = @torbrowser
+ .child(expected_title, roleName: 'document frame')
+ .children(roleName: 'heading')
+ assert(
+ headings.any? { |heading| heading.text == expected_heading }
+ )
+end
+
+Given /^I plug and mount a USB drive containing a sample PNG$/ do
+ @png_dir = share_host_files(Dir.glob("#{MISC_FILES_DIR}/*.png"))
+end
+
+Then /^MAT can clean some sample PNG file$/ do
+ for png_on_host in Dir.glob("#{MISC_FILES_DIR}/*.png") do
+ png_name = File.basename(png_on_host)
+ png_on_guest = "/home/#{LIVE_USER}/#{png_name}"
+ step "I copy \"#{@png_dir}/#{png_name}\" to \"#{png_on_guest}\" as user \"#{LIVE_USER}\""
+ raw_check_cmd = "grep --quiet --fixed-strings --text " +
+ "'Created with GIMP' '#{png_on_guest}'"
+ assert($vm.execute(raw_check_cmd, user: LIVE_USER).success?,
+ 'The comment is not present in the PNG')
+ check_before = $vm.execute_successfully("mat --check '#{png_on_guest}'",
:user => LIVE_USER).stdout
- assert(check_before.include?("#{pdf_on_guest} is not clean"),
- "MAT failed to see that '#{pdf_on_host}' is dirty")
- $vm.execute_successfully("mat '#{pdf_on_guest}'", :user => LIVE_USER)
- check_after = $vm.execute_successfully("mat --check '#{pdf_on_guest}'",
+ assert(check_before.include?("#{png_on_guest} is not clean"),
+ "MAT failed to see that '#{png_on_host}' is dirty")
+ $vm.execute_successfully("mat '#{png_on_guest}'", :user => LIVE_USER)
+ check_after = $vm.execute_successfully("mat --check '#{png_on_guest}'",
:user => LIVE_USER).stdout
- assert(check_after.include?("#{pdf_on_guest} is clean"),
- "MAT failed to clean '#{pdf_on_host}'")
- $vm.execute_successfully("rm '#{pdf_on_guest}'")
+ assert(check_after.include?("#{png_on_guest} is clean"),
+ "MAT failed to clean '#{png_on_host}'")
+ assert($vm.execute(raw_check_cmd, user: LIVE_USER).failure?,
+ 'The comment is still present in the PNG')
+ $vm.execute_successfully("rm '#{png_on_guest}'")
end
end
+
+
Then /^AppArmor is enabled$/ do
assert($vm.execute("aa-status").success?, "AppArmor is not enabled")
end
@@ -184,13 +169,8 @@ def get_apparmor_status(pid)
end
Then /^the running process "(.+)" is confined with AppArmor in (complain|enforce) mode$/ do |process, mode|
- if process == 'i2p'
- $vm.execute_successfully('service i2p status')
- pid = $vm.file_content('/run/i2p/i2p.pid').chomp
- else
- assert($vm.has_process?(process), "Process #{process} not running.")
- pid = $vm.pidof(process)[0]
- end
+ assert($vm.has_process?(process), "Process #{process} not running.")
+ pid = $vm.pidof(process)[0]
assert_equal(mode, get_apparmor_status(pid))
end
@@ -238,12 +218,10 @@ Then /^tails-debugging-info is not susceptible to symlink attacks$/ do
end
When /^I disable all networking in the Tails Greeter$/ do
- begin
- @screen.click('TailsGreeterDisableAllNetworking.png')
- rescue FindFailed
- @screen.type(Sikuli::Key.PAGE_DOWN)
- @screen.click('TailsGreeterDisableAllNetworking.png')
- end
+ open_greeter_additional_settings()
+ @screen.wait_and_click('TailsGreeterNetworkConnection.png', 30)
+ @screen.wait_and_click('TailsGreeterDisableAllNetworking.png', 10)
+ @screen.wait_and_click("TailsGreeterAdditionalSettingsAdd.png", 10)
end
Then /^the Tor Status icon tells me that Tor is( not)? usable$/ do |not_usable|
diff --git a/cucumber/features/step_definitions/common_steps.rb b/cucumber/features/step_definitions/common_steps.rb
index feec90e2..ca7604f8 100644
--- a/cucumber/features/step_definitions/common_steps.rb
+++ b/cucumber/features/step_definitions/common_steps.rb
@@ -8,24 +8,6 @@ def post_vm_start_hook
@screen.click_point(@screen.w-1, @screen.h/2)
end
-def activate_filesystem_shares
- # XXX-9p: First of all, filesystem shares cannot be mounted while we
- # do a snapshot save+restore, so unmounting+remounting them seems
- # like a good idea. However, the 9p modules get into a broken state
- # during the save+restore, so we also would like to unload+reload
- # them, but loading of 9pnet_virtio fails after a restore with
- # "probe of virtio2 failed with error -2" (in dmesg) which makes the
- # shares unavailable. Hence we leave this code commented for now.
- #for mod in ["9pnet_virtio", "9p"] do
- # $vm.execute("modprobe #{mod}")
- #end
-
- $vm.list_shares.each do |share|
- $vm.execute("mkdir -p #{share}")
- $vm.execute("mount -t 9p -o trans=virtio #{share} #{share}")
- end
-end
-
def context_menu_helper(top, bottom, menu_item)
try_for(60) do
t = @screen.wait(top, 10)
@@ -41,64 +23,12 @@ def context_menu_helper(top, bottom, menu_item)
end
end
-def deactivate_filesystem_shares
- $vm.list_shares.each do |share|
- $vm.execute("umount #{share}")
- end
-
- # XXX-9p: See XXX-9p above
- #for mod in ["9p", "9pnet_virtio"] do
- # $vm.execute("modprobe -r #{mod}")
- #end
-end
-
-# This helper requires that the notification image is the one shown in
-# the notification applet's list, not the notification pop-up.
-def robust_notification_wait(notification_image, time_to_wait)
- error_msg = "Didn't not manage to open the notification applet"
- wait_start = Time.now
- try_for(time_to_wait, :delay => 0, :msg => error_msg) do
- @screen.hide_cursor
- @screen.click("GnomeNotificationApplet.png")
- @screen.wait("GnomeNotificationAppletOpened.png", 10)
- end
-
- error_msg = "Didn't not see notification '#{notification_image}'"
- time_to_wait -= (Time.now - wait_start).ceil
- try_for(time_to_wait, :delay => 0, :msg => error_msg) do
- found = false
- entries = @screen.findAll("GnomeNotificationEntry.png")
- while(entries.hasNext) do
- entry = entries.next
- @screen.hide_cursor
- @screen.click(entry)
- close_entry = @screen.wait("GnomeNotificationEntryClose.png", 10)
- if @screen.exists(notification_image)
- found = true
- @screen.click(close_entry)
- break
- else
- @screen.click(entry)
- end
- end
- found
- end
-
- # Click anywhere to close the notification applet
- @screen.hide_cursor
- @screen.click("GnomeApplicationsMenu.png")
- @screen.hide_cursor
-end
-
def post_snapshot_restore_hook
# FIXME -- we've got a brain-damaged version of this, unlike Tails, so it breaks after restores at present
# that being the case, let's not worry until we actually miss the feature
#$vm.wait_until_remote_shell_is_up
post_vm_start_hook
- # XXX-9p: See XXX-9p above
- #activate_filesystem_shares
-
# debian-TODO: move to tor feature
# The guest's Tor's circuits' states are likely to get out of sync
# with the other relays, so we ensure that we have fresh circuits.
@@ -106,18 +36,10 @@ def post_snapshot_restore_hook
#if $vm.has_network?
# if $vm.execute("systemctl --quiet is-active tor@default.service").success?
# $vm.execute("systemctl stop tor@default.service")
- # $vm.execute("rm -f /var/log/tor/log")
# $vm.execute("systemctl --no-block restart tails-tor-has-bootstrapped.target")
# $vm.host_to_guest_time_sync
- # $vm.spawn("restart-tor")
+ # $vm.execute("systemctl start tor@default.service")
# wait_until_tor_is_working
- # if $vm.file_content('/proc/cmdline').include?(' i2p')
- # $vm.execute_successfully('/usr/local/sbin/tails-i2p stop')
- # # we "killall tails-i2p" to prevent multiple
- # # copies of the script from running
- # $vm.execute_successfully('killall tails-i2p')
- # $vm.spawn('/usr/local/sbin/tails-i2p start')
- # end
# end
#else
# $vm.host_to_guest_time_sync
@@ -137,10 +59,6 @@ Given /^a computer$/ do
$vm = VM.new($virt, VM_XML_PATH, $vmnet, $vmstorage, DISPLAY)
end
-Given /^the computer has (\d+) ([[:alpha:]]+) of RAM$/ do |size, unit|
- $vm.set_ram_size(size, unit)
-end
-
Then /^the VM shuts down within (\d+) minutes$/ do |mins|
timeout = 60*mins.to_i
try_for(timeout, :msg => "VM is still running after #{timeout} seconds") do
@@ -156,7 +74,7 @@ Given /^the computer is set to boot from (.+?) drive$/ do |type|
$vm.set_disk_boot(JOB_NAME, type.downcase)
end
-Given /^I (temporarily )?create a (\d+) ([[:alpha:]]+) disk named "([^"]+)"$/ do |temporary, size, unit, name|
+Given /^I (temporarily )?create an? (\d+) ([[:alpha:]]+) disk named "([^"]+)"$/ do |temporary, size, unit, name|
$vm.storage.create_new_disk(name, {:size => size, :unit => unit,
:type => "qcow2"})
add_after_scenario_hook { $vm.storage.delete_volume(name) } if temporary
@@ -184,6 +102,11 @@ Given /^the network is unplugged$/ do
$vm.unplug_network
end
+Given /^the network connection is ready(?: within (\d+) seconds)?$/ do |timeout|
+ timeout ||= 30
+ try_for(timeout.to_i) { $vm.has_network? }
+end
+
Given /^the hardware clock is set to "([^"]*)"$/ do |time|
$vm.set_hardware_clock(DateTime.parse(time).to_time)
end
@@ -220,51 +143,43 @@ end
Given /^I start Tails( from DVD)?( with network unplugged)?( and I login)?$/ do |dvd_boot, network_unplugged, do_login|
step "the computer is set to boot from the Tails DVD" if dvd_boot
- if network_unplugged.nil?
- step "the network is plugged"
- else
+ if network_unplugged
step "the network is unplugged"
+ else
+ step "the network is plugged"
end
step "I start the computer"
step "the computer boots Tails"
if do_login
step "I log in to a new session"
- step "Tails seems to have booted normally"
- if network_unplugged.nil?
- step "Tor is ready"
+ if network_unplugged
step "all notifications have disappeared"
- step "available upgrades have been checked"
else
+ step "Tor is ready"
step "all notifications have disappeared"
+ step "available upgrades have been checked"
end
end
end
-Given /^I start Tails from (.+?) drive "(.+?)"(| with network unplugged)( and I login(| with(| read-only) persistence enabled))?$/ do |drive_type, drive_name, network_unplugged, do_login, persistence_on, persistence_ro|
+Given /^I start Tails from (.+?) drive "(.+?)"( with network unplugged)?( and I login( with persistence enabled)?)?$/ do |drive_type, drive_name, network_unplugged, do_login, persistence_on|
step "the computer is set to boot from #{drive_type} drive \"#{drive_name}\""
- if network_unplugged.empty?
- step "the network is plugged"
- else
+ if network_unplugged
step "the network is unplugged"
+ else
+ step "the network is plugged"
end
step "I start the computer"
step "the computer boots Tails"
if do_login
- if ! persistence_on.empty?
- if persistence_ro.empty?
- step "I enable persistence"
- else
- step "I enable read-only persistence"
- end
- end
+ step "I enable persistence" if persistence_on
step "I log in to a new session"
- step "Tails seems to have booted normally"
- if network_unplugged.empty?
- step "Tor is ready"
+ if network_unplugged
step "all notifications have disappeared"
- step "available upgrades have been checked"
else
+ step "Tor is ready"
step "all notifications have disappeared"
+ step "available upgrades have been checked"
end
end
end
@@ -691,16 +606,16 @@ Given /^I should see a ([a-zA-Z]*) Login prompt$/ do |style|
@screen.waitAny(loginPrompt[style], 20 * 60)
end
-def bootsplash
+def boot_menu_cmdline_image
case @os_loader
when "UEFI"
- 'TailsBootSplashUEFI.png'
+ 'TailsBootMenuKernelCmdlineUEFI.png'
else
'd-i8_bootsplash.png'
end
end
-def bootsplash_tab_msg
+def boot_menu_tab_msg_image
case @os_loader
when "UEFI"
'TailsBootSplashTabMsgUEFI.png'
@@ -719,21 +634,12 @@ def bootsplash_tab_msg
end
Given /^the computer (re)?boots Tails$/ do |reboot|
-
- boot_timeout = 30
- # We need some extra time for memory wiping if rebooting
- boot_timeout += 90 if reboot
-
- @screen.wait(bootsplash, boot_timeout)
- @screen.wait(bootsplash_tab_msg, 10)
- @screen.type(Sikuli::Key.TAB)
- @screen.waitVanish(bootsplash_tab_msg, 1)
-
+ step "Tails is at the boot menu's cmdline" + (reboot ? ' after rebooting' : '')
@screen.type(" autotest_never_use_this_option blacklist=psmouse #{@boot_options}" +
Sikuli::Key.ENTER)
- @screen.wait("DebianLive#{version}Greeter.png", 5*60)
- @vm.wait_until_remote_shell_is_up
- activate_filesystem_shares
+ @screen.wait('TailsGreeter.png', 5*60)
+ $vm.wait_until_remote_shell_is_up
+ step 'I configure Tails to use a simulated Tor network'
end
Given /^I log in to a new session(?: in )?(|German)$/ do |lang|
@@ -741,37 +647,60 @@ Given /^I log in to a new session(?: in )?(|German)$/ do |lang|
when 'German'
@language = "German"
@screen.wait_and_click('TailsGreeterLanguage.png', 10)
- @screen.wait_and_click("TailsGreeterLanguage#{@language}.png", 10)
+ @screen.wait('TailsGreeterLanguagePopover.png', 10)
+ @screen.type(@language)
+ sleep(2) # Gtk needs some time to filter the results
+ @screen.type(Sikuli::Key.ENTER)
@screen.wait_and_click("TailsGreeterLoginButton#{@language}.png", 10)
when ''
@screen.wait_and_click('TailsGreeterLoginButton.png', 10)
else
raise "Unsupported language: #{lang}"
end
+ step 'Tails Greeter has applied all settings'
+ step 'the Tails desktop is ready'
end
-Given /^I set sudo password "([^"]*)"$/ do |password|
- @sudo_password = password
- next if @skip_steps_while_restoring_background
- #@screen.wait("TailsGreeterAdminPassword.png", 20)
+def open_greeter_additional_settings
+ @screen.click('TailsGreeterAddMoreOptions.png')
+ @screen.wait('TailsGreeterAdditionalSettingsDialog.png', 10)
+end
+
+Given /^I open Tails Greeter additional settings dialog$/ do
+ open_greeter_additional_settings()
+end
+
+Given /^I enable the specific Tor configuration option$/ do
+ open_greeter_additional_settings()
+ @screen.wait_and_click('TailsGreeterNetworkConnection.png', 30)
+ @screen.wait_and_click("TailsGreeterSpecificTorConfiguration.png", 10)
+ @screen.wait_and_click("TailsGreeterAdditionalSettingsAdd.png", 10)
+end
+
+Given /^I set an administration password$/ do
+ open_greeter_additional_settings()
+ @screen.wait_and_click("TailsGreeterAdminPassword.png", 20)
@screen.type(@sudo_password)
@screen.type(Sikuli::Key.TAB)
@screen.type(@sudo_password)
+ @screen.type(Sikuli::Key.ENTER)
end
-Given /^Tails Greeter has dealt with the sudo password$/ do
- f1 = "/etc/sudoers.d/tails-greeter"
- f2 = "#{f1}-no-password-lecture"
- try_for(20) {
- $vm.execute("test -e '#{f1}' -o -e '#{f2}'").success?
+Given /^Tails Greeter has applied all settings$/ do
+ # I.e. it is done with PostLogin, which is ensured to happen before
+ # a logind session is opened for LIVE_USER.
+ try_for(120) {
+ $vm.execute_successfully("loginctl").stdout
+ .match(/^\s*\S+\s+\d+\s+#{LIVE_USER}\s+seat\d+\s+\S+\s*$/) != nil
}
end
Given /^the Tails desktop is ready$/ do
desktop_started_picture = "GnomeApplicationsMenu#{@language}.png"
- # We wait for the Florence icon to be displayed to ensure reliable systray icon clicking.
- @screen.wait("GnomeSystrayFlorence.png", 180)
@screen.wait(desktop_started_picture, 180)
+ # We wait for the Florence icon to be displayed to ensure reliable systray icon clicking.
+ @screen.wait("GnomeSystrayFlorence.png", 30)
+ @screen.wait("DesktopTailsDocumentation.png", 30)
# Disable screen blanking since we sometimes need to wait long
# enough for it to activate, which can mess with Sikuli wait():ing
# for some image.
@@ -779,14 +708,22 @@ Given /^the Tails desktop is ready$/ do
'gsettings set org.gnome.desktop.session idle-delay 0',
:user => LIVE_USER
)
+ # We need to enable the accessibility toolkit for dogtail.
+ $vm.execute_successfully(
+ 'gsettings set org.gnome.desktop.interface toolkit-accessibility true',
+ :user => LIVE_USER,
+ )
end
-Then /^Tails seems to have booted normally$/ do
- step "the Tails desktop is ready"
-end
-
-When /^I see the 'Tor is ready' notification$/ do
- robust_notification_wait('TorIsReadyNotification.png', 300)
+When /^I see the "(.+)" notification(?: after at most (\d+) seconds)?$/ do |title, timeout|
+ timeout = timeout ? timeout.to_i : nil
+ gnome_shell = Dogtail::Application.new('gnome-shell')
+ notification_list = gnome_shell.child(
+ 'No Notifications', roleName: 'label', showingOnly: false
+ ).parent.parent
+ try_for(timeout) do
+ notification_list.child?(title, roleName: 'label', showingOnly: false)
+ end
end
Given /^Tor is ready$/ do
@@ -803,43 +740,63 @@ Given /^Tor has built a circuit$/ do
end
Given /^the time has synced$/ do
- ["/var/run/tordate/done", "/var/run/htpdate/success"].each do |file|
+ ["/run/tordate/done", "/run/htpdate/success"].each do |file|
try_for(300) { $vm.execute("test -e #{file}").success? }
end
end
Given /^available upgrades have been checked$/ do
try_for(300) {
- $vm.execute("test -e '/var/run/tails-upgrader/checked_upgrades'").success?
+ $vm.execute("test -e '/run/tails-upgrader/checked_upgrades'").success?
}
end
-Given /^the Tor Browser has started$/ do
- tor_browser_picture = "TorBrowserWindow.png"
- @screen.wait(tor_browser_picture, 60)
+When /^I start the Tor Browser( in offline mode)?$/ do |offline|
+ step 'I start "Tor Browser" via GNOME Activities Overview'
+ if offline
+ offline_prompt = Dogtail::Application.new('zenity')
+ .dialog('Tor is not ready')
+ offline_prompt.button('Start Tor Browser').click
+ end
+ step "the Tor Browser has started#{offline}"
+ if offline
+ step 'the Tor Browser shows the "The proxy server is refusing connections" error'
+ end
end
-Given /^the Tor Browser (?:has started and )?load(?:ed|s) the (startup page|Tails roadmap)$/ do |page|
+Given /^the Tor Browser has started( in offline mode)?$/ do |offline|
+ try_for(60) do
+ @torbrowser = Dogtail::Application.new('Firefox')
+ @torbrowser.child?(roleName: 'frame', recursive: false)
+ end
+end
+
+Given /^the Tor Browser loads the (startup page|Tails roadmap)$/ do |page|
case page
when "startup page"
- picture = "TorBrowserStartupPage.png"
+ title = 'Tails - News'
when "Tails roadmap"
- picture = "TorBrowserTailsRoadmap.png"
+ title = 'Roadmap - Tails - RiseupLabs Code Repository'
else
raise "Unsupported page: #{page}"
end
- step "the Tor Browser has started"
- @screen.wait(picture, 120)
+ step "\"#{title}\" has loaded in the Tor Browser"
end
-Given /^the Tor Browser has started in offline mode$/ do
- @screen.wait("TorBrowserOffline.png", 60)
+When /^I request a new identity using Torbutton$/ do
+ @screen.wait_and_click('TorButtonIcon.png', 30)
+ @screen.wait_and_click('TorButtonNewIdentity.png', 30)
+end
+
+When /^I acknowledge Torbutton's New Identity confirmation prompt$/ do
+ @screen.wait('GnomeQuestionDialogIcon.png', 30)
+ step 'I type "y"'
end
Given /^I add a bookmark to eff.org in the Tor Browser$/ do
url = "https://www.eff.org"
step "I open the address \"#{url}\" in the Tor Browser"
- @screen.wait("TorBrowserOffline.png", 5)
+ step 'the Tor Browser shows the "The proxy server is refusing connections" error'
@screen.type("d", Sikuli::KeyModifier.CTRL)
@screen.wait("TorBrowserBookmarkPrompt.png", 10)
@screen.type(url + Sikuli::Key.ENTER)
@@ -851,24 +808,18 @@ Given /^the Tor Browser has a bookmark to eff.org$/ do
end
Given /^all notifications have disappeared$/ do
- next if not(@screen.exists("GnomeNotificationApplet.png"))
- @screen.click("GnomeNotificationApplet.png")
- @screen.wait("GnomeNotificationAppletOpened.png", 10)
- begin
- entries = @screen.findAll("GnomeNotificationEntry.png")
- while(entries.hasNext) do
- entry = entries.next
- @screen.hide_cursor
- @screen.click(entry)
- @screen.wait_and_click("GnomeNotificationEntryClose.png", 10)
+ # These magic coordinates always locates GNOME's clock in the top
+ # bar, which when clicked opens the calendar.
+ x, y = 512, 10
+ gnome_shell = Dogtail::Application.new('gnome-shell')
+ retry_action(10, recovery_proc: Proc.new { @screen.type(Sikuli::Key.ESC) }) do
+ @screen.click_point(x, y)
+ unless gnome_shell.child?('No Notifications', roleName: 'label')
+ @screen.click('GnomeCloseAllNotificationsButton.png')
end
- rescue FindFailed
- # No notifications, so we're good to go.
+ gnome_shell.child?('No Notifications', roleName: 'label')
end
- @screen.hide_cursor
- # Click anywhere to close the notification applet
- @screen.click("GnomeApplicationsMenu.png")
- @screen.hide_cursor
+ @screen.type(Sikuli::Key.ESC)
end
Then /^I (do not )?see "([^"]*)" after at most (\d+) seconds$/ do |negation, image, time|
@@ -890,24 +841,31 @@ Then /^I (do not )?see the "([^"]*)" screen, after at most (\d+) seconds$/ do |n
end
Then /^all Internet traffic has only flowed through Tor$/ do
- leaks = FirewallLeakCheck.new(@sniffer.pcap_file,
- :accepted_hosts => get_all_tor_nodes)
- leaks.assert_no_leaks
+ allowed_hosts = allowed_hosts_under_tor_enforcement
+ assert_all_connections(@sniffer.pcap_file) do |c|
+ allowed_hosts.include?({ address: c.daddr, port: c.dport })
+ end
end
Given /^I enter the sudo password in the pkexec prompt$/ do
step "I enter the \"#{@sudo_password}\" password in the pkexec prompt"
end
-def deal_with_polkit_prompt (image, password)
+def deal_with_polkit_prompt(password, opts = {})
+ opts[:expect_success] ||= true
+ image = 'PolicyKitAuthPrompt.png'
@screen.wait(image, 60)
@screen.type(password)
@screen.type(Sikuli::Key.ENTER)
- @screen.waitVanish(image, 10)
+ if opts[:expect_success]
+ @screen.waitVanish(image, 20)
+ else
+ @screen.wait('PolicyKitAuthFailure.png', 20)
+ end
end
Given /^I enter the "([^"]*)" password in the pkexec prompt$/ do |password|
- deal_with_polkit_prompt('PolicyKitAuthPrompt.png', password)
+ deal_with_polkit_prompt(password)
end
Given /^process "([^"]+)" is (not )?running$/ do |process, not_running|
@@ -939,19 +897,17 @@ Given /^I kill the process "([^"]+)"$/ do |process|
}
end
-Then /^Tails eventually shuts down$/ do
- nr_gibs_of_ram = convert_from_bytes($vm.get_ram_size_in_bytes, 'GiB').ceil
- timeout = nr_gibs_of_ram*5*60
- try_for(timeout, :msg => "VM is still running after #{timeout} seconds") do
- ! $vm.is_running?
+Then /^Tails eventually (shuts down|restarts)$/ do |mode|
+ try_for(3*60) do
+ if mode == 'restarts'
+ @screen.find('TailsGreeter.png')
+ true
+ else
+ ! $vm.is_running?
+ end
end
end
-Then /^Tails eventually restarts$/ do
- nr_gibs_of_ram = convert_from_bytes($vm.get_ram_size_in_bytes, 'GiB').ceil
- @screen.wait('TailsBootSplash.png', nr_gibs_of_ram*5*60)
-end
-
Given /^I shutdown Tails and wait for the computer to power off$/ do
$vm.spawn("poweroff")
step 'Tails eventually shuts down'
@@ -960,6 +916,11 @@ end
When /^I request a shutdown using the emergency shutdown applet$/ do
@screen.hide_cursor
@screen.wait_and_click('TailsEmergencyShutdownButton.png', 10)
+ # Sometimes the next button too fast, before the menu has settled
+ # down to its final size and the icon we want to click is in its
+ # final position. dogtail might allow us to fix that, but given how
+ # rare this problem is, it's not worth the effort.
+ step 'I wait 5 seconds'
@screen.wait_and_click('TailsEmergencyShutdownHalt.png', 10)
end
@@ -970,57 +931,38 @@ end
When /^I request a reboot using the emergency shutdown applet$/ do
@screen.hide_cursor
@screen.wait_and_click('TailsEmergencyShutdownButton.png', 10)
+ # See comment on /^I request a shutdown using the emergency shutdown applet$/
+ # that explains why we need to wait.
+ step 'I wait 5 seconds'
@screen.wait_and_click('TailsEmergencyShutdownReboot.png', 10)
end
-Given /^package "([^"]+)" is installed$/ do |package|
+Given /^the package "([^"]+)" is installed$/ do |package|
assert($vm.execute("dpkg -s '#{package}' 2>/dev/null | grep -qs '^Status:.*installed$'").success?,
"Package '#{package}' is not installed")
end
-When /^I start the Tor Browser$/ do
- step 'I start "TorBrowser" via the GNOME "Internet" applications menu'
-end
-
-When /^I request a new identity using Torbutton$/ do
- @screen.wait_and_click('TorButtonIcon.png', 30)
- @screen.wait_and_click('TorButtonNewIdentity.png', 30)
-end
-
-When /^I acknowledge Torbutton's New Identity confirmation prompt$/ do
- @screen.wait('GnomeQuestionDialogIcon.png', 30)
- step 'I type "y"'
-end
-
-When /^I start the Tor Browser in offline mode$/ do
- step "I start the Tor Browser"
- @screen.wait_and_click("TorBrowserOfflinePrompt.png", 10)
- @screen.click("TorBrowserOfflinePromptStart.png")
-end
-
-Given /^I add a wired DHCP NetworkManager connection called "([^"]+)"$/ do |con_name|
- con_content = <<EOF
-[802-3-ethernet]
-duplex=full
-
+Given /^I add a ([a-z0-9.]+ |)wired DHCP NetworkManager connection called "([^"]+)"$/ do |version, con_name|
+ if version and version == '2.x'
+ con_content = <<EOF
[connection]
id=#{con_name}
-uuid=bbc60668-1be0-11e4-a9c6-2f1ce0e75bf1
-type=802-3-ethernet
-timestamp=1395406011
-
-[ipv6]
-method=auto
-
-[ipv4]
-method=auto
+uuid=b04afa94-c3a1-41bf-aa12-1a743d964162
+interface-name=eth0
+type=ethernet
EOF
- con_content.split("\n").each do |line|
- $vm.execute("echo '#{line}' >> /tmp/NM.#{con_name}")
+ con_file = "/etc/NetworkManager/system-connections/#{con_name}"
+ $vm.file_overwrite(con_file, con_content)
+ $vm.execute_successfully("chmod 600 '#{con_file}'")
+ $vm.execute_successfully("nmcli connection load '#{con_file}'")
+ elsif version and version == '3.x'
+ raise "Unsupported version '#{version}'"
+ else
+ $vm.execute_successfully(
+ "nmcli connection add con-name #{con_name} " + \
+ "type ethernet autoconnect yes ifname eth0"
+ )
end
- con_file = "/etc/NetworkManager/system-connections/#{con_name}"
- $vm.execute("install -m 0600 '/tmp/NM.#{con_name}' '#{con_file}'")
- $vm.execute_successfully("nmcli connection load '#{con_file}'")
try_for(10) {
nm_con_list = $vm.execute("nmcli --terse --fields NAME connection show").stdout
nm_con_list.split("\n").include? "#{con_name}"
@@ -1035,8 +977,8 @@ Given /^I switch to the "([^"]+)" NetworkManager connection$/ do |con_name|
end
When /^I start and focus GNOME Terminal$/ do
- step 'I start "Terminal" via the GNOME "Utilities" applications menu'
- @screen.wait('GnomeTerminalWindow.png', 20)
+ step 'I start "GNOME Terminal" via GNOME Activities Overview'
+ @screen.wait('GnomeTerminalWindow.png', 40)
end
When /^I run "([^"]+)" in GNOME Terminal$/ do |command|
@@ -1091,57 +1033,12 @@ Then /^persistence for "([^"]+)" is (|not )enabled$/ do |app, enabled|
end
end
-def gnome_app_menu_click_helper(click_me, verify_me = nil)
- try_for(30) do
- @screen.hide_cursor
- # The sensitivity for submenus to open by just hovering past them
- # is extremely high, and may result in the wrong one
- # opening. Hence we better avoid hovering over undesired submenus
- # entirely by "approaching" the menu strictly horizontally.
- r = @screen.wait(click_me, 10)
- @screen.hover_point(@screen.w, r.getY)
- @screen.click(r)
- @screen.wait(verify_me, 10) if verify_me
- return
- end
-end
-
-Given /^I start "([^"]+)" via the GNOME "([^"]+)" applications menu$/ do |app, submenu|
- menu_button = "GnomeApplicationsMenu.png"
- sub_menu_entry = "GnomeApplications" + submenu + ".png"
- application_entry = "GnomeApplications" + app + ".png"
- try_for(120) do
- begin
- gnome_app_menu_click_helper(menu_button, sub_menu_entry)
- gnome_app_menu_click_helper(sub_menu_entry, application_entry)
- gnome_app_menu_click_helper(application_entry)
- rescue Exception => e
- # Close menu, if still open
- @screen.type(Sikuli::Key.ESC)
- raise e
- end
- true
- end
-end
-
-Given /^I start "([^"]+)" via the GNOME "([^"]+)"\/"([^"]+)" applications menu$/ do |app, submenu, subsubmenu|
- menu_button = "GnomeApplicationsMenu.png"
- sub_menu_entry = "GnomeApplications" + submenu + ".png"
- sub_sub_menu_entry = "GnomeApplications" + subsubmenu + ".png"
- application_entry = "GnomeApplications" + app + ".png"
- try_for(120) do
- begin
- gnome_app_menu_click_helper(menu_button, sub_menu_entry)
- gnome_app_menu_click_helper(sub_menu_entry, sub_sub_menu_entry)
- gnome_app_menu_click_helper(sub_sub_menu_entry, application_entry)
- gnome_app_menu_click_helper(application_entry)
- rescue Exception => e
- # Close menu, if still open
- @screen.type(Sikuli::Key.ESC)
- raise e
- end
- true
- end
+Given /^I start "([^"]+)" via GNOME Activities Overview$/ do |app_name|
+ @screen.wait('GnomeApplicationsMenu.png', 10)
+ $vm.execute_successfully('xdotool key Super', user: LIVE_USER)
+ @screen.wait('GnomeActivitiesOverview.png', 10)
+ @screen.type(app_name)
+ @screen.type(Sikuli::Key.ENTER, Sikuli::KeyModifier.CTRL)
end
When /^I type "([^"]+)"$/ do |string|
@@ -1198,8 +1095,14 @@ When /^(no|\d+) application(?:s?) (?:is|are) playing audio(?:| after (\d+) secon
assert_equal(nb.to_i, pulseaudio_sink_inputs)
end
-When /^I double-click on the "Tails documentation" link on the Desktop$/ do
- @screen.wait_and_double_click("DesktopTailsDocumentationIcon.png", 10)
+When /^I double-click on the (Tails documentation|Report an Error) launcher on the desktop$/ do |launcher|
+ image = 'Desktop' + launcher.split.map { |s| s.capitalize } .join + '.png'
+ info = xul_application_info('Tor Browser')
+ # Sometimes the double-click is lost (#12131).
+ retry_action(10) do
+ @screen.wait_and_double_click(image, 10) if $vm.execute("pgrep --uid #{info[:user]} --full --exact '#{info[:cmd_regex]}'").failure?
+ step 'the Tor Browser has started'
+ end
end
When /^I click the blocked video icon$/ do
@@ -1265,9 +1168,9 @@ When /^I can print the current page as "([^"]+[.]pdf)" to the (default downloads
end
Given /^a web server is running on the LAN$/ do
- web_server_ip_addr = $vmnet.bridge_ip_addr
- web_server_port = 8000
- @web_server_url = "http://#{web_server_ip_addr}:#{web_server_port}"
+ @web_server_ip_addr = $vmnet.bridge_ip_addr
+ @web_server_port = 8000
+ @web_server_url = "http://#{@web_server_ip_addr}:#{@web_server_port}"
web_server_hello_msg = "Welcome to the LAN web server!"
# I've tested ruby Thread:s, fork(), etc. but nothing works due to
@@ -1282,14 +1185,15 @@ Given /^a web server is running on the LAN$/ do
require "webrick"
STDOUT.reopen("/dev/null", "w")
STDERR.reopen("/dev/null", "w")
- server = WEBrick::HTTPServer.new(:BindAddress => "#{web_server_ip_addr}",
- :Port => #{web_server_port},
+ server = WEBrick::HTTPServer.new(:BindAddress => "#{@web_server_ip_addr}",
+ :Port => #{@web_server_port},
:DocumentRoot => "/dev/null")
server.mount_proc("/") do |req, res|
res.body = "#{web_server_hello_msg}"
end
server.start
EOF
+ add_lan_host(@web_server_ip_addr, @web_server_port)
proc = IO.popen(['ruby', '-e', code])
try_for(10, :msg => "It seems the LAN web server failed to start") do
Process.kill(0, proc.pid) == 1
@@ -1365,8 +1269,7 @@ When /^AppArmor has (not )?denied "([^"]+)" from opening "([^"]+)"(?: after at m
end
Then /^I force Tor to use a new circuit$/ do
- debug_log("Forcing new Tor circuit...")
- $vm.execute_successfully('tor_control_send "signal NEWNYM"', :libs => 'tor')
+ force_new_tor_circuit
end
When /^I eject the boot medium$/ do
@@ -1374,7 +1277,7 @@ When /^I eject the boot medium$/ do
dev_type = device_info(dev)['ID_TYPE']
case dev_type
when 'cd'
- $vm.remove_cdrom
+ $vm.eject_cdrom
when 'disk'
boot_disk_name = $vm.disk_name(dev)
$vm.unplug_drive(boot_disk_name)
@@ -1382,3 +1285,103 @@ When /^I eject the boot medium$/ do
raise "Unsupported medium type '#{dev_type}' for boot device '#{dev}'"
end
end
+
+Given /^Tails is fooled to think it is running version (.+)$/ do |version|
+ $vm.execute_successfully(
+ "sed -i " +
+ "'s/^TAILS_VERSION_ID=.*$/TAILS_VERSION_ID=\"#{version}\"/' " +
+ "/etc/os-release"
+ )
+end
+
+Then /^Tails is running version (.+)$/ do |version|
+ v1 = $vm.execute_successfully('tails-version').stdout.split.first
+ assert_equal(version, v1, "The version doesn't match tails-version's output")
+ v2 = $vm.file_content('/etc/os-release')
+ .scan(/TAILS_VERSION_ID="(#{version})"/).flatten.first
+ assert_equal(version, v2, "The version doesn't match /etc/os-release")
+end
+
+def share_host_files(files)
+ files = [files] if files.class == String
+ assert_equal(Array, files.class)
+ disk_size = files.map { |f| File.new(f).size } .inject(0, :+)
+ # Let's add some extra space for filesysten overhead etc.
+ disk_size += [convert_to_bytes(1, 'MiB'), (disk_size * 0.10).ceil].max
+ disk = random_alpha_string(10)
+ step "I temporarily create an #{disk_size} bytes disk named \"#{disk}\""
+ step "I create a gpt partition labeled \"#{disk}\" with an ext4 " +
+ "filesystem on disk \"#{disk}\""
+ $vm.storage.guestfs_disk_helper(disk) do |g, _|
+ partition = g.list_partitions().first
+ g.mount(partition, "/")
+ files.each { |f| g.upload(f, "/" + File.basename(f)) }
+ end
+ step "I plug USB drive \"#{disk}\""
+ mount_dir = $vm.execute_successfully('mktemp -d').stdout.chomp
+ dev = $vm.disk_dev(disk)
+ partition = dev + '1'
+ $vm.execute_successfully("mount #{partition} #{mount_dir}")
+ $vm.execute_successfully("chmod -R a+rX '#{mount_dir}'")
+ return mount_dir
+end
+
+def mount_USB_drive(disk, fs_options = {})
+ fs_options[:encrypted] ||= false
+ @tmp_usb_drive_mount_dir = $vm.execute_successfully('mktemp -d').stdout.chomp
+ dev = $vm.disk_dev(disk)
+ partition = dev + '1'
+ if fs_options[:encrypted]
+ password = fs_options[:password]
+ assert_not_nil(password)
+ luks_mapping = "#{disk}_unlocked"
+ $vm.execute_successfully(
+ "echo #{password} | " +
+ "cryptsetup luksOpen #{partition} #{luks_mapping}"
+ )
+ $vm.execute_successfully(
+ "mount /dev/mapper/#{luks_mapping} #{@tmp_usb_drive_mount_dir}"
+ )
+ else
+ $vm.execute_successfully("mount #{partition} #{@tmp_usb_drive_mount_dir}")
+ end
+ @tmp_filesystem_disk = disk
+ @tmp_filesystem_options = fs_options
+ @tmp_filesystem_partition = partition
+ return @tmp_usb_drive_mount_dir
+end
+
+When(/^I plug and mount a (\d+) MiB USB drive with an? (.*)$/) do |size_MiB, fs|
+ disk_size = convert_to_bytes(size_MiB.to_i, 'MiB')
+ disk = random_alpha_string(10)
+ step "I temporarily create an #{disk_size} bytes disk named \"#{disk}\""
+ step "I create a gpt partition labeled \"#{disk}\" with " +
+ "an #{fs} on disk \"#{disk}\""
+ step "I plug USB drive \"#{disk}\""
+ fs_options = {}
+ fs_options[:filesystem] = /(.*) filesystem/.match(fs)[1]
+ if /\bencrypted with password\b/.match(fs)
+ fs_options[:encrypted] = true
+ fs_options[:password] = /encrypted with password "([^"]+)"/.match(fs)[1]
+ end
+ mount_dir = mount_USB_drive(disk, fs_options)
+ @tmp_filesystem_size_b = convert_to_bytes(
+ avail_space_in_mountpoint_kB(mount_dir),
+ 'KB'
+ )
+end
+
+When(/^I mount the USB drive again$/) do
+ mount_USB_drive(@tmp_filesystem_disk, @tmp_filesystem_options)
+end
+
+When(/^I umount the USB drive$/) do
+ $vm.execute_successfully("umount #{@tmp_usb_drive_mount_dir}")
+ if @tmp_filesystem_options[:encrypted]
+ $vm.execute_successfully("cryptsetup luksClose #{@tmp_filesystem_disk}_unlocked")
+ end
+end
+
+When /^Tails system time is magically synchronized$/ do
+ $vm.host_to_guest_time_sync
+end
diff --git a/cucumber/features/step_definitions/dhcp.rb b/cucumber/features/step_definitions/dhcp.rb
index ef4d9e15..3c834224 100644
--- a/cucumber/features/step_definitions/dhcp.rb
+++ b/cucumber/features/step_definitions/dhcp.rb
@@ -1,19 +1,23 @@
Then /^the hostname should not have been leaked on the network$/ do
- hostname = $vm.execute("hostname").stdout.chomp
- packets = PacketFu::PcapFile.new.file_to_array(:filename => @sniffer.pcap_file)
- packets.each do |p|
- # if PacketFu::TCPPacket.can_parse?(p)
- # ipv4_tcp_packets << PacketFu::TCPPacket.parse(p)
- if PacketFu::IPPacket.can_parse?(p)
- payload = PacketFu::IPPacket.parse(p).payload
- elsif PacketFu::IPv6Packet.can_parse?(p)
- payload = PacketFu::IPv6Packet.parse(p).payload
- else
- @sniffer.save_pcap_file
- raise "Found something in the pcap file that either is non-IP, or cannot be parsed"
- end
- if payload.match(hostname)
- raise "Hostname leak detected"
+ begin
+ hostname = $vm.execute("hostname").stdout.chomp
+ packets = PacketFu::PcapFile.new.file_to_array(filename: @sniffer.pcap_file)
+ packets.each do |p|
+ # if PacketFu::TCPPacket.can_parse?(p)
+ # ipv4_tcp_packets << PacketFu::TCPPacket.parse(p)
+ if PacketFu::IPPacket.can_parse?(p)
+ payload = PacketFu::IPPacket.parse(p).payload
+ elsif PacketFu::IPv6Packet.can_parse?(p)
+ payload = PacketFu::IPv6Packet.parse(p).payload
+ else
+ raise "Found something in the pcap file that either is non-IP, or cannot be parsed"
+ end
+ if payload.match(hostname)
+ raise "Hostname leak detected"
+ end
end
+ rescue Exception => e
+ save_failure_artifact("Network capture", @sniffer.pcap_file)
+ raise e
end
end
diff --git a/cucumber/features/step_definitions/electrum.rb b/cucumber/features/step_definitions/electrum.rb
index 447983d4..eaeb22aa 100644
--- a/cucumber/features/step_definitions/electrum.rb
+++ b/cucumber/features/step_definitions/electrum.rb
@@ -1,12 +1,12 @@
Then /^I start Electrum through the GNOME menu$/ do
- step "I start \"Electrum\" via the GNOME \"Internet\" applications menu"
+ step "I start \"Electrum Bitcoin Wallet\" via GNOME Activities Overview"
end
When /^a bitcoin wallet is (|not )present$/ do |existing|
wallet = "/home/#{LIVE_USER}/.electrum/wallets/default_wallet"
case existing
when ""
- step "the file \"#{wallet}\" exists after at most 10 seconds"
+ step "the file \"#{wallet}\" exists after at most 30 seconds"
when "not "
step "the file \"#{wallet}\" does not exist"
else
@@ -17,20 +17,22 @@ end
When /^I create a new bitcoin wallet$/ do
@screen.wait("ElectrumNoWallet.png", 10)
@screen.wait_and_click("ElectrumNextButton.png", 10)
+ @screen.wait("ElectrumCreateNewSeed.png", 10)
+ @screen.wait_and_click("ElectrumNextButton.png", 10)
@screen.wait("ElectrumWalletGenerationSeed.png", 15)
@screen.wait_and_click("ElectrumWalletSeedTextbox.png", 15)
@screen.type('a', Sikuli::KeyModifier.CTRL) # select wallet seed
@screen.type('c', Sikuli::KeyModifier.CTRL) # copy seed to clipboard
seed = $vm.get_clipboard
@screen.wait_and_click("ElectrumNextButton.png", 15)
- @screen.wait("ElectrumWalletSeedTextbox.png", 15)
+ @screen.wait("ElectrumSeedVerificationPrompt.png", 15)
+ @screen.wait_and_click("ElectrumWalletSeedTextbox.png", 15)
@screen.type(seed) # Confirm seed
@screen.wait_and_click("ElectrumNextButton.png", 10)
- @screen.wait_and_click("ElectrumEncryptWallet.png", 10)
+ @screen.wait("ElectrumEncryptWallet.png", 10)
+ @screen.type(Sikuli::Key.TAB) # focus first password field
@screen.type("asdf" + Sikuli::Key.TAB) # set password
@screen.type("asdf" + Sikuli::Key.TAB) # confirm password
- @screen.type(Sikuli::Key.ENTER)
- @screen.wait("ElectrumConnectServer.png", 20)
@screen.wait_and_click("ElectrumNextButton.png", 10)
@screen.wait("ElectrumPreferencesButton.png", 30)
end
@@ -39,8 +41,8 @@ Then /^I see a warning that Electrum is not persistent$/ do
@screen.wait('GnomeQuestionDialogIcon.png', 30)
end
-Then /^I am prompted to create a new wallet$/ do
- @screen.wait('ElectrumNoWallet.png', 60)
+Then /^I am prompted to configure Electrum$/ do
+ @screen.wait("ElectrumNoWallet.png", 60)
end
Then /^I see the main Electrum client window$/ do
diff --git a/cucumber/features/step_definitions/encryption.rb b/cucumber/features/step_definitions/encryption.rb
index 9f7f1b96..3b20a5b4 100644
--- a/cucumber/features/step_definitions/encryption.rb
+++ b/cucumber/features/step_definitions/encryption.rb
@@ -23,16 +23,16 @@ Given /^I generate an OpenPGP key named "([^"]+)" with password "([^"]+)"$/ do |
Passphrase: #{pwd}
%commit
EOF
- gpg_key_recipie.split("\n").each do |line|
- $vm.execute("echo '#{line}' >> /tmp/gpg_key_recipie", :user => LIVE_USER)
- end
- c = $vm.execute("gpg --batch --gen-key < /tmp/gpg_key_recipie",
+ recipe_path = '/tmp/gpg_key_recipe'
+ $vm.file_overwrite(recipe_path, gpg_key_recipie)
+ $vm.execute("chown #{LIVE_USER}:#{LIVE_USER} #{recipe_path}")
+ c = $vm.execute("gpg --batch --gen-key < #{recipe_path}",
:user => LIVE_USER)
assert(c.success?, "Failed to generate OpenPGP key:\n#{c.stderr}")
end
When /^I type a message into gedit$/ do
- step 'I start "Gedit" via the GNOME "Accessories" applications menu'
+ step 'I start "gedit" via GNOME Activities Overview'
@screen.wait_and_click("GeditWindow.png", 20)
# We don't have a good visual indicator for when we can continue. Without the
# sleep we may start typing in the gedit window far too soon, causing
@@ -60,7 +60,7 @@ def gedit_copy_all_text
context_menu_helper('GeditWindow.png', 'GeditStatusBar.png', 'GeditCopy.png')
end
-def paste_into_a_new_tab
+def gedit_paste_into_a_new_tab
@screen.wait_and_click("GeditNewTab.png", 20)
context_menu_helper('GeditWindow.png', 'GeditStatusBar.png', 'GeditPaste.png')
end
@@ -74,7 +74,7 @@ def encrypt_sign_helper
sleep 5
yield
maybe_deal_with_pinentry
- paste_into_a_new_tab
+ gedit_paste_into_a_new_tab
end
def decrypt_verify_helper(icon)
@@ -129,5 +129,5 @@ When /^I symmetrically encrypt the message with password "([^"]+)"$/ do |pwd|
seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletEncryptPassphrase.png')
maybe_deal_with_pinentry # enter password
maybe_deal_with_pinentry # confirm password
- paste_into_a_new_tab
+ gedit_paste_into_a_new_tab
end
diff --git a/cucumber/features/step_definitions/firewall_leaks.rb b/cucumber/features/step_definitions/firewall_leaks.rb
index 942d00b8..0cd94cca 100644
--- a/cucumber/features/step_definitions/firewall_leaks.rb
+++ b/cucumber/features/step_definitions/firewall_leaks.rb
@@ -1,29 +1,6 @@
-Then(/^the firewall leak detector has detected (.*?) leaks$/) do |type|
- leaks = FirewallLeakCheck.new(@sniffer.pcap_file,
- :accepted_hosts => get_all_tor_nodes)
- case type.downcase
- when 'ipv4 tcp'
- if leaks.ipv4_tcp_leaks.empty?
- leaks.save_pcap_file
- raise "Couldn't detect any IPv4 TCP leaks"
- end
- when 'ipv4 non-tcp'
- if leaks.ipv4_nontcp_leaks.empty?
- leaks.save_pcap_file
- raise "Couldn't detect any IPv4 non-TCP leaks"
- end
- when 'ipv6'
- if leaks.ipv6_leaks.empty?
- leaks.save_pcap_file
- raise "Couldn't detect any IPv6 leaks"
- end
- when 'non-ip'
- if leaks.nonip_leaks.empty?
- leaks.save_pcap_file
- raise "Couldn't detect any non-IP leaks"
- end
- else
- raise "Incorrect packet type '#{type}'"
+Then(/^the firewall leak detector has detected leaks$/) do
+ assert_raise(FirewallAssertionFailedError) do
+ step 'all Internet traffic has only flowed through Tor'
end
end
@@ -40,12 +17,12 @@ Given(/^I disable Tails' firewall$/) do
end
When(/^I do a TCP DNS lookup of "(.*?)"$/) do |host|
- lookup = $vm.execute("host -T #{host} #{SOME_DNS_SERVER}", :user => LIVE_USER)
+ lookup = $vm.execute("host -T -t A #{host} #{SOME_DNS_SERVER}", :user => LIVE_USER)
assert(lookup.success?, "Failed to resolve #{host}:\n#{lookup.stdout}")
end
When(/^I do a UDP DNS lookup of "(.*?)"$/) do |host|
- lookup = $vm.execute("host #{host} #{SOME_DNS_SERVER}", :user => LIVE_USER)
+ lookup = $vm.execute("host -t A #{host} #{SOME_DNS_SERVER}", :user => LIVE_USER)
assert(lookup.success?, "Failed to resolve #{host}:\n#{lookup.stdout}")
end
diff --git a/cucumber/features/step_definitions/git.rb b/cucumber/features/step_definitions/git.rb
index bf6f869d..bd8fcf7d 100644
--- a/cucumber/features/step_definitions/git.rb
+++ b/cucumber/features/step_definitions/git.rb
@@ -1,3 +1,29 @@
+When /^I clone the Git repository "([\S]+)" in GNOME Terminal$/ do |repo|
+ repo_directory = /[\S]+\/([\S]+)(\.git)?$/.match(repo)[1]
+ assert(!$vm.directory_exist?("/home/#{LIVE_USER}/#{repo_directory}"))
+
+ recovery_proc = Proc.new do
+ $vm.execute("rm -rf /home/#{LIVE_USER}/#{repo_directory}",
+ :user => LIVE_USER)
+ step 'I kill the process "git"'
+ @screen.type('clear' + Sikuli::Key.ENTER)
+ end
+
+ retry_tor(recovery_proc) do
+ step "I run \"git clone #{repo}\" in GNOME Terminal"
+ m = /^(https?|git):\/\//.match(repo)
+ unless m
+ step 'I verify the SSH fingerprint for the Git repository'
+ end
+ try_for(180, :msg => 'Git process took too long') {
+ !$vm.has_process?('/usr/bin/git')
+ }
+ Dogtail::Application.new('gnome-terminal-server')
+ .child('Terminal', roleName: 'terminal')
+ .text['Unpacking objects: 100%']
+ end
+end
+
Then /^the Git repository "([\S]+)" has been cloned successfully$/ do |repo|
assert($vm.directory_exist?("/home/#{LIVE_USER}/#{repo}/.git"))
assert($vm.file_exist?("/home/#{LIVE_USER}/#{repo}/.git/config"))
diff --git a/cucumber/features/step_definitions/icedove.rb b/cucumber/features/step_definitions/icedove.rb
deleted file mode 100644
index d3672895..00000000
--- a/cucumber/features/step_definitions/icedove.rb
+++ /dev/null
@@ -1,94 +0,0 @@
-Then /^Icedove has started$/ do
- step 'process "icedove" is running within 30 seconds'
- @screen.wait('IcedoveMainWindow.png', 60)
-end
-
-When /^I have not configured an email account$/ do
- icedove_prefs = $vm.file_content("/home/#{LIVE_USER}/.icedove/profile.default/prefs.js").chomp
- assert(!icedove_prefs.include?('mail.accountmanager.accounts'))
-end
-
-Then /^I am prompted to setup an email account$/ do
- $vm.focus_window('Mail Account Setup')
- @screen.wait('IcedoveMailAccountSetup.png', 30)
-end
-
-Then /^IMAP is the default protocol$/ do
- $vm.focus_window('Mail Account Setup')
- @screen.wait('IcedoveProtocolIMAP.png', 10)
-end
-
-Then /^I cancel setting up an email account$/ do
- $vm.focus_window('Mail Account Setup')
- @screen.type(Sikuli::Key.ESC)
- @screen.waitVanish('IcedoveMailAccountSetup.png', 10)
-end
-
-Then /^I open Icedove's Add-ons Manager$/ do
- $vm.focus_window('Icedove')
- @screen.wait_and_click('MozillaMenuButton.png', 10)
- @screen.wait_and_click('IcedoveToolsMenuAddOns.png', 10)
- @screen.wait('MozillaAddonsManagerExtensions.png', 30)
-end
-
-Then /^I click the extensions tab$/ do
- @screen.wait_and_click('MozillaAddonsManagerExtensions.png', 10)
-end
-
-Then /^I see that Adblock is not installed in Icedove$/ do
- if @screen.exists('MozillaExtensionsAdblockPlus.png')
- raise 'Adblock should not be enabled within Icedove'
- end
-end
-
-When /^I go into Enigmail's preferences$/ do
- $vm.focus_window('Icedove')
- @screen.type("a", Sikuli::KeyModifier.ALT)
- @screen.wait_and_click('IcedoveEnigmailPreferences.png', 10)
- @screen.wait('IcedoveEnigmailPreferencesWindow.png', 10)
- @screen.click('IcedoveEnigmailExpertSettingsButton.png')
- @screen.wait('IcedoveEnigmailKeyserverTab.png', 10)
-end
-
-When /^I click Enigmail's keyserver tab$/ do
- @screen.wait_and_click('IcedoveEnigmailKeyserverTab.png', 10)
-end
-
-Then /^I see that Enigmail is configured to use the correct keyserver$/ do
- @screen.wait('IcedoveEnigmailKeyserver.png', 10)
-end
-
-Then /^I click Enigmail's advanced tab$/ do
- @screen.wait_and_click('IcedoveEnigmailAdvancedTab.png', 10)
-end
-
-Then /^I see that Enigmail is configured to use the correct SOCKS proxy$/ do
- @screen.click('IcedoveEnigmailAdvancedParameters.png')
- @screen.type(Sikuli::Key.END)
- @screen.wait('IcedoveEnigmailProxy.png', 10)
-end
-
-Then /^I see that Torbirdy is configured to use Tor$/ do
- @screen.wait('IcedoveTorbirdyEnabled.png', 10)
-end
-
-When /^I open Torbirdy's preferences$/ do
- step "I open Icedove's Add-ons Manager"
- step 'I click the extensions tab'
- @screen.wait_and_click('MozillaExtensionsTorbirdy.png', 10)
- @screen.type(Sikuli::Key.TAB) # Select 'More' link
- @screen.type(Sikuli::Key.TAB) # Select 'Preferences' button
- @screen.type(Sikuli::Key.SPACE) # Press 'Preferences' button
- @screen.wait('GnomeQuestionDialogIcon.png', 10)
- @screen.type(Sikuli::Key.ENTER)
-end
-
-When /^I test Torbirdy's proxy settings$/ do
- @screen.wait('IcedoveTorbirdyPreferencesWindow.png', 10)
- @screen.click('IcedoveTorbirdyTestProxySettingsButton.png')
- @screen.wait('IcedoveTorbirdyCongratulationsTab.png', 180)
-end
-
-Then /^Torbirdy's proxy test is successful$/ do
- @screen.wait('IcedoveTorbirdyCongratulationsTab.png', 180)
-end
diff --git a/cucumber/features/step_definitions/mac_spoofing.rb b/cucumber/features/step_definitions/mac_spoofing.rb
index a4aa8714..260b28fd 100644
--- a/cucumber/features/step_definitions/mac_spoofing.rb
+++ b/cucumber/features/step_definitions/mac_spoofing.rb
@@ -5,51 +5,51 @@ def all_ethernet_nics
end
When /^I disable MAC spoofing in Tails Greeter$/ do
+ open_greeter_additional_settings()
@screen.wait_and_click("TailsGreeterMACSpoofing.png", 30)
+ @screen.wait_and_click("TailsGreeterDisableMACSpoofing.png", 10)
+ @screen.wait_and_click("TailsGreeterAdditionalSettingsAdd.png", 10)
end
-Then /^the network device has (its default|a spoofed) MAC address configured$/ do |mode|
+Then /^the (\d+)(?:st|nd|rd|th) network device has (its real|a spoofed) MAC address configured$/ do |dev_nr, mode|
is_spoofed = (mode == "a spoofed")
- nic = "eth0"
- assert_equal([nic], all_ethernet_nics,
- "We only expected NIC #{nic} but these are present: " +
- all_ethernet_nics.join(", "))
- nic_real_mac = $vm.real_mac
+ alias_name = "net#{dev_nr.to_i - 1}"
+ nic_real_mac = $vm.real_mac(alias_name)
+ nic = "eth#{dev_nr.to_i - 1}"
nic_current_mac = $vm.execute_successfully(
"get_current_mac_of_nic #{nic}", :libs => 'hardware'
).stdout.chomp
- if is_spoofed
- if nic_real_mac == nic_current_mac
- save_pcap_file
- raise "The MAC address was expected to be spoofed but wasn't"
- end
- else
- if nic_real_mac != nic_current_mac
- save_pcap_file
- raise "The MAC address is spoofed but was expected to not be"
+ begin
+ if is_spoofed
+ if nic_real_mac == nic_current_mac
+ raise "The MAC address was expected to be spoofed but wasn't"
+ end
+ else
+ if nic_real_mac != nic_current_mac
+ raise "The MAC address is spoofed but was expected to not be"
+ end
end
+ rescue Exception => e
+ save_failure_artifact("Network capture", @sniffer.pcap_file)
+ raise e
end
end
-Then /^the real MAC address was (not )?leaked$/ do |mode|
- is_leaking = mode.nil?
- leaks = FirewallLeakCheck.new(@sniffer.pcap_file)
- mac_leaks = leaks.mac_leaks
- if is_leaking
- if !mac_leaks.include?($vm.real_mac)
- save_pcap_file
- raise "The real MAC address was expected to leak but didn't. We " +
- "observed the following MAC addresses: #{mac_leaks}"
- end
- else
- if mac_leaks.include?($vm.real_mac)
- save_pcap_file
- raise "The real MAC address was leaked but was expected not to. We " +
- "observed the following MAC addresses: #{mac_leaks}"
+Then /^no network device leaked the real MAC address$/ do
+ macs = $vm.all_real_macs
+ assert_all_connections(@sniffer.pcap_file) do |c|
+ macs.all? do |mac|
+ not [c.mac_saddr, c.mac_daddr].include?(mac)
end
end
end
+Then /^some network device leaked the real MAC address$/ do
+ assert_raise(FirewallAssertionFailedError) do
+ step 'no network device leaked the real MAC address'
+ end
+end
+
Given /^macchanger will fail by not spoofing and always returns ([\S]+)$/ do |mode|
$vm.execute_successfully("mv /usr/bin/macchanger /usr/bin/macchanger.orig")
$vm.execute_successfully("ln -s /bin/#{mode} /usr/bin/macchanger")
@@ -76,14 +76,6 @@ EOF
$vm.execute_successfully("chmod a+rx /sbin/modprobe")
end
-When /^see the "Network card disabled" notification$/ do
- robust_notification_wait("MACSpoofNetworkCardDisabled.png", 60)
-end
-
-When /^see the "All networking disabled" notification$/ do
- robust_notification_wait("MACSpoofNetworkingDisabled.png", 60)
-end
-
Then /^(\d+|no) network interface(?:s)? (?:is|are) enabled$/ do |expected_nr_nics|
# note that "no".to_i => 0 in Ruby.
expected_nr_nics = expected_nr_nics.to_i
@@ -106,3 +98,22 @@ Then /^the MAC spoofing panic mode disabled networking$/ do
end
end
end
+
+When /^I hotplug a network device( and wait for it to be initialized)?$/ do |wait|
+ initial_nr_nics = wait ? all_ethernet_nics.size : nil
+ xml = <<-EOF
+ <interface type='network'>
+ <alias name='net1'/>
+ <mac address='52:54:00:11:22:33'/>
+ <source network='TailsToasterNet'/>
+ <model type='virtio'/>
+ <link state='up'/>
+ </interface>
+ EOF
+ $vm.plug_device(xml)
+ if wait
+ try_for(20) do
+ all_ethernet_nics.size >= initial_nr_nics + 1
+ end
+ end
+end
diff --git a/cucumber/features/step_definitions/pidgin.rb b/cucumber/features/step_definitions/pidgin.rb
index 3f5ed931..43949b68 100644
--- a/cucumber/features/step_definitions/pidgin.rb
+++ b/cucumber/features/step_definitions/pidgin.rb
@@ -28,26 +28,26 @@ def wait_and_focus(img, time = 10, window)
end
def focus_pidgin_irc_conversation_window(account)
- if account == 'I2P'
- # After connecting to Irc2P messages are sent from services. Most of the
- # time the services will send their messages right away. If there's lag we
- # may in fact join the channel _before_ the message is received. We'll look
- # for a message from InfoServ first then default to looking for '#i2p'
- try_for(20) do
- begin
- $vm.focus_window('InfoServ')
- rescue ExecutionFailedInVM
- $vm.focus_window('#i2p')
- end
- end
- else
- account = account.sub(/^irc\./, '')
- try_for(20) do
- $vm.focus_window(".*#{Regexp.escape(account)}$")
- end
+ account = account.sub(/^irc\./, '')
+ try_for(20) do
+ $vm.focus_window(".*#{Regexp.escape(account)}$")
end
end
+def pidgin_dbus_call(method, *args)
+ dbus_send(
+ 'im.pidgin.purple.PurpleService',
+ '/im/pidgin/purple/PurpleObject',
+ "im.pidgin.purple.PurpleInterface.#{method}",
+ *args, user: LIVE_USER
+ )
+end
+
+def pidgin_account_connected?(account, prpl_protocol)
+ account_id = pidgin_dbus_call('PurpleAccountsFind', account, prpl_protocol)
+ pidgin_dbus_call('PurpleAccountIsConnected', account_id) == 1
+end
+
When /^I create my XMPP account$/ do
account = xmpp_account("Tails_account")
@screen.click("PidginAccountManagerAddButton.png")
@@ -74,6 +74,11 @@ When /^I create my XMPP account$/ do
end
Then /^Pidgin automatically enables my XMPP account$/ do
+ account = xmpp_account("Tails_account")
+ jid = account["username"] + '@' + account["domain"]
+ try_for(3*60) do
+ pidgin_account_connected?(jid, 'prpl-jabber')
+ end
$vm.focus_window('Buddy List')
@screen.wait("PidginAvailableStatus.png", 60*3)
end
@@ -109,8 +114,9 @@ When /^I start a conversation with my friend$/ do
@screen.wait("PidginConversationWindowMenuBar.png", 10)
end
-And /^I say something to my friend( in the multi-user chat)?$/ do |multi_chat|
- msg = "ping" + Sikuli::Key.ENTER
+And /^I say (.*) to my friend( in the multi-user chat)?$/ do |msg, multi_chat|
+ msg = "ping" if msg == "something"
+ msg = msg + Sikuli::Key.ENTER
if multi_chat
$vm.focus_window(@chat_room_jid.split("@").first)
msg = @friend_name + ": " + msg
@@ -126,7 +132,12 @@ Then /^I receive a response from my friend( in the multi-user chat)?$/ do |multi
else
$vm.focus_window(@friend_name)
end
- @screen.wait("PidginFriendExpectedAnswer.png", 20)
+ try_for(60) do
+ if @screen.exists('PidginServerMessage.png')
+ @screen.click('PidginDialogCloseButton.png')
+ end
+ @screen.find('PidginFriendExpectedAnswer.png')
+ end
end
When /^I start an OTR session with my friend$/ do
@@ -203,15 +214,26 @@ end
def configured_pidgin_accounts
accounts = Hash.new
- xml = REXML::Document.new($vm.file_content('$HOME/.purple/accounts.xml',
- LIVE_USER))
+ xml = REXML::Document.new(
+ $vm.file_content("/home/#{LIVE_USER}/.purple/accounts.xml")
+ )
xml.elements.each("account/account") do |e|
account = e.elements["name"].text
account_name, network = account.split("@")
protocol = e.elements["protocol"].text
port = e.elements["settings/setting[@name='port']"].text
- nickname = e.elements["settings/setting[@name='username']"].text
- real_name = e.elements["settings/setting[@name='realname']"].text
+ username_element = e.elements["settings/setting[@name='username']"]
+ realname_elemenet = e.elements["settings/setting[@name='realname']"]
+ if username_element
+ nickname = username_element.text
+ else
+ nickname = nil
+ end
+ if realname_elemenet
+ real_name = realname_elemenet.text
+ else
+ real_name = nil
+ end
accounts[network] = {
'name' => account_name,
'network' => network,
@@ -227,34 +249,25 @@ end
def chan_image (account, channel, image)
images = {
- 'irc.oftc.net' => {
- '#tails' => {
- 'roster' => 'PidginTailsChannelEntry',
+ 'conference.riseup.net' => {
+ 'tails' => {
'conversation_tab' => 'PidginTailsConversationTab',
'welcome' => 'PidginTailsChannelWelcome',
}
},
- 'I2P' => {
- '#i2p' => {
- 'roster' => 'PidginI2PChannelEntry',
- 'conversation_tab' => 'PidginI2PConversationTab',
- 'welcome' => 'PidginI2PChannelWelcome',
- }
- }
}
return images[account][channel][image] + ".png"
end
def default_chan (account)
chans = {
- 'irc.oftc.net' => '#tails',
- 'I2P' => '#i2p',
+ 'conference.riseup.net' => 'tails',
}
return chans[account]
end
def pidgin_otr_keys
- return $vm.file_content('$HOME/.purple/otr.private_key', LIVE_USER)
+ return $vm.file_content("/home/#{LIVE_USER}/.purple/otr.private_key")
end
Given /^Pidgin has the expected accounts configured with random nicknames$/ do
@@ -278,10 +291,6 @@ Given /^Pidgin has the expected accounts configured with random nicknames$/ do
"#{expected}")
end
-When /^I start Pidgin through the GNOME menu$/ do
- step 'I start "Pidgin" via the GNOME "Internet" applications menu'
-end
-
When /^I open Pidgin's account manager window$/ do
@screen.wait_and_click('PidginMenuAccounts.png', 20)
@screen.wait_and_click('PidginMenuManageAccounts.png', 20)
@@ -293,7 +302,13 @@ When /^I see Pidgin's account manager window$/ do
end
When /^I close Pidgin's account manager window$/ do
- @screen.wait_and_click("PidginAccountManagerCloseButton.png", 10)
+ @screen.wait_and_click("PidginDialogCloseButton.png", 10)
+end
+
+When /^I close Pidgin$/ do
+ $vm.focus_window('Buddy List')
+ @screen.type("q", Sikuli::KeyModifier.CTRL)
+ @screen.waitVanish('PidginAvailableStatus.png', 10)
end
When /^I (de)?activate the "([^"]+)" Pidgin account$/ do |deactivate, account|
@@ -331,8 +346,7 @@ Then /^Pidgin successfully connects to the "([^"]+)" account$/ do |account|
deactivate_and_activate_pidgin_account(account)
end
end
- retrier_method = account == 'I2P' ? method(:retry_i2p) : method(:retry_tor)
- retrier_method.call(recovery_on_failure) do
+ retry_tor(recovery_on_failure) do
begin
$vm.focus_window('Buddy List')
rescue ExecutionFailedInVM
@@ -363,10 +377,22 @@ Then /^the "([^"]*)" account only responds to PING and VERSION CTCP requests$/ d
ctcp_check.verify_ctcp_responses
end
-Then /^I can join the "([^"]+)" channel on "([^"]+)"$/ do |channel, account|
- @screen.doubleClick( chan_image(account, channel, 'roster'))
+Then /^I can join the( pre-configured)? "([^"]+)" channel on "([^"]+)"$/ do |preconfigured, channel, account|
+ if preconfigured
+ @screen.doubleClick(chan_image(account, channel, 'roster'))
+ focus_pidgin_irc_conversation_window(account)
+ else
+ $vm.focus_window('Buddy List')
+ @screen.wait_and_click("PidginBuddiesMenu.png", 20)
+ @screen.wait_and_click("PidginBuddiesMenuJoinChat.png", 10)
+ @screen.wait_and_click("PidginJoinChatWindow.png", 10)
+ @screen.click_mid_right_edge("PidginJoinChatRoomLabel.png")
+ @screen.type(channel)
+ @screen.click("PidginJoinChatButton.png")
+ @chat_room_jid = channel + "@" + account
+ $vm.focus_window(@chat_room_jid)
+ end
@screen.hide_cursor
- focus_pidgin_irc_conversation_window(account)
try_for(60) do
begin
@screen.wait_and_click(chan_image(account, channel, 'conversation_tab'), 5)
@@ -405,7 +431,7 @@ end
def pidgin_add_certificate_from (cert_file)
# Here, we need a certificate that is not already in the NSS database
- step "I copy \"/usr/share/ca-certificates/spi-inc.org/spi-cacert-2008.crt\" to \"#{cert_file}\" as user \"amnesia\""
+ step "I copy \"/usr/share/ca-certificates/mozilla/CNNIC_ROOT.crt\" to \"#{cert_file}\" as user \"amnesia\""
$vm.focus_window('Buddy List')
@screen.wait_and_click('PidginToolsMenu.png', 10)
@@ -453,6 +479,9 @@ end
When /^I see the Tails roadmap URL$/ do
try_for(60) do
+ if @screen.exists('PidginServerMessage.png')
+ @screen.click('PidginDialogCloseButton.png')
+ end
begin
@screen.find('PidginTailsRoadmapUrl.png')
rescue FindFailed => e
@@ -464,4 +493,5 @@ end
When /^I click on the Tails roadmap URL$/ do
@screen.click('PidginTailsRoadmapUrl.png')
+ try_for(60) { @torbrowser = Dogtail::Application.new('Firefox') }
end
diff --git a/cucumber/features/step_definitions/root_access_control.rb b/cucumber/features/step_definitions/root_access_control.rb
index ff1bdfcc..8362342d 100644
--- a/cucumber/features/step_definitions/root_access_control.rb
+++ b/cucumber/features/step_definitions/root_access_control.rb
@@ -34,8 +34,7 @@ end
Then /^I should not be able to run a command as root with pkexec and the standard passwords$/ do
step "I run \"pkexec touch /root/pkexec-test\" in GNOME Terminal"
['', 'live', 'amnesia'].each do |password|
- step "I enter the \"#{password}\" password in the pkexec prompt"
- @screen.wait('PolicyKitAuthFailure.png', 20)
+ deal_with_polkit_prompt(password, expect_success: false)
end
@screen.type(Sikuli::Key.ESC)
@screen.wait('PolicyKitAuthCompleteFailure.png', 20)
diff --git a/cucumber/features/step_definitions/snapshots.rb b/cucumber/features/step_definitions/snapshots.rb
index 74c60d20..16e59a4b 100644
--- a/cucumber/features/step_definitions/snapshots.rb
+++ b/cucumber/features/step_definitions/snapshots.rb
@@ -6,7 +6,7 @@ def checkpoints
:parent_checkpoint => nil,
:steps => [
'I create a 8 GiB disk named "'+JOB_NAME+'"',
- 'I plug ide drive "'+JOB_NAME+'"',
+ 'I plug sata drive "'+JOB_NAME+'"',
]
}
@@ -16,7 +16,7 @@ def checkpoints
:parent_checkpoint => nil,
:steps => [
'I create a 64 GiB disk named "'+JOB_NAME+'"',
- 'I plug ide drive "'+JOB_NAME+'"',
+ 'I plug sata drive "'+JOB_NAME+'"',
]
}
@@ -54,7 +54,7 @@ def checkpoints
'I allow reboot after the install is complete',
'I wait for the reboot',
'I power off the computer',
- 'the computer is set to boot from ide drive',
+ 'the computer is set to boot from sata drive',
]
}
end
@@ -85,12 +85,12 @@ def reach_checkpoint(name)
post_snapshot_restore_hook
end
debug_log(scenario_indent + "Checkpoint: #{checkpoint_description}",
- :color => :white)
+ color: :white, timestamp: false)
step_action = "Given"
if parent_checkpoint
parent_description = checkpoints[parent_checkpoint][:description]
debug_log(step_indent + "#{step_action} #{parent_description}",
- :color => :green)
+ color: :green, timestamp: false)
step_action = "And"
end
steps.each do |s|
@@ -99,10 +99,11 @@ def reach_checkpoint(name)
rescue Exception => e
debug_log(scenario_indent +
"Step failed while creating checkpoint: #{s}",
- :color => :red)
+ color: :red, timestamp: false)
raise e
end
- debug_log(step_indent + "#{step_action} #{s}", :color => :green)
+ debug_log(step_indent + "#{step_action} #{s}",
+ color: :green, timestamp: false)
step_action = "And"
end
$vm.save_snapshot(name)
diff --git a/cucumber/features/step_definitions/ssh.rb b/cucumber/features/step_definitions/ssh.rb
index 038b2977..1fd0efaf 100644
--- a/cucumber/features/step_definitions/ssh.rb
+++ b/cucumber/features/step_definitions/ssh.rb
@@ -60,6 +60,7 @@ end
Given /^I (?:am prompted to )?verify the SSH fingerprint for the (?:Git|SSH) (?:repository|server)$/ do
@screen.wait("SSHFingerprint.png", 60)
+ sleep 1 # brief pause to ensure that the following keystrokes do not get lost
@screen.type('yes' + Sikuli::Key.ENTER)
end
@@ -75,6 +76,7 @@ Given /^an SSH server is running on the LAN$/ do
@sshd_server_host = $vmnet.bridge_ip_addr
sshd = SSHServer.new(@sshd_server_host, @sshd_server_port)
sshd.start
+ add_lan_host(@sshd_server_host, @sshd_server_port)
add_after_scenario_hook { sshd.stop }
end
@@ -94,8 +96,17 @@ When /^I connect to an SSH server on the (Internet|LAN)$/ do |location|
cmd = "ssh #{@ssh_username}@#{@ssh_host} #{ssh_port_suffix}"
step 'process "ssh" is not running'
- step "I run \"#{cmd}\" in GNOME Terminal"
- step 'process "ssh" is running within 10 seconds'
+
+ recovery_proc = Proc.new do
+ step 'I kill the process "ssh"' if $vm.has_process?("ssh")
+ step 'I run "clear" in GNOME Terminal'
+ end
+
+ retry_tor(recovery_proc) do
+ step "I run \"#{cmd}\" in GNOME Terminal"
+ step 'process "ssh" is running within 10 seconds'
+ step 'I verify the SSH fingerprint for the SSH server'
+ end
end
Then /^I have sucessfully logged into the SSH server$/ do
@@ -104,19 +115,42 @@ end
Then /^I connect to an SFTP server on the Internet$/ do
read_and_validate_ssh_config "SFTP"
+
@sftp_port ||= 22
@sftp_port = @sftp_port.to_s
- step 'I start "Files" via the GNOME "Accessories" applications menu'
- @screen.wait_and_click("GnomeFilesConnectToServer.png", 10)
- @screen.wait("GnomeConnectToServerWindow.png", 10)
- @screen.type("sftp://" + @sftp_username + "@" + @sftp_host + ":" + @sftp_port)
- @screen.wait_and_click("GnomeConnectToServerConnectButton.png", 10)
+
+ recovery_proc = Proc.new do
+ step 'I kill the process "ssh"'
+ step 'I kill the process "nautilus"'
+ end
+
+ retry_tor(recovery_proc) do
+ step 'I start "Nautilus" via GNOME Activities Overview'
+ nautilus = Dogtail::Application.new('nautilus')
+ nautilus.child(roleName: 'frame')
+ nautilus.child('Other Locations', roleName: 'label').click
+ connect_bar = nautilus.child('Connect to Server', roleName: 'label').parent
+ connect_bar
+ .child(roleName: 'filler', recursive: false)
+ .child(roleName: 'text', recursive: false)
+ .text = "sftp://" + @sftp_username + "@" + @sftp_host + ":" + @sftp_port
+ connect_bar.button('Connect', recursive: false).click
+ step "I verify the SSH fingerprint for the SFTP server"
+ end
end
Then /^I verify the SSH fingerprint for the SFTP server$/ do
- @screen.wait_and_click("GnomeSSHVerificationConfirm.png", 60)
+ try_for(30) do
+ Dogtail::Application.new('gnome-shell').child?('Log In Anyway')
+ end
+ # Here we'd like to click on the button using Dogtail, but something
+ # is buggy so let's just use the keyboard.
+ @screen.type(Sikuli::Key.ENTER)
end
Then /^I successfully connect to the SFTP server$/ do
- @screen.wait("GnomeSSHSuccess.png", 60)
+ try_for(60) do
+ Dogtail::Application.new('nautilus')
+ .child?("#{@sftp_username} on #{@sftp_host}")
+ end
end
diff --git a/cucumber/features/step_definitions/time_syncing.rb b/cucumber/features/step_definitions/time_syncing.rb
index 319fb521..d1b81073 100644
--- a/cucumber/features/step_definitions/time_syncing.rb
+++ b/cucumber/features/step_definitions/time_syncing.rb
@@ -47,23 +47,23 @@ Then /^Tails clock is less than (\d+) minutes incorrect$/ do |max_diff_mins|
puts "Time was #{diff} seconds off"
end
-Then /^the system clock is just past Tails' build date$/ do
+Then /^the system clock is just past Tails' source date$/ do
system_time_str = $vm.execute_successfully('date').to_s
system_time = DateTime.parse(system_time_str).to_time
- build_time_cmd = 'sed -n -e "1s/^.* - \([0-9]\+\)$/\1/p;q" ' +
- '/etc/amnesia/version'
- build_time_str = $vm.execute_successfully(build_time_cmd).to_s
- build_time = DateTime.parse(build_time_str).to_time
- diff = system_time - build_time # => in seconds
+ source_time_cmd = 'sed -n -e "1s/^.* - \([0-9]\+\)$/\1/p;q" ' +
+ '/etc/amnesia/version'
+ source_time_str = $vm.execute_successfully(source_time_cmd).to_s
+ source_time = DateTime.parse(source_time_str).to_time
+ diff = system_time - source_time # => in seconds
# Half an hour should be enough to boot Tails on any reasonable
# hardware and VM setup.
max_diff = 30*60
assert(diff > 0,
"The system time (#{system_time}) is before the Tails " +
- "build date (#{build_time})")
+ "source date (#{source_time})")
assert(diff <= max_diff,
"The system time (#{system_time}) is more than #{max_diff} seconds " +
- "past the build date (#{build_time})")
+ "past the source date (#{source_time})")
end
Then /^Tails' hardware clock is close to the host system's time$/ do
diff --git a/cucumber/features/step_definitions/tor.rb b/cucumber/features/step_definitions/tor.rb
index ac12fd4c..04852f76 100644
--- a/cucumber/features/step_definitions/tor.rb
+++ b/cucumber/features/step_definitions/tor.rb
@@ -90,7 +90,7 @@ Then /^the firewall is configured to only allow the (.+) users? to connect direc
"The following rule has an unexpected destination:\n" +
rule.to_s)
state_cond = try_xml_element_text(rule, "conditions/state/state")
- next if state_cond == "RELATED,ESTABLISHED"
+ next if state_cond == "ESTABLISHED"
assert_not_nil(rule.elements['conditions/owner/uid-owner'])
rule.elements.each('conditions/owner/uid-owner') do |owner|
uid = owner.text.to_i
@@ -184,7 +184,7 @@ def firewall_has_dropped_packet_to?(proto, host, port)
$vm.execute("journalctl --dmesg --output=cat | grep -qP '#{regex}'").success?
end
-When /^I open an untorified (TCP|UDP|ICMP) connections to (\S*)(?: on port (\d+))? that is expected to fail$/ do |proto, host, port|
+When /^I open an untorified (TCP|UDP|ICMP) connection to (\S*)(?: on port (\d+))?$/ do |proto, host, port|
assert(!firewall_has_dropped_packet_to?(proto, host, port),
"A #{proto} packet to #{host}" +
(port.nil? ? "" : ":#{port}") +
@@ -195,11 +195,11 @@ When /^I open an untorified (TCP|UDP|ICMP) connections to (\S*)(?: on port (\d+)
case proto
when "TCP"
assert_not_nil(port)
- cmd = "echo | netcat #{host} #{port}"
+ cmd = "echo | nc.traditional #{host} #{port}"
user = LIVE_USER
when "UDP"
assert_not_nil(port)
- cmd = "echo | netcat -u #{host} #{port}"
+ cmd = "echo | nc.traditional -u #{host} #{port}"
user = LIVE_USER
when "ICMP"
cmd = "ping -c 5 #{host}"
@@ -243,34 +243,38 @@ def stream_isolation_info(application)
case application
when "htpdate"
{
- :grep_monitor_expr => '/curl\>',
+ :grep_monitor_expr => 'users:(("curl"',
:socksport => 9062
}
- when "tails-security-check", "tails-upgrade-frontend-wrapper"
- # We only grep connections with ESTABLISHED state since `perl`
- # is also used by monkeysphere's validation agent, which LISTENs
+ when "tails-security-check"
{
- :grep_monitor_expr => '\<ESTABLISHED\>.\+/perl\>',
+ :grep_monitor_expr => 'users:(("tails-security-"',
+ :socksport => 9062
+ }
+ when "tails-upgrade-frontend-wrapper"
+ {
+ :grep_monitor_expr => 'users:(("tails-iuk-get-u"',
:socksport => 9062
}
when "Tor Browser"
{
- :grep_monitor_expr => '/firefox\>',
- :socksport => 9150
+ :grep_monitor_expr => 'users:(("firefox"',
+ :socksport => 9150,
+ :controller => true,
}
when "Gobby"
{
- :grep_monitor_expr => '/gobby\>',
+ :grep_monitor_expr => 'users:(("gobby-0.5"',
:socksport => 9050
}
when "SSH"
{
- :grep_monitor_expr => '/\(connect-proxy\|ssh\)\>',
+ :grep_monitor_expr => 'users:(("\(nc\|ssh\)"',
:socksport => 9050
}
when "whois"
{
- :grep_monitor_expr => '/whois\>',
+ :grep_monitor_expr => 'users:(("whois"',
:socksport => 9050
}
else
@@ -279,26 +283,28 @@ def stream_isolation_info(application)
end
When /^I monitor the network connections of (.*)$/ do |application|
- @process_monitor_log = "/tmp/netstat.log"
+ @process_monitor_log = "/tmp/ss.log"
info = stream_isolation_info(application)
$vm.spawn("while true; do " +
- " netstat -taupen | grep \"#{info[:grep_monitor_expr]}\"; " +
+ " ss -taupen | grep '#{info[:grep_monitor_expr]}'; " +
" sleep 0.1; " +
"done > #{@process_monitor_log}")
end
Then /^I see that (.+) is properly stream isolated$/ do |application|
- expected_port = stream_isolation_info(application)[:socksport]
+ info = stream_isolation_info(application)
+ expected_ports = [info[:socksport]]
+ expected_ports << 9051 if info[:controller]
assert_not_nil(@process_monitor_log)
log_lines = $vm.file_content(@process_monitor_log).split("\n")
assert(log_lines.size > 0,
"Couldn't see any connection made by #{application} so " \
"something is wrong")
log_lines.each do |line|
- addr_port = line.split(/\s+/)[4]
- assert_equal("127.0.0.1:#{expected_port}", addr_port,
- "#{application} should use SocksPort #{expected_port} but " \
- "was seen connecting to #{addr_port}")
+ ip_port = line.split(/\s+/)[5]
+ assert(expected_ports.map { |port| "127.0.0.1:#{port}" }.include?(ip_port),
+ "#{application} should only connect to #{expected_ports} but " \
+ "was seen connecting to #{ip_port}")
end
end
@@ -308,7 +314,7 @@ end
And /^I re-run htpdate$/ do
$vm.execute_successfully("service htpdate stop && " \
- "rm -f /var/run/htpdate/* && " \
+ "rm -f /run/htpdate/* && " \
"systemctl --no-block start htpdate.service")
step "the time has synced"
end
@@ -318,18 +324,22 @@ And /^I re-run tails-upgrade-frontend-wrapper$/ do
end
When /^I connect Gobby to "([^"]+)"$/ do |host|
- @screen.wait("GobbyWindow.png", 30)
- @screen.wait("GobbyWelcomePrompt.png", 10)
- @screen.click("GnomeCloseButton.png")
- @screen.wait("GobbyWindow.png", 10)
+ gobby = Dogtail::Application.new('gobby-0.5')
+ gobby.child('Welcome to Gobby', roleName: 'label')
+ gobby.button('Close').click
# This indicates that Gobby has finished initializing itself
# (generating DH parameters, etc.) -- before, the UI is not responsive
# and our CTRL-t is lost.
- @screen.wait("GobbyFailedToShareDocuments.png", 30)
+ gobby.child('Failed to share documents', roleName: 'label')
+ gobby.menu('File').click
+ gobby.menuItem('Connect to Server...').click
@screen.type("t", Sikuli::KeyModifier.CTRL)
- @screen.wait("GobbyConnectPrompt.png", 10)
- @screen.type(host + Sikuli::Key.ENTER)
- @screen.wait("GobbyConnectionComplete.png", 60)
+ connect_dialog = gobby.dialog('Connect to Server')
+ connect_dialog.child('', roleName: 'text').typeText(host)
+ connect_dialog.button('Connect').click
+ # This looks for the live user's presence entry in the chat, which
+ # will only be shown if the connection succeeded.
+ try_for(60) { gobby.child(LIVE_USER, roleName: 'table cell'); true }
end
When /^the Tor Launcher autostarts$/ do
@@ -337,35 +347,47 @@ When /^the Tor Launcher autostarts$/ do
end
When /^I configure some (\w+) pluggable transports in Tor Launcher$/ do |bridge_type|
- bridge_type.downcase!
- bridge_type.capitalize!
- begin
- @bridges = $config["Tor"]["Transports"][bridge_type]
- assert_not_nil(@bridges)
- assert(!@bridges.empty?)
- rescue NoMethodError, Test::Unit::AssertionFailedError
- raise(
-<<EOF
-It seems no '#{bridge_type}' pluggable transports are defined in your local configuration file (#{LOCAL_CONFIG_FILE}). See wiki/src/contribute/release_process/test/usage.mdwn for the format.
-EOF
-)
- end
- @bridge_hosts = []
- for bridge in @bridges do
- @bridge_hosts << bridge["ipv4_address"]
- end
-
@screen.wait_and_click('TorLauncherConfigureButton.png', 10)
@screen.wait('TorLauncherBridgePrompt.png', 10)
@screen.wait_and_click('TorLauncherYesRadioOption.png', 10)
@screen.wait_and_click('TorLauncherNextButton.png', 10)
@screen.wait_and_click('TorLauncherBridgeList.png', 10)
- for bridge in @bridges do
- bridge_line = bridge_type.downcase + " " +
- bridge["ipv4_address"] + ":" +
- bridge["ipv4_port"].to_s
- bridge_line += " " + bridge["fingerprint"].to_s if bridge["fingerprint"]
- bridge_line += " " + bridge["extra"].to_s if bridge["extra"]
+ @bridge_hosts = []
+ chutney_src_dir = "#{GIT_DIR}/submodules/chutney"
+ bridge_dirs = Dir.glob(
+ "#{$config['TMPDIR']}/chutney-data/nodes/*#{bridge_type}/"
+ )
+ bridge_dirs.each do |bridge_dir|
+ address = $vmnet.bridge_ip_addr
+ port = nil
+ fingerprint = nil
+ extra = nil
+ if bridge_type == 'bridge'
+ open(bridge_dir + "/torrc") do |f|
+ port = f.grep(/^OrPort\b/).first.split.last
+ end
+ else
+ # This is the pluggable transport case. While we could set a
+ # static port via ServerTransportListenAddr we instead let it be
+ # picked randomly so an already used port is not picked --
+ # Chutney already has issues with that for OrPort selection.
+ pt_re = /Registered server transport '#{bridge_type}' at '[^']*:(\d+)'/
+ open(bridge_dir + "/notice.log") do |f|
+ pt_lines = f.grep(pt_re)
+ port = pt_lines.last.match(pt_re)[1]
+ end
+ if bridge_type == 'obfs4'
+ open(bridge_dir + "/pt_state/obfs4_bridgeline.txt") do |f|
+ extra = f.readlines.last.chomp.sub(/^.* cert=/, 'cert=')
+ end
+ end
+ end
+ open(bridge_dir + "/fingerprint") do |f|
+ fingerprint = f.read.chomp.split.last
+ end
+ @bridge_hosts << { address: address, port: port.to_i }
+ bridge_line = bridge_type + " " + address + ":" + port
+ [fingerprint, extra].each { |e| bridge_line += " " + e.to_s if e }
@screen.type(bridge_line + Sikuli::Key.ENTER)
end
@screen.wait_and_click('TorLauncherNextButton.png', 10)
@@ -378,25 +400,7 @@ end
When /^all Internet traffic has only flowed through the configured pluggable transports$/ do
assert_not_nil(@bridge_hosts, "No bridges has been configured via the " +
"'I configure some ... bridges in Tor Launcher' step")
- leaks = FirewallLeakCheck.new(@sniffer.pcap_file,
- :accepted_hosts => @bridge_hosts)
- leaks.assert_no_leaks
-end
-
-Then /^the Tor binary is configured to use the expected Tor authorities$/ do
- tor_auths = Set.new
- tor_binary_orport_strings = $vm.execute_successfully(
- "strings /usr/bin/tor | grep -E 'orport=[0-9]+'").stdout.chomp.split("\n")
- tor_binary_orport_strings.each do |potential_auth_string|
- auth_regex = /^\S+ orport=\d+( bridge)?( no-v2)?( v3ident=[A-Z0-9]{40})? ([0-9\.]+):\d+( [A-Z0-9]{4}){10}$/
- m = auth_regex.match(potential_auth_string)
- if m
- auth_ipv4_addr = m[4]
- tor_auths << auth_ipv4_addr
- end
+ assert_all_connections(@sniffer.pcap_file) do |c|
+ @bridge_hosts.include?({ address: c.daddr, port: c.dport })
end
- expected_tor_auths = Set.new(TOR_AUTHORITIES)
- assert_equal(expected_tor_auths, tor_auths,
- "The Tor binary does not have the expected Tor authorities " +
- "configured")
end
diff --git a/cucumber/features/step_definitions/torified_browsing.rb b/cucumber/features/step_definitions/torified_browsing.rb
index c8f3ff1d..76760789 100644
--- a/cucumber/features/step_definitions/torified_browsing.rb
+++ b/cucumber/features/step_definitions/torified_browsing.rb
@@ -1,5 +1,5 @@
-When /^no traffic has flowed to the LAN$/ do
- leaks = FirewallLeakCheck.new(@sniffer.pcap_file, :ignore_lan => false)
- assert(not(leaks.ipv4_tcp_leaks.include?(@lan_host)),
- "Traffic was sent to LAN host #{@lan_host}")
+Then /^no traffic was sent to the web server on the LAN$/ do
+ assert_no_connections(@sniffer.pcap_file) do |c|
+ c.daddr == @web_server_ip_addr and c.dport == @web_server_port
+ end
end
diff --git a/cucumber/features/step_definitions/torified_gnupg.rb b/cucumber/features/step_definitions/torified_gnupg.rb
index 4b4cc040..f5f61cef 100644
--- a/cucumber/features/step_definitions/torified_gnupg.rb
+++ b/cucumber/features/step_definitions/torified_gnupg.rb
@@ -1,3 +1,5 @@
+require 'resolv'
+
class OpenPGPKeyserverCommunicationError < StandardError
end
@@ -20,7 +22,7 @@ def start_or_restart_seahorse
if @withgpgapplet
seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletManageKeys.png')
else
- step 'I start "Seahorse" via the GNOME "Utilities" applications menu'
+ step 'I start "Passwords and Keys" via GNOME Activities Overview'
end
step 'Seahorse has opened'
end
@@ -43,6 +45,18 @@ When /^the "([^"]+)" OpenPGP key is not in the live user's public keyring$/ do |
"The '#{keyid}' key is in the live user's public keyring.")
end
+def setup_onion_keyserver
+ resolver = Resolv::DNS.new
+ keyservers = resolver.getaddresses('pool.sks-keyservers.net').select do |addr|
+ addr.class == Resolv::IPv4
+ end
+ onion_keyserver_address = keyservers.sample
+ hkp_port = 11371
+ @onion_keyserver_job = chutney_onionservice_redir(
+ onion_keyserver_address, hkp_port
+ )
+end
+
When /^I fetch the "([^"]+)" OpenPGP key using the GnuPG CLI( without any signatures)?$/ do |keyid, without|
# Make keyid an instance variable so we can reference it in the Seahorse
# keysyncing step.
@@ -52,7 +66,7 @@ When /^I fetch the "([^"]+)" OpenPGP key using the GnuPG CLI( without any signat
else
importopts = ''
end
- retry_tor do
+ retry_tor(Proc.new { setup_onion_keyserver }) do
@gnupg_recv_key_res = $vm.execute_successfully(
"timeout 120 gpg --batch #{importopts} --recv-key '#{@fetched_openpgp_keyid}'",
:user => LIVE_USER)
@@ -74,11 +88,6 @@ When /^the Seahorse operation is successful$/ do
$vm.has_process?('seahorse')
end
-When /^GnuPG uses the configured keyserver$/ do
- assert(@gnupg_recv_key_res.stderr[CONFIGURED_KEYSERVER_HOSTNAME],
- "GnuPG's stderr did not mention keyserver #{CONFIGURED_KEYSERVER_HOSTNAME}")
-end
-
When /^the "([^"]+)" key is in the live user's public keyring(?: after at most (\d) seconds)?$/ do |keyid, delay|
delay = 10 unless delay
try_for(delay.to_i, :msg => "The '#{keyid}' key is not in the live user's public keyring") {
@@ -87,7 +96,7 @@ When /^the "([^"]+)" key is in the live user's public keyring(?: after at most (
}
end
-When /^I start Seahorse( via the Tails OpenPGP Applet)?$/ do |withgpgapplet|
+When /^I start Seahorse( via the OpenPGP Applet)?$/ do |withgpgapplet|
@withgpgapplet = !!withgpgapplet
start_or_restart_seahorse
end
@@ -108,7 +117,8 @@ end
Then /^I synchronize keys in Seahorse$/ do
recovery_proc = Proc.new do
- # The versions of Seahorse in Wheezy and Jessie will abort with a
+ setup_onion_keyserver
+ # The version of Seahorse in Jessie will abort with a
# segmentation fault whenever there's any sort of network error while
# syncing keys. This will usually happens after clicking away the error
# message. This does not appear to be a problem in Stretch.
@@ -151,7 +161,7 @@ Then /^I synchronize keys in Seahorse$/ do
end
end
-When /^I fetch the "([^"]+)" OpenPGP key using Seahorse( via the Tails OpenPGP Applet)?$/ do |keyid, withgpgapplet|
+When /^I fetch the "([^"]+)" OpenPGP key using Seahorse( via the OpenPGP Applet)?$/ do |keyid, withgpgapplet|
step "I start Seahorse#{withgpgapplet}"
def change_of_status?(keyid)
@@ -166,6 +176,7 @@ When /^I fetch the "([^"]+)" OpenPGP key using Seahorse( via the Tails OpenPGP A
end
recovery_proc = Proc.new do
+ setup_onion_keyserver
@screen.click('GnomeCloseButton.png') if @screen.exists('GnomeCloseButton.png')
@screen.type("w", Sikuli::KeyModifier.CTRL)
end
@@ -198,11 +209,55 @@ When /^I fetch the "([^"]+)" OpenPGP key using Seahorse( via the Tails OpenPGP A
end
end
-Then /^Seahorse is configured to use the correct keyserver$/ do
- @gnome_keyservers = YAML.load($vm.execute_successfully('gsettings get org.gnome.crypto.pgp keyservers',
- :user => LIVE_USER).stdout)
- assert_equal(1, @gnome_keyservers.count, 'Seahorse should only have one keyserver configured.')
- # Seahorse doesn't support hkps so that part of the domain is stripped out.
- # We also insert hkp:// to the beginning of the domain.
- assert_equal(CONFIGURED_KEYSERVER_HOSTNAME.sub('hkps.', 'hkp://'), @gnome_keyservers[0])
+Given /^(GnuPG|Seahorse) is configured to use Chutney's onion keyserver$/ do |app|
+ setup_onion_keyserver unless @onion_keyserver_job
+ _, _, onion_address, onion_port = chutney_onionservice_info
+ case app
+ when 'GnuPG'
+ # Validate the shipped configuration ...
+ server = /keyserver\s+(\S+)$/.match($vm.file_content("/home/#{LIVE_USER}/.gnupg/dirmngr.conf"))[1]
+ assert_equal(
+ "hkp://#{CONFIGURED_KEYSERVER_HOSTNAME}", server,
+ "GnuPG's dirmngr does not use the correct keyserver"
+ )
+ # ... before replacing it
+ $vm.execute_successfully(
+ "sed -i 's/#{CONFIGURED_KEYSERVER_HOSTNAME}/#{onion_address}:#{onion_port}/' " +
+ "'/home/#{LIVE_USER}/.gnupg/dirmngr.conf'"
+ )
+ when 'Seahorse'
+ # Validate the shipped configuration ...
+ @gnome_keyservers = YAML.load(
+ $vm.execute_successfully(
+ 'gsettings get org.gnome.crypto.pgp keyservers',
+ user: LIVE_USER
+ ).stdout
+ )
+ assert_equal(1, @gnome_keyservers.count,
+ 'Seahorse should only have one keyserver configured.')
+ assert_equal(
+ 'hkp://' + CONFIGURED_KEYSERVER_HOSTNAME, @gnome_keyservers[0],
+ "GnuPG's dirmngr does not use the correct keyserver"
+ )
+ # ... before replacing it
+ $vm.execute_successfully(
+ "gsettings set org.gnome.crypto.pgp keyservers \"['hkp://#{onion_address}:#{onion_port}']\"",
+ user: LIVE_USER
+ )
+ end
+end
+
+Then /^GnuPG's dirmngr uses the configured keyserver$/ do
+ _, _, onion_keyserver_address, _ = chutney_onionservice_info
+ dirmngr_request = $vm.execute_successfully(
+ 'gpg-connect-agent --dirmngr "keyserver --hosttable" /bye', user: LIVE_USER
+ )
+ server = dirmngr_request.stdout.chomp.lines[1].split[4]
+ server = /keyserver\s+(\S+)$/.match(
+ $vm.file_content("/home/#{LIVE_USER}/.gnupg/dirmngr.conf")
+ )[1]
+ assert_equal(
+ "hkp://#{onion_keyserver_address}:5858", server,
+ "GnuPG's dirmngr does not use the correct keyserver"
+ )
end
diff --git a/cucumber/features/step_definitions/torified_misc.rb b/cucumber/features/step_definitions/torified_misc.rb
index 7112776a..7ccdb227 100644
--- a/cucumber/features/step_definitions/torified_misc.rb
+++ b/cucumber/features/step_definitions/torified_misc.rb
@@ -1,3 +1,5 @@
+require 'resolv'
+
When /^I query the whois directory service for "([^"]+)"$/ do |domain|
retry_tor do
@vm_execute_res = $vm.execute("whois '#{domain}'", :user => LIVE_USER)
@@ -9,10 +11,18 @@ When /^I query the whois directory service for "([^"]+)"$/ do |domain|
end
end
-When /^I wget "([^"]+)" to stdout(?:| with the '([^']+)' options)$/ do |url, options|
- arguments = "-O - '#{url}'"
- arguments = "#{options} #{arguments}" if options
+When /^I wget "([^"]+)" to stdout(?:| with the '([^']+)' options)$/ do |target, options|
retry_tor do
+ if target == "some Tails mirror"
+ host = 'dl.amnesia.boum.org'
+ address = Resolv.new.getaddresses(host).sample
+ puts "Resolved #{host} to #{address}"
+ url = "http://#{address}/tails/stable/"
+ else
+ url = target
+ end
+ arguments = "-O - '#{url}'"
+ arguments = "#{options} #{arguments}" if options
@vm_execute_res = $vm.execute("wget #{arguments}", :user => LIVE_USER)
if @vm_execute_res.failure?
raise "wget:ing #{url} with options #{options} failed with:\n" +
diff --git a/cucumber/features/step_definitions/totem.rb b/cucumber/features/step_definitions/totem.rb
index 72698dde..a5b88d14 100644
--- a/cucumber/features/step_definitions/totem.rb
+++ b/cucumber/features/step_definitions/totem.rb
@@ -1,23 +1,24 @@
Given /^I create sample videos$/ do
- @shared_video_dir_on_host = "#{$config["TMPDIR"]}/shared_video_dir"
- @shared_video_dir_on_guest = "/tmp/shared_video_dir"
- FileUtils.mkdir_p(@shared_video_dir_on_host)
- add_after_scenario_hook { FileUtils.rm_r(@shared_video_dir_on_host) }
+ @video_dir_on_host = "#{$config["TMPDIR"]}/video_dir"
+ FileUtils.mkdir_p(@video_dir_on_host)
+ add_after_scenario_hook { FileUtils.rm_r(@video_dir_on_host) }
fatal_system("avconv -loop 1 -t 30 -f image2 " +
- "-i 'features/images/TailsBootSplash.png' " +
+ "-i 'features/images/USBTailsLogo.png' " +
"-an -vcodec libx264 -y " +
'-filter:v "crop=in_w-mod(in_w\,2):in_h-mod(in_h\,2)" ' +
- "'#{@shared_video_dir_on_host}/video.mp4' >/dev/null 2>&1")
+ "'#{@video_dir_on_host}/video.mp4' >/dev/null 2>&1")
end
-Given /^I setup a filesystem share containing sample videos$/ do
- $vm.add_share(@shared_video_dir_on_host, @shared_video_dir_on_guest)
+Given /^I plug and mount a USB drive containing sample videos$/ do
+ @video_dir_on_guest = share_host_files(
+ Dir.glob("#{@video_dir_on_host}/*")
+ )
end
Given /^I copy the sample videos to "([^"]+)" as user "([^"]+)"$/ do |destination, user|
- for video_on_host in Dir.glob("#{@shared_video_dir_on_host}/*.mp4") do
+ for video_on_host in Dir.glob("#{@video_dir_on_host}/*.mp4") do
video_name = File.basename(video_on_host)
- src_on_guest = "#{@shared_video_dir_on_guest}/#{video_name}"
+ src_on_guest = "#{@video_dir_on_guest}/#{video_name}"
dst_on_guest = "#{destination}/#{video_name}"
step "I copy \"#{src_on_guest}\" to \"#{dst_on_guest}\" as user \"amnesia\""
end
@@ -32,7 +33,7 @@ When /^I close Totem$/ do
end
Then /^I can watch a WebM video over HTTPs$/ do
- test_url = 'https://webm.html5.org/test.webm'
+ test_url = 'https://tails.boum.org/lib/test_suite/test.webm'
recovery_on_failure = Proc.new do
step 'I close Totem'
end
diff --git a/cucumber/features/step_definitions/unsafe_browser.rb b/cucumber/features/step_definitions/unsafe_browser.rb
index b8c04983..160279ca 100644
--- a/cucumber/features/step_definitions/unsafe_browser.rb
+++ b/cucumber/features/step_definitions/unsafe_browser.rb
@@ -1,6 +1,11 @@
-When /^I see and accept the Unsafe Browser start verification$/ do
+When /^I see and accept the Unsafe Browser start verification(?:| in the "([^"]+)" locale)$/ do |locale|
@screen.wait('GnomeQuestionDialogIcon.png', 30)
- @screen.type(Sikuli::Key.ESC)
+ if ['ar_EG.utf8', 'fa_IR'].include?(locale)
+ # Take into account button ordering in RTL languages
+ @screen.type(Sikuli::Key.LEFT + Sikuli::Key.ENTER)
+ else
+ @screen.type(Sikuli::Key.RIGHT + Sikuli::Key.ENTER)
+ end
end
def supported_torbrowser_languages
@@ -8,7 +13,8 @@ def supported_torbrowser_languages
File.read(localization_descriptions).split("\n").map do |line|
# The line will be of the form "xx:YY:..." or "xx-YY:YY:..."
first, second = line.sub('-', '_').split(':')
- candidates = ["#{first}_#{second}.utf8", "#{first}.utf8",
+ candidates = ["#{first}_#{second}.UTF-8", "#{first}_#{second}.utf8",
+ "#{first}.UTF-8", "#{first}.utf8",
"#{first}_#{second}", first]
when_not_found = Proc.new { raise "Could not find a locale for '#{line}'" }
candidates.find(when_not_found) do |candidate|
@@ -19,12 +25,12 @@ end
Then /^I start the Unsafe Browser in the "([^"]+)" locale$/ do |loc|
step "I run \"LANG=#{loc} LC_ALL=#{loc} sudo unsafe-browser\" in GNOME Terminal"
- step "I see and accept the Unsafe Browser start verification"
+ step "I see and accept the Unsafe Browser start verification in the \"#{loc}\" locale"
end
Then /^the Unsafe Browser works in all supported languages$/ do
failed = Array.new
- supported_torbrowser_languages.each do |lang|
+ supported_torbrowser_languages.sample(3).each do |lang|
step "I start the Unsafe Browser in the \"#{lang}\" locale"
begin
step "the Unsafe Browser has started"
@@ -85,7 +91,7 @@ Then /^the Unsafe Browser has only Firefox's default bookmarks configured$/ do
assert_equal(5, mozilla_uris_counter,
"Unexpected number (#{mozilla_uris_counter}) of mozilla " \
"bookmarks")
- assert_equal(3, places_uris_counter,
+ assert_equal(2, places_uris_counter,
"Unexpected number (#{places_uris_counter}) of places " \
"bookmarks")
@screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
@@ -108,7 +114,7 @@ Then /^I can start the Unsafe Browser again$/ do
end
Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do
- socks_proxy = 'c' # Alt+c for socks proxy
+ socks_proxy = 'C' # Alt+Shift+c for socks proxy
no_proxy = 'y' # Alt+y for no proxy
proxies = [[no_proxy, nil, nil]]
socksport_lines =
@@ -120,7 +126,7 @@ Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do
proxies.each do |proxy_type, proxy_host, proxy_port|
@screen.hide_cursor
- # Open proxy settings and select manual proxy configuration
+ # Open proxy settings
@screen.click('UnsafeBrowserMenuButton.png')
@screen.wait_and_click('UnsafeBrowserPreferencesButton.png', 10)
@screen.wait_and_click('UnsafeBrowserAdvancedSettingsButton.png', 10)
@@ -129,20 +135,25 @@ Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do
@screen.click(hit) if hit == 'UnsafeBrowserNetworkTab.png'
@screen.wait_and_click('UnsafeBrowserNetworkTabSettingsButton.png', 10)
@screen.wait_and_click('UnsafeBrowserProxySettingsWindow.png', 10)
- @screen.type("m", Sikuli::KeyModifier.ALT)
- # Configure the proxy
- @screen.type(proxy_type, Sikuli::KeyModifier.ALT) # Select correct proxy type
- @screen.type(proxy_host + Sikuli::Key.TAB + proxy_port) if proxy_type != no_proxy
+ # Ensure the desired proxy configuration
+ if proxy_type == no_proxy
+ @screen.type(proxy_type, Sikuli::KeyModifier.ALT)
+ @screen.wait('UnsafeBrowserNoProxySelected.png', 10)
+ else
+ @screen.type("M", Sikuli::KeyModifier.ALT)
+ @screen.type(proxy_type, Sikuli::KeyModifier.ALT)
+ @screen.type(proxy_host + Sikuli::Key.TAB + proxy_port)
+ end
# Close settings
@screen.click('UnsafeBrowserProxySettingsOkButton.png')
@screen.waitVanish('UnsafeBrowserProxySettingsWindow.png', 10)
# Test that the proxy settings work as they should
- step "I open the address \"https://check.torproject.org\" in the Unsafe Browser"
+ step 'I open Tails homepage in the Unsafe Browser'
if proxy_type == no_proxy
- @screen.wait('UnsafeBrowserTorCheckFail.png', 60)
+ step 'Tails homepage loads in the Unsafe Browser'
else
@screen.wait('UnsafeBrowserProxyRefused.png', 60)
end
@@ -162,7 +173,11 @@ Then /^the Unsafe Browser has no proxy configured$/ do
end
Then /^the Unsafe Browser complains that no DNS server is configured$/ do
- @screen.wait("UnsafeBrowserDNSError.png", 30)
+ assert_not_nil(
+ Dogtail::Application.new('zenity')
+ .child(roleName: 'label')
+ .text['No DNS server was obtained']
+ )
end
Then /^I configure the Unsafe Browser to check for updates more frequently$/ do
diff --git a/cucumber/features/step_definitions/untrusted_partitions.rb b/cucumber/features/step_definitions/untrusted_partitions.rb
index 43453b2f..603c8b4f 100644
--- a/cucumber/features/step_definitions/untrusted_partitions.rb
+++ b/cucumber/features/step_definitions/untrusted_partitions.rb
@@ -27,7 +27,7 @@ Given /^I create an? ([[:alnum:]]+) partition( labeled "([^"]+)")? with an? ([[:
$vm.storage.disk_mkpartfs(name, parttype, fstype, opts)
end
-Given /^I cat an ISO of the Tails image to disk "([^"]+)"$/ do |name|
+Given /^I write the Tails ISO image to disk "([^"]+)"$/ do |name|
src_disk = {
:path => TAILS_ISO,
:opts => {
@@ -55,7 +55,7 @@ end
Then /^Tails Greeter has( not)? detected a persistence partition$/ do |no_persistence|
expecting_persistence = no_persistence.nil?
@screen.find('TailsGreeter.png')
- found_persistence = ! @screen.exists('TailsGreeterPersistence.png').nil?
+ found_persistence = ! @screen.exists('TailsGreeterPersistencePassphrase.png').nil?
assert_equal(expecting_persistence, found_persistence,
"Persistence is unexpectedly#{no_persistence} enabled")
end
diff --git a/cucumber/features/step_definitions/usb.rb b/cucumber/features/step_definitions/usb.rb
index 76f94d2f..e030f68e 100644
--- a/cucumber/features/step_definitions/usb.rb
+++ b/cucumber/features/step_definitions/usb.rb
@@ -48,6 +48,14 @@ def persistent_volumes_mountpoints
$vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split
end
+def recover_from_upgrader_failure
+ $vm.execute('killall tails-upgrade-frontend tails-upgrade-frontend-wrapper zenity')
+ # Remove unnecessary sleep for retry
+ $vm.execute_successfully('sed -i "/^sleep 30$/d" ' +
+ '/usr/local/bin/tails-upgrade-frontend-wrapper')
+ $vm.spawn('tails-upgrade-frontend-wrapper', user: LIVE_USER)
+end
+
Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to|
$vm.storage.clone_to_new_disk(from, to)
end
@@ -65,66 +73,105 @@ Given /^the computer is set to boot in UEFI mode$/ do
@os_loader = 'UEFI'
end
+def tails_installer_selected_device
+ @installer.child('Target Device:', roleName: 'label').parent
+ .child('', roleName: 'combo box', recursive: false).name
+end
+
+def tails_installer_is_device_selected?(name)
+ device = $vm.disk_dev(name)
+ tails_installer_selected_device[/#{device}\d*$/]
+end
+
+def tails_installer_match_status(pattern)
+ @installer.child('', roleName: 'text').text[pattern]
+end
+
class UpgradeNotSupported < StandardError
end
def usb_install_helper(name)
- @screen.wait('USBTailsLogo.png', 10)
- if @screen.exists("USBCannotUpgrade.png")
+ if tails_installer_match_status(/It is impossible to upgrade the device .+ #{$vm.disk_dev(name)}\d* /)
raise UpgradeNotSupported
end
- @screen.wait_and_click('USBCreateLiveUSB.png', 10)
- @screen.wait('USBCreateLiveUSBConfirmWindow.png', 10)
- @screen.wait_and_click('USBCreateLiveUSBConfirmYes.png', 10)
- @screen.wait('USBInstallationComplete.png', 30*60)
+ assert(tails_installer_is_device_selected?(name))
+ begin
+ @installer.button('Install Tails').click
+ @installer.child('Question', roleName: 'alert').button('Yes').click
+ try_for(30*60) do
+ @installer
+ .child('Information', roleName: 'alert')
+ .child('Installation complete!', roleName: 'label')
+ true
+ end
+ rescue FindFailed => e
+ path = $vm.execute_successfully('ls -1 /tmp/tails-installer-*').stdout.chomp
+ debug_log("Tails Installer debug log:\n" + $vm.file_content(path))
+ raise e
+ end
end
-When /^I start Tails Installer$/ do
- step 'I start "TailsInstaller" via the GNOME "Tails" applications menu'
- @screen.wait('USBCloneAndInstall.png', 30)
+When /^I start Tails Installer in "([^"]+)" mode$/ do |mode|
+ step 'I run "export DEBUG=1 ; tails-installer-launcher" in GNOME Terminal'
+ installer_launcher = Dogtail::Application.new('tails-installer-launcher')
+ .child('Tails Installer', roleName: 'frame')
+ # Sometimes Dogtail will find the button and click it before it is
+ # shown (searchShowingOnly is not perfect) which generally means
+ # clicking somewhere on the Terminal => the click is lost *and* the
+ # installer does no go to the foreground. So let's wait a bit extra.
+ sleep 3
+ installer_launcher.button(mode).click
+ @installer = Dogtail::Application.new('tails-installer')
+ @installer.child('Tails Installer', roleName: 'frame')
+ # ... and something similar (for consecutive steps) again.
+ sleep 3
+ $vm.focus_window('Tails Installer')
end
-When /^I start Tails Installer in "([^"]+)" mode$/ do |mode|
- step 'I start Tails Installer'
- case mode
- when 'Clone & Install'
- @screen.wait_and_click('USBCloneAndInstall.png', 10)
- when 'Clone & Upgrade'
- @screen.wait_and_click('USBCloneAndUpgrade.png', 10)
- when 'Upgrade from ISO'
- @screen.wait_and_click('USBUpgradeFromISO.png', 10)
- else
- raise "Unsupported mode '#{mode}'"
+Then /^Tails Installer detects that a device is too small$/ do
+ try_for(10) do
+ tails_installer_match_status(/^The device .* is too small to install Tails/)
end
end
-Then /^Tails Installer detects that a device is too small$/ do
- @screen.wait('TailsInstallerTooSmallDevice.png', 10)
+When /^I am told that the destination device cannot be upgraded$/ do
+ try_for(10) do
+ tails_installer_match_status(/^It is impossible to upgrade the device/)
+ end
end
-When /^I "Clone & Install" Tails to USB drive "([^"]+)"$/ do |name|
- step 'I start Tails Installer in "Clone & Install" mode'
- usb_install_helper(name)
+When /^I am suggested to do a "Install by cloning"$/ do
+ try_for(10) do
+ tails_installer_match_status(
+ /You should instead use "Install by cloning" to upgrade Tails/
+ )
+ end
end
-When /^I "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
- step 'I start Tails Installer in "Clone & Upgrade" mode'
- usb_install_helper(name)
+Then /^a suitable USB device is (?:still )?not found$/ do
+ @installer.child(
+ 'No device suitable to install Tails could be found', roleName: 'label'
+ )
end
-When /^I try a "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name|
- begin
- step "I \"Clone & Upgrade\" Tails to USB drive \"#{name}\""
- rescue UpgradeNotSupported
- # this is what we expect
- else
- raise "The USB installer should not succeed"
+Then /^(no|the "([^"]+)") USB drive is selected$/ do |mode, name|
+ try_for(30) do
+ if mode == 'no'
+ tails_installer_selected_device == ''
+ else
+ tails_installer_is_device_selected?(name)
+ end
end
end
-When /^I try to "Upgrade from ISO" USB drive "([^"]+)"$/ do |name|
+When /^I "([^"]*)" Tails to USB drive "([^"]+)"$/ do |mode, name|
+ step "I start Tails Installer in \"#{mode}\" mode"
+ usb_install_helper(name)
+end
+
+When /^I fail to "([^"]*)" Tails to USB drive "([^"]+)"$/ do |mode, name|
begin
- step "I do a \"Upgrade from ISO\" on USB drive \"#{name}\""
+ step "I \"#{mode}\" Tails to USB drive \"#{name}\""
rescue UpgradeNotSupported
# this is what we expect
else
@@ -132,35 +179,20 @@ When /^I try to "Upgrade from ISO" USB drive "([^"]+)"$/ do |name|
end
end
-When /^I am suggested to do a "Clone & Install"$/ do
- @screen.find("USBCannotUpgrade.png")
-end
-
-When /^I am told that the destination device cannot be upgraded$/ do
- @screen.find("USBCannotUpgrade.png")
-end
-
-Given /^I setup a filesystem share containing the Tails ISO$/ do
- shared_iso_dir_on_host = "#{$config["TMPDIR"]}/shared_iso_dir"
- @shared_iso_dir_on_guest = "/tmp/shared_iso_dir"
- FileUtils.mkdir_p(shared_iso_dir_on_host)
- FileUtils.cp(TAILS_ISO, shared_iso_dir_on_host)
- add_after_scenario_hook { FileUtils.rm_r(shared_iso_dir_on_host) }
- $vm.add_share(shared_iso_dir_on_host, @shared_iso_dir_on_guest)
+Given /^I plug and mount a USB drive containing the Tails ISO$/ do
+ iso_dir = share_host_files(TAILS_ISO)
+ @iso_path = "#{iso_dir}/#{File.basename(TAILS_ISO)}"
end
When /^I do a "Upgrade from ISO" on USB drive "([^"]+)"$/ do |name|
step 'I start Tails Installer in "Upgrade from ISO" mode'
- @screen.wait('USBUseLiveSystemISO.png', 10)
- match = @screen.find('USBUseLiveSystemISO.png')
- @screen.click(match.getCenter.offset(0, match.h*2))
- @screen.wait('USBSelectISO.png', 10)
- @screen.wait_and_click('GnomeFileDiagHome.png', 10)
+ @installer.child('Use existing Live system ISO:', roleName: 'label')
+ .parent.button('(None)').click
+ file_chooser = @installer.child('Select a File', roleName: 'file chooser')
@screen.type("l", Sikuli::KeyModifier.CTRL)
- @screen.wait('GnomeFileDiagTypeFilename.png', 10)
- iso = "#{@shared_iso_dir_on_guest}/#{File.basename(TAILS_ISO)}"
- @screen.type(iso)
- @screen.wait_and_click('GnomeFileDiagOpenButton.png', 10)
+ # The only visible text element will be the path entry
+ file_chooser.child(roleName: 'text').typeText(@iso_path + '\n')
+ file_chooser.button('Open').click
usb_install_helper(name)
end
@@ -174,13 +206,22 @@ Given /^I enable all persistence presets$/ do
@screen.type(Sikuli::Key.TAB + Sikuli::Key.SPACE)
end
@screen.wait_and_click('PersistenceWizardSave.png', 10)
+ @screen.wait('PersistenceWizardDone.png', 60)
+ @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
+end
+
+When /^I disable the first persistence preset$/ do
+ step 'I start "Configure persistent volume" via GNOME Activities Overview'
+ @screen.wait('PersistenceWizardPresets.png', 300)
+ @screen.type(Sikuli::Key.SPACE)
+ @screen.wait_and_click('PersistenceWizardSave.png', 10)
@screen.wait('PersistenceWizardDone.png', 30)
@screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT)
end
Given /^I create a persistent partition$/ do
- step 'I start "ConfigurePersistentVolume" via the GNOME "Tails" applications menu'
- @screen.wait('PersistenceWizardStart.png', 20)
+ step 'I start "Configure persistent volume" via GNOME Activities Overview'
+ @screen.wait('PersistenceWizardStart.png', 60)
@screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER)
@screen.wait('PersistenceWizardPresets.png', 300)
step "I enable all persistence presets"
@@ -254,10 +295,9 @@ Then /^the running Tails is installed on USB drive "([^"]+)"$/ do |target_name|
end
Then /^the ISO's Tails is installed on USB drive "([^"]+)"$/ do |target_name|
- iso = "#{@shared_iso_dir_on_guest}/#{File.basename(TAILS_ISO)}"
iso_root = "/mnt/iso"
$vm.execute("mkdir -p #{iso_root}")
- $vm.execute("mount -o loop #{iso} #{iso_root}")
+ $vm.execute("mount -o loop #{@iso_path} #{iso_root}")
tails_is_installed_helper(target_name, iso_root, "isolinux")
$vm.execute("umount #{iso_root}")
end
@@ -274,10 +314,10 @@ Then /^a Tails persistence partition exists on USB drive "([^"]+)"$/ do |name|
# The LUKS container may already be opened, e.g. by udisks after
# we've run tails-persistence-setup.
- c = $vm.execute("ls -1 /dev/mapper/")
+ c = $vm.execute("ls -1 --hide 'control' /dev/mapper/")
if c.success?
for candidate in c.stdout.split("\n")
- luks_info = $vm.execute("cryptsetup status #{candidate}")
+ luks_info = $vm.execute("cryptsetup status '#{candidate}'")
if luks_info.success? and luks_info.stdout.match("^\s+device:\s+#{dev}$")
luks_dev = "/dev/mapper/#{candidate}"
break
@@ -300,7 +340,7 @@ Then /^a Tails persistence partition exists on USB drive "([^"]+)"$/ do |name|
mount_dir = "/mnt/#{name}"
$vm.execute("mkdir -p #{mount_dir}")
- c = $vm.execute("mount #{luks_dev} #{mount_dir}")
+ c = $vm.execute("mount '#{luks_dev}' #{mount_dir}")
assert(c.success?,
"Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'")
@@ -310,12 +350,9 @@ Then /^a Tails persistence partition exists on USB drive "([^"]+)"$/ do |name|
end
Given /^I enable persistence$/ do
- @screen.wait('TailsGreeterPersistence.png', 10)
- @screen.type(Sikuli::Key.SPACE)
- @screen.wait('TailsGreeterPersistencePassphrase.png', 10)
- match = @screen.find('TailsGreeterPersistencePassphrase.png')
- @screen.click(match.getCenter.offset(match.w*2, match.h/2))
- @screen.type(@persistence_password)
+ @screen.wait_and_click('TailsGreeterPersistencePassphrase.png', 10)
+ @screen.type(@persistence_password + Sikuli::Key.ENTER)
+ @screen.wait('TailsGreeterPersistenceUnlocked.png', 30)
end
def tails_persistence_enabled?
@@ -325,13 +362,21 @@ def tails_persistence_enabled?
'test "$TAILS_PERSISTENCE_ENABLED" = true').success?
end
-Given /^all persistence presets(| from the old Tails version) are enabled$/ do |old_tails|
+Given /^all persistence presets(| from the old Tails version)(| but the first one) are enabled$/ do |old_tails, except_first|
+ assert(old_tails.empty? || except_first.empty?, "Unsupported case.")
try_for(120, :msg => "Persistence is disabled") do
tails_persistence_enabled?
end
+ unexpected_mounts = Array.new
# Check that all persistent directories are mounted
if old_tails.empty?
expected_mounts = persistent_mounts
+ if ! except_first.empty?
+ first_expected_mount_source = expected_mounts.keys[0]
+ first_expected_mount_destination = expected_mounts[first_expected_mount_source]
+ expected_mounts.delete(first_expected_mount_source)
+ unexpected_mounts = [first_expected_mount_destination]
+ end
else
assert_not_nil($remembered_persistence_mounts)
expected_mounts = $remembered_persistence_mounts
@@ -341,17 +386,16 @@ Given /^all persistence presets(| from the old Tails version) are enabled$/ do |
assert(mount.include?("on #{dir} "),
"Persistent directory '#{dir}' is not mounted")
end
+ for dir in unexpected_mounts do
+ assert(! mount.include?("on #{dir} "),
+ "Persistent directory '#{dir}' is mounted")
+ end
end
Given /^persistence is disabled$/ do
assert(!tails_persistence_enabled?, "Persistence is enabled")
end
-Given /^I enable read-only persistence$/ do
- step "I enable persistence"
- @screen.wait_and_click('TailsGreeterPersistenceReadOnly.png', 10)
-end
-
def boot_device
# Approach borrowed from
# config/chroot_local_includes/lib/live/config/998-permissions
@@ -374,23 +418,21 @@ end
Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name|
bus = bus.downcase
case bus
- when "ide"
+ when "sata"
expected_bus = "ata"
else
expected_bus = bus
end
assert_equal(expected_bus, boot_device_type)
actual_dev = boot_device
- # The boot partition differs between a "normal" install using the
- # USB installer and isohybrid installations
- expected_dev_normal = $vm.disk_dev(name) + "1"
- expected_dev_isohybrid = $vm.disk_dev(name) + "4"
- assert(actual_dev == expected_dev_normal ||
- actual_dev == expected_dev_isohybrid,
+ # The boot partition differs between an using Tails installer and
+ # isohybrids. There's also a strange case isohybrids are thought to
+ # be booting from the "raw" device, and not a partition of it
+ # (#10504).
+ expected_devs = ['', '1', '4'].map { |e| $vm.disk_dev(name) + e }
+ assert(expected_devs.include?(actual_dev),
"We are running from device #{actual_dev}, but for #{bus} drive " +
- "'#{name}' we expected to run from either device " +
- "#{expected_dev_normal} (when installed via the USB installer) " +
- "or #{expected_dev_isohybrid} (when installed from an isohybrid)")
+ "'#{name}' we expected to run from one of #{expected_devs}")
end
Then /^the boot device has safe access rights$/ do
@@ -493,6 +535,12 @@ When /^I write some files expected to persist$/ do
end
end
+When /^I write some dotfile expected to persist$/ do
+ assert($vm.execute("touch /live/persistence/TailsData_unlocked/dotfiles/.XXX_persist",
+ :user => LIVE_USER).success?,
+ "Could not create a file in the dotfiles persistence.")
+end
+
When /^I remove some files expected to persist$/ do
persistent_mounts.each do |_, dir|
owner = $vm.execute("stat -c %U #{dir}").stdout.chomp
@@ -529,6 +577,14 @@ Then /^the expected persistent files(| created with the old Tails version) are p
end
end
+Then /^the expected persistent dotfile is present in the filesystem$/ do
+ expected_dirs = persistent_dirs
+ assert($vm.execute("test -L #{expected_dirs['dotfiles']}/.XXX_persist").success?,
+ "Could not find expected persistent dotfile link.")
+ assert($vm.execute("test -e $(readlink -f #{expected_dirs['dotfiles']}/.XXX_persist)").success?,
+ "Could not find expected persistent dotfile link target.")
+end
+
Then /^only the expected files are present on the persistence partition on USB drive "([^"]+)"$/ do |name|
assert(!$vm.is_running?)
disk = {
@@ -568,8 +624,8 @@ Then /^only the expected files are present on the persistence partition on USB d
end
When /^I delete the persistent partition$/ do
- step 'I start "DeletePersistentVolume" via the GNOME "Tails" applications menu'
- @screen.wait("PersistenceWizardDeletionStart.png", 20)
+ step 'I start "Delete persistent volume" via GNOME Activities Overview'
+ @screen.wait("PersistenceWizardDeletionStart.png", 120)
@screen.type(" ")
@screen.wait("PersistenceWizardDone.png", 120)
end
@@ -583,14 +639,109 @@ Given /^I create a ([[:alpha:]]+) label on disk "([^"]+)"$/ do |type, name|
$vm.storage.disk_mklabel(name, type)
end
-Then /^a suitable USB device is (?:still )?not found$/ do
- @screen.wait("TailsInstallerNoQEMUHardDisk.png", 30)
+Given /^the file system changes introduced in version (.+) are (not )?present(?: in the (\S+) Browser's chroot)?$/ do |version, not_present, chroot_browser|
+ assert_equal('1.1~test', version)
+ upgrade_applied = not_present.nil?
+ chroot_browser = "#{chroot_browser.downcase}-browser" if chroot_browser
+ changes = [
+ {
+ filesystem: :rootfs,
+ path: 'some_new_file',
+ status: :added,
+ new_content: <<-EOF
+Some content
+ EOF
+ },
+ {
+ filesystem: :rootfs,
+ path: 'etc/amnesia/version',
+ status: :modified,
+ new_content: <<-EOF
+#{version} - 20380119
+ffffffffffffffffffffffffffffffffffffffff
+live-build: 3.0.5+really+is+2.0.12-0.tails2
+live-boot: 4.0.2-1
+live-config: 4.0.4-1
+ EOF
+ },
+ {
+ filesystem: :rootfs,
+ path: 'etc/os-release',
+ status: :modified,
+ new_content: <<-EOF
+TAILS_PRODUCT_NAME="Tails"
+TAILS_VERSION_ID="#{version}"
+ EOF
+ },
+ {
+ filesystem: :rootfs,
+ path: 'usr/share/common-licenses/BSD',
+ status: :removed
+ },
+ {
+ filesystem: :medium,
+ path: 'utils/linux/syslinux',
+ status: :removed
+ },
+ ]
+ changes.each do |change|
+ case change[:filesystem]
+ when :rootfs
+ path = '/'
+ path += "var/lib/#{chroot_browser}/chroot/" if chroot_browser
+ path += change[:path]
+ when :medium
+ path = '/lib/live/mount/medium/' + change[:path]
+ else
+ raise "Unknown filesysten '#{change[:filesystem]}'"
+ end
+ case change[:status]
+ when :removed
+ assert_equal(!upgrade_applied, $vm.file_exist?(path))
+ when :added
+ assert_equal(upgrade_applied, $vm.file_exist?(path))
+ if upgrade_applied && change[:new_content]
+ assert_equal(change[:new_content], $vm.file_content(path))
+ end
+ when :modified
+ assert($vm.file_exist?(path))
+ if upgrade_applied
+ assert_not_nil(change[:new_content])
+ assert_equal(change[:new_content], $vm.file_content(path))
+ end
+ else
+ raise "Unknown status '#{change[:status]}'"
+ end
+ end
+end
+
+Then /^I am proposed to install an incremental upgrade to version (.+)$/ do |version|
+ recovery_proc = Proc.new do
+ recover_from_upgrader_failure
+ end
+ failure_pic = 'TailsUpgraderFailure.png'
+ success_pic = "TailsUpgraderUpgradeTo#{version}.png"
+ retry_tor(recovery_proc) do
+ match, _ = @screen.waitAny([success_pic, failure_pic], 2*60)
+ assert_equal(success_pic, match)
+ end
end
-Then /^the "(?:[^"]+)" USB drive is selected$/ do
- @screen.wait("TailsInstallerQEMUHardDisk.png", 30)
+When /^I agree to install the incremental upgrade$/ do
+ @screen.click('TailsUpgraderUpgradeNowButton.png')
end
-Then /^no USB drive is selected$/ do
- @screen.wait("TailsInstallerNoQEMUHardDisk.png", 30)
+Then /^I can successfully install the incremental upgrade to version (.+)$/ do |version|
+ step 'I agree to install the incremental upgrade'
+ recovery_proc = Proc.new do
+ recover_from_upgrader_failure
+ step "I am proposed to install an incremental upgrade to version #{version}"
+ step 'I agree to install the incremental upgrade'
+ end
+ failure_pic = 'TailsUpgraderFailure.png'
+ success_pic = "TailsUpgraderDone.png"
+ retry_tor(recovery_proc) do
+ match, _ = @screen.waitAny([success_pic, failure_pic], 2*60)
+ assert_equal(success_pic, match)
+ end
end