diff options
Diffstat (limited to 'features')
124 files changed, 0 insertions, 9686 deletions
diff --git a/features/DebianLive/apt.feature b/features/DebianLive/apt.feature deleted file mode 100644 index 3b71b6c2..00000000 --- a/features/DebianLive/apt.feature +++ /dev/null @@ -1,14 +0,0 @@ -@product -Feature: Doing a trivial d-i install - As a normal user - I should be able to do a text-mode install - - Scenario Outline: Install Debian and boot to login prompt - Given I have installed <type> Debian - And I start the computer - Then I wait for a Login Prompt - - Examples: - | type | - | Minimal | - | Gnome Desktop | diff --git a/features/README-sikuli-cucumber b/features/README-sikuli-cucumber deleted file mode 100644 index 24d90aaf..00000000 --- a/features/README-sikuli-cucumber +++ /dev/null @@ -1,13 +0,0 @@ -Key names: - - http://doc.sikuli.org/keys.html - -Running the thing by hand: - - ./bin/lvc/run_test_suite --view --iso /var/lib/libvirt/images/debian-8.3.0-amd64-i386-netinst.iso --old-iso /var/lib/libvirt/images/debian-8.3.0-amd64-i386-netinst.iso DebianLive/apt.feature - -FIXME - I added a git repo to: - - /var/lib/jenkins/workspace/lvc_debian-installer_jessie_standard_apt - -in order to shut-up Tail's script that looks for git repos to populate some variables -- need to strip that out, or make it overridable. diff --git a/features/apt.feature b/features/apt.feature deleted file mode 100644 index ac778c26..00000000 --- a/features/apt.feature +++ /dev/null @@ -1,36 +0,0 @@ -#10497: wait_until_tor_is_working -@product @fragile -Feature: Installing packages through APT - As a Tails user - when I set an administration password in Tails Greeter - I should be able to install packages using APT and Synaptic - and all Internet traffic should flow only through Tor. - - Background: - Given a computer - And I capture all network traffic - And I start the computer - And the computer boots DebianLive7 - And I set sudo password "asdf" - And I log in to a new session - And GNOME has started - And Tor is ready - And all notifications have disappeared - And available upgrades have been checked - And I save the state so the background can be restored next scenario - - Scenario: APT sources are configured correctly - Then the only hosts in APT sources are "ftp.us.debian.org,http.debian.net,ftp.debian.org,security.debian.org" - - #10496: apt-get scenarios are fragile - @check_tor_leaks @fragile - Scenario: Install packages using apt - When I update APT using apt - Then I should be able to install a package using apt - - #10441: Synaptic test is fragile - @check_tor_leaks @fragile - Scenario: Install packages using Synaptic - When I start Synaptic - And I update APT using Synaptic - Then I should be able to install a package using Synaptic diff --git a/features/build.feature b/features/build.feature deleted file mode 100644 index 74d314de..00000000 --- a/features/build.feature +++ /dev/null @@ -1,212 +0,0 @@ -@source -Feature: custom APT sources to build branches - As a Tails developer, when I build Tails, I'd be happy if - the proper APT sources were automatically picked depending - on which Git branch I am working on. - - Scenario: build from an untagged stable branch where the config/APT_overlays.d directory is empty - Given I am working on the stable base branch - And the last version mentioned in debian/changelog is 1.0 - And Tails 1.0 has not been released yet - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the 'stable' suite - - Scenario: build from an untagged stable branch where config/APT_overlays.d is not empty - Given I am working on the stable base branch - And the last version mentioned in debian/changelog is 1.0 - And Tails 1.0 has not been released yet - And config/APT_overlays.d contains 'feature-foo' - And config/APT_overlays.d contains 'bugfix-bar' - When I successfully run tails-custom-apt-sources - Then I should see the 'stable' suite - And I should see the 'feature-foo' suite - And I should see the 'bugfix-bar' suite - But I should not see the '1.0' suite - - Scenario: build from a tagged stable branch where the config/APT_overlays.d directory is empty - Given Tails 0.10 has been released - And the last version mentioned in debian/changelog is 0.10 - And I am working on the stable base branch - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the '0.10' suite - - Scenario: build from a tagged stable branch where config/APT_overlays.d is not empty - Given Tails 0.10 has been released - And the last version mentioned in debian/changelog is 0.10 - And I am working on the stable base branch - And config/APT_overlays.d contains 'feature-foo' - When I run tails-custom-apt-sources - Then it should fail - - Scenario: build from a bugfix branch without overlays for a stable release - Given Tails 0.10 has been released - And the last version mentioned in debian/changelog is 0.10.1 - And Tails 0.10.1 has not been released yet - And I am working on the bugfix/disable_gdomap branch based on stable - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the 'stable' suite - - Scenario: build from a bugfix branch with overlays for a stable release - Given Tails 0.10 has been released - And the last version mentioned in debian/changelog is 0.10.1 - And Tails 0.10.1 has not been released yet - And I am working on the bugfix/disable_gdomap branch based on stable - And config/APT_overlays.d contains 'bugfix-disable-gdomap' - And config/APT_overlays.d contains 'bugfix-bar' - When I successfully run tails-custom-apt-sources - Then I should see the 'stable' suite - And I should see the 'bugfix-disable-gdomap' suite - And I should see the 'bugfix-bar' suite - But I should not see the '0.10' suite - - Scenario: build from an untagged testing branch where the config/APT_overlays.d directory is empty - Given I am working on the testing base branch - And the last version mentioned in debian/changelog is 0.11 - And Tails 0.11 has not been released yet - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see the 'testing' suite - And I should not see the '0.11' suite - And I should not see the 'feature-foo' suite - And I should not see the 'bugfix-bar' suite - - Scenario: build from an untagged testing branch where config/APT_overlays.d is not empty - Given I am working on the testing base branch - And the last version mentioned in debian/changelog is 0.11 - And Tails 0.11 has not been released yet - And config/APT_overlays.d contains 'feature-foo' - And config/APT_overlays.d contains 'bugfix-bar' - When I successfully run tails-custom-apt-sources - Then I should see the 'testing' suite - And I should see the 'feature-foo' suite - And I should see the 'bugfix-bar' suite - But I should not see the '0.11' suite - - Scenario: build from a tagged testing branch where the config/APT_overlays.d directory is empty - Given I am working on the testing base branch - And the last version mentioned in debian/changelog is 0.11 - And Tails 0.11 has been released - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the '0.11' suite - - Scenario: build from a tagged testing branch where config/APT_overlays.d is not empty - Given I am working on the testing base branch - And the last version mentioned in debian/changelog is 0.11 - And Tails 0.11 has been released - And config/APT_overlays.d contains 'feature-foo' - When I run tails-custom-apt-sources - Then it should fail - - Scenario: build a release candidate from a tagged testing branch - Given I am working on the testing base branch - And Tails 0.11 has been released - And the last version mentioned in debian/changelog is 0.12~rc1 - And Tails 0.12-rc1 has been tagged - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the '0.12-rc1' suite - - Scenario: build a release candidate from a tagged testing branch where config/APT_overlays.d is not empty - Given I am working on the testing base branch - And Tails 0.11 has been released - And the last version mentioned in debian/changelog is 0.12~rc1 - And Tails 0.12-rc1 has been tagged - And config/APT_overlays.d contains 'bugfix-bar' - When I run tails-custom-apt-sources - Then it should fail - - Scenario: build from the devel branch without overlays - Given I am working on the devel base branch - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the 'devel' suite - - Scenario: build from the devel branch with overlays - Given I am working on the devel base branch - And config/APT_overlays.d contains 'feature-foo' - And config/APT_overlays.d contains 'bugfix-bar' - When I successfully run tails-custom-apt-sources - Then I should see the 'devel' suite - And I should see the 'feature-foo' suite - And I should see the 'bugfix-bar' suite - - Scenario: build from the feature/jessie branch without overlays - Given I am working on the feature/jessie base branch - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the 'feature-jessie' suite - - Scenario: build from the feature/jessie branch with overlays - Given I am working on the feature/jessie base branch - And config/APT_overlays.d contains 'feature-7756-reintroduce-whisperback' - When I successfully run tails-custom-apt-sources - Then I should see the 'feature-jessie' suite - And I should see the 'feature-7756-reintroduce-whisperback' suite - - Scenario: build from the experimental branch - Given I am working on the experimental branch based on devel - And config/APT_overlays.d contains 'feature-foo' - And config/APT_overlays.d contains 'bugfix-bar' - When I successfully run tails-custom-apt-sources - Then I should see the 'devel' suite - And I should see the 'feature-foo' suite - And I should see the 'bugfix-bar' suite - - Scenario: build from a feature branch with overlays based on devel - Given I am working on the feature/icedove branch based on devel - And config/APT_overlays.d contains 'feature-icedove' - And config/APT_overlays.d contains 'bugfix-bar' - When I successfully run tails-custom-apt-sources - Then I should see the 'devel' suite - And I should see the 'feature-icedove' suite - And I should see the 'bugfix-bar' suite - - Scenario: build from a feature branch without overlays based on devel - Given I am working on the feature/icedove branch based on devel - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the 'devel' suite - - Scenario: build from a feature branch with overlays based on feature/jessie - Given I am working on the feature/7756-reintroduce-whisperback branch based on feature/jessie - And config/APT_overlays.d contains 'feature-7756-reintroduce-whisperback' - And config/APT_overlays.d contains 'bugfix-bar' - When I successfully run tails-custom-apt-sources - Then I should see the 'feature-jessie' suite - And I should see the 'feature-7756-reintroduce-whisperback' suite - And I should see the 'bugfix-bar' suite - - Scenario: build from a feature branch without overlays based on feature/jessie - Given I am working on the feature/icedove branch based on feature/jessie - And the config/APT_overlays.d directory is empty - When I successfully run tails-custom-apt-sources - Then I should see only the 'feature-jessie' suite - - Scenario: build from a feature branch based on devel with dots in its name - Given I am working on the feature/live-boot-3.x branch based on devel - And config/APT_overlays.d contains 'feature-live-boot-3.x' - When I successfully run tails-custom-apt-sources - Then I should see the 'devel' suite - And I should see the 'feature-live-boot-3.x' suite - - Scenario: build from a branch that has no config/APT_overlays.d directory - Given I am working on the stable base branch - And the config/APT_overlays.d directory does not exist - When I run tails-custom-apt-sources - Then it should fail - - Scenario: build from a branch that has no config/base_branch file - Given I am working on the stable base branch - And the config/base_branch file does not exist - When I run tails-custom-apt-sources - Then it should fail - - Scenario: build from a branch where config/base_branch is empty - Given I am working on the stable base branch - And the config/base_branch file is empty - When I run tails-custom-apt-sources - Then it should fail diff --git a/features/checks.feature b/features/checks.feature deleted file mode 100644 index 24d35943..00000000 --- a/features/checks.feature +++ /dev/null @@ -1,107 +0,0 @@ -@product -Feature: Various checks - - Scenario: AppArmor is enabled and has enforced profiles - Given I have started Tails from DVD without network and logged in - Then AppArmor is enabled - And some AppArmor profiles are enforced - - Scenario: A screenshot is taken when the PRINTSCREEN key is pressed - Given I have started Tails from DVD without network and logged in - And there is no screenshot in the live user's Pictures directory - When I press the "PRINTSCREEN" key - Then a screenshot is saved to the live user's Pictures directory - - Scenario: VirtualBox guest modules are available - Given I have started Tails from DVD without network and logged in - When Tails has booted a 64-bit kernel - Then the VirtualBox guest modules are available - - Scenario: The shipped Tails OpenPGP keys are up-to-date - Given I have started Tails from DVD without network and logged in - Then the OpenPGP keys shipped with Tails will be valid for the next 3 months - - Scenario: The Tails Debian repository key is up-to-date - Given I have started Tails from DVD without network and logged in - Then the shipped Debian repository key will be valid for the next 3 months - - @doc @fragile - Scenario: The "Report an Error" launcher will open the support documentation - Given I have started Tails from DVD without network and logged in - And the network is plugged - And Tor is ready - And all notifications have disappeared - When I double-click the Report an Error launcher on the desktop - Then the support documentation page opens in Tor Browser - - Scenario: The live user is setup correctly - Given I have started Tails from DVD without network and logged in - Then the live user has been setup by live-boot - And the live user is a member of only its own group and "audio cdrom dialout floppy video plugdev netdev scanner lp lpadmin vboxsf" - And the live user owns its home dir and it has normal permissions - - @fragile - Scenario: No initial network - Given I have started Tails from DVD without network and logged in - And I wait between 30 and 60 seconds - Then the Tor Status icon tells me that Tor is not usable - When the network is plugged - Then Tor is ready - And the Tor Status icon tells me that Tor is usable - And all notifications have disappeared - And the time has synced - - @fragile - Scenario: The 'Tor is ready' notification is shown when Tor has bootstrapped - Given I have started Tails from DVD without network and logged in - And the network is plugged - When I see the 'Tor is ready' notification - Then Tor is ready - - @fragile - Scenario: The tor process should be confined with Seccomp - Given I have started Tails from DVD without network and logged in - And the network is plugged - And Tor is ready - Then the running process "tor" is confined with Seccomp in filter mode - - @fragile - Scenario: No unexpected network services - Given I have started Tails from DVD without network and logged in - When the network is plugged - And Tor is ready - Then no unexpected services are listening for network connections - - Scenario: The emergency shutdown applet can shutdown Tails - Given I have started Tails from DVD without network and logged in - When I request a shutdown using the emergency shutdown applet - Then Tails eventually shuts down - - Scenario: The emergency shutdown applet can reboot Tails - Given I have started Tails from DVD without network and logged in - When I request a reboot using the emergency shutdown applet - Then Tails eventually restarts - - Scenario: tails-debugging-info does not leak information - Given I have started Tails from DVD without network and logged in - Then tails-debugging-info is not susceptible to symlink attacks - - Scenario: Tails shuts down on DVD boot medium removal - Given I have started Tails from DVD without network and logged in - When I eject the boot medium - Then Tails eventually shuts down - - #10720 - @fragile - Scenario: Tails shuts down on USB boot medium removal - Given I have started Tails without network from a USB drive without a persistent partition and logged in - When I eject the boot medium - Then Tails eventually shuts down - - Scenario: The Tails Greeter "disable all networking" option disables networking within Tails - Given I have started Tails from DVD without network and stopped at Tails Greeter's login screen - And I enable more Tails Greeter options - And I disable all networking in the Tails Greeter - And I log in to a new session - And the Tails desktop is ready - Then no network interfaces are enabled diff --git a/features/config/defaults.yml b/features/config/defaults.yml deleted file mode 100644 index 9c312146..00000000 --- a/features/config/defaults.yml +++ /dev/null @@ -1,36 +0,0 @@ -CAPTURE: false -CAPTURE_ALL: false -MAX_NEW_TOR_CIRCUIT_RETRIES: 10 -PAUSE_ON_FAIL: false -SIKULI_RETRY_FINDFAILED: false -TMPDIR: "/tmp/DebianToaster" - -Unsafe_SSH_private_key: | - -----BEGIN RSA PRIVATE KEY----- - MIIEowIBAAKCAQEAvMUNgUUM/kyuo26m+Xw7igG6zgGFMFbS3u8m5StGsJOn7zLi - J8P5Mml/R+4tdOS6owVU4RaZTPsNZZK/ClYmOPhmNvJ04pVChk2DZ8AARg/TANj3 - qjKs3D+MeKbk1bt6EsA55kgGsTUky5Ti8cc2Wna25jqjagIiyM822PGG9mmI6/zL - YR6QLUizNaciXrRM3Q4R4sQkEreVlHeonPEiGUs9zx0swCpLtPM5UIYte1PVHgkw - ePsU6vM8UqVTK/VwtLLgLanXnsMFuzq7DTAXPq49+XSFNq4JlxbEF6+PQXZvYZ5N - eW00Gq7NSpPP8uoHr6f1J+mMxxnM85jzYtRx+QIDAQABAoIBAA8Bs1MlhCTrP67q - awfGYo1UGd+qq0XugREL/hGV4SbEdkNDzkrO/46MaHv1aVOzo0q2b8r9Gu7NvoDm - q51Mv/kjdizEFZq1tvYqT1n+H4dyVpnopbe4E5nmy2oECokbQFchRPkTnMSVrvko - OupxpdaHPX8MBlW1GcLRBlE00j/gfK1SXX5rcxkF5EHVND1b6iHddTPearDbU8yr - wga1XO6WeohAYzqmGtMD0zk6lOk0LmnTNG6WvHiFTAc/0yTiKub6rNOIEMS/82+V - l437H0hKcIN/7/mf6FpqRNPJTuhOVFf+L4G/ZQ8zHoMGVIbhuTiIPqZ/KMu3NaUF - R634jckCgYEA+jJ31hom/d65LfxWPkmiSkNTEOTfjbfcgpfc7sS3enPsYnfnmn5L - O3JJzAKShSVP8NVuPN5Mg5FGp9QLKrN3kV6QWQ3EnqeW748DXMU6zKGJQ5wo7ZVm - w2DhJ/3PAuBTL/5X4mjPQL+dr86Aq2JBDC7LHJs40I8O7UbhnsdMxKcCgYEAwSXc - 3znAkAX8o2g37RiAl36HdONgxr2eaGK7OExp03pbKmoISw6bFbVpicBy6eTytn0A - 2PuFcBKJRfKrViHyiE8UfAJ31JbUaxpg4bFF6UEszN4CmgKS8fnwEe1aX0qSjvkE - NQSuhN5AfykXY/1WVIaWuC500uB7Ow6M16RDyF8CgYEAqFTeNYlg5Hs+Acd9SukF - rItBTuN92P5z+NUtyuNFQrjNuK5Nf68q9LL/Hag5ZiVldHZUddVmizpp3C6Y2MDo - WEDUQ2Y0/D1rGoAQ1hDIb7bbAEcHblmPSzJaKirkZV4B+g9Yl7bGghypfggkn6o6 - c3TkKLnybrdhZpjC4a3bY48CgYBnWRYdD27c4Ycz/GDoaZLs/NQIFF5FGVL4cdPR - pPl/IdpEEKZNWwxaik5lWedjBZFlWe+pKrRUqmZvWhCZruJyUzYXwM5Tnz0b7epm - +Q76Z1hMaoKj27q65UyymvkfQey3ucCpic7D45RJNjiA1R5rbfSZqqnx6BGoIPn1 - rLxkKwKBgDXiWeUKJCydj0NfHryGBkQvaDahDE3Yigcma63b8vMZPBrJSC4SGAHJ - NWema+bArbaF0rKVJpwvpkZWGcr6qRn94Ts0kJAzR+VIVTOjB9sVwdxjadwWHRs5 - kKnpY0tnSF7hyVRwN7GOsNDJEaFjCW7k4+55D2ZNBy2iN3beW8CZ - -----END RSA PRIVATE KEY----- -Unsafe_SSH_public_key: = "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC8xQ2BRQz+TK6jbqb5fDuKAbrOAYUwVtLe7yblK0awk6fvMuInw/kyaX9H7i105LqjBVThFplM+w1lkr8KViY4+GY28nTilUKGTYNnwABGD9MA2PeqMqzcP4x4puTVu3oSwDnmSAaxNSTLlOLxxzZadrbmOqNqAiLIzzbY8Yb2aYjr/MthHpAtSLM1pyJetEzdDhHixCQSt5WUd6ic8SIZSz3PHSzAKku08zlQhi17U9UeCTB4+xTq8zxSpVMr9XC0suAtqdeewwW7OrsNMBc+rj35dIU2rgmXFsQXr49Bdm9hnk15bTQars1Kk8/y6gevp/Un6YzHGczzmPNi1HH5 amnesia@amnesia" diff --git a/features/dhcp.feature b/features/dhcp.feature deleted file mode 100644 index 18874dbf..00000000 --- a/features/dhcp.feature +++ /dev/null @@ -1,22 +0,0 @@ -@product @fragile -Feature: Getting a DHCP lease without leaking too much information - As a Tails user - when I connect to a network with a DHCP server - I should be able to connect to the Internet - and the hostname should not have been leaked on the network. - - Background: - Given I have started Tails from DVD without network and logged in - And I capture all network traffic - And the network is plugged - And Tor is ready - And all notifications have disappeared - And available upgrades have been checked - - Scenario: Getting a DHCP lease with the default NetworkManager connection - Then the hostname should not have been leaked on the network - - Scenario: Getting a DHCP lease with a manually configured NetworkManager connection - When I add a wired DHCP NetworkManager connection called "manually-added-con" - And I switch to the "manually-added-con" NetworkManager connection - Then the hostname should not have been leaked on the network diff --git a/features/domains/default.xml b/features/domains/default.xml deleted file mode 100644 index f1004dcf..00000000 --- a/features/domains/default.xml +++ /dev/null @@ -1,59 +0,0 @@ -<domain type='kvm' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> - <name>DebianToaster</name> - <memory unit='KiB'>1310720</memory> - <currentMemory unit='KiB'>1310720</currentMemory> - <vcpu>1</vcpu> - <os> - <type arch='x86_64' machine='pc-0.15'>hvm</type> - <boot dev='cdrom'/> - </os> - <features> - <acpi/> - <apic/> - <pae/> - </features> - <cpu mode='host-model'/> - <clock offset='utc'/> - <on_poweroff>destroy</on_poweroff> - <on_reboot>restart</on_reboot> - <on_crash>restart</on_crash> - <devices> - <emulator>/usr/bin/qemu-system-x86_64</emulator> - <disk type='file' device='cdrom'> - <driver name='qemu' type='raw'/> - <source file=''/> - <target dev='hdc' bus='ide'/> - <readonly/> - </disk> - <controller type='usb' index='0' model='ich9-ehci1'/> - <controller type='usb' index='0' model='ich9-uhci1'> - <master startport='0'/> - </controller> - <controller type='ide' index='0'/> - <controller type='virtio-serial' index='0'/> - <interface type='network'> - <mac address='52:54:00:ac:dd:ee'/> - <source network='DebianToasterNet'/> - <model type='virtio'/> - <link state='up'/> - </interface> - <serial type='tcp'> - <source mode="bind" host='127.0.0.1' service='1337'/> - <target port='0'/> - </serial> - <input type='tablet' bus='usb'/> - <channel type='spicevmc'> - <target type='virtio' name='com.redhat.spice.0'/> - </channel> - <graphics type='spice' port='-1' tlsPort='-1' autoport='yes'> - <mouse mode='client'/> - </graphics> - <sound model='ich6'/> - <video> - <model type='qxl' ram='65536' vram='131072' vgamem='16384' heads='1'/> - <address type='pci' domain='0x0000' bus='0x00' slot='0x02' function='0x0'/> - </video> - <memballoon model='virtio'/> - </devices> -</domain> - diff --git a/features/domains/default_net.xml b/features/domains/default_net.xml deleted file mode 100644 index fd2966eb..00000000 --- a/features/domains/default_net.xml +++ /dev/null @@ -1,13 +0,0 @@ -<network> - <name>DebianToasterNet</name> - <forward mode='nat'/> - <bridge name='virbr10' stp='on' delay='0' /> - <ip address='10.2.1.1' netmask='255.255.255.0'> - <dhcp> - <range start='10.2.1.2' end='10.2.1.254' /> - <host mac="52:54:00:ac:dd:ee" name="amnesia" ip="10.2.1.2" /> - </dhcp> - </ip> - <ip family="ipv6" address="fc00::1" prefix="7" /> -</network> - diff --git a/features/domains/disk.xml b/features/domains/disk.xml deleted file mode 100644 index 8193fea3..00000000 --- a/features/domains/disk.xml +++ /dev/null @@ -1,5 +0,0 @@ -<disk type='file' device='disk'> - <driver name='qemu' type=''/> - <source file=''/> - <target dev='' bus=''/> -</disk> diff --git a/features/domains/fs_share.xml b/features/domains/fs_share.xml deleted file mode 100644 index 718755ea..00000000 --- a/features/domains/fs_share.xml +++ /dev/null @@ -1,6 +0,0 @@ -<filesystem type='mount' accessmode='passthrough'> - <driver type='path' wrpolicy='immediate'/> - <source dir=''/> - <target dir=''/> - <readonly/> -</filesystem> diff --git a/features/domains/storage_pool.xml b/features/domains/storage_pool.xml deleted file mode 100644 index ce0a6915..00000000 --- a/features/domains/storage_pool.xml +++ /dev/null @@ -1,6 +0,0 @@ -<pool type="dir"> - <name>DebianToasterStorage</name> - <target> - <path></path> - </target> -</pool> diff --git a/features/domains/volume.xml b/features/domains/volume.xml deleted file mode 100644 index 702d5a05..00000000 --- a/features/domains/volume.xml +++ /dev/null @@ -1,14 +0,0 @@ -<volume> - <name></name> - <allocation>0</allocation> - <capacity unit="b"></capacity> - <target> - <path></path> - <format type='qcow2'/> - <permissions> - <owner></owner> - <group></group> - <mode>0664</mode> - </permissions> - </target> -</volume> diff --git a/features/electrum.feature b/features/electrum.feature deleted file mode 100644 index e4e8d749..00000000 --- a/features/electrum.feature +++ /dev/null @@ -1,34 +0,0 @@ -#10497: wait_until_tor_is_working -#10720: Tails Installer freezes on Jenkins -@product @check_tor_leaks @fragile -Feature: Electrum Bitcoin client - As a Tails user - I might want to use a Bitcoin client - And all Internet traffic should flow only through Tor - - Scenario: A warning will be displayed if Electrum is not persistent - Given I have started Tails from DVD and logged in and the network is connected - When I start Electrum through the GNOME menu - But persistence for "electrum" is not enabled - Then I see a warning that Electrum is not persistent - - Scenario: Using a persistent Electrum configuration - Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in - And the network is plugged - And Tor is ready - And available upgrades have been checked - And all notifications have disappeared - Then persistence for "electrum" is enabled - When I start Electrum through the GNOME menu - But a bitcoin wallet is not present - Then I am prompted to create a new wallet - When I create a new bitcoin wallet - Then a bitcoin wallet is present - And I see the main Electrum client window - And Electrum successfully connects to the network - And I shutdown Tails and wait for the computer to power off - Given I start Tails from USB drive "__internal" and I login with persistence enabled - When I start Electrum through the GNOME menu - And a bitcoin wallet is present - And I see the main Electrum client window - Then Electrum successfully connects to the network diff --git a/features/encryption.feature b/features/encryption.feature deleted file mode 100644 index 52cc152d..00000000 --- a/features/encryption.feature +++ /dev/null @@ -1,35 +0,0 @@ -@product -Feature: Encryption and verification using GnuPG - As a Tails user - I want to be able to easily encrypt and sign messages using GnuPG - And decrypt and verify GnuPG blocks - - Background: - Given I have started Tails from DVD without network and logged in - And I generate an OpenPGP key named "test" with password "asdf" - - #10992 - @fragile - Scenario: Encryption and decryption using Tails OpenPGP Applet - When I type a message into gedit - And I encrypt the message using my OpenPGP key - Then I can decrypt the encrypted message - - #10992 - @fragile - Scenario: Signing and verification using Tails OpenPGP Applet - When I type a message into gedit - And I sign the message using my OpenPGP key - Then I can verify the message's signature - - #10991 - @fragile - Scenario: Encryption/signing and decryption/verification using Tails OpenPGP Applet - When I type a message into gedit - And I both encrypt and sign the message using my OpenPGP key - Then I can decrypt and verify the encrypted message - - Scenario: Symmetric encryption and decryption using Tails OpenPGP Applet - When I type a message into gedit - And I symmetrically encrypt the message with password "asdf" - Then I can decrypt the encrypted message diff --git a/features/evince.feature b/features/evince.feature deleted file mode 100644 index 6fd27eca..00000000 --- a/features/evince.feature +++ /dev/null @@ -1,66 +0,0 @@ -@product -Feature: Using Evince - As a Tails user - I want to view and print PDF files in Evince - And AppArmor should prevent Evince from doing dangerous things - - #10994 - @fragile - Scenario: I can view and print a PDF file stored in /usr/share - Given I have started Tails from DVD without network and logged in - When I open "/usr/share/cups/data/default-testpage.pdf" with Evince - Then I see "CupsTestPage.png" after at most 20 seconds - And I can print the current document to "/home/amnesia/output.pdf" - - #10994 - @fragile - Scenario: I can view and print a PDF file stored in non-persistent /home/amnesia - Given I have started Tails from DVD without network and logged in - And I copy "/usr/share/cups/data/default-testpage.pdf" to "/home/amnesia" as user "amnesia" - When I open "/home/amnesia/default-testpage.pdf" with Evince - Then I see "CupsTestPage.png" after at most 20 seconds - And I can print the current document to "/home/amnesia/output.pdf" - - Scenario: I cannot view a PDF file stored in non-persistent /home/amnesia/.gnupg - Given I have started Tails from DVD without network and logged in - And I copy "/usr/share/cups/data/default-testpage.pdf" to "/home/amnesia/.gnupg" as user "amnesia" - Then the file "/home/amnesia/.gnupg/default-testpage.pdf" exists - And the file "/lib/live/mount/overlay/home/amnesia/.gnupg/default-testpage.pdf" exists - And the file "/live/overlay/home/amnesia/.gnupg/default-testpage.pdf" exists - Given I start monitoring the AppArmor log of "/usr/bin/evince" - When I try to open "/home/amnesia/.gnupg/default-testpage.pdf" with Evince - Then I see "EvinceUnableToOpen.png" after at most 10 seconds - And AppArmor has denied "/usr/bin/evince" from opening "/home/amnesia/.gnupg/default-testpage.pdf" - When I close Evince - Given I restart monitoring the AppArmor log of "/usr/bin/evince" - When I try to open "/lib/live/mount/overlay/home/amnesia/.gnupg/default-testpage.pdf" with Evince - Then I see "EvinceUnableToOpen.png" after at most 10 seconds - And AppArmor has denied "/usr/bin/evince" from opening "/lib/live/mount/overlay/home/amnesia/.gnupg/default-testpage.pdf" - When I close Evince - Given I restart monitoring the AppArmor log of "/usr/bin/evince" - When I try to open "/live/overlay/home/amnesia/.gnupg/default-testpage.pdf" with Evince - Then I see "EvinceUnableToOpen.png" after at most 10 seconds - # Due to our AppArmor aliases, /live/overlay will be treated - # as /lib/live/mount/overlay. - And AppArmor has denied "/usr/bin/evince" from opening "/lib/live/mount/overlay/home/amnesia/.gnupg/default-testpage.pdf" - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: I can view and print a PDF file stored in persistent /home/amnesia/Persistent - Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in - And I copy "/usr/share/cups/data/default-testpage.pdf" to "/home/amnesia/Persistent" as user "amnesia" - Then the file "/home/amnesia/Persistent/default-testpage.pdf" exists - When I open "/home/amnesia/Persistent/default-testpage.pdf" with Evince - Then I see "CupsTestPage.png" after at most 20 seconds - And I can print the current document to "/home/amnesia/Persistent/output.pdf" - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: I cannot view a PDF file stored in persistent /home/amnesia/.gnupg - Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in - And I copy "/usr/share/cups/data/default-testpage.pdf" to "/home/amnesia/.gnupg" as user "amnesia" - Then the file "/home/amnesia/.gnupg/default-testpage.pdf" exists - Given I start monitoring the AppArmor log of "/usr/bin/evince" - And I try to open "/home/amnesia/.gnupg/default-testpage.pdf" with Evince - Then I see "EvinceUnableToOpen.png" after at most 10 seconds - And AppArmor has denied "/usr/bin/evince" from opening "/home/amnesia/.gnupg/default-testpage.pdf" diff --git a/features/icedove.feature b/features/icedove.feature deleted file mode 100644 index e05f024c..00000000 --- a/features/icedove.feature +++ /dev/null @@ -1,39 +0,0 @@ -@product @check_tor_leaks @fragile -Feature: Icedove email client - As a Tails user - I may want to use an email client - - Background: - Given I have started Tails from DVD and logged in and the network is connected - When I start "Icedove" via the GNOME "Internet" applications menu - And Icedove has started - And I have not configured an email account - Then I am prompted to setup an email account - - Scenario: Icedove defaults to using IMAP - Then IMAP is the default protocol - - Scenario: Adblock is not enabled within Icedove - Given I cancel setting up an email account - When I open Icedove's Add-ons Manager - And I click the extensions tab - Then I see that Adblock is not installed in Icedove - - Scenario: Enigmail is configured to use the correct keyserver - Given I cancel setting up an email account - And I go into Enigmail's preferences - When I click Enigmail's keyserver tab - Then I see that Enigmail is configured to use the correct keyserver - When I click Enigmail's advanced tab - Then I see that Enigmail is configured to use the correct SOCKS proxy - - Scenario: Torbirdy is configured to use Tor - Given I cancel setting up an email account - And I open Torbirdy's preferences - Then I see that Torbirdy is configured to use Tor - - Scenario: Icedove will work over Tor - Given I cancel setting up an email account - And I open Torbirdy's preferences - When I test Torbirdy's proxy settings - Then Torbirdy's proxy test is successful diff --git a/features/images/DebianInstallerCountrySelection.png b/features/images/DebianInstallerCountrySelection.png Binary files differdeleted file mode 100644 index fe130993..00000000 --- a/features/images/DebianInstallerCountrySelection.png +++ /dev/null diff --git a/features/images/DebianInstallerDomainPrompt.png b/features/images/DebianInstallerDomainPrompt.png Binary files differdeleted file mode 100644 index d7fca5f8..00000000 --- a/features/images/DebianInstallerDomainPrompt.png +++ /dev/null diff --git a/features/images/DebianInstallerHostnamePrompt.png b/features/images/DebianInstallerHostnamePrompt.png Binary files differdeleted file mode 100644 index f1325c8d..00000000 --- a/features/images/DebianInstallerHostnamePrompt.png +++ /dev/null diff --git a/features/images/DebianInstallerHttpProxy.png b/features/images/DebianInstallerHttpProxy.png Binary files differdeleted file mode 100644 index 04b3e13e..00000000 --- a/features/images/DebianInstallerHttpProxy.png +++ /dev/null diff --git a/features/images/DebianInstallerInstallingBaseSystem.png b/features/images/DebianInstallerInstallingBaseSystem.png Binary files differdeleted file mode 100644 index 0b9e1c7f..00000000 --- a/features/images/DebianInstallerInstallingBaseSystem.png +++ /dev/null diff --git a/features/images/DebianInstallerMirrorCountry.png b/features/images/DebianInstallerMirrorCountry.png Binary files differdeleted file mode 100644 index 9b4df5ea..00000000 --- a/features/images/DebianInstallerMirrorCountry.png +++ /dev/null diff --git a/features/images/DebianInstallerNameOfUser.png b/features/images/DebianInstallerNameOfUser.png Binary files differdeleted file mode 100644 index e37c7ec4..00000000 --- a/features/images/DebianInstallerNameOfUser.png +++ /dev/null diff --git a/features/images/DebianInstallerNoDiskFound.png b/features/images/DebianInstallerNoDiskFound.png Binary files differdeleted file mode 100644 index 671f52d6..00000000 --- a/features/images/DebianInstallerNoDiskFound.png +++ /dev/null diff --git a/features/images/DebianInstallerPartitioningMethod.png b/features/images/DebianInstallerPartitioningMethod.png Binary files differdeleted file mode 100644 index 9e44360e..00000000 --- a/features/images/DebianInstallerPartitioningMethod.png +++ /dev/null diff --git a/features/images/DebianInstallerPartitioningScheme.png b/features/images/DebianInstallerPartitioningScheme.png Binary files differdeleted file mode 100644 index 97105b62..00000000 --- a/features/images/DebianInstallerPartitioningScheme.png +++ /dev/null diff --git a/features/images/DebianInstallerRootPassword.png b/features/images/DebianInstallerRootPassword.png Binary files differdeleted file mode 100644 index 27368fd7..00000000 --- a/features/images/DebianInstallerRootPassword.png +++ /dev/null diff --git a/features/images/DebianInstallerSelectDiskToPartition.png b/features/images/DebianInstallerSelectDiskToPartition.png Binary files differdeleted file mode 100644 index 1f14bb1a..00000000 --- a/features/images/DebianInstallerSelectDiskToPartition.png +++ /dev/null diff --git a/features/images/DebianInstallerSelectLangEnglish.png b/features/images/DebianInstallerSelectLangEnglish.png Binary files differdeleted file mode 100644 index 85f848d5..00000000 --- a/features/images/DebianInstallerSelectLangEnglish.png +++ /dev/null diff --git a/features/images/DebianInstallerSelectLangEnglishUK.png b/features/images/DebianInstallerSelectLangEnglishUK.png Binary files differdeleted file mode 100644 index c0da761f..00000000 --- a/features/images/DebianInstallerSelectLangEnglishUK.png +++ /dev/null diff --git a/features/images/DebianInstallerUserPassword.png b/features/images/DebianInstallerUserPassword.png Binary files differdeleted file mode 100644 index bf9964aa..00000000 --- a/features/images/DebianInstallerUserPassword.png +++ /dev/null diff --git a/features/images/DebianLive7BootSplash.png b/features/images/DebianLive7BootSplash.png Binary files differdeleted file mode 100644 index b64353aa..00000000 --- a/features/images/DebianLive7BootSplash.png +++ /dev/null diff --git a/features/images/DebianLive7BootSplashTabMsg.png b/features/images/DebianLive7BootSplashTabMsg.png Binary files differdeleted file mode 100644 index 150830b7..00000000 --- a/features/images/DebianLive7BootSplashTabMsg.png +++ /dev/null diff --git a/features/images/DebianLive7Greeter.png b/features/images/DebianLive7Greeter.png Binary files differdeleted file mode 100644 index f1afaabe..00000000 --- a/features/images/DebianLive7Greeter.png +++ /dev/null diff --git a/features/images/DebianLiveBootSplash.png b/features/images/DebianLiveBootSplash.png Binary files differdeleted file mode 100644 index 11ee1494..00000000 --- a/features/images/DebianLiveBootSplash.png +++ /dev/null diff --git a/features/images/DebianLiveBootSplashTabMsg.png b/features/images/DebianLiveBootSplashTabMsg.png Binary files differdeleted file mode 100644 index cdddaf1d..00000000 --- a/features/images/DebianLiveBootSplashTabMsg.png +++ /dev/null diff --git a/features/images/DebianLoginPromptVT.png b/features/images/DebianLoginPromptVT.png Binary files differdeleted file mode 100644 index ec267820..00000000 --- a/features/images/DebianLoginPromptVT.png +++ /dev/null diff --git a/features/images/d-i8_bootsplash.png b/features/images/d-i8_bootsplash.png Binary files differdeleted file mode 100644 index 086c65cb..00000000 --- a/features/images/d-i8_bootsplash.png +++ /dev/null diff --git a/features/images/d-i_ArchiveMirror.png b/features/images/d-i_ArchiveMirror.png Binary files differdeleted file mode 100644 index 7e53f189..00000000 --- a/features/images/d-i_ArchiveMirror.png +++ /dev/null diff --git a/features/images/d-i_ChooseSoftware.png b/features/images/d-i_ChooseSoftware.png Binary files differdeleted file mode 100644 index 93447158..00000000 --- a/features/images/d-i_ChooseSoftware.png +++ /dev/null diff --git a/features/images/d-i_DesktopTask_No.png b/features/images/d-i_DesktopTask_No.png Binary files differdeleted file mode 100644 index 6dbf9df4..00000000 --- a/features/images/d-i_DesktopTask_No.png +++ /dev/null diff --git a/features/images/d-i_DesktopTask_Yes.png b/features/images/d-i_DesktopTask_Yes.png Binary files differdeleted file mode 100644 index 02cbaa5d..00000000 --- a/features/images/d-i_DesktopTask_Yes.png +++ /dev/null diff --git a/features/images/d-i_F12BootMenu.png b/features/images/d-i_F12BootMenu.png Binary files differdeleted file mode 100644 index 67a21856..00000000 --- a/features/images/d-i_F12BootMenu.png +++ /dev/null diff --git a/features/images/d-i_FinishPartitioning.png b/features/images/d-i_FinishPartitioning.png Binary files differdeleted file mode 100644 index 50396500..00000000 --- a/features/images/d-i_FinishPartitioning.png +++ /dev/null diff --git a/features/images/d-i_GRUBEnterDev.png b/features/images/d-i_GRUBEnterDev.png Binary files differdeleted file mode 100644 index 6df484ed..00000000 --- a/features/images/d-i_GRUBEnterDev.png +++ /dev/null diff --git a/features/images/d-i_GRUB_Debian.png b/features/images/d-i_GRUB_Debian.png Binary files differdeleted file mode 100644 index 3b67cfbe..00000000 --- a/features/images/d-i_GRUB_Debian.png +++ /dev/null diff --git a/features/images/d-i_GRUBdev.png b/features/images/d-i_GRUBdev.png Binary files differdeleted file mode 100644 index 9d554d74..00000000 --- a/features/images/d-i_GRUBdev.png +++ /dev/null diff --git a/features/images/d-i_HttpProxy.png b/features/images/d-i_HttpProxy.png Binary files differdeleted file mode 100644 index 4163a5b3..00000000 --- a/features/images/d-i_HttpProxy.png +++ /dev/null diff --git a/features/images/d-i_InstallComplete.png b/features/images/d-i_InstallComplete.png Binary files differdeleted file mode 100644 index a8564464..00000000 --- a/features/images/d-i_InstallComplete.png +++ /dev/null diff --git a/features/images/d-i_InstallGRUB.png b/features/images/d-i_InstallGRUB.png Binary files differdeleted file mode 100644 index e491fbd1..00000000 --- a/features/images/d-i_InstallGRUB.png +++ /dev/null diff --git a/features/images/d-i_No.png b/features/images/d-i_No.png Binary files differdeleted file mode 100644 index 1108addc..00000000 --- a/features/images/d-i_No.png +++ /dev/null diff --git a/features/images/d-i_ScanCD.png b/features/images/d-i_ScanCD.png Binary files differdeleted file mode 100644 index 5790bcce..00000000 --- a/features/images/d-i_ScanCD.png +++ /dev/null diff --git a/features/images/d-i_SelectBootDev.png b/features/images/d-i_SelectBootDev.png Binary files differdeleted file mode 100644 index 7abef3ec..00000000 --- a/features/images/d-i_SelectBootDev.png +++ /dev/null diff --git a/features/images/d-i_UseNetMirror.png b/features/images/d-i_UseNetMirror.png Binary files differdeleted file mode 100644 index 2b41228b..00000000 --- a/features/images/d-i_UseNetMirror.png +++ /dev/null diff --git a/features/images/d-i_Yes.png b/features/images/d-i_Yes.png Binary files differdeleted file mode 100644 index 17fab5b9..00000000 --- a/features/images/d-i_Yes.png +++ /dev/null diff --git a/features/images/d-i_popcon.png b/features/images/d-i_popcon.png Binary files differdeleted file mode 100644 index ed0ba618..00000000 --- a/features/images/d-i_popcon.png +++ /dev/null diff --git a/features/localization.feature b/features/localization.feature deleted file mode 100644 index f533addf..00000000 --- a/features/localization.feature +++ /dev/null @@ -1,19 +0,0 @@ -@product @fragile -Feature: Localization - As a Tails user - I want Tails to be localized in my native language - And various Tails features should still work - - @doc - Scenario: The Report an Error launcher will open the support documentation in supported non-English locales - Given I have started Tails from DVD without network and stopped at Tails Greeter's login screen - And the network is plugged - And I log in to a new session in German - And Tails seems to have booted normally - And Tor is ready - When I double-click the Report an Error launcher on the desktop - Then the support documentation page opens in Tor Browser - - Scenario: The Unsafe Browser can be used in all languages supported in Tails - Given I have started Tails from DVD and logged in and the network is connected - Then the Unsafe Browser works in all supported languages diff --git a/features/mac_spoofing.feature b/features/mac_spoofing.feature deleted file mode 100644 index 57773721..00000000 --- a/features/mac_spoofing.feature +++ /dev/null @@ -1,71 +0,0 @@ -@product -Feature: Spoofing MAC addresses - In order to not reveal information about the physical location - As a Tails user - I want to be able to control whether my network devices MAC addresses should be spoofed - And I want this feature to fail safe and notify me in case of errors - - Background: - Given I have started Tails from DVD without network and stopped at Tails Greeter's login screen - And I capture all network traffic - And the network is plugged - - @fragile - Scenario: MAC address spoofing is disabled - When I enable more Tails Greeter options - And I disable MAC spoofing in Tails Greeter - And I log in to a new session - And the Tails desktop is ready - And Tor is ready - Then 1 network interface is enabled - And the network device has its default MAC address configured - And the real MAC address was leaked - - @fragile - Scenario: MAC address spoofing is successful - When I log in to a new session - And the Tails desktop is ready - And Tor is ready - Then 1 network interface is enabled - And the network device has a spoofed MAC address configured - And the real MAC address was not leaked - - #10774 - @fragile - Scenario: MAC address spoofing fails and macchanger returns false - Given macchanger will fail by not spoofing and always returns false - When I log in to a new session - And see the "Network card disabled" notification - And the Tails desktop is ready - Then no network interfaces are enabled - And the real MAC address was not leaked - - #10774 - @fragile - Scenario: MAC address spoofing fails and macchanger returns true - Given macchanger will fail by not spoofing and always returns true - When I log in to a new session - And see the "Network card disabled" notification - And the Tails desktop is ready - Then no network interfaces are enabled - And the real MAC address was not leaked - - #10774 - @fragile - Scenario: MAC address spoofing fails and the module is not removed - Given macchanger will fail by not spoofing and always returns true - And no network interface modules can be unloaded - When I log in to a new session - And see the "All networking disabled" notification - And the Tails desktop is ready - Then 1 network interface is enabled - But the MAC spoofing panic mode disabled networking - And the real MAC address was not leaked - - Scenario: The MAC address is not leaked when booting Tails - Given a computer - And I capture all network traffic - When I start the computer - Then the computer boots Tails - And no network interfaces are enabled - And the real MAC address was not leaked diff --git a/features/mat.feature b/features/mat.feature deleted file mode 100644 index e492b0fb..00000000 --- a/features/mat.feature +++ /dev/null @@ -1,13 +0,0 @@ -@product -Feature: Metadata Anonymization Toolkit - As a Tails user - I want to be able to remove leaky metadata from documents and media files - - # In this feature we cannot restore from snapshots since it's - # incompatible with filesystem shares. - - Scenario: MAT can clean a PDF file - Given a computer - And I setup a filesystem share containing a sample PDF - And I start Tails from DVD with network unplugged and I login - Then MAT can clean some sample PDF file diff --git a/features/misc_files/sample.pdf b/features/misc_files/sample.pdf Binary files differdeleted file mode 100644 index d0cc9502..00000000 --- a/features/misc_files/sample.pdf +++ /dev/null diff --git a/features/misc_files/sample.tex b/features/misc_files/sample.tex deleted file mode 100644 index 043faaec..00000000 --- a/features/misc_files/sample.tex +++ /dev/null @@ -1,8 +0,0 @@ -\documentclass[12pt]{article} -\title{Sample PDF document} -\author{Tails developers} -\date{March 12, 2013} -\begin{document} -\maketitle -Does this PDF still have metadata? -\end{document} diff --git a/features/persistence.feature b/features/persistence.feature deleted file mode 100644 index 13f0af70..00000000 --- a/features/persistence.feature +++ /dev/null @@ -1,55 +0,0 @@ -#10720: Tails Installer freezes on Jenkins -@product @fragile -Feature: Tails persistence - As a Tails user - I want to use Tails persistence feature - - Scenario: Booting Tails from a USB drive with a disabled persistent partition - Given I have started Tails without network from a USB drive with a persistent partition and stopped at Tails Greeter's login screen - When I log in to a new session - Then Tails seems to have booted normally - And Tails is running from USB drive "__internal" - And persistence is disabled - But a Tails persistence partition exists on USB drive "__internal" - - Scenario: Booting Tails from a USB drive with an enabled persistent partition - Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in - Then Tails is running from USB drive "__internal" - And all persistence presets are enabled - And all persistent directories have safe access rights - - @fragile - Scenario: Writing files first to a read/write-enabled persistent partition, and then to a read-only-enabled persistent partition - Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in - And the network is plugged - And Tor is ready - And I take note of which persistence presets are available - When I write some files expected to persist - And I add a wired DHCP NetworkManager connection called "persistent-con" - And I shutdown Tails and wait for the computer to power off - # XXX: The next step succeeds (and the --debug output confirms that it's actually looking for the files) but will fail in a subsequent scenario restoring the same snapshot. This exactly what we want, but why does it work? What is guestfs's behaviour when qcow2 internal snapshots are involved? - Then only the expected files are present on the persistence partition on USB drive "__internal" - Given I start Tails from USB drive "__internal" with network unplugged and I login with read-only persistence enabled - And the network is plugged - And Tor is ready - Then Tails is running from USB drive "__internal" - And the boot device has safe access rights - And all persistence presets are enabled - And I switch to the "persistent-con" NetworkManager connection - And there is no GNOME bookmark for the persistent Tor Browser directory - And I write some files not expected to persist - And I remove some files expected to persist - And I take note of which persistence presets are available - And I shutdown Tails and wait for the computer to power off - Then only the expected files are present on the persistence partition on USB drive "__internal" - - Scenario: Deleting a Tails persistent partition - Given I have started Tails without network from a USB drive with a persistent partition and stopped at Tails Greeter's login screen - And I log in to a new session - Then Tails is running from USB drive "__internal" - And the boot device has safe access rights - And persistence is disabled - But a Tails persistence partition exists on USB drive "__internal" - And all notifications have disappeared - When I delete the persistent partition - Then there is no persistence partition on USB drive "__internal" diff --git a/features/pidgin.feature b/features/pidgin.feature deleted file mode 100644 index cbfddbe3..00000000 --- a/features/pidgin.feature +++ /dev/null @@ -1,135 +0,0 @@ -#10497: wait_until_tor_is_working -@product @fragile -Feature: Chatting anonymously using Pidgin - As a Tails user - when I chat using Pidgin - I should be able to use OTR - And I should be able to persist my Pidgin configuration - And AppArmor should prevent Pidgin from doing dangerous things - And all Internet traffic should flow only through Tor - - @check_tor_leaks - Scenario: Chatting with some friend over XMPP - Given I have started Tails from DVD and logged in and the network is connected - When I start Pidgin through the GNOME menu - Then I see Pidgin's account manager window - When I create my XMPP account - And I close Pidgin's account manager window - Then Pidgin automatically enables my XMPP account - Given my XMPP friend goes online - When I start a conversation with my friend - And I say something to my friend - Then I receive a response from my friend - - @check_tor_leaks - Scenario: Chatting with some friend over XMPP in a multi-user chat - Given I have started Tails from DVD and logged in and the network is connected - When I start Pidgin through the GNOME menu - Then I see Pidgin's account manager window - When I create my XMPP account - And I close Pidgin's account manager window - Then Pidgin automatically enables my XMPP account - When I join some empty multi-user chat - And I clear the multi-user chat's scrollback - And my XMPP friend goes online and joins the multi-user chat - Then I can see that my friend joined the multi-user chat - And I say something to my friend in the multi-user chat - Then I receive a response from my friend in the multi-user chat - - @check_tor_leaks - Scenario: Chatting with some friend over XMPP and with OTR - Given I have started Tails from DVD and logged in and the network is connected - When I start Pidgin through the GNOME menu - Then I see Pidgin's account manager window - When I create my XMPP account - And I close Pidgin's account manager window - Then Pidgin automatically enables my XMPP account - Given my XMPP friend goes online - When I start a conversation with my friend - And I start an OTR session with my friend - Then Pidgin automatically generates an OTR key - And an OTR session was successfully started with my friend - When I say something to my friend - Then I receive a response from my friend - - # 10376 - "the Tor Browser loads the (startup page|Tails roadmap)" step is fragile - # 10443 - OFTC tests are fragile - @check_tor_leaks @fragile - Scenario: Connecting to the #tails IRC channel with the pre-configured account - Given I have started Tails from DVD and logged in and the network is connected - And Pidgin has the expected accounts configured with random nicknames - When I start Pidgin through the GNOME menu - Then I see Pidgin's account manager window - When I activate the "irc.oftc.net" Pidgin account - And I close Pidgin's account manager window - Then Pidgin successfully connects to the "irc.oftc.net" account - And I can join the "#tails" channel on "irc.oftc.net" - When I type "/topic" - And I press the "ENTER" key - Then I see the Tails roadmap URL - When I wait 10 seconds - And I click on the Tails roadmap URL - Then the Tor Browser has started and loaded the Tails roadmap - And the "irc.oftc.net" account only responds to PING and VERSION CTCP requests - - Scenario: Adding a certificate to Pidgin - Given I have started Tails from DVD and logged in and the network is connected - And I start Pidgin through the GNOME menu - And I see Pidgin's account manager window - And I close Pidgin's account manager window - Then I can add a certificate from the "/home/amnesia" directory to Pidgin - - Scenario: Failing to add a certificate to Pidgin - Given I have started Tails from DVD and logged in and the network is connected - When I start Pidgin through the GNOME menu - And I see Pidgin's account manager window - And I close Pidgin's account manager window - Then I cannot add a certificate from the "/home/amnesia/.gnupg" directory to Pidgin - When I close Pidgin's certificate import failure dialog - And I close Pidgin's certificate manager - Then I cannot add a certificate from the "/lib/live/mount/overlay/home/amnesia/.gnupg" directory to Pidgin - When I close Pidgin's certificate import failure dialog - And I close Pidgin's certificate manager - Then I cannot add a certificate from the "/live/overlay/home/amnesia/.gnupg" directory to Pidgin - - #10443 - OFTC tests are fragile - #10720: Tails Installer freezes on Jenkins - @check_tor_leaks @fragile - Scenario: Using a persistent Pidgin configuration - Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in - And Pidgin has the expected accounts configured with random nicknames - And the network is plugged - And Tor is ready - And available upgrades have been checked - And all notifications have disappeared - When I start Pidgin through the GNOME menu - Then I see Pidgin's account manager window - # And I generate an OTR key for the default Pidgin account - And I take note of the configured Pidgin accounts - # And I take note of the OTR key for Pidgin's "irc.oftc.net" account - And I shutdown Tails and wait for the computer to power off - Given a computer - And I start Tails from USB drive "__internal" and I login with persistence enabled - And Pidgin has the expected persistent accounts configured - # And Pidgin has the expected persistent OTR keys - When I start Pidgin through the GNOME menu - Then I see Pidgin's account manager window - When I activate the "irc.oftc.net" Pidgin account - And I close Pidgin's account manager window - Then Pidgin successfully connects to the "irc.oftc.net" account - And I can join the "#tails" channel on "irc.oftc.net" - # Exercise Pidgin AppArmor profile with persistence enabled. - # This should really be in dedicated scenarios, but it would be - # too costly to set up the virtual USB drive with persistence more - # than once in this feature. - Given I start monitoring the AppArmor log of "/usr/bin/pidgin" - Then I cannot add a certificate from the "/home/amnesia/.gnupg" directory to Pidgin - And AppArmor has denied "/usr/bin/pidgin" from opening "/home/amnesia/.gnupg/test.crt" - When I close Pidgin's certificate import failure dialog - And I close Pidgin's certificate manager - Given I restart monitoring the AppArmor log of "/usr/bin/pidgin" - Then I cannot add a certificate from the "/live/persistence/TailsData_unlocked/gnupg" directory to Pidgin - And AppArmor has denied "/usr/bin/pidgin" from opening "/live/persistence/TailsData_unlocked/gnupg/test.crt" - When I close Pidgin's certificate import failure dialog - And I close Pidgin's certificate manager - Then I can add a certificate from the "/home/amnesia" directory to Pidgin diff --git a/features/po.feature b/features/po.feature deleted file mode 100644 index 91b8fd00..00000000 --- a/features/po.feature +++ /dev/null @@ -1,9 +0,0 @@ -@source -Feature: check PO files - As a Tails developer, when I build Tails, I want to make sure - the PO files in use are correct. - - @doc - Scenario: check all PO files - Given I am in the Git branch being tested - Then all the PO files should be correct diff --git a/features/root_access_control.feature b/features/root_access_control.feature deleted file mode 100644 index 569dd2a8..00000000 --- a/features/root_access_control.feature +++ /dev/null @@ -1,33 +0,0 @@ -@product -Feature: Root access control enforcement - As a Tails user - when I set an administration password in Tails Greeter - I can use the password for attaining administrative privileges. - But when I do not set an administration password - I should not be able to attain administration privileges at all. - - Scenario: If an administrative password is set in Tails Greeter the live user should be able to run arbitrary commands with administrative privileges. - Given I set sudo password "asdf" - And I log in to a new session - And Tails Greeter has dealt with the sudo password - Then I should be able to run administration commands as the live user - - Scenario: If no administrative password is set in Tails Greeter the live user should not be able to run arbitrary commands administrative privileges. - Given I have started Tails from DVD without network and logged in - And Tails Greeter has dealt with the sudo password - Then I should not be able to run administration commands as the live user with the "" password - And I should not be able to run administration commands as the live user with the "amnesia" password - And I should not be able to run administration commands as the live user with the "live" password - - Scenario: If an administrative password is set in Tails Greeter the live user should be able to get administrative privileges through PolicyKit - Given I set sudo password "asdf" - And I log in to a new session - And Tails Greeter has dealt with the sudo password - And GNOME has started - And running a command as root with pkexec requires PolicyKit administrator privileges - Then I should be able to run a command as root with pkexec - - Scenario: If no administrative password is set in Tails Greeter the live user should not be able to get administrative privileges through PolicyKit with the standard passwords. - Given I have started Tails from DVD without network and logged in - And running a command as root with pkexec requires PolicyKit administrator privileges - Then I should not be able to run a command as root with pkexec and the standard passwords diff --git a/features/scripts/otr-bot.py b/features/scripts/otr-bot.py deleted file mode 100755 index 0afd15a4..00000000 --- a/features/scripts/otr-bot.py +++ /dev/null @@ -1,206 +0,0 @@ -#!/usr/bin/python -import sys -import jabberbot -import xmpp -import potr -import logging -from argparse import ArgumentParser - -class OtrContext(potr.context.Context): - - def __init__(self, account, peer): - super(OtrContext, self).__init__(account, peer) - - def getPolicy(self, key): - return True - - def inject(self, msg, appdata = None): - mess = appdata["base_reply"] - mess.setBody(msg) - appdata["send_raw_message_fn"](mess) - - -class BotAccount(potr.context.Account): - - def __init__(self, jid, keyFilePath): - protocol = 'xmpp' - max_message_size = 10*1024 - super(BotAccount, self).__init__(jid, protocol, max_message_size) - self.keyFilePath = keyFilePath - - def loadPrivkey(self): - with open(self.keyFilePath, 'rb') as keyFile: - return potr.crypt.PK.parsePrivateKey(keyFile.read())[0] - - -class OtrContextManager: - - def __init__(self, jid, keyFilePath): - self.account = BotAccount(jid, keyFilePath) - self.contexts = {} - - def start_context(self, other): - if not other in self.contexts: - self.contexts[other] = OtrContext(self.account, other) - return self.contexts[other] - - def get_context_for_user(self, other): - return self.start_context(other) - - -class OtrBot(jabberbot.JabberBot): - - PING_FREQUENCY = 60 - - def __init__(self, account, password, otr_key_path, - connect_server = None, log_file = None): - self.__connect_server = connect_server - self.__password = password - self.__log_file = log_file - super(OtrBot, self).__init__(account, password) - self.__otr_manager = OtrContextManager(account, otr_key_path) - self.send_raw_message_fn = super(OtrBot, self).send_message - self.__default_otr_appdata = { - "send_raw_message_fn": self.send_raw_message_fn - } - - def __otr_appdata_for_mess(self, mess): - appdata = self.__default_otr_appdata.copy() - appdata["base_reply"] = mess - return appdata - - # Unfortunately Jabberbot's connect() is not very friendly to - # overriding in subclasses so we have to re-implement it - # completely (copy-paste mostly) in order to add support for using - # an XMPP "Connect Server". - def connect(self): - logging.basicConfig(filename = self.__log_file, - level = logging.DEBUG) - if not self.conn: - conn = xmpp.Client(self.jid.getDomain(), debug=[]) - if self.__connect_server: - try: - conn_server, conn_port = self.__connect_server.split(":", 1) - except ValueError: - conn_server = self.__connect_server - conn_port = 5222 - conres = conn.connect((conn_server, int(conn_port))) - else: - conres = conn.connect() - if not conres: - return None - authres = conn.auth(self.jid.getNode(), self.__password, self.res) - if not authres: - return None - self.conn = conn - self.conn.sendInitPresence() - self.roster = self.conn.Roster.getRoster() - for (handler, callback) in self.handlers: - self.conn.RegisterHandler(handler, callback) - return self.conn - - # Wrap OTR encryption around Jabberbot's most low-level method for - # sending messages. - def send_message(self, mess): - body = str(mess.getBody()) - user = str(mess.getTo().getStripped()) - otrctx = self.__otr_manager.get_context_for_user(user) - if otrctx.state == potr.context.STATE_ENCRYPTED: - otrctx.sendMessage(potr.context.FRAGMENT_SEND_ALL, body, - appdata = self.__otr_appdata_for_mess(mess)) - else: - self.send_raw_message_fn(mess) - - # Wrap OTR decryption around Jabberbot's callback mechanism. - def callback_message(self, conn, mess): - body = str(mess.getBody()) - user = str(mess.getFrom().getStripped()) - otrctx = self.__otr_manager.get_context_for_user(user) - if mess.getType() == "chat": - try: - appdata = self.__otr_appdata_for_mess(mess.buildReply()) - decrypted_body, tlvs = otrctx.receiveMessage(body, - appdata = appdata) - otrctx.processTLVs(tlvs) - except potr.context.NotEncryptedError: - otrctx.authStartV2(appdata = appdata) - return - except (potr.context.UnencryptedMessage, potr.context.NotOTRMessage): - decrypted_body = body - else: - decrypted_body = body - if decrypted_body == None: - return - if mess.getType() == "groupchat": - bot_prefix = self.jid.getNode() + ": " - if decrypted_body.startswith(bot_prefix): - decrypted_body = decrypted_body[len(bot_prefix):] - else: - return - mess.setBody(decrypted_body) - super(OtrBot, self).callback_message(conn, mess) - - # Override Jabberbot quitting on keep alive failure. - def on_ping_timeout(self): - self.__lastping = None - - @jabberbot.botcmd - def ping(self, mess, args): - """Why not just test it?""" - return "pong" - - @jabberbot.botcmd - def say(self, mess, args): - """Unleash my inner parrot""" - return args - - @jabberbot.botcmd - def clear_say(self, mess, args): - """Make me speak in the clear even if we're in an OTR chat""" - self.send_raw_message_fn(mess.buildReply(args)) - return "" - - @jabberbot.botcmd - def start_otr(self, mess, args): - """Make me *initiate* (but not refresh) an OTR session""" - if mess.getType() == "groupchat": - return - return "?OTRv2?" - - @jabberbot.botcmd - def end_otr(self, mess, args): - """Make me gracefully end the OTR session if there is one""" - if mess.getType() == "groupchat": - return - user = str(mess.getFrom().getStripped()) - self.__otr_manager.get_context_for_user(user).disconnect(appdata = - self.__otr_appdata_for_mess(mess.buildReply())) - return "" - -if __name__ == '__main__': - parser = ArgumentParser() - parser.add_argument("account", - help = "the user account, given as user@domain") - parser.add_argument("password", - help = "the user account's password") - parser.add_argument("otr_key_path", - help = "the path to the account's OTR key file") - parser.add_argument("-c", "--connect-server", metavar = 'ADDRESS', - help = "use a Connect Server, given as host[:port] " + - "(port defaults to 5222)") - parser.add_argument("-j", "--auto-join", nargs = '+', metavar = 'ROOMS', - help = "auto-join multi-user chatrooms on start") - parser.add_argument("-l", "--log-file", metavar = 'LOGFILE', - help = "Log to file instead of stderr") - args = parser.parse_args() - otr_bot_opt_args = dict() - if args.connect_server: - otr_bot_opt_args["connect_server"] = args.connect_server - if args.log_file: - otr_bot_opt_args["log_file"] = args.log_file - otr_bot = OtrBot(args.account, args.password, args.otr_key_path, - **otr_bot_opt_args) - if args.auto_join: - for room in args.auto_join: - otr_bot.join_room(room) - otr_bot.serve_forever() diff --git a/features/scripts/vm-execute b/features/scripts/vm-execute deleted file mode 100755 index fc1bf459..00000000 --- a/features/scripts/vm-execute +++ /dev/null @@ -1,52 +0,0 @@ -#!/usr/bin/env ruby - -require 'optparse' -begin - require "#{`git rev-parse --show-toplevel`.chomp}/features/support/helpers/exec_helper.rb" -rescue LoadError => e - raise "This script must be run from within Tails' Git directory." -end -$config = Hash.new - -def debug_log(*args) ; end - -class FakeVM - def get_remote_shell_port - 1337 - end -end - -cmd_opts = { - :spawn => false, - :user => "root" -} - -opt_parser = OptionParser.new do |opts| - opts.banner = "Usage: features/scripts/vm-execute [opts] COMMAND" - opts.separator "" - opts.separator "Runs commands in the VM guest being tested. This script " \ - "must be run from within Tails' Git directory." - opts.separator "" - opts.separator "Options:" - - opts.on("-h", "--help", "Show this message") do - puts opts - exit - end - - opts.on("-u", "--user USER", "Run command as USER") do |user| - cmd_opts[:user] = user - end - - opts.on("-s", "--spawn", - "Run command in non-blocking mode") do |type| - cmd_opts[:spawn] = true - end -end -opt_parser.parse!(ARGV) -cmd = ARGV.join(" ") -c = VMCommand.new(FakeVM.new, cmd, cmd_opts) -puts "Return status: #{c.returncode}" -puts "STDOUT:\n#{c.stdout}" -puts "STDERR:\n#{c.stderr}" -exit c.returncode diff --git a/features/ssh.feature b/features/ssh.feature deleted file mode 100644 index 85289992..00000000 --- a/features/ssh.feature +++ /dev/null @@ -1,31 +0,0 @@ -#10497: wait_until_tor_is_working -#10498: SSH tests are fragile -@product @fragile -Feature: Logging in via SSH - As a Tails user - When I connect to SSH servers on the Internet - all Internet traffic should flow only through Tor - - Background: - Given I have started Tails from DVD and logged in and the network is connected - - @check_tor_leaks - Scenario: Connecting to an SSH server on the Internet - Given I have the SSH key pair for an SSH server - When I connect to an SSH server on the Internet - And I verify the SSH fingerprint for the SSH server - Then I have sucessfully logged into the SSH server - - @check_tor_leaks - Scenario: Connecting to an SSH server on the LAN - Given I have the SSH key pair for an SSH server - And an SSH server is running on the LAN - When I connect to an SSH server on the LAN - Then I am prompted to verify the SSH fingerprint for the SSH server - - @check_tor_leaks - Scenario: Connecting to an SFTP server on the Internet using the GNOME "Connect to Server" feature - Given I have the SSH key pair for an SFTP server - When I connect to an SFTP server on the Internet - And I verify the SSH fingerprint for the SFTP server - Then I successfully connect to the SFTP server diff --git a/features/step_definitions/apt.rb b/features/step_definitions/apt.rb deleted file mode 100644 index c69d2598..00000000 --- a/features/step_definitions/apt.rb +++ /dev/null @@ -1,56 +0,0 @@ -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| - 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 - -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) - 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) - 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) -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) - 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) -end diff --git a/features/step_definitions/browser.rb b/features/step_definitions/browser.rb deleted file mode 100644 index 84ef1d35..00000000 --- a/features/step_definitions/browser.rb +++ /dev/null @@ -1,195 +0,0 @@ -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) -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" -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" -end - -When /^I close the (?:Unsafe|I2P) 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'] - case application - when "Tor Browser" - user = LIVE_USER - cmd_regex = "#{binary} .* -profile /home/#{user}/\.tor-browser/profile\.default" - chroot = "" - new_tab_button_image = "TorBrowserNewTabButton.png" - when "Unsafe Browser" - user = "clearnet" - 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. - binary = "#{binary}-unconfined" - tor_launcher_install = $vm.execute_successfully( - 'echo ${TOR_LAUNCHER_INSTALL}', :libs => 'tor-browser' - ).stdout.chomp - cmd_regex = "#{binary}\s+-app #{tor_launcher_install}/application\.ini.*" - chroot = "" - new_tab_button_image = nil - address_bar_image = nil - # The standalone Tor Launcher uses fewer libs than the full - # browser. - unused_tbb_libs.concat(["libfreebl3.so", "libnssckbi.so", "libsoftokn3.so"]) - else - raise "Invalid browser or XUL application: #{application}" - end - return { - :user => user, - :cmd_regex => cmd_regex, - :chroot => chroot, - :new_tab_button_image => new_tab_button_image, - :address_bar_image => address_bar_image, - :unused_tbb_libs => unused_tbb_libs, - } -end - -When /^I open a new tab in the (.*)$/ do |browser| - info = xul_application_info(browser) - @screen.click(info[:new_tab_button_image]) - @screen.wait(info[:address_bar_image], 10) -end - -When /^I open the address "([^"]*)" in the (.*)$/ do |address, browser| - step "I open a new tab in the #{browser}" - info = xul_application_info(browser) - open_address = Proc.new do - @screen.click(info[:address_bar_image]) - # This static here since we have no reliable visual indicators - # that we can watch to know when typing is "safe". - sleep 5 - # The browser sometimes loses keypresses when suggestions are - # shown, which we work around by pasting the address from the - # clipboard, in one go. - $vm.set_clipboard(address) - @screen.type('v', Sikuli::KeyModifier.CTRL) - @screen.type(Sikuli::Key.ENTER) - end - open_address.call - 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 - end -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" -end - -def xul_app_shared_lib_check(pid, chroot, expected_absent_tbb_libs = []) - absent_tbb_libs = [] - unwanted_native_libs = [] - tbb_libs = $vm.execute_successfully("ls -1 #{chroot}${TBB_INSTALL}/*.so", - :libs => 'tor-browser').stdout.split - firefox_pmap_info = $vm.execute("pmap --show-path #{pid}").stdout - for lib in tbb_libs do - lib_name = File.basename lib - if not /\W#{lib}$/.match firefox_pmap_info - absent_tbb_libs << lib_name - end - native_libs = $vm.execute_successfully( - "find /usr/lib /lib -name \"#{lib_name}\"" - ).stdout.split - for native_lib in native_libs do - if /\W#{native_lib}$"/.match firefox_pmap_info - unwanted_native_libs << lib_name - end - end - end - absent_tbb_libs -= expected_absent_tbb_libs - assert(absent_tbb_libs.empty? && unwanted_native_libs.empty?, - "The loaded shared libraries for the firefox process are not the " + - "way we expect them.\n" + - "Expected TBB libs that are absent: #{absent_tbb_libs}\n" + - "Native libs that we don't want: #{unwanted_native_libs}") -end - -Then /^the (.*) uses all expected TBB shared libraries$/ do |application| - info = xul_application_info(application) - pid = $vm.execute_successfully("pgrep --uid #{info[:user]} --full --exact '#{info[:cmd_regex]}'").stdout.chomp - assert(/\A\d+\z/.match(pid), "It seems like #{application} is not running") - xul_app_shared_lib_check(pid, info[:chroot], info[:unused_tbb_libs]) -end - -Then /^the (.*) chroot is torn down$/ do |browser| - info = xul_application_info(browser) - try_for(30, :msg => "The #{browser} chroot '#{info[:chroot]}' was " \ - "not removed") do - !$vm.execute("test -d '#{info[:chroot]}'").success? - end -end - -Then /^the (.*) runs as the expected user$/ do |browser| - info = xul_application_info(browser) - assert_vmcommand_success($vm.execute( - "pgrep --full --exact '#{info[:cmd_regex]}'"), - "The #{browser} is not running") - assert_vmcommand_success($vm.execute( - "pgrep --uid #{info[:user]} --full --exact '#{info[:cmd_regex]}'"), - "The #{browser} is not running as the #{info[:user]} user") -end - -When /^I download some file in the Tor Browser$/ do - @some_file = 'tails-signing.key' - some_url = "https://tails.boum.org/#{@some_file}" - step "I open the address \"#{some_url}\" in the Tor Browser" -end - -Then /^I get the browser download dialog$/ do - @screen.wait('BrowserDownloadDialog.png', 60) - @screen.wait('BrowserDownloadDialogSaveAsButton.png', 10) -end - -When /^I save the file to the default Tor Browser download directory$/ do - @screen.click('BrowserDownloadDialogSaveAsButton.png') - @screen.wait('BrowserDownloadFileToDialog.png', 10) - @screen.type(Sikuli::Key.ENTER) -end - -Then /^the file is saved to the default Tor Browser download directory$/ do - assert_not_nil(@some_file) - expected_path = "/home/#{LIVE_USER}/Tor Browser/#{@some_file}" - try_for(10) { $vm.file_exist?(expected_path) } -end diff --git a/features/step_definitions/build.rb b/features/step_definitions/build.rb deleted file mode 100644 index fd001ff4..00000000 --- a/features/step_definitions/build.rb +++ /dev/null @@ -1,115 +0,0 @@ -Given /^Tails ([[:alnum:].]+) has been released$/ do |version| - create_git unless git_exists? - - old_branch = current_branch - - fatal_system "git checkout --quiet stable" - old_entries = File.open('debian/changelog') { |f| f.read } - File.open('debian/changelog', 'w') do |changelog| - changelog.write(<<END_OF_CHANGELOG) -tails (#{version}) stable; urgency=low - - * New upstream release. - - -- Tails developers <tails@boum.org> Tue, 31 Jan 2012 15:12:57 +0100 - -#{old_entries} -END_OF_CHANGELOG - end - fatal_system "git commit --quiet debian/changelog -m 'Release #{version}'" - fatal_system "git tag '#{version}'" - - if old_branch != 'stable' - fatal_system "git checkout --quiet '#{old_branch}'" - fatal_system "git merge --quiet 'stable'" - end -end - -Given /^Tails ([[:alnum:].-]+) has been tagged$/ do |version| - fatal_system "git tag '#{version}'" -end - -Given /^Tails ([[:alnum:].]+) has not been released yet$/ do |version| - !File.exists? ".git/refs/tags/#{version}" -end - -Given /^the last version mentioned in debian\/changelog is ([[:alnum:]~.]+)$/ do |version| - last = `dpkg-parsechangelog | awk '/^Version: / { print $2 }'`.strip - raise StandardError.new('dpkg-parsechangelog failed.') if $? != 0 - - if last != version - fatal_system "debchange -v '#{version}' 'New upstream release'" - end -end - -Given %r{I am working on the ([[:alnum:]./_-]+) base branch$} do |branch| - create_git unless git_exists? - - if current_branch != branch - fatal_system "git checkout --quiet '#{branch}'" - end - - File.open('config/base_branch', 'w+') do |base_branch_file| - base_branch_file.write("#{branch}\n") - end -end - -Given %r{I am working on the ([[:alnum:]./_-]+) branch based on ([[:alnum:]./_-]+)$} do |branch, base| - create_git unless git_exists? - - if current_branch != branch - fatal_system "git checkout --quiet -b '#{branch}' '#{base}'" - end - - File.open('config/base_branch', 'w+') do |base_branch_file| - base_branch_file.write("#{base}\n") - end -end - -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| - @output = `#{File.expand_path("../../../auto/scripts/#{command}", __FILE__)}` - @exit_code = $?.exitstatus -end - -Then /^I should see the ['"]?([[:alnum:].-]+)['"]? suite$/ do |suite| - @output.should have_suite(suite) -end - -Then /^I should see only the ['"]?([[:alnum:].-]+)['"]? suite$/ do |suite| - assert_equal(1, @output.lines.count) - @output.should have_suite(suite) -end - -Then /^I should not see the ['"]?([[:alnum:].-]+)['"]? suite$/ do |suite| - @output.should_not have_suite(suite) -end - -Given(/^the config\/APT_overlays\.d directory is empty$/) do - Dir.glob('config/APT_overlays.d/*').empty? \ - or raise "config/APT_overlays.d/ is not empty" -end - -Given(/^config\/APT_overlays\.d contains ['"]?([[:alnum:].-]+)['"]?$/) do |suite| - FileUtils.touch("config/APT_overlays.d/#{suite}") -end - -Then(/^it should fail$/) do - assert_not_equal(0, @exit_code) -end - -Given(/^the (config\/base_branch) file does not exist$/) do |file| - File.delete(file) -end - -Given(/^the (config\/APT_overlays\.d) directory does not exist$/) do |dir| - Dir.rmdir(dir) -end - -Given(/^the config\/base_branch file is empty$/) do - File.truncate('config/base_branch', 0) -end diff --git a/features/step_definitions/checks.rb b/features/step_definitions/checks.rb deleted file mode 100644 index 423b8390..00000000 --- a/features/step_definitions/checks.rb +++ /dev/null @@ -1,252 +0,0 @@ -def shipped_openpgp_keys - shipped_gpg_keys = $vm.execute_successfully('gpg --batch --with-colons --fingerprint --list-key', :user => LIVE_USER).stdout - openpgp_fingerprints = shipped_gpg_keys.scan(/^fpr:::::::::([A-Z0-9]+):$/).flatten - return openpgp_fingerprints -end - -Then /^the OpenPGP keys shipped with Tails will be valid for the next (\d+) months$/ do |months| - invalid = Array.new - shipped_openpgp_keys.each do |key| - begin - step "the shipped OpenPGP key #{key} will be valid for the next #{months} months" - rescue Test::Unit::AssertionFailedError - invalid << key - next - end - end - assert(invalid.empty?, "The following key(s) will not be valid in #{months} months: #{invalid.join(', ')}") -end - -Then /^the shipped (?:Debian repository key|OpenPGP key ([A-Z0-9]+)) will be valid for the next (\d+) months$/ do |fingerprint, max_months| - if fingerprint - cmd = 'gpg' - user = LIVE_USER - else - fingerprint = TAILS_DEBIAN_REPO_KEY - cmd = 'apt-key adv' - user = 'root' - end - shipped_sig_key_info = $vm.execute_successfully("#{cmd} --batch --list-key #{fingerprint}", :user => user).stdout - m = /\[expire[ds]: ([0-9-]*)\]/.match(shipped_sig_key_info) - if m - expiration_date = Date.parse(m[1]) - assert((expiration_date << max_months.to_i) > DateTime.now, - "The shipped key #{fingerprint} will not be valid #{max_months} months from now.") - 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") - actual_username = $vm.execute(". /etc/live/config/username.conf; " + - "echo $LIVE_USERNAME").stdout.chomp - assert_equal(LIVE_USER, actual_username) -end - -Then /^the live user is a member of only its own group and "(.*?)"$/ do |groups| - expected_groups = groups.split(" ") << LIVE_USER - actual_groups = $vm.execute("groups #{LIVE_USER}").stdout.chomp.sub(/^#{LIVE_USER} : /, "").split(" ") - unexpected = actual_groups - expected_groups - missing = expected_groups - actual_groups - assert_equal(0, unexpected.size, - "live user in unexpected groups #{unexpected}") - assert_equal(0, missing.size, - "live user not in expected groups #{missing}") -end - -Then /^the live user owns its home dir and it has normal permissions$/ do - home = "/home/#{LIVE_USER}" - assert($vm.execute("test -d #{home}").success?, - "The live user's home doesn't exist or is not a directory") - owner = $vm.execute("stat -c %U:%G #{home}").stdout.chomp - perms = $vm.execute("stat -c %a #{home}").stdout.chomp - assert_equal("#{LIVE_USER}:#{LIVE_USER}", owner) - assert_equal("700", perms) -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 - 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] - # 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 - SERVICES_EXPECTED_ON_ALL_IFACES.include? [proc, laddr, "*"] - puts "Service '#{proc}' is listening on #{laddr}:#{lport} " + - "but has an exception" - else - raise "Unexpected service '#{proc}' listening on #{laddr}:#{lport}" - end - end - end -end - -When /^Tails has booted a 64-bit kernel$/ do - assert($vm.execute("uname -r | grep -qs 'amd64$'").success?, - "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}'", - :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}'", - :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}'") - end -end - -Then /^AppArmor is enabled$/ do - assert($vm.execute("aa-status").success?, "AppArmor is not enabled") -end - -Then /^some AppArmor profiles are enforced$/ do - assert($vm.execute("aa-status --enforced").stdout.chomp.to_i > 0, - "No AppArmor profile is enforced") -end - -def get_seccomp_status(process) - assert($vm.has_process?(process), "Process #{process} not running.") - pid = $vm.pidof(process)[0] - status = $vm.file_content("/proc/#{pid}/status") - return status.match(/^Seccomp:\s+([0-9])/)[1].chomp.to_i -end - -def get_apparmor_status(pid) - apparmor_status = $vm.file_content("/proc/#{pid}/attr/current").chomp - if apparmor_status.include?(')') - # matches something like /usr/sbin/cupsd (enforce) - # and only returns what's in the parentheses - return apparmor_status.match(/[^\s]+\s+\((.+)\)$/)[1].chomp - else - return apparmor_status - end -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_equal(mode, get_apparmor_status(pid)) -end - -Then /^the running process "(.+)" is confined with Seccomp in (filter|strict) mode$/ do |process,mode| - status = get_seccomp_status(process) - if mode == 'strict' - assert_equal(1, status, "#{process} not confined with Seccomp in strict mode") - elsif mode == 'filter' - assert_equal(2, status, "#{process} not confined with Seccomp in filter mode") - else - raise "Unsupported mode #{mode} passed" - end -end - -Then /^tails-debugging-info is not susceptible to symlink attacks$/ do - secret_file = '/secret' - secret_contents = 'T0P S3Cr1t -- 3yEs oN1y' - $vm.file_append(secret_file, secret_contents) - $vm.execute_successfully("chmod u=rw,go= #{secret_file}") - $vm.execute_successfully("chown root:root #{secret_file}") - script_path = '/usr/local/sbin/tails-debugging-info' - script_lines = $vm.file_content(script_path).split("\n") - script_lines.grep(/^debug_file\s+/).each do |line| - _, user, debug_file = line.split - # root can always mount symlink attacks - next if user == 'root' - # Remove quoting around the file - debug_file.gsub!(/["']/, '') - # Skip files that do not exist, or cannot be removed (e.g. the - # ones in /proc). - next if not($vm.execute("rm #{debug_file}").success?) - # Check what would happen *if* the amnesia user managed to replace - # the debugging file with a symlink to the secret. - $vm.execute_successfully("ln -s #{secret_file} #{debug_file}") - $vm.execute_successfully("chown --no-dereference #{LIVE_USER}:#{LIVE_USER} #{debug_file}") - if $vm.execute("sudo /usr/local/sbin/tails-debugging-info | " + - "grep '#{secret_contents}'", - :user => LIVE_USER).success? - raise "The secret was leaked by tails-debugging-info via '#{debug_file}'" - end - # Remove the secret so it cannot possibly interfere with the - # following iterations (even though it should not). - $vm.execute_successfully("echo > #{debug_file}") - end -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 -end - -Then /^the Tor Status icon tells me that Tor is( not)? usable$/ do |not_usable| - picture = not_usable ? 'TorStatusNotUsable' : 'TorStatusUsable' - @screen.find("#{picture}.png") -end diff --git a/features/step_definitions/common_steps.rb b/features/step_definitions/common_steps.rb deleted file mode 100644 index bd03cebb..00000000 --- a/features/step_definitions/common_steps.rb +++ /dev/null @@ -1,1086 +0,0 @@ -require 'fileutils' - -def post_vm_start_hook - # Sometimes the first click is lost (presumably it's used to give - # focus to virt-viewer or similar) so we do that now rather than - # having an important click lost. The point we click should be - # somewhere where no clickable elements generally reside. - @screen.click_point(@screen.w, @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) - b = @screen.wait(bottom, 10) - # In Sikuli, lower x == closer to the left, lower y == closer to the top - assert(t.y < b.y) - center = Sikuli::Location.new(((t.x + t.w) + b.x)/2, - ((t.y + t.h) + b.y)/2) - @screen.right_click(center) - @screen.hide_cursor - @screen.wait_and_click(menu_item, 10) - return - 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 - $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. - # Time jumps and incorrect clocks also confuses Tor in many ways. - #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") - # 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 - #end -end - -Given /^a computer$/ do - $vm.destroy_and_undefine if $vm - $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 - -Given /^the computer is set to boot from the Tails DVD$/ do - $vm.set_cdrom_boot(TAILS_ISO) -end - -Given /^the computer is set to boot from (.+?) drive "(.+?)"$/ do |type, name| - $vm.set_disk_boot(name, type.downcase) -end - -Given /^I (temporarily )?create a (\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 -end - -Given /^I plug (.+) drive "([^"]+)"$/ do |bus, name| - $vm.plug_drive(name, bus.downcase) - if $vm.is_running? - step "drive \"#{name}\" is detected by Tails" - end -end - -Then /^drive "([^"]+)" is detected by Tails$/ do |name| - raise "Tails is not running" unless $vm.is_running? - try_for(10, :msg => "Drive '#{name}' is not detected by Tails") do - $vm.disk_detected?(name) - end -end - -Given /^the network is plugged$/ do - $vm.plug_network -end - -Given /^the network is unplugged$/ do - $vm.unplug_network -end - -Given /^the hardware clock is set to "([^"]*)"$/ do |time| - $vm.set_hardware_clock(DateTime.parse(time).to_time) -end - -Given /^I capture all network traffic$/ do - @sniffer = Sniffer.new("sniffer", $vmnet) - @sniffer.capture - add_after_scenario_hook do - @sniffer.stop - @sniffer.clear - end -end - -Given /^I set Tails to boot with options "([^"]*)"$/ do |options| - @boot_options = options -end - -When /^I start the computer$/ do - assert(!$vm.is_running?, - "Trying to start a VM that is already running") - $vm.start - post_vm_start_hook -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 - step "the network is unplugged" - 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" - step "all notifications have disappeared" - step "available upgrades have been checked" - else - step "all notifications have disappeared" - 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| - step "the computer is set to boot from #{drive_type} drive \"#{drive_name}\"" - if network_unplugged.empty? - step "the network is plugged" - else - step "the network is unplugged" - 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 log in to a new session" - step "Tails seems to have booted normally" - if network_unplugged.empty? - step "Tor is ready" - step "all notifications have disappeared" - step "available upgrades have been checked" - else - step "all notifications have disappeared" - end - end -end - -When /^I power off the computer$/ do - assert($vm.is_running?, - "Trying to power off an already powered off VM") - $vm.power_off -end - -When /^I cold reboot the computer$/ do - step "I power off the computer" - step "I start the computer" -end - -When /^I destroy the computer$/ do - $vm.destroy_and_undefine -end - -Given /^the computer (re)?boots DebianInstaller(|\d+)$/ do |reboot,version| - - boot_timeout = 30 - # We need some extra time for memory wiping if rebooting - - @screen.wait("d-i8_bootsplash.png", boot_timeout) - @screen.type(Sikuli::Key.TAB) - - @screen.type(' preseed/early_command="echo ttyS0::askfirst:-/bin/sh>>/etc/inittab;kill -HUP 1"' + " blacklist=psmouse #{@boot_options}" + - Sikuli::Key.ENTER) - $vm.wait_until_remote_shell_is_up -end - -Given /^I select British English$/ do - @screen.wait("DebianInstallerSelectLangEnglish.png", 30) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("DebianInstallerCountrySelection.png", 10) - @screen.type(Sikuli::Key.UP) - @screen.waitVanish("DebianInstallerCountrySelection.png", 10) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("DebianInstallerSelectLangEnglishUK.png", 10) - @screen.type(Sikuli::Key.ENTER) -end - -Given /^I accept the hostname, using "([^"]*)" as the domain$/ do |domain| - @screen.wait("DebianInstallerHostnamePrompt.png", 5*60) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("DebianInstallerDomainPrompt.png", 10) - @screen.type(domain + Sikuli::Key.ENTER) - @screen.waitVanish("DebianInstallerDomainPrompt.png", 10) -end - -Given /^I set the root password to "([^"]*)"$/ do |rootpw| -# Root Password, twice - @screen.wait("DebianInstallerRootPassword.png", 30) - @screen.type(rootpw + Sikuli::Key.ENTER) - @screen.waitVanish("DebianInstallerRootPassword.png", 10) - @screen.type(rootpw + Sikuli::Key.ENTER) -end - -Given /^I set the password for "([^"]*)" to be "([^"]*)"$/ do |fullname,password| -# Username, and password twice - @screen.wait("DebianInstallerNameOfUser.png", 10) - @screen.type(fullname + Sikuli::Key.ENTER) - @screen.waitVanish("DebianInstallerNameOfUser.png", 10) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("DebianInstallerUserPassword.png", 10) - @screen.type(password + Sikuli::Key.ENTER) - @screen.waitVanish("DebianInstallerUserPassword.png", 10) - @screen.type(password + Sikuli::Key.ENTER) -end - - #@screen.wait("DebianInstallerNoDiskFound.png", 60) - -Given /^I select full-disk, single-filesystem partitioning$/ do - @screen.wait("DebianInstallerPartitioningMethod.png", 60) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("DebianInstallerSelectDiskToPartition.png", 10) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("DebianInstallerPartitioningScheme.png", 10) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("d-i_FinishPartitioning.png", 10) - sleep(5) # FIXME -- why do we need this? It's weird that the wait is not enough - @screen.type(Sikuli::Key.ENTER) - # prompt about Writing Partitions to disk: - @screen.wait("d-i_No.png", 10) - @screen.type(Sikuli::Key.TAB) - @screen.wait("d-i_Yes.png", 10) - @screen.type(Sikuli::Key.ENTER) -end - -Given /^I note that the Base system is being installed$/ do - @screen.wait("DebianInstallerInstallingBaseSystem.png", 30) - @screen.waitVanish("DebianInstallerInstallingBaseSystem.png", 15 * 60) -end - -Given /^I accept the default mirror$/ do - @screen.wait("DebianInstallerMirrorCountry.png", 10 * 60) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("d-i_ArchiveMirror.png", 5) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("d-i_HttpProxy.png", 5) - @screen.type(Sikuli::Key.ENTER) -end - -Given /^I neglect to scan more CDs$/ do - @screen.wait("d-i_ScanCD.png", 15 * 60) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("d-i_UseNetMirror.png", 10) - @screen.wait("d-i_Yes.png", 10) - @screen.type(Sikuli::Key.TAB) - @screen.wait("d-i_No.png", 10) - @screen.type(Sikuli::Key.ENTER) -end - -Given /^I ignore Popcon$/ do - #@screen.wait("d-i_popcon.png", 10 * 60) - @screen.wait("d-i_No.png", 10 * 60) - @screen.type(Sikuli::Key.ENTER) -end - -Given /^we reach the Tasksel prompt$/ do - @screen.wait("d-i_ChooseSoftware.png", 5 * 60) -end - -Given /^I hit ENTER$/ do - @screen.type(Sikuli::Key.ENTER) -end - -Given /^I select the Desktop task$/ do - @screen.wait("d-i_ChooseSoftware.png", 10) - @screen.type(Sikuli::Key.SPACE) - @screen.type(Sikuli::Key.DOWN) - @screen.type(Sikuli::Key.SPACE) - @screen.wait("d-i_DesktopTask_Yes.png", 10) - @screen.type(Sikuli::Key.ENTER) -end - -Given /^I install GRUB$/ do - @screen.wait("d-i_InstallGRUB.png", 80 * 60) - #@screen.wait("Install the GRUB", 80 * 60) - @screen.type(Sikuli::Key.ENTER) - @screen.wait("d-i_GRUBEnterDev.png", 10 * 60) - @screen.type(Sikuli::Key.DOWN) - @screen.wait("d-i_GRUBdev.png", 10) - @screen.type(Sikuli::Key.ENTER) -end - -Given /^I allow reboot after the install is complete$/ do - @screen.wait("d-i_InstallComplete.png", 2 * 60) - @screen.type(Sikuli::Key.ENTER) -end - -Given /^I wait for the reboot$/ do - @screen.wait(bootsplash, 10 * 60) -end - -Given /^I make sure that we boot from disk$/ do - @screen.wait("d-i_GRUB_Debian.png", 5 * 60) -end - -Given /^I wait for a Login Prompt$/ do - @screen.wait("DebianLoginPromptVT.png", 2 * 60) -end - -def bootsplash - case @os_loader - when "UEFI" - 'TailsBootSplashUEFI.png' - else - 'd-i8_bootsplash.png' - end -end - -def bootsplash_tab_msg - case @os_loader - when "UEFI" - 'TailsBootSplashTabMsgUEFI.png' - else - #if reboot - # bootsplash = 'TailsBootSplashPostReset.png' - # bootsplash_tab_msg = 'TailsBootSplashTabMsgPostReset.png' - # boot_timeout = 120 - #else - #bootsplash = "DebianLive#{version}BootSplash.png" - bootsplash = "DebianLiveBootSplash.png" - bootsplash_tab_msg = "DebianLiveBootSplashTabMsg.png" - boot_timeout = 30 - #end - end -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) - - @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 -end - -Given /^I log in to a new session(?: in )?(|German)$/ do |lang| - case lang - when 'German' - @language = "German" - @screen.wait_and_click('TailsGreeterLanguage.png', 10) - @screen.wait_and_click("TailsGreeterLanguage#{@language}.png", 10) - @screen.wait_and_click("TailsGreeterLoginButton#{@language}.png", 10) - when '' - @screen.wait_and_click('TailsGreeterLoginButton.png', 10) - else - raise "Unsupported language: #{lang}" - end -end - -Given /^I set sudo password "([^"]*)"$/ do |password| - @sudo_password = password - next if @skip_steps_while_restoring_background - #@screen.wait("TailsGreeterAdminPassword.png", 20) - @screen.type(@sudo_password) - @screen.type(Sikuli::Key.TAB) - @screen.type(@sudo_password) -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? - } -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) - # 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. - $vm.execute_successfully( - 'gsettings set org.gnome.desktop.session idle-delay 0', - :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) -end - -Given /^Tor is ready$/ do - step "Tor has built a circuit" - step "the time has synced" - if $vm.execute('systemctl is-system-running').failure? - units_status = $vm.execute('systemctl').stdout - raise "At least one system service failed to start:\n#{units_status}" - end -end - -Given /^Tor has built a circuit$/ do - wait_until_tor_is_working -end - -Given /^the time has synced$/ do - ["/var/run/tordate/done", "/var/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? - } -end - -Given /^the Tor Browser has started$/ do - tor_browser_picture = "TorBrowserWindow.png" - @screen.wait(tor_browser_picture, 60) -end - -Given /^the Tor Browser (?:has started and )?load(?:ed|s) the (startup page|Tails roadmap)$/ do |page| - case page - when "startup page" - picture = "TorBrowserStartupPage.png" - when "Tails roadmap" - picture = "TorBrowserTailsRoadmap.png" - else - raise "Unsupported page: #{page}" - end - step "the Tor Browser has started" - @screen.wait(picture, 120) -end - -Given /^the Tor Browser has started in offline mode$/ do - @screen.wait("TorBrowserOffline.png", 60) -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) - @screen.type("d", Sikuli::KeyModifier.CTRL) - @screen.wait("TorBrowserBookmarkPrompt.png", 10) - @screen.type(url + Sikuli::Key.ENTER) -end - -Given /^the Tor Browser has a bookmark to eff.org$/ do - @screen.type("b", Sikuli::KeyModifier.ALT) - @screen.wait("TorBrowserEFFBookmark.png", 10) -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) - end - rescue FindFailed - # No notifications, so we're good to go. - end - @screen.hide_cursor - # Click anywhere to close the notification applet - @screen.click("GnomeApplicationsMenu.png") - @screen.hide_cursor -end - -Then /^I (do not )?see "([^"]*)" after at most (\d+) seconds$/ do |negation, image, time| - begin - @screen.wait(image, time.to_i) - raise "found '#{image}' while expecting not to" if negation - rescue FindFailed => e - raise e if not(negation) - end -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 -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) - @screen.wait(image, 60) - @screen.type(password) - @screen.type(Sikuli::Key.ENTER) - @screen.waitVanish(image, 10) -end - -Given /^I enter the "([^"]*)" password in the pkexec prompt$/ do |password| - deal_with_polkit_prompt('PolicyKitAuthPrompt.png', password) -end - -Given /^process "([^"]+)" is (not )?running$/ do |process, not_running| - if not_running - assert(!$vm.has_process?(process), "Process '#{process}' is running") - else - assert($vm.has_process?(process), "Process '#{process}' is not running") - end -end - -Given /^process "([^"]+)" is running within (\d+) seconds$/ do |process, time| - try_for(time.to_i, :msg => "Process '#{process}' is not running after " + - "waiting for #{time} seconds") do - $vm.has_process?(process) - end -end - -Given /^process "([^"]+)" has stopped running after at most (\d+) seconds$/ do |process, time| - try_for(time.to_i, :msg => "Process '#{process}' is still running after " + - "waiting for #{time} seconds") do - not $vm.has_process?(process) - end -end - -Given /^I kill the process "([^"]+)"$/ do |process| - $vm.execute("killall #{process}") - try_for(10, :msg => "Process '#{process}' could not be killed") { - !$vm.has_process?(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? - 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' -end - -When /^I request a shutdown using the emergency shutdown applet$/ do - @screen.hide_cursor - @screen.wait_and_click('TailsEmergencyShutdownButton.png', 10) - @screen.wait_and_click('TailsEmergencyShutdownHalt.png', 10) -end - -When /^I warm reboot the computer$/ do - $vm.spawn("reboot") -end - -When /^I request a reboot using the emergency shutdown applet$/ do - @screen.hide_cursor - @screen.wait_and_click('TailsEmergencyShutdownButton.png', 10) - @screen.wait_and_click('TailsEmergencyShutdownReboot.png', 10) -end - -Given /^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 - -[connection] -id=#{con_name} -uuid=bbc60668-1be0-11e4-a9c6-2f1ce0e75bf1 -type=802-3-ethernet -timestamp=1395406011 - -[ipv6] -method=auto - -[ipv4] -method=auto -EOF - con_content.split("\n").each do |line| - $vm.execute("echo '#{line}' >> /tmp/NM.#{con_name}") - 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}" - } -end - -Given /^I switch to the "([^"]+)" NetworkManager connection$/ do |con_name| - $vm.execute("nmcli connection up id #{con_name}") - try_for(60) do - $vm.execute("nmcli --terse --fields NAME,STATE connection show").stdout.chomp.split("\n").include?("#{con_name}:activated") - end -end - -When /^I start and focus GNOME Terminal$/ do - step 'I start "Terminal" via the GNOME "Utilities" applications menu' - @screen.wait('GnomeTerminalWindow.png', 20) -end - -When /^I run "([^"]+)" in GNOME Terminal$/ do |command| - if !$vm.has_process?("gnome-terminal-server") - step "I start and focus GNOME Terminal" - else - @screen.wait_and_click('GnomeTerminalWindow.png', 20) - end - @screen.type(command + Sikuli::Key.ENTER) -end - -When /^the file "([^"]+)" exists(?:| after at most (\d+) seconds)$/ do |file, timeout| - timeout = 0 if timeout.nil? - try_for( - timeout.to_i, - :msg => "The file #{file} does not exist after #{timeout} seconds" - ) { - $vm.file_exist?(file) - } -end - -When /^the file "([^"]+)" does not exist$/ do |file| - assert(! ($vm.file_exist?(file))) -end - -When /^the directory "([^"]+)" exists$/ do |directory| - assert($vm.directory_exist?(directory)) -end - -When /^the directory "([^"]+)" does not exist$/ do |directory| - assert(! ($vm.directory_exist?(directory))) -end - -When /^I copy "([^"]+)" to "([^"]+)" as user "([^"]+)"$/ do |source, destination, user| - c = $vm.execute("cp \"#{source}\" \"#{destination}\"", :user => LIVE_USER) - assert(c.success?, "Failed to copy file:\n#{c.stdout}\n#{c.stderr}") -end - -def is_persistent?(app) - conf = get_persistence_presets(true)["#{app}"] - c = $vm.execute("findmnt --noheadings --output SOURCE --target '#{conf}'") - # This check assumes that we haven't enabled read-only persistence. - c.success? and c.stdout.chomp != "aufs" -end - -Then /^persistence for "([^"]+)" is (|not )enabled$/ do |app, enabled| - case enabled - when '' - assert(is_persistent?(app), "Persistence should be enabled.") - when 'not ' - assert(!is_persistent?(app), "Persistence should not be 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 -end - -When /^I type "([^"]+)"$/ do |string| - @screen.type(string) -end - -When /^I press the "([^"]+)" key$/ do |key| - begin - @screen.type(eval("Sikuli::Key.#{key}")) - rescue RuntimeError - raise "unsupported key #{key}" - end -end - -Then /^the (amnesiac|persistent) Tor Browser directory (exists|does not exist)$/ do |persistent_or_not, mode| - case persistent_or_not - when "amnesiac" - dir = "/home/#{LIVE_USER}/Tor Browser" - when "persistent" - dir = "/home/#{LIVE_USER}/Persistent/Tor Browser" - end - step "the directory \"#{dir}\" #{mode}" -end - -Then /^there is a GNOME bookmark for the (amnesiac|persistent) Tor Browser directory$/ do |persistent_or_not| - case persistent_or_not - when "amnesiac" - bookmark_image = 'TorBrowserAmnesicFilesBookmark.png' - when "persistent" - bookmark_image = 'TorBrowserPersistentFilesBookmark.png' - end - @screen.wait_and_click('GnomePlaces.png', 10) - @screen.wait(bookmark_image, 40) - @screen.type(Sikuli::Key.ESC) -end - -Then /^there is no GNOME bookmark for the persistent Tor Browser directory$/ do - try_for(65) do - @screen.wait_and_click('GnomePlaces.png', 10) - @screen.wait("GnomePlacesWithoutTorBrowserPersistent.png", 10) - @screen.type(Sikuli::Key.ESC) - end -end - -def pulseaudio_sink_inputs - pa_info = $vm.execute_successfully('pacmd info', :user => LIVE_USER).stdout - sink_inputs_line = pa_info.match(/^\d+ sink input\(s\) available\.$/)[0] - return sink_inputs_line.match(/^\d+/)[0].to_i -end - -When /^(no|\d+) application(?:s?) (?:is|are) playing audio(?:| after (\d+) seconds)$/ do |nb, wait_time| - nb = 0 if nb == "no" - sleep wait_time.to_i if ! wait_time.nil? - 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) -end - -When /^I click the blocked video icon$/ do - @screen.wait_and_click("TorBrowserBlockedVideo.png", 30) -end - -When /^I accept to temporarily allow playing this video$/ do - @screen.wait_and_click("TorBrowserOkButton.png", 10) -end - -When /^I click the HTML5 play button$/ do - @screen.wait_and_click("TorBrowserHtml5PlayButton.png", 30) -end - -When /^I (can|cannot) save the current page as "([^"]+[.]html)" to the (.*) directory$/ do |should_work, output_file, output_dir| - should_work = should_work == 'can' ? true : false - @screen.type("s", Sikuli::KeyModifier.CTRL) - @screen.wait("TorBrowserSaveDialog.png", 10) - if output_dir == "persistent Tor Browser" - output_dir = "/home/#{LIVE_USER}/Persistent/Tor Browser" - @screen.wait_and_click("GtkTorBrowserPersistentBookmark.png", 10) - @screen.wait("GtkTorBrowserPersistentBookmarkSelected.png", 10) - # The output filename (without its extension) is already selected, - # let's use the keyboard shortcut to focus its field - @screen.type("n", Sikuli::KeyModifier.ALT) - @screen.wait("TorBrowserSaveOutputFileSelected.png", 10) - elsif output_dir == "default downloads" - output_dir = "/home/#{LIVE_USER}/Tor Browser" - else - @screen.type(output_dir + '/') - end - # Only the part of the filename before the .html extension can be easily replaced - # so we have to remove it before typing it into the arget filename entry widget. - @screen.type(output_file.sub(/[.]html$/, '')) - @screen.type(Sikuli::Key.ENTER) - if should_work - try_for(10, :msg => "The page was not saved to #{output_dir}/#{output_file}") { - $vm.file_exist?("#{output_dir}/#{output_file}") - } - else - @screen.wait("TorBrowserCannotSavePage.png", 10) - end -end - -When /^I can print the current page as "([^"]+[.]pdf)" to the (default downloads|persistent Tor Browser) directory$/ do |output_file, output_dir| - if output_dir == "persistent Tor Browser" - output_dir = "/home/#{LIVE_USER}/Persistent/Tor Browser" - else - output_dir = "/home/#{LIVE_USER}/Tor Browser" - end - @screen.type("p", Sikuli::KeyModifier.CTRL) - @screen.wait("TorBrowserPrintDialog.png", 20) - @screen.wait_and_click("BrowserPrintToFile.png", 10) - @screen.wait_and_double_click("TorBrowserPrintOutputFile.png", 10) - @screen.hide_cursor - @screen.wait("TorBrowserPrintOutputFileSelected.png", 10) - # Only the file's basename is selected by double-clicking, - # so we type only the desired file's basename to replace it - @screen.type(output_dir + '/' + output_file.sub(/[.]pdf$/, '') + Sikuli::Key.ENTER) - try_for(30, :msg => "The page was not printed to #{output_dir}/#{output_file}") { - $vm.file_exist?("#{output_dir}/#{output_file}") - } -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_hello_msg = "Welcome to the LAN web server!" - - # I've tested ruby Thread:s, fork(), etc. but nothing works due to - # various strange limitations in the ruby interpreter. For instance, - # apparently concurrent IO has serious limits in the thread - # scheduler (e.g. sikuli's wait() would block WEBrick from reading - # from its socket), and fork():ing results in a lot of complex - # cucumber stuff (like our hooks!) ending up in the child process, - # breaking stuff in the parent process. After asking some supposed - # ruby pros, I've settled on the following. - code = <<-EOF - 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}, - :DocumentRoot => "/dev/null") - server.mount_proc("/") do |req, res| - res.body = "#{web_server_hello_msg}" - end - server.start -EOF - 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 - end - - add_after_scenario_hook { Process.kill("TERM", proc.pid) } - - # It seems necessary to actually check that the LAN server is - # serving, possibly because it isn't doing so reliably when setting - # up. If e.g. the Unsafe Browser (which *should* be able to access - # the web server) tries to access it too early, Firefox seems to - # take some random amount of time to retry fetching. Curl gives a - # more consistent result, so let's rely on that instead. Note that - # this forces us to capture traffic *after* this step in case - # accessing this server matters, like when testing the Tor Browser.. - try_for(30, :msg => "Something is wrong with the LAN web server") do - msg = $vm.execute_successfully("curl #{@web_server_url}", - :user => LIVE_USER).stdout.chomp - web_server_hello_msg == msg - end -end - -When /^I open a page on the LAN web server in the (.*)$/ do |browser| - step "I open the address \"#{@web_server_url}\" in the #{browser}" -end - -Given /^I wait (?:between (\d+) and )?(\d+) seconds$/ do |min, max| - if min - time = rand(max.to_i - min.to_i + 1) + min.to_i - else - time = max.to_i - end - puts "Slept for #{time} seconds" - sleep(time) -end - -Given /^I (?:re)?start monitoring the AppArmor log of "([^"]+)"$/ do |profile| - # AppArmor log entries may be dropped if printk rate limiting is - # enabled. - $vm.execute_successfully('sysctl -w kernel.printk_ratelimit=0') - # We will only care about entries for this profile from this time - # and on. - guest_time = $vm.execute_successfully( - 'date +"%Y-%m-%d %H:%M:%S"').stdout.chomp - @apparmor_profile_monitoring_start ||= Hash.new - @apparmor_profile_monitoring_start[profile] = guest_time -end - -When /^AppArmor has (not )?denied "([^"]+)" from opening "([^"]+)"(?: after at most (\d+) seconds)?$/ do |anti_test, profile, file, time| - assert(@apparmor_profile_monitoring_start && - @apparmor_profile_monitoring_start[profile], - "It seems the profile '#{profile}' isn't being monitored by the " + - "'I monitor the AppArmor log of ...' step") - audit_line_regex = 'apparmor="DENIED" operation="open" profile="%s" name="%s"' % [profile, file] - block = Proc.new do - audit_log = $vm.execute( - "journalctl --full --no-pager " + - "--since='#{@apparmor_profile_monitoring_start[profile]}' " + - "SYSLOG_IDENTIFIER=kernel | grep -w '#{audit_line_regex}'" - ).stdout.chomp - assert(audit_log.empty? == (anti_test ? true : false)) - true - end - begin - if time - try_for(time.to_i) { block.call } - else - block.call - end - rescue Timeout::Error, Test::Unit::AssertionFailedError => e - raise e, "AppArmor has #{anti_test ? "" : "not "}denied the operation" - end -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') -end - -When /^I eject the boot medium$/ do - dev = boot_device - dev_type = device_info(dev)['ID_TYPE'] - case dev_type - when 'cd' - $vm.remove_cdrom - when 'disk' - boot_disk_name = $vm.disk_name(dev) - $vm.unplug_drive(boot_disk_name) - else - raise "Unsupported medium type '#{dev_type}' for boot device '#{dev}'" - end -end diff --git a/features/step_definitions/dhcp.rb b/features/step_definitions/dhcp.rb deleted file mode 100644 index ef4d9e15..00000000 --- a/features/step_definitions/dhcp.rb +++ /dev/null @@ -1,19 +0,0 @@ -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" - end - end -end diff --git a/features/step_definitions/electrum.rb b/features/step_definitions/electrum.rb deleted file mode 100644 index 447983d4..00000000 --- a/features/step_definitions/electrum.rb +++ /dev/null @@ -1,52 +0,0 @@ -Then /^I start Electrum through the GNOME menu$/ do - step "I start \"Electrum\" via the GNOME \"Internet\" applications menu" -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" - when "not " - step "the file \"#{wallet}\" does not exist" - else - raise "Unknown value specified for #{existing}" - end -end - -When /^I create a new bitcoin wallet$/ do - @screen.wait("ElectrumNoWallet.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.type(seed) # Confirm seed - @screen.wait_and_click("ElectrumNextButton.png", 10) - @screen.wait_and_click("ElectrumEncryptWallet.png", 10) - @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 - -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) -end - -Then /^I see the main Electrum client window$/ do - @screen.wait('ElectrumPreferencesButton.png', 20) -end - -Then /^Electrum successfully connects to the network$/ do - @screen.wait('ElectrumStatus.png', 180) -end diff --git a/features/step_definitions/encryption.rb b/features/step_definitions/encryption.rb deleted file mode 100644 index 9f7f1b96..00000000 --- a/features/step_definitions/encryption.rb +++ /dev/null @@ -1,133 +0,0 @@ -def seahorse_menu_click_helper(main, sub, verify = nil) - try_for(60) do - step "process \"#{verify}\" is running" if verify - @screen.hide_cursor - @screen.wait_and_click(main, 10) - @screen.wait_and_click(sub, 10) - return - end -end - -Given /^I generate an OpenPGP key named "([^"]+)" with password "([^"]+)"$/ do |name, pwd| - @passphrase = pwd - @key_name = name - gpg_key_recipie = <<EOF - Key-Type: RSA - Key-Length: 4096 - Subkey-Type: RSA - Subkey-Length: 4096 - Name-Real: #{@key_name} - Name-Comment: Blah - Name-Email: #{@key_name}@test.org - Expire-Date: 0 - 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", - :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' - @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 - # keystrokes to go missing. - sleep 5 - @screen.type("ATTACK AT DAWN") -end - -def maybe_deal_with_pinentry - begin - @screen.wait_and_click("PinEntryPrompt.png", 10) - # Without this sleep here (and reliable visual indicators) we can sometimes - # miss keystrokes by typing too soon. This sleep prevents this problem from - # coming up. - sleep 5 - @screen.type(@passphrase + Sikuli::Key.ENTER) - rescue FindFailed - # The passphrase was cached or we wasn't prompted at all (e.g. when - # only encrypting to a public key) - end -end - -def gedit_copy_all_text - context_menu_helper('GeditWindow.png', 'GeditStatusBar.png', 'GeditSelectAll.png') - context_menu_helper('GeditWindow.png', 'GeditStatusBar.png', 'GeditCopy.png') -end - -def paste_into_a_new_tab - @screen.wait_and_click("GeditNewTab.png", 20) - context_menu_helper('GeditWindow.png', 'GeditStatusBar.png', 'GeditPaste.png') -end - -def encrypt_sign_helper - gedit_copy_all_text - seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletSignEncrypt.png') - @screen.wait_and_click("GpgAppletChooseKeyWindow.png", 30) - # We don't have a good visual indicator for when we can continue without - # keystrokes being lost. - sleep 5 - yield - maybe_deal_with_pinentry - paste_into_a_new_tab -end - -def decrypt_verify_helper(icon) - gedit_copy_all_text - seahorse_menu_click_helper(icon, 'GpgAppletDecryptVerify.png') - maybe_deal_with_pinentry - @screen.wait("GpgAppletResults.png", 20) - @screen.wait("GpgAppletResultsMsg.png", 20) -end - -When /^I encrypt the message using my OpenPGP key$/ do - encrypt_sign_helper do - @screen.type(@key_name + Sikuli::Key.ENTER + Sikuli::Key.ENTER) - end -end - -Then /^I can decrypt the encrypted message$/ do - decrypt_verify_helper("GpgAppletIconEncrypted.png") - @screen.wait("GpgAppletResultsEncrypted.png", 20) -end - -When /^I sign the message using my OpenPGP key$/ do - encrypt_sign_helper do - @screen.type(Sikuli::Key.TAB + Sikuli::Key.DOWN + Sikuli::Key.ENTER) - end -end - -Then /^I can verify the message's signature$/ do - decrypt_verify_helper("GpgAppletIconSigned.png") - @screen.wait("GpgAppletResultsSigned.png", 20) -end - -When /^I both encrypt and sign the message using my OpenPGP key$/ do - encrypt_sign_helper do - @screen.wait_and_click('GpgAppletEncryptionKey.png', 20) - @screen.type(Sikuli::Key.SPACE) - @screen.wait('GpgAppletKeySelected.png', 10) - @screen.type(Sikuli::Key.TAB + Sikuli::Key.DOWN + Sikuli::Key.ENTER) - @screen.type(Sikuli::Key.ENTER) - end -end - -Then /^I can decrypt and verify the encrypted message$/ do - decrypt_verify_helper("GpgAppletIconEncrypted.png") - @screen.wait("GpgAppletResultsEncrypted.png", 20) - @screen.wait("GpgAppletResultsSigned.png", 20) -end - -When /^I symmetrically encrypt the message with password "([^"]+)"$/ do |pwd| - @passphrase = pwd - gedit_copy_all_text - 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 -end diff --git a/features/step_definitions/evince.rb b/features/step_definitions/evince.rb deleted file mode 100644 index 9411ac4d..00000000 --- a/features/step_definitions/evince.rb +++ /dev/null @@ -1,25 +0,0 @@ -When /^I(?:| try to) open "([^"]+)" with Evince$/ do |filename| - step "I run \"evince #{filename}\" in GNOME Terminal" -end - -Then /^I can print the current document to "([^"]+)"$/ do |output_file| - @screen.type("p", Sikuli::KeyModifier.CTRL) - @screen.wait("EvincePrintDialog.png", 10) - @screen.wait_and_click("EvincePrintToFile.png", 10) - @screen.wait_and_click("EvincePrintOutputFileButton.png", 10) - @screen.wait("EvincePrintFileDialog.png", 10) - # Only the file's basename is selected by double-clicking, - # so we type only the desired file's basename to replace it - $vm.set_clipboard(output_file.sub(/[.]pdf$/, '')) - @screen.type('v', Sikuli::KeyModifier.CTRL) - @screen.type(Sikuli::Key.ENTER) - @screen.wait_and_click("EvincePrintButton.png", 10) - try_for(10, :msg => "The document was not printed to #{output_file}") { - $vm.file_exist?(output_file) - } -end - -When /^I close Evince$/ do - @screen.type("w", Sikuli::KeyModifier.CTRL) - step 'process "evince" has stopped running after at most 20 seconds' -end diff --git a/features/step_definitions/firewall_leaks.rb b/features/step_definitions/firewall_leaks.rb deleted file mode 100644 index 942d00b8..00000000 --- a/features/step_definitions/firewall_leaks.rb +++ /dev/null @@ -1,56 +0,0 @@ -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}'" - end -end - -Given(/^I disable Tails' firewall$/) do - $vm.execute("/usr/local/lib/do_not_ever_run_me") - iptables = $vm.execute("iptables -L -n -v").stdout.chomp.split("\n") - for line in iptables do - if !line[/Chain (INPUT|OUTPUT|FORWARD) \(policy ACCEPT/] and - !line[/pkts[[:blank:]]+bytes[[:blank:]]+target/] and - !line.empty? - raise "The Tails firewall was not successfully disabled:\n#{iptables}" - end - end -end - -When(/^I do a TCP DNS lookup of "(.*?)"$/) do |host| - lookup = $vm.execute("host -T #{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) - assert(lookup.success?, "Failed to resolve #{host}:\n#{lookup.stdout}") -end - -When(/^I send some ICMP pings$/) do - # We ping an IP address to avoid a DNS lookup - ping = $vm.execute("ping -c 5 #{SOME_DNS_SERVER}") - assert(ping.success?, "Failed to ping #{SOME_DNS_SERVER}:\n#{ping.stderr}") -end diff --git a/features/step_definitions/git.rb b/features/step_definitions/git.rb deleted file mode 100644 index bf6f869d..00000000 --- a/features/step_definitions/git.rb +++ /dev/null @@ -1,6 +0,0 @@ -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")) - $vm.execute_successfully("cd '/home/#{LIVE_USER}/#{repo}/' && git status", - :user => LIVE_USER) -end diff --git a/features/step_definitions/icedove.rb b/features/step_definitions/icedove.rb deleted file mode 100644 index d3672895..00000000 --- a/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/features/step_definitions/mac_spoofing.rb b/features/step_definitions/mac_spoofing.rb deleted file mode 100644 index a4aa8714..00000000 --- a/features/step_definitions/mac_spoofing.rb +++ /dev/null @@ -1,108 +0,0 @@ -def all_ethernet_nics - $vm.execute_successfully( - "get_all_ethernet_nics", :libs => 'hardware' - ).stdout.split -end - -When /^I disable MAC spoofing in Tails Greeter$/ do - @screen.wait_and_click("TailsGreeterMACSpoofing.png", 30) -end - -Then /^the network device has (its default|a spoofed) MAC address configured$/ do |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 - 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" - end - 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}" - end - 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") -end - -Given /^no network interface modules can be unloaded$/ do - # Note that the real /sbin/modprobe is a symlink to /bin/kmod, and - # for it to run in modprobe compatibility mode the name must be - # exactly "modprobe", so we just move it somewhere our of the path - # instead of renaming it ".real" or whatever we usuablly do when - # diverting executables for wrappers. - modprobe_divert = "/usr/local/lib/modprobe" - $vm.execute_successfully( - "dpkg-divert --add --rename --divert '#{modprobe_divert}' /sbin/modprobe" - ) - fake_modprobe_wrapper = <<EOF -#!/bin/sh -if echo "${@}" | grep -q -- -r; then - exit 1 -fi -exec '#{modprobe_divert}' "${@}" -EOF - $vm.file_append('/sbin/modprobe', fake_modprobe_wrapper) - $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 - nr_nics = all_ethernet_nics.size - assert_equal(expected_nr_nics, nr_nics) -end - -Then /^the MAC spoofing panic mode disabled networking$/ do - nm_state = $vm.execute_successfully('systemctl show NetworkManager').stdout - nm_is_disabled = $vm.pidof('NetworkManager').empty? && - nm_state[/^LoadState=masked$/] && - nm_state[/^ActiveState=inactive$/] - assert(nm_is_disabled, "NetworkManager was not disabled") - all_ethernet_nics.each do |nic| - ["nic_ipv4_addr", "nic_ipv6_addr"].each do |function| - addr = $vm.execute_successfully( - "#{function} #{nic}", :libs => 'hardware' - ).stdout.chomp - assert_equal("", addr, "NIC #{nic} was assigned address #{addr}") - end - end -end diff --git a/features/step_definitions/pidgin.rb b/features/step_definitions/pidgin.rb deleted file mode 100644 index 3f5ed931..00000000 --- a/features/step_definitions/pidgin.rb +++ /dev/null @@ -1,467 +0,0 @@ -# Extracts the secrets for the XMMP account `account_name`. -def xmpp_account(account_name, required_options = []) - begin - account = $config["Pidgin"]["Accounts"]["XMPP"][account_name] - check_keys = ["username", "domain", "password"] + required_options - for key in check_keys do - assert(account.has_key?(key)) - assert_not_nil(account[key]) - assert(!account[key].empty?) - end - rescue NoMethodError, Test::Unit::AssertionFailedError - raise( -<<EOF -Your Pidgin:Accounts:XMPP:#{account} is incorrect or missing from your local configuration file (#{LOCAL_CONFIG_FILE}). See wiki/src/contribute/release_process/test/usage.mdwn for the format. -EOF -) - end - return account -end - -def wait_and_focus(img, time = 10, window) - begin - @screen.wait(img, time) - rescue FindFailed - $vm.focus_window(window) - @screen.wait(img, time) - end -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 - end -end - -When /^I create my XMPP account$/ do - account = xmpp_account("Tails_account") - @screen.click("PidginAccountManagerAddButton.png") - @screen.wait("PidginAddAccountWindow.png", 20) - @screen.click_mid_right_edge("PidginAddAccountProtocolLabel.png") - @screen.click("PidginAddAccountProtocolXMPP.png") - # We first wait for some field that is shown for XMPP but not the - # default (IRC) since we otherwise may decide where we click before - # the GUI has updated after switching protocol. - @screen.wait("PidginAddAccountXMPPDomain.png", 5) - @screen.click_mid_right_edge("PidginAddAccountXMPPUsername.png") - @screen.type(account["username"]) - @screen.click_mid_right_edge("PidginAddAccountXMPPDomain.png") - @screen.type(account["domain"]) - @screen.click_mid_right_edge("PidginAddAccountXMPPPassword.png") - @screen.type(account["password"]) - @screen.click("PidginAddAccountXMPPRememberPassword.png") - if account["connect_server"] - @screen.click("PidginAddAccountXMPPAdvancedTab.png") - @screen.click_mid_right_edge("PidginAddAccountXMPPConnectServer.png") - @screen.type(account["connect_server"]) - end - @screen.click("PidginAddAccountXMPPAddButton.png") -end - -Then /^Pidgin automatically enables my XMPP account$/ do - $vm.focus_window('Buddy List') - @screen.wait("PidginAvailableStatus.png", 60*3) -end - -Given /^my XMPP friend goes online( and joins the multi-user chat)?$/ do |join_chat| - account = xmpp_account("Friend_account", ["otr_key"]) - bot_opts = account.select { |k, v| ["connect_server"].include?(k) } - if join_chat - bot_opts["auto_join"] = [@chat_room_jid] - end - @friend_name = account["username"] - @chatbot = ChatBot.new(account["username"] + "@" + account["domain"], - account["password"], account["otr_key"], bot_opts) - @chatbot.start - add_after_scenario_hook { @chatbot.stop } - $vm.focus_window('Buddy List') - @screen.wait("PidginFriendOnline.png", 60) -end - -When /^I start a conversation with my friend$/ do - $vm.focus_window('Buddy List') - # Clicking the middle, bottom of this image should query our - # friend, given it's the only subscribed user that's online, which - # we assume. - r = @screen.find("PidginFriendOnline.png") - bottom_left = r.getBottomLeft() - x = bottom_left.getX + r.getW/2 - y = bottom_left.getY - @screen.doubleClick_point(x, y) - # Since Pidgin sets the window name to the contact, we have no good - # way to identify the conversation window. Let's just look for the - # expected menu bar. - @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 - if multi_chat - $vm.focus_window(@chat_room_jid.split("@").first) - msg = @friend_name + ": " + msg - else - $vm.focus_window(@friend_name) - end - @screen.type(msg) -end - -Then /^I receive a response from my friend( in the multi-user chat)?$/ do |multi_chat| - if multi_chat - $vm.focus_window(@chat_room_jid.split("@").first) - else - $vm.focus_window(@friend_name) - end - @screen.wait("PidginFriendExpectedAnswer.png", 20) -end - -When /^I start an OTR session with my friend$/ do - $vm.focus_window(@friend_name) - @screen.click("PidginConversationOTRMenu.png") - @screen.hide_cursor - @screen.click("PidginOTRMenuStartSession.png") -end - -Then /^Pidgin automatically generates an OTR key$/ do - @screen.wait("PidginOTRKeyGenPrompt.png", 30) - @screen.wait_and_click("PidginOTRKeyGenPromptDoneButton.png", 30) -end - -Then /^an OTR session was successfully started with my friend$/ do - $vm.focus_window(@friend_name) - @screen.wait("PidginConversationOTRUnverifiedSessionStarted.png", 10) -end - -# The reason the chat must be empty is to guarantee that we don't mix -# up messages/events from other users with the ones we expect from the -# bot. -When /^I join some empty multi-user chat$/ do - $vm.focus_window('Buddy List') - @screen.click("PidginBuddiesMenu.png") - @screen.wait_and_click("PidginBuddiesMenuJoinChat.png", 10) - @screen.wait_and_click("PidginJoinChatWindow.png", 10) - @screen.click_mid_right_edge("PidginJoinChatRoomLabel.png") - account = xmpp_account("Tails_account") - if account.has_key?("chat_room") && \ - !account["chat_room"].nil? && \ - !account["chat_room"].empty? - chat_room = account["chat_room"] - else - chat_room = random_alnum_string(10, 15) - end - @screen.type(chat_room) - - # We will need the conference server later, when starting the bot. - @screen.click_mid_right_edge("PidginJoinChatServerLabel.png") - @screen.type("a", Sikuli::KeyModifier.CTRL) - @screen.type("c", Sikuli::KeyModifier.CTRL) - conference_server = - $vm.execute_successfully("xclip -o", :user => LIVE_USER).stdout.chomp - @chat_room_jid = chat_room + "@" + conference_server - - @screen.click("PidginJoinChatButton.png") - # The following will both make sure that the we joined the chat, and - # that it is empty. We'll also deal with the *potential* "Create New - # Room" prompt that Pidgin shows for some server configurations. - images = ["PidginCreateNewRoomPrompt.png", - "PidginChat1UserInRoom.png"] - image_found, _ = @screen.waitAny(images, 30) - if image_found == "PidginCreateNewRoomPrompt.png" - @screen.click("PidginCreateNewRoomAcceptDefaultsButton.png") - end - $vm.focus_window(@chat_room_jid) - @screen.wait("PidginChat1UserInRoom.png", 10) -end - -# Since some servers save the scrollback, and sends it when joining, -# it's safer to clear it so we do not get false positives from old -# messages when looking for a particular response, or similar. -When /^I clear the multi-user chat's scrollback$/ do - $vm.focus_window(@chat_room_jid) - @screen.click("PidginConversationMenu.png") - @screen.wait_and_click("PidginConversationMenuClearScrollback.png", 10) -end - -Then /^I can see that my friend joined the multi-user chat$/ do - $vm.focus_window(@chat_room_jid) - @screen.wait("PidginChat2UsersInRoom.png", 60) -end - -def configured_pidgin_accounts - accounts = Hash.new - xml = REXML::Document.new($vm.file_content('$HOME/.purple/accounts.xml', - LIVE_USER)) - 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 - accounts[network] = { - 'name' => account_name, - 'network' => network, - 'protocol' => protocol, - 'port' => port, - 'nickname' => nickname, - 'real_name' => real_name, - } - end - - return accounts -end - -def chan_image (account, channel, image) - images = { - 'irc.oftc.net' => { - '#tails' => { - 'roster' => 'PidginTailsChannelEntry', - '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', - } - return chans[account] -end - -def pidgin_otr_keys - return $vm.file_content('$HOME/.purple/otr.private_key', LIVE_USER) -end - -Given /^Pidgin has the expected accounts configured with random nicknames$/ do - expected = [ - ["irc.oftc.net", "prpl-irc", "6697"], - ["127.0.0.1", "prpl-irc", "6668"], - ] - configured_pidgin_accounts.values.each() do |account| - assert(account['nickname'] != "XXX_NICK_XXX", "Nickname was no randomised") - assert_equal(account['nickname'], account['real_name'], - "Nickname and real name are not identical: " + - account['nickname'] + " vs. " + account['real_name']) - assert_equal(account['name'], account['nickname'], - "Account name and nickname are not identical: " + - account['name'] + " vs. " + account['nickname']) - candidate = [account['network'], account['protocol'], account['port']] - assert(expected.include?(candidate), "Unexpected account: #{candidate}") - expected.delete(candidate) - end - assert(expected.empty?, "These Pidgin accounts are not configured: " + - "#{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) - step "I see Pidgin's account manager window" -end - -When /^I see Pidgin's account manager window$/ do - @screen.wait("PidginAccountWindow.png", 40) -end - -When /^I close Pidgin's account manager window$/ do - @screen.wait_and_click("PidginAccountManagerCloseButton.png", 10) -end - -When /^I (de)?activate the "([^"]+)" Pidgin account$/ do |deactivate, account| - @screen.click("PidginAccount_#{account}.png") - @screen.type(Sikuli::Key.LEFT + Sikuli::Key.SPACE) - if deactivate - @screen.waitVanish('PidginAccountEnabledCheckbox.png', 5) - else - # wait for the Pidgin to be connecting, otherwise sometimes the step - # that closes the account management dialog happens before the account - # is actually enabled - @screen.waitAny(['PidginConnecting.png', 'PidginAvailableStatus.png'], 5) - end -end - -def deactivate_and_activate_pidgin_account(account) - debug_log("Deactivating and reactivating Pidgin account #{account}") - step "I open Pidgin's account manager window" - step "I deactivate the \"#{account}\" Pidgin account" - step "I close Pidgin's account manager window" - step "I open Pidgin's account manager window" - step "I activate the \"#{account}\" Pidgin account" - step "I close Pidgin's account manager window" -end - - - -Then /^Pidgin successfully connects to the "([^"]+)" account$/ do |account| - expected_channel_entry = chan_image(account, default_chan(account), 'roster') - reconnect_button = 'PidginReconnect.png' - recovery_on_failure = Proc.new do - if @screen.exists('PidginReconnect.png') - @screen.click('PidginReconnect.png') - else - 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 - begin - $vm.focus_window('Buddy List') - rescue ExecutionFailedInVM - # Sometimes focusing the window with xdotool will fail with the - # conversation window right on top of it. We'll try to close the - # conversation window. At worst, the test will still fail... - close_pidgin_conversation_window(account) - end - on_screen, _ = @screen.waitAny([expected_channel_entry, reconnect_button], 60) - unless on_screen == expected_channel_entry - raise "Connecting to account #{account} failed." - end - end -end - -Then /^the "([^"]*)" account only responds to PING and VERSION CTCP requests$/ do |irc_server| - ctcp_cmds = [ - "CLIENTINFO", "DATE", "ERRMSG", "FINGER", "PING", "SOURCE", "TIME", - "USERINFO", "VERSION" - ] - expected_ctcp_replies = { - "PING" => /^\d+$/, - "VERSION" => /^Purple IRC$/ - } - spam_target = configured_pidgin_accounts[irc_server]["nickname"] - ctcp_check = CtcpChecker.new(irc_server, 6667, spam_target, ctcp_cmds, - expected_ctcp_replies) - ctcp_check.verify_ctcp_responses -end - -Then /^I can join the "([^"]+)" channel on "([^"]+)"$/ do |channel, account| - @screen.doubleClick( chan_image(account, channel, 'roster')) - @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) - rescue FindFailed => e - # If the channel tab can't be found it could be because there were - # multiple connection attempts and the channel tab we want is off the - # screen. We'll try closing tabs until the one we want can be found. - @screen.type("w", Sikuli::KeyModifier.CTRL) - raise e - end - end - @screen.hide_cursor - @screen.wait( chan_image(account, channel, 'welcome'), 10) -end - -Then /^I take note of the configured Pidgin accounts$/ do - @persistent_pidgin_accounts = configured_pidgin_accounts -end - -Then /^I take note of the OTR key for Pidgin's "([^"]+)" account$/ do |account_name| - @persistent_pidgin_otr_keys = pidgin_otr_keys -end - -Then /^Pidgin has the expected persistent accounts configured$/ do - current_accounts = configured_pidgin_accounts - assert(current_accounts <=> @persistent_pidgin_accounts, - "Currently configured Pidgin accounts do not match the persistent ones:\n" + - "Current:\n#{current_accounts}\n" + - "Persistent:\n#{@persistent_pidgin_accounts}" - ) -end - -Then /^Pidgin has the expected persistent OTR keys$/ do - assert_equal(pidgin_otr_keys, @persistent_pidgin_otr_keys) -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\"" - - $vm.focus_window('Buddy List') - @screen.wait_and_click('PidginToolsMenu.png', 10) - @screen.wait_and_click('PidginCertificatesMenuItem.png', 10) - @screen.wait('PidginCertificateManagerDialog.png', 10) - @screen.wait_and_click('PidginCertificateAddButton.png', 10) - begin - @screen.wait_and_click('GtkFileChooserDesktopButton.png', 10) - rescue FindFailed - # The first time we're run, the file chooser opens in the Recent - # view, so we have to browse a directory before we can use the - # "Type file name" button. But on subsequent runs, the file - # chooser is already in the Desktop directory, so we don't need to - # do anything. Hence, this noop exception handler. - end - @screen.wait_and_click('GtkFileTypeFileNameButton.png', 10) - @screen.type("l", Sikuli::KeyModifier.ALT) # "Location" field - @screen.type(cert_file + Sikuli::Key.ENTER) -end - -Then /^I can add a certificate from the "([^"]+)" directory to Pidgin$/ do |cert_dir| - pidgin_add_certificate_from("#{cert_dir}/test.crt") - wait_and_focus('PidginCertificateAddHostnameDialog.png', 10, 'Certificate Import') - @screen.type("XXX test XXX" + Sikuli::Key.ENTER) - wait_and_focus('PidginCertificateTestItem.png', 10, 'Certificate Manager') -end - -Then /^I cannot add a certificate from the "([^"]+)" directory to Pidgin$/ do |cert_dir| - pidgin_add_certificate_from("#{cert_dir}/test.crt") - wait_and_focus('PidginCertificateImportFailed.png', 10, 'Import Error') -end - -When /^I close Pidgin's certificate manager$/ do - wait_and_focus('PidginCertificateManagerDialog.png', 10, 'Certificate Manager') - @screen.type(Sikuli::Key.ESC) - # @screen.wait_and_click('PidginCertificateManagerClose.png', 10) - @screen.waitVanish('PidginCertificateManagerDialog.png', 10) -end - -When /^I close Pidgin's certificate import failure dialog$/ do - @screen.type(Sikuli::Key.ESC) - # @screen.wait_and_click('PidginCertificateManagerClose.png', 10) - @screen.waitVanish('PidginCertificateImportFailed.png', 10) -end - -When /^I see the Tails roadmap URL$/ do - try_for(60) do - begin - @screen.find('PidginTailsRoadmapUrl.png') - rescue FindFailed => e - @screen.type(Sikuli::Key.PAGE_UP) - raise e - end - end -end - -When /^I click on the Tails roadmap URL$/ do - @screen.click('PidginTailsRoadmapUrl.png') -end diff --git a/features/step_definitions/po.rb b/features/step_definitions/po.rb deleted file mode 100644 index c73bacef..00000000 --- a/features/step_definitions/po.rb +++ /dev/null @@ -1,8 +0,0 @@ -Given /^I am in the Git branch being tested$/ do - Dir.chdir(GIT_DIR) -end - -Then /^all the PO files should be correct$/ do - File.exists?('./submodules/jenkins-tools/slaves/check_po') - cmd_helper(['./submodules/jenkins-tools/slaves/check_po']) -end diff --git a/features/step_definitions/root_access_control.rb b/features/step_definitions/root_access_control.rb deleted file mode 100644 index ff1bdfcc..00000000 --- a/features/step_definitions/root_access_control.rb +++ /dev/null @@ -1,42 +0,0 @@ -Then /^I should be able to run administration commands as the live user$/ do - stdout = $vm.execute("echo #{@sudo_password} | sudo -S whoami", - :user => LIVE_USER).stdout - actual_user = stdout.sub(/^\[sudo\] password for #{LIVE_USER}: /, "").chomp - assert_equal("root", actual_user, "Could not use sudo") -end - -Then /^I should not be able to run administration commands as the live user with the "([^"]*)" password$/ do |password| - stderr = $vm.execute("echo #{password} | sudo -S whoami", - :user => LIVE_USER).stderr - sudo_failed = stderr.include?("The administration password is disabled") || stderr.include?("is not allowed to execute") - assert(sudo_failed, "The administration password is not disabled:" + stderr) -end - -When /^running a command as root with pkexec requires PolicyKit administrator privileges$/ do - action = 'org.freedesktop.policykit.exec' - action_details = $vm.execute("pkaction --verbose --action-id #{action}").stdout - assert(action_details[/\s+implicit any:\s+auth_admin$/], - "Expected 'auth_admin' for 'any':\n#{action_details}") - assert(action_details[/\s+implicit inactive:\s+auth_admin$/], - "Expected 'auth_admin' for 'inactive':\n#{action_details}") - assert(action_details[/\s+implicit active:\s+auth_admin$/], - "Expected 'auth_admin' for 'active':\n#{action_details}") -end - -Then /^I should be able to run a command as root with pkexec$/ do - step "I run \"pkexec touch /root/pkexec-test\" in GNOME Terminal" - step 'I enter the sudo password in the pkexec prompt' - try_for(10, :msg => 'The /root/pkexec-test file was not created.') { - $vm.execute('ls /root/pkexec-test').success? - } -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) - end - @screen.type(Sikuli::Key.ESC) - @screen.wait('PolicyKitAuthCompleteFailure.png', 20) -end diff --git a/features/step_definitions/snapshots.rb b/features/step_definitions/snapshots.rb deleted file mode 100644 index 13e4a5b6..00000000 --- a/features/step_definitions/snapshots.rb +++ /dev/null @@ -1,257 +0,0 @@ -def checkpoints - { - 'boot-d-i-to-tasksel' => { - :description => "I have started Debian Installer and stopped at the Tasksel prompt", - #:parent_checkpoint => 'no-network-logged-in', - :steps => [ - 'I create a 8 GiB disk named "target"', - 'I plug ide drive "target"', - 'I start the computer', - 'the computer boots DebianInstaller', - 'I select British English', - 'I accept the hostname, using "example.com" as the domain', - 'I set the root password to "rootme"', - 'I set the password for "Philip Hands" to be "verysecret"', - 'I select full-disk, single-filesystem partitioning', - 'I note that the Base system is being installed', - 'I accept the default mirror', - 'I ignore Popcon', - 'we reach the Tasksel prompt', - ], - }, - - 'debian-minimal-install' => { - :description => "I have installed Minimal Debian", - :parent_checkpoint => 'boot-d-i-to-tasksel', - :steps => [ - 'I hit ENTER', - 'I install GRUB', - '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 "target"', - ], - }, - - 'debian-gnome-install' => { - :description => "I have installed Gnome Desktop Debian", - :parent_checkpoint => 'boot-d-i-to-tasksel', - :steps => [ - 'I select the Desktop task', - 'I install GRUB', - '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 "target"', - ], - }, - - 'tails-greeter' => { - :description => "I have started Tails from DVD without network and stopped at Tails Greeter's login screen", - :parent_checkpoint => nil, - :steps => [ - 'the network is unplugged', - 'I start the computer', - 'the computer boots Tails' - ], - }, - - 'no-network-logged-in' => { - :description => "I have started Tails from DVD without network and logged in", - :parent_checkpoint => "tails-greeter", - :steps => [ - 'I log in to a new session', - 'Tails Greeter has dealt with the sudo password', - 'the Tails desktop is ready', - ], - }, - - 'with-no-network-and-i2p' => { - :temporary => true, - :description => 'I have started Tails from DVD with I2P enabled and logged in', - :steps => [ - 'I set Tails to boot with options "i2p"', - 'the network is unplugged', - 'I start the computer', - 'the computer boots Tails', - 'I log in to a new session', - 'the Tails desktop is ready', - ], - }, - - 'with-network-and-i2p' => { - :temporary => true, - :description => 'I have started Tails from DVD with I2P enabled and logged in and the network is connected', - :parent_checkpoint => "with-no-network-and-i2p", - :steps => [ - 'the network is plugged', - 'Tor is ready', - 'I2P is running', - 'all notifications have disappeared', - 'available upgrades have been checked', - "I2P's reseeding completed", - ], - }, - - 'with-network-logged-in' => { - :description => "I have started Tails from DVD and logged in and the network is connected", - :parent_checkpoint => "no-network-logged-in", - :steps => [ - 'the network is plugged', - 'Tor is ready', - 'all notifications have disappeared', - 'available upgrades have been checked', - ], - }, - - 'no-network-bridge-mode' => { - :temporary => true, - :description => "I have started Tails from DVD without network and logged in with bridge mode enabled", - :parent_checkpoint => "tails-greeter", - :steps => [ - 'I enable more Tails Greeter options', - 'I enable the specific Tor configuration option', - 'I log in to a new session', - 'Tails Greeter has dealt with the sudo password', - 'the Tails desktop is ready', - 'all notifications have disappeared', - ], - }, - - 'no-network-logged-in-sudo-passwd' => { - :temporary => true, - :description => "I have started Tails from DVD without network and logged in with an administration password", - :parent_checkpoint => "tails-greeter", - :steps => [ - 'I enable more Tails Greeter options', - 'I set an administration password', - 'I log in to a new session', - 'Tails Greeter has dealt with the sudo password', - 'the Tails desktop is ready', - ], - }, - - 'with-network-logged-in-sudo-passwd' => { - :temporary => true, - :description => "I have started Tails from DVD and logged in with an administration password and the network is connected", - :parent_checkpoint => "no-network-logged-in-sudo-passwd", - :steps => [ - 'the network is plugged', - 'Tor is ready', - 'all notifications have disappeared', - 'available upgrades have been checked', - ], - }, - - 'usb-install-tails-greeter' => { - :description => "I have started Tails without network from a USB drive without a persistent partition and stopped at Tails Greeter's login screen", - :parent_checkpoint => 'no-network-logged-in', - :steps => [ - 'I create a 4 GiB disk named "__internal"', - 'I plug USB drive "__internal"', - 'I "Clone & Install" Tails to USB drive "__internal"', - 'the running Tails is installed on USB drive "__internal"', - 'there is no persistence partition on USB drive "__internal"', - 'I shutdown Tails and wait for the computer to power off', - 'I start Tails from USB drive "__internal" with network unplugged', - 'the boot device has safe access rights', - 'Tails is running from USB drive "__internal"', - 'there is no persistence partition on USB drive "__internal"', - 'process "udev-watchdog" is running', - 'udev-watchdog is monitoring the correct device', - ], - }, - - 'usb-install-logged-in' => { - :description => "I have started Tails without network from a USB drive without a persistent partition and logged in", - :parent_checkpoint => 'usb-install-tails-greeter', - :steps => [ - 'I log in to a new session', - 'the Tails desktop is ready', - ], - }, - - 'usb-install-with-persistence-tails-greeter' => { - :description => "I have started Tails without network from a USB drive with a persistent partition and stopped at Tails Greeter's login screen", - :parent_checkpoint => 'usb-install-logged-in', - :steps => [ - 'I create a persistent partition', - 'a Tails persistence partition exists on USB drive "__internal"', - 'I shutdown Tails and wait for the computer to power off', - 'I start Tails from USB drive "__internal" with network unplugged', - 'the boot device has safe access rights', - 'Tails is running from USB drive "__internal"', - 'process "udev-watchdog" is running', - 'udev-watchdog is monitoring the correct device', - ], - }, - - 'usb-install-with-persistence-logged-in' => { - :description => "I have started Tails without network from a USB drive with a persistent partition enabled and logged in", - :parent_checkpoint => 'usb-install-with-persistence-tails-greeter', - :steps => [ - 'I enable persistence', - 'I log in to a new session', - 'the Tails desktop is ready', - 'all persistence presets are enabled', - 'all persistent filesystems have safe access rights', - 'all persistence configuration files have safe access rights', - 'all persistent directories have safe access rights', - ], - }, - } -end - -def reach_checkpoint(name) - scenario_indent = " "*4 - step_indent = " "*6 - - step "a computer" - if VM.snapshot_exists?(name) - $vm.restore_snapshot(name) - post_snapshot_restore_hook - else - checkpoint = checkpoints[name] - checkpoint_description = checkpoint[:description] - parent_checkpoint = checkpoint[:parent_checkpoint] - steps = checkpoint[:steps] - if parent_checkpoint - if VM.snapshot_exists?(parent_checkpoint) - $vm.restore_snapshot(parent_checkpoint) - else - reach_checkpoint(parent_checkpoint) - end - post_snapshot_restore_hook - end - debug_log(scenario_indent + "Checkpoint: #{checkpoint_description}", - :color => :white) - step_action = "Given" - if parent_checkpoint - parent_description = checkpoints[parent_checkpoint][:description] - debug_log(step_indent + "#{step_action} #{parent_description}", - :color => :green) - step_action = "And" - end - steps.each do |s| - begin - step(s) - rescue Exception => e - debug_log(scenario_indent + - "Step failed while creating checkpoint: #{s}", - :color => :red) - raise e - end - debug_log(step_indent + "#{step_action} #{s}", :color => :green) - step_action = "And" - end - $vm.save_snapshot(name) - end -end - -# For each checkpoint we generate a step to reach it. -checkpoints.each do |name, desc| - step_regex = Regexp.new("^#{Regexp.escape(desc[:description])}$") - Given step_regex do - reach_checkpoint(name) - end -end diff --git a/features/step_definitions/ssh.rb b/features/step_definitions/ssh.rb deleted file mode 100644 index 038b2977..00000000 --- a/features/step_definitions/ssh.rb +++ /dev/null @@ -1,122 +0,0 @@ -require 'socket' - -def assert_not_ipaddr(s) - err_msg = "'#{s}' looks like a LAN IP address." - assert_raise(IPAddr::InvalidAddressError, err_msg) do - IPAddr.new(s) - end -end - -def read_and_validate_ssh_config srv_type - conf = $config[srv_type] - begin - required_settings = ["private_key", "public_key", "username", "hostname"] - required_settings.each do |key| - assert(conf.has_key?(key)) - assert_not_nil(conf[key]) - assert(!conf[key].empty?) - end - rescue NoMethodError - raise( - <<EOF -Your #{srv_type} config is incorrect or missing from your local configuration file (#{LOCAL_CONFIG_FILE}). See wiki/src/contribute/release_process/test/usage.mdwn for the format. -EOF - ) - end - - case srv_type - when 'SSH' - @ssh_host = conf["hostname"] - @ssh_port = conf["port"].to_i if conf["port"] - @ssh_username = conf["username"] - assert_not_ipaddr(@ssh_host) - when 'SFTP' - @sftp_host = conf["hostname"] - @sftp_port = conf["port"].to_i if conf["port"] - @sftp_username = conf["username"] - assert_not_ipaddr(@sftp_host) - end -end - -Given /^I have the SSH key pair for an? (Git|SSH|SFTP) (?:repository|server)( on the LAN)?$/ do |server_type, lan| - $vm.execute_successfully("install -m 0700 -d '/home/#{LIVE_USER}/.ssh/'", - :user => LIVE_USER) - unless server_type == 'Git' || lan - read_and_validate_ssh_config server_type - secret_key = $config[server_type]["private_key"] - public_key = $config[server_type]["public_key"] - else - secret_key = $config["Unsafe_SSH_private_key"] - public_key = $config["Unsafe_SSH_public_key"] - end - - $vm.execute_successfully("echo '#{secret_key}' > '/home/#{LIVE_USER}/.ssh/id_rsa'", - :user => LIVE_USER) - $vm.execute_successfully("echo '#{public_key}' > '/home/#{LIVE_USER}/.ssh/id_rsa.pub'", - :user => LIVE_USER) - $vm.execute_successfully("chmod 0600 '/home/#{LIVE_USER}/.ssh/'id*", - :user => LIVE_USER) -end - -Given /^I (?:am prompted to )?verify the SSH fingerprint for the (?:Git|SSH) (?:repository|server)$/ do - @screen.wait("SSHFingerprint.png", 60) - @screen.type('yes' + Sikuli::Key.ENTER) -end - -def get_free_tcp_port - server = TCPServer.new('127.0.0.1', 0) - return server.addr[1] -ensure - server.close -end - -Given /^an SSH server is running on the LAN$/ do - @sshd_server_port = get_free_tcp_port - @sshd_server_host = $vmnet.bridge_ip_addr - sshd = SSHServer.new(@sshd_server_host, @sshd_server_port) - sshd.start - add_after_scenario_hook { sshd.stop } -end - -When /^I connect to an SSH server on the (Internet|LAN)$/ do |location| - - case location - when 'Internet' - read_and_validate_ssh_config "SSH" - when 'LAN' - @ssh_port = @sshd_server_port - @ssh_username = 'user' - @ssh_host = @sshd_server_host - end - - ssh_port_suffix = "-p #{@ssh_port}" if @ssh_port - - 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' -end - -Then /^I have sucessfully logged into the SSH server$/ do - @screen.wait('SSHLoggedInPrompt.png', 60) -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) -end - -Then /^I verify the SSH fingerprint for the SFTP server$/ do - @screen.wait_and_click("GnomeSSHVerificationConfirm.png", 60) -end - -Then /^I successfully connect to the SFTP server$/ do - @screen.wait("GnomeSSHSuccess.png", 60) -end diff --git a/features/step_definitions/time_syncing.rb b/features/step_definitions/time_syncing.rb deleted file mode 100644 index 319fb521..00000000 --- a/features/step_definitions/time_syncing.rb +++ /dev/null @@ -1,86 +0,0 @@ -# In some steps below we allow some slack when verifying that the date -# was set appropriately because it may take time to send the `date` -# command over the remote shell and get the answer back, parsing and -# post-processing of the result, etc. -def max_time_drift - 10 -end - -When /^I set the system time to "([^"]+)"$/ do |time| - $vm.execute_successfully("date -s '#{time}'") - new_time = DateTime.parse($vm.execute_successfully("date").stdout).to_time - expected_time_lower_bound = DateTime.parse(time).to_time - expected_time_upper_bound = expected_time_lower_bound + max_time_drift - assert(expected_time_lower_bound <= new_time && - new_time <= expected_time_upper_bound, - "The guest's time was supposed to be set to " \ - "'#{expected_time_lower_bound}' but is '#{new_time}'") -end - -When /^I bump the (hardware clock's|system) time with "([^"]+)"$/ do |clock_type, timediff| - case clock_type - when "hardware clock's" - old_time = DateTime.parse($vm.execute_successfully("hwclock -r").stdout).to_time - $vm.execute_successfully("hwclock --set --date 'now #{timediff}'") - new_time = DateTime.parse($vm.execute_successfully("hwclock -r").stdout).to_time - when 'system' - old_time = DateTime.parse($vm.execute_successfully("date").stdout).to_time - $vm.execute_successfully("date -s 'now #{timediff}'") - new_time = DateTime.parse($vm.execute_successfully("date").stdout).to_time - end - expected_time_lower_bound = DateTime.parse( - cmd_helper(["date", "-d", "#{old_time} #{timediff}"])).to_time - expected_time_upper_bound = expected_time_lower_bound + max_time_drift - assert(expected_time_lower_bound <= new_time && - new_time <= expected_time_upper_bound, - "The #{clock_type} time was supposed to be bumped to " \ - "'#{expected_time_lower_bound}' but is '#{new_time}'") -end - -Then /^Tails clock is less than (\d+) minutes incorrect$/ do |max_diff_mins| - guest_time_str = $vm.execute("date --rfc-2822").stdout.chomp - guest_time = Time.rfc2822(guest_time_str) - host_time = Time.now - diff = (host_time - guest_time).abs - assert(diff < max_diff_mins.to_i*60, - "The guest's clock is off by #{diff} seconds (#{guest_time})") - puts "Time was #{diff} seconds off" -end - -Then /^the system clock is just past Tails' build 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 - # 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})") - assert(diff <= max_diff, - "The system time (#{system_time}) is more than #{max_diff} seconds " + - "past the build date (#{build_time})") -end - -Then /^Tails' hardware clock is close to the host system's time$/ do - host_time = Time.now - hwclock_time_str = $vm.execute('hwclock -r').stdout.chomp - hwclock_time = DateTime.parse(hwclock_time_str).to_time - diff = (hwclock_time - host_time).abs - assert(diff <= max_time_drift) -end - -Then /^the hardware clock is still off by "([^"]+)"$/ do |timediff| - hwclock = DateTime.parse($vm.execute_successfully("hwclock -r").stdout.chomp).to_time - expected_time_lower_bound = DateTime.parse( - cmd_helper(["date", "-d", "now #{timediff}"])).to_time - max_time_drift - expected_time_upper_bound = expected_time_lower_bound + max_time_drift - assert(expected_time_lower_bound <= hwclock && - hwclock <= expected_time_upper_bound, - "The host's hwclock should be approximately " \ - "'#{expected_time_lower_bound}' but is actually '#{hwclock}'") -end diff --git a/features/step_definitions/tor.rb b/features/step_definitions/tor.rb deleted file mode 100644 index ac12fd4c..00000000 --- a/features/step_definitions/tor.rb +++ /dev/null @@ -1,402 +0,0 @@ -def iptables_chains_parse(iptables, table = "filter", &block) - assert(block_given?) - cmd = "#{iptables}-save -c -t #{table} | iptables-xml" - xml_str = $vm.execute_successfully(cmd).stdout - rexml = REXML::Document.new(xml_str) - rexml.get_elements('iptables-rules/table/chain').each do |element| - yield( - element.attribute('name').to_s, - element.attribute('policy').to_s, - element.get_elements('rule') - ) - end -end - -def ip4tables_chains(table = "filter", &block) - iptables_chains_parse('iptables', table, &block) -end - -def ip6tables_chains(table = "filter", &block) - iptables_chains_parse('ip6tables', table, &block) -end - -def iptables_rules_parse(iptables, chain, table) - iptables_chains_parse(iptables, table) do |name, _, rules| - return rules if name == chain - end - return nil -end - -def iptables_rules(chain, table = "filter") - iptables_rules_parse("iptables", chain, table) -end - -def ip6tables_rules(chain, table = "filter") - iptables_rules_parse("ip6tables", chain, table) -end - -def ip4tables_packet_counter_sum(filters = {}) - pkts = 0 - ip4tables_chains do |name, _, rules| - next if filters[:tables] && not(filters[:tables].include?(name)) - rules.each do |rule| - next if filters[:uid] && not(rule.elements["conditions/owner/uid-owner[text()=#{filters[:uid]}]"]) - pkts += rule.attribute('packet-count').to_s.to_i - end - end - return pkts -end - -def try_xml_element_text(element, xpath, default = nil) - node = element.elements[xpath] - (node.nil? or not(node.has_text?)) ? default : node.text -end - -Then /^the firewall's policy is to (.+) all IPv4 traffic$/ do |expected_policy| - expected_policy.upcase! - ip4tables_chains do |name, policy, _| - if ["INPUT", "FORWARD", "OUTPUT"].include?(name) - assert_equal(expected_policy, policy, - "Chain #{name} has unexpected policy #{policy}") - end - end -end - -Then /^the firewall is configured to only allow the (.+) users? to connect directly to the Internet over IPv4$/ do |users_str| - users = users_str.split(/, | and /) - expected_uids = Set.new - users.each do |user| - expected_uids << $vm.execute_successfully("id -u #{user}").stdout.to_i - end - allowed_output = iptables_rules("OUTPUT").find_all do |rule| - out_iface = rule.elements['conditions/match/o'] - is_maybe_accepted = rule.get_elements('actions/*').find do |action| - not(["DROP", "REJECT", "LOG"].include?(action.name)) - end - is_maybe_accepted && - ( - # nil => match all interfaces according to iptables-xml - out_iface.nil? || - ((out_iface.text == 'lo') == (out_iface.attribute('invert').to_s == '1')) - ) - end - uids = Set.new - allowed_output.each do |rule| - rule.elements.each('actions/*') do |action| - destination = try_xml_element_text(rule, "conditions/match/d") - if action.name == "ACCEPT" - # nil == 0.0.0.0/0 according to iptables-xml - assert(destination == '0.0.0.0/0' || destination.nil?, - "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" - assert_not_nil(rule.elements['conditions/owner/uid-owner']) - rule.elements.each('conditions/owner/uid-owner') do |owner| - uid = owner.text.to_i - uids << uid - assert(expected_uids.include?(uid), - "The following rule allows uid #{uid} to access the " + - "network, but we only expect uids #{expected_uids.to_a} " + - "(#{users_str}) to have such access:\n#{rule.to_s}") - end - elsif action.name == "call" && action.elements[1].name == "lan" - lan_subnets = ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"] - assert(lan_subnets.include?(destination), - "The following lan-targeted rule's destination is " + - "#{destination} which may not be a private subnet:\n" + - rule.to_s) - else - raise "Unexpected iptables OUTPUT chain rule:\n#{rule.to_s}" - end - end - end - uids_not_found = expected_uids - uids - assert(uids_not_found.empty?, - "Couldn't find rules allowing uids #{uids_not_found.to_a.to_s} " \ - "access to the network") -end - -Then /^the firewall's NAT rules only redirect traffic for Tor's TransPort and DNSPort$/ do - loopback_address = "127.0.0.1/32" - tor_onion_addr_space = "127.192.0.0/10" - tor_trans_port = "9040" - dns_port = "53" - tor_dns_port = "5353" - ip4tables_chains('nat') do |name, _, rules| - if name == "OUTPUT" - good_rules = rules.find_all do |rule| - redirect = rule.get_elements('actions/*').all? do |action| - action.name == "REDIRECT" - end - destination = try_xml_element_text(rule, "conditions/match/d") - redir_port = try_xml_element_text(rule, "actions/REDIRECT/to-ports") - redirected_to_trans_port = redir_port == tor_trans_port - udp_destination_port = try_xml_element_text(rule, "conditions/udp/dport") - dns_redirected_to_tor_dns_port = (udp_destination_port == dns_port) && - (redir_port == tor_dns_port) - redirect && - ( - (destination == tor_onion_addr_space && redirected_to_trans_port) || - (destination == loopback_address && dns_redirected_to_tor_dns_port) - ) - end - bad_rules = rules - good_rules - assert(bad_rules.empty?, - "The NAT table's OUTPUT chain contains some unexpected " + - "rules:\n#{bad_rules}") - else - assert(rules.empty?, - "The NAT table contains unexpected rules for the #{name} " + - "chain:\n#{rules}") - end - end -end - -Then /^the firewall is configured to block all external IPv6 traffic$/ do - ip6_loopback = '::1/128' - expected_policy = "DROP" - ip6tables_chains do |name, policy, rules| - assert_equal(expected_policy, policy, - "The IPv6 #{name} chain has policy #{policy} but we " \ - "expected #{expected_policy}") - good_rules = rules.find_all do |rule| - ["DROP", "REJECT", "LOG"].any? do |target| - rule.elements["actions/#{target}"] - end \ - || - ["s", "d"].all? do |x| - try_xml_element_text(rule, "conditions/match/#{x}") == ip6_loopback - end - end - bad_rules = rules - good_rules - assert(bad_rules.empty?, - "The IPv6 table's #{name} chain contains some unexpected rules:\n" + - (bad_rules.map { |r| r.to_s }).join("\n")) - end -end - -def firewall_has_dropped_packet_to?(proto, host, port) - regex = "^Dropped outbound packet: .* " - regex += "DST=#{Regexp.escape(host)} .* " - regex += "PROTO=#{Regexp.escape(proto)} " - regex += ".* DPT=#{port} " if 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| - assert(!firewall_has_dropped_packet_to?(proto, host, port), - "A #{proto} packet to #{host}" + - (port.nil? ? "" : ":#{port}") + - " has already been dropped by the firewall") - @conn_proto = proto - @conn_host = host - @conn_port = port - case proto - when "TCP" - assert_not_nil(port) - cmd = "echo | netcat #{host} #{port}" - user = LIVE_USER - when "UDP" - assert_not_nil(port) - cmd = "echo | netcat -u #{host} #{port}" - user = LIVE_USER - when "ICMP" - cmd = "ping -c 5 #{host}" - user = 'root' - end - @conn_res = $vm.execute(cmd, :user => user) -end - -Then /^the untorified connection fails$/ do - case @conn_proto - when "TCP" - expected_in_stderr = "Connection refused" - conn_failed = !@conn_res.success? && - @conn_res.stderr.chomp.end_with?(expected_in_stderr) - when "UDP", "ICMP" - conn_failed = !@conn_res.success? - end - assert(conn_failed, - "The untorified #{@conn_proto} connection didn't fail as expected:\n" + - @conn_res.to_s) -end - -Then /^the untorified connection is logged as dropped by the firewall$/ do - assert(firewall_has_dropped_packet_to?(@conn_proto, @conn_host, @conn_port), - "No #{@conn_proto} packet to #{@conn_host}" + - (@conn_port.nil? ? "" : ":#{@conn_port}") + - " was dropped by the firewall") -end - -When /^the system DNS is(?: still)? using the local DNS resolver$/ do - resolvconf = $vm.file_content("/etc/resolv.conf") - bad_lines = resolvconf.split("\n").find_all do |line| - !line.start_with?("#") && !/^nameserver\s+127\.0\.0\.1$/.match(line) - end - assert_empty(bad_lines, - "The following bad lines were found in /etc/resolv.conf:\n" + - bad_lines.join("\n")) -end - -def stream_isolation_info(application) - case application - when "htpdate" - { - :grep_monitor_expr => '/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 - { - :grep_monitor_expr => '\<ESTABLISHED\>.\+/perl\>', - :socksport => 9062 - } - when "Tor Browser" - { - :grep_monitor_expr => '/firefox\>', - :socksport => 9150 - } - when "Gobby" - { - :grep_monitor_expr => '/gobby\>', - :socksport => 9050 - } - when "SSH" - { - :grep_monitor_expr => '/\(connect-proxy\|ssh\)\>', - :socksport => 9050 - } - when "whois" - { - :grep_monitor_expr => '/whois\>', - :socksport => 9050 - } - else - raise "Unknown application '#{application}' for the stream isolation tests" - end -end - -When /^I monitor the network connections of (.*)$/ do |application| - @process_monitor_log = "/tmp/netstat.log" - info = stream_isolation_info(application) - $vm.spawn("while true; do " + - " netstat -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] - 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}") - end -end - -And /^I re-run tails-security-check$/ do - $vm.execute_successfully("tails-security-check", :user => LIVE_USER) -end - -And /^I re-run htpdate$/ do - $vm.execute_successfully("service htpdate stop && " \ - "rm -f /var/run/htpdate/* && " \ - "systemctl --no-block start htpdate.service") - step "the time has synced" -end - -And /^I re-run tails-upgrade-frontend-wrapper$/ do - $vm.execute_successfully("tails-upgrade-frontend-wrapper", :user => LIVE_USER) -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) - # 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) - @screen.type("t", Sikuli::KeyModifier.CTRL) - @screen.wait("GobbyConnectPrompt.png", 10) - @screen.type(host + Sikuli::Key.ENTER) - @screen.wait("GobbyConnectionComplete.png", 60) -end - -When /^the Tor Launcher autostarts$/ do - @screen.wait('TorLauncherWindow.png', 60) -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"] - @screen.type(bridge_line + Sikuli::Key.ENTER) - end - @screen.wait_and_click('TorLauncherNextButton.png', 10) - @screen.hide_cursor - @screen.wait_and_click('TorLauncherFinishButton.png', 10) - @screen.wait('TorLauncherConnectingWindow.png', 10) - @screen.waitVanish('TorLauncherConnectingWindow.png', 120) -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 - 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/features/step_definitions/torified_browsing.rb b/features/step_definitions/torified_browsing.rb deleted file mode 100644 index c8f3ff1d..00000000 --- a/features/step_definitions/torified_browsing.rb +++ /dev/null @@ -1,5 +0,0 @@ -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}") -end diff --git a/features/step_definitions/torified_gnupg.rb b/features/step_definitions/torified_gnupg.rb deleted file mode 100644 index 4b4cc040..00000000 --- a/features/step_definitions/torified_gnupg.rb +++ /dev/null @@ -1,208 +0,0 @@ -class OpenPGPKeyserverCommunicationError < StandardError -end - -def count_gpg_signatures(key) - output = $vm.execute_successfully("gpg --batch --list-sigs #{key}", - :user => LIVE_USER).stdout - output.scan(/^sig/).count -end - -def check_for_seahorse_error - if @screen.exists('GnomeCloseButton.png') - raise OpenPGPKeyserverCommunicationError.new( - "Found GnomeCloseButton.png' on the screen" - ) - end -end - -def start_or_restart_seahorse - assert_not_nil(@withgpgapplet) - if @withgpgapplet - seahorse_menu_click_helper('GpgAppletIconNormal.png', 'GpgAppletManageKeys.png') - else - step 'I start "Seahorse" via the GNOME "Utilities" applications menu' - end - step 'Seahorse has opened' -end - -Then /^the key "([^"]+)" has (only|more than) (\d+) signatures$/ do |key, qualifier, num| - count = count_gpg_signatures(key) - case qualifier - when 'only' - assert_equal(count, num.to_i, "Expected #{num} signatures but instead found #{count}") - when 'more than' - assert(count > num.to_i, "Expected more than #{num} signatures but found #{count}") - else - raise "Unknown operator #{qualifier} passed" - end -end - -When /^the "([^"]+)" OpenPGP key is not in the live user's public keyring$/ do |keyid| - assert(!$vm.execute("gpg --batch --list-keys '#{keyid}'", - :user => LIVE_USER).success?, - "The '#{keyid}' key is in the live user's public keyring.") -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. - @fetched_openpgp_keyid = keyid - if without - importopts = '--keyserver-options import-clean' - else - importopts = '' - end - retry_tor do - @gnupg_recv_key_res = $vm.execute_successfully( - "timeout 120 gpg --batch #{importopts} --recv-key '#{@fetched_openpgp_keyid}'", - :user => LIVE_USER) - if @gnupg_recv_key_res.failure? - raise "Fetching keys with the GnuPG CLI failed with:\n" + - "#{@gnupg_recv_key_res.stdout}\n" + - "#{@gnupg_recv_key_res.stderr}" - end - end -end - -When /^the GnuPG fetch is successful$/ do - assert(@gnupg_recv_key_res.success?, - "gpg keyserver fetch failed:\n#{@gnupg_recv_key_res.stderr}") -end - -When /^the Seahorse operation is successful$/ do - !@screen.exists('GnomeCloseButton.png') - $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") { - $vm.execute("gpg --batch --list-keys '#{keyid}'", - :user => LIVE_USER).success? - } -end - -When /^I start Seahorse( via the Tails OpenPGP Applet)?$/ do |withgpgapplet| - @withgpgapplet = !!withgpgapplet - start_or_restart_seahorse -end - -Then /^Seahorse has opened$/ do - @screen.wait('SeahorseWindow.png', 20) -end - -Then /^I enable key synchronization in Seahorse$/ do - step 'process "seahorse" is running' - @screen.wait_and_click("SeahorseWindow.png", 10) - seahorse_menu_click_helper('GnomeEditMenu.png', 'SeahorseEditPreferences.png', 'seahorse') - @screen.wait('SeahorsePreferences.png', 20) - @screen.type("p", Sikuli::KeyModifier.ALT) # Option: "Publish keys to...". - @screen.type(Sikuli::Key.DOWN) # select HKP server - @screen.type("c", Sikuli::KeyModifier.ALT) # Button: "Close" -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 - # 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. - # - # We'll kill the Seahorse process to avoid waiting for the inevitable - # segfault. We'll also make sure the process is still running (= hasn't - # yet segfaulted) before terminating it. - if @screen.exists('GnomeCloseButton.png') || !$vm.has_process?('seahorse') - step 'I kill the process "seahorse"' if $vm.has_process?('seahorse') - debug_log('Restarting Seahorse.') - start_or_restart_seahorse - end - end - - def change_of_status? - # Due to a lack of visual feedback in Seahorse we'll break out of the - # try_for loop below by returning "true" when there's something we can act - # upon. - if count_gpg_signatures(@fetched_openpgp_keyid) > 2 || \ - @screen.exists('GnomeCloseButton.png') || \ - !$vm.has_process?('seahorse') - true - end - end - - retry_tor(recovery_proc) do - @screen.wait_and_click("SeahorseWindow.png", 10) - seahorse_menu_click_helper('SeahorseRemoteMenu.png', - 'SeahorseRemoteMenuSync.png', - 'seahorse') - @screen.wait('SeahorseSyncKeys.png', 20) - @screen.type("s", Sikuli::KeyModifier.ALT) # Button: Sync - # There's no visual feedback of Seahorse in Tails/Jessie, except on error. - try_for(120) { - change_of_status? - } - check_for_seahorse_error - raise OpenPGPKeyserverCommunicationError.new( - 'Seahorse crashed with a segfault.') unless $vm.has_process?('seahorse') - end -end - -When /^I fetch the "([^"]+)" OpenPGP key using Seahorse( via the Tails OpenPGP Applet)?$/ do |keyid, withgpgapplet| - step "I start Seahorse#{withgpgapplet}" - - def change_of_status?(keyid) - # Due to a lack of visual feedback in Seahorse we'll break out of the - # try_for loop below by returning "true" when there's something we can act - # upon. - if $vm.execute_successfully( - "gpg --batch --list-keys '#{keyid}'", :user => LIVE_USER) || - @screen.exists('GnomeCloseButton.png') - true - end - end - - recovery_proc = Proc.new do - @screen.click('GnomeCloseButton.png') if @screen.exists('GnomeCloseButton.png') - @screen.type("w", Sikuli::KeyModifier.CTRL) - end - retry_tor(recovery_proc) do - @screen.wait_and_click("SeahorseWindow.png", 10) - seahorse_menu_click_helper('SeahorseRemoteMenu.png', - 'SeahorseRemoteMenuFind.png', - 'seahorse') - @screen.wait('SeahorseFindKeysWindow.png', 10) - # Seahorse doesn't seem to support searching for fingerprints - @screen.type(keyid + Sikuli::Key.ENTER) - begin - @screen.waitAny(['SeahorseFoundKeyResult.png', - 'GnomeCloseButton.png'], 120) - rescue FindAnyFailed - # We may end up here if Seahorse appears to be "frozen". - # Sometimes--but not always--if we click another window - # the main Seahorse window will unfreeze, allowing us - # to continue normally. - @screen.click("SeahorseSearch.png") - end - check_for_seahorse_error - @screen.click("SeahorseKeyResultWindow.png") - @screen.click("SeahorseFoundKeyResult.png") - @screen.click("SeahorseImport.png") - try_for(120) do - change_of_status?(keyid) - end - check_for_seahorse_error - 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]) -end diff --git a/features/step_definitions/torified_misc.rb b/features/step_definitions/torified_misc.rb deleted file mode 100644 index 7112776a..00000000 --- a/features/step_definitions/torified_misc.rb +++ /dev/null @@ -1,41 +0,0 @@ -When /^I query the whois directory service for "([^"]+)"$/ do |domain| - retry_tor do - @vm_execute_res = $vm.execute("whois '#{domain}'", :user => LIVE_USER) - if @vm_execute_res.failure? || @vm_execute_res.stdout['LIMIT EXCEEDED'] - raise "Looking up whois info for #{domain} failed with:\n" + - "#{@vm_execute_res.stdout}\n" + - "#{@vm_execute_res.stderr}" - end - end -end - -When /^I wget "([^"]+)" to stdout(?:| with the '([^']+)' options)$/ do |url, options| - arguments = "-O - '#{url}'" - arguments = "#{options} #{arguments}" if options - retry_tor do - @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" + - "#{@vm_execute_res.stdout}\n" + - "#{@vm_execute_res.stderr}" - end - end -end - -Then /^the (wget|whois) command is successful$/ do |command| - assert( - @vm_execute_res.success?, - "#{command} failed:\n" + - "#{@vm_execute_res.stdout}\n" + - "#{@vm_execute_res.stderr}" - ) -end - -Then /^the (wget|whois) standard output contains "([^"]+)"$/ do |command, text| - assert( - @vm_execute_res.stdout[text], - "The #{command} standard output does not contain #{text}:\n" + - "#{@vm_execute_res.stdout}\n" + - "#{@vm_execute_res.stderr}" - ) -end diff --git a/features/step_definitions/totem.rb b/features/step_definitions/totem.rb deleted file mode 100644 index 72698dde..00000000 --- a/features/step_definitions/totem.rb +++ /dev/null @@ -1,43 +0,0 @@ -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) } - fatal_system("avconv -loop 1 -t 30 -f image2 " + - "-i 'features/images/TailsBootSplash.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") -end - -Given /^I setup a filesystem share containing sample videos$/ do - $vm.add_share(@shared_video_dir_on_host, @shared_video_dir_on_guest) -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 - video_name = File.basename(video_on_host) - src_on_guest = "#{@shared_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 -end - -When /^I(?:| try to) open "([^"]+)" with Totem$/ do |filename| - step "I run \"totem #{filename}\" in GNOME Terminal" -end - -When /^I close Totem$/ do - step 'I kill the process "totem"' -end - -Then /^I can watch a WebM video over HTTPs$/ do - test_url = 'https://webm.html5.org/test.webm' - recovery_on_failure = Proc.new do - step 'I close Totem' - end - retry_tor(recovery_on_failure) do - step "I open \"#{test_url}\" with Totem" - @screen.wait("SampleRemoteWebMVideoFrame.png", 120) - end -end diff --git a/features/step_definitions/unsafe_browser.rb b/features/step_definitions/unsafe_browser.rb deleted file mode 100644 index b8c04983..00000000 --- a/features/step_definitions/unsafe_browser.rb +++ /dev/null @@ -1,189 +0,0 @@ -When /^I see and accept the Unsafe Browser start verification$/ do - @screen.wait('GnomeQuestionDialogIcon.png', 30) - @screen.type(Sikuli::Key.ESC) -end - -def supported_torbrowser_languages - localization_descriptions = "#{Dir.pwd}/config/chroot_local-includes/usr/share/tails/browser-localization/descriptions" - 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", - "#{first}_#{second}", first] - when_not_found = Proc.new { raise "Could not find a locale for '#{line}'" } - candidates.find(when_not_found) do |candidate| - $vm.directory_exist?("/usr/lib/locale/#{candidate}") - end - end -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" -end - -Then /^the Unsafe Browser works in all supported languages$/ do - failed = Array.new - supported_torbrowser_languages.each do |lang| - step "I start the Unsafe Browser in the \"#{lang}\" locale" - begin - step "the Unsafe Browser has started" - rescue RuntimeError - failed << lang - next - end - step "I close the Unsafe Browser" - step "the Unsafe Browser chroot is torn down" - end - assert(failed.empty?, "Unsafe Browser failed to launch in the following locale(s): #{failed.join(', ')}") -end - -Then /^the Unsafe Browser has no add-ons installed$/ do - step "I open the address \"about:addons\" in the Unsafe Browser" - step "I see \"UnsafeBrowserNoAddons.png\" after at most 30 seconds" -end - -Then /^the Unsafe Browser has only Firefox's default bookmarks configured$/ do - info = xul_application_info("Unsafe Browser") - # "Show all bookmarks" - @screen.type("o", Sikuli::KeyModifier.SHIFT + Sikuli::KeyModifier.CTRL) - @screen.wait_and_click("UnsafeBrowserExportBookmarksButton.png", 20) - @screen.wait_and_click("UnsafeBrowserExportBookmarksMenuEntry.png", 20) - @screen.wait("UnsafeBrowserExportBookmarksSavePrompt.png", 20) - path = "/home/#{info[:user]}/bookmarks" - @screen.type(path + Sikuli::Key.ENTER) - chroot_path = "#{info[:chroot]}/#{path}.json" - try_for(10) { $vm.file_exist?(chroot_path) } - dump = JSON.load($vm.file_content(chroot_path)) - - def check_bookmarks_helper(a) - mozilla_uris_counter = 0 - places_uris_counter = 0 - a.each do |h| - h.each_pair do |k, v| - if k == "children" - m, p = check_bookmarks_helper(v) - mozilla_uris_counter += m - places_uris_counter += p - elsif k == "uri" - uri = v - if uri.match("^https://www\.mozilla\.org/") - mozilla_uris_counter += 1 - elsif uri.match("^place:(sort|folder|type)=") - places_uris_counter += 1 - else - raise "Unexpected Unsafe Browser bookmark for '#{uri}'" - end - end - end - end - return [mozilla_uris_counter, places_uris_counter] - end - - mozilla_uris_counter, places_uris_counter = - check_bookmarks_helper(dump["children"]) - assert_equal(5, mozilla_uris_counter, - "Unexpected number (#{mozilla_uris_counter}) of mozilla " \ - "bookmarks") - assert_equal(3, places_uris_counter, - "Unexpected number (#{places_uris_counter}) of places " \ - "bookmarks") - @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT) -end - -Then /^the Unsafe Browser has a red theme$/ do - @screen.wait("UnsafeBrowserRedTheme.png", 10) -end - -Then /^the Unsafe Browser shows a warning as its start page$/ do - @screen.wait("UnsafeBrowserStartPage.png", 10) -end - -Then /^I see a warning about another instance already running$/ do - @screen.wait('UnsafeBrowserWarnAlreadyRunning.png', 10) -end - -Then /^I can start the Unsafe Browser again$/ do - step "I start the Unsafe Browser" -end - -Then /^I cannot configure the Unsafe Browser to use any local proxies$/ do - socks_proxy = 'c' # Alt+c for socks proxy - no_proxy = 'y' # Alt+y for no proxy - proxies = [[no_proxy, nil, nil]] - socksport_lines = - $vm.execute_successfully('grep -w "^SocksPort" /etc/tor/torrc').stdout - assert(socksport_lines.size >= 4, "We got fewer than four Tor SocksPorts") - socksports = socksport_lines.scan(/^SocksPort\s([^:]+):(\d+)/) - proxies += socksports.map { |host, port| [socks_proxy, host, port] } - - proxies.each do |proxy_type, proxy_host, proxy_port| - @screen.hide_cursor - - # Open proxy settings and select manual proxy configuration - @screen.click('UnsafeBrowserMenuButton.png') - @screen.wait_and_click('UnsafeBrowserPreferencesButton.png', 10) - @screen.wait_and_click('UnsafeBrowserAdvancedSettingsButton.png', 10) - hit, _ = @screen.waitAny(['UnsafeBrowserNetworkTabAlreadySelected.png', - 'UnsafeBrowserNetworkTab.png'], 10) - @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 - - # 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" - if proxy_type == no_proxy - @screen.wait('UnsafeBrowserTorCheckFail.png', 60) - else - @screen.wait('UnsafeBrowserProxyRefused.png', 60) - end - end -end - -Then /^the Unsafe Browser has no proxy configured$/ do - @screen.click('UnsafeBrowserMenuButton.png') - @screen.wait_and_click('UnsafeBrowserPreferencesButton.png', 10) - @screen.wait_and_click('UnsafeBrowserAdvancedSettingsButton.png', 10) - @screen.wait_and_click('UnsafeBrowserNetworkTab.png', 10) - @screen.wait_and_click('UnsafeBrowserNetworkTabSettingsButton.png', 10) - @screen.wait('UnsafeBrowserProxySettingsWindow.png', 10) - @screen.wait('UnsafeBrowserNoProxySelected.png', 10) - @screen.type(Sikuli::Key.F4, Sikuli::KeyModifier.ALT) - @screen.type("w", Sikuli::KeyModifier.CTRL) -end - -Then /^the Unsafe Browser complains that no DNS server is configured$/ do - @screen.wait("UnsafeBrowserDNSError.png", 30) -end - -Then /^I configure the Unsafe Browser to check for updates more frequently$/ do - prefs = '/usr/share/tails/chroot-browsers/unsafe-browser/prefs.js' - $vm.file_append(prefs, 'pref("app.update.idletime", 1);') - $vm.file_append(prefs, 'pref("app.update.promptWaitTime", 1);') - $vm.file_append(prefs, 'pref("app.update.interval", 5);') -end - -But /^checking for updates is disabled in the Unsafe Browser's configuration$/ do - prefs = '/usr/share/tails/chroot-browsers/common/prefs.js' - assert($vm.file_content(prefs).include?('pref("app.update.enabled", false)')) -end - -Then /^the clearnet user has (|not )sent packets out to the Internet$/ do |sent| - uid = $vm.execute_successfully("id -u clearnet").stdout.chomp.to_i - pkts = ip4tables_packet_counter_sum(:tables => ['OUTPUT'], :uid => uid) - case sent - when '' - assert(pkts > 0, "Packets have not gone out to the internet.") - when 'not' - assert_equal(pkts, 0, "Packets have gone out to the internet.") - end -end diff --git a/features/step_definitions/untrusted_partitions.rb b/features/step_definitions/untrusted_partitions.rb deleted file mode 100644 index 43453b2f..00000000 --- a/features/step_definitions/untrusted_partitions.rb +++ /dev/null @@ -1,61 +0,0 @@ -Given /^I create an? ([[:alnum:]]+) swap partition on disk "([^"]+)"$/ do |parttype, name| - $vm.storage.disk_mkswap(name, parttype) -end - -Then /^an? "([^"]+)" partition was detected by Tails on drive "([^"]+)"$/ do |type, name| - part_info = $vm.execute_successfully( - "blkid '#{$vm.disk_dev(name)}'").stdout.strip - assert(part_info.split.grep(/^TYPE=\"#{Regexp.escape(type)}\"$/), - "No #{type} partition was detected by Tails on disk '#{name}'") -end - -Then /^Tails has no disk swap enabled$/ do - # Skip first line which contain column headers - swap_info = $vm.execute_successfully("tail -n+2 /proc/swaps").stdout - assert(swap_info.empty?, - "Disk swapping is enabled according to /proc/swaps:\n" + swap_info) - mem_info = $vm.execute_successfully("grep '^Swap' /proc/meminfo").stdout - assert(mem_info.match(/^SwapTotal:\s+0 kB$/), - "Disk swapping is enabled according to /proc/meminfo:\n" + - mem_info) -end - -Given /^I create an? ([[:alnum:]]+) partition( labeled "([^"]+)")? with an? ([[:alnum:]]+) filesystem( encrypted with password "([^"]+)")? on disk "([^"]+)"$/ do |parttype, has_label, label, fstype, is_encrypted, luks_password, name| - opts = {} - opts.merge!(:label => label) if has_label - opts.merge!(:luks_password => luks_password) if is_encrypted - $vm.storage.disk_mkpartfs(name, parttype, fstype, opts) -end - -Given /^I cat an ISO of the Tails image to disk "([^"]+)"$/ do |name| - src_disk = { - :path => TAILS_ISO, - :opts => { - :format => "raw", - :readonly => true - } - } - dest_disk = { - :path => $vm.storage.disk_path(name), - :opts => { - :format => $vm.storage.disk_format(name) - } - } - $vm.storage.guestfs_disk_helper(src_disk, dest_disk) do |g, src_disk_handle, dest_disk_handle| - g.copy_device_to_device(src_disk_handle, dest_disk_handle, {}) - end -end - -Then /^drive "([^"]+)" is not mounted$/ do |name| - dev = $vm.disk_dev(name) - assert(!$vm.execute("grep -qs '^#{dev}' /proc/mounts").success?, - "an untrusted partition from drive '#{name}' was automounted") -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? - assert_equal(expecting_persistence, found_persistence, - "Persistence is unexpectedly#{no_persistence} enabled") -end diff --git a/features/step_definitions/usb.rb b/features/step_definitions/usb.rb deleted file mode 100644 index 76f94d2f..00000000 --- a/features/step_definitions/usb.rb +++ /dev/null @@ -1,596 +0,0 @@ -# Returns a hash that for each preset the running Tails is aware of -# maps the source to the destination. -def get_persistence_presets(skip_links = false) - # Perl script that prints all persistence presets (one per line) on - # the form: <mount_point>:<comma-separated-list-of-options> - script = <<-EOF - use strict; - use warnings FATAL => "all"; - use Tails::Persistence::Configuration::Presets; - foreach my $preset (Tails::Persistence::Configuration::Presets->new()->all) { - say $preset->destination, ":", join(",", @{$preset->options}); - } -EOF - # VMCommand:s cannot handle newlines, and they're irrelevant in the - # above perl script any way - script.delete!("\n") - presets = $vm.execute_successfully("perl -E '#{script}'").stdout.chomp.split("\n") - assert presets.size >= 10, "Got #{presets.size} persistence presets, " + - "which is too few" - persistence_mapping = Hash.new - for line in presets - destination, options_str = line.split(":") - options = options_str.split(",") - is_link = options.include? "link" - next if is_link and skip_links - source_str = options.find { |option| /^source=/.match option } - # If no source is given as an option, live-boot's persistence - # feature defaults to the destination minus the initial "/". - if source_str.nil? - source = destination.partition("/").last - else - source = source_str.split("=")[1] - end - persistence_mapping[source] = destination - end - return persistence_mapping -end - -def persistent_dirs - get_persistence_presets -end - -def persistent_mounts - get_persistence_presets(true) -end - -def persistent_volumes_mountpoints - $vm.execute("ls -1 -d /live/persistence/*_unlocked/").stdout.chomp.split -end - -Given /^I clone USB drive "([^"]+)" to a new USB drive "([^"]+)"$/ do |from, to| - $vm.storage.clone_to_new_disk(from, to) -end - -Given /^I unplug USB drive "([^"]+)"$/ do |name| - $vm.unplug_drive(name) -end - -Given /^the computer is set to boot from the old Tails DVD$/ do - $vm.set_cdrom_boot(OLD_TAILS_ISO) -end - -Given /^the computer is set to boot in UEFI mode$/ do - $vm.set_os_loader('UEFI') - @os_loader = 'UEFI' -end - -class UpgradeNotSupported < StandardError -end - -def usb_install_helper(name) - @screen.wait('USBTailsLogo.png', 10) - if @screen.exists("USBCannotUpgrade.png") - 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) -end - -When /^I start Tails Installer$/ do - step 'I start "TailsInstaller" via the GNOME "Tails" applications menu' - @screen.wait('USBCloneAndInstall.png', 30) -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}'" - end -end - -Then /^Tails Installer detects that a device is too small$/ do - @screen.wait('TailsInstallerTooSmallDevice.png', 10) -end - -When /^I "Clone & Install" Tails to USB drive "([^"]+)"$/ do |name| - step 'I start Tails Installer in "Clone & Install" mode' - usb_install_helper(name) -end - -When /^I "Clone & Upgrade" Tails to USB drive "([^"]+)"$/ do |name| - step 'I start Tails Installer in "Clone & Upgrade" mode' - usb_install_helper(name) -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" - end -end - -When /^I try to "Upgrade from ISO" USB drive "([^"]+)"$/ do |name| - begin - step "I do a \"Upgrade from ISO\" on USB drive \"#{name}\"" - rescue UpgradeNotSupported - # this is what we expect - else - raise "The USB installer should not succeed" - 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) -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) - @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) - usb_install_helper(name) -end - -Given /^I enable all persistence presets$/ do - @screen.wait('PersistenceWizardPresets.png', 20) - # Select the "Persistent" folder preset, which is checked by default. - @screen.type(Sikuli::Key.TAB) - # Check all non-default persistence presets, i.e. all *after* the - # "Persistent" folder, which are unchecked by default. - (persistent_dirs.size - 1).times do - @screen.type(Sikuli::Key.TAB + Sikuli::Key.SPACE) - end - @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) - @screen.type(@persistence_password + "\t" + @persistence_password + Sikuli::Key.ENTER) - @screen.wait('PersistenceWizardPresets.png', 300) - step "I enable all persistence presets" -end - -def check_disk_integrity(name, dev, scheme) - info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout - info_split = info.split("\n org\.freedesktop\.UDisks2\.PartitionTable:\n") - dev_info = info_split[0] - part_table_info = info_split[1] - assert(part_table_info.match("^ Type: +#{scheme}$"), - "Unexpected partition scheme on USB drive '#{name}', '#{dev}'") -end - -def check_part_integrity(name, dev, usage, fs_type, part_label, part_type = nil) - info = $vm.execute("udisksctl info --block-device '#{dev}'").stdout - info_split = info.split("\n org\.freedesktop\.UDisks2\.Partition:\n") - dev_info = info_split[0] - part_info = info_split[1] - assert(dev_info.match("^ IdUsage: +#{usage}$"), - "Unexpected device field 'usage' on USB drive '#{name}', '#{dev}'") - assert(dev_info.match("^ IdType: +#{fs_type}$"), - "Unexpected device field 'IdType' on USB drive '#{name}', '#{dev}'") - assert(part_info.match("^ Name: +#{part_label}$"), - "Unexpected partition label on USB drive '#{name}', '#{dev}'") - if part_type - assert(part_info.match("^ Type: +#{part_type}$"), - "Unexpected partition type on USB drive '#{name}', '#{dev}'") - end -end - -def tails_is_installed_helper(name, tails_root, loader) - disk_dev = $vm.disk_dev(name) - part_dev = disk_dev + "1" - check_disk_integrity(name, disk_dev, "gpt") - check_part_integrity(name, part_dev, "filesystem", "vfat", "Tails", - # EFI System Partition - 'c12a7328-f81f-11d2-ba4b-00a0c93ec93b') - - target_root = "/mnt/new" - $vm.execute("mkdir -p #{target_root}") - $vm.execute("mount #{part_dev} #{target_root}") - - c = $vm.execute("diff -qr '#{tails_root}/live' '#{target_root}/live'") - assert(c.success?, - "USB drive '#{name}' has differences in /live:\n#{c.stdout}\n#{c.stderr}") - - syslinux_files = $vm.execute("ls -1 #{target_root}/syslinux").stdout.chomp.split - # We deal with these files separately - ignores = ["syslinux.cfg", "exithelp.cfg", "ldlinux.c32", "ldlinux.sys"] - for f in syslinux_files - ignores do - c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{f}' " + - "'#{target_root}/syslinux/#{f}'") - assert(c.success?, "USB drive '#{name}' has differences in " + - "'/syslinux/#{f}'") - end - - # The main .cfg is named differently vs isolinux - c = $vm.execute("diff -q '#{tails_root}/#{loader}/#{loader}.cfg' " + - "'#{target_root}/syslinux/syslinux.cfg'") - assert(c.success?, "USB drive '#{name}' has differences in " + - "'/syslinux/syslinux.cfg'") - - $vm.execute("umount #{target_root}") - $vm.execute("sync") -end - -Then /^the running Tails is installed on USB drive "([^"]+)"$/ do |target_name| - loader = boot_device_type == "usb" ? "syslinux" : "isolinux" - tails_is_installed_helper(target_name, "/lib/live/mount/medium", loader) -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}") - tails_is_installed_helper(target_name, iso_root, "isolinux") - $vm.execute("umount #{iso_root}") -end - -Then /^there is no persistence partition on USB drive "([^"]+)"$/ do |name| - data_part_dev = $vm.disk_dev(name) + "2" - assert(!$vm.execute("test -b #{data_part_dev}").success?, - "USB drive #{name} has a partition '#{data_part_dev}'") -end - -Then /^a Tails persistence partition exists on USB drive "([^"]+)"$/ do |name| - dev = $vm.disk_dev(name) + "2" - check_part_integrity(name, dev, "crypto", "crypto_LUKS", "TailsData") - - # 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/") - if c.success? - for candidate in c.stdout.split("\n") - 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 - end - end - end - if luks_dev.nil? - c = $vm.execute("echo #{@persistence_password} | " + - "cryptsetup luksOpen #{dev} #{name}") - assert(c.success?, "Couldn't open LUKS device '#{dev}' on drive '#{name}'") - luks_dev = "/dev/mapper/#{name}" - end - - # Adapting check_part_integrity() seems like a bad idea so here goes - info = $vm.execute("udisksctl info --block-device '#{luks_dev}'").stdout - assert info.match("^ CryptoBackingDevice: +'/[a-zA-Z0-9_/]+'$") - assert info.match("^ IdUsage: +filesystem$") - assert info.match("^ IdType: +ext[34]$") - assert info.match("^ IdLabel: +TailsData$") - - mount_dir = "/mnt/#{name}" - $vm.execute("mkdir -p #{mount_dir}") - c = $vm.execute("mount #{luks_dev} #{mount_dir}") - assert(c.success?, - "Couldn't mount opened LUKS device '#{dev}' on drive '#{name}'") - - $vm.execute("umount #{mount_dir}") - $vm.execute("sync") - $vm.execute("cryptsetup luksClose #{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) -end - -def tails_persistence_enabled? - persistence_state_file = "/var/lib/live/config/tails.persistence" - return $vm.execute("test -e '#{persistence_state_file}'").success? && - $vm.execute(". '#{persistence_state_file}' && " + - 'test "$TAILS_PERSISTENCE_ENABLED" = true').success? -end - -Given /^all persistence presets(| from the old Tails version) are enabled$/ do |old_tails| - try_for(120, :msg => "Persistence is disabled") do - tails_persistence_enabled? - end - # Check that all persistent directories are mounted - if old_tails.empty? - expected_mounts = persistent_mounts - else - assert_not_nil($remembered_persistence_mounts) - expected_mounts = $remembered_persistence_mounts - end - mount = $vm.execute("mount").stdout.chomp - for _, dir in expected_mounts do - assert(mount.include?("on #{dir} "), - "Persistent directory '#{dir}' is not 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 - boot_dev_id = $vm.execute("udevadm info --device-id-of-file=/lib/live/mount/medium").stdout.chomp - boot_dev = $vm.execute("readlink -f /dev/block/'#{boot_dev_id}'").stdout.chomp - return boot_dev -end - -def device_info(dev) - # Approach borrowed from - # config/chroot_local_includes/lib/live/config/998-permissions - info = $vm.execute("udevadm info --query=property --name='#{dev}'").stdout.chomp - info.split("\n").map { |e| e.split('=') } .to_h -end - -def boot_device_type - device_info(boot_device)['ID_BUS'] -end - -Then /^Tails is running from (.*) drive "([^"]+)"$/ do |bus, name| - bus = bus.downcase - case bus - when "ide" - 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, - "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)") -end - -Then /^the boot device has safe access rights$/ do - - super_boot_dev = boot_device.sub(/[[:digit:]]+$/, "") - devs = $vm.execute("ls -1 #{super_boot_dev}*").stdout.chomp.split - assert(devs.size > 0, "Could not determine boot device") - all_users = $vm.execute("cut -d':' -f1 /etc/passwd").stdout.chomp.split - all_users_with_groups = all_users.collect do |user| - groups = $vm.execute("groups #{user}").stdout.chomp.sub(/^#{user} : /, "").split(" ") - [user, groups] - end - for dev in devs do - dev_owner = $vm.execute("stat -c %U #{dev}").stdout.chomp - dev_group = $vm.execute("stat -c %G #{dev}").stdout.chomp - dev_perms = $vm.execute("stat -c %a #{dev}").stdout.chomp - assert_equal("root", dev_owner) - assert(dev_group == "disk" || dev_group == "root", - "Boot device '#{dev}' owned by group '#{dev_group}', expected " + - "'disk' or 'root'.") - assert_equal("660", dev_perms) - for user, groups in all_users_with_groups do - next if user == "root" - assert(!(groups.include?(dev_group)), - "Unprivileged user '#{user}' is in group '#{dev_group}' which " + - "owns boot device '#{dev}'") - end - end - - info = $vm.execute("udisksctl info --block-device '#{super_boot_dev}'").stdout - assert(info.match("^ HintSystem: +true$"), - "Boot device '#{super_boot_dev}' is not system internal for udisks") -end - -Then /^all persistent filesystems have safe access rights$/ do - persistent_volumes_mountpoints.each do |mountpoint| - fs_owner = $vm.execute("stat -c %U #{mountpoint}").stdout.chomp - fs_group = $vm.execute("stat -c %G #{mountpoint}").stdout.chomp - fs_perms = $vm.execute("stat -c %a #{mountpoint}").stdout.chomp - assert_equal("root", fs_owner) - assert_equal("root", fs_group) - assert_equal('775', fs_perms) - end -end - -Then /^all persistence configuration files have safe access rights$/ do - persistent_volumes_mountpoints.each do |mountpoint| - assert($vm.execute("test -e #{mountpoint}/persistence.conf").success?, - "#{mountpoint}/persistence.conf does not exist, while it should") - assert($vm.execute("test ! -e #{mountpoint}/live-persistence.conf").success?, - "#{mountpoint}/live-persistence.conf does exist, while it should not") - $vm.execute( - "ls -1 #{mountpoint}/persistence.conf #{mountpoint}/live-*.conf" - ).stdout.chomp.split.each do |f| - file_owner = $vm.execute("stat -c %U '#{f}'").stdout.chomp - file_group = $vm.execute("stat -c %G '#{f}'").stdout.chomp - file_perms = $vm.execute("stat -c %a '#{f}'").stdout.chomp - assert_equal("tails-persistence-setup", file_owner) - assert_equal("tails-persistence-setup", file_group) - assert_equal("600", file_perms) - end - end -end - -Then /^all persistent directories(| from the old Tails version) have safe access rights$/ do |old_tails| - if old_tails.empty? - expected_dirs = persistent_dirs - else - assert_not_nil($remembered_persistence_dirs) - expected_dirs = $remembered_persistence_dirs - end - persistent_volumes_mountpoints.each do |mountpoint| - expected_dirs.each do |src, dest| - full_src = "#{mountpoint}/#{src}" - assert_vmcommand_success $vm.execute("test -d #{full_src}") - dir_perms = $vm.execute_successfully("stat -c %a '#{full_src}'").stdout.chomp - dir_owner = $vm.execute_successfully("stat -c %U '#{full_src}'").stdout.chomp - if dest.start_with?("/home/#{LIVE_USER}") - expected_perms = "700" - expected_owner = LIVE_USER - else - expected_perms = "755" - expected_owner = "root" - end - assert_equal(expected_perms, dir_perms, - "Persistent source #{full_src} has permission " \ - "#{dir_perms}, expected #{expected_perms}") - assert_equal(expected_owner, dir_owner, - "Persistent source #{full_src} has owner " \ - "#{dir_owner}, expected #{expected_owner}") - end - end -end - -When /^I write some files expected to persist$/ do - persistent_mounts.each do |_, dir| - owner = $vm.execute("stat -c %U #{dir}").stdout.chomp - assert($vm.execute("touch #{dir}/XXX_persist", :user => owner).success?, - "Could not create file in persistent directory #{dir}") - end -end - -When /^I remove some files expected to persist$/ do - persistent_mounts.each do |_, dir| - owner = $vm.execute("stat -c %U #{dir}").stdout.chomp - assert($vm.execute("rm #{dir}/XXX_persist", :user => owner).success?, - "Could not remove file in persistent directory #{dir}") - end -end - -When /^I write some files not expected to persist$/ do - persistent_mounts.each do |_, dir| - owner = $vm.execute("stat -c %U #{dir}").stdout.chomp - assert($vm.execute("touch #{dir}/XXX_gone", :user => owner).success?, - "Could not create file in persistent directory #{dir}") - end -end - -When /^I take note of which persistence presets are available$/ do - $remembered_persistence_mounts = persistent_mounts - $remembered_persistence_dirs = persistent_dirs -end - -Then /^the expected persistent files(| created with the old Tails version) are present in the filesystem$/ do |old_tails| - if old_tails.empty? - expected_mounts = persistent_mounts - else - assert_not_nil($remembered_persistence_mounts) - expected_mounts = $remembered_persistence_mounts - end - expected_mounts.each do |_, dir| - assert($vm.execute("test -e #{dir}/XXX_persist").success?, - "Could not find expected file in persistent directory #{dir}") - assert(!$vm.execute("test -e #{dir}/XXX_gone").success?, - "Found file that should not have persisted in persistent directory #{dir}") - end -end - -Then /^only the expected files are present on the persistence partition on USB drive "([^"]+)"$/ do |name| - assert(!$vm.is_running?) - disk = { - :path => $vm.storage.disk_path(name), - :opts => { - :format => $vm.storage.disk_format(name), - :readonly => true - } - } - $vm.storage.guestfs_disk_helper(disk) do |g, disk_handle| - partitions = g.part_list(disk_handle).map do |part_desc| - disk_handle + part_desc["part_num"].to_s - end - partition = partitions.find do |part| - g.blkid(part)["PART_ENTRY_NAME"] == "TailsData" - end - assert_not_nil(partition, "Could not find the 'TailsData' partition " \ - "on disk '#{disk_handle}'") - luks_mapping = File.basename(partition) + "_unlocked" - g.luks_open(partition, @persistence_password, luks_mapping) - luks_dev = "/dev/mapper/#{luks_mapping}" - mount_point = "/" - g.mount(luks_dev, mount_point) - assert_not_nil($remembered_persistence_mounts) - $remembered_persistence_mounts.each do |dir, _| - # Guestfs::exists may have a bug; if the file exists, 1 is - # returned, but if it doesn't exist false is returned. It seems - # the translation of C types into Ruby types is glitchy. - assert(g.exists("/#{dir}/XXX_persist") == 1, - "Could not find expected file in persistent directory #{dir}") - assert(g.exists("/#{dir}/XXX_gone") != 1, - "Found file that should not have persisted in persistent directory #{dir}") - end - g.umount(mount_point) - g.luks_close(luks_dev) - end -end - -When /^I delete the persistent partition$/ do - step 'I start "DeletePersistentVolume" via the GNOME "Tails" applications menu' - @screen.wait("PersistenceWizardDeletionStart.png", 20) - @screen.type(" ") - @screen.wait("PersistenceWizardDone.png", 120) -end - -Then /^Tails has started in UEFI mode$/ do - assert($vm.execute("test -d /sys/firmware/efi").success?, - "/sys/firmware/efi does not exist") - end - -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) -end - -Then /^the "(?:[^"]+)" USB drive is selected$/ do - @screen.wait("TailsInstallerQEMUHardDisk.png", 30) -end - -Then /^no USB drive is selected$/ do - @screen.wait("TailsInstallerNoQEMUHardDisk.png", 30) -end diff --git a/features/support/config.rb b/features/support/config.rb deleted file mode 100644 index 25c107b4..00000000 --- a/features/support/config.rb +++ /dev/null @@ -1,100 +0,0 @@ -require 'fileutils' -require 'yaml' -require "features/support/helpers/misc_helpers.rb" - -# These files deal with options like some of the settings passed -# to the `run_test_suite` script, and "secrets" like credentials -# (passwords, SSH keys) to be used in tests. -CONFIG_DIR = "/srv/jenkins/features/config" -DEFAULTS_CONFIG_FILE = "#{CONFIG_DIR}/defaults.yml" -LOCAL_CONFIG_FILE = "#{CONFIG_DIR}/local.yml" -LOCAL_CONFIG_DIRS_FILES_GLOB = "#{CONFIG_DIR}/*.d/*.yml" - -# Dynamic -$tails_iso = ENV['ISO'] || get_newest_iso -$old_tails_iso = ENV['OLD_ISO'] || get_oldest_iso -$tmp_dir = ENV['PWD'] -$vm_xml_path = ENV['VM_XML_PATH'] -$misc_files_dir = "features/misc_files" -$keep_snapshots = !ENV['KEEP_SNAPSHOTS'].nil? -$x_display = ENV['DISPLAY'] -$debug = !ENV['DEBUG'].nil? -$pause_on_fail = !ENV['PAUSE_ON_FAIL'].nil? -$time_at_start = Time.now -$live_user = "user" -$sikuli_retry_findfailed = !ENV['SIKULI_RETRY_FINDFAILED'].nil? - -assert File.exists?(DEFAULTS_CONFIG_FILE) -$config = YAML.load(File.read(DEFAULTS_CONFIG_FILE)) -config_files = Dir.glob(LOCAL_CONFIG_DIRS_FILES_GLOB).sort -config_files.insert(0, LOCAL_CONFIG_FILE) if File.exists?(LOCAL_CONFIG_FILE) -config_files.each do |config_file| - yaml_struct = YAML.load(File.read(config_file)) || Hash.new - if not(yaml_struct.instance_of?(Hash)) - raise "Local configuration file '#{config_file}' is malformed" - end - $config.merge!(yaml_struct) -end -# Options passed to the `run_test_suite` script will always take -# precedence. The way we import these keys is only safe for values -# with types boolean or string. If we need more, we'll have to invoke -# YAML's type autodetection on ENV some how. -$config.merge!(ENV) - -# Export TMPDIR back to the environment for subprocesses that we start -# (e.g. guestfs). Note that this export will only make a difference if -# TMPDIR wasn't already set and --tmpdir wasn't passed, i.e. only when -# we use the default. -ENV['TMPDIR'] = $config['TMPDIR'] - -# Dynamic constants initialized through the environment or similar, -# e.g. options we do not want to be configurable through the YAML -# configuration files. -DEBUG_LOG_PSEUDO_FIFO = "#{$config["TMPDIR"]}/debug_log_pseudo_fifo" -DISPLAY = ENV['DISPLAY'] -GIT_DIR = ENV['PWD'] -KEEP_SNAPSHOTS = !ENV['KEEP_SNAPSHOTS'].nil? -LIVE_USER = "live_user" -TAILS_ISO = ENV['ISO'] -OLD_TAILS_ISO = ENV['OLD_ISO'] || TAILS_ISO -TIME_AT_START = Time.now -loop do - ARTIFACTS_DIR = $config['TMPDIR'] + "/results" - if not(File.exist?(ARTIFACTS_DIR)) - FileUtils.mkdir_p(ARTIFACTS_DIR) - break - end -end - -# Constants that are statically initialized. -CONFIGURED_KEYSERVER_HOSTNAME = 'hkps.pool.sks-keyservers.net' -LIBVIRT_DOMAIN_NAME = "DebianToaster" -LIBVIRT_DOMAIN_UUID = "203552d5-819c-41f3-800e-2c8ef2545404" -LIBVIRT_NETWORK_NAME = "DebianToasterNet" -LIBVIRT_NETWORK_UUID = "f2305af3-2a64-4f16-afe6-b9dbf02a597e" -MISC_FILES_DIR = "/srv/jenkins/features/misc_files" -SERVICES_EXPECTED_ON_ALL_IFACES = - [ - ["cupsd", "0.0.0.0", "631"], - ["dhclient", "0.0.0.0", "*"] - ] -# OpenDNS -SOME_DNS_SERVER = "208.67.222.222" -TOR_AUTHORITIES = - # List grabbed from Tor's sources, src/or/config.c:~750. - [ - "86.59.21.38", - "128.31.0.39", - "194.109.206.212", - "82.94.251.203", - "199.254.238.52", - "131.188.40.189", - "193.23.244.244", - "208.83.223.34", - "171.25.193.9", - "154.35.175.225", - ] -VM_XML_PATH = "/srv/jenkins/features/domains" - -#TAILS_SIGNING_KEY = cmd_helper(". #{Dir.pwd}/config/amnesia; echo ${AMNESIA_DEV_KEYID}").tr(' ', '').chomp -TAILS_DEBIAN_REPO_KEY = "221F9A3C6FA3E09E182E060BC7988EA7A358D82E" diff --git a/features/support/env.rb b/features/support/env.rb deleted file mode 100644 index 2e17ae76..00000000 --- a/features/support/env.rb +++ /dev/null @@ -1,90 +0,0 @@ -require 'rubygems' -require "features/support/extra_hooks.rb" -require 'time' -require 'rspec' - -# Force UTF-8. Ruby will default to the system locale, and if it is -# non-UTF-8, String-methods will fail when operating on non-ASCII -# strings. -Encoding.default_external = Encoding::UTF_8 -Encoding.default_internal = Encoding::UTF_8 - -def fatal_system(str) - unless system(str) - raise StandardError.new("Command exited with #{$?}") - end -end - -def git_exists? - File.exists? '.git' -end - -def create_git - Dir.mkdir 'config' - FileUtils.touch('config/base_branch') - Dir.mkdir('config/APT_overlays.d') - Dir.mkdir 'debian' - File.open('debian/changelog', 'w') do |changelog| - changelog.write(<<END_OF_CHANGELOG) -tails (0) stable; urgency=low - - * First release. - - -- Tails developers <tails@boum.org> Mon, 30 Jan 2012 01:00:00 +0000 -END_OF_CHANGELOG - end - - fatal_system "git init --quiet" - fatal_system "git config user.email 'tails@boum.org'" - fatal_system "git config user.name 'Tails developers'" - fatal_system "git add debian/changelog" - fatal_system "git commit --quiet debian/changelog -m 'First release'" - fatal_system "git branch -M stable" - fatal_system "git branch testing stable" - fatal_system "git branch devel stable" - fatal_system "git branch feature/jessie devel" -end - -def current_branch - cmd = 'git rev-parse --symbolic-full-name --abbrev-ref HEAD'.split - branch = cmd_helper(cmd).strip - assert_not_equal("HEAD", branch, "We are in 'detached HEAD' state") - return branch -end - -# In order: if git HEAD is tagged, return its name; if a branch is -# checked out, return its name; otherwise we are in 'detached HEAD' -# state, and we return the empty string. -def describe_git_head - cmd_helper("git describe --tags --exact-match #{current_commit}".split).strip -rescue Test::Unit::AssertionFailedError - begin - current_branch - rescue Test::Unit::AssertionFailedError - "" - end -end - -def current_commit - cmd_helper('git rev-parse HEAD'.split).strip -end - -def current_short_commit - current_commit[0, 7] -end - -RSpec::Matchers.define :have_suite do |suite| - match do |string| - # e.g.: `deb http://deb.tails.boum.org/ 0.10 main contrib non-free` - %r{^deb +http://deb\.tails\.boum\.org/ +#{Regexp.escape(suite)} main}.match(string) - end - failure_message_for_should do |string| - "expected the sources to include #{suite}\nCurrent sources : #{string}" - end - failure_message_for_should_not do |string| - "expected the sources to exclude #{suite}\nCurrent sources : #{string}" - end - description do - "expected an output with #{suite}" - end -end diff --git a/features/support/extra_hooks.rb b/features/support/extra_hooks.rb deleted file mode 100644 index 16196a55..00000000 --- a/features/support/extra_hooks.rb +++ /dev/null @@ -1,165 +0,0 @@ -# Make the code below work with cucumber >= 2.0. Once we stop -# supporting <2.0 we should probably do this differently, but this way -# we can easily support both at the same time. -begin - if not(Cucumber::Core::Ast::Feature.instance_methods.include?(:accept_hook?)) - require 'gherkin/tag_expression' - class Cucumber::Core::Ast::Feature - # Code inspired by Cucumber::Core::Test::Case.match_tags?() in - # cucumber-ruby-core 1.1.3, lib/cucumber/core/test/case.rb:~59. - def accept_hook?(hook) - tag_expr = Gherkin::TagExpression.new(hook.tag_expressions.flatten) - tags = @tags.map do |t| - Gherkin::Formatter::Model::Tag.new(t.name, t.line) - end - tag_expr.evaluate(tags) - end - end - end -rescue NameError => e - raise e if e.to_s != "uninitialized constant Cucumber::Core" -end - -# Sort of inspired by Cucumber::RbSupport::RbHook (from cucumber -# < 2.0) but really we just want an object with a 'tag_expressions' -# attribute to make accept_hook?() (used below) happy. -class SimpleHook - attr_reader :tag_expressions - - def initialize(tag_expressions, proc) - @tag_expressions = tag_expressions - @proc = proc - end - - def invoke(arg) - @proc.call(arg) - end -end - -def BeforeFeature(*tag_expressions, &block) - $before_feature_hooks ||= [] - $before_feature_hooks << SimpleHook.new(tag_expressions, block) -end - -def AfterFeature(*tag_expressions, &block) - $after_feature_hooks ||= [] - $after_feature_hooks << SimpleHook.new(tag_expressions, block) -end - -require 'cucumber/formatter/console' -if not($at_exit_print_artifacts_dir_patching_done) - module Cucumber::Formatter::Console - if method_defined?(:print_stats) - alias old_print_stats print_stats - end - def print_stats(*args) - if Dir.exists?(ARTIFACTS_DIR) and Dir.entries(ARTIFACTS_DIR).size > 2 - @io.puts "Artifacts directory: #{ARTIFACTS_DIR}" - @io.puts - end - if self.class.method_defined?(:old_print_stats) - old_print_stats(*args) - end - end - end - $at_exit_print_artifacts_dir_patching_done = true -end - -def info_log(message = "", options = {}) - options[:color] = :clear - # This trick allows us to use a module's (~private) method on a - # one-off basis. - cucumber_console = Class.new.extend(Cucumber::Formatter::Console) - puts cucumber_console.format_string(message, options[:color]) -end - -def debug_log(message, options = {}) - $debug_log_fns.each { |fn| fn.call(message, options) } if $debug_log_fns -end - -require 'cucumber/formatter/pretty' -# Backport part of commit af940a8 from the cucumber-ruby repo. This -# fixes the "out hook output" for the Pretty formatter so stuff -# written via `puts` after a Scenario has run its last step will be -# written, instead of delayed to the next Feature/Scenario (if any) or -# dropped completely (if not). -# XXX: This can be removed once we stop supporting Debian Jessie -# around when Debian Stretch is released. -if Gem::Version.new(Cucumber::VERSION) < Gem::Version.new('2.0.0.beta.4') - module Cucumber - module Formatter - class Pretty - def after_feature_element(feature_element) - print_messages - @io.puts - @io.flush - end - end - end - end -end - -module ExtraFormatters - # This is a null formatter in the sense that it doesn't ever output - # anything. We only use it do hook into the correct events so we can - # add our extra hooks. - class ExtraHooks - def initialize(*args) - # We do not care about any of the arguments. - end - - def before_feature(feature) - if $before_feature_hooks - $before_feature_hooks.each do |hook| - hook.invoke(feature) if feature.accept_hook?(hook) - end - end - end - - def after_feature(feature) - if $after_feature_hooks - $after_feature_hooks.reverse.each do |hook| - hook.invoke(feature) if feature.accept_hook?(hook) - end - end - end - end - - # The pretty formatter with debug logging mixed into its output. - class PrettyDebug < Cucumber::Formatter::Pretty - def initialize(*args) - super(*args) - $debug_log_fns ||= [] - $debug_log_fns << self.method(:debug_log) - end - - def debug_log(message, options) - options[:color] ||= :blue - @io.puts(format_string(message, options[:color])) - @io.flush - end - end - -end - -module Cucumber - module Cli - class Options - BUILTIN_FORMATS['pretty_debug'] = - [ - 'ExtraFormatters::PrettyDebug', - 'Prints the feature with debugging information - in colours.' - ] - BUILTIN_FORMATS['debug'] = BUILTIN_FORMATS['pretty_debug'] - end - end -end - -AfterConfiguration do |config| - # Cucumber may read this file multiple times, and hence run this - # AfterConfiguration hook multiple times. We only want our - # ExtraHooks formatter to be loaded once, otherwise the hooks would - # be run miltiple times. - extra_hooks = ['ExtraFormatters::ExtraHooks', '/dev/null'] - config.formats << extra_hooks if not(config.formats.include?(extra_hooks)) -end diff --git a/features/support/helpers/chatbot_helper.rb b/features/support/helpers/chatbot_helper.rb deleted file mode 100644 index 23ce3e1a..00000000 --- a/features/support/helpers/chatbot_helper.rb +++ /dev/null @@ -1,59 +0,0 @@ -require 'tempfile' - -class ChatBot - - def initialize(account, password, otr_key, opts = Hash.new) - @account = account - @password = password - @otr_key = otr_key - @opts = opts - @pid = nil - @otr_key_file = nil - end - - def start - @otr_key_file = Tempfile.new("otr_key.", $config["TMPDIR"]) - @otr_key_file << @otr_key - @otr_key_file.close - - cmd_helper(['/usr/bin/convertkey', @otr_key_file.path]) - cmd_helper(["mv", "#{@otr_key_file.path}3", @otr_key_file.path]) - - cmd = [ - "#{GIT_DIR}/features/scripts/otr-bot.py", - @account, - @password, - @otr_key_file.path - ] - cmd += ["--connect-server", @opts["connect_server"]] if @opts["connect_server"] - cmd += ["--auto-join"] + @opts["auto_join"] if @opts["auto_join"] - cmd += ["--log-file", DEBUG_LOG_PSEUDO_FIFO] - - job = IO.popen(cmd) - @pid = job.pid - end - - def stop - @otr_key_file.delete - begin - Process.kill("TERM", @pid) - rescue - # noop - end - end - - def active? - begin - ret = Process.kill(0, @pid) - rescue Errno::ESRCH => e - if e.message == "No such process" - return false - else - raise e - end - end - assert_equal(1, ret, "This shouldn't happen") - return true - end - -end diff --git a/features/support/helpers/ctcp_helper.rb b/features/support/helpers/ctcp_helper.rb deleted file mode 100644 index ee5180ab..00000000 --- a/features/support/helpers/ctcp_helper.rb +++ /dev/null @@ -1,126 +0,0 @@ -require 'net/irc' -require 'timeout' - -class CtcpChecker < Net::IRC::Client - - CTCP_SPAM_DELAY = 5 - - # `spam_target`: the nickname of the IRC user to CTCP spam. - # `ctcp_cmds`: the Array of CTCP commands to send. - # `expected_ctcp_replies`: Hash where the keys are the exact set of replies - # we expect, and their values a regex the reply data must match. - def initialize(host, port, spam_target, ctcp_cmds, expected_ctcp_replies) - @spam_target = spam_target - @ctcp_cmds = ctcp_cmds - @expected_ctcp_replies = expected_ctcp_replies - nickname = self.class.random_irc_nickname - opts = { - :nick => nickname, - :user => nickname, - :real => nickname, - } - opts[:logger] = Logger.new(DEBUG_LOG_PSEUDO_FIFO) - super(host, port, opts) - end - - # Makes sure that only the expected CTCP replies are received. - def verify_ctcp_responses - @sent_ctcp_cmds = Set.new - @received_ctcp_replies = Set.new - - # Give 60 seconds for connecting to the server and other overhead - # beyond the expected time to spam all CTCP commands. - expected_ctcp_spam_time = @ctcp_cmds.length * CTCP_SPAM_DELAY - timeout = expected_ctcp_spam_time + 60 - - begin - Timeout::timeout(timeout) do - start - end - rescue Timeout::Error - # Do nothing as we'll check for errors below. - ensure - finish - end - - ctcp_cmds_not_sent = @ctcp_cmds - @sent_ctcp_cmds.to_a - expected_ctcp_replies_not_received = - @expected_ctcp_replies.keys - @received_ctcp_replies.to_a - - if !ctcp_cmds_not_sent.empty? || !expected_ctcp_replies_not_received.empty? - raise "Failed to spam all CTCP commands and receive the expected " + - "replies within #{timeout} seconds.\n" + - (ctcp_cmds_not_sent.empty? ? "" : - "CTCP commands not sent: #{ctcp_cmds_not_sent}\n") + - (expected_ctcp_replies_not_received.empty? ? "" : - "Expected CTCP replies not received: " + - expected_ctcp_replies_not_received.to_s) - end - - end - - # Generate a random IRC nickname, in this case an alpha-numeric - # string with length 10 to 15. To make it legal, the first character - # is forced to be alpha. - def self.random_irc_nickname - random_alpha_string(1) + random_alnum_string(9, 14) - end - - def spam(spam_target) - post(NOTICE, spam_target, "Hi! I'm gonna test your CTCP capabilities now.") - @ctcp_cmds.each do |cmd| - sleep CTCP_SPAM_DELAY - full_cmd = cmd - case cmd - when "PING" - full_cmd += " #{Time.now.to_i}" - when "ACTION" - full_cmd += " barfs on the floor." - when "ERRMSG" - full_cmd += " Pidgin should not respond to this." - end - post(PRIVMSG, spam_target, ctcp_encode(full_cmd)) - @sent_ctcp_cmds << cmd - end - end - - def on_rpl_welcome(m) - super - Thread.new { spam(@spam_target) } - end - - def on_message(m) - if m.command == ERR_NICKNAMEINUSE - finish - new_nick = self.class.random_irc_nickname - @opts.marshal_load({ - :nick => new_nick, - :user => new_nick, - :real => new_nick, - }) - start - return - end - - if m.ctcp? and /^:#{Regexp.escape(@spam_target)}!/.match(m) - m.ctcps.each do |ctcp_reply| - reply_type, _, reply_data = ctcp_reply.partition(" ") - if @expected_ctcp_replies.has_key?(reply_type) - if @expected_ctcp_replies[reply_type].match(reply_data) - @received_ctcp_replies << reply_type - else - raise "Received expected CTCP reply '#{reply_type}' but with " + - "unexpected data '#{reply_data}' " - end - else - raise "Received unexpected CTCP reply '#{reply_type}' with " + - "data '#{reply_data}'" - end - end - end - if Set.new(@ctcp_cmds) == @sent_ctcp_cmds && \ - Set.new(@expected_ctcp_replies.keys) == @received_ctcp_replies - finish - end - end -end diff --git a/features/support/helpers/display_helper.rb b/features/support/helpers/display_helper.rb deleted file mode 100644 index b4dce733..00000000 --- a/features/support/helpers/display_helper.rb +++ /dev/null @@ -1,48 +0,0 @@ - -class Display - - def initialize(domain, x_display) - @domain = domain - @x_display = x_display - end - - def active? - p = IO.popen(["xprop", "-display", @x_display, - "-name", "#{@domain} (1) - Virt Viewer", - :err => ["/dev/null", "w"]]) - Process.wait(p.pid) - $?.success? - end - - def start - @virtviewer = IO.popen(["virt-viewer", "--direct", - "--kiosk", - "--reconnect", - "--connect", "qemu:///system", - "--display", @x_display, - @domain, - :err => ["/dev/null", "w"]]) - # We wait for the display to be active to not lose actions - # (e.g. key presses via sikuli) that come immediately after - # starting (or restoring) a vm - try_for(20, { :delay => 0.1, :msg => "virt-viewer failed to start"}) { - active? - } - end - - def stop - return if @virtviewer.nil? - Process.kill("TERM", @virtviewer.pid) - @virtviewer.close - rescue IOError - # IO.pid throws this if the process wasn't started yet. Possibly - # there's a race when doing a start() and then quickly running - # stop(). - end - - def restart - stop - start - end - -end diff --git a/features/support/helpers/exec_helper.rb b/features/support/helpers/exec_helper.rb deleted file mode 100644 index 14e12269..00000000 --- a/features/support/helpers/exec_helper.rb +++ /dev/null @@ -1,79 +0,0 @@ -require 'json' -require 'socket' -require 'io/wait' - -class VMCommand - - attr_reader :cmd, :returncode, :stdout, :stderr - - def initialize(vm, cmd, options = {}) - @cmd = cmd - @returncode, @stdout, @stderr = VMCommand.execute(vm, cmd, options) - end - - def VMCommand.wait_until_remote_shell_is_up(vm, timeout = 90) - try_for(timeout, :msg => "Remote shell seems to be down") do - sleep(20) - Timeout::timeout(10) do - VMCommand.execute(vm, "echo 'true'") - end - end - end - - # The parameter `cmd` cannot contain newlines. Separate multiple - # commands using ";" instead. - # If `:spawn` is false the server will block until it has finished - # executing `cmd`. If it's true the server won't block, and the - # response will always be [0, "", ""] (only used as an - # ACK). execute() will always block until a response is received, - # though. Spawning is useful when starting processes in the - # background (or running scripts that does the same) like our - # onioncircuits wrapper, or any application we want to interact with. - def VMCommand.execute(vm, cmd, options = {}) - options[:user] ||= "root" - options[:spawn] ||= false - type = options[:spawn] ? "spawn" : "call" - socket = TCPSocket.new("127.0.0.1", vm.get_remote_shell_port) - debug_log("#{type}ing as #{options[:user]}: #{cmd}") - begin - #socket.puts(JSON.dump([type, options[:user], cmd])) - socket.puts( "\n") - sleep(1) - socket.puts( "\003") - sleep(1) - socket.puts( cmd + "\n") - sleep(1) - while socket.ready? - s = socket.readline(sep = "\n").chomp("\n") - debug_log("#{type} read: #{s}") if not(options[:spawn]) - if ('true' == s) then - break - end - end - ensure - socket.close - end - if ('true' == s) - return true - else - return VMCommand.execute(vm, cmd, options) - end - end - - def success? - return @returncode == 0 - end - - def failure? - return not(success?) - end - - def to_s - "Return status: #{@returncode}\n" + - "STDOUT:\n" + - @stdout + - "STDERR:\n" + - @stderr - end - -end diff --git a/features/support/helpers/firewall_helper.rb b/features/support/helpers/firewall_helper.rb deleted file mode 100644 index fce363c5..00000000 --- a/features/support/helpers/firewall_helper.rb +++ /dev/null @@ -1,121 +0,0 @@ -require 'packetfu' -require 'ipaddr' - -# Extent IPAddr with a private/public address space checks -class IPAddr - PrivateIPv4Ranges = [ - IPAddr.new("10.0.0.0/8"), - IPAddr.new("172.16.0.0/12"), - IPAddr.new("192.168.0.0/16"), - IPAddr.new("255.255.255.255/32") - ] - - PrivateIPv6Ranges = [ - IPAddr.new("fc00::/7") - ] - - def private? - private_ranges = self.ipv4? ? PrivateIPv4Ranges : PrivateIPv6Ranges - private_ranges.any? { |range| range.include?(self) } - end - - def public? - !private? - end -end - -class FirewallLeakCheck - attr_reader :ipv4_tcp_leaks, :ipv4_nontcp_leaks, :ipv6_leaks, :nonip_leaks, :mac_leaks - - def initialize(pcap_file, options = {}) - options[:accepted_hosts] ||= [] - options[:ignore_lan] ||= true - @pcap_file = pcap_file - packets = PacketFu::PcapFile.new.file_to_array(:filename => @pcap_file) - mac_leaks = Set.new - ipv4_tcp_packets = [] - ipv4_nontcp_packets = [] - ipv6_packets = [] - nonip_packets = [] - packets.each do |p| - if PacketFu::EthPacket.can_parse?(p) - packet = PacketFu::EthPacket.parse(p) - mac_leaks << packet.eth_saddr - mac_leaks << packet.eth_daddr - end - - if PacketFu::TCPPacket.can_parse?(p) - ipv4_tcp_packets << PacketFu::TCPPacket.parse(p) - elsif PacketFu::IPPacket.can_parse?(p) - ipv4_nontcp_packets << PacketFu::IPPacket.parse(p) - elsif PacketFu::IPv6Packet.can_parse?(p) - ipv6_packets << PacketFu::IPv6Packet.parse(p) - elsif PacketFu::Packet.can_parse?(p) - nonip_packets << PacketFu::Packet.parse(p) - else - save_pcap_file - raise "Found something in the pcap file that cannot be parsed" - end - end - ipv4_tcp_hosts = filter_hosts_from_ippackets(ipv4_tcp_packets, - options[:ignore_lan]) - accepted = Set.new(options[:accepted_hosts]) - @mac_leaks = mac_leaks - @ipv4_tcp_leaks = ipv4_tcp_hosts.select { |host| !accepted.member?(host) } - @ipv4_nontcp_leaks = filter_hosts_from_ippackets(ipv4_nontcp_packets, - options[:ignore_lan]) - @ipv6_leaks = filter_hosts_from_ippackets(ipv6_packets, - options[:ignore_lan]) - @nonip_leaks = nonip_packets - end - - def save_pcap_file - save_failure_artifact("Network capture", @pcap_file) - end - - # Returns a list of all unique destination IP addresses found in - # `packets`. Exclude LAN hosts if ignore_lan is set. - def filter_hosts_from_ippackets(packets, ignore_lan) - hosts = [] - packets.each do |p| - candidate = nil - if p.kind_of?(PacketFu::IPPacket) - candidate = p.ip_daddr - elsif p.kind_of?(PacketFu::IPv6Packet) - candidate = p.ipv6_header.ipv6_daddr - else - save_pcap_file - raise "Expected an IP{v4,v6} packet, but got something else:\n" + - p.peek_format - end - if candidate != nil and (not(ignore_lan) or IPAddr.new(candidate).public?) - hosts << candidate - end - end - hosts.uniq - end - - def assert_no_leaks - err = "" - if !@ipv4_tcp_leaks.empty? - err += "The following IPv4 TCP non-Tor Internet hosts were " + - "contacted:\n" + ipv4_tcp_leaks.join("\n") - end - if !@ipv4_nontcp_leaks.empty? - err += "The following IPv4 non-TCP Internet hosts were contacted:\n" + - ipv4_nontcp_leaks.join("\n") - end - if !@ipv6_leaks.empty? - err += "The following IPv6 Internet hosts were contacted:\n" + - ipv6_leaks.join("\n") - end - if !@nonip_leaks.empty? - err += "Some non-IP packets were sent\n" - end - if !err.empty? - save_pcap_file - raise err - end - end - -end diff --git a/features/support/helpers/misc_helpers.rb b/features/support/helpers/misc_helpers.rb deleted file mode 100644 index 7e09411f..00000000 --- a/features/support/helpers/misc_helpers.rb +++ /dev/null @@ -1,253 +0,0 @@ -require 'date' -require 'timeout' -require 'test/unit' - -# Test::Unit adds an at_exit hook which, among other things, consumes -# the command-line arguments that were intended for cucumber. If -# e.g. `--format` was passed it will throw an error since it's not a -# valid option for Test::Unit, and it throwing an error at this time -# (at_exit) will make Cucumber think it failed and consequently exit -# with an error. Fooling Test::Unit that this hook has already run -# works around this craziness. -Test::Unit.run = true - -# Make all the assert_* methods easily accessible in any context. -include Test::Unit::Assertions - -def assert_vmcommand_success(p, msg = nil) - assert(p.success?, msg.nil? ? "Command failed: #{p.cmd}\n" + \ - "error code: #{p.returncode}\n" \ - "stderr: #{p.stderr}" : \ - msg) -end - -# It's forbidden to throw this exception (or subclasses) in anything -# but try_for() below. Just don't use it anywhere else! -class UniqueTryForTimeoutError < Exception -end - -# Call block (ignoring any exceptions it may throw) repeatedly with -# one second breaks until it returns true, or until `timeout` seconds have -# passed when we throw a Timeout::Error exception. -def try_for(timeout, options = {}) - options[:delay] ||= 1 - last_exception = nil - # Create a unique exception used only for this particular try_for - # call's Timeout to allow nested try_for:s. If we used the same one, - # the innermost try_for would catch all outer ones', creating a - # really strange situation. - unique_timeout_exception = Class.new(UniqueTryForTimeoutError) - Timeout::timeout(timeout, unique_timeout_exception) do - loop do - begin - return if yield - rescue NameError, UniqueTryForTimeoutError => e - # NameError most likely means typos, and hiding that is rarely - # (never?) a good idea, so we rethrow them. See below why we - # also rethrow *all* the unique exceptions. - raise e - rescue Exception => e - # All other exceptions are ignored while trying the - # block. Well we save the last exception so we can print it in - # case of a timeout. - last_exception = e - end - sleep options[:delay] - end - end - # At this point the block above either succeeded and we'll return, - # or we are throwing an exception. If the latter, we either have a - # NameError that we'll not catch (and will any try_for below us in - # the stack), or we have a unique exception. That can mean one of - # two things: - # 1. it's the one unique to this try_for, and in that case we'll - # catch it, rethrowing it as something that will be ignored by - # inside the blocks of all try_for:s below us in the stack. - # 2. it's an exception unique to another try_for. Assuming that we - # do not throw the unique exceptions in any other place or way - # than we do it in this function, this means that there is a - # try_for below us in the stack to which this exception must be - # unique to. - # Let 1 be the base step, and 2 the inductive step, and we sort of - # an inductive proof for the correctness of try_for when it's - # nested. It shows that for an infinite stack of try_for:s, any of - # the unique exceptions will be caught only by the try_for instance - # it is unique to, and all try_for:s in between will ignore it so it - # ends up there immediately. -rescue unique_timeout_exception => e - msg = options[:msg] || 'try_for() timeout expired' - if last_exception - msg += "\nLast ignored exception was: " + - "#{last_exception.class}: #{last_exception}" - end - raise Timeout::Error.new(msg) -end - -class TorFailure < StandardError -end - -class MaxRetriesFailure < StandardError -end - -# This will retry the block up to MAX_NEW_TOR_CIRCUIT_RETRIES -# times. The block must raise an exception for a run to be considered -# as a failure. After a failure recovery_proc will be called (if -# given) and the intention with it is to bring us back to the state -# expected by the block, so it can be retried. -def retry_tor(recovery_proc = nil, &block) - tor_recovery_proc = Proc.new do - force_new_tor_circuit - recovery_proc.call if recovery_proc - end - - retry_action($config['MAX_NEW_TOR_CIRCUIT_RETRIES'], - :recovery_proc => tor_recovery_proc, - :operation_name => 'Tor operation', &block) -end - -def retry_i2p(recovery_proc = nil, &block) - retry_action(15, :recovery_proc => recovery_proc, - :operation_name => 'I2P operation', &block) -end - -def retry_action(max_retries, options = {}, &block) - assert(max_retries.is_a?(Integer), "max_retries must be an integer") - options[:recovery_proc] ||= nil - options[:operation_name] ||= 'Operation' - - retries = 1 - loop do - begin - block.call - return - rescue Exception => e - if retries <= max_retries - debug_log("#{options[:operation_name]} failed (Try #{retries} of " + - "#{max_retries}) with:\n" + - "#{e.class}: #{e.message}") - options[:recovery_proc].call if options[:recovery_proc] - retries += 1 - else - raise MaxRetriesFailure.new("#{options[:operation_name]} failed (despite retrying " + - "#{max_retries} times) with\n" + - "#{e.class}: #{e.message}") - end - end - end -end - -def wait_until_tor_is_working - try_for(270) { $vm.execute('/usr/local/sbin/tor-has-bootstrapped').success? } -rescue Timeout::Error => e - c = $vm.execute("journalctl SYSLOG_IDENTIFIER=restart-tor") - if c.success? - debug_log("From the journal:\n" + c.stdout.sub(/^/, " ")) - else - debug_log("Nothing was in the journal about 'restart-tor'") - end - raise e -end - -def convert_bytes_mod(unit) - case unit - when "bytes", "b" then mod = 1 - when "KB" then mod = 10**3 - when "k", "KiB" then mod = 2**10 - when "MB" then mod = 10**6 - when "M", "MiB" then mod = 2**20 - when "GB" then mod = 10**9 - when "G", "GiB" then mod = 2**30 - when "TB" then mod = 10**12 - when "T", "TiB" then mod = 2**40 - else - raise "invalid memory unit '#{unit}'" - end - return mod -end - -def convert_to_bytes(size, unit) - return (size*convert_bytes_mod(unit)).to_i -end - -def convert_to_MiB(size, unit) - return (size*convert_bytes_mod(unit) / (2**20)).to_i -end - -def convert_from_bytes(size, unit) - return size.to_f/convert_bytes_mod(unit).to_f -end - -def cmd_helper(cmd) - if cmd.instance_of?(Array) - cmd << {:err => [:child, :out]} - elsif cmd.instance_of?(String) - cmd += " 2>&1" - end - IO.popen(cmd) do |p| - out = p.readlines.join("\n") - p.close - ret = $? - assert_equal(0, ret, "Command failed (returned #{ret}): #{cmd}:\n#{out}") - return out - end -end - -# This command will grab all router IP addresses from the Tor -# consensus in the VM + the hardcoded TOR_AUTHORITIES. -def get_all_tor_nodes - cmd = 'awk "/^r/ { print \$6 }" /var/lib/tor/cached-microdesc-consensus' - $vm.execute(cmd).stdout.chomp.split("\n") + TOR_AUTHORITIES -end - -def get_free_space(machine, path) - case machine - when 'host' - assert(File.exists?(path), "Path '#{path}' not found on #{machine}.") - free = cmd_helper(["df", path]) - when 'guest' - assert($vm.file_exist?(path), "Path '#{path}' not found on #{machine}.") - free = $vm.execute_successfully("df '#{path}'") - else - raise 'Unsupported machine type #{machine} passed.' - end - output = free.split("\n").last - return output.match(/[^\s]\s+[0-9]+\s+[0-9]+\s+([0-9]+)\s+.*/)[1].chomp.to_i -end - -def random_string_from_set(set, min_len, max_len) - len = (min_len..max_len).to_a.sample - len ||= min_len - (0..len-1).map { |n| set.sample }.join -end - -def random_alpha_string(min_len, max_len = 0) - alpha_set = ('A'..'Z').to_a + ('a'..'z').to_a - random_string_from_set(alpha_set, min_len, max_len) -end - -def random_alnum_string(min_len, max_len = 0) - alnum_set = ('A'..'Z').to_a + ('a'..'z').to_a + (0..9).to_a.map { |n| n.to_s } - random_string_from_set(alnum_set, min_len, max_len) -end - -# Sanitize the filename from unix-hostile filename characters -def sanitize_filename(filename, options = {}) - options[:replacement] ||= '_' - bad_unix_filename_chars = Regexp.new("[^A-Za-z0-9_\\-.,+:]") - filename.gsub(bad_unix_filename_chars, options[:replacement]) -end - -def info_log_artifact_location(type, path) - if $config['ARTIFACTS_BASE_URI'] - # Remove any trailing slashes, we'll add one ourselves - base_url = $config['ARTIFACTS_BASE_URI'].gsub(/\/*$/, "") - path = "#{base_url}/#{File.basename(path)}" - end - info_log("#{type.capitalize}: #{path}") -end - -def pause(message = "Paused") - STDERR.puts - STDERR.puts "#{message} (Press ENTER to continue!)" - STDIN.gets -end diff --git a/features/support/helpers/sikuli_helper.rb b/features/support/helpers/sikuli_helper.rb deleted file mode 100644 index 486b0e2e..00000000 --- a/features/support/helpers/sikuli_helper.rb +++ /dev/null @@ -1,213 +0,0 @@ -require 'rjb' -require 'rjbextension' -$LOAD_PATH << ENV['SIKULI_HOME'] -require 'sikuli-script.jar' -Rjb::load - -package_members = [ - "java.io.FileOutputStream", - "java.io.PrintStream", - "java.lang.System", - "org.sikuli.script.Finder", - "org.sikuli.script.Key", - "org.sikuli.script.KeyModifier", - "org.sikuli.script.Location", - "org.sikuli.script.Match", - "org.sikuli.script.Pattern", - "org.sikuli.script.Region", - "org.sikuli.script.Screen", - "org.sikuli.script.Settings", - ] - -translations = Hash[ - "org.sikuli.script", "Sikuli", - "java.lang", "Java::Lang", - "java.io", "Java::Io", - ] - -for p in package_members - imported_class = Rjb::import(p) - package, ignore, class_name = p.rpartition(".") - next if ! translations.include? package - mod_name = translations[package] - mod = mod_name.split("::").inject(Object) do |parent_obj, child_name| - if parent_obj.const_defined?(child_name, false) - parent_obj.const_get(child_name, false) - else - child_obj = Module.new - parent_obj.const_set(child_name, child_obj) - end - end - mod.const_set(class_name, imported_class) -end - -# Bind Java's stdout to debug_log() via our magical pseudo fifo -# logger. -def bind_java_to_pseudo_fifo_logger - file_output_stream = Java::Io::FileOutputStream.new(DEBUG_LOG_PSEUDO_FIFO) - print_stream = Java::Io::PrintStream.new(file_output_stream) - Java::Lang::System.setOut(print_stream) -end - -def findfailed_hook(pic) - pause("FindFailed for: '#{pic}'") -end - -# Since rjb imports Java classes without creating a corresponding -# Ruby class (it's just an instance of Rjb_JavaProxy) we can't -# monkey patch any class, so additional methods must be added -# to each Screen object. -# -# All Java classes' methods are immediately available in the proxied -# Ruby classes, but care has to be given to match their type. For a -# list of methods, see: <http://doc.sikuli.org/javadoc/index.html>. -# The type "PRSML" is a union of Pattern, Region, Screen, Match and -# Location. -# -# Also, due to limitations in Ruby's syntax we can't do: -# def Sikuli::Screen.new -# so we work around it with the following vairable. -sikuli_script_proxy = Sikuli::Screen -$_original_sikuli_screen_new ||= Sikuli::Screen.method :new - -# For waitAny()/findAny() we are forced to throw this exception since -# Rjb::throw doesn't block until the Java exception has been received -# by Ruby, so strange things can happen. -class FindAnyFailed < StandardError -end - -def sikuli_script_proxy.new(*args) - s = $_original_sikuli_screen_new.call(*args) - - if $config["SIKULI_RETRY_FINDFAILED"] - # The usage of `_invoke()` below exemplifies how one can wrap - # around Java objects' methods when they're imported using RJB. It - # isn't pretty. The seconds argument is the parameter signature, - # which can be obtained by creating the intended Java object using - # RJB, and then calling its `java_methods` method. - - def s.wait(pic, time) - self._invoke('wait', 'Ljava.lang.Object;D', pic, time) - rescue FindFailed => e - findfailed_hook(pic) - self._invoke('wait', 'Ljava.lang.Object;D', pic, time) - end - - def s.find(pic) - self._invoke('find', 'Ljava.lang.Object;', pic) - rescue FindFailed => e - findfailed_hook(pic) - self._invoke('find', 'Ljava.lang.Object;', pic) - end - - def s.waitVanish(pic, time) - self._invoke('waitVanish', 'Ljava.lang.Object;D', pic, time) - rescue FindFailed => e - findfailed_hook(pic) - self._invoke('waitVanish', 'Ljava.lang.Object;D', pic, time) - end - - def s.click(pic) - self._invoke('click', 'Ljava.lang.Object;', pic) - rescue FindFailed => e - findfailed_hook(pic) - self._invoke('click', 'Ljava.lang.Object;', pic) - end - end - - def s.click_point(x, y) - self.click(Sikuli::Location.new(x, y)) - end - - def s.doubleClick_point(x, y) - self.doubleClick(Sikuli::Location.new(x, y)) - end - - def s.click_mid_right_edge(pic) - r = self.find(pic) - top_right = r.getTopRight() - x = top_right.getX - y = top_right.getY + r.getH/2 - self.click_point(x, y) - end - - def s.wait_and_click(pic, time) - self.click(self.wait(pic, time)) - end - - def s.wait_and_double_click(pic, time) - self.doubleClick(self.wait(pic, time)) - end - - def s.wait_and_right_click(pic, time) - self.rightClick(self.wait(pic, time)) - end - - def s.wait_and_hover(pic, time) - self.hover(self.wait(pic, time)) - end - - def s.existsAny(images) - images.each do |image| - region = self.exists(image) - return [image, region] if region - end - return nil - end - - def s.findAny(images) - images.each do |image| - begin - return [image, self.find(image)] - rescue FindFailed - # Ignore. We deal we'll throw an appropriate exception after - # having looped through all images and found none of them. - end - end - # If we've reached this point, none of the images could be found. - raise FindAnyFailed.new("can not find any of the images #{images} on the " + - "screen") - end - - def s.waitAny(images, time) - Timeout::timeout(time) do - loop do - result = self.existsAny(images) - return result if result - end - end - rescue Timeout::Error - raise FindAnyFailed.new("can not find any of the images #{images} on the " + - "screen") - end - - def s.hover_point(x, y) - self.hover(Sikuli::Location.new(x, y)) - end - - def s.hide_cursor - self.hover_point(self.w, self.h/2) - end - - s -end - -# Configure sikuli - -# ruby and rjb doesn't play well together when it comes to static -# fields (and possibly methods) so we instantiate and access the field -# via objects instead. It actually works inside this file, but when -# it's required from "outside", and the file has been completely -# required, ruby's require method complains that the method for the -# field accessor is missing. -sikuli_settings = Sikuli::Settings.new -sikuli_settings.OcrDataPath = $config["TMPDIR"] -# sikuli_ruby, which we used before, defaulted to 0.9 minimum -# similarity, so all our current images are adapted to that value. -# Also, Sikuli's default of 0.7 is simply too low (many false -# positives). -sikuli_settings.MinSimilarity = 0.9 -sikuli_settings.ActionLogs = true -sikuli_settings.DebugLogs = false -sikuli_settings.InfoLogs = true -sikuli_settings.ProfileLogs = true diff --git a/features/support/helpers/sniffing_helper.rb b/features/support/helpers/sniffing_helper.rb deleted file mode 100644 index 213411eb..00000000 --- a/features/support/helpers/sniffing_helper.rb +++ /dev/null @@ -1,43 +0,0 @@ -# -# Sniffer is a very dumb wrapper to start and stop tcpdumps instances, possibly -# with customized filters. Captured traffic is stored in files whose name -# depends on the sniffer name. The resulting captured packets for each sniffers -# can be accessed as an array through its `packets` method. -# -# Use of more rubyish internal ways to sniff a network like with pcap-able gems -# is waaay to much resource consumming, notmuch reliable and soooo slow. Let's -# not bother too much with that. :) -# -# Should put all that in a Module. - -class Sniffer - - attr_reader :name, :pcap_file, :pid - - def initialize(name, vmnet) - @name = name - @vmnet = vmnet - pcap_name = sanitize_filename("#{name}.pcap") - @pcap_file = "#{$config["TMPDIR"]}/#{pcap_name}" - end - - def capture(filter="not ether src host #{@vmnet.bridge_mac} and not ether proto \\arp and not ether proto \\rarp") - job = IO.popen(["/usr/sbin/tcpdump", "-n", "-i", @vmnet.bridge_name, "-w", - @pcap_file, "-U", filter, :err => ["/dev/null", "w"]]) - @pid = job.pid - end - - def stop - begin - Process.kill("TERM", @pid) - rescue - # noop - end - end - - def clear - if File.exist?(@pcap_file) - File.delete(@pcap_file) - end - end -end diff --git a/features/support/helpers/sshd_helper.rb b/features/support/helpers/sshd_helper.rb deleted file mode 100644 index 2e0069c0..00000000 --- a/features/support/helpers/sshd_helper.rb +++ /dev/null @@ -1,67 +0,0 @@ -require 'tempfile' - -class SSHServer - def initialize(sshd_host, sshd_port, authorized_keys = nil) - @sshd_host = sshd_host - @sshd_port = sshd_port - @authorized_keys = authorized_keys - @pid = nil - end - - def start - @sshd_key_file = Tempfile.new("ssh_host_rsa_key", $config["TMPDIR"]) - # 'hack' to prevent ssh-keygen from prompting to overwrite the file - File.delete(@sshd_key_file.path) - cmd_helper(['ssh-keygen', '-t', 'rsa', '-N', "", '-f', "#{@sshd_key_file.path}"]) - @sshd_key_file.close - - sshd_config =<<EOF -Port #{@sshd_port} -ListenAddress #{@sshd_host} -UsePrivilegeSeparation no -HostKey #{@sshd_key_file.path} -Pidfile #{$config['TMPDIR']}/ssh.pid -EOF - - @sshd_config_file = Tempfile.new("sshd_config", $config["TMPDIR"]) - @sshd_config_file.write(sshd_config) - - if @authorized_keys - @authorized_keys_file = Tempfile.new("authorized_keys", $config['TMPDIR']) - @authorized_keys_file.write(@authorized_keys) - @authorized_keys_file.close - @sshd_config_file.write("AuthorizedKeysFile #{@authorized_keys_file.path}") - end - - @sshd_config_file.close - - cmd = ["/usr/sbin/sshd", "-4", "-f", @sshd_config_file.path, "-D"] - - job = IO.popen(cmd) - @pid = job.pid - end - - def stop - File.delete("#{@sshd_key_file.path}.pub") - File.delete("#{$config['TMPDIR']}/ssh.pid") - begin - Process.kill("TERM", @pid) - rescue - # noop - end - end - - def active? - begin - ret = Process.kill(0, @pid) - rescue Errno::ESRCH => e - if e.message == "No such process" - return false - else - raise e - end - end - assert_equal(1, ret, "This shouldn't happen") - return true - end -end diff --git a/features/support/helpers/storage_helper.rb b/features/support/helpers/storage_helper.rb deleted file mode 100644 index 21537a92..00000000 --- a/features/support/helpers/storage_helper.rb +++ /dev/null @@ -1,216 +0,0 @@ -# Helper class for manipulating VM storage *volumes*, i.e. it deals -# only with creation of images and keeps a name => volume path lookup -# table (plugging drives or getting info of plugged devices is done in -# the VM class). We'd like better coupling, but given the ridiculous -# disconnect between Libvirt::StoragePool and Libvirt::Domain (hint: -# they have nothing with each other to do whatsoever) it's what makes -# sense. - -require 'libvirt' -require 'guestfs' -require 'rexml/document' -require 'etc' - -class VMStorage - - def initialize(virt, xml_path) - @virt = virt - @xml_path = xml_path - pool_xml = REXML::Document.new(File.read("#{@xml_path}/storage_pool.xml")) - pool_name = pool_xml.elements['pool/name'].text - @pool_path = "#{$config["TMPDIR"]}/#{pool_name}" - begin - @pool = @virt.lookup_storage_pool_by_name(pool_name) - rescue Libvirt::RetrieveError - @pool = nil - end - if @pool and not(KEEP_SNAPSHOTS) - VMStorage.clear_storage_pool(@pool) - @pool = nil - end - unless @pool - pool_xml.elements['pool/target/path'].text = @pool_path - @pool = @virt.define_storage_pool_xml(pool_xml.to_s) - if not(Dir.exists?(@pool_path)) - # We'd like to use @pool.build, which will just create the - # @pool_path directory, but it does so with root:root as owner - # (at least with libvirt 1.2.21-2). libvirt itself can handle - # that situation, but guestfs (at least with <= - # 1:1.28.12-1+b3) cannot when invoked by a non-root user, - # which we want to support. - FileUtils.mkdir(@pool_path) - FileUtils.chown(nil, 'libvirt-qemu', @pool_path) - FileUtils.chmod("ug+wrx", @pool_path) - end - end - @pool.create unless @pool.active? - @pool.refresh - end - - def VMStorage.clear_storage_pool_volumes(pool) - was_not_active = !pool.active? - if was_not_active - pool.create - end - pool.list_volumes.each do |vol_name| - vol = pool.lookup_volume_by_name(vol_name) - vol.delete - end - if was_not_active - pool.destroy - end - rescue - # Some of the above operations can fail if the pool's path was - # deleted by external means; let's ignore that. - end - - def VMStorage.clear_storage_pool(pool) - VMStorage.clear_storage_pool_volumes(pool) - pool.destroy if pool.active? - pool.undefine - end - - def clear_pool - VMStorage.clear_storage_pool(@pool) - end - - def clear_volumes - VMStorage.clear_storage_pool_volumes(@pool) - end - - def delete_volume(name) - @pool.lookup_volume_by_name(name).delete - end - - def create_new_disk(name, options = {}) - options[:size] ||= 2 - options[:unit] ||= "GiB" - options[:type] ||= "qcow2" - # Require 'slightly' more space to be available to give a bit more leeway - # with rounding, temp file creation, etc. - reserved = 500 - needed = convert_to_MiB(options[:size].to_i, options[:unit]) - avail = convert_to_MiB(get_free_space('host', @pool_path), "KiB") - assert(avail - reserved >= needed, - "Error creating disk \"#{name}\" in \"#{@pool_path}\". " \ - "Need #{needed} MiB but only #{avail} MiB is available of " \ - "which #{reserved} MiB is reserved for other temporary files.") - begin - old_vol = @pool.lookup_volume_by_name(name) - rescue Libvirt::RetrieveError - # noop - else - old_vol.delete - end - uid = Etc::getpwnam("libvirt-qemu").uid - gid = Etc::getgrnam("libvirt-qemu").gid - vol_xml = REXML::Document.new(File.read("#{@xml_path}/volume.xml")) - vol_xml.elements['volume/name'].text = name - size_b = convert_to_bytes(options[:size].to_f, options[:unit]) - vol_xml.elements['volume/capacity'].text = size_b.to_s - vol_xml.elements['volume/target/format'].attributes["type"] = options[:type] - vol_xml.elements['volume/target/path'].text = "#{@pool_path}/#{name}" - vol_xml.elements['volume/target/permissions/owner'].text = uid.to_s - vol_xml.elements['volume/target/permissions/group'].text = gid.to_s - vol = @pool.create_volume_xml(vol_xml.to_s) - @pool.refresh - end - - def clone_to_new_disk(from, to) - begin - old_to_vol = @pool.lookup_volume_by_name(to) - rescue Libvirt::RetrieveError - # noop - else - old_to_vol.delete - end - from_vol = @pool.lookup_volume_by_name(from) - xml = REXML::Document.new(from_vol.xml_desc) - pool_path = REXML::Document.new(@pool.xml_desc).elements['pool/target/path'].text - xml.elements['volume/name'].text = to - xml.elements['volume/target/path'].text = "#{pool_path}/#{to}" - @pool.create_volume_xml_from(xml.to_s, from_vol) - end - - def disk_format(name) - vol = @pool.lookup_volume_by_name(name) - vol_xml = REXML::Document.new(vol.xml_desc) - return vol_xml.elements['volume/target/format'].attributes["type"] - end - - def disk_path(name) - @pool.lookup_volume_by_name(name).path - end - - def disk_mklabel(name, parttype) - disk = { - :path => disk_path(name), - :opts => { - :format => disk_format(name) - } - } - guestfs_disk_helper(disk) do |g, disk_handle| - g.part_init(disk_handle, parttype) - end - end - - def disk_mkpartfs(name, parttype, fstype, opts = {}) - opts[:label] ||= nil - opts[:luks_password] ||= nil - disk = { - :path => disk_path(name), - :opts => { - :format => disk_format(name) - } - } - guestfs_disk_helper(disk) do |g, disk_handle| - g.part_disk(disk_handle, parttype) - g.part_set_name(disk_handle, 1, opts[:label]) if opts[:label] - primary_partition = g.list_partitions()[0] - if opts[:luks_password] - g.luks_format(primary_partition, opts[:luks_password], 0) - luks_mapping = File.basename(primary_partition) + "_unlocked" - g.luks_open(primary_partition, opts[:luks_password], luks_mapping) - luks_dev = "/dev/mapper/#{luks_mapping}" - g.mkfs(fstype, luks_dev) - g.luks_close(luks_dev) - else - g.mkfs(fstype, primary_partition) - end - end - end - - def disk_mkswap(name, parttype) - disk = { - :path => disk_path(name), - :opts => { - :format => disk_format(name) - } - } - guestfs_disk_helper(disk) do |g, disk_handle| - g.part_disk(disk_handle, parttype) - primary_partition = g.list_partitions()[0] - g.mkswap(primary_partition) - end - end - - def guestfs_disk_helper(*disks) - assert(block_given?) - g = Guestfs::Guestfs.new() - g.set_trace(1) - message_callback = Proc.new do |event, _, message, _| - debug_log("libguestfs: #{Guestfs.event_to_string(event)}: #{message}") - end - g.set_event_callback(message_callback, - Guestfs::EVENT_TRACE) - g.set_autosync(1) - disks.each do |disk| - g.add_drive_opts(disk[:path], disk[:opts]) - end - g.launch() - yield(g, *g.list_devices()) - ensure - g.close - end - -end diff --git a/features/support/helpers/vm_helper.rb b/features/support/helpers/vm_helper.rb deleted file mode 100644 index 6d7204d4..00000000 --- a/features/support/helpers/vm_helper.rb +++ /dev/null @@ -1,676 +0,0 @@ -require 'libvirt' -require 'rexml/document' - -class ExecutionFailedInVM < StandardError -end - -class VMNet - - attr_reader :net_name, :net - - def initialize(virt, xml_path) - @virt = virt - @net_name = LIBVIRT_NETWORK_NAME - net_xml = File.read("#{xml_path}/default_net.xml") - rexml = REXML::Document.new(net_xml) - rexml.elements['network'].add_element('name') - rexml.elements['network/name'].text = @net_name - rexml.elements['network'].add_element('uuid') - rexml.elements['network/uuid'].text = LIBVIRT_NETWORK_UUID - update(rexml.to_s) - rescue Exception => e - destroy_and_undefine - raise e - end - - # We lookup by name so we also catch networks from previous test - # suite runs that weren't properly cleaned up (e.g. aborted). - def destroy_and_undefine - begin - old_net = @virt.lookup_network_by_name(@net_name) - old_net.destroy if old_net.active? - old_net.undefine - rescue - end - end - - def update(xml) - destroy_and_undefine - @net = @virt.define_network_xml(xml) - @net.create - end - - def bridge_name - @net.bridge_name - end - - def bridge_ip_addr - net_xml = REXML::Document.new(@net.xml_desc) - IPAddr.new(net_xml.elements['network/ip'].attributes['address']).to_s - end - - def guest_real_mac - net_xml = REXML::Document.new(@net.xml_desc) - net_xml.elements['network/ip/dhcp/host/'].attributes['mac'] - end - - def bridge_mac - File.open("/sys/class/net/#{bridge_name}/address", "rb").read.chomp - end -end - - -class VM - - attr_reader :domain, :display, :vmnet, :storage - - def initialize(virt, xml_path, vmnet, storage, x_display) - @virt = virt - @xml_path = xml_path - @vmnet = vmnet - @storage = storage - @domain_name = LIBVIRT_DOMAIN_NAME - default_domain_xml = File.read("#{@xml_path}/default.xml") - rexml = REXML::Document.new(default_domain_xml) - rexml.elements['domain'].add_element('name') - rexml.elements['domain/name'].text = @domain_name - rexml.elements['domain'].add_element('uuid') - rexml.elements['domain/uuid'].text = LIBVIRT_DOMAIN_UUID - update(rexml.to_s) - @display = Display.new(@domain_name, x_display) - set_cdrom_boot(TAILS_ISO) - plug_network - rescue Exception => e - destroy_and_undefine - raise e - end - - def update(xml) - destroy_and_undefine - @domain = @virt.define_domain_xml(xml) - end - - # We lookup by name so we also catch domains from previous test - # suite runs that weren't properly cleaned up (e.g. aborted). - def destroy_and_undefine - @display.stop if @display && @display.active? - begin - old_domain = @virt.lookup_domain_by_name(@domain_name) - old_domain.destroy if old_domain.active? - old_domain.undefine - rescue - end - end - - def real_mac - @vmnet.guest_real_mac - end - - def set_hardware_clock(time) - assert(not(is_running?), 'The hardware clock cannot be set when the ' + - 'VM is running') - assert(time.instance_of?(Time), "Argument must be of type 'Time'") - adjustment = (time - Time.now).to_i - domain_rexml = REXML::Document.new(@domain.xml_desc) - clock_rexml_element = domain_rexml.elements['domain'].add_element('clock') - clock_rexml_element.add_attributes('offset' => 'variable', - 'basis' => 'utc', - 'adjustment' => adjustment.to_s) - update(domain_rexml.to_s) - end - - def set_network_link_state(state) - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/devices/interface/link'].attributes['state'] = state - if is_running? - @domain.update_device(domain_xml.elements['domain/devices/interface'].to_s) - else - update(domain_xml.to_s) - end - end - - def plug_network - set_network_link_state('up') - end - - def unplug_network - set_network_link_state('down') - end - - def set_boot_device(dev) - if is_running? - raise "boot settings can only be set for inactive vms" - end - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/os/boot'].attributes['dev'] = dev - update(domain_xml.to_s) - end - - def set_cdrom_image(image) - image = nil if image == '' - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements.each('domain/devices/disk') do |e| - if e.attribute('device').to_s == "cdrom" - if image.nil? - e.elements.delete('source') - else - if ! e.elements['source'] - e.add_element('source') - end - e.elements['source'].attributes['file'] = image - end - if is_running? - @domain.update_device(e.to_s) - else - update(domain_xml.to_s) - end - end - end - end - - def remove_cdrom - set_cdrom_image(nil) - rescue Libvirt::Error => e - # While the CD-ROM is removed successfully we still get this - # error, so let's ignore it. - acceptable_error = - "Call to virDomainUpdateDeviceFlags failed: internal error: unable to " + - "execute QEMU command 'eject': (Tray of device '.*' is not open|" + - "Device '.*' is locked)" - raise e if not(Regexp.new(acceptable_error).match(e.to_s)) - end - - def set_cdrom_boot(image) - if is_running? - raise "boot settings can only be set for inactive vms" - end - set_boot_device('cdrom') - set_cdrom_image(image) - end - - def list_disk_devs - ret = [] - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements.each('domain/devices/disk') do |e| - ret << e.elements['target'].attribute('dev').to_s - end - return ret - end - - def plug_drive(name, type) - if disk_plugged?(name) - raise "disk '#{name}' already plugged" - end - removable_usb = nil - case type - when "removable usb", "usb" - type = "usb" - removable_usb = "on" - when "non-removable usb" - type = "usb" - removable_usb = "off" - end - # Get the next free /dev/sdX on guest - letter = 'a' - dev = "sd" + letter - while list_disk_devs.include?(dev) - letter = (letter[0].ord + 1).chr - dev = "sd" + letter - end - assert letter <= 'z' - - xml = REXML::Document.new(File.read("#{@xml_path}/disk.xml")) - xml.elements['disk/source'].attributes['file'] = @storage.disk_path(name) - xml.elements['disk/driver'].attributes['type'] = @storage.disk_format(name) - xml.elements['disk/target'].attributes['dev'] = dev - xml.elements['disk/target'].attributes['bus'] = type - xml.elements['disk/target'].attributes['removable'] = removable_usb if removable_usb - - if is_running? - @domain.attach_device(xml.to_s) - else - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/devices'].add_element(xml) - update(domain_xml.to_s) - end - end - - def disk_xml_desc(name) - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements.each('domain/devices/disk') do |e| - begin - if e.elements['source'].attribute('file').to_s == @storage.disk_path(name) - return e.to_s - end - rescue - next - end - end - return nil - end - - def disk_rexml_desc(name) - xml = disk_xml_desc(name) - if xml - return REXML::Document.new(xml) - else - return nil - end - end - - def unplug_drive(name) - xml = disk_xml_desc(name) - @domain.detach_device(xml) - end - - def disk_type(dev) - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements.each('domain/devices/disk') do |e| - if e.elements['target'].attribute('dev').to_s == dev - return e.elements['driver'].attribute('type').to_s - end - end - raise "No such disk device '#{dev}'" - end - - def disk_dev(name) - rexml = disk_rexml_desc(name) or return nil - return "/dev/" + rexml.elements['disk/target'].attribute('dev').to_s - end - - def disk_name(dev) - dev = File.basename(dev) - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements.each('domain/devices/disk') do |e| - if /^#{e.elements['target'].attribute('dev').to_s}/.match(dev) - return File.basename(e.elements['source'].attribute('file').to_s) - end - end - raise "No such disk device '#{dev}'" - end - - def udisks_disk_dev(name) - return disk_dev(name).gsub('/dev/', '/org/freedesktop/UDisks/devices/') - end - - def disk_detected?(name) - dev = disk_dev(name) or return false - return execute("test -b #{dev}").success? - end - - def disk_plugged?(name) - return not(disk_xml_desc(name).nil?) - end - - def set_disk_boot(name, type) - if is_running? - raise "boot settings can only be set for inactive vms" - end - plug_drive(name, type) if not(disk_plugged?(name)) - set_boot_device('hd') - # For some reason setting the boot device doesn't prevent cdrom - # boot unless it's empty - remove_cdrom - end - - # XXX-9p: Shares don't work together with snapshot save+restore. See - # XXX-9p in common_steps.rb for more information. - def add_share(source, tag) - if is_running? - raise "shares can only be added to inactive vms" - end - # The complete source directory must be group readable by the user - # running the virtual machine, and world readable so the user inside - # the VM can access it (since we use the passthrough security model). - FileUtils.chown_R(nil, "libvirt-qemu", source) - FileUtils.chmod_R("go+rX", source) - xml = REXML::Document.new(File.read("#{@xml_path}/fs_share.xml")) - xml.elements['filesystem/source'].attributes['dir'] = source - xml.elements['filesystem/target'].attributes['dir'] = tag - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/devices'].add_element(xml) - update(domain_xml.to_s) - end - - def list_shares - list = [] - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements.each('domain/devices/filesystem') do |e| - list << e.elements['target'].attribute('dir').to_s - end - return list - end - - def set_ram_size(size, unit = "KiB") - raise "System memory can only be added to inactive vms" if is_running? - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/memory'].text = size - domain_xml.elements['domain/memory'].attributes['unit'] = unit - domain_xml.elements['domain/currentMemory'].text = size - domain_xml.elements['domain/currentMemory'].attributes['unit'] = unit - update(domain_xml.to_s) - end - - def get_ram_size_in_bytes - domain_xml = REXML::Document.new(@domain.xml_desc) - unit = domain_xml.elements['domain/memory'].attribute('unit').to_s - size = domain_xml.elements['domain/memory'].text.to_i - return convert_to_bytes(size, unit) - end - - def set_arch(arch) - raise "System architecture can only be set to inactive vms" if is_running? - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/os/type'].attributes['arch'] = arch - update(domain_xml.to_s) - end - - def add_hypervisor_feature(feature) - raise "Hypervisor features can only be added to inactive vms" if is_running? - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/features'].add_element(feature) - update(domain_xml.to_s) - end - - def drop_hypervisor_feature(feature) - raise "Hypervisor features can only be fropped from inactive vms" if is_running? - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/features'].delete_element(feature) - update(domain_xml.to_s) - end - - def disable_pae_workaround - # add_hypervisor_feature("nonpae") results in a libvirt error, and - # drop_hypervisor_feature("pae") alone won't disable pae. Hence we - # use this workaround. - xml = <<EOF - <qemu:commandline xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'> - <qemu:arg value='-cpu'/> - <qemu:arg value='qemu32,-pae'/> - </qemu:commandline> -EOF - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain'].add_element(REXML::Document.new(xml)) - update(domain_xml.to_s) - end - - def set_os_loader(type) - if is_running? - raise "boot settings can only be set for inactive vms" - end - if type == 'UEFI' - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements['domain/os'].add_element(REXML::Document.new( - '<loader>/usr/share/ovmf/OVMF.fd</loader>' - )) - update(domain_xml.to_s) - else - raise "unsupported OS loader type" - end - end - - def is_running? - begin - return @domain.active? - rescue - return false - end - end - - def execute(cmd, options = {}) - options[:user] ||= "root" - options[:spawn] ||= false - if options[:libs] - libs = options[:libs] - options.delete(:libs) - libs = [libs] if not(libs.methods.include? :map) - cmds = libs.map do |lib_name| - ". /usr/local/lib/tails-shell-library/#{lib_name}.sh" - end - cmds << cmd - cmd = cmds.join(" && ") - end - return VMCommand.new(self, cmd, options) - end - - def execute_successfully(*args) - p = execute(*args) - begin - assert_vmcommand_success(p) - rescue Test::Unit::AssertionFailedError => e - raise ExecutionFailedInVM.new(e) - end - return p - end - - def spawn(cmd, options = {}) - options[:spawn] = true - return execute(cmd, options) - end - - def wait_until_remote_shell_is_up(timeout = 90) - VMCommand.wait_until_remote_shell_is_up(self, timeout) - end - - def host_to_guest_time_sync - host_time= DateTime.now.strftime("%s").to_s - execute("date -s '@#{host_time}'").success? - end - - def has_network? - return execute("/sbin/ifconfig eth0 | grep -q 'inet addr'").success? - end - - def has_process?(process) - return execute("pidof -x -o '%PPID' " + process).success? - end - - def pidof(process) - return execute("pidof -x -o '%PPID' " + process).stdout.chomp.split - end - - def select_virtual_desktop(desktop_number, user = LIVE_USER) - assert(desktop_number >= 0 && desktop_number <=3, - "Only values between 0 and 3 are valid virtual desktop numbers") - execute_successfully( - "xdotool set_desktop '#{desktop_number}'", - :user => user - ) - end - - def focus_window(window_title, user = LIVE_USER) - def do_focus(window_title, user) - execute_successfully( - "xdotool search --name '#{window_title}' windowactivate --sync", - :user => user - ) - end - - begin - do_focus(window_title, user) - rescue ExecutionFailedInVM - # Often when xdotool fails to focus a window it'll work when retried - # after redrawing the screen. Switching to a new virtual desktop then - # back seems to be a reliable way to handle this. - select_virtual_desktop(3) - select_virtual_desktop(0) - sleep 5 # there aren't any visual indicators which can be used here - do_focus(window_title, user) - end - end - - def file_exist?(file) - execute("test -e '#{file}'").success? - end - - def directory_exist?(directory) - execute("test -d '#{directory}'").success? - end - - def file_content(file, user = 'root') - # We don't quote #{file} on purpose: we sometimes pass environment variables - # or globs that we want to be interpreted by the shell. - cmd = execute("cat #{file}", :user => user) - assert(cmd.success?, - "Could not cat '#{file}':\n#{cmd.stdout}\n#{cmd.stderr}") - return cmd.stdout - end - - def file_append(file, lines, user = 'root') - lines = lines.split("\n") if lines.class == String - lines.each do |line| - cmd = execute("echo '#{line}' >> '#{file}'", :user => user) - assert(cmd.success?, - "Could not append to '#{file}':\n#{cmd.stdout}\n#{cmd.stderr}") - end - end - - def set_clipboard(text) - execute_successfully("echo -n '#{text}' | xsel --input --clipboard", - :user => LIVE_USER) - end - - def get_clipboard - execute_successfully("xsel --output --clipboard", :user => LIVE_USER).stdout - end - - def internal_snapshot_xml(name) - disk_devs = list_disk_devs - disks_xml = " <disks>\n" - for dev in disk_devs - snapshot_type = disk_type(dev) == "qcow2" ? 'internal' : 'no' - disks_xml += - " <disk name='#{dev}' snapshot='#{snapshot_type}'></disk>\n" - end - disks_xml += " </disks>" - return <<-EOF -<domainsnapshot> - <name>#{name}</name> - <description>Snapshot for #{name}</description> -#{disks_xml} - </domainsnapshot> -EOF - end - - def VM.ram_only_snapshot_path(name) - return "#{$config["TMPDIR"]}/#{name}-snapshot.memstate" - end - - def save_snapshot(name) - # If we have no qcow2 disk device, we'll use "memory state" - # snapshots, and if we have at least one qcow2 disk device, we'll - # use internal "system checkpoint" (memory + disks) snapshots. We - # have to do this since internal snapshots don't work when no - # such disk is available. We can do this with external snapshots, - # which are better in many ways, but libvirt doesn't know how to - # restore (revert back to) them yet. - # WARNING: If only transient disks, i.e. disks that were plugged - # after starting the domain, are used then the memory state will - # be dropped. External snapshots would also fix this. - internal_snapshot = false - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements.each('domain/devices/disk') do |e| - if e.elements['driver'].attribute('type').to_s == "qcow2" - internal_snapshot = true - break - end - end - - # Note: In this case the "opposite" of `internal_snapshot` is not - # anything relating to external snapshots, but actually "memory - # state"(-only) snapshots. - if internal_snapshot - xml = internal_snapshot_xml(name) - @domain.snapshot_create_xml(xml) - else - snapshot_path = VM.ram_only_snapshot_path(name) - @domain.save(snapshot_path) - # For consistency with the internal snapshot case (which is - # "live", so the domain doesn't go down) we immediately restore - # the snapshot. - # Assumption: that *immediate* save + restore doesn't mess up - # with network state and similar, and is fast enough to not make - # the clock drift too much. - restore_snapshot(name) - end - end - - def restore_snapshot(name) - @domain.destroy if is_running? - @display.stop if @display and @display.active? - # See comment in save_snapshot() for details on why we use two - # different type of snapshots. - potential_ram_only_snapshot_path = VM.ram_only_snapshot_path(name) - if File.exist?(potential_ram_only_snapshot_path) - Libvirt::Domain::restore(@virt, potential_ram_only_snapshot_path) - @domain = @virt.lookup_domain_by_name(@domain_name) - else - begin - potential_internal_snapshot = @domain.lookup_snapshot_by_name(name) - @domain.revert_to_snapshot(potential_internal_snapshot) - rescue Libvirt::RetrieveError - raise "No such (internal nor external) snapshot #{name}" - end - end - @display.start - end - - def VM.remove_snapshot(name) - old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME) - potential_ram_only_snapshot_path = VM.ram_only_snapshot_path(name) - if File.exist?(potential_ram_only_snapshot_path) - File.delete(potential_ram_only_snapshot_path) - else - snapshot = old_domain.lookup_snapshot_by_name(name) - snapshot.delete - end - end - - def VM.snapshot_exists?(name) - return true if File.exist?(VM.ram_only_snapshot_path(name)) - old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME) - snapshot = old_domain.lookup_snapshot_by_name(name) - return snapshot != nil - rescue Libvirt::RetrieveError - return false - end - - def VM.remove_all_snapshots - Dir.glob("#{$config["TMPDIR"]}/*-snapshot.memstate").each do |file| - File.delete(file) - end - old_domain = $virt.lookup_domain_by_name(LIBVIRT_DOMAIN_NAME) - old_domain.list_all_snapshots.each { |snapshot| snapshot.delete } - rescue Libvirt::RetrieveError - # No such domain, so no snapshots either. - end - - def start - return if is_running? - @domain.create - @display.start - end - - def reset - @domain.reset if is_running? - end - - def power_off - @domain.destroy if is_running? - @display.stop - end - - def take_screenshot(description) - @display.take_screenshot(description) - end - - def get_remote_shell_port - domain_xml = REXML::Document.new(@domain.xml_desc) - domain_xml.elements.each('domain/devices/serial') do |e| - if e.attribute('type').to_s == "tcp" - return e.elements['source'].attribute('service').to_s.to_i - end - end - end - -end diff --git a/features/support/hooks.rb b/features/support/hooks.rb deleted file mode 100644 index b3bdecef..00000000 --- a/features/support/hooks.rb +++ /dev/null @@ -1,280 +0,0 @@ -require 'fileutils' -require 'rb-inotify' -require 'time' -require 'tmpdir' - -# Run once, before any feature -AfterConfiguration do |config| - # Reorder the execution of some features. As we progress through a - # run we accumulate more and more snapshots and hence use more and - # more disk space, but some features will leave nothing behind - # and/or possibly use large amounts of disk space temporarily for - # various reasons. By running these first we minimize the amount of - # disk space needed. - prioritized_features = [ - # Features not using snapshots but using large amounts of scratch - # space for other reasons: - 'features/erase_memory.feature', - 'features/untrusted_partitions.feature', - # Features using temporary snapshots: - 'features/apt.feature', - 'features/i2p.feature', - 'features/root_access_control.feature', - 'features/time_syncing.feature', - 'features/tor_bridges.feature', - # This feature needs the almost biggest snapshot (USB install, - # excluding persistence) and will create yet another disk and - # install Tails on it. This should be the peak of disk usage. - 'features/usb_install.feature', - ] - feature_files = config.feature_files - # The &-intersection is specified to keep the element ordering of - # the *left* operand. - intersection = prioritized_features & feature_files - if not intersection.empty? - feature_files -= intersection - feature_files = intersection + feature_files - config.define_singleton_method(:feature_files) { feature_files } - end - - # Used to keep track of when we start our first @product feature, when - # we'll do some special things. - $started_first_product_feature = false - - if File.exist?($config["TMPDIR"]) - if !File.directory?($config["TMPDIR"]) - raise "Temporary directory '#{$config["TMPDIR"]}' exists but is not a " + - "directory" - end - if !File.owned?($config["TMPDIR"]) - raise "Temporary directory '#{$config["TMPDIR"]}' must be owned by the " + - "current user" - end - FileUtils.chmod(0755, $config["TMPDIR"]) - else - begin - FileUtils.mkdir_p($config["TMPDIR"]) - rescue Errno::EACCES => e - raise "Cannot create temporary directory: #{e.to_s}" - end - end - - # Start a thread that monitors a pseudo fifo file and debug_log():s - # anything written to it "immediately" (well, as fast as inotify - # detects it). We're forced to a convoluted solution like this - # because CRuby's thread support is horribly as soon as IO is mixed - # in (other threads get blocked). - FileUtils.rm(DEBUG_LOG_PSEUDO_FIFO) if File.exist?(DEBUG_LOG_PSEUDO_FIFO) - FileUtils.touch(DEBUG_LOG_PSEUDO_FIFO) - at_exit do - FileUtils.rm(DEBUG_LOG_PSEUDO_FIFO) if File.exist?(DEBUG_LOG_PSEUDO_FIFO) - end - Thread.new do - File.open(DEBUG_LOG_PSEUDO_FIFO) do |fd| - watcher = INotify::Notifier.new - watcher.watch(DEBUG_LOG_PSEUDO_FIFO, :modify) do - line = fd.read.chomp - debug_log(line) if line and line.length > 0 - end - watcher.run - end - end - # Fix Sikuli's debug_log():ing. - bind_java_to_pseudo_fifo_logger -end - -# Common -######## - -After do - if @after_scenario_hooks - @after_scenario_hooks.each { |block| block.call } - end - @after_scenario_hooks = Array.new -end - -BeforeFeature('@product', '@source') do |feature| - raise "Feature #{feature.file} is tagged both @product and @source, " + - "which is an impossible combination" -end - -at_exit do - $vm.destroy_and_undefine if $vm - if $virt - unless KEEP_SNAPSHOTS - VM.remove_all_snapshots - $vmstorage.clear_pool - end - $vmnet.destroy_and_undefine - $virt.close - end - # The artifacts directory is empty (and useless) if it contains - # nothing but the mandatory . and .. - if Dir.entries(ARTIFACTS_DIR).size <= 2 - FileUtils.rmdir(ARTIFACTS_DIR) - end -end - -# For @product tests -#################### - -def add_after_scenario_hook(&block) - @after_scenario_hooks ||= Array.new - @after_scenario_hooks << block -end - -def save_failure_artifact(type, path) - $failure_artifacts << [type, path] -end - -BeforeFeature('@product') do |feature| - if TAILS_ISO.nil? - raise "No ISO image specified, and none could be found in the " + - "current directory" - end - if File.exist?(TAILS_ISO) - # Workaround: when libvirt takes ownership of the ISO image it may - # become unreadable for the live user inside the guest in the - # host-to-guest share used for some tests. - - if !File.world_readable?(TAILS_ISO) - if File.owned?(TAILS_ISO) - File.chmod(0644, TAILS_ISO) - else - raise "warning: the Tails ISO image must be world readable or be " + - "owned by the current user to be available inside the guest " + - "VM via host-to-guest shares, which is required by some tests" - end - end - else - raise "The specified Tails ISO image '#{TAILS_ISO}' does not exist" - end - if !File.exist?(OLD_TAILS_ISO) - raise "The specified old Tails ISO image '#{OLD_TAILS_ISO}' does not exist" - end - if not($started_first_product_feature) - $virt = Libvirt::open("qemu:///system") - VM.remove_all_snapshots if !KEEP_SNAPSHOTS - $vmnet = VMNet.new($virt, VM_XML_PATH) - $vmstorage = VMStorage.new($virt, VM_XML_PATH) - $started_first_product_feature = true - end -end - -AfterFeature('@product') do - unless KEEP_SNAPSHOTS - checkpoints.each do |name, vals| - if vals[:temporary] and VM.snapshot_exists?(name) - VM.remove_snapshot(name) - end - end - end -end - -# Cucumber Before hooks are executed in the order they are listed, and -# we want this hook to always run first, so it must always be the -# *first* Before hook matching @product listed in this file. -Before('@product') do |scenario| - $failure_artifacts = Array.new - if $config["CAPTURE"] - video_name = sanitize_filename("#{scenario.name}.mkv") - @video_path = "#{ARTIFACTS_DIR}/#{video_name}" - capture = IO.popen(['avconv', - '-f', 'x11grab', - '-s', '1024x768', - '-r', '15', - '-i', "#{$config['DISPLAY']}.0", - '-an', - '-c:v', 'libx264', - '-y', - @video_path, - :err => ['/dev/null', 'w'], - ]) - @video_capture_pid = capture.pid - end - @screen = Sikuli::Screen.new - # English will be assumed if this is not overridden - @language = "" - @os_loader = "MBR" - @sudo_password = "asdf" - @persistence_password = "asdf" -end - -# Cucumber After hooks are executed in the *reverse* order they are -# listed, and we want this hook to always run second last, so it must always -# be the *second* After hook matching @product listed in this file -- -# hooks added dynamically via add_after_scenario_hook() are supposed to -# truly be last. -After('@product') do |scenario| - if @video_capture_pid - # We can be incredibly fast at detecting errors sometimes, so the - # screen barely "settles" when we end up here and kill the video - # capture. Let's wait a few seconds more to make it easier to see - # what the error was. - sleep 3 if scenario.failed? - Process.kill("INT", @video_capture_pid) - save_failure_artifact("Video", @video_path) - end - if scenario.failed? - time_of_fail = Time.now - TIME_AT_START - secs = "%02d" % (time_of_fail % 60) - mins = "%02d" % ((time_of_fail / 60) % 60) - hrs = "%02d" % (time_of_fail / (60*60)) - elapsed = "#{hrs}:#{mins}:#{secs}" - info_log("Scenario failed at time #{elapsed}") - screen_capture = @screen.capture - save_failure_artifact("Screenshot", screen_capture.getFilename) - $failure_artifacts.sort! - $failure_artifacts.each do |type, file| - artifact_name = sanitize_filename("#{elapsed}_#{scenario.name}#{File.extname(file)}") - artifact_path = "#{ARTIFACTS_DIR}/#{artifact_name}" - assert(File.exist?(file)) - FileUtils.mv(file, artifact_path) - info_log - info_log_artifact_location(type, artifact_path) - end - pause("Scenario failed") if $config["PAUSE_ON_FAIL"] - else - if @video_path && File.exist?(@video_path) && not($config['CAPTURE_ALL']) - FileUtils.rm(@video_path) - end - end -end - -Before('@product', '@check_tor_leaks') do |scenario| - @tor_leaks_sniffer = Sniffer.new(sanitize_filename(scenario.name), $vmnet) - @tor_leaks_sniffer.capture - add_after_scenario_hook do - @tor_leaks_sniffer.clear - end -end - -After('@product', '@check_tor_leaks') do |scenario| - @tor_leaks_sniffer.stop - if scenario.passed? - if @bridge_hosts.nil? - expected_tor_nodes = get_all_tor_nodes - else - expected_tor_nodes = @bridge_hosts - end - leaks = FirewallLeakCheck.new(@tor_leaks_sniffer.pcap_file, - :accepted_hosts => expected_tor_nodes) - leaks.assert_no_leaks - end -end - -# For @source tests -################### - -# BeforeScenario -Before('@source') do - @orig_pwd = Dir.pwd - @git_clone = Dir.mktmpdir 'tails-apt-tests' - Dir.chdir @git_clone -end - -# AfterScenario -After('@source') do - Dir.chdir @orig_pwd - FileUtils.remove_entry_secure @git_clone -end diff --git a/features/time_syncing.feature b/features/time_syncing.feature deleted file mode 100644 index 69a0c9e2..00000000 --- a/features/time_syncing.feature +++ /dev/null @@ -1,129 +0,0 @@ -@product @check_tor_leaks -Feature: Time syncing - As a Tails user - I want Tor to work properly - And for that I need a reasonably accurate system clock - - #10497: wait_until_tor_is_working - @fragile - Scenario: Clock with host's time - Given I have started Tails from DVD without network and logged in - When the network is plugged - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - @fragile - Scenario: Clock with host's time in bridge mode - Given I have started Tails from DVD without network and logged in with bridge mode enabled - When the network is plugged - And the Tor Launcher autostarts - And I configure some Bridge pluggable transports in Tor Launcher - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - @fragile - Scenario: Clock is one day in the past - Given I have started Tails from DVD without network and logged in - When I bump the system time with "-1 day" - And the network is plugged - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - @fragile - Scenario: Clock is one day in the past in bridge mode - Given I have started Tails from DVD without network and logged in with bridge mode enabled - When I bump the system time with "-1 day" - And the network is plugged - And the Tor Launcher autostarts - And I configure some Bridge pluggable transports in Tor Launcher - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - @fragile - Scenario: Clock is way in the past - Given I have started Tails from DVD without network and logged in - # 13 weeks will span over two Tails release cycles. - When I bump the system time with "-13 weeks" - And the network is plugged - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - @fragile - Scenario: Clock way in the past in bridge mode - Given I have started Tails from DVD without network and logged in with bridge mode enabled - When I bump the system time with "-6 weeks" - And the network is plugged - And the Tor Launcher autostarts - And I configure some Bridge pluggable transports in Tor Launcher - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - #10440: Time syncing tests are fragile - @fragile - Scenario: Clock is one day in the future - Given I have started Tails from DVD without network and logged in - When I bump the system time with "+1 day" - And the network is plugged - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - @fragile - Scenario: Clock is one day in the future in bridge mode - Given I have started Tails from DVD without network and logged in with bridge mode enabled - When I bump the system time with "+1 day" - And the network is plugged - And the Tor Launcher autostarts - And I configure some Bridge pluggable transports in Tor Launcher - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - #10440: Time syncing tests are fragile - @fragile - Scenario: Clock way in the future - Given I have started Tails from DVD without network and logged in - When I set the system time to "01 Jan 2020 12:34:56" - And the network is plugged - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - - #10497: wait_until_tor_is_working - #10440: Time syncing tests are fragile - @fragile - Scenario: Clock way in the future in bridge mode - Given I have started Tails from DVD without network and logged in with bridge mode enabled - When I set the system time to "01 Jan 2020 12:34:56" - And the network is plugged - And the Tor Launcher autostarts - And I configure some Bridge pluggable transports in Tor Launcher - And Tor is ready - Then Tails clock is less than 5 minutes incorrect - -Scenario: The system time is not synced to the hardware clock - Given I have started Tails from DVD without network and logged in - When I bump the system time with "-15 days" - And I warm reboot the computer - And the computer reboots Tails - Then Tails' hardware clock is close to the host system's time - - Scenario: Anti-test: Changes to the hardware clock are kept when rebooting - Given I have started Tails from DVD without network and logged in - When I bump the hardware clock's time with "-15 days" - And I warm reboot the computer - And the computer reboots Tails - Then the hardware clock is still off by "-15 days" - - Scenario: Boot with a hardware clock set way in the past and make sure that Tails sets the clock to the build date - Given a computer - And the network is unplugged - And the hardware clock is set to "01 Jan 2000 12:34:56" - And I start the computer - And the computer boots Tails - Then the system clock is just past Tails' build date diff --git a/features/tor_bridges.feature b/features/tor_bridges.feature deleted file mode 100644 index b5277ca7..00000000 --- a/features/tor_bridges.feature +++ /dev/null @@ -1,36 +0,0 @@ -@product @fragile -Feature: Using Tails with Tor pluggable transports - As a Tails user - I want to circumvent censorship of Tor by using Tor pluggable transports - And avoid connecting directly to the Tor Network - - Background: - Given I have started Tails from DVD without network and logged in with bridge mode enabled - And I capture all network traffic - When the network is plugged - Then the Tor Launcher autostarts - And the Tor Launcher uses all expected TBB shared libraries - - Scenario: Using bridges - When I configure some Bridge pluggable transports in Tor Launcher - Then Tor is ready - And available upgrades have been checked - And all Internet traffic has only flowed through the configured pluggable transports - - Scenario: Using obfs2 pluggable transports - When I configure some obfs2 pluggable transports in Tor Launcher - Then Tor is ready - And available upgrades have been checked - And all Internet traffic has only flowed through the configured pluggable transports - - Scenario: Using obfs3 pluggable transports - When I configure some obfs3 pluggable transports in Tor Launcher - Then Tor is ready - And available upgrades have been checked - And all Internet traffic has only flowed through the configured pluggable transports - - Scenario: Using obfs4 pluggable transports - When I configure some obfs4 pluggable transports in Tor Launcher - Then Tor is ready - And available upgrades have been checked - And all Internet traffic has only flowed through the configured pluggable transports diff --git a/features/tor_enforcement.feature b/features/tor_enforcement.feature deleted file mode 100644 index 164220a2..00000000 --- a/features/tor_enforcement.feature +++ /dev/null @@ -1,76 +0,0 @@ -#10497: wait_until_tor_is_working -@product @fragile -Feature: The Tor enforcement is effective - As a Tails user - I want all direct Internet connections I do by mistake or applications do by misconfiguration or buggy leaks to be blocked - And as a Tails developer - I want to ensure that the automated test suite detects firewall leaks reliably - - Scenario: Tails' Tor binary is configured to use the expected Tor authorities - Given I have started Tails from DVD and logged in and the network is connected - Then the Tor binary is configured to use the expected Tor authorities - - Scenario: The firewall configuration is very restrictive - Given I have started Tails from DVD and logged in and the network is connected - Then the firewall's policy is to drop all IPv4 traffic - And the firewall is configured to only allow the clearnet and debian-tor users to connect directly to the Internet over IPv4 - And the firewall's NAT rules only redirect traffic for Tor's TransPort and DNSPort - And the firewall is configured to block all external IPv6 traffic - - @fragile - Scenario: Anti test: Detecting IPv4 TCP leaks from the Unsafe Browser with the firewall leak detector - Given I have started Tails from DVD and logged in and the network is connected - And I capture all network traffic - When I successfully start the Unsafe Browser - And I open the address "https://check.torproject.org" in the Unsafe Browser - And I see "UnsafeBrowserTorCheckFail.png" after at most 60 seconds - Then the firewall leak detector has detected IPv4 TCP leaks - - Scenario: Anti test: Detecting IPv4 TCP leaks of TCP DNS lookups with the firewall leak detector - Given I have started Tails from DVD and logged in and the network is connected - And I capture all network traffic - And I disable Tails' firewall - When I do a TCP DNS lookup of "torproject.org" - Then the firewall leak detector has detected IPv4 TCP leaks - - Scenario: Anti test: Detecting IPv4 non-TCP leaks (UDP) of UDP DNS lookups with the firewall leak detector - Given I have started Tails from DVD and logged in and the network is connected - And I capture all network traffic - And I disable Tails' firewall - When I do a UDP DNS lookup of "torproject.org" - Then the firewall leak detector has detected IPv4 non-TCP leaks - - Scenario: Anti test: Detecting IPv4 non-TCP (ICMP) leaks of ping with the firewall leak detector - Given I have started Tails from DVD and logged in and the network is connected - And I capture all network traffic - And I disable Tails' firewall - When I send some ICMP pings - Then the firewall leak detector has detected IPv4 non-TCP leaks - - @check_tor_leaks - Scenario: The Tor enforcement is effective at blocking untorified TCP connection attempts - Given I have started Tails from DVD and logged in and the network is connected - When I open an untorified TCP connections to 1.2.3.4 on port 42 that is expected to fail - Then the untorified connection fails - And the untorified connection is logged as dropped by the firewall - - @check_tor_leaks - Scenario: The Tor enforcement is effective at blocking untorified UDP connection attempts - Given I have started Tails from DVD and logged in and the network is connected - When I open an untorified UDP connections to 1.2.3.4 on port 42 that is expected to fail - Then the untorified connection fails - And the untorified connection is logged as dropped by the firewall - - @check_tor_leaks @fragile - Scenario: The Tor enforcement is effective at blocking untorified ICMP connection attempts - Given I have started Tails from DVD and logged in and the network is connected - When I open an untorified ICMP connections to 1.2.3.4 that is expected to fail - Then the untorified connection fails - And the untorified connection is logged as dropped by the firewall - - Scenario: The system DNS is always set up to use Tor's DNSPort - Given I have started Tails from DVD without network and logged in - And the system DNS is using the local DNS resolver - And the network is plugged - And Tor is ready - Then the system DNS is still using the local DNS resolver diff --git a/features/tor_stream_isolation.feature b/features/tor_stream_isolation.feature deleted file mode 100644 index c51c6410..00000000 --- a/features/tor_stream_isolation.feature +++ /dev/null @@ -1,62 +0,0 @@ -#10497: wait_until_tor_is_working -@product @check_tor_leaks @fragile -Feature: Tor stream isolation is effective - As a Tails user - I want my Torified sessions to be sensibly isolated from each other to prevent identity correlation - - Background: - Given I have started Tails from DVD and logged in and the network is connected - - Scenario: tails-security-check is using the Tails-specific SocksPort - When I monitor the network connections of tails-security-check - And I re-run tails-security-check - Then I see that tails-security-check is properly stream isolated - - Scenario: htpdate is using the Tails-specific SocksPort - When I monitor the network connections of htpdate - And I re-run htpdate - Then I see that htpdate is properly stream isolated - - Scenario: tails-upgrade-frontend-wrapper is using the Tails-specific SocksPort - When I monitor the network connections of tails-upgrade-frontend-wrapper - And I re-run tails-upgrade-frontend-wrapper - Then I see that tails-upgrade-frontend-wrapper is properly stream isolated - - Scenario: The Tor Browser is using the web browser-specific SocksPort - When I monitor the network connections of Tor Browser - And I start the Tor Browser - And the Tor Browser has started and loaded the startup page - Then I see that Tor Browser is properly stream isolated - - @fragile - Scenario: Gobby is using the default SocksPort - When I monitor the network connections of Gobby - And I start "Gobby" via the GNOME "Internet" applications menu - And I connect Gobby to "gobby.debian.org" - Then I see that Gobby is properly stream isolated - - Scenario: SSH is using the default SocksPort - When I monitor the network connections of SSH - And I run "ssh lizard.tails.boum.org" in GNOME Terminal - And I see "SSHAuthVerification.png" after at most 60 seconds - Then I see that SSH is properly stream isolated - - Scenario: whois lookups use the default SocksPort - When I monitor the network connections of whois - And I query the whois directory service for "boum.org" - And the whois command is successful - Then I see that whois is properly stream isolated - - @fragile - Scenario: Explicitly torify-wrapped applications are using the default SocksPort - When I monitor the network connections of Gobby - And I run "torify /usr/bin/gobby-0.5" in GNOME Terminal - And I connect Gobby to "gobby.debian.org" - Then I see that Gobby is properly stream isolated - - @fragile - Scenario: Explicitly torsocks-wrapped applications are using the default SocksPort - When I monitor the network connections of Gobby - And I run "torsocks /usr/bin/gobby-0.5" in GNOME Terminal - And I connect Gobby to "gobby.debian.org" - Then I see that Gobby is properly stream isolated diff --git a/features/torified_browsing.feature b/features/torified_browsing.feature deleted file mode 100644 index 78a40135..00000000 --- a/features/torified_browsing.feature +++ /dev/null @@ -1,172 +0,0 @@ -#10376: The "the Tor Browser loads the (startup page|Tails roadmap)" step is fragile -#10497: wait_until_tor_is_working -@product @fragile -Feature: Browsing the web using the Tor Browser - As a Tails user - when I browse the web using the Tor Browser - all Internet traffic should flow only through Tor - - Scenario: The Tor Browser cannot access the LAN - Given I have started Tails from DVD and logged in and the network is connected - And a web server is running on the LAN - And I capture all network traffic - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - And I open a page on the LAN web server in the Tor Browser - Then I see "TorBrowserUnableToConnect.png" after at most 20 seconds - And no traffic has flowed to the LAN - - @check_tor_leaks - Scenario: The Tor Browser directory is usable - Given I have started Tails from DVD and logged in and the network is connected - Then the amnesiac Tor Browser directory exists - And there is a GNOME bookmark for the amnesiac Tor Browser directory - And the persistent Tor Browser directory does not exist - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - Then I can save the current page as "index.html" to the default downloads directory - And I can print the current page as "output.pdf" to the default downloads directory - - @check_tor_leaks @fragile - Scenario: Downloading files with the Tor Browser - Given I have started Tails from DVD and logged in and the network is connected - When I start the Tor Browser - Then the Tor Browser has started and loaded the startup page - When I download some file in the Tor Browser - Then I get the browser download dialog - When I save the file to the default Tor Browser download directory - Then the file is saved to the default Tor Browser download directory - - @check_tor_leaks @fragile - Scenario: Playing HTML5 audio - Given I have started Tails from DVD and logged in and the network is connected - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - And no application is playing audio - And I open the address "http://www.terrillthompson.com/tests/html5-audio.html" in the Tor Browser - And I click the HTML5 play button - And 1 application is playing audio after 10 seconds - - @check_tor_leaks @fragile - Scenario: Watching a WebM video - Given I have started Tails from DVD and logged in and the network is connected - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - And I open the address "https://webm.html5.org/test.webm" in the Tor Browser - And I click the blocked video icon - And I see "TorBrowserNoScriptTemporarilyAllowDialog.png" after at most 30 seconds - And I accept to temporarily allow playing this video - Then I see "TorBrowserSampleRemoteWebMVideoFrame.png" after at most 180 seconds - - Scenario: I can view a file stored in "~/Tor Browser" but not in ~/.gnupg - Given I have started Tails from DVD and logged in and the network is connected - And I copy "/usr/share/synaptic/html/index.html" to "/home/amnesia/Tor Browser/synaptic.html" as user "amnesia" - And I copy "/usr/share/synaptic/html/index.html" to "/home/amnesia/.gnupg/synaptic.html" as user "amnesia" - And I copy "/usr/share/synaptic/html/index.html" to "/tmp/synaptic.html" as user "amnesia" - Then the file "/home/amnesia/.gnupg/synaptic.html" exists - And the file "/lib/live/mount/overlay/home/amnesia/.gnupg/synaptic.html" exists - And the file "/live/overlay/home/amnesia/.gnupg/synaptic.html" exists - And the file "/tmp/synaptic.html" exists - Given I start monitoring the AppArmor log of "/usr/local/lib/tor-browser/firefox" - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - And I open the address "file:///home/amnesia/Tor Browser/synaptic.html" in the Tor Browser - Then I see "TorBrowserSynapticManual.png" after at most 5 seconds - And AppArmor has not denied "/usr/local/lib/tor-browser/firefox" from opening "/home/amnesia/Tor Browser/synaptic.html" - Given I restart monitoring the AppArmor log of "/usr/local/lib/tor-browser/firefox" - When I open the address "file:///home/amnesia/.gnupg/synaptic.html" in the Tor Browser - Then I do not see "TorBrowserSynapticManual.png" after at most 5 seconds - And AppArmor has denied "/usr/local/lib/tor-browser/firefox" from opening "/home/amnesia/.gnupg/synaptic.html" - Given I restart monitoring the AppArmor log of "/usr/local/lib/tor-browser/firefox" - When I open the address "file:///lib/live/mount/overlay/home/amnesia/.gnupg/synaptic.html" in the Tor Browser - Then I do not see "TorBrowserSynapticManual.png" after at most 5 seconds - And AppArmor has denied "/usr/local/lib/tor-browser/firefox" from opening "/lib/live/mount/overlay/home/amnesia/.gnupg/synaptic.html" - Given I restart monitoring the AppArmor log of "/usr/local/lib/tor-browser/firefox" - When I open the address "file:///live/overlay/home/amnesia/.gnupg/synaptic.html" in the Tor Browser - Then I do not see "TorBrowserSynapticManual.png" after at most 5 seconds - # Due to our AppArmor aliases, /live/overlay will be treated - # as /lib/live/mount/overlay. - And AppArmor has denied "/usr/local/lib/tor-browser/firefox" from opening "/lib/live/mount/overlay/home/amnesia/.gnupg/synaptic.html" - # We do not get any AppArmor log for when access to files in /tmp is denied - # since we explictly override (commit 51c0060) the rules (from the user-tmp - # abstration) that would otherwise allow it, and we do so with "deny", which - # also specifies "noaudit". We could explicitly specify "audit deny" and - # then have logs, but it could be a problem when we set up desktop - # notifications for AppArmor denials (#9337). - When I open the address "file:///tmp/synaptic.html" in the Tor Browser - Then I do not see "TorBrowserSynapticManual.png" after at most 5 seconds - - @doc - Scenario: The "Tails documentation" link on the Desktop works - Given I have started Tails from DVD and logged in and the network is connected - When I double-click on the "Tails documentation" link on the Desktop - Then the Tor Browser has started - And I see "TailsOfflineDocHomepage.png" after at most 10 seconds - - Scenario: The Tor Browser uses TBB's shared libraries - Given I have started Tails from DVD and logged in and the network is connected - When I start the Tor Browser - And the Tor Browser has started - Then the Tor Browser uses all expected TBB shared libraries - - @check_tor_leaks @fragile - Scenario: Opening check.torproject.org in the Tor Browser shows the green onion and the congratulations message - Given I have started Tails from DVD and logged in and the network is connected - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - And I open the address "https://check.torproject.org" in the Tor Browser - Then I see "TorBrowserTorCheck.png" after at most 180 seconds - - @check_tor_leaks @fragile - Scenario: The Tor Browser's "New identity" feature works as expected - Given I have started Tails from DVD and logged in and the network is connected - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - And I open the address "https://check.torproject.org" in the Tor Browser - Then I see "TorBrowserTorCheck.png" after at most 180 seconds - When I request a new identity using Torbutton - And I acknowledge Torbutton's New Identity confirmation prompt - Then the Tor Browser loads the startup page - - Scenario: The Tor Browser should not have any plugins enabled - Given I have started Tails from DVD and logged in and the network is connected - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - Then the Tor Browser has no plugins installed - - #10497, #10720 - @fragile - Scenario: The persistent Tor Browser directory is usable - Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in - And the network is plugged - And Tor is ready - And available upgrades have been checked - And all notifications have disappeared - Then the persistent Tor Browser directory exists - And there is a GNOME bookmark for the persistent Tor Browser directory - When I start the Tor Browser - And the Tor Browser has started and loaded the startup page - And I can save the current page as "index.html" to the persistent Tor Browser directory - When I open the address "file:///home/amnesia/Persistent/Tor Browser/index.html" in the Tor Browser - Then I see "TorBrowserSavedStartupPage.png" after at most 10 seconds - And I can print the current page as "output.pdf" to the persistent Tor Browser directory - - #10720 - @fragile - Scenario: Persistent browser bookmarks - Given I have started Tails without network from a USB drive with a persistent partition enabled and logged in - And all persistence presets are enabled - And all persistent filesystems have safe access rights - And all persistence configuration files have safe access rights - And all persistent directories have safe access rights - And I start the Tor Browser in offline mode - And the Tor Browser has started in offline mode - And I add a bookmark to eff.org in the Tor Browser - And I warm reboot the computer - And the computer reboots Tails - And I enable read-only persistence - And I log in to a new session - And the Tails desktop is ready - And I start the Tor Browser in offline mode - And the Tor Browser has started in offline mode - Then the Tor Browser has a bookmark to eff.org diff --git a/features/torified_git.feature b/features/torified_git.feature deleted file mode 100644 index 04e19a53..00000000 --- a/features/torified_git.feature +++ /dev/null @@ -1,31 +0,0 @@ -#10497: wait_until_tor_is_working -#10444: Git tests are fragile -@product @check_tor_leaks @fragile -Feature: Cloning a Git repository - As a Tails user - when I clone a Git repository - all Internet traffic should flow only through Tor - - Background: - Given I have started Tails from DVD and logged in and the network is connected - - @fragile - Scenario: Cloning a Git repository anonymously over HTTPS - When I run "git clone https://git-tails.immerda.ch/myprivatekeyispublic/testing" in GNOME Terminal - Then process "git" is running within 10 seconds - And process "git" has stopped running after at most 180 seconds - And the Git repository "testing" has been cloned successfully - - Scenario: Cloning a Git repository anonymously over the Git protocol - When I run "git clone git://git.tails.boum.org/myprivatekeyispublic/testing" in GNOME Terminal - Then process "git" is running within 10 seconds - And process "git" has stopped running after at most 180 seconds - And the Git repository "testing" has been cloned successfully - - Scenario: Cloning git repository over SSH - Given I have the SSH key pair for a Git repository - When I run "git clone tails@git.tails.boum.org:myprivatekeyispublic/testing" in GNOME Terminal - Then process "git" is running within 10 seconds - When I verify the SSH fingerprint for the Git repository - And process "git" has stopped running after at most 180 seconds - Then the Git repository "testing" has been cloned successfully diff --git a/features/torified_gnupg.feature b/features/torified_gnupg.feature deleted file mode 100644 index 374c7ba3..00000000 --- a/features/torified_gnupg.feature +++ /dev/null @@ -1,53 +0,0 @@ -@product @check_tor_leaks @fragile -Feature: Keyserver interaction with GnuPG - As a Tails user - when I interact with keyservers using various GnuPG tools - the configured keyserver must be used - and all Internet traffic should flow only through Tor. - - Background: - Given I have started Tails from DVD and logged in and the network is connected - And the "10CC5BC7" OpenPGP key is not in the live user's public keyring - - Scenario: Seahorse is configured to use the correct keyserver - Then Seahorse is configured to use the correct keyserver - - Scenario: Fetching OpenPGP keys using GnuPG should work and be done over Tor. - When I fetch the "10CC5BC7" OpenPGP key using the GnuPG CLI - Then GnuPG uses the configured keyserver - And the GnuPG fetch is successful - And the "10CC5BC7" key is in the live user's public keyring - - Scenario: Fetching OpenPGP keys using Seahorse should work and be done over Tor. - When I fetch the "10CC5BC7" OpenPGP key using Seahorse - And the Seahorse operation is successful - Then the "10CC5BC7" key is in the live user's public keyring - - Scenario: Fetching OpenPGP keys using Seahorse via the Tails OpenPGP Applet should work and be done over Tor. - When I fetch the "10CC5BC7" OpenPGP key using Seahorse via the Tails OpenPGP Applet - And the Seahorse operation is successful - Then the "10CC5BC7" key is in the live user's public keyring - - Scenario: Syncing OpenPGP keys using Seahorse should work and be done over Tor. - Given I fetch the "10CC5BC7" OpenPGP key using the GnuPG CLI without any signatures - And the GnuPG fetch is successful - And the "10CC5BC7" key is in the live user's public keyring - But the key "10CC5BC7" has only 2 signatures - When I start Seahorse - Then Seahorse has opened - And I enable key synchronization in Seahorse - And I synchronize keys in Seahorse - And the Seahorse operation is successful - Then the key "10CC5BC7" has more than 2 signatures - - Scenario: Syncing OpenPGP keys using Seahorse started from the Tails OpenPGP Applet should work and be done over Tor. - Given I fetch the "10CC5BC7" OpenPGP key using the GnuPG CLI without any signatures - And the GnuPG fetch is successful - And the "10CC5BC7" key is in the live user's public keyring - But the key "10CC5BC7" has only 2 signatures - When I start Seahorse via the Tails OpenPGP Applet - Then Seahorse has opened - And I enable key synchronization in Seahorse - And I synchronize keys in Seahorse - And the Seahorse operation is successful - Then the key "10CC5BC7" has more than 2 signatures diff --git a/features/torified_misc.feature b/features/torified_misc.feature deleted file mode 100644 index 75f3fd0b..00000000 --- a/features/torified_misc.feature +++ /dev/null @@ -1,24 +0,0 @@ -@product @check_tor_leaks @fragile -Feature: Various checks for torified software - - Background: - Given I have started Tails from DVD and logged in and the network is connected - - Scenario: wget(1) should work for HTTP and go through Tor. - When I wget "http://example.com/" to stdout - Then the wget command is successful - And the wget standard output contains "Example Domain" - - Scenario: wget(1) should work for HTTPS and go through Tor. - When I wget "https://example.com/" to stdout - Then the wget command is successful - And the wget standard output contains "Example Domain" - - Scenario: wget(1) with tricky options should work for HTTP and go through Tor. - When I wget "http://195.154.14.189/tails/stable/" to stdout with the '--spider --header="Host: dl.amnesia.boum.org"' options - Then the wget command is successful - - Scenario: whois(1) should work and go through Tor. - When I query the whois directory service for "torproject.org" - Then the whois command is successful - Then the whois standard output contains "The Tor Project" diff --git a/features/totem.feature b/features/totem.feature deleted file mode 100644 index 0e6fa05d..00000000 --- a/features/totem.feature +++ /dev/null @@ -1,70 +0,0 @@ -@product -Feature: Using Totem - As a Tails user - I want to watch local and remote videos in Totem - And AppArmor should prevent Totem from doing dangerous things - And all Internet traffic should flow only through Tor - - Background: - Given I create sample videos - - Scenario: Watching a MP4 video stored on the non-persistent filesystem - Given a computer - And I setup a filesystem share containing sample videos - And I start Tails from DVD with network unplugged and I login - And I copy the sample videos to "/home/amnesia" as user "amnesia" - And the file "/home/amnesia/video.mp4" exists - Given I start monitoring the AppArmor log of "/usr/bin/totem" - When I open "/home/amnesia/video.mp4" with Totem - Then I see "SampleLocalMp4VideoFrame.png" after at most 20 seconds - And AppArmor has not denied "/usr/bin/totem" from opening "/home/amnesia/video.mp4" - Given I close Totem - And I copy the sample videos to "/home/amnesia/.gnupg" as user "amnesia" - And the file "/home/amnesia/.gnupg/video.mp4" exists - And I restart monitoring the AppArmor log of "/usr/bin/totem" - When I try to open "/home/amnesia/.gnupg/video.mp4" with Totem - Then I see "TotemUnableToOpen.png" after at most 10 seconds - And AppArmor has denied "/usr/bin/totem" from opening "/home/amnesia/.gnupg/video.mp4" - Given I close Totem - And the file "/lib/live/mount/overlay/home/amnesia/.gnupg/video.mp4" exists - And I restart monitoring the AppArmor log of "/usr/bin/totem" - When I try to open "/lib/live/mount/overlay/home/amnesia/.gnupg/video.mp4" with Totem - Then I see "TotemUnableToOpen.png" after at most 10 seconds - And AppArmor has denied "/usr/bin/totem" from opening "/lib/live/mount/overlay/home/amnesia/.gnupg/video.mp4" - Given I close Totem - And the file "/live/overlay/home/amnesia/.gnupg/video.mp4" exists - And I restart monitoring the AppArmor log of "/usr/bin/totem" - When I try to open "/live/overlay/home/amnesia/.gnupg/video.mp4" with Totem - Then I see "TotemUnableToOpen.png" after at most 10 seconds - # Due to our AppArmor aliases, /live/overlay will be treated - # as /lib/live/mount/overlay. - And AppArmor has denied "/usr/bin/totem" from opening "/lib/live/mount/overlay/home/amnesia/.gnupg/video.mp4" - - #10497: wait_until_tor_is_working - @check_tor_leaks @fragile - Scenario: Watching a WebM video over HTTPS - Given I have started Tails from DVD and logged in and the network is connected - Then I can watch a WebM video over HTTPs - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: Watching MP4 videos stored on the persistent volume should work as expected given our AppArmor confinement - Given I have started Tails without network from a USB drive with a persistent partition and stopped at Tails Greeter's login screen - # Due to bug #5571 we have to reboot to be able to use - # filesystem shares. - And I shutdown Tails and wait for the computer to power off - And I setup a filesystem share containing sample videos - And I start Tails from USB drive "__internal" with network unplugged and I login with persistence enabled - And I copy the sample videos to "/home/amnesia/Persistent" as user "amnesia" - And I copy the sample videos to "/home/amnesia/.gnupg" as user "amnesia" - And I shutdown Tails and wait for the computer to power off - And I start Tails from USB drive "__internal" with network unplugged and I login with persistence enabled - And the file "/home/amnesia/Persistent/video.mp4" exists - When I open "/home/amnesia/Persistent/video.mp4" with Totem - Then I see "SampleLocalMp4VideoFrame.png" after at most 10 seconds - Given I close Totem - And the file "/home/amnesia/.gnupg/video.mp4" exists - And I start monitoring the AppArmor log of "/usr/bin/totem" - When I try to open "/home/amnesia/.gnupg/video.mp4" with Totem - Then I see "TotemUnableToOpen.png" after at most 10 seconds - And AppArmor has denied "/usr/bin/totem" from opening "/home/amnesia/.gnupg/video.mp4" diff --git a/features/unsafe_browser.feature b/features/unsafe_browser.feature deleted file mode 100644 index d47f770c..00000000 --- a/features/unsafe_browser.feature +++ /dev/null @@ -1,73 +0,0 @@ -@product -Feature: Browsing the web using the Unsafe Browser - As a Tails user - when I browse the web using the Unsafe Browser - I should have direct access to the web - - @fragile - Scenario: The Unsafe Browser can access the LAN - Given I have started Tails from DVD and logged in and the network is connected - And a web server is running on the LAN - When I successfully start the Unsafe Browser - And I open a page on the LAN web server in the Unsafe Browser - Then I see "UnsafeBrowserHelloLANWebServer.png" after at most 20 seconds - - @fragile - Scenario: Starting the Unsafe Browser works as it should. - Given I have started Tails from DVD and logged in and the network is connected - When I successfully start the Unsafe Browser - Then the Unsafe Browser runs as the expected user - And the Unsafe Browser has a red theme - And the Unsafe Browser shows a warning as its start page - And the Unsafe Browser has no plugins installed - And the Unsafe Browser has no add-ons installed - And the Unsafe Browser has only Firefox's default bookmarks configured - And the Unsafe Browser has no proxy configured - And the Unsafe Browser uses all expected TBB shared libraries - - @fragile - Scenario: Closing the Unsafe Browser shows a stop notification and properly tears down the chroot. - Given I have started Tails from DVD and logged in and the network is connected - When I successfully start the Unsafe Browser - And I close the Unsafe Browser - Then I see the Unsafe Browser stop notification - And the Unsafe Browser chroot is torn down - - @fragile - Scenario: Starting a second instance of the Unsafe Browser results in an error message being shown. - Given I have started Tails from DVD and logged in and the network is connected - When I successfully start the Unsafe Browser - And I start the Unsafe Browser - Then I see a warning about another instance already running - - @fragile - Scenario: Opening check.torproject.org in the Unsafe Browser shows the red onion and a warning message. - Given I have started Tails from DVD and logged in and the network is connected - When I successfully start the Unsafe Browser - And I open the address "https://check.torproject.org" in the Unsafe Browser - Then I see "UnsafeBrowserTorCheckFail.png" after at most 60 seconds - And the clearnet user has sent packets out to the Internet - - @fragile - Scenario: The Unsafe Browser cannot be configured to use Tor and other local proxies. - Given I have started Tails from DVD and logged in and the network is connected - When I successfully start the Unsafe Browser - Then I cannot configure the Unsafe Browser to use any local proxies - - @fragile - Scenario: The Unsafe Browser will not make any connections to the Internet which are not user initiated - Given I have started Tails from DVD and logged in and the network is connected - And I capture all network traffic - And Tor is ready - And I configure the Unsafe Browser to check for updates more frequently - But checking for updates is disabled in the Unsafe Browser's configuration - When I successfully start the Unsafe Browser - Then the Unsafe Browser has started - And I wait 120 seconds - And the clearnet user has not sent packets out to the Internet - And all Internet traffic has only flowed through Tor - - Scenario: Starting the Unsafe Browser without a network connection results in a complaint about no DNS server being configured - Given I have started Tails from DVD without network and logged in - When I start the Unsafe Browser - Then the Unsafe Browser complains that no DNS server is configured diff --git a/features/untrusted_partitions.feature b/features/untrusted_partitions.feature deleted file mode 100644 index 55490136..00000000 --- a/features/untrusted_partitions.feature +++ /dev/null @@ -1,80 +0,0 @@ -@product -Feature: Untrusted partitions - As a Tails user - I don't want to touch other media than the one Tails runs from - - Scenario: Tails will not enable disk swap - Given a computer - And I temporarily create a 100 MiB disk named "swap" - And I create a gpt swap partition on disk "swap" - And I plug ide drive "swap" - When I start Tails with network unplugged and I login - Then a "swap" partition was detected by Tails on drive "swap" - But Tails has no disk swap enabled - - Scenario: Tails will detect LUKS-encrypted GPT partitions labeled "TailsData" stored on USB drives as persistence volumes when the removable flag is set - Given a computer - And I temporarily create a 100 MiB disk named "fake_TailsData" - And I create a gpt partition labeled "TailsData" with an ext4 filesystem encrypted with password "asdf" on disk "fake_TailsData" - And I plug removable usb drive "fake_TailsData" - When I start the computer - And the computer boots Tails - Then drive "fake_TailsData" is detected by Tails - And Tails Greeter has detected a persistence partition - - Scenario: Tails will not detect LUKS-encrypted GPT partitions labeled "TailsData" stored on USB drives as persistence volumes when the removable flag is unset - Given a computer - And I temporarily create a 100 MiB disk named "fake_TailsData" - And I create a gpt partition labeled "TailsData" with an ext4 filesystem encrypted with password "asdf" on disk "fake_TailsData" - And I plug non-removable usb drive "fake_TailsData" - When I start the computer - And the computer boots Tails - Then drive "fake_TailsData" is detected by Tails - And Tails Greeter has not detected a persistence partition - - Scenario: Tails will not detect LUKS-encrypted GPT partitions labeled "TailsData" stored on local hard drives as persistence volumes - Given a computer - And I temporarily create a 100 MiB disk named "fake_TailsData" - And I create a gpt partition labeled "TailsData" with an ext4 filesystem encrypted with password "asdf" on disk "fake_TailsData" - And I plug ide drive "fake_TailsData" - When I start the computer - And the computer boots Tails - Then drive "fake_TailsData" is detected by Tails - And Tails Greeter has not detected a persistence partition - - Scenario: Tails can boot from live systems stored on hard drives - Given a computer - And I temporarily create a 2 GiB disk named "live_hd" - And I cat an ISO of the Tails image to disk "live_hd" - And the computer is set to boot from ide drive "live_hd" - And I set Tails to boot with options "live-media=" - When I start Tails with network unplugged and I login - Then Tails is running from ide drive "live_hd" - And Tails seems to have booted normally - - Scenario: Tails booting from a DVD does not use live systems stored on hard drives - Given a computer - And I temporarily create a 2 GiB disk named "live_hd" - And I cat an ISO of the Tails image to disk "live_hd" - And I plug ide drive "live_hd" - And I start Tails from DVD with network unplugged and I login - Then drive "live_hd" is detected by Tails - And drive "live_hd" is not mounted - - Scenario: Booting Tails does not automount untrusted ext2 partitions - Given a computer - And I temporarily create a 100 MiB disk named "gpt_ext2" - And I create a gpt partition with an ext2 filesystem on disk "gpt_ext2" - And I plug ide drive "gpt_ext2" - And I start Tails from DVD with network unplugged and I login - Then drive "gpt_ext2" is detected by Tails - And drive "gpt_ext2" is not mounted - - Scenario: Booting Tails does not automount untrusted fat32 partitions - Given a computer - And I temporarily create a 100 MiB disk named "msdos_fat32" - And I create an msdos partition with a vfat filesystem on disk "msdos_fat32" - And I plug ide drive "msdos_fat32" - And I start Tails from DVD with network unplugged and I login - Then drive "msdos_fat32" is detected by Tails - And drive "msdos_fat32" is not mounted diff --git a/features/usb_install.feature b/features/usb_install.feature deleted file mode 100644 index 750df7ab..00000000 --- a/features/usb_install.feature +++ /dev/null @@ -1,107 +0,0 @@ -@product -Feature: Installing Tails to a USB drive - As a Tails user - I want to install Tails to a suitable USB drive - - Scenario: Try installing Tails to a too small USB drive - Given I have started Tails from DVD without network and logged in - And I temporarily create a 2 GiB disk named "too-small-device" - And I start Tails Installer in "Clone & Install" mode - But a suitable USB device is not found - When I plug USB drive "too-small-device" - Then Tails Installer detects that a device is too small - And a suitable USB device is not found - When I unplug USB drive "too-small-device" - And I create a 4 GiB disk named "big-enough" - And I plug USB drive "big-enough" - Then the "big-enough" USB drive is selected - - Scenario: Detecting when a target USB drive is inserted or removed - Given I have started Tails from DVD without network and logged in - And I temporarily create a 4 GiB disk named "temp" - And I start Tails Installer in "Clone & Install" mode - But a suitable USB device is not found - When I plug USB drive "temp" - Then the "temp" USB drive is selected - When I unplug USB drive "temp" - Then no USB drive is selected - And a suitable USB device is not found - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: Installing Tails to a pristine USB drive - Given I have started Tails from DVD without network and logged in - And I temporarily create a 4 GiB disk named "install" - And I plug USB drive "install" - And I "Clone & Install" Tails to USB drive "install" - Then the running Tails is installed on USB drive "install" - But there is no persistence partition on USB drive "install" - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: Booting Tails from a USB drive without a persistent partition and creating one - Given I have started Tails without network from a USB drive without a persistent partition and stopped at Tails Greeter's login screen - And I log in to a new session - Then Tails seems to have booted normally - When I create a persistent partition - Then a Tails persistence partition exists on USB drive "__internal" - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: Booting Tails from a USB drive without a persistent partition - Given I have started Tails without network from a USB drive without a persistent partition and stopped at Tails Greeter's login screen - When I log in to a new session - Then Tails seems to have booted normally - And Tails is running from USB drive "__internal" - And the persistent Tor Browser directory does not exist - And there is no persistence partition on USB drive "__internal" - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: Booting Tails from a USB drive in UEFI mode - Given I have started Tails without network from a USB drive without a persistent partition and stopped at Tails Greeter's login screen - Then I power off the computer - Given the computer is set to boot in UEFI mode - When I start Tails from USB drive "__internal" with network unplugged and I login - Then the boot device has safe access rights - And Tails is running from USB drive "__internal" - And the boot device has safe access rights - And Tails has started in UEFI mode - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: Installing Tails to a USB drive with an MBR partition table but no partitions, and making sure that it boots - Given I have started Tails from DVD without network and logged in - And I temporarily create a 4 GiB disk named "mbr" - And I create a msdos label on disk "mbr" - And I plug USB drive "mbr" - And I "Clone & Install" Tails to USB drive "mbr" - Then the running Tails is installed on USB drive "mbr" - But there is no persistence partition on USB drive "mbr" - When I shutdown Tails and wait for the computer to power off - And I start Tails from USB drive "mbr" with network unplugged and I login - Then Tails is running from USB drive "mbr" - And the boot device has safe access rights - And there is no persistence partition on USB drive "mbr" - - #10720: Tails Installer freezes on Jenkins - @fragile - Scenario: Cat:ing a Tails isohybrid to a USB drive and booting it, then trying to upgrading it but ending up having to do a fresh installation, which boots - Given a computer - And I temporarily create a 4 GiB disk named "isohybrid" - And I cat an ISO of the Tails image to disk "isohybrid" - And I start Tails from USB drive "isohybrid" with network unplugged and I login - Then Tails is running from USB drive "isohybrid" - When I shutdown Tails and wait for the computer to power off - And I start Tails from DVD with network unplugged and I login - And I try a "Clone & Upgrade" Tails to USB drive "isohybrid" - Then I am suggested to do a "Clone & Install" - When I kill the process "tails-installer" - And I "Clone & Install" Tails to USB drive "isohybrid" - Then the running Tails is installed on USB drive "isohybrid" - But there is no persistence partition on USB drive "isohybrid" - When I shutdown Tails and wait for the computer to power off - And I start Tails from USB drive "isohybrid" with network unplugged and I login - Then Tails is running from USB drive "isohybrid" - And the boot device has safe access rights - And there is no persistence partition on USB drive "isohybrid" diff --git a/features/usb_upgrade.feature b/features/usb_upgrade.feature deleted file mode 100644 index 7462489a..00000000 --- a/features/usb_upgrade.feature +++ /dev/null @@ -1,164 +0,0 @@ -#10720: Tails Installer freezes on Jenkins -@product @fragile -Feature: Upgrading an old Tails USB installation - As a Tails user - If I have an old versoin of Tails installed on a USB device - and the USB device has a persistent partition - I want to upgrade Tails on it - and keep my persistent partition in the process - - # An issue with this feature is that scenarios depend on each - # other. When editing this feature, make sure you understand these - # dependencies (which are documented below). - - Scenario: Try to "Upgrade from ISO" Tails to a pristine USB drive - Given a computer - And I setup a filesystem share containing the Tails ISO - And I start Tails from DVD with network unplugged and I login - And I temporarily create a 4 GiB disk named "pristine" - And I plug USB drive "pristine" - And I start Tails Installer in "Upgrade from ISO" mode - Then a suitable USB device is not found - And I am told that the destination device cannot be upgraded - - Scenario: Try to "Clone & Upgrade" Tails to a pristine USB drive - Given I have started Tails from DVD without network and logged in - And I temporarily create a 4 GiB disk named "pristine" - And I plug USB drive "pristine" - And I start Tails Installer in "Clone & Upgrade" mode - Then a suitable USB device is not found - And I am told that the destination device cannot be upgraded - - Scenario: Try to "Upgrade from ISO" Tails to a USB drive with GPT and a FAT partition - Given a computer - And I setup a filesystem share containing the Tails ISO - And I start Tails from DVD with network unplugged and I login - And I temporarily create a 4 GiB disk named "gptfat" - And I create a gpt partition with a vfat filesystem on disk "gptfat" - And I plug USB drive "gptfat" - And I start Tails Installer in "Upgrade from ISO" mode - Then a suitable USB device is not found - And I am told that the destination device cannot be upgraded - - Scenario: Try to "Clone & Upgrade" Tails to a USB drive with GPT and a FAT partition - Given I have started Tails from DVD without network and logged in - And I temporarily create a 4 GiB disk named "gptfat" - And I create a gpt partition with a vfat filesystem on disk "gptfat" - And I plug USB drive "gptfat" - And I start Tails Installer in "Upgrade from ISO" mode - Then a suitable USB device is not found - And I am told that the destination device cannot be upgraded - - Scenario: Installing an old version of Tails to a pristine USB drive - Given a computer - And the computer is set to boot from the old Tails DVD - And the network is unplugged - And I start the computer - When the computer boots Tails - And I log in to a new session - And the Tails desktop is ready - And all notifications have disappeared - And I create a 4 GiB disk named "old" - And I plug USB drive "old" - And I "Clone & Install" Tails to USB drive "old" - Then the running Tails is installed on USB drive "old" - But there is no persistence partition on USB drive "old" - And I unplug USB drive "old" - - # Depends on scenario: Installing an old version of Tails to a pristine USB drive - Scenario: Creating a persistent partition with the old Tails USB installation - Given a computer - And I start Tails from USB drive "old" with network unplugged and I login - Then Tails is running from USB drive "old" - And I create a persistent partition - And I take note of which persistence presets are available - Then a Tails persistence partition exists on USB drive "old" - And I shutdown Tails and wait for the computer to power off - - # Depends on scenario: Creating a persistent partition with the old Tails USB installation - Scenario: Writing files to a read/write-enabled persistent partition with the old Tails USB installation - Given a computer - And I start Tails from USB drive "old" with network unplugged and I login with persistence enabled - Then Tails is running from USB drive "old" - And all persistence presets are enabled - And I write some files expected to persist - And all persistent filesystems have safe access rights - And all persistence configuration files have safe access rights - And all persistent directories from the old Tails version have safe access rights - And I take note of which persistence presets are available - And I shutdown Tails and wait for the computer to power off - # XXX: how does guestfs work vs snapshots? - Then only the expected files are present on the persistence partition on USB drive "old" - - # Depends on scenario: Writing files to a read/write-enabled persistent partition with the old Tails USB installation - Scenario: Upgrading an old Tails USB installation from a Tails DVD - Given I have started Tails from DVD without network and logged in - And I clone USB drive "old" to a new USB drive "to_upgrade" - And I plug USB drive "to_upgrade" - When I "Clone & Upgrade" Tails to USB drive "to_upgrade" - Then the running Tails is installed on USB drive "to_upgrade" - And I unplug USB drive "to_upgrade" - - # Depends on scenario: Upgrading an old Tails USB installation from a Tails DVD - Scenario: Booting Tails from a USB drive upgraded from DVD with persistence enabled - Given a computer - And I start Tails from USB drive "to_upgrade" with network unplugged and I login with persistence enabled - Then all persistence presets from the old Tails version are enabled - And Tails is running from USB drive "to_upgrade" - And the boot device has safe access rights - And the expected persistent files created with the old Tails version are present in the filesystem - And all persistent directories from the old Tails version have safe access rights - - # Depends on scenario: Writing files to a read/write-enabled persistent partition with the old Tails USB installation - Scenario: Upgrading an old Tails USB installation from another Tails USB drive - Given I have started Tails without network from a USB drive without a persistent partition and stopped at Tails Greeter's login screen - And I log in to a new session - And Tails seems to have booted normally - And I clone USB drive "old" to a new USB drive "to_upgrade" - And I plug USB drive "to_upgrade" - When I "Clone & Upgrade" Tails to USB drive "to_upgrade" - Then the running Tails is installed on USB drive "to_upgrade" - And I unplug USB drive "to_upgrade" - And I unplug USB drive "__internal" - - # Depends on scenario: Upgrading an old Tails USB installation from another Tails USB drive - Scenario: Booting Tails from a USB drive upgraded from USB with persistence enabled - Given a computer - And I start Tails from USB drive "to_upgrade" with network unplugged and I login with persistence enabled - Then all persistence presets from the old Tails version are enabled - And Tails is running from USB drive "to_upgrade" - And the boot device has safe access rights - And the expected persistent files created with the old Tails version are present in the filesystem - And all persistent directories from the old Tails version have safe access rights - - # Depends on scenario: Writing files to a read/write-enabled persistent partition with the old Tails USB installation - Scenario: Upgrading an old Tails USB installation from an ISO image, running on the old version - Given a computer - And I clone USB drive "old" to a new USB drive "to_upgrade" - And I setup a filesystem share containing the Tails ISO - When I start Tails from USB drive "old" with network unplugged and I login - And I plug USB drive "to_upgrade" - And I do a "Upgrade from ISO" on USB drive "to_upgrade" - Then the ISO's Tails is installed on USB drive "to_upgrade" - And I unplug USB drive "to_upgrade" - - # Depends on scenario: Writing files to a read/write-enabled persistent partition with the old Tails USB installation - Scenario: Upgrading an old Tails USB installation from an ISO image, running on the new version - Given a computer - And I clone USB drive "old" to a new USB drive "to_upgrade" - And I setup a filesystem share containing the Tails ISO - And I start Tails from DVD with network unplugged and I login - And I plug USB drive "to_upgrade" - And I do a "Upgrade from ISO" on USB drive "to_upgrade" - Then the ISO's Tails is installed on USB drive "to_upgrade" - And I unplug USB drive "to_upgrade" - - # Depends on scenario: Upgrading an old Tails USB installation from an ISO image, running on the new version - Scenario: Booting a USB drive upgraded from ISO with persistence enabled - Given a computer - And I start Tails from USB drive "to_upgrade" with network unplugged and I login with persistence enabled - Then all persistence presets from the old Tails version are enabled - And Tails is running from USB drive "to_upgrade" - And the boot device has safe access rights - And the expected persistent files created with the old Tails version are present in the filesystem - And all persistent directories from the old Tails version have safe access rights |