From 414389951e28d88e195aaf5e23316a39523b354a Mon Sep 17 00:00:00 2001
From: lars
Date: Mon, 6 Nov 2006 16:05:00 +0000
Subject: [PATCH] moved pythonrewrite branch to trunk
---
{bin => bin-perl-old}/Makefile | 0
{bin => bin-perl-old}/cbox-manage.sh | 0
{bin => bin-perl-old}/cbox-root-actions.sh | 0
{bin => bin-perl-old}/cryptobox.pl | 2 +-
{bin => bin-perl-old}/cryptobox_wrapper.c | 0
{bin => bin-perl-old}/ro-system.sh | 0
bin/CryptoBox.py | 276 +
bin/CryptoBoxContainer.py | 607 +
bin/CryptoBoxExceptions.py | 107 +
bin/CryptoBoxPlugin.py | 165 +
bin/CryptoBoxRootActions.py | 386 +
bin/CryptoBoxSettings.py | 481 +
bin/CryptoBoxTools.py | 186 +
bin/CryptoBoxWebserver.py | 38 +
bin/Plugins.py | 67 +
bin/WebInterfaceDataset.py | 136 +
bin/WebInterfaceSites.py | 427 +
bin/WebInterfaceTestClass.py | 77 +
bin/coding_guidelines.txt | 18 +
bin/cryptobox.conf | 83 +
bin/cryptoboxd | 39 +
bin/cryptoboxwebserver.conf | 17 +
bin/do_unittests.sh | 22 +
bin/example-super.tab | 2 +
bin/test.complete.CryptoBox.py | 116 +
bin/uml-setup.sh | 23 +
bin/unittests.CryptoBox.py | 138 +
bin/unittests.CryptoBoxTools.py | 48 +
bin/unittests.Plugins.py | 33 +
bin/unittests.WebSites.py | 39 +
debian/README.Debian | 3 +-
debian/control | 3 +-
debian/rules | 7 +-
design/background_frame_corner.svg | 265 +
design/icon_background_active.svg | 92 +
design/icons/applications-system_tango.svg | 245 +
design/icons/computer_tango.svg | 738 +
design/icons/dialog-error_tango.svg | 316 +
design/icons/dialog-information_tango.svg | 1145 +
design/icons/dialog-warning_tango.svg | 290 +
design/icons/drive-cdrom_tango.svg | 444 +
design/icons/drive-harddisk_tango.svg | 469 +
design/icons/drive-removable-media_tango.svg | 390 +
design/icons/globe-lips.svg | 512 +
.../icons/gnome-dev-removable-usb_nuvola.svg | 1004 +
design/icons/gnome-globe_nuvola.svg | 1195 +
design/icons/gtk-zoom-in_nuvola.svg | 433 +
design/icons/help_contents.svg | 701 +
.../inaccessible_tango_emblem-unreadable.svg | 357 +
design/icons/language.png | Bin 0 -> 3520 bytes
design/icons/locked_tango-emblem-readonly.svg | 298 +
.../icons/multimedia-dell-dj-pocket_tango.svg | 4405 ++++
...multimedia-player-ipod-mini-blue_tango.svg | 4126 +++
.../multimedia-player-motorola-rokr_tango.svg | 1025 +
.../icons/network-transmit-receive_design.svg | 1041 +
design/icons/pile_of_devices.png | Bin 0 -> 17218 bytes
design/icons/pile_of_devices.svg | 22002 ++++++++++++++++
.../preferences-desktop-locale_tango.svg | 828 +
design/icons/preferences-system_tango.svg | 396 +
design/icons/redhat-config-users_wasp.svg | 1904 ++
design/icons/seahorse-preferences_gnome.svg | 1250 +
design/icons/spherecrystal_help.svg | 60 +
design/icons/system-log-out_tango.svg | 362 +
design/icons/unlocked_clavdia.svg | 515 +
design/icons/unlocked_lars.svg | 1198 +
doc/html/fr | 1 +
doc/html/si | 1 +
known_problems | 4 +
lang/README | 31 +-
lang/TODO | 2 +
lang/de.hdf | 437 +-
lang/en.hdf | 360 +-
lang/fr.hdf | 502 +-
lang/language_specification.txt | 24 +
lang/si.hdf | 400 +-
plugins/date/date.py | 69 +
plugins/date/form_date.cs | 44 +
plugins/date/lang/en.hdf | 35 +
plugins/date/plugin_icon.png | Bin 0 -> 3285 bytes
plugins/date/root_action.py | 36 +
plugins/date/unittests.py | 60 +
plugins/disks/disks.cs | 17 +
plugins/disks/disks.py | 17 +
plugins/disks/lang/de.hdf | 6 +
plugins/disks/lang/en.hdf | 6 +
plugins/disks/plugin_icon.png | Bin 0 -> 6279 bytes
plugins/disks/unittests.py | 9 +
plugins/format_fs/format_fs.py | 95 +
plugins/format_fs/lang/en.hdf | 49 +
plugins/format_fs/plugin_icon.png | Bin 0 -> 6376 bytes
plugins/format_fs/unittests.py | 10 +
plugins/format_fs/volume_format.cs | 37 +
plugins/format_fs/volume_format_luks.cs | 32 +
plugins/help/doc.cs | 9 +
plugins/help/help.py | 25 +
plugins/help/lang/en.hdf | 5 +
plugins/help/plugin_icon.png | Bin 0 -> 12693 bytes
plugins/help/unittests.py | 29 +
plugins/language_selection/lang/en.hdf | 5 +
.../language_selection/language_selection.cs | 15 +
.../language_selection/language_selection.py | 16 +
plugins/language_selection/plugin_icon.png | Bin 0 -> 13094 bytes
plugins/language_selection/unittests.py | 10 +
plugins/logs/lang/en.hdf | 6 +
plugins/logs/logs.css | 6 +
plugins/logs/logs.py | 29 +
plugins/logs/plugin_icon.png | Bin 0 -> 16601 bytes
plugins/logs/show_log.cs | 19 +
plugins/logs/unittests.py | 21 +
plugins/network/form_network.cs | 30 +
plugins/network/lang/en.hdf | 23 +
plugins/network/network.py | 126 +
plugins/network/plugin_icon.png | Bin 0 -> 13698 bytes
plugins/network/root_action.py | 42 +
plugins/network/unittests.py | 45 +
plugins/partition/current_partition_info.cs | 11 +
plugins/partition/lang/en.hdf | 83 +
plugins/partition/partition.css | 4 +
plugins/partition/partition.py | 416 +
plugins/partition/plugin_icon.png | Bin 0 -> 2943 bytes
plugins/partition/root_action.py | 96 +
plugins/partition/select_device.cs | 45 +
plugins/partition/set_partitions.cs | 78 +
plugins/partition/show_format_progress.cs | 17 +
plugins/partition/unittests.py | 10 +
plugins/plugin-interface.txt | 63 +
plugins/plugin_icon_unknown.png | Bin 0 -> 14269 bytes
plugins/plugin_manager/lang/en.hdf | 15 +
plugins/plugin_manager/plugin_icon.png | Bin 0 -> 631 bytes
plugins/plugin_manager/plugin_list.cs | 65 +
plugins/plugin_manager/plugin_manager.py | 52 +
plugins/plugin_manager/unittests.py | 12 +
plugins/shutdown/form_shutdown.cs | 15 +
plugins/shutdown/gnome-reboot.png | Bin 0 -> 3752 bytes
plugins/shutdown/gnome-shutdown.png | Bin 0 -> 4532 bytes
plugins/shutdown/lang/en.hdf | 34 +
plugins/shutdown/plugin_icon.png | Bin 0 -> 7588 bytes
plugins/shutdown/progress_reboot.cs | 6 +
plugins/shutdown/progress_shutdown.cs | 6 +
plugins/shutdown/root_action.py | 48 +
plugins/shutdown/shutdown.py | 51 +
plugins/shutdown/unittests.py | 11 +
plugins/system_preferences/lang/en.hdf | 5 +
plugins/system_preferences/plugin_icon.png | Bin 0 -> 12762 bytes
plugins/system_preferences/show_plugins.cs | 15 +
.../system_preferences/system_preferences.py | 16 +
plugins/system_preferences/unittests.py | 8 +
plugins/user_manager/lang/en.hdf | 51 +
plugins/user_manager/plugin_icon.png | Bin 0 -> 10613 bytes
plugins/user_manager/unittests.py | 27 +
plugins/user_manager/user_list.cs | 82 +
plugins/user_manager/user_manager.py | 81 +
plugins/volume_details/lang/en.hdf | 20 +
plugins/volume_details/plugin_icon.png | Bin 0 -> 11235 bytes
plugins/volume_details/unittests.py | 10 +
plugins/volume_details/volume_details.cs | 21 +
plugins/volume_details/volume_details.py | 18 +
plugins/volume_mount/lang/en.hdf | 56 +
plugins/volume_mount/plugin_icon.png | Bin 0 -> 4535 bytes
plugins/volume_mount/unittests.py | 10 +
plugins/volume_mount/volume_mount.cs | 18 +
plugins/volume_mount/volume_mount.py | 103 +
plugins/volume_mount/volume_status.cs | 9 +
plugins/volume_mount/volume_umount.cs | 10 +
plugins/volume_props/lang/en.hdf | 63 +
plugins/volume_props/plugin_icon.png | Bin 0 -> 15249 bytes
plugins/volume_props/unittests.py | 10 +
plugins/volume_props/volume_properties.cs | 75 +
plugins/volume_props/volume_props.py | 81 +
scripts/check_languages.py | 106 +
scripts/check_languages.sh | 26 -
scripts/userdocexport.sh | 2 +-
templates/access_denied.cs | 6 +
templates/empty.cs | 3 +
templates/error.cs | 3 -
templates/footer.cs | 37 +-
templates/form_config.cs | 34 -
templates/form_init.cs | 27 -
templates/form_init_partition.cs | 33 -
templates/form_mount.cs | 40 -
templates/form_system.cs | 29 -
templates/form_umount.cs | 37 -
templates/header.cs | 71 +-
templates/macros.cs | 204 +-
templates/main.cs | 10 +-
templates/nav.cs | 26 -
templates/show_doc.cs | 7 -
templates/show_log.cs | 13 -
templates/show_status.cs | 28 -
templates/show_volume.cs | 73 +-
templates/show_volume_footer.cs | 3 +
templates/show_volume_header.cs | 21 +
templates/show_volumes.cs | 15 -
templates/volume_plugins.cs | 25 +
www-data/background_frame_corner.png | Bin 0 -> 2355 bytes
www-data/background_frame_top.png | Bin 0 -> 2109 bytes
www-data/cryptobox.css | 518 +-
www-data/dialog-error_tango.png | Bin 0 -> 8750 bytes
www-data/dialog-information_tango.png | Bin 0 -> 14120 bytes
www-data/dialog-warning_tango.png | Bin 0 -> 7611 bytes
www-data/disc_gray.png | Bin 0 -> 4524 bytes
www-data/disc_green.png | Bin 4752 -> 4936 bytes
www-data/disc_red.png | Bin 4804 -> 4988 bytes
www-data/evil_stick.png | Bin 0 -> 32626 bytes
www-data/footer_line.png | Bin 0 -> 262 bytes
www-data/icon_background_active.png | Bin 0 -> 14372 bytes
www-data/icon_background_active_060.png | Bin 0 -> 1845 bytes
www-data/icon_background_active_080.png | Bin 0 -> 2891 bytes
www-data/icon_background_active_100.png | Bin 0 -> 4101 bytes
www-data/icon_background_active_256.png | Bin 0 -> 14372 bytes
www-data/icon_background_passive_060.png | Bin 0 -> 1570 bytes
www-data/icon_background_passive_080.png | Bin 0 -> 2338 bytes
www-data/icon_background_passive_100.png | Bin 0 -> 3292 bytes
www-data/pane_bottom_left.png | Bin 0 -> 249 bytes
www-data/pane_bottom_right.png | Bin 0 -> 249 bytes
www-data/pane_side_bottom.png | Bin 0 -> 131 bytes
www-data/pane_side_left.png | Bin 0 -> 131 bytes
www-data/pane_side_right.png | Bin 0 -> 131 bytes
www-data/pane_side_top.png | Bin 0 -> 133 bytes
www-data/pane_top_left.png | Bin 0 -> 249 bytes
www-data/pane_top_right.png | Bin 0 -> 273 bytes
www-data/register_active.png | Bin 0 -> 560 bytes
www-data/register_active2.png | Bin 0 -> 1078 bytes
www-data/register_passive.png | Bin 0 -> 523 bytes
www-data/register_passive2.png | Bin 0 -> 1087 bytes
www-data/volume_active_crypto.png | Bin 0 -> 6590 bytes
www-data/volume_active_plain.png | Bin 0 -> 5903 bytes
www-data/volume_passive_crypto.png | Bin 0 -> 8140 bytes
www-data/volume_passive_plain.png | Bin 0 -> 7661 bytes
www-data/volume_property_frame.png | Bin 0 -> 6389 bytes
230 files changed, 56014 insertions(+), 1607 deletions(-)
rename {bin => bin-perl-old}/Makefile (100%)
rename {bin => bin-perl-old}/cbox-manage.sh (100%)
rename {bin => bin-perl-old}/cbox-root-actions.sh (100%)
rename {bin => bin-perl-old}/cryptobox.pl (99%)
rename {bin => bin-perl-old}/cryptobox_wrapper.c (100%)
rename {bin => bin-perl-old}/ro-system.sh (100%)
create mode 100755 bin/CryptoBox.py
create mode 100755 bin/CryptoBoxContainer.py
create mode 100644 bin/CryptoBoxExceptions.py
create mode 100644 bin/CryptoBoxPlugin.py
create mode 100755 bin/CryptoBoxRootActions.py
create mode 100644 bin/CryptoBoxSettings.py
create mode 100644 bin/CryptoBoxTools.py
create mode 100755 bin/CryptoBoxWebserver.py
create mode 100644 bin/Plugins.py
create mode 100644 bin/WebInterfaceDataset.py
create mode 100755 bin/WebInterfaceSites.py
create mode 100644 bin/WebInterfaceTestClass.py
create mode 100644 bin/coding_guidelines.txt
create mode 100644 bin/cryptobox.conf
create mode 100755 bin/cryptoboxd
create mode 100644 bin/cryptoboxwebserver.conf
create mode 100755 bin/do_unittests.sh
create mode 100644 bin/example-super.tab
create mode 100755 bin/test.complete.CryptoBox.py
create mode 100755 bin/uml-setup.sh
create mode 100755 bin/unittests.CryptoBox.py
create mode 100755 bin/unittests.CryptoBoxTools.py
create mode 100755 bin/unittests.Plugins.py
create mode 100755 bin/unittests.WebSites.py
create mode 100644 design/background_frame_corner.svg
create mode 100644 design/icon_background_active.svg
create mode 100644 design/icons/applications-system_tango.svg
create mode 100644 design/icons/computer_tango.svg
create mode 100644 design/icons/dialog-error_tango.svg
create mode 100644 design/icons/dialog-information_tango.svg
create mode 100644 design/icons/dialog-warning_tango.svg
create mode 100644 design/icons/drive-cdrom_tango.svg
create mode 100644 design/icons/drive-harddisk_tango.svg
create mode 100644 design/icons/drive-removable-media_tango.svg
create mode 100644 design/icons/globe-lips.svg
create mode 100644 design/icons/gnome-dev-removable-usb_nuvola.svg
create mode 100644 design/icons/gnome-globe_nuvola.svg
create mode 100644 design/icons/gtk-zoom-in_nuvola.svg
create mode 100644 design/icons/help_contents.svg
create mode 100644 design/icons/inaccessible_tango_emblem-unreadable.svg
create mode 100644 design/icons/language.png
create mode 100644 design/icons/locked_tango-emblem-readonly.svg
create mode 100644 design/icons/multimedia-dell-dj-pocket_tango.svg
create mode 100644 design/icons/multimedia-player-ipod-mini-blue_tango.svg
create mode 100644 design/icons/multimedia-player-motorola-rokr_tango.svg
create mode 100644 design/icons/network-transmit-receive_design.svg
create mode 100644 design/icons/pile_of_devices.png
create mode 100644 design/icons/pile_of_devices.svg
create mode 100644 design/icons/preferences-desktop-locale_tango.svg
create mode 100644 design/icons/preferences-system_tango.svg
create mode 100644 design/icons/redhat-config-users_wasp.svg
create mode 100644 design/icons/seahorse-preferences_gnome.svg
create mode 100644 design/icons/spherecrystal_help.svg
create mode 100644 design/icons/system-log-out_tango.svg
create mode 100644 design/icons/unlocked_clavdia.svg
create mode 100644 design/icons/unlocked_lars.svg
create mode 120000 doc/html/fr
create mode 120000 doc/html/si
create mode 100644 known_problems
create mode 100644 lang/language_specification.txt
create mode 100644 plugins/date/date.py
create mode 100644 plugins/date/form_date.cs
create mode 100644 plugins/date/lang/en.hdf
create mode 100644 plugins/date/plugin_icon.png
create mode 100755 plugins/date/root_action.py
create mode 100644 plugins/date/unittests.py
create mode 100644 plugins/disks/disks.cs
create mode 100644 plugins/disks/disks.py
create mode 100644 plugins/disks/lang/de.hdf
create mode 100644 plugins/disks/lang/en.hdf
create mode 100644 plugins/disks/plugin_icon.png
create mode 100644 plugins/disks/unittests.py
create mode 100644 plugins/format_fs/format_fs.py
create mode 100644 plugins/format_fs/lang/en.hdf
create mode 100644 plugins/format_fs/plugin_icon.png
create mode 100644 plugins/format_fs/unittests.py
create mode 100644 plugins/format_fs/volume_format.cs
create mode 100644 plugins/format_fs/volume_format_luks.cs
create mode 100644 plugins/help/doc.cs
create mode 100644 plugins/help/help.py
create mode 100644 plugins/help/lang/en.hdf
create mode 100644 plugins/help/plugin_icon.png
create mode 100644 plugins/help/unittests.py
create mode 100644 plugins/language_selection/lang/en.hdf
create mode 100644 plugins/language_selection/language_selection.cs
create mode 100644 plugins/language_selection/language_selection.py
create mode 100644 plugins/language_selection/plugin_icon.png
create mode 100644 plugins/language_selection/unittests.py
create mode 100644 plugins/logs/lang/en.hdf
create mode 100644 plugins/logs/logs.css
create mode 100644 plugins/logs/logs.py
create mode 100644 plugins/logs/plugin_icon.png
create mode 100644 plugins/logs/show_log.cs
create mode 100644 plugins/logs/unittests.py
create mode 100644 plugins/network/form_network.cs
create mode 100644 plugins/network/lang/en.hdf
create mode 100644 plugins/network/network.py
create mode 100644 plugins/network/plugin_icon.png
create mode 100755 plugins/network/root_action.py
create mode 100644 plugins/network/unittests.py
create mode 100644 plugins/partition/current_partition_info.cs
create mode 100644 plugins/partition/lang/en.hdf
create mode 100644 plugins/partition/partition.css
create mode 100644 plugins/partition/partition.py
create mode 100644 plugins/partition/plugin_icon.png
create mode 100755 plugins/partition/root_action.py
create mode 100644 plugins/partition/select_device.cs
create mode 100644 plugins/partition/set_partitions.cs
create mode 100644 plugins/partition/show_format_progress.cs
create mode 100644 plugins/partition/unittests.py
create mode 100644 plugins/plugin-interface.txt
create mode 100644 plugins/plugin_icon_unknown.png
create mode 100644 plugins/plugin_manager/lang/en.hdf
create mode 100644 plugins/plugin_manager/plugin_icon.png
create mode 100644 plugins/plugin_manager/plugin_list.cs
create mode 100644 plugins/plugin_manager/plugin_manager.py
create mode 100644 plugins/plugin_manager/unittests.py
create mode 100644 plugins/shutdown/form_shutdown.cs
create mode 100644 plugins/shutdown/gnome-reboot.png
create mode 100644 plugins/shutdown/gnome-shutdown.png
create mode 100644 plugins/shutdown/lang/en.hdf
create mode 100644 plugins/shutdown/plugin_icon.png
create mode 100644 plugins/shutdown/progress_reboot.cs
create mode 100644 plugins/shutdown/progress_shutdown.cs
create mode 100755 plugins/shutdown/root_action.py
create mode 100644 plugins/shutdown/shutdown.py
create mode 100644 plugins/shutdown/unittests.py
create mode 100644 plugins/system_preferences/lang/en.hdf
create mode 100644 plugins/system_preferences/plugin_icon.png
create mode 100644 plugins/system_preferences/show_plugins.cs
create mode 100644 plugins/system_preferences/system_preferences.py
create mode 100644 plugins/system_preferences/unittests.py
create mode 100644 plugins/user_manager/lang/en.hdf
create mode 100644 plugins/user_manager/plugin_icon.png
create mode 100644 plugins/user_manager/unittests.py
create mode 100644 plugins/user_manager/user_list.cs
create mode 100644 plugins/user_manager/user_manager.py
create mode 100644 plugins/volume_details/lang/en.hdf
create mode 100644 plugins/volume_details/plugin_icon.png
create mode 100644 plugins/volume_details/unittests.py
create mode 100644 plugins/volume_details/volume_details.cs
create mode 100644 plugins/volume_details/volume_details.py
create mode 100644 plugins/volume_mount/lang/en.hdf
create mode 100644 plugins/volume_mount/plugin_icon.png
create mode 100644 plugins/volume_mount/unittests.py
create mode 100644 plugins/volume_mount/volume_mount.cs
create mode 100644 plugins/volume_mount/volume_mount.py
create mode 100644 plugins/volume_mount/volume_status.cs
create mode 100644 plugins/volume_mount/volume_umount.cs
create mode 100644 plugins/volume_props/lang/en.hdf
create mode 100644 plugins/volume_props/plugin_icon.png
create mode 100644 plugins/volume_props/unittests.py
create mode 100644 plugins/volume_props/volume_properties.cs
create mode 100644 plugins/volume_props/volume_props.py
create mode 100755 scripts/check_languages.py
delete mode 100755 scripts/check_languages.sh
create mode 100644 templates/access_denied.cs
delete mode 100644 templates/error.cs
delete mode 100644 templates/form_config.cs
delete mode 100644 templates/form_init.cs
delete mode 100644 templates/form_init_partition.cs
delete mode 100644 templates/form_mount.cs
delete mode 100644 templates/form_system.cs
delete mode 100644 templates/form_umount.cs
delete mode 100644 templates/nav.cs
delete mode 100644 templates/show_doc.cs
delete mode 100644 templates/show_log.cs
delete mode 100644 templates/show_status.cs
create mode 100644 templates/show_volume_footer.cs
create mode 100644 templates/show_volume_header.cs
delete mode 100644 templates/show_volumes.cs
create mode 100644 templates/volume_plugins.cs
create mode 100644 www-data/background_frame_corner.png
create mode 100644 www-data/background_frame_top.png
create mode 100644 www-data/dialog-error_tango.png
create mode 100644 www-data/dialog-information_tango.png
create mode 100644 www-data/dialog-warning_tango.png
create mode 100644 www-data/disc_gray.png
create mode 100644 www-data/evil_stick.png
create mode 100644 www-data/footer_line.png
create mode 100644 www-data/icon_background_active.png
create mode 100644 www-data/icon_background_active_060.png
create mode 100644 www-data/icon_background_active_080.png
create mode 100644 www-data/icon_background_active_100.png
create mode 100644 www-data/icon_background_active_256.png
create mode 100644 www-data/icon_background_passive_060.png
create mode 100644 www-data/icon_background_passive_080.png
create mode 100644 www-data/icon_background_passive_100.png
create mode 100644 www-data/pane_bottom_left.png
create mode 100644 www-data/pane_bottom_right.png
create mode 100644 www-data/pane_side_bottom.png
create mode 100644 www-data/pane_side_left.png
create mode 100644 www-data/pane_side_right.png
create mode 100644 www-data/pane_side_top.png
create mode 100644 www-data/pane_top_left.png
create mode 100644 www-data/pane_top_right.png
create mode 100644 www-data/register_active.png
create mode 100644 www-data/register_active2.png
create mode 100644 www-data/register_passive.png
create mode 100644 www-data/register_passive2.png
create mode 100644 www-data/volume_active_crypto.png
create mode 100644 www-data/volume_active_plain.png
create mode 100644 www-data/volume_passive_crypto.png
create mode 100644 www-data/volume_passive_plain.png
create mode 100644 www-data/volume_property_frame.png
diff --git a/bin/Makefile b/bin-perl-old/Makefile
similarity index 100%
rename from bin/Makefile
rename to bin-perl-old/Makefile
diff --git a/bin/cbox-manage.sh b/bin-perl-old/cbox-manage.sh
similarity index 100%
rename from bin/cbox-manage.sh
rename to bin-perl-old/cbox-manage.sh
diff --git a/bin/cbox-root-actions.sh b/bin-perl-old/cbox-root-actions.sh
similarity index 100%
rename from bin/cbox-root-actions.sh
rename to bin-perl-old/cbox-root-actions.sh
diff --git a/bin/cryptobox.pl b/bin-perl-old/cryptobox.pl
similarity index 99%
rename from bin/cryptobox.pl
rename to bin-perl-old/cryptobox.pl
index 9c3a999..7f36806 100755
--- a/bin/cryptobox.pl
+++ b/bin-perl-old/cryptobox.pl
@@ -532,7 +532,7 @@ if ( ! &check_ssl()) {
if ($device eq '') {
&debug_msg(DEBUG_INFO, "invalid device: " . $query->param('device'));
$pagedata->setValue('Data.Warning', 'InvalidDevice');
- $pagedata->setValue('Data.Action', 'empty');
+ $pagedata->setValue('Data.Action', 'emptu');
} elsif ( ! &check_config()) {
$pagedata->setValue('Data.Warning', 'NotInitialized');
$pagedata->setValue('Data.Action', 'form_init');
diff --git a/bin/cryptobox_wrapper.c b/bin-perl-old/cryptobox_wrapper.c
similarity index 100%
rename from bin/cryptobox_wrapper.c
rename to bin-perl-old/cryptobox_wrapper.c
diff --git a/bin/ro-system.sh b/bin-perl-old/ro-system.sh
similarity index 100%
rename from bin/ro-system.sh
rename to bin-perl-old/ro-system.sh
diff --git a/bin/CryptoBox.py b/bin/CryptoBox.py
new file mode 100755
index 0000000..1472b53
--- /dev/null
+++ b/bin/CryptoBox.py
@@ -0,0 +1,276 @@
+#!/usr/bin/env python2.4
+'''
+This is the web interface for a fileserver managing encrypted filesystems.
+
+It was originally written in bash/perl. Now a complete rewrite is in
+progress. So things might be confusing here. Hopefully not for long.
+:)
+'''
+
+# check python version
+import sys
+(ver_major, ver_minor, ver_sub, ver_desc, ver_subsub) = sys.version_info
+if (ver_major < 2) or ((ver_major == 2) and (ver_minor < 4)):
+ sys.stderr.write("You need a python version >= 2.4\nCurrent version is:\n %s\n" % sys.version)
+ sys.exit(1)
+
+import CryptoBoxContainer
+from CryptoBoxExceptions import *
+import re
+import os
+import CryptoBoxTools
+import subprocess
+
+
+
+class CryptoBox:
+ '''this class rules them all!
+
+ put things like logging, conf and oter stuff in here,
+ that might be used by more classes, it will be passed on to them'''
+
+ VERSION = "0.3~1"
+
+ def __init__(self, config_file=None):
+ import CryptoBoxSettings
+ self.log = self.__getStartupLogger()
+ self.prefs = CryptoBoxSettings.CryptoBoxSettings(config_file)
+ self.__runTests()
+
+
+ def __getStartupLogger(self):
+ import logging
+ '''initialises the logging system
+
+ use it with: 'self.log.[debug|info|warning|error|critical](logmessage)'
+ all classes should get the logging instance during __init__:
+ self.log = logging.getLogger("CryptoBox")
+
+ first we output all warnings/errors to stderr
+ as soon as we opened the config file successfully, we redirect debug output
+ to the configured destination'''
+ ## basicConfig(...) needs python >= 2.4
+ try:
+ log_handler = logging.getLogger("CryptoBox")
+ logging.basicConfig(
+ format='%(asctime)s CryptoBox %(levelname)s: %(message)s',
+ stderr=sys.stderr)
+ log_handler.setLevel(logging.ERROR)
+ log_handler.info("loggingsystem is up'n running")
+ ## from now on everything can be logged via self.log...
+ except:
+ raise CBEnvironmentError("couldn't initialise the loggingsystem. I give up.")
+ return log_handler
+
+
+ # do some initial checks
+ def __runTests(self):
+ self.__runTestUID()
+ self.__runTestRootPriv()
+
+
+ def __runTestUID(self):
+ if os.geteuid() == 0:
+ raise CBEnvironmentError("you may not run the cryptobox as root")
+
+
+ def __runTestRootPriv(self):
+ """try to run 'super' with 'CryptoBoxRootActions'"""
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ raise CBEnvironmentError("could not open %s for writing!" % os.devnull)
+ try:
+ prog_super = self.prefs["Programs"]["super"]
+ except KeyError:
+ raise CBConfigUndefinedError("Programs", "super")
+ try:
+ prog_rootactions = self.prefs["Programs"]["CryptoBoxRootActions"]
+ except KeyError:
+ raise CBConfigUndefinedError("Programs", "CryptoBoxRootActions")
+ try:
+ proc = subprocess.Popen(
+ shell = False,
+ stdout = devnull,
+ stderr = devnull,
+ args = [prog_super, prog_rootactions, "check"])
+ except OSError:
+ raise CBEnvironmentError("failed to execute 'super' (%s)" % self.prefs["Programs"]["super"])
+ proc.wait()
+ if proc.returncode != 0:
+ raise CBEnvironmentError("failed to call CryptoBoxRootActions (%s) via 'super' - maybe you did not add the appropriate line to /etc/super.tab?" % prog_rootactions)
+
+
+ # this method just demonstrates inheritance effects - may be removed
+ def cbx_inheritance_test(self, string="you lucky widow"):
+ self.log.info(string)
+
+
+# RFC: why should CryptoBoxProps inherit CryptoBox? [l]
+# RFC: shouldn't we move all useful functions of CryptoBoxProps to CryptoBox? [l]
+class CryptoBoxProps(CryptoBox):
+ '''Get and set the properties of a CryptoBox
+
+ This class contains all available devices that may be accessed.
+ All properties of the cryptobox can be accessed by this class.
+ '''
+
+ def __init__(self, config_file=None):
+ '''read config and fill class variables'''
+ CryptoBox.__init__(self, config_file)
+ self.reReadContainerList()
+
+
+ def reReadContainerList(self):
+ self.log.debug("rereading container list")
+ self.containers = []
+ for device in CryptoBoxTools.getAvailablePartitions():
+ if self.isDeviceAllowed(device) and not self.isConfigPartition(device):
+ self.containers.append(CryptoBoxContainer.CryptoBoxContainer(device, self))
+ ## sort by container name
+ self.containers.sort(cmp = lambda x,y: x.getName() < y.getName() and -1 or 1)
+
+
+ def isConfigPartition(self, device):
+ proc = subprocess.Popen(
+ shell = False,
+ stdout = subprocess.PIPE,
+ args = [
+ self.prefs["Programs"]["blkid"],
+ "-c", os.path.devnull,
+ "-o", "value",
+ "-s", "LABEL",
+ device])
+ (output, error) = proc.communicate()
+ return output.strip() == self.prefs["Main"]["ConfigVolumeLabel"]
+
+
+ def isDeviceAllowed(self, devicename):
+ "check if a device is white-listed for being used as cryptobox containers"
+ import types
+ allowed = self.prefs["Main"]["AllowedDevices"]
+ if type(allowed) == types.StringType: allowed = [allowed]
+ for a_dev in allowed:
+ "remove double dots and so on ..."
+ real_device = os.path.realpath(devicename)
+ if a_dev and re.search('^' + a_dev, real_device): return True
+ return False
+
+
+ def getLogData(self, lines=None, maxSize=None):
+ """get the most recent log entries of the cryptobox
+
+ the maximum number and size of these entries can be limited by 'lines' and 'maxSize'
+ """
+ # return nothing if the currently selected log output is not a file
+ try:
+ if self.prefs["Log"]["Destination"].upper() != "FILE": return []
+ log_file = self.prefs["Log"]["Details"]
+ except KeyError:
+ self.log.error("could not evaluate one of the following config settings: [Log]->Destination or [Log]->Details")
+ return []
+ try:
+ fd = open(log_file, "r")
+ if maxSize: fd.seek(-maxSize, 2) # seek relative to the end of the file
+ content = fd.readlines()
+ fd.close()
+ except IOError:
+ self.log.warn("failed to read the log file (%s)" % log_file)
+ return []
+ if lines: content = content[-lines:]
+ content.reverse()
+ return content
+
+
+ def getContainerList(self, filterType=None, filterName=None):
+ "retrieve the list of all containers of this cryptobox"
+ try:
+ result = self.containers[:]
+ if filterType != None:
+ if filterType in range(len(CryptoBoxContainer.Types)):
+ return [e for e in self.containers if e.getType() == filterType]
+ else:
+ self.log.info("invalid filterType (%d)" % filterType)
+ result.clear()
+ if filterName != None:
+ result = [e for e in self.containers if e.getName() == filterName]
+ return result
+ except AttributeError:
+ return []
+
+
+ def getContainer(self, device):
+ "retrieve the container element for this device"
+ all = [e for e in self.getContainerList() if e.device == device]
+ if all:
+ return all[0]
+ else:
+ return None
+
+
+ def setNameForUUID(self, uuid, name):
+ "assign a name to a uuid in the ContainerNameDatabase"
+ used_uuid = self.getUUIDForName(name)
+ "first remove potential conflicting uuid/name combination"
+ if used_uuid:
+ ## remember the container which name was overriden
+ for e in self.containers:
+ if e.getName() == name:
+ forcedRename = e
+ break
+ del self.prefs.nameDB[used_uuid]
+ self.prefs.nameDB[uuid] = name
+ self.prefs.nameDB.write()
+ ## rename the container that lost its name (necessary while we use cherrypy)
+ if used_uuid:
+ ## this is surely not the best way to regenerate the name
+ dev = e.getDevice()
+ old_index = self.containers.index(e)
+ self.containers.remove(e)
+ self.containers.insert(old_index, CryptoBoxContainer.CryptoBoxContainer(dev,self))
+ ## there should be no reason for any failure
+ return True
+
+
+ def getNameForUUID(self, uuid):
+ "get the name belonging to a specified key (usually the UUID of a fs)"
+ try:
+ return self.prefs.nameDB[uuid]
+ except KeyError:
+ return None
+
+
+ def getUUIDForName(self, name):
+ """ get the key belonging to a value in the ContainerNameDatabase
+ this is the reverse action of 'getNameForUUID' """
+ for key in self.prefs.nameDB.keys():
+ if self.prefs.nameDB[key] == name: return key
+ "the uuid was not found"
+ return None
+
+
+ def removeUUID(self, uuid):
+ if uuid in self.prefs.nameDB.keys():
+ del self.prefs.nameDB[uuid]
+ return True
+ else:
+ return False
+
+
+ def getAvailableLanguages(self):
+ '''reads all files in path LangDir and returns a list of
+ basenames from existing hdf files, that should are all available
+ languages'''
+ languages = [ f.rstrip(".hdf")
+ for f in os.listdir(self.prefs["Locations"]["LangDir"])
+ if f.endswith(".hdf") ]
+ if len(languages) < 1:
+ self.log.error("No .hdf files found! The website won't render properly.")
+ return languages
+
+
+
+
+if __name__ == "__main__":
+ cb = CryptoBoxProps()
+
diff --git a/bin/CryptoBoxContainer.py b/bin/CryptoBoxContainer.py
new file mode 100755
index 0000000..e658c53
--- /dev/null
+++ b/bin/CryptoBoxContainer.py
@@ -0,0 +1,607 @@
+#!/usr/bin/env python2.4
+
+## check python version
+import sys
+(ver_major, ver_minor, ver_sub, ver_desc, ver_subsub) = sys.version_info
+if (ver_major < 2) or ((ver_major == 2) and (ver_minor < 4)):
+ sys.stderr.write("You need a python version >= 2.4\nCurrent version is:\n %s\n" % sys.version)
+ sys.exit(1)
+
+import subprocess
+import os
+import re
+import logging
+from CryptoBoxExceptions import *
+
+"""exceptions:
+ VolumeIsActive
+ NameActivelyUsed
+ InvalidName
+ InvalidPassword
+ InvalidType
+ CreateError
+ MountError
+ ChangePasswordError
+ """
+
+class CryptoBoxContainer:
+
+ Types = {
+ "unused":0,
+ "plain":1,
+ "luks":2,
+ "swap":3}
+
+
+ __fsTypes = {
+ "plain":["ext3", "ext2", "vfat", "reiser"],
+ "swap":["swap"]}
+ # TODO: more filesystem types? / check 'reiser'
+
+ __dmDir = "/dev/mapper"
+
+
+ def __init__(self, device, cbox):
+ self.device = device
+ self.cbox = cbox
+ self.log = logging.getLogger("CryptoBox")
+ self.resetObject()
+
+
+ def getName(self):
+ return self.name
+
+
+ def setName(self, new_name):
+ if new_name == self.name: return
+ if self.isMounted():
+ raise CBVolumeIsActive("the container must be inactive during renaming")
+ if not re.search(r'^[a-zA-Z0-9_\.\- ]+$', new_name):
+ raise CBInvalidName("the supplied new name contains illegal characters")
+ "check for active partitions with the same name"
+ prev_name_owner = self.cbox.getContainerList(filterName=new_name)
+ if prev_name_owner:
+ for a in prev_name_owner:
+ if a.isMounted():
+ raise CBNameActivelyUsed("the supplied new name is already in use for an active partition")
+ if not self.cbox.setNameForUUID(self.uuid, new_name):
+ raise CBContainerError("failed to change the volume name for unknown reasons")
+ self.name = new_name
+
+
+ def getDevice(self):
+ return self.device
+
+
+ def getType(self):
+ return self.type
+
+
+ def isMounted(self):
+ return os.path.ismount(self.__getMountPoint())
+
+
+ def getCapacity(self):
+ """return the current capacity state of the volume
+
+ the volume may not be mounted
+ the result is a tuple of values in megabyte:
+ (size, available, used)
+ """
+ info = os.statvfs(self.__getMountPoint())
+ return (
+ int(info.f_bsize*info.f_blocks/1024/1024),
+ int(info.f_bsize*info.f_bavail/1024/1024),
+ int(info.f_bsize*(info.f_blocks-info.f_bavail)/1024/1024))
+
+
+ def getSize(self):
+ """return the size of the block device (_not_ of the filesystem)
+
+ the result is a value in megabyte
+ an error is indicated by "-1"
+ """
+ import CryptoBoxTools
+ return CryptoBoxTools.getBlockDeviceSize(self.device)
+
+
+ def resetObject(self):
+ """ recheck the information about this container
+ this is especially useful after changing the type via 'create' """
+ self.uuid = self.__getUUID()
+ self.type = self.__getTypeOfPartition()
+ self.name = self.__getNameOfContainer()
+ if self.type == self.Types["luks"]:
+ self.mount = self.__mountLuks
+ self.umount = self.__umountLuks
+ elif self.type == self.Types["plain"]:
+ self.mount = self.__mountPlain
+ self.umount = self.__umountPlain
+
+
+ def create(self, type, password=None):
+ old_name = self.getName()
+ if type == self.Types["luks"]:
+ self.__createLuks(password)
+ elif type == self.Types["plain"]:
+ self.__createPlain()
+ else:
+ raise CBInvalidType("invalid container type (%d) supplied" % (type, ))
+ ## no exception was raised during creation -> we can continue
+ ## reset the properties (encryption state, ...) of the device
+ self.resetObject()
+ ## restore the old name (must be after resetObject)
+ self.setName(old_name)
+
+
+ def changePassword(self, oldpw, newpw):
+ if self.type != self.Types["luks"]:
+ raise CBInvalidType("changing of password is possible only for luks containers")
+ if not oldpw:
+ raise CBInvalidPassword("no old password supplied for password change")
+ if not newpw:
+ raise CBInvalidPassword("no new password supplied for password change")
+ "return if new and old passwords are the same"
+ if oldpw == newpw: return
+ if self.isMounted():
+ raise CBVolumeIsActive("this container is currently active")
+ devnull = None
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ "remove any potential open luks mapping"
+ self.__umountLuks()
+ "create the luks header"
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = subprocess.PIPE,
+ stdout = subprocess.PIPE,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "cryptsetup",
+ "luksAddKey",
+ self.device,
+ "--batch-mode"])
+ proc.stdin.write("%s\n%s" % (oldpw, newpw))
+ (output, errout) = proc.communicate()
+ if proc.returncode != 0:
+ errorMsg = "Could not add a new luks key: %s - %s" % (output.strip(), errout.strip(), )
+ self.log.error(errorMsg)
+ raise CBChangePasswordError(errorMsg)
+ ## retrieve the key slot we used for unlocking
+ keys_found = re.search(r'key slot (\d{1,3}) unlocked', output).groups()
+ if keys_found:
+ keyslot = int(keys_found[0])
+ else:
+ raise CBChangePasswordError("could not get the old key slot")
+ "remove the old key"
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["cryptsetup"],
+ "--batch-mode",
+ "luksDelKey",
+ self.device,
+ "%d" % (keyslot, )])
+ proc.wait()
+ if proc.returncode != 0:
+ errorMsg = "Could not remove the old luks key: %s" % (proc.stderr.read().strip(), )
+ self.log.error(errorMsg)
+ raise CBChangePasswordError(errorMsg)
+
+
+
+ " ****************** internal stuff ********************* "
+
+ def __getNameOfContainer(self):
+ "retrieve the name of the container by querying the database"
+ def_name = self.cbox.getNameForUUID(self.uuid)
+ if def_name: return def_name
+ "there is no name defined for this uuid - we will propose a good one"
+ prefix = self.cbox.prefs["Main"]["DefaultVolumePrefix"]
+ unused_found = False
+ counter = 1
+ while not unused_found:
+ guess = prefix + str(counter)
+ if self.cbox.getUUIDForName(guess):
+ counter += 1
+ else:
+ unused_found = True
+ self.cbox.setNameForUUID(self.uuid, guess)
+ return guess
+
+
+ def __getUUID(self):
+ if self.__getTypeOfPartition() == self.Types["luks"]:
+ guess = self.__getLuksUUID()
+ else:
+ guess = self.__getNonLuksUUID()
+ ## did we get a valid value?
+ if guess:
+ return guess
+ else:
+ ## emergency default value
+ return self.device.replace(os.path.sep, "_")
+
+
+ def __getLuksUUID(self):
+ """get uuid for luks devices"""
+ proc = subprocess.Popen(
+ shell = False,
+ stdout = subprocess.PIPE,
+ stderr = subprocess.PIPE,
+ args = [self.cbox.prefs["Programs"]["cryptsetup"],
+ "luksUUID",
+ self.device])
+ (stdout, stderr) = proc.communicate()
+ if proc.returncode != 0:
+ self.cbox.log.info("could not retrieve luks uuid (%s): %s", (self.device, stderr.strip()))
+ return None
+ return stdout.strip()
+
+
+ def __getNonLuksUUID(self):
+ """return UUID for ext2/3 and vfat filesystems"""
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.warn("Could not open %s" % (os.devnull, ))
+ proc = subprocess.Popen(
+ shell=False,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ args=[self.cbox.prefs["Programs"]["blkid"],
+ "-s", "UUID",
+ "-o", "value",
+ "-c", os.devnull,
+ "-w", os.devnull,
+ self.device])
+ (stdout, stderr) = proc.communicate()
+ devnull.close()
+ ## execution failed?
+ if proc.returncode != 0:
+ self.log.info("retrieving of partition type (%s) via 'blkid' failed: %s - maybe it is encrypted?" % (self.device, stderr.strip()))
+ return None
+ ## return output of blkid
+ return stdout.strip()
+
+
+ def __getTypeOfPartition(self):
+ "retrieve the type of the given partition (see CryptoBoxContainer.Types)"
+ if self.__isLuksPartition(): return self.Types["luks"]
+ typeOfPartition = self.__getTypeIdOfPartition()
+ if typeOfPartition in self.__fsTypes["plain"]:
+ return self.Types["plain"]
+ if typeOfPartition in self.__fsTypes["swap"]:
+ return self.Types["swap"]
+ return self.Types["unused"]
+
+
+ def __getTypeIdOfPartition(self):
+ "returns the type of the partition (see 'man blkid')"
+ devnull = None
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ proc = subprocess.Popen(
+ shell=False,
+ stdin=None,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ args=[self.cbox.prefs["Programs"]["blkid"],
+ "-s", "TYPE",
+ "-o", "value",
+ "-c", os.devnull,
+ "-w", os.devnull,
+ self.device])
+ proc.wait()
+ output = proc.stdout.read().strip()
+ if proc.returncode != 0:
+ self.log.warn("retrieving of partition type via 'blkid' failed: %s" % (proc.stderr.read().strip(), ))
+ return None
+ devnull.close()
+ return output
+
+
+ def __isLuksPartition(self):
+ "check if the given device is a luks partition"
+ devnull = None
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = devnull,
+ args = [
+ self.cbox.prefs["Programs"]["cryptsetup"],
+ "--batch-mode",
+ "isLuks",
+ self.device])
+ proc.wait()
+ devnull.close()
+ return proc.returncode == 0
+
+
+ def __getMountPoint(self):
+ "return the name of the mountpoint of this volume"
+ return os.path.join(self.cbox.prefs["Locations"]["MountParentDir"], self.name)
+
+
+ def __mountLuks(self, password):
+ "mount a luks partition"
+ if not password:
+ raise CBInvalidPassword("no password supplied for luksOpen")
+ if self.isMounted(): raise CBVolumeIsActive("this container is already active")
+ self.__umountLuks()
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ self.__cleanMountDirs()
+ if not os.path.exists(self.__getMountPoint()):
+ os.mkdir(self.__getMountPoint())
+ if not os.path.exists(self.__getMountPoint()):
+ errorMsg = "Could not create mountpoint (%s)" % (self.__getMountPoint(), )
+ self.log.error(errorMsg)
+ raise CBMountError(errorMsg)
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = subprocess.PIPE,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "cryptsetup",
+ "luksOpen",
+ self.device,
+ self.name,
+ "--batch-mode"])
+ proc.stdin.write(password)
+ (output, errout) = proc.communicate()
+ if proc.returncode != 0:
+ errorMsg = "Could not open the luks mapping: %s" % (errout.strip(), )
+ self.log.warn(errorMsg)
+ raise CBMountError(errorMsg)
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "mount",
+ os.path.join(self.__dmDir, self.name),
+ self.__getMountPoint()])
+ proc.wait()
+ if proc.returncode != 0:
+ errorMsg = "Could not mount the filesystem: %s" % (proc.stderr.read().strip(), )
+ self.log.warn(errorMsg)
+ raise CBMountError(errorMsg)
+ devnull.close()
+
+
+ def __umountLuks(self):
+ "umount a luks partition"
+ devnull = None
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ if self.isMounted():
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "umount",
+ self.__getMountPoint()])
+ proc.wait()
+ if proc.returncode != 0:
+ errorMsg = "Could not umount the filesystem: %s" % (proc.stderr.read().strip(), )
+ self.log.warn(errorMsg)
+ raise CBUmountError(errorMsg)
+ if os.path.exists(os.path.join(self.__dmDir, self.name)):
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "cryptsetup",
+ "luksClose",
+ self.name,
+ "--batch-mode"])
+ proc.wait()
+ if proc.returncode != 0:
+ errorMsg = "Could not remove the luks mapping: %s" % (proc.stderr.read().strip(), )
+ self.log.warn(errorMsg)
+ raise CBUmountError(errorMsg)
+ devnull.close()
+
+
+ def __mountPlain(self):
+ "mount a plaintext partition"
+ if self.isMounted(): raise CBVolumeIsActive("this container is already active")
+ devnull = None
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ self.__cleanMountDirs()
+ if not os.path.exists(self.__getMountPoint()):
+ os.mkdir(self.__getMountPoint())
+ if not os.path.exists(self.__getMountPoint()):
+ errorMsg = "Could not create mountpoint (%s)" % (self.__getMountPoint(), )
+ self.log.error(errorMsg)
+ raise CBMountError(errorMsg)
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "mount",
+ self.device,
+ self.__getMountPoint()])
+ proc.wait()
+ if proc.returncode != 0:
+ errorMsg = "Could not mount the filesystem: %s" % (proc.stderr.read().strip(), )
+ self.log.warn(errorMsg)
+ raise CBMountError(errorMsg)
+ devnull.close()
+
+
+ def __umountPlain(self):
+ "umount a plaintext partition"
+ devnull = None
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ if self.isMounted():
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "umount",
+ self.__getMountPoint()])
+ proc.wait()
+ if proc.returncode != 0:
+ errorMsg = "Could not umount the filesystem: %s" % (proc.stderr.read().strip(), )
+ self.log.warn(errorMsg)
+ raise CBUmountError(errorMsg)
+ devnull.close()
+
+
+ def __createPlain(self):
+ "make a plaintext partition"
+ if self.isMounted():
+ raise CBVolumeIsActive("deactivate the partition before filesystem initialization")
+ devnull = None
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["mkfs-data"],
+ self.device])
+ proc.wait()
+ if proc.returncode != 0:
+ errorMsg = "Could not create the filesystem: %s" % (proc.stderr.read().strip(), )
+ self.log.error(errorMsg)
+ raise CBCreateError(errorMsg)
+ devnull.close()
+
+
+ def __createLuks(self, password):
+ "make a luks partition"
+ if not password:
+ raise CBInvalidPassword("no password supplied for new luks mapping")
+ if self.isMounted():
+ raise CBVolumeIsActive("deactivate the partition before filesystem initialization")
+ devnull = None
+ try:
+ devnull = open(os.devnull, "w")
+ except IOError:
+ self.log.warn("Could not open %s" % (os.devnull, ))
+ "remove any potential open luks mapping"
+ self.__umountLuks()
+ "create the luks header"
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = subprocess.PIPE,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "cryptsetup",
+ "luksFormat",
+ self.device,
+ "--batch-mode",
+ "--cipher", self.cbox.prefs["Main"]["DefaultCipher"],
+ "--iter-time", "2000"])
+ proc.stdin.write(password)
+ (output, errout) = proc.communicate()
+ if proc.returncode != 0:
+ errorMsg = "Could not create the luks header: %s" % (errout.strip(), )
+ self.log.error(errorMsg)
+ raise CBCreateError(errorMsg)
+ "open the luks container for mkfs"
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = subprocess.PIPE,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["super"],
+ self.cbox.prefs["Programs"]["CryptoBoxRootActions"],
+ "cryptsetup",
+ "luksOpen",
+ self.device,
+ self.name,
+ "--batch-mode"])
+ proc.stdin.write(password)
+ (output, errout) = proc.communicate()
+ if proc.returncode != 0:
+ errorMsg = "Could not open the new luks mapping: %s" % (errout.strip(), )
+ self.log.error(errorMsg)
+ raise CBCreateError(errorMsg)
+ "make the filesystem"
+ proc = subprocess.Popen(
+ shell = False,
+ stdin = None,
+ stdout = devnull,
+ stderr = subprocess.PIPE,
+ args = [
+ self.cbox.prefs["Programs"]["mkfs-data"],
+ os.path.join(self.__dmDir, self.name)])
+ proc.wait()
+ "remove the mapping - for every exit status"
+ self.__umountLuks()
+ if proc.returncode != 0:
+ errorMsg = "Could not create the filesystem: %s" % (proc.stderr.read().strip(), )
+ self.log.error(errorMsg)
+ "remove the luks mapping"
+ raise CBCreateError(errorMsg)
+ devnull.close()
+
+
+ def __cleanMountDirs(self):
+ """ remove all unnecessary subdirs of the mount parent directory
+ this should be called for every (u)mount """
+ subdirs = os.listdir(self.cbox.prefs["Locations"]["MountParentDir"])
+ for dir in subdirs:
+ abs_dir = os.path.join(self.cbox.prefs["Locations"]["MountParentDir"], dir)
+ if (not os.path.islink(abs_dir)) and os.path.isdir(abs_dir) and (not os.path.ismount(abs_dir)):
+ os.rmdir(abs_dir)
+
+
diff --git a/bin/CryptoBoxExceptions.py b/bin/CryptoBoxExceptions.py
new file mode 100644
index 0000000..743bfcd
--- /dev/null
+++ b/bin/CryptoBoxExceptions.py
@@ -0,0 +1,107 @@
+"""
+exceptions of the cryptobox package
+"""
+
+
+class CryptoBoxError(Exception):
+ """base class for exceptions of the cryptobox"""
+ pass
+
+
+class CBConfigError(CryptoBoxError):
+ """any kind of error related to the configuration of a cryptobox"""
+ pass
+
+
+class CBConfigUnavailableError(CBConfigError):
+ """config file/input was not available at all"""
+
+ def __init__(self, source=None):
+ self.source = source
+
+ def __str__(self):
+ if self.source:
+ return "failed to access the configuration of the cryptobox: %s" % self.source
+ else:
+ return "failed to access the configuration of the cryptobox"
+
+
+class CBConfigUndefinedError(CBConfigError):
+ """a specific configuration setting was not defined"""
+
+ def __init__(self, section, name=None):
+ self.section = section
+ self.name = name
+
+ def __str__(self):
+ # is it a settings or a section?
+ if self.name:
+ # setting
+ return "undefined configuration setting: [%s]->%s - please check your configuration file" % (self.section, self.name)
+ else:
+ # section
+ return "undefined configuration section: [%s] - please check your configuration file" % (self.section, )
+
+
+
+class CBConfigInvalidValueError(CBConfigError):
+ """a configuration setting was invalid somehow"""
+
+ def __init__(self, section, name, value, reason):
+ self.section = section
+ self.name = name
+ self.value = value
+ self.reason = reason
+
+ def __str__(self):
+ return "invalid configuration setting [%s]->%s (%s): %s" % (self.section, self.name, self.value, self.reason)
+
+
+class CBEnvironmentError(CryptoBoxError):
+ """some part of the environment of the cryptobox is broken
+ e.g. the wrong version of a required program
+ """
+
+ def __init__(self, desc):
+ self.desc = desc
+
+ def __str__(self):
+ return "misconfiguration detected: %s" % self.desc
+
+
+class CBContainerError(CryptoBoxError):
+ """any error raised while manipulating a cryptobox container"""
+
+ def __init__(self, desc):
+ self.desc = desc
+
+ def __str__(self):
+ return self.desc
+
+class CBCreateError(CBContainerError):
+ pass
+
+class CBVolumeIsActive(CBContainerError):
+ pass
+
+class CBInvalidName(CBContainerError):
+ pass
+
+class CBNameActivelyUsed(CBContainerError):
+ pass
+
+class CBInvalidType(CBContainerError):
+ pass
+
+class CBInvalidPassword(CBContainerError):
+ pass
+
+class CBChangePasswordError(CBContainerError):
+ pass
+
+class CBMountError(CBContainerError):
+ pass
+
+class CBUmountError(CBContainerError):
+ pass
+
diff --git a/bin/CryptoBoxPlugin.py b/bin/CryptoBoxPlugin.py
new file mode 100644
index 0000000..abb3f0c
--- /dev/null
+++ b/bin/CryptoBoxPlugin.py
@@ -0,0 +1,165 @@
+# $Id$
+#
+# parent class for all plugins of the CryptoBox
+#
+
+import os
+import cherrypy
+
+
+class CryptoBoxPlugin:
+
+ ## default capability is "system" - the other supported capability is: "volume"
+ pluginCapabilities = [ "system" ]
+
+ ## does this plugin require admin authentification?
+ requestAuth = False
+
+ ## is this plugin enabled by default?
+ enabled = True
+
+ ## default rank (0..100) of the plugin in listings (lower value means higher priority)
+ rank = 80
+
+
+ ## default icon of this plugin (relative path)
+ defaultIconFileName = "plugin_icon.png"
+
+
+ def __init__(self, cbox, pluginDir):
+ self.cbox = cbox
+ self.hdf = {}
+ self.pluginDir = pluginDir
+ self.hdf_prefix = "Data.Plugins.%s." % self.getName()
+
+
+ def doAction(self, **args):
+ """override doAction with your plugin code"""
+ raise Exception, "undefined action handler ('doAction') in plugin '%'" % self.getName()
+
+
+ def getStatus(self):
+ """you should override this, to supply useful state information"""
+ raise Exception, "undefined state handler ('getStatus') in plugin '%'" % self.getName()
+
+
+ def getName(self):
+ """the name of the python file (module) should be the name of the plugin"""
+ return self.__module__
+
+
+ @cherrypy.expose
+ def getIcon(self, image=None, **kargs):
+ """return the image data of the icon of the plugin
+
+ the parameter 'image' may be used for alternative image locations (relative
+ to the directory of the plugin)
+ '**kargs' is necessary, as a 'weblang' attribute may be specified (and ignored)"""
+ import cherrypy, re
+ if (image is None): # or (re.search(u'[\w-\.]', image)):
+ plugin_icon_file = os.path.join(self.pluginDir, self.defaultIconFileName)
+ else:
+ plugin_icon_file = os.path.join(self.pluginDir, image)
+ if not os.access(plugin_icon_file, os.R_OK):
+ plugin_icon_file = os.path.join(self.cbox.prefs["Locations"]["PluginDir"], "plugin_icon_unknown.png")
+ return cherrypy.lib.cptools.serveFile(plugin_icon_file)
+
+
+ def getTemplateFileName(self, template_name):
+ """return the filename of the template, if it is part of this plugin
+
+ use this function to check, if the plugin provides the specified template
+ """
+ result_file = os.path.join(self.pluginDir, template_name + ".cs")
+ if os.access(result_file, os.R_OK) and os.path.isfile(result_file):
+ return result_file
+ else:
+ return None
+
+
+ def getLanguageData(self, lang="en"):
+ try:
+ import neo_cgi, neo_util
+ except:
+ raise CryptoBoxExceptions.CBEnvironmentError("couldn't import 'neo_*'! Try 'apt-get install python-clearsilver'.")
+ langdir = os.path.abspath(os.path.join(self.pluginDir, "lang"))
+ ## first: the default language file (english)
+ langFiles = [os.path.join(langdir, "en.hdf")]
+ ## maybe we have to load a translation afterwards
+ if lang != "en":
+ langFiles.append(os.path.join(langdir, lang + ".hdf"))
+ file_found = False
+ lang_hdf = neo_util.HDF()
+ for langFile in langFiles:
+ if os.access(langFile, os.R_OK):
+ lang_hdf.readFile(langFile)
+ file_found = True
+ if file_found:
+ return lang_hdf
+ else:
+ self.cbox.log.debug("Couldn't find a valid plugin language file (%s)" % str(langFiles))
+ return None
+
+
+ def loadDataSet(self, hdf):
+ for (key, value) in self.hdf.items():
+ hdf.setValue(key, str(value))
+
+
+ def isAuthRequired(self):
+ """check if this plugin requires authentication
+ first step: check plugin configuration
+ second step: check default value of plugin"""
+ try:
+ if self.cbox.prefs.pluginConf[self.getName()]["requestAuth"] is None:
+ return self.requestAuth
+ if self.cbox.prefs.pluginConf[self.getName()]["requestAuth"]:
+ return True
+ else:
+ return False
+ except KeyError:
+ return self.requestAuth
+
+
+ def isEnabled(self):
+ """check if this plugin is enabled
+ first step: check plugin configuration
+ second step: check default value of plugin"""
+ import types
+ try:
+ if self.cbox.prefs.pluginConf[self.getName()]["enabled"] is None:
+ return self.enabled
+ if self.cbox.prefs.pluginConf[self.getName()]["enabled"]:
+ return True
+ else:
+ return False
+ except KeyError:
+ return self.enabled
+
+
+ def getRank(self):
+ """check the rank of this plugin
+ first step: check plugin configuration
+ second step: check default value of plugin"""
+ try:
+ if self.cbox.prefs.pluginConf[self.getName()]["rank"] is None:
+ return self.rank
+ return int(self.cbox.prefs.pluginConf[self.getName()]["rank"])
+ except KeyError, TypeError:
+ return self.rank
+
+
+ def getTestClass(self):
+ import imp
+ pl_file = os.path.join(self.pluginDir, "unittests.py")
+ if os.access(pl_file, os.R_OK) and os.path.isfile(pl_file):
+ try:
+ return getattr(imp.load_source("unittests_%s" % self.getName(), pl_file), "unittests")
+ except AttributeError:
+ pass
+ try:
+ self.cbox.log.info("could not load unittests for plugin: %s" % self.getName())
+ except AttributeError:
+ pass
+ return None
+
diff --git a/bin/CryptoBoxRootActions.py b/bin/CryptoBoxRootActions.py
new file mode 100755
index 0000000..b92ae3c
--- /dev/null
+++ b/bin/CryptoBoxRootActions.py
@@ -0,0 +1,386 @@
+#!/usr/bin/env python2.4
+
+"""module for executing the programs, that need root privileges
+
+Syntax:
+ - program
+ - device
+ - [action]
+ - [action args]
+
+this script will always return with an exitcode 0 (true), if "check" is the only argument
+"""
+
+import os
+import sys
+import subprocess
+import pwd
+import grp
+import types
+
+allowedProgs = {
+ "sfdisk": "/sbin/sfdisk",
+ "cryptsetup": "/sbin/cryptsetup",
+ "mount": "/bin/mount",
+ "umount": "/bin/umount",
+ "blkid": "/sbin/blkid",
+ }
+
+
+DEV_TYPES = { "pipe":1, "char":2, "dir":4, "block":6, "file":8, "link":10, "socket":12}
+
+
+def checkIfPluginIsSafe(plugin):
+ """check if the plugin and its parents are only writeable for root"""
+ #FIXME: for now we may skip this test - but users will not like it this way :)
+ return True
+ props = os.stat(plugin)
+ ## check if it is owned by non-root
+ if props.st_uid != 0: return False
+ ## check group-write permission if gid is not zero
+ if (props.st_gid != 0) and (props.st_mode % 32 / 16 > 0): return False
+ ## check if it is world-writeable
+ if props.st_mode % 4 / 2 > 0: return False
+ ## are we at root-level (directory-wise)? If yes, then we are ok ...
+ if plugin == os.path.sep: return True
+ ## check if the parent directory is ok - recursively :)
+ return checkIfPluginIsSafe(os.path.dirname(os.path.abspath(plugin)))
+
+
+def checkIfPluginIsValid(plugin):
+ import imp
+ try:
+ x = imp.load_source("cbox_plugin",plugin)
+ except Exception:
+ return False
+ try:
+ if getattr(x, "PLUGIN_TYPE") == "cryptobox":
+ return True
+ else:
+ return False
+ except Exception:
+ return False
+
+
+def call_plugin(args):
+ """check if the plugin may be called - and do it finally ..."""
+ plugin = os.path.abspath(args[0])
+ del args[0]
+ ## check existence and excutability
+ if not os.access(plugin, os.X_OK):
+ raise Exception, "could not find executable plugin (%s)" % plugin
+ ## check if the plugin (and its parents) are only writeable for root
+ if not checkIfPluginIsSafe(plugin):
+ raise Exception, "the plugin (%s) was not safe - check its (and its parents') permissions" % plugin
+ ## check if the plugin is a python program, that is marked as a cryptobox plugin
+ if not checkIfPluginIsValid(plugin):
+ raise Exception, "the plugin (%s) is not a correctly marked python script" % plugin
+ args.insert(0,plugin)
+ proc = subprocess.Popen(
+ shell = False,
+ args = args)
+ proc.wait()
+ return proc.returncode == 0
+
+
+def isWriteable(device, force_dev_type=None):
+ """check if the calling user (not root!) has write access to the device/file
+
+ the real (not the effictive) user id is used for the check
+ additionally the permissions of the default groups of the real uid are checked
+ this check works nicely together with "super", as it changes (by default) only
+ the effective uid (not the real uid)
+ """
+ # first check, if the device/file exists
+ if not os.path.exists(device):
+ return False
+ # check the type of the device - if necessary
+ if not force_dev_type is None:
+ dev_type = os.stat(device).st_mode % 65536 / 4096
+ if dev_type != force_dev_type: return False
+ # retrieve the information for the real user id
+ (trustUserName, trustUID, groupsOfTrustUser) = getUserInfo(os.getuid())
+ # set the default groups of the caller for the check (restore them later)
+ savedGroups = os.getgroups()
+ os.setgroups(groupsOfTrustUser)
+ # check permissions
+ result = os.access(device, os.W_OK) and os.access(device, os.R_OK)
+ # reset the groups of this process
+ os.setgroups(savedGroups)
+ return result
+
+
+def run_cryptsetup(args):
+ """execute cryptsetup as root
+
+ @args: list of arguments - they will be treated accordingly to the first element
+ of this list (the action)"""
+ if not args: raise "WrongArguments", "no action for cryptsetup supplied"
+ if type(args) != types.ListType: raise "WrongArguments", "invalid arguments supplied: %s" % (args, )
+ try:
+ action = args[0]
+ del args[0]
+ device = None
+ cmd_args = []
+ if action == "luksFormat":
+ device = args[0]; del args[0]
+ cmd_args.append(action)
+ cmd_args.append(device)
+ elif action == "luksUUID":
+ device = args[0]; del args[0]
+ cmd_args.append(action)
+ cmd_args.append(device)
+ elif action == "luksOpen":
+ if len(args) < 2: raise "WrongArguments", "missing arguments"
+ device = args[0]; del args[0]
+ destination = args[0]; del args[0]
+ cmd_args.append(action)
+ cmd_args.append(device)
+ cmd_args.append(destination)
+ elif action == "luksClose":
+ if len(args) < 1: raise "WrongArguments", "missing arguments"
+ destination = args[0]; del args[0]
+ # maybe add a check for the mapped device's permissions?
+ # dmsetup deps self.device
+ cmd_args.append(action)
+ cmd_args.append(destination)
+ elif action == "luksAddKey":
+ device = args[0]; del args[0]
+ cmd_args.append(action)
+ cmd_args.append(device)
+ elif action == "luksDelKey":
+ if len(cs_args) < 2: raise "WrongArguments", "missing arguments"
+ device = args[0]; del args[0]
+ cmd_args.insert(-1, action)
+ cmd_args.insert(-1, device)
+ elif action == "isLuks":
+ device = args[0]; del args[0]
+ cmd_args.append(action)
+ cmd_args.append(device)
+ else: raise "WrongArguments", "invalid action supplied: %s" % (action, )
+ # check if a device was defined - and check it
+ if (not device is None) and (not isWriteable(device, DEV_TYPES["block"])):
+ raise "WrongArguments", "%s is not a writeable block device" % (device, )
+ cs_args = [allowedProgs["cryptsetup"]]
+ cs_args.extend(args)
+ cs_args.extend(cmd_args)
+ except (TypeError, IndexError):
+ raise "WrongArguments", "invalid arguments supplied: %s" % (args, )
+ # execute cryptsetup with the given parameters
+ proc = subprocess.Popen(
+ shell = False,
+ args = cs_args)
+ proc.wait()
+ ## chown the devmapper block device to the cryptobox user
+ if (proc.returncode == 0) and (action == "luksOpen"):
+ os.chown(os.path.join(os.path.sep, "dev", "mapper", destination), os.getuid(), os.getgid())
+ return proc.returncode == 0
+
+
+def run_sfdisk(args):
+ """execute sfdisk for partitioning
+
+ not implemented yet"""
+ print "ok - you are free to call sfdisk ..."
+ print " not yet implemented ..."
+ return True
+
+
+def getFSType(device):
+ """get the filesystem type of a device"""
+ proc = subprocess.Popen(
+ shell = False,
+ stdout = subprocess.PIPE,
+ args = [ allowedProgs["blkid"],
+ "-s", "TYPE",
+ "-o", "value",
+ "-c", os.devnull,
+ "-w", os.devnull,
+ device])
+ (stdout, stderr) = proc.communicate()
+ if proc.returncode != 0:
+ return None
+ return stdout.strip()
+
+
+def run_mount(args):
+ """execute mount
+ """
+ if not args: raise "WrongArguments", "no destination for mount supplied"
+ if type(args) != types.ListType: raise "WrongArguments", "invalid arguments supplied: %s" % (args, )
+ try:
+ device = args[0]
+ del args[0]
+ destination = args[0]
+ del args[0]
+ # check permissions for the device
+ if not isWriteable(device, DEV_TYPES["block"]):
+ raise "WrongArguments", "%s is not a writeable block device" % (device, )
+ ## check permissions for the mountpoint
+ if not isWriteable(destination, DEV_TYPES["dir"]):
+ raise "WrongArguments", "the mountpoint (%s) is not writeable" % (destination, )
+ # check for additional (not allowed) arguments
+ if len(args) != 0:
+ raise "WrongArguments", "too many arguments for 'mount': %s" % (args, )
+ except TypeError:
+ raise "WrongArguments", "invalid arguments supplied: %s" % (args, )
+ # execute mount with the given parameters
+ # first overwrite the real uid, as 'mount' wants this to be zero (root)
+ savedUID = os.getuid()
+ os.setuid(os.geteuid())
+ ## we have to change the permissions of the mounted directory - otherwise it will
+ ## not be writeable for the cryptobox user
+ ## for 'vfat' we have to do this during mount
+ ## for ext2/3 we have to do it afterward
+ ## first: get the user/group of the target
+ (trustUserName, trustUID, groupsOfTrustUser) = getUserInfo(savedUID)
+ trustGID = groupsOfTrustUser[0]
+ fsType = getFSType(device)
+ ## define arguments
+ if fsType == "vfat":
+ ## add the "uid/gid" arguments to the mount call
+ mount_args = [allowedProgs["mount"],
+ "-o", "uid=%d,gid=%d" % (trustUID, trustGID),
+ device,
+ destination]
+ else:
+ ## all other filesystem types will be handled after mount
+ mount_args = [allowedProgs["mount"], device, destination]
+ # execute mount
+ proc = subprocess.Popen(
+ shell = False,
+ args = mount_args)
+ proc.wait()
+ ## return in case of an error
+ if proc.returncode != 0:
+ return False
+ ## for vfat: we are done
+ if fsType == "vfat": return True
+ ## for all other filesystem types: chown the mount directory
+ try:
+ os.chown(destination, trustUID, groupsOfTrustUser[0])
+ except OSError, errMsg:
+ sys.stderr.write("could not chown the mount destination (%s) to the specified user (%d/%d): %s\n" % (destination, trustUID, groupsOfTrustUser[0], errMsg))
+ sys.stderr.write("UID: %d\n" % (os.geteuid(),))
+ return False
+ ## BEWARE: it would be nice, if we could restore the previous uid (not euid) but
+ ## this would also override the euid (see 'man 2 setuid') - any ideas?
+ return True
+
+
+def run_umount(args):
+ """execute mount
+ """
+ if not args: raise "WrongArguments", "no mountpoint for umount supplied"
+ if type(args) != types.ListType: raise "WrongArguments", "invalid arguments supplied"
+ try:
+ destination = args[0]
+ del args[0]
+ # check permissions for the destination
+ if not isWriteable(os.path.dirname(destination), DEV_TYPES["dir"]):
+ raise "WrongArguments", "the parent of the mountpoint (%s) is not writeable" % (destination, )
+ if len(args) != 0: raise "WrongArguments", "umount does not allow arguments"
+ except TypeError:
+ raise "WrongArguments", "invalid arguments supplied"
+ # execute umount with the given parameters
+ # first overwrite the real uid, as 'umount' wants this to be zero (root)
+ savedUID = os.getuid()
+ os.setuid(os.geteuid())
+ # execute umount (with the parameter '-l' - lazy umount)
+ proc = subprocess.Popen(
+ shell = False,
+ args = [allowedProgs["umount"], "-l", destination])
+ proc.wait()
+ # restore previous real uid
+ os.setuid(savedUID)
+ return proc.returncode == 0
+
+
+def getUserInfo(user):
+ """return information about the specified user
+
+ @user: (uid or name)
+ @return: tuple of (name, uid, (groups))
+ """
+ if user is None: raise "KeyError", "no user supplied"
+ # first check, if 'user' contains an id - then check for a name
+ try:
+ userinfo = pwd.getpwuid(user)
+ except TypeError:
+ # if a KeyError is raised again, then the supplied user was invalid
+ userinfo = pwd.getpwnam(user)
+ u_groups =[one_group.gr_gid
+ for one_group in grp.getgrall()
+ if userinfo.pw_name in one_group.gr_mem]
+ if not userinfo.pw_gid in u_groups: u_groups.append(userinfo.pw_gid)
+ return (userinfo.pw_name, userinfo.pw_uid, u_groups)
+
+
+# **************** main **********************
+
+# prevent import
+if __name__ == "__main__":
+
+ # do we have root privileges (effective uid is zero)?
+ if os.geteuid() != 0:
+ sys.stderr.write("the effective uid is not zero - you should use 'super' to call this script (%s)" % sys.argv[0])
+ sys.exit(100)
+
+ # remove program name
+ args = sys.argv[1:]
+
+ # do not allow to use root permissions (real uid may not be zero)
+ if os.getuid() == 0:
+ sys.stderr.write("the uid of the caller is zero (root) - this is not allowed\n")
+ sys.exit(100)
+
+ # check if there were arguments
+ if (len(args) == 0):
+ sys.stderr.write("No arguments supplied\n")
+ sys.exit(100)
+
+ # did the user call the "check" action?
+ if (len(args) == 1) and (args[0].lower() == "check"):
+ # exit silently
+ sys.exit(0)
+
+ if args[0].lower() == "plugin":
+ del args[0]
+ try:
+ isOK = call_plugin(args)
+ except Exception, errMsg:
+ sys.stderr.write("Execution of plugin failed: %s\n" % errMsg)
+ sys.exit(100)
+ if isOK:
+ sys.exit(0)
+ else:
+ sys.exit(1)
+
+ # check parameters count
+ if len(args) < 2:
+ sys.stderr.write("Not enough arguments supplied (%s)!\n" % " ".join(args))
+ sys.exit(100)
+
+ progRequest = args[0]
+ del args[0]
+
+ if not progRequest in allowedProgs.keys():
+ sys.stderr.write("Invalid program requested: %s\n" % progRequest)
+ sys.exit(100)
+
+ if progRequest == "cryptsetup": runner = run_cryptsetup
+ elif progRequest == "sfdisk": runner = run_sfdisk
+ elif progRequest == "mount": runner = run_mount
+ elif progRequest == "umount": runner = run_umount
+ else:
+ sys.stderr.write("The interface for this program (%s) is not yet implemented!\n" % progRequest)
+ sys.exit(100)
+ try:
+ if runner(args):
+ sys.exit(0)
+ else:
+ sys.exit(1)
+ except "WrongArguments", errstr:
+ sys.stderr.write("Execution failed: %s\n" % errstr)
+ sys.exit(100)
+
diff --git a/bin/CryptoBoxSettings.py b/bin/CryptoBoxSettings.py
new file mode 100644
index 0000000..73ca9a6
--- /dev/null
+++ b/bin/CryptoBoxSettings.py
@@ -0,0 +1,481 @@
+import logging
+try:
+ import validate
+except:
+ raise CryptoBoxExceptions.CBEnvironmentError("couldn't import 'validate'! Try 'apt-get install python-formencode'.")
+import os
+import CryptoBoxExceptions
+import subprocess
+try:
+ import configobj ## needed for reading and writing of the config file
+except:
+ raise CryptoBoxExceptions.CBEnvironmentError("couldn't import 'configobj'! Try 'apt-get install python-configobj'.")
+
+
+
+class CryptoBoxSettings:
+
+ CONF_LOCATIONS = [
+ "./cryptobox.conf",
+ "~/.cryptobox.conf",
+ "/etc/cryptobox/cryptobox.conf"]
+
+ NAMEDB_FILE = "cryptobox_names.db"
+ PLUGINCONF_FILE = "cryptobox_plugins.conf"
+ USERDB_FILE = "cryptobox_users.db"
+
+
+ def __init__(self, config_file=None):
+ self.log = logging.getLogger("CryptoBox")
+ config_file = self.__getConfigFileName(config_file)
+ self.log.info("loading config file: %s" % config_file)
+ self.prefs = self.__getPreferences(config_file)
+ self.__validateConfig()
+ self.__configureLogHandler()
+ self.__checkUnknownPreferences()
+ self.preparePartition()
+ self.nameDB = self.__getNameDatabase()
+ self.pluginConf = self.__getPluginConfig()
+ self.userDB = self.__getUserDB()
+ self.misc_files = self.__getMiscFiles()
+
+
+ def write(self):
+ """
+ write all local setting files including the content of the "misc" subdirectory
+ """
+ ok = True
+ try:
+ self.nameDB.write()
+ except IOError:
+ self.log.warn("could not save the name database")
+ ok = False
+ try:
+ self.pluginConf.write()
+ except IOError:
+ self.log.warn("could not save the plugin configuration")
+ ok = False
+ try:
+ self.userDB.write()
+ except IOError:
+ self.log.warn("could not save the user database")
+ ok = False
+ for misc_file in self.misc_files:
+ if not misc_file.save():
+ self.log.warn("could not save a misc setting file (%s)" % misc_file.filename)
+ ok = False
+ return ok
+
+
+ def requiresPartition(self):
+ return bool(self.prefs["Main"]["UseConfigPartition"])
+
+
+ def getActivePartition(self):
+ settings_dir = self.prefs["Locations"]["SettingsDir"]
+ if not os.path.ismount(settings_dir): return None
+ for line in file("/proc/mounts"):
+ fields = line.split(" ")
+ mount_dir = fields[1]
+ try:
+ if os.path.samefile(mount_dir, settings_dir): return fields[0]
+ except OSError:
+ pass
+ ## no matching entry found
+ return None
+
+
+ def mountPartition(self):
+ self.log.debug("trying to mount configuration partition")
+ if not self.requiresPartition():
+ self.log.warn("mountConfigPartition: configuration partition is not required - mounting anyway")
+ if self.getActivePartition():
+ self.log.warn("mountConfigPartition: configuration partition already mounted - not mounting again")
+ return False
+ confPartitions = self.getAvailablePartitions()
+ if not confPartitions:
+ self.log.error("no configuration partitions found - you have to create it first")
+ return False
+ partition = confPartitions[0]
+ proc = subprocess.Popen(
+ shell = False,
+ stdout = subprocess.PIPE,
+ stderr = subprocess.PIPE,
+ args = [
+ self.prefs["Programs"]["super"],
+ self.prefs["Programs"]["CryptoBoxRootActions"],
+ "mount",
+ partition,
+ self.prefs["Locations"]["SettingsDir"]])
+ (stdout, stderr) = proc.communicate()
+ if proc.returncode != 0:
+ self.log.error("failed to mount the configuration partition: %s" % partition)
+ self.log.error("output of mount: %s" % (stderr,))
+ return False
+ self.log.info("configuration partition mounted: %s" % partition)
+ return True
+
+
+ def umountPartition(self):
+ if not self.getActivePartition():
+ self.log.warn("umountConfigPartition: no configuration partition mounted")
+ return False
+ proc = subprocess.Popen(
+ shell = False,
+ stdout = subprocess.PIPE,
+ stderr = subprocess.PIPE,
+ args = [
+ self.prefs["Programs"]["super"],
+ self.prefs["Programs"]["CryptoBoxRootActions"],
+ "umount",
+ self.prefs["Locations"]["SettingsDir"]])
+ (stdout, stderr) = proc.communicate()
+ if proc.returncode != 0:
+ self.log.error("failed to unmount the configuration partition")
+ self.log.error("output of mount: %s" % (stderr,))
+ return False
+ self.log.info("configuration partition unmounted")
+ return True
+
+
+ def getAvailablePartitions(self):
+ """returns a sequence of found config partitions"""
+ proc = subprocess.Popen(
+ shell = False,
+ stdout = subprocess.PIPE,
+ args = [
+ self.prefs["Programs"]["blkid"],
+ "-c", os.path.devnull,
+ "-t", "LABEL=%s" % self.prefs["Main"]["ConfigVolumeLabel"] ])
+ (output, error) = proc.communicate()
+ if output:
+ return [e.strip().split(":",1)[0] for e in output.splitlines()]
+ else:
+ return []
+
+
+ def preparePartition(self):
+ if self.requiresPartition() and not self.getActivePartition():
+ self.mountPartition()
+
+
+ def __getitem__(self, key):
+ """redirect all requests to the 'prefs' attribute"""
+ return self.prefs[key]
+
+
+ def __getPreferences(self, config_file):
+ import StringIO
+ config_rules = StringIO.StringIO(self.validation_spec)
+ try:
+ prefs = configobj.ConfigObj(config_file, configspec=config_rules)
+ if prefs:
+ self.log.info("found config: %s" % prefs.items())
+ else:
+ raise CryptoBoxExceptions.CBConfigUnavailableError("failed to load the config file: %s" % config_file)
+ except IOError:
+ raise CryptoBoxExceptions.CBConfigUnavailableError("unable to open the config file: %s" % config_file)
+ return prefs
+
+
+ def __validateConfig(self):
+ result = self.prefs.validate(CryptoBoxSettingsValidator(), preserve_errors=True)
+ error_list = configobj.flatten_errors(self.prefs, result)
+ if not error_list: return
+ errorMsgs = []
+ for sections, key, text in error_list:
+ section_name = "->".join(sections)
+ if not text:
+ errorMsg = "undefined configuration value (%s) in section '%s'" % (key, section_name)
+ else:
+ errorMsg = "invalid configuration value (%s) in section '%s': %s" % (key, section_name, text)
+ errorMsgs.append(errorMsg)
+ raise CryptoBoxExceptions.CBConfigError, "\n".join(errorMsgs)
+
+
+ def __checkUnknownPreferences(self):
+ import StringIO
+ config_rules = configobj.ConfigObj(StringIO.StringIO(self.validation_spec), list_values=False)
+ self.__recursiveConfigSectionCheck("", self.prefs, config_rules)
+
+
+ def __recursiveConfigSectionCheck(self, section_path, section_config, section_rules):
+ """should be called by '__checkUnknownPreferences' for every section
+ sends a warning message to the logger for every undefined (see validation_spec)
+ configuration setting
+ """
+ for e in section_config.keys():
+ element_path = section_path + e
+ if e in section_rules.keys():
+ if isinstance(section_config[e], configobj.Section):
+ if isinstance(section_rules[e], configobj.Section):
+ self.__recursiveConfigSectionCheck(element_path + "->", section_config[e], section_rules[e])
+ else:
+ self.log.warn("configuration setting should be a value instead of a section name: %s" % element_path)
+ else:
+ if not isinstance(section_rules[e], configobj.Section):
+ pass # good - the setting is valid
+ else:
+ self.log.warn("configuration setting should be a section name instead of a value: %s" % element_path)
+ else:
+ self.log.warn("unknown configuration setting: %s" % element_path)
+
+
+ def __getNameDatabase(self):
+ try:
+ try:
+ nameDB_file = os.path.join(self.prefs["Locations"]["SettingsDir"], self.NAMEDB_FILE)
+ except KeyError:
+ raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "SettingsDir")
+ except SyntaxError:
+ raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "SettingsDir", nameDB_file, "failed to interprete the filename of the name database correctly (%s)" % nameDB_file)
+ ## create nameDB if necessary
+ if os.path.exists(nameDB_file):
+ nameDB = configobj.ConfigObj(nameDB_file)
+ else:
+ nameDB = configobj.ConfigObj(nameDB_file, create_empty=True)
+ ## check if nameDB file was created successfully?
+ if not os.path.exists(nameDB_file):
+ raise CryptoBoxExceptions.CBEnvironmentError("failed to create name database (%s)" % nameDB_file)
+ return nameDB
+
+
+ def __getPluginConfig(self):
+ import StringIO
+ plugin_rules = StringIO.StringIO(self.pluginValidationSpec)
+ try:
+ try:
+ pluginConf_file = os.path.join(self.prefs["Locations"]["SettingsDir"], self.PLUGINCONF_FILE)
+ except KeyError:
+ raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "SettingsDir")
+ except SyntaxError:
+ raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "SettingsDir", pluginConf_file, "failed to interprete the filename of the plugin config file correctly (%s)" % pluginConf_file)
+ ## create pluginConf_file if necessary
+ if os.path.exists(pluginConf_file):
+ pluginConf = configobj.ConfigObj(pluginConf_file, configspec=plugin_rules)
+ else:
+ pluginConf = configobj.ConfigObj(pluginConf_file, configspec=plugin_rules, create_empty=True)
+ ## validate and convert values according to the spec
+ pluginConf.validate(validate.Validator())
+ ## check if pluginConf_file file was created successfully?
+ if not os.path.exists(pluginConf_file):
+ raise CryptoBoxExceptions.CBEnvironmentError("failed to create plugin configuration file (%s)" % pluginConf_file)
+ return pluginConf
+
+
+ def __getUserDB(self):
+ import StringIO, sha
+ userDB_rules = StringIO.StringIO(self.userDatabaseSpec)
+ try:
+ try:
+ userDB_file = os.path.join(self.prefs["Locations"]["SettingsDir"], self.USERDB_FILE)
+ except KeyError:
+ raise CryptoBoxExceptions.CBConfigUndefinedError("Locations", "SettingsDir")
+ except SyntaxError:
+ raise CryptoBoxExceptions.CBConfigInvalidValueError("Locations", "SettingsDir", userDB_file, "failed to interprete the filename of the users database file correctly (%s)" % userDB_file)
+ ## create userDB_file if necessary
+ if os.path.exists(userDB_file):
+ userDB = configobj.ConfigObj(userDB_file, configspec=userDB_rules)
+ else:
+ userDB = configobj.ConfigObj(userDB_file, configspec=userDB_rules, create_empty=True)
+ ## validate and set default value for "admin" user
+ userDB.validate(validate.Validator())
+ ## check if userDB file was created successfully?
+ if not os.path.exists(userDB_file):
+ raise CryptoBoxExceptions.CBEnvironmentError("failed to create user database file (%s)" % userDB_file)
+ ## define password hash function - never use "sha" directly - SPOT
+ userDB.getDigest = lambda password: sha.new(password).hexdigest()
+ return userDB
+
+
+ def __getMiscFiles(self):
+ misc_dir = os.path.join(self.prefs["Locations"]["SettingsDir"], "misc")
+ if (not os.path.isdir(misc_dir)) or (not os.access(misc_dir, os.X_OK)):
+ return []
+ return [MiscConfigFile(os.path.join(misc_dir, f), self.log)
+ for f in os.listdir(misc_dir)
+ if os.path.isfile(os.path.join(misc_dir, f))]
+
+
+ def __getConfigFileName(self, config_file):
+ # search for the configuration file
+ import types
+ if config_file is None:
+ # no config file was specified - we will look for it in the ususal locations
+ conf_file_list = [os.path.expanduser(f)
+ for f in self.CONF_LOCATIONS
+ if os.path.exists(os.path.expanduser(f))]
+ if not conf_file_list:
+ # no possible config file found in the usual locations
+ raise CryptoBoxExceptions.CBConfigUnavailableError()
+ config_file = conf_file_list[0]
+ else:
+ # a config file was specified (e.g. via command line)
+ if type(config_file) != types.StringType:
+ raise CryptoBoxExceptions.CBConfigUnavailableError("invalid config file specified: %s" % config_file)
+ if not os.path.exists(config_file):
+ raise CryptoBoxExceptions.CBConfigUnavailableError("could not find the specified configuration file (%s)" % config_file)
+ return config_file
+
+
+ def __configureLogHandler(self):
+ try:
+ log_level = self.prefs["Log"]["Level"].upper()
+ log_level_avail = ["DEBUG", "INFO", "WARN", "ERROR"]
+ if not log_level in log_level_avail:
+ raise TypeError
+ except KeyError:
+ raise CryptoBoxExceptions.CBConfigUndefinedError("Log", "Level")
+ except TypeError:
+ raise CryptoBoxExceptions.CBConfigInvalidValueError("Log", "Level", log_level, "invalid log level: only %s are allowed" % log_level_avail)
+ try:
+ try:
+ log_handler = logging.FileHandler(self.prefs["Log"]["Details"])
+ except KeyError:
+ raise CryptoBoxExceptions.CBConfigUndefinedError("Log", "Details")
+ except IOError:
+ raise CryptoBoxExceptions.CBEnvironmentError("could not create the log file (%s)" % self.prefs["Log"]["Details"])
+ log_handler.setFormatter(logging.Formatter('%(asctime)s CryptoBox %(levelname)s: %(message)s'))
+ cbox_log = logging.getLogger("CryptoBox")
+ ## remove previous handlers
+ cbox_log.handlers = []
+ ## add new one
+ cbox_log.addHandler(log_handler)
+ ## do not call parent's handlers
+ cbox_log.propagate = False
+ ## 'log_level' is a string -> use 'getattr'
+ cbox_log.setLevel(getattr(logging,log_level))
+ ## the logger named "CryptoBox" is configured now
+
+
+ validation_spec = """
+[Main]
+AllowedDevices = list(min=1)
+DefaultVolumePrefix = string(min=1)
+DefaultCipher = string(default="aes-cbc-essiv:sha256")
+ConfigVolumeLabel = string(min=1, default="cbox_config")
+UseConfigPartition = integer(min=0, max=1, default=0)
+
+[Locations]
+MountParentDir = directoryExists(default="/var/cache/cryptobox/mnt")
+SettingsDir = directoryExists(default="/var/cache/cryptobox/settings")
+TemplateDir = directoryExists(default="/usr/share/cryptobox/template")
+LangDir = directoryExists(default="/usr/share/cryptobox/lang")
+DocDir = directoryExists(default="/usr/share/doc/cryptobox/html")
+PluginDir = directoryExists(default="/usr/share/cryptobox/plugins")
+
+[Log]
+Level = option("debug", "info", "warn", "error", default="warn")
+Destination = option("file", default="file")
+Details = string(min=1)
+
+[WebSettings]
+Stylesheet = string(min=1)
+Language = string(min=1, default="en")
+
+[Programs]
+cryptsetup = fileExecutable(default="/sbin/cryptsetup")
+mkfs-data = fileExecutable(default="/sbin/mkfs.ext3")
+blkid = fileExecutable(default="/sbin/blkid")
+blockdev = fileExecutable(default="/sbin/blockdev")
+mount = fileExecutable(default="/bin/mount")
+umount = fileExecutable(default="/bin/umount")
+super = fileExecutable(default="/usr/bin/super")
+# this is the "program" name as defined in /etc/super.tab
+CryptoBoxRootActions = string(min=1)
+ """
+
+ pluginValidationSpec = """
+[__many__]
+enabled = boolean(default=None)
+requestAuth = boolean(default=None)
+rank = integer(default=None)
+ """
+
+ userDatabaseSpec = """
+[admins]
+admin = string(default=d033e22ae348aeb5660fc2140aec35850c4da997)
+ """
+
+
+class CryptoBoxSettingsValidator(validate.Validator):
+
+ def __init__(self):
+ validate.Validator.__init__(self)
+ self.functions["directoryExists"] = self.check_directoryExists
+ self.functions["fileExecutable"] = self.check_fileExecutable
+ self.functions["fileWriteable"] = self.check_fileWriteable
+
+
+ def check_directoryExists(self, value):
+ dir_path = os.path.abspath(value)
+ if not os.path.isdir(dir_path):
+ raise validate.VdtValueError("%s (not found)" % value)
+ if not os.access(dir_path, os.X_OK):
+ raise validate.VdtValueError("%s (access denied)" % value)
+ return dir_path
+
+
+ def check_fileExecutable(self, value):
+ file_path = os.path.abspath(value)
+ if not os.path.isfile(file_path):
+ raise validate.VdtValueError("%s (not found)" % value)
+ if not os.access(file_path, os.X_OK):
+ raise validate.VdtValueError("%s (access denied)" % value)
+ return file_path
+
+
+ def check_fileWriteable(self, value):
+ file_path = os.path.abspath(value)
+ if os.path.isfile(file_path):
+ if not os.access(file_path, os.W_OK):
+ raise validate.VdtValueError("%s (not found)" % value)
+ else:
+ parent_dir = os.path.dirname(file_path)
+ if os.path.isdir(parent_dir) and os.access(parent_dir, os.W_OK):
+ return file_path
+ raise validate.VdtValueError("%s (directory does not exist)" % value)
+ return file_path
+
+
+
+class MiscConfigFile:
+
+ maxSize = 20480
+
+ def __init__(self, filename, logger):
+ self.filename = filename
+ self.log = logger
+ self.load()
+
+
+ def load(self):
+ fd = open(self.filename, "rb")
+ ## limit the maximum size
+ self.content = fd.read(self.maxSize)
+ if fd.tell() == self.maxSize:
+ self.log.warn("file in misc settings directory (%s) is bigger than allowed (%s)" % (self.filename, self.maxSize))
+ fd.close()
+
+
+ def save(self):
+ save_dir = os.path.dirname(self.filename)
+ ## create the directory, if necessary
+ if not os.path.isdir(save_dir):
+ try:
+ os.mkdir(save_dir)
+ except IOError:
+ return False
+ ## save the content of the file
+ try:
+ fd = open(self.filename, "wb")
+ except IOError:
+ return False
+ try:
+ fd.write(self.content)
+ fd.close()
+ return True
+ except IOError:
+ fd.close()
+ return False
+
diff --git a/bin/CryptoBoxTools.py b/bin/CryptoBoxTools.py
new file mode 100644
index 0000000..25ffa03
--- /dev/null
+++ b/bin/CryptoBoxTools.py
@@ -0,0 +1,186 @@
+import logging
+import os
+import re
+
+logger = logging.getLogger("CryptoBox")
+
+
+def getAvailablePartitions():
+ "retrieve a list of all available containers"
+ ret_list = []
+ try:
+ "the following reads all lines of /proc/partitions and adds the mentioned devices"
+ fpart = open("/proc/partitions", "r")
+ try:
+ line = fpart.readline()
+ while line:
+ p_details = line.split()
+ if (len(p_details) == 4):
+ "the following code prevents double entries like /dev/hda and /dev/hda1"
+ (p_major, p_minor, p_size, p_device) = p_details
+ ## ignore lines with: invalid minor/major or extend partitions (size=1)
+ if re.search('^[0-9]*$', p_major) and re.search('^[0-9]*$', p_minor) and (p_size != "1"):
+ p_parent = re.sub('[1-9]?[0-9]$', '', p_device)
+ if p_parent == p_device:
+ if [e for e in ret_list if re.search('^' + p_parent + '[1-9]?[0-9]$', e)]:
+ "major partition - its children are already in the list"
+ pass
+ else:
+ "major partition - but there are no children for now"
+ ret_list.append(p_device)
+ else:
+ "minor partition - remove parent if necessary"
+ if p_parent in ret_list: ret_list.remove(p_parent)
+ ret_list.append(p_device)
+ line = fpart.readline()
+ finally:
+ fpart.close()
+ return map(getAbsoluteDeviceName, ret_list)
+ except IOError:
+ logger.warning("Could not read /proc/partitions")
+ return []
+
+
+def getAbsoluteDeviceName(shortname):
+ """ returns the absolute file name of a device (e.g.: "hda1" -> "/dev/hda1")
+ this does also work for device mapper devices
+ if the result is non-unique, one arbitrary value is returned"""
+ if re.search('^/', shortname): return shortname
+ default = os.path.join("/dev", shortname)
+ if os.path.exists(default): return default
+ result = findMajorMinorOfDevice(shortname)
+ "if no valid major/minor was found -> exit"
+ if not result: return default
+ (major, minor) = result
+ "for device-mapper devices (major == 254) ..."
+ if major == 254:
+ result = findMajorMinorDeviceName("/dev/mapper", major, minor)
+ if result: return result[0]
+ "now check all files in /dev"
+ result = findMajorMinorDeviceName("/dev", major, minor)
+ if result: return result[0]
+ return default
+
+
+def findMajorMinorOfDevice(device):
+ "return the major/minor numbers of a block device"
+ if re.match("/", device) or not os.path.exists(os.path.join(os.path.sep,"sys","block",device)):
+ ## maybe it is an absolute device name
+ if not os.path.exists(device): return None
+ ## okay - it seems to to a device node
+ rdev = os.stat(device).st_rdev
+ return (os.major(rdev), os.minor(rdev))
+ blockdev_info_file = os.path.join(os.path.join(os.path.sep,"sys","block", device), "dev")
+ try:
+ f_blockdev_info = open(blockdev_info_file, "r")
+ blockdev_info = f_blockdev_info.read()
+ f_blockdev_info.close()
+ (str_major, str_minor) = blockdev_info.split(":")
+ "numeric conversion"
+ try:
+ major = int(str_major)
+ minor = int(str_minor)
+ return (major, minor)
+ except ValueError:
+ "unknown device numbers -> stop guessing"
+ return None
+ except IOError:
+ pass
+ return None
+
+
+def findMajorMinorDeviceName(dir, major, minor):
+ "returns the names of devices with the specified major and minor number"
+ collected = []
+ try:
+ subdirs = [os.path.join(dir, e) for e in os.listdir(dir) if (not os.path.islink(os.path.join(dir, e))) and os.path.isdir(os.path.join(dir, e))]
+ "do a recursive call to parse the directory tree"
+ for dirs in subdirs:
+ collected.extend(findMajorMinorDeviceName(dirs, major, minor))
+ "filter all device inodes in this directory"
+ collected.extend([os.path.realpath(os.path.join(dir, e)) for e in os.listdir(dir) if (os.major(os.stat(os.path.join(dir, e)).st_rdev) == major) and (os.minor(os.stat(os.path.join(dir, e)).st_rdev) == minor)])
+ ## remove double entries
+ result = []
+ for e in collected:
+ if e not in result: result.append(e)
+ return result
+ except OSError:
+ return []
+
+
+def getParentBlockDevices():
+ devs = []
+ for line in file("/proc/partitions"):
+ p_details = line.split()
+ ## we expect four values - otherwise continue with next iteration
+ if len(p_details) != 4: continue
+ (p_major, p_minor, p_size, p_device) = p_details
+ ## we expect numeric values in the first two columns
+ if re.search(u'\D',p_major) or re.search(u'\D',p_minor): continue
+ ## now let us check, if it is a (parent) block device or a partition
+ if not os.path.isdir(os.path.join(os.path.sep, "sys", "block", p_device)): continue
+ devs.append(p_device)
+ return map(getAbsoluteDeviceName, devs)
+
+
+def isPartOfBlockDevice(parent, subdevice):
+ """check if the given block device is a parent of 'subdevice'
+ e.g. for checking if a partition belongs to a block device"""
+ try:
+ (par_major, par_minor) = findMajorMinorOfDevice(parent)
+ (sub_major, sub_minor) = findMajorMinorOfDevice(subdevice)
+ except TypeError:
+ ## at least one of these devices did not return a valid major/minor combination
+ return False
+ ## search the entry below '/sys/block' belonging to the parent
+ root = os.path.join(os.path.sep, 'sys', 'block')
+ for bldev in os.listdir(root):
+ blpath = os.path.join(root, bldev, 'dev')
+ if os.access(blpath, os.R_OK):
+ try:
+ if (str(par_major), str(par_minor)) == tuple([e for e in file(blpath)][0].strip().split(":",1)):
+ parent_path = os.path.join(root, bldev)
+ break
+ except IndexError, OSError:
+ pass
+ else:
+ ## no block device with this major/minor combination found below '/sys/block'
+ return False
+ for subbldev in os.listdir(parent_path):
+ subblpath = os.path.join(parent_path, subbldev, "dev")
+ if os.access(subblpath, os.R_OK):
+ try:
+ if (str(sub_major), str(sub_minor)) == tuple([e for e in file(subblpath)][0].strip().split(":",1)):
+ ## the name of the subdevice node is not important - we found it!
+ return True
+ except IndexError, OSError:
+ pass
+ return False
+
+
+def getBlockDeviceSize(device):
+ if not device: return -1
+ try:
+ rdev = os.stat(device).st_rdev
+ except OSError:
+ return -1
+ minor = os.minor(rdev)
+ major = os.major(rdev)
+ for f in file("/proc/partitions"):
+ try:
+ elements = f.split()
+ if len(elements) != 4: continue
+ if (int(elements[0]) == major) and (int(elements[1]) == minor):
+ return int(elements[2])/1024
+ except ValueError:
+ pass
+ return -1
+
+
+def getBlockDeviceSizeHumanly(device):
+ size = getBlockDeviceSize(device)
+ if size > 5120:
+ return "%sGB" % size/1024
+ else:
+ return "%sMB" % size
+
diff --git a/bin/CryptoBoxWebserver.py b/bin/CryptoBoxWebserver.py
new file mode 100755
index 0000000..b841262
--- /dev/null
+++ b/bin/CryptoBoxWebserver.py
@@ -0,0 +1,38 @@
+#!/usr/bin/env python2.4
+import os
+import WebInterfaceSites
+import sys
+
+try:
+ import cherrypy
+except:
+ print "Could not import the cherrypy module! Try 'apt-get install python-cherrypy'."
+ sys.exit(1)
+
+class CryptoBoxWebserver:
+ '''this class starts the cherryp webserver and serves the single sites'''
+
+ def __init__(self):
+ cherrypy.root = WebInterfaceSites.WebInterfaceSites()
+ #expose static content:
+ #I currently have no idea how to cleanly extract the stylesheet path from
+ #the config object without an extra CryptoBox.CryptoBoxProps instance.
+ #perhaps put config handling into a seperate class in CryptoBox.py?
+ #
+ # the following manual mapping is necessary, as we may not use relative
+ # paths in the config file
+ cherrypy.config.configMap.update({
+ "/cryptobox-misc": {
+ "staticFilter.on" : True,
+ "staticFilter.dir": os.path.abspath("../www-data" )}
+ })
+
+ def start(self):
+ # just use this config, when we're started directly
+ cherrypy.config.update(file = "cryptoboxwebserver.conf")
+ cherrypy.server.start()
+
+if __name__ == "__main__":
+ cbw = CryptoBoxWebserver()
+ cbw.start()
+
diff --git a/bin/Plugins.py b/bin/Plugins.py
new file mode 100644
index 0000000..97a7e83
--- /dev/null
+++ b/bin/Plugins.py
@@ -0,0 +1,67 @@
+# $Id$
+
+import imp
+import os
+import logging
+
+
+class PluginManager:
+ """manage available plugins"""
+
+ def __init__(self, cbox, plugin_dirs="."):
+ self.cbox = cbox
+ self.log = logging.getLogger("CryptoBox")
+ if hasattr(plugin_dirs, "__iter__"):
+ self.plugin_dirs = [os.path.abspath(dir) for dir in plugin_dirs]
+ else:
+ self.plugin_dirs = [os.path.abspath(plugin_dirs)]
+ self.pluginList = self.__getAllPlugins()
+
+
+ def getPlugins(self):
+ return self.pluginList[:]
+
+
+ def getPlugin(self, name):
+ for p in self.pluginList[:]:
+ if p.getName() == name:
+ return p
+ return None
+
+
+ def __getAllPlugins(self):
+ list = []
+ for plfile in self.__getPluginFiles():
+ list.append(self.__getPluginClass(os.path.basename(plfile)[:-3]))
+ return list
+
+
+ def __getPluginClass(self, name):
+ for plfile in self.__getPluginFiles():
+ if name == os.path.basename(plfile)[:-3]:
+ try:
+ pl_class = getattr(imp.load_source(name, plfile), name)
+ except AttributeError:
+ return None
+ return pl_class(self.cbox, os.path.dirname(plfile))
+ else:
+ return None
+
+
+ def __getPluginFiles(self):
+ result = []
+ for dir in [os.path.abspath(e) for e in self.plugin_dirs if os.access(e, os.R_OK) and os.path.isdir(e)]:
+ for plname in [f for f in os.listdir(dir)]:
+ pldir = os.path.join(dir, plname)
+ plfile = os.path.join(pldir, plname + ".py")
+ if os.path.isfile(plfile) and os.access(plfile, os.R_OK):
+ result.append(plfile)
+ return result
+
+
+if __name__ == "__main__":
+ x = PluginManager(None, "../plugins")
+ for a in x.getPlugins():
+ if not a is None:
+ print "Plugin: %s" % a.getName()
+
diff --git a/bin/WebInterfaceDataset.py b/bin/WebInterfaceDataset.py
new file mode 100644
index 0000000..7f2de6c
--- /dev/null
+++ b/bin/WebInterfaceDataset.py
@@ -0,0 +1,136 @@
+import os
+import CryptoBoxContainer
+import CryptoBoxTools
+
+## useful constant for some functions
+CONT_TYPES = CryptoBoxContainer.CryptoBoxContainer.Types
+
+class WebInterfaceDataset(dict):
+ """this class contains all data that should be available for the clearsilver
+ templates
+ """
+
+ def __init__(self, cbox, prefs, plugins):
+ self.prefs = prefs
+ self.cbox = cbox
+ self.__setConfigValues()
+ self.plugins = plugins
+ self.setCryptoBoxState()
+ self.setPluginData()
+ self.setContainersState()
+
+
+ def setCryptoBoxState(self):
+ import cherrypy
+ self["Data.Version"] = self.cbox.VERSION
+ langs = self.cbox.getAvailableLanguages()
+ langs.sort()
+ for (index, lang) in enumerate(langs):
+ self.cbox.log.info("language loaded: %s" % lang)
+ self["Data.Languages.%d.name" % index] = lang
+ self["Data.Languages.%d.link" % index] = self.__getLanguageName(lang)
+ try:
+ self["Data.ScriptURL.Prot"] = cherrypy.request.scheme
+ host = cherrypy.request.headers["Host"]
+ self["Data.ScriptURL.Host"] = host.split(":",1)[0]
+ complete_url = "%s://%s" % (self["Data.ScriptURL.Prot"], self["Data.ScriptURL.Host"])
+ try:
+ port = int(host.split(":",1)[1])
+ complete_url += ":%s" % port
+ except (IndexError, ValueError):
+ if cherrypy.request.scheme == "http":
+ port = 80
+ elif cherrypy.request.scheme == "https":
+ port = 443
+ else:
+ ## unknown scheme -> port 0
+ self.cbox.log.info("unknown protocol scheme used: %s" % (cherrypy.request.scheme,))
+ port = 0
+ self["Data.ScriptURL.Port"] = port
+ ## retrieve the relative address of the CGI (or the cherrypy base address)
+ ## remove the last part of the url and add a slash
+ path = "/".join(cherrypy.request.path.split("/")[:-1]) + "/"
+ self["Data.ScriptURL.Path"] = path
+ complete_url += path
+ self["Data.ScriptURL"] = complete_url
+ except AttributeError:
+ self["Data.ScriptURL"] = ""
+
+
+ def setCurrentDiskState(self, device):
+ for container in self.cbox.getContainerList():
+ if container.getDevice() == device:
+ isEncrypted = (container.getType() == CONT_TYPES["luks"]) and 1 or 0
+ isPlain = (container.getType() == CONT_TYPES["plain"]) and 1 or 0
+ isMounted = container.isMounted() and 1 or 0
+ self["Data.CurrentDisk.device"] = container.getDevice()
+ self["Data.CurrentDisk.name"] = container.getName()
+ self["Data.CurrentDisk.encryption"] = isEncrypted
+ self["Data.CurrentDisk.plaintext"] = isPlain
+ self["Data.CurrentDisk.active"] = isMounted
+ self["Data.CurrentDisk.size"] = CryptoBoxTools.getBlockDeviceSizeHumanly(container.getDevice())
+ if isMounted:
+ (size, avail, used) = container.getCapacity()
+ percent = used / size
+ self["Data.CurrentDisk.capacity.used"] = used
+ self["Data.CurrentDisk.capacity.free"] = avail
+ self["Data.CurrentDisk.capacity.size"] = size
+ self["Data.CurrentDisk.capacity.percent"] = percent
+ self["Settings.LinkAttrs.device"] = device
+
+
+ def setContainersState(self):
+ avail_counter = 0
+ active_counter = 0
+ for container in self.cbox.getContainerList():
+ ## useful if the container was changed during an action
+ container.resetObject()
+ isEncrypted = (container.getType() == CONT_TYPES["luks"]) and 1 or 0
+ isPlain = (container.getType() == CONT_TYPES["plain"]) and 1 or 0
+ isMounted = container.isMounted() and 1 or 0
+ self["Data.Disks.%d.device" % avail_counter] = container.getDevice()
+ self["Data.Disks.%d.name" % avail_counter] = container.getName()
+ self["Data.Disks.%d.encryption" % avail_counter] = isEncrypted
+ self["Data.Disks.%d.plaintext" % avail_counter] = isPlain
+ self["Data.Disks.%d.active" % avail_counter] = isMounted
+ self["Data.Disks.%d.size" % avail_counter] = CryptoBoxTools.getBlockDeviceSizeHumanly(container.getDevice())
+ if isMounted: active_counter += 1
+ avail_counter += 1
+ self["Data.activeDisksCount"] = active_counter
+
+
+ def setPluginData(self):
+ for p in self.plugins:
+ lang_data = p.getLanguageData()
+ entryName = "Settings.PluginList." + p.getName()
+ self[entryName] = p.getName()
+ self[entryName + ".Link"] = lang_data.getValue("Link", p.getName())
+ self[entryName + ".Rank"] = p.getRank()
+ self[entryName + ".RequestAuth"] = p.isAuthRequired() and "1" or "0"
+ self[entryName + ".Enabled"] = p.isEnabled() and "1" or "0"
+ for a in p.pluginCapabilities:
+ self[entryName + ".Types." + a] = "1"
+
+
+ def __setConfigValues(self):
+ self["Settings.TemplateDir"] = os.path.abspath(self.prefs["Locations"]["TemplateDir"])
+ self["Settings.LanguageDir"] = os.path.abspath(self.prefs["Locations"]["LangDir"])
+ self["Settings.DocDir"] = os.path.abspath(self.prefs["Locations"]["DocDir"])
+ self["Settings.Stylesheet"] = self.prefs["WebSettings"]["Stylesheet"]
+ self["Settings.Language"] = self.prefs["WebSettings"]["Language"]
+ self["Settings.PluginDir"] = self.prefs["Locations"]["PluginDir"]
+ self["Settings.SettingsDir"] = self.prefs["Locations"]["SettingsDir"]
+
+
+ def __getLanguageName(self, lang):
+ try:
+ import neo_cgi, neo_util, neo_cs
+ except:
+ raise CryptoBoxExceptions.CBEnvironmentError("couldn't import 'neo_*'! Try 'apt-get install python-clearsilver'.")
+ hdf_path = os.path.join(self.prefs["Locations"]["LangDir"], lang + ".hdf")
+ hdf = neo_util.HDF()
+ hdf.readFile(hdf_path)
+ return hdf.getValue("Name",lang)
+
+
+
diff --git a/bin/WebInterfaceSites.py b/bin/WebInterfaceSites.py
new file mode 100755
index 0000000..82405e9
--- /dev/null
+++ b/bin/WebInterfaceSites.py
@@ -0,0 +1,427 @@
+import CryptoBox
+import WebInterfaceDataset
+import re
+import Plugins
+from CryptoBoxExceptions import *
+import cherrypy
+import types
+import os
+
+try:
+ import neo_cgi, neo_util, neo_cs
+except ImportError:
+ errorMsg = "Could not import clearsilver module. Try 'apt-get install python-clearsilver'."
+ self.log.error(errorMsg)
+ sys.stderr.write(errorMsg)
+ raise ImportError, errorMsg
+
+
+
+class PluginIconHandler:
+
+ def __init__(self, plugins):
+ for plugin in plugins.getPlugins():
+ if not plugin: continue
+ plname = plugin.getName()
+ ## expose the getIcon function of this plugin
+ setattr(self, plname, plugin.getIcon)
+
+
+
+class WebInterfaceSites:
+ '''
+ '''
+
+ ## this template is used under strange circumstances
+ defaultTemplate = "empty"
+
+
+ def __init__(self):
+ import logging
+ self.cbox = CryptoBox.CryptoBoxProps()
+ self.log = logging.getLogger("CryptoBox")
+ self.prefs = self.cbox.prefs
+ self.__resetDataset()
+
+
+ def __resetDataset(self):
+ """this method has to be called at the beginning of every "site" action
+ important: only at the beginning of an action (to not loose information)
+ important: for _every_ "site" action (cherrypy is stateful)
+ also take care for the plugins, as they also contain datasets
+ """
+ self.__loadPlugins()
+ self.dataset = WebInterfaceDataset.WebInterfaceDataset(self.cbox, self.prefs, self.pluginList.getPlugins())
+ ## publish plugin icons
+ self.icons = PluginIconHandler(self.pluginList)
+ self.icons.exposed = True
+ ## check, if a configuration partition has become available
+ self.cbox.prefs.preparePartition()
+
+
+ def __loadPlugins(self):
+ self.pluginList = Plugins.PluginManager(self.cbox, self.prefs["Locations"]["PluginDir"])
+ for plugin in self.pluginList.getPlugins():
+ if not plugin: continue
+ plname = plugin.getName()
+ if plugin.isEnabled():
+ self.cbox.log.info("Plugin '%s' loaded" % plname)
+ ## this should be the "easiest" way to expose all plugins as URLs
+ setattr(self, plname, self.return_plugin_action(plugin))
+ setattr(getattr(self, plname), "exposed", True)
+ # TODO: check, if this really works - for now the "stream_response" feature seems to be broken
+ #setattr(getattr(self, plname), "stream_respones", True)
+ else:
+ self.cbox.log.info("Plugin '%s' is disabled" % plname)
+ ## remove the plugin, if it was active before
+ setattr(self, plname, None)
+
+
+ ## this is a function decorator to check authentication
+ ## it has to be defined before any page definition requiring authentification
+ def __requestAuth(self=None):
+ def check_credentials(site):
+ def _inner_wrapper(self, *args, **kargs):
+ import base64
+ ## define a "non-allowed" function
+ user, password = None, None
+ try:
+ resp = cherrypy.request.headers["Authorization"][6:] # ignore "Basic "
+ (user, password) = base64.b64decode(resp).split(":",1)
+ except KeyError:
+ ## no "authorization" header was sent
+ pass
+ except TypeError:
+ ## invalid base64 string
+ pass
+ except AttributeError:
+ ## no cherrypy response header defined
+ pass
+ authDict = self.cbox.prefs.userDB["admins"]
+ if user in authDict.keys():
+ if self.cbox.prefs.userDB.getDigest(password) == authDict[user]:
+ ## ok: return the choosen page
+ self.cbox.log.info("access granted for: %s" % user)
+ return site(self, *args, **kargs)
+ else:
+ self.cbox.log.info("wrong password supplied for: %s" % user)
+ else:
+ self.cbox.log.info("unknown user: %s" % str(user))
+ ## wrong credentials: return "access denied"
+ cherrypy.response.headers["WWW-Authenticate"] = '''Basic realm="CryptoBox"'''
+ cherrypy.response.status = 401
+ return self.__render("access_denied")
+ return _inner_wrapper
+ return check_credentials
+
+
+ ######################################################################
+ ## put real sites down here and don't forget to expose them at the end
+
+
+ @cherrypy.expose
+ def index(self, weblang=""):
+ self.__resetDataset()
+ self.__setWebLang(weblang)
+ self.__checkEnvironment()
+ ## do not forget the language!
+ param_dict = {"weblang":weblang}
+ ## render "disks" plugin by default
+ return self.return_plugin_action(self.pluginList.getPlugin("disks"))(**param_dict)
+
+
+ def return_plugin_action(self, plugin):
+ def handler(self, **args):
+ self.__resetDataset()
+ self.__checkEnvironment()
+ args_orig = dict(args)
+ ## set web interface language
+ try:
+ self.__setWebLang(args["weblang"])
+ del args["weblang"]
+ except KeyError:
+ self.__setWebLang("")
+ ## we always read the "device" setting - otherwise volume-plugin links
+ ## would not work easily (see "volume_props" linking to "format_fs")
+ ## it will get ignored for non-volume plugins
+ try:
+ plugin.device = None
+ if self.__setDevice(args["device"]):
+ plugin.device = args["device"]
+ del args["device"]
+ except KeyError:
+ pass
+ ## check the device argument of volume plugins
+ if "volume" in plugin.pluginCapabilities:
+ ## initialize the dataset of the selected device if necessary
+ if plugin.device:
+ self.dataset.setCurrentDiskState(plugin.device)
+ else:
+ ## invalid (or missing) device setting
+ return self.__render(self.defaultTemplate)
+ ## check if there is a "redirect" setting - this will override the return
+ ## value of the doAction function (e.g. useful for umount-before-format)
+ try:
+ if args["redirect"]:
+ override_nextTemplate = { "plugin":args["redirect"] }
+ if "volume" in plugin.pluginCapabilities:
+ override_nextTemplate["values"] = {"device":plugin.device}
+ del args["redirect"]
+ except KeyError:
+ override_nextTemplate = None
+ ## call the plugin handler
+ nextTemplate = plugin.doAction(**args)
+ ## for 'volume' plugins: reread the dataset of the current disk
+ ## additionally: set the default template for plugins
+ if "volume" in plugin.pluginCapabilities:
+ ## maybe the state of the current volume was changed?
+ self.dataset.setCurrentDiskState(plugin.device)
+ if not nextTemplate: nextTemplate = { "plugin":"volume_mount", "values":{"device":plugin.device}}
+ else:
+ ## maybe a non-volume plugin changed some plugin settings (e.g. plugin_manager)
+ self.dataset.setPluginData()
+ ## update the container hdf-dataset (maybe a plugin changed the state of a container)
+ self.dataset.setContainersState()
+ ## default page for non-volume plugins is the disk selection
+ if not nextTemplate: nextTemplate = { "plugin":"disks", "values":{} }
+ ## was a redirect requested?
+ if override_nextTemplate:
+ nextTemplate = override_nextTemplate
+ ## if another plugins was choosen for 'nextTemplate', then do it!
+ if isinstance(nextTemplate, types.DictType) \
+ and "plugin" in nextTemplate.keys() \
+ and "values" in nextTemplate.keys() \
+ and self.pluginList.getPlugin(nextTemplate["plugin"]):
+ valueDict = dict(nextTemplate["values"])
+ ## force the current weblang attribute - otherwise it gets lost
+ valueDict["weblang"] = self.dataset["Settings.Language"]
+ new_plugin = self.pluginList.getPlugin(nextTemplate["plugin"])
+ return self.return_plugin_action(new_plugin)(**valueDict)
+ ## save the currently active plugin name
+ self.dataset["Data.ActivePlugin"] = plugin.getName()
+ return self.__render(nextTemplate, plugin)
+ ## apply authentication?
+ if plugin.isAuthRequired():
+ return lambda **args: self.__requestAuth()(handler)(self, **args)
+ else:
+ return lambda **args: handler(self, **args)
+
+
+ ## test authentication
+ @cherrypy.expose
+ @__requestAuth
+ def test(self, weblang=""):
+ self.__resetDataset()
+ self.__setWebLang(weblang)
+ self.__checkEnvironment()
+ return "test passed"
+
+
+ @cherrypy.expose
+ def test_stream(self):
+ """just for testing purposes - to check if the "stream_response" feature
+ actually works - for now (September 02006) it does not seem to be ok"""
+ import time
+ yield "neu"
+ for a in range(10):
+ yield "- yes: %d - %s
" % (a, str(time.time()))
+ time.sleep(1)
+ yield "
"
+
+
+
+ ##################### input checker ##########################
+
+ def __checkEnvironment(self):
+ """here we should place all interesting checks to inform the user of problems
+
+ examples are: non-https, readonly-config, ...
+ """
+ ## TODO: maybe add an option "mount"?
+ if self.cbox.prefs.requiresPartition() and not self.cbox.prefs.getActivePartition():
+ self.dataset["Data.EnvironmentWarning"] = "ReadOnlyConfig"
+ # TODO: turn this on soon (add "not") - for now it is annoying
+ if self.__checkHTTPS():
+ self.dataset["Data.EnvironmentWarning"] = "NoSSL"
+
+
+ def __checkHTTPS(self):
+ ## check the request scheme
+ if cherrypy.request.scheme == "https": return True
+ ## check an environment setting - this is quite common behind proxies
+ try:
+ if os.environ["HTTPS"]: return True
+ except KeyError:
+ pass
+ ## check http header TODO (check pound for the name)
+ try:
+ if cherrypy.request.headers["TODO"]: return True
+ except KeyError:
+ pass
+ ## the connection seems to be unencrypted
+ return False
+
+
+ def __setWebLang(self, value):
+ guess = value
+ availLangs = self.cbox.getAvailableLanguages()
+ ## no language specified: check browser language
+ if not guess:
+ guess = self.__getPreferredBrowserLanguage(availLangs)
+ ## no preferred language or invalid language?
+ if not guess \
+ or not guess in availLangs \
+ or re.search(u'\W', guess):
+ ## warn only for invalid languages
+ if not guess is None:
+ self.cbox.log.info("invalid language choosen: %s" % guess)
+ guess = self.prefs["WebSettings"]["Language"]
+ ## maybe the language is still not valid
+ if not guess in availLangs:
+ self.log.warn("the configured language is invalid: %s" % guess)
+ guess = "en"
+ ## maybe there is no english dataset???
+ if not guess in availLangs:
+ self.log.warn("couldn't find the english dataset")
+ guess = availLangs[0]
+ self.dataset["Settings.Language"] = guess
+ ## we only have to save it, if it was specified correctly and explicitly
+ if value == guess:
+ self.dataset["Settings.LinkAttrs.weblang"] = guess
+
+
+ def __getPreferredBrowserLanguage(self, availLangs):
+ """guess the preferred language of the user (as sent by the browser)
+ take the first language, that is part of 'availLangs'
+ """
+ try:
+ pref_lang_header = cherrypy.request.headers["Accept-Language"]
+ except KeyError:
+ ## no language header was specified
+ return None
+ ## this could be a typical 'Accept-Language' header:
+ ## de-de,de;q=0.8,en-us;q=0.5,en;q=0.3
+ regex = re.compile(u"\w+(-\w+)?(;q=[\d\.]+)?$")
+ pref_langs = [e.split(";",1)[0]
+ for e in pref_lang_header.split(",")
+ if regex.match(e)]
+ ## is one of these preferred languages available?
+ for lang in pref_langs:
+ if lang in availLangs: return lang
+ ## we try to be nice: also look for "de" if "de-de" was specified ...
+ for lang in pref_langs:
+ ## use only the first part of the language
+ short_lang = lang.split("-",1)[0]
+ if short_lang in availLangs: return short_lang
+ ## we give up
+ return None
+
+
+ def __setDevice(self, device):
+ if device and re.match(u'[\w /\-]+$', device) and self.cbox.getContainer(device):
+ self.log.debug("select device: %s" % device)
+ return True
+ else:
+ self.log.warn("invalid device: %s" % device)
+ self.dataset["Data.Warning"] = "InvalidDevice"
+ return False
+
+
+ def __checkVolumeName(self, name):
+ if name and re.match(u'[\w \-]+$', name):
+ return True
+ else:
+ return False
+
+
+ def __getLanguageValue(self, value):
+ hdf = self.__getLanguageData(self.dataset["Settings.Language"])
+ return hdf.getValue(value, "")
+
+
+ def __getLanguageData(self, web_lang="en"):
+ default_lang = "en"
+ conf_lang = self.prefs["WebSettings"]["Language"]
+ hdf = neo_util.HDF()
+ langDir = os.path.abspath(self.prefs["Locations"]["LangDir"])
+ langFiles = []
+ ## first: read default language (en)
+ if (default_lang != conf_lang) and (default_lang != web_lang):
+ langFiles.append(os.path.join(langDir, default_lang + ".hdf"))
+ ## second: read language as defined in the config file
+ if (conf_lang != web_lang):
+ langFiles.append(os.path.join(langDir, conf_lang + ".hdf"))
+ ## third: read language as configured via web interface
+ langFiles.append(os.path.join(langDir, web_lang + ".hdf"))
+ for langFile in langFiles:
+ if os.access(langFile, os.R_OK):
+ hdf.readFile(langFile)
+ else:
+ log.warn("Couldn't read language file: %s" % langFile)
+ return hdf
+
+
+ def __render(self, renderInfo, plugin=None):
+ '''renders from clearsilver templates and returns the resulting html
+ '''
+ ## is renderInfo a string (filename of the template) or a dictionary?
+ if type(renderInfo) == types.DictType:
+ template = renderInfo["template"]
+ if renderInfo.has_key("generator"):
+ generator = renderInfo["generator"]
+ else:
+ generator = False
+ else:
+ (template, generator) = (renderInfo, None)
+
+ ## load the language data
+ hdf = neo_util.HDF()
+ hdf.copy("Lang", self.__getLanguageData(self.dataset["Settings.Language"]))
+
+ ## first: assume, that the template file is in the global template directory
+ self.dataset["Settings.TemplateFile"] = os.path.abspath(os.path.join(self.prefs["Locations"]["TemplateDir"], template + ".cs"))
+
+ if plugin:
+ ## check, if the plugin provides the template file -> overriding
+ plugin_cs_file = plugin.getTemplateFileName(template)
+ if plugin_cs_file:
+ self.dataset["Settings.TemplateFile"] = plugin_cs_file
+
+ ## add the current state of the plugins to the hdf dataset
+ self.dataset["Data.Status.Plugins.%s" % plugin.getName()] = plugin.getStatus()
+ ## load the language data
+ pl_lang = plugin.getLanguageData(self.dataset["Settings.Language"])
+ if pl_lang:
+ hdf.copy("Lang.Plugins.%s" % plugin.getName(), pl_lang)
+ ## load the dataset of the plugin
+ plugin.loadDataSet(hdf)
+
+ self.log.info("rendering site: " + template)
+
+ cs_path = os.path.abspath(os.path.join(self.prefs["Locations"]["TemplateDir"], "main.cs"))
+ if not os.access(cs_path, os.R_OK):
+ log.error("Couldn't read clearsilver file: %s" % cs_path)
+ yield "Couldn't read clearsilver file: %s" % cs_path
+ return
+
+ self.log.debug(self.dataset)
+ for key in self.dataset.keys():
+ hdf.setValue(key,str(self.dataset[key]))
+ cs = neo_cs.CS(hdf)
+ cs.parseFile(cs_path)
+
+ ## is there a generator containing additional information?
+ if generator is None:
+ ## all content in one flush
+ yield cs.render()
+ else:
+ content_generate = generator()
+ dummy_line = """"""
+ ## now we do it linewise - checking for the content marker
+ for line in cs.render().splitlines():
+ if line.find(dummy_line) != -1:
+ yield line.replace(dummy_line, content_generate.next())
+ else:
+ yield line + "\n"
+
+
diff --git a/bin/WebInterfaceTestClass.py b/bin/WebInterfaceTestClass.py
new file mode 100644
index 0000000..c210a0f
--- /dev/null
+++ b/bin/WebInterfaceTestClass.py
@@ -0,0 +1,77 @@
+"""
+super class of all web interface unittests for the cryptobox
+
+just inherit this class and add some test functions
+"""
+
+import unittest
+import twill
+import cherrypy
+import WebInterfaceSites
+
+## we do the following, for easy surfing
+## e.g. use: cbx.go(your_url)
+## commands api: http://twill.idyll.org/commands.html
+CBXHOST="localhost"
+CBXPORT=8081
+CBX_URL="http://%s:%d/" % (CBXHOST, CBXPORT)
+LOG_FILE="/tmp/twill.log"
+
+class WebInterfaceTestClass(unittest.TestCase):
+ '''this class checks the webserver, using "twill"
+
+ the tests in this class are from the browsers point of view, so not
+ really unittests.
+ fetch twill from: http://twill.idyll.org
+ one way to manually run twill code is through the python
+ interpreter commandline e.g.:
+
+ import twill
+ twill.shell.main()
+ go http://localhost:8080
+ find "my very special html content"
+ help
+ '''
+
+ def setUp(self):
+ '''configures the cherrypy server that it works nice with twill
+ '''
+ cherrypy.config.update({
+ 'server.logToScreen' : False,
+ 'autoreload.on': False,
+ 'server.threadPool': 1,
+ 'server.environment': 'production',
+ })
+ cherrypy.root = WebInterfaceSites.WebInterfaceSites()
+ cherrypy.server.start(initOnly=True, serverClass=None)
+
+ from cherrypy._cpwsgi import wsgiApp
+ twill.add_wsgi_intercept(CBXHOST, CBXPORT, lambda: wsgiApp)
+
+ # grab the output of twill commands
+ self.output = open(LOG_FILE,"a")
+ twill.set_output(self.output)
+ self.cmd = twill.commands
+ self.URL = CBX_URL
+ self.cbox = cherrypy.root.cbox
+ self.globals, self.locals = twill.namespaces.get_twill_glocals()
+
+
+ def tearDown(self):
+ '''clean up the room when leaving'''
+ # remove intercept.
+ twill.remove_wsgi_intercept(CBXHOST, CBXPORT)
+ # shut down the cherrypy server.
+ cherrypy.server.stop()
+ self.output.close()
+
+
+ def __get_soup():
+ browser = twill.commands.get_browser()
+ soup = BeautifulSoup(browser.get_html())
+ return soup
+
+
+ def register_auth(self, url, user="admin", password="admin"):
+ self.cmd.add_auth("CryptoBox", url, user, password)
+
diff --git a/bin/coding_guidelines.txt b/bin/coding_guidelines.txt
new file mode 100644
index 0000000..a6fb47c
--- /dev/null
+++ b/bin/coding_guidelines.txt
@@ -0,0 +1,18 @@
+Maybe we can add some notes here to get a consistent coding experience :)
+
+-------------------------------------------------------------------------------
+
+comments:
+ - should be usable for pydoc
+ - ''' or """ at the beginning of every class/method
+ - ## for longterm comments, that are useful for understanding
+ - #blabla for codelines, that are out for experimenting and might be used later again
+
+error handling:
+ - unspecific error handling is evil (try: "grep -r except: .")
+
+unit testing:
+ - first write a unittest and then write the relating code until the unittest stops failing :)
+ - 'unittests.ClassName.py' should contain all tests for 'ClassName.py'
+ - commits with broken unit tests are evil (fix or disable the code (not the test ;) ))
+
diff --git a/bin/cryptobox.conf b/bin/cryptobox.conf
new file mode 100644
index 0000000..02ef334
--- /dev/null
+++ b/bin/cryptobox.conf
@@ -0,0 +1,83 @@
+[Main]
+
+# comma separated list of possible prefixes for accesible devices
+# beware: .e.g "/dev/hd" grants access to _all_ harddisks
+AllowedDevices = /dev/loop, /dev/ubdb
+
+# use sepepate config partition? (1=yes / 0=no)
+UseConfigPartition = 1
+
+# the default name prefix of not unnamed containers
+DefaultVolumePrefix = "Disk "
+
+# which cipher should cryptsetup-luks use?
+#TODO: uml does not support this module - DefaultCipher = aes-cbc-essiv:sha256
+DefaultCipher = aes-plain
+
+# label of the configuration partition (you should never change this)
+ConfigVolumeLabel = cbox_config
+
+
+[Locations]
+# where should we mount volumes?
+# this directory must be writeable by the cryptobox user (see above)
+MountParentDir = /var/cache/cryptobox/mnt
+
+# settings directory: contains name database and plugin configuration
+SettingsDir = /var/cache/cryptobox/settings
+
+# where are the clearsilver templates?
+#TemplateDir = /usr/share/cryptobox/templates
+TemplateDir = ../templates
+
+# path to language files
+#LangDir = /usr/share/cryptobox/lang
+LangDir = ../lang
+
+# path to documentation files
+#DocDir = /usr/share/doc/cryptobox/html
+DocDir = ../doc/html
+
+# path to the plugin directory
+#PluginDir = /usr/share/cryptobox/plugins
+PluginDir = ../plugins
+
+
+
+[Log]
+# possible values are "debug", "info", "warn" and "error" or numbers from
+# 0 (debug) to 7 (error)
+Level = debug
+
+# where to write the log messages to?
+# possible values are: file
+# syslog support will be added later
+Destination = file
+
+# depending on the choosen destination (see above) you may select
+# details. Possible values for the different destinations are:
+# file: $FILENAME
+# syslog: $LOG_FACILITY
+#Details = /var/log/cryptobox.log
+Details = ./cryptobox.log
+
+
+[WebSettings]
+# URL of default stylesheet
+Stylesheet = /cryptobox-misc/cryptobox.css
+
+# default language
+Language = de
+
+
+[Programs]
+cryptsetup = /sbin/cryptsetup
+mkfs-data = /sbin/mkfs.ext3
+blkid = /sbin/blkid
+blockdev = /sbin/blockdev
+mount = /bin/mount
+umount = /bin/umount
+super = /usr/bin/super
+# this is the "program" name as defined in /etc/super.tab
+CryptoBoxRootActions = CryptoBoxRootActions
+
diff --git a/bin/cryptoboxd b/bin/cryptoboxd
new file mode 100755
index 0000000..85258bd
--- /dev/null
+++ b/bin/cryptoboxd
@@ -0,0 +1,39 @@
+#!/bin/sh
+
+#TODO: CBXPATH=/usr/lib/cryptobox
+CBXPATH=$(pwd)
+CBXSERVER=CryptoBoxWebserver.py
+PIDFILE=/var/run/cryptobox.pid
+DAEMON=/usr/bin/python2.4
+DAEMON_OPTS=${CBXPATH}/CryptoBoxWebserver.py
+NAME=cryptoboxd
+DESC="CryptoBox Daemon (webinterface)"
+#TODO: RUNAS=cryptobox
+RUNAS=$USERNAME
+
+#test -x $DAEMON -a -f /etc/exports || exit 0
+
+set -e
+
+case "$1" in
+ start)
+ echo -n "Starting $DESC: "
+ start-stop-daemon --background --chdir "$CBXPATH" --chuid "$RUNAS" --start --quiet --oknodo --user "$RUNAS" --make-pidfile --pidfile "$PIDFILE" --exec "$DAEMON" \
+ -- $DAEMON_OPTS
+ echo "$NAME."
+ ;;
+
+ stop)
+ echo -n "Stopping $DESC: "
+ #FIXME: this is the same as "killall python2.4"
+ # using a pid file instead prevents problems, but does not kill children???
+ start-stop-daemon --stop --oknodo --exec "$DAEMON"
+ echo "$NAME."
+ ;;
+ *)
+ echo "Usage: $(basename $0) {start|stop}" >&2
+ exit 1
+ ;;
+esac
+
+exit 0
diff --git a/bin/cryptoboxwebserver.conf b/bin/cryptoboxwebserver.conf
new file mode 100644
index 0000000..b8e1e6a
--- /dev/null
+++ b/bin/cryptoboxwebserver.conf
@@ -0,0 +1,17 @@
+[global]
+server.socketPort = 8080
+#server.environment = "production"
+server.environment = "development"
+server.logToScreen = True
+server.log_tracebacks = True
+server.threadPool = 1
+server.reverseDNS = False
+server.logFile = "cryptoboxwebserver.log"
+
+[/favicon.ico]
+static_filter.on = True
+# TODO: use live-cd/live-cd-tree.d/var/www/favicon.ico
+static_filter.file = "/usr/share/doc/python-cherrypy/cherrypy/favicon.ico"
+
+[/test_stream]
+stream_response = True
diff --git a/bin/do_unittests.sh b/bin/do_unittests.sh
new file mode 100755
index 0000000..6af0d02
--- /dev/null
+++ b/bin/do_unittests.sh
@@ -0,0 +1,22 @@
+#!/bin/sh
+#
+# run this script _before_ you do a commit and fix errors before uploading
+#
+
+# check if /dev/loop1 is available - otherwise some tests will fail!
+if /sbin/losetup /dev/loop1 &>/dev/null
+ then true
+ else echo "misconfiguration detected: sorry - you need /dev/loop1 for the tests" >&2
+ echo "just do the following:" >&2
+ echo " dd if=/dev/zero of=test.img bs=1M count=1 seek=100" >&2
+ echo " sudo /sbin/losetup /dev/loop1 test.img" >&2
+ echo "then you can run the tests again ..." >&2
+ echo >&2
+ exit 1
+ fi
+
+# do the tests
+for a in unittests.*.py
+ do testoob -v "$a"
+ done
+
diff --git a/bin/example-super.tab b/bin/example-super.tab
new file mode 100644
index 0000000..03d21c0
--- /dev/null
+++ b/bin/example-super.tab
@@ -0,0 +1,2 @@
+# adapt the following line to your local setup and add it to /etc/super.tab
+CryptoBoxRootActions /your/local/path/to/CryptoBoxRootActions.py yourUserName
diff --git a/bin/test.complete.CryptoBox.py b/bin/test.complete.CryptoBox.py
new file mode 100755
index 0000000..db5300d
--- /dev/null
+++ b/bin/test.complete.CryptoBox.py
@@ -0,0 +1,116 @@
+#!/usr/bin/env python2.4
+
+"""
+BEWARE: this script may overwrite the data of one of your loop devices. You
+should restrict the AllowedDevices directive in cryptobox.conf to exclude
+your precious black devices from being used by this script.
+
+the following script runs a number of tests for different parts
+"""
+
+from CryptoBox import CryptoBoxProps
+from CryptoBoxContainer import CryptoBoxContainer
+import sys
+
+
+def main():
+ cb = CryptoBoxProps()
+
+ print "Confguration:"
+ print "\tConfig file:\t\t%s" % (cb.prefs.prefs.filename, )
+ print "\tAllowed devices:\t%s" % (cb.prefs["Main"]["AllowedDevices"], )
+
+ """for e in cb.getContainerList(filterType=CryptoBoxContainer.Types["luks"]):"""
+ for e in cb.getContainerList():
+ print "\t\t%d\t\t%s - %s - %d" % (cb.getContainerList().index(e), e.getDevice(), e.getName(), e.getType())
+
+ if not cb.getContainerList() or len(cb.getContainerList()) < 1:
+ print "no loop devices found for testing"
+ sys.exit(1)
+
+ if len(cb.getContainerList()) > 1:
+ print "I found more than one available loop device - I will stop now to avoid risking data loss."
+ print "Please change the 'AllowedDevices' setting in 'cryptobox.conf' to reduce the number of allowed devices to only one."
+ sys.exit(1)
+
+ testElement = cb.getContainerList()[0]
+ print "\nRunning some tests now ..."
+ if not plain_tests(testElement):
+ print "some previous tests failed - we should stop now"
+ sys.exit(1)
+ luks_tests(testElement)
+
+
+" ***************** some functions ******************** "
+
+def luks_tests(e):
+ # umount if necessary
+ try:
+ e.umount()
+ except "MountError":
+ pass
+
+ e.create(e.Types["luks"], "alt")
+ print "\tluks create:\tok"
+
+ e.changePassword("alt","neu")
+ print "\tluks changepw:\tok"
+
+ e.setName("lalla")
+ print "\tluks setName:\tok"
+
+ try:
+ e.mount("neu")
+ except "MountError":
+ pass
+ if e.isMounted(): print "\tluks mount:\tok"
+ else: print "\tluks mount:\tfailed"
+
+ print "\tCapacity (size, free, used) [MB]:\t%s" % (e.getCapacity(), )
+
+ try:
+ e.umount()
+ except "MountError":
+ pass
+ if e.isMounted(): print "\tluks umount:\tfailed"
+ else: print "\tluks umount:\tok"
+
+ if e.isMounted(): return False
+ else: return True
+
+
+def plain_tests(e):
+ # umount if necessary
+ try:
+ e.umount()
+ except "MountError":
+ pass
+
+ e.create(e.Types["plain"])
+ print "\tplain create:\tok"
+
+ e.setName("plain-lili")
+ print "\tplain setName:\tok"
+
+ try:
+ e.mount()
+ except "MountError":
+ pass
+ if e.isMounted(): print "\tplain mount:\tok"
+ else: print "\tplain mount:\tfailed"
+
+ print "\tCapacity (size, free, used) [MB]:\t%s" % (e.getCapacity(), )
+
+ try:
+ e.umount()
+ except "MountError":
+ pass
+ if e.isMounted(): print "\tplain umount:\tfailed"
+ else: print "\tplain umount:\tok"
+
+ if e.isMounted(): return False
+ else: return True
+
+# ************ main ****************
+
+main()
diff --git a/bin/uml-setup.sh b/bin/uml-setup.sh
new file mode 100755
index 0000000..8826f3d
--- /dev/null
+++ b/bin/uml-setup.sh
@@ -0,0 +1,23 @@
+#!/bin/sh
+
+ROOT_IMG=/home/lars/devel-stuff/devel-chroots/cryptobox.img
+TEST_IMG=test.img
+TEST_SIZE=256
+MEM_SIZE=128M
+
+# Preparations:
+# echo "tun" >>/etc/modules
+# follow the instructions in /usr/share/doc/uml-utilities/README.Debian
+# add your user to the group 'uml-net'
+#
+
+/sbin/ifconfig tap0 &>/dev/null || { echo "tap0 is not configured - read /usr/share/doc/uml-utilities/README.Debian for hints"; exit 1; }
+
+
+if [ ! -e "$TEST_IMG" ]
+ then echo "Creating testing image file ..."
+ dd if=/dev/zero of="$TEST_IMG" bs=1M count=$TEST_SIZE
+ fi
+
+linux ubd0="$ROOT_IMG" ubd1="$TEST_IMG" con=xterm hostfs=../ fakehd eth0=daemon mem=$MEM_SIZE
+
diff --git a/bin/unittests.CryptoBox.py b/bin/unittests.CryptoBox.py
new file mode 100755
index 0000000..baaad3c
--- /dev/null
+++ b/bin/unittests.CryptoBox.py
@@ -0,0 +1,138 @@
+#!/usr/bin/env python2.4
+
+import unittest
+import sys
+from CryptoBox import *
+from CryptoBoxExceptions import *
+import CryptoBoxSettings
+
+class CryptoBoxPropsDeviceTests(unittest.TestCase):
+ import CryptoBox
+ cb = CryptoBox.CryptoBoxProps()
+
+ def testAllowedDevices(self):
+ '''isDeviceAllowed should accept permitted devices'''
+ self.assertTrue(self.cb.isDeviceAllowed("/dev/loop"))
+ self.assertTrue(self.cb.isDeviceAllowed("/dev/loop1"))
+ self.assertTrue(self.cb.isDeviceAllowed("/dev/loop/urgd"))
+ self.assertTrue(self.cb.isDeviceAllowed("/dev/usb/../loop1"))
+
+ def testDeniedDevices(self):
+ '''isDeviceAllowed should fail with not explicitly allowed devices'''
+ self.assertFalse(self.cb.isDeviceAllowed("/dev/hda"))
+ self.assertFalse(self.cb.isDeviceAllowed("/dev/loopa/../hda"))
+ self.assertFalse(self.cb.isDeviceAllowed("/"))
+
+
+class CryptoBoxPropsConfigTests(unittest.TestCase):
+ '''test here if everything with the config turns right'''
+ import os
+ import CryptoBox
+
+ files = {
+ "configFileOK" : "cbox-test_ok.conf",
+ "configFileBroken" : "cbox-test_broken.conf",
+ "nameDBFile" : "cryptobox_names.db",
+ "pluginConf" : "cryptobox_plugins.conf",
+ "userDB" : "cryptobox_users.db",
+ "logFile" : "cryptobox.log",
+ "tmpdir" : "cryptobox-mnt" }
+ tmpdirname = ""
+ filenames = {}
+ configContentOK = """
+[Main]
+AllowedDevices = /dev/loop
+DefaultVolumePrefix = "Data "
+DefaultCipher = aes-cbc-essiv:sha256
+[Locations]
+SettingsDir = %s
+MountParentDir = %s
+TemplateDir = ../templates
+LangDir = ../lang
+DocDir = ../doc/html
+PluginDir = ../plugins
+[Log]
+Level = debug
+Destination = file
+Details = %s/cryptobox.log
+[WebSettings]
+Stylesheet = /cryptobox-misc/cryptobox.css
+[Programs]
+blkid = /sbin/blkid
+cryptsetup = /sbin/cryptsetup
+super = /usr/bin/super
+CryptoBoxRootActions = CryptoBoxRootActions
+"""
+
+
+ def setUp(self):
+ '''generate all files in tmp and remember the names'''
+ import tempfile
+ os = self.os
+ self.tmpdirname = tempfile.mkdtemp(prefix="cbox-")
+ for file in self.files.keys():
+ self.filenames[file] = os.path.join(self.tmpdirname, self.files[file])
+ self.writeConfig()
+
+
+ def tearDown(self):
+ '''remove the created tmpfiles'''
+ os = self.os
+ # remove temp files
+ for file in self.filenames.values():
+ compl_name = os.path.join(self.tmpdirname, file)
+ if os.path.exists(compl_name):
+ os.remove(compl_name)
+ # remove temp dir
+ os.rmdir(self.tmpdirname)
+
+
+ def testConfigInit(self):
+ '''Check various branches of config file loading'''
+ import os
+ self.assertRaises(CBConfigUnavailableError, self.CryptoBox.CryptoBoxProps,"/invalid/path/to/config/file")
+ self.assertRaises(CBConfigUnavailableError, self.CryptoBox.CryptoBoxProps,"/etc/shadow")
+ """ check one of the following things:
+ 1) are we successfully using an existing config file?
+ 2) do we break, if no config file is there?
+ depending on the existence of a config file, only one of these conditions
+ can be checked - hints for more comprehensive tests are appreciated :) """
+ for a in CryptoBoxSettings.CryptoBoxSettings.CONF_LOCATIONS:
+ if os.path.exists(a):
+ self.CryptoBox.CryptoBoxProps()
+ break # this skips the 'else' clause
+ else: self.assertRaises(CBConfigUnavailableError, self.CryptoBox.CryptoBoxProps)
+ self.assertRaises(CBConfigUnavailableError, self.CryptoBox.CryptoBoxProps,[])
+
+
+ def testBrokenConfigs(self):
+ """Check various broken configurations"""
+ self.writeConfig("SettingsDir", "SettingsDir=/foo/bar", filename=self.filenames["configFileBroken"])
+ self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"])
+ self.writeConfig("Level", "Level = ho", filename=self.filenames["configFileBroken"])
+ self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"])
+ self.writeConfig("Details", "#out", filename=self.filenames["configFileBroken"])
+ self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"])
+ self.writeConfig("super", "super=/bin/invalid/no", filename=self.filenames["configFileBroken"])
+ self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"])
+ self.writeConfig("CryptoBoxRootActions", "#not here", filename=self.filenames["configFileBroken"])
+ self.assertRaises(CBConfigError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"])
+ self.writeConfig("CryptoBoxRootActions", "CryptoBoxRootActions = /bin/false", filename=self.filenames["configFileBroken"])
+ self.assertRaises(CBEnvironmentError, self.CryptoBox.CryptoBoxProps,self.filenames["configFileBroken"])
+
+
+ def writeConfig(self, replace=None, newline=None, filename=None):
+ """write a config file and (optional) replace a line in it"""
+ import re
+ if not filename: filename = self.filenames["configFileOK"]
+ content = self.configContentOK % (self.tmpdirname, self.tmpdirname, self.tmpdirname)
+ if replace:
+ pattern = re.compile('^' + replace + '\\s*=.*$', flags=re.M)
+ content = re.sub(pattern, newline, content)
+ cf = open(filename, "w")
+ cf.write(content)
+ cf.close()
+
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/bin/unittests.CryptoBoxTools.py b/bin/unittests.CryptoBoxTools.py
new file mode 100755
index 0000000..10daf4e
--- /dev/null
+++ b/bin/unittests.CryptoBoxTools.py
@@ -0,0 +1,48 @@
+#!/usr/bin/env python2.4
+
+import unittest
+import CryptoBoxTools
+import os
+
+
+class CryptoBoxToolsTests(unittest.TestCase):
+
+ def testGetAbsoluteDeviceName(self):
+ func = CryptoBoxTools.getAbsoluteDeviceName
+ self.assertTrue(func("hda") == "/dev/hda")
+ self.assertTrue(func("loop0") == "/dev/loop0")
+ self.assertTrue(func(os.path.devnull) == os.path.devnull)
+
+
+ def testFindMajorMinorOfDevice(self):
+ func = CryptoBoxTools.findMajorMinorOfDevice
+ self.assertTrue(func("/dev/hda") == (3,0))
+ self.assertTrue(func("/dev/hda1") == (3,1))
+ self.assertTrue(func(os.path.devnull) == (1,3))
+ self.assertTrue(func("/dev/nothere") is None)
+
+
+ def testFindMajorMinorDeviceName(self):
+ func = CryptoBoxTools.findMajorMinorDeviceName
+ dir = os.path.join(os.path.sep, "dev")
+ self.assertTrue(os.path.join(dir,"hda") in func(dir,3,0))
+ self.assertTrue(os.path.devnull in func(dir,1,3))
+ self.assertFalse(os.path.devnull in func(dir,2,3))
+
+
+ def testIsPartOfBlockDevice(self):
+ func = CryptoBoxTools.isPartOfBlockDevice
+ self.assertTrue(func("/dev/hda", "/dev/hda1"))
+ self.assertFalse(func("/dev/hda", "/dev/hda"))
+ self.assertFalse(func("/dev/hda1", "/dev/hda"))
+ self.assertFalse(func("/dev/hda1", "/dev/hda1"))
+ self.assertFalse(func("/dev/hda", "/dev/hdb1"))
+ self.assertFalse(func(None, "/dev/hdb1"))
+ self.assertFalse(func("/dev/hda", None))
+ self.assertFalse(func(None, ""))
+ self.assertFalse(func("loop0", "loop1"))
+
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/bin/unittests.Plugins.py b/bin/unittests.Plugins.py
new file mode 100755
index 0000000..929c9de
--- /dev/null
+++ b/bin/unittests.Plugins.py
@@ -0,0 +1,33 @@
+#!/usr/bin/python2.4
+
+import unittest
+import Plugins
+
+class CheckForUndefinedTestCases(unittest.TestCase):
+ """here we will add failing test functions for every non-existing testcase"""
+
+
+def create_testcases():
+
+ plugins = Plugins.PluginManager(None, "../plugins").getPlugins()
+ glob_dict = globals()
+ loc_dict = locals()
+ for pl in plugins:
+ test_class = pl.getTestClass()
+ if test_class:
+ ## add the testclass to the global dictionary
+ glob_dict["unittest" + pl.getName()] = test_class
+ else:
+ subname = "test_existence_%s" % pl.getName()
+ def test_existence(self):
+ """check if the plugin (%s) contains tests""" % pl.getName()
+ self.fail("no tests defined for plugin: %s" % pl.getName())
+ ## add this function to the class above
+ setattr(CheckForUndefinedTestCases, subname, test_existence)
+ #FIXME: the failure output always contains the same name for all plugins
+
+
+create_testcases()
+
+if __name__ == "__main__":
+ unittest.main()
diff --git a/bin/unittests.WebSites.py b/bin/unittests.WebSites.py
new file mode 100755
index 0000000..89e514d
--- /dev/null
+++ b/bin/unittests.WebSites.py
@@ -0,0 +1,39 @@
+#!/usr/bin/env python2.4
+
+import unittest
+
+## this makes assertRaises shorter
+from twill.errors import *
+from mechanize import BrowserStateError, LinkNotFoundError
+
+## import the module of the common super class of all web interface test classes
+import WebInterfaceTestClass
+
+
+
+class WebServer(WebInterfaceTestClass.WebInterfaceTestClass):
+
+ def test_is_server_running(self):
+ '''the server should run under given name and port'''
+ self.cmd.go(self.URL)
+ ## other URLs must not be checked, as we do not know, if they are valid
+
+
+class BuiltinPages(WebInterfaceTestClass.WebInterfaceTestClass):
+
+
+ def test_goto_index(self):
+ '''display all devices'''
+ self.cmd.go(self.URL + "?weblang=en")
+ self.cmd.find("The CryptoBox")
+ self.cmd.go(self.URL + "?weblang=de")
+ self.cmd.find("Die CryptoBox")
+ self.cmd.go(self.URL + "?weblang=si")
+ self.cmd.find("Privatnost v vsako vas")
+ self.cmd.go(self.URL + "?weblang=fr")
+ self.cmd.find("La CryptoBox")
+
+
+if __name__ == "__main__":
+ unittest.main()
+
diff --git a/debian/README.Debian b/debian/README.Debian
index 8a503fc..f06e815 100644
--- a/debian/README.Debian
+++ b/debian/README.Debian
@@ -1,6 +1,5 @@
CryptoBox for Debian - installation notes
-be aware of two things:
+be aware of one thing:
1) you need cryptsetup with luks support (for now only in unstable)
-2) the debian perl-clearsilver package is broken (at least until April 02006)
diff --git a/debian/control b/debian/control
index b2dba57..26058dc 100644
--- a/debian/control
+++ b/debian/control
@@ -7,8 +7,7 @@ Standards-Version: 3.6.2
Package: cryptobox
Architecture: any
-Depends: bash (>=2.0), sed (>=4.0), coreutils, grep (>=2.0), perl, httpd-cgi, hashalot, libconfigfile-perl, cryptsetup (>=20050111), dmsetup, pmount, initscripts, e2fsprogs (>= 1.27), adduser
-Recommends: perl-clearsilver
+Depends: bash (>=2.0), sed (>=4.0), coreutils, grep (>=2.0), httpd-cgi, hashalot, cryptsetup (>=20050111), dmsetup, initscripts, e2fsprogs (>= 1.27), adduser, python (>=2.4), python-clearsilver
Suggests: cron, samba
Description: Web interface for an encrypting fileserver
This bundle of scripts and cgis allow you to manage an encrypted harddisk
diff --git a/debian/rules b/debian/rules
index 33284e8..5d6d90b 100755
--- a/debian/rules
+++ b/debian/rules
@@ -74,19 +74,14 @@ binary-arch: build install
# dh_installmenu
# dh_installdebconf
# dh_installlogrotate
-# dh_installemacsen
-# dh_installpam
-# dh_installmime
dh_installinit
# dh_installcron
-# dh_installinfo
dh_installman
dh_link
dh_strip
dh_compress
dh_fixperms
- dh_perl
-# dh_python
+ dh_python
# dh_makeshlibs
dh_installdeb
dh_shlibdeps
diff --git a/design/background_frame_corner.svg b/design/background_frame_corner.svg
new file mode 100644
index 0000000..deb4ae3
--- /dev/null
+++ b/design/background_frame_corner.svg
@@ -0,0 +1,265 @@
+
+
+
diff --git a/design/icon_background_active.svg b/design/icon_background_active.svg
new file mode 100644
index 0000000..c6ee31a
--- /dev/null
+++ b/design/icon_background_active.svg
@@ -0,0 +1,92 @@
+
+
+
diff --git a/design/icons/applications-system_tango.svg b/design/icons/applications-system_tango.svg
new file mode 100644
index 0000000..35e2ffa
--- /dev/null
+++ b/design/icons/applications-system_tango.svg
@@ -0,0 +1,245 @@
+
+
+
diff --git a/design/icons/computer_tango.svg b/design/icons/computer_tango.svg
new file mode 100644
index 0000000..d6e0f6b
--- /dev/null
+++ b/design/icons/computer_tango.svg
@@ -0,0 +1,738 @@
+
+
+
diff --git a/design/icons/dialog-error_tango.svg b/design/icons/dialog-error_tango.svg
new file mode 100644
index 0000000..602fa79
--- /dev/null
+++ b/design/icons/dialog-error_tango.svg
@@ -0,0 +1,316 @@
+
+
+
diff --git a/design/icons/dialog-information_tango.svg b/design/icons/dialog-information_tango.svg
new file mode 100644
index 0000000..1e957cc
--- /dev/null
+++ b/design/icons/dialog-information_tango.svg
@@ -0,0 +1,1145 @@
+
+
+
diff --git a/design/icons/dialog-warning_tango.svg b/design/icons/dialog-warning_tango.svg
new file mode 100644
index 0000000..3870db2
--- /dev/null
+++ b/design/icons/dialog-warning_tango.svg
@@ -0,0 +1,290 @@
+
+
+
diff --git a/design/icons/drive-cdrom_tango.svg b/design/icons/drive-cdrom_tango.svg
new file mode 100644
index 0000000..6588a65
--- /dev/null
+++ b/design/icons/drive-cdrom_tango.svg
@@ -0,0 +1,444 @@
+
+
+
diff --git a/design/icons/drive-harddisk_tango.svg b/design/icons/drive-harddisk_tango.svg
new file mode 100644
index 0000000..406c4ac
--- /dev/null
+++ b/design/icons/drive-harddisk_tango.svg
@@ -0,0 +1,469 @@
+
+
+
diff --git a/design/icons/drive-removable-media_tango.svg b/design/icons/drive-removable-media_tango.svg
new file mode 100644
index 0000000..e448605
--- /dev/null
+++ b/design/icons/drive-removable-media_tango.svg
@@ -0,0 +1,390 @@
+
+
+
diff --git a/design/icons/globe-lips.svg b/design/icons/globe-lips.svg
new file mode 100644
index 0000000..8b700f9
--- /dev/null
+++ b/design/icons/globe-lips.svg
@@ -0,0 +1,512 @@
+
+
+
diff --git a/design/icons/gnome-dev-removable-usb_nuvola.svg b/design/icons/gnome-dev-removable-usb_nuvola.svg
new file mode 100644
index 0000000..a2c624a
--- /dev/null
+++ b/design/icons/gnome-dev-removable-usb_nuvola.svg
@@ -0,0 +1,1004 @@
+
+
+
+
diff --git a/design/icons/gnome-globe_nuvola.svg b/design/icons/gnome-globe_nuvola.svg
new file mode 100644
index 0000000..3a5a620
--- /dev/null
+++ b/design/icons/gnome-globe_nuvola.svg
@@ -0,0 +1,1195 @@
+
+
+
+
diff --git a/design/icons/gtk-zoom-in_nuvola.svg b/design/icons/gtk-zoom-in_nuvola.svg
new file mode 100644
index 0000000..1737355
--- /dev/null
+++ b/design/icons/gtk-zoom-in_nuvola.svg
@@ -0,0 +1,433 @@
+
+
+
\ No newline at end of file
diff --git a/design/icons/help_contents.svg b/design/icons/help_contents.svg
new file mode 100644
index 0000000..c3beee8
--- /dev/null
+++ b/design/icons/help_contents.svg
@@ -0,0 +1,701 @@
+
+
+
diff --git a/design/icons/inaccessible_tango_emblem-unreadable.svg b/design/icons/inaccessible_tango_emblem-unreadable.svg
new file mode 100644
index 0000000..82a4a4f
--- /dev/null
+++ b/design/icons/inaccessible_tango_emblem-unreadable.svg
@@ -0,0 +1,357 @@
+
+
+
diff --git a/design/icons/language.png b/design/icons/language.png
new file mode 100644
index 0000000000000000000000000000000000000000..3334b5d6db6c37e0f2bb49331307d4e076172507
GIT binary patch
literal 3520
zcmai1`9IX%7yryKjD*RatWC0Hh$Lkj(_k`RQP!#KC0mTGgkgBdR<;Na#@NY|AqrWV
zL?hcoAf*T(m4
z@fSHwHaxW)&C$a&g^ll5Plkm&mbA)oF#BEjRSo(8uUhdLX-SUlg0qW{{9DyHuqg#RK5o&Mj_eJlxQ@H6znIq+xR
z8F!#Ypi2s}>I%4mt3z6EEQ(zQE?1qAqecQG%hfjqkQpK1mS%3hln=c9eeWV@PmC(V
zVn2L%Si_|~e9NXp*X?}aE2rj*YDMeQB_&EV-aM<4L>5501QnbKy%2M*EJ9ya*#AqX
z+&xARzkV7v&;8%^9sJJPEjvd?PhVfl3+)rE;G(2*qoJE+9DAUC!e14EFmrHlKxK*m
zhIG%K6p8TJ1Sw>aVKgTLs!0ZrI)LF?FT8$gB{)G53NzDg$%hYXhaN0k+nI=6?$R5N
zr+PFHVR2IdZ9)(TWa-BbUgB=y#)@H6L7YisKlbCj(uj91Bv_d8Ci+E`6Z
z;Qjme0CBX|5eu#d_ws)@(yR#}v+Oc70b&OH3v}LZKfmwaf~+jj)!*Hpthgfcs6sPs3?^mbYGSN4W_TZ0`g%{rfJ=~KPmH2+J`f&R*Bvb>c`%N`f
z#gVGL=4tJqv0G9gh`&&D&zDhtQT^egN3n~6VT9md9j}uNkA{J31#a(<8RK~yHvkIH
zwi;2q3|F8y8Y16YDArHVR!H9jnz4)HZ?W1q#?}B%|A>}3#Zd~u80qR_yDvq)AshFnDOKn
zs^LPt98IDLh&bxovracj9~c;j&v*P>@6Iz+Y?8jb>}zAm-dQa6+*xBaIv{9mg5lsZ
z{V$pWn19>MW1Tu8&gunD9|rRB@~AzjQdVF2n(!dM*z0~{j?bClV8n3tt0@$IU}I~mluTB1_aGVq7;EcX+cT!Hnf0Byls#lvoFZ}YJ`O5N
zGZi;u9T>f(U-VW2gaJv!wVT_8j_Et#8q!yN95AANhT$~gFHiM-A>#l<3{54TQZK{B
zx4b?oj)?P6LZ2k6=0_r+NsAhaKlk@g$;rF