#!/usr/bin/perl
#---------------------------------------------------------------
# Project         : Mandriva Linux
# Module          : mountloop
# File            : drakloop
# Version         : $Id: drakloop 145743 2005-11-14 14:04:39Z tvignaud $
# Author          : Frederic Lepied
# Enhanced by     : Robert Vojta <robert.vojta@mandrake.cz>
# Created On      : Tue Jun 11 22:44:24 2002
#---------------------------------------------------------------

#
# FIXME: It's a new, enhanced code, needs optimalization
# 
use lib qw(/usr/lib/libDrakX);
use standalone;
use interactive;

BEGIN { unshift @::textdomains, 'drakloop' }

use MDK::Common::System;
use MDK::Common::File;
use common;
use security::level;
use ugtk2 qw(:all);

# i18n: IMPORTANT: to get correct namespace (drakconf instead of libDrakX)

my @ENCRYPTIONS = ("aes128", "aes192", "aes256");
my @YESNO = (N("Yes"), N("No"));
my $CFGFILE = $ENV{HOME} . "/.mountlooprc";
my $MINPASSLEN = (3, 3, 3, 4, 6, 8)[security::level::get()];

my $_in = 'interactive'->vnew;

# Main Window
my $win = ugtk2->new(N("Drakloop - Encrypted Folders Management Tool"));
$win->{rwindow}->set_position('center');
$win->{window}->signal_connect(delete_event => \&handle_quit_event);
gtkset_size_request($win->{rwindow}, 450, 220);

# Menu
my ($menu, $_factory) = create_factory_menu($::isEmbedded ? $::Plug : $win->{rwindow},
					   ([ N("/_File"), undef, undef, undef, '<Branch>' ],
					    [ N("/_File") . N("/_Quit"), N("<control>Q"), \&handle_quit_event, undef, '<StockItem>', 'gtk-quit' ],



                                            [ N("/_Help"), undef, undef, undef, '<Branch>' ],
                                            [ N("/_Help") . N("/_Report Bug"), undef, \&handle_report_bug_event, undef, '<StockItem>', 'gtk-dialog-error' ],
					    [ N("/_Help") . N("/_About...") , undef, \&handle_about_event, undef, '<StockItem>', 'gtk-preferences' ],
));

# Toolbar
my $toolbar = Gtk2::Toolbar->new;
my %toolbar_buttons = {};

foreach ([ "add", "gtk-add", N("Add"), N("Create new encrypted directory"), \&handle_new_event, 1 ],
	 [ "remove", "gtk-remove", N("Remove"), N("Remove selected directory"), \&handle_remove_event, 0 ],
	 [ "mount", "gtk-execute", N("Mount"), N("Mount selected directory"), \&handle_mount_event, 0 ],
	 [ "unmount", "gtk-stop", N("Unmount"), N("Unmount selected directory"), \&handle_unmount_event, 0 ])
{
    $toolbar_buttons{$_->[0]} = $toolbar->append_item($_->[2], $_->[3], $_->[3], Gtk2::Image->new_from_stock($_->[1], "button"), $_->[4], $toolbar);
    $toolbar_buttons{$_->[0]}->set_sensitive($_->[5]);
}

# Cipher, Directory, File, Mounted
my $folders_model = Gtk2::ListStore->new("Glib::String", "Glib::String", "Glib::String", "Glib::String");
reload_mountlooprc($folders_model);
my $folders_tree = create_tree($folders_model);

my @folders_col_size = (30, 50, 60, 30);

each_index {
    my $col = Gtk2::TreeViewColumn->new_with_attributes($_, Gtk2::CellRendererText->new, 'text' => $::i);
    $col->set_sort_column_id($::i);
    $col->set_min_width($folders_col_size[$::i]);
    $folders_tree->append_column($col);
} (N("Encryption"), N("Directory"), N("Status"), N("On boot"));

# Main Window
$win->{window}->add(gtkpack_(Gtk2::VBox->new(0, 0),
			     0, $menu,
			     0, $toolbar,
			     0, Gtk2::HSeparator->new,
			     1, create_scrolled_window($folders_tree)));

$win->{rwindow}->show_all;
Gtk2->main;
ugtk2->exit(0);

sub create_tree {
    my ($model) = @_;
    my $tree = Gtk2::TreeView->new_with_model($model);
    $tree->get_selection->set_mode('browse');
    $tree->set_headers_visible(1);
    $tree->set_rules_hint(1);

    $tree->get_selection->signal_connect('changed' => sub {
	my (undef, $iter) = $tree->get_selection->get_selected;
	if (defined $iter)
	{
	    handle_actions_sensitivity(is_mounted($model->get($iter, 1)), 0);
	} else {
	    handle_actions_sensitivity(0, 1);
	}
	});
    $tree;
}

sub is_mounted {
    my ($directory) = @_;

    foreach (cat_("/proc/mounts")) {
	return 1 if /loop.*$directory /;
    }
    return 0;
}

sub reload_mountlooprc {
    my ($model) = @_;

    $model->clear;
    my @lines = cat_($CFGFILE);

    foreach (@lines) {
	if (/^([^ ]+) +([^ ]+) +([^ ]+) *(yes|no)?$/) {
	    $model->append_set([ 0 => $1, 1 => $3, 2 => is_mounted($3) ? N("mounted") : N("not mounted"), 3 => ($4 ne "no") ? N("Yes") : N("No") ]);
	}
    }
}

sub show_warning {
    my ($text) = @_;

    my $w = new_dialog(N("Warning"));
    gtkpack($w->vbox, gtkpack(gtkset_border_width(Gtk2::VBox->new, 12),
			      Gtk2::Label->new($text)));

    $w->add_buttons('gtk-close', 'close');
    $w->show_all;
    $w->run;
    $w->destroy;
}

sub new_dialog {
    my ($label) = @_;

    my $w = Gtk2::Dialog->new;
    $w->set_title($label);
    $w->set_position('center');
    gtkset_modal($w, 1);
}

sub handle_actions_sensitivity {
    my ($mounted, $forceoff) = @_;

    enable_unmount_actions($forceoff ? 0 : $mounted);
    enable_mount_actions($forceoff ? 0 : !$mounted);
    enable_remove_actions($forceoff ? 0 : !$mounted);
}

sub enable_remove_actions {
    my ($enable) = @_;
    $toolbar_buttons{remove}->set_sensitive($enable);
}

sub enable_mount_actions {
    my ($enable) = @_;
    ($toolbar_buttons{mount})->set_sensitive($enable);
}

sub enable_unmount_actions {
    my ($enable) = @_;
    $toolbar_buttons{unmount}->set_sensitive($enable);
}

sub handle_quit_event() {
    Gtk2->main_quit;
}

sub handle_report_bug_event() {
    system("www-browser http://qa.mandriva.com/enter_bug.cgi?product=mountloop &");
}

sub handle_mount_event() {
    my $w = new_dialog(N("Enter Password"));

    my $password = Gtk2::Entry->new;
    $password->set_visibility(0);
    $password->set_width_chars(15);
    $password->set_activates_default(1);

    gtkpack($w->vbox, gtkpack(gtkset_border_width(Gtk2::VBox->new, 12), $password));

    $w->add_buttons('gtk-cancel', 'cancel', 'gtk-ok', 'ok');
    $w->set_default_response('ok');
    $w->show_all;
    if ($w->run eq 'ok')
    {
	my (undef, $iter) = $folders_tree->get_selection->get_selected;

	mountloop($folders_model->get($iter, 0), $folders_model->get($iter, 1), $password->get_text);

	my $mounted = is_mounted($folders_model->get($iter, 1));
	handle_actions_sensitivity($mounted, 0);

	if ($mounted)
	{
	    $folders_model->set_value($iter, 2, N("mounted"));
	} else {
	    show_warning(N("Failed to mount selected directory."));
	}
    }

    $w->destroy;
    undef $password;
}

sub handle_unmount_event() {
    my (undef, $iter) = $folders_tree->get_selection->get_selected;
    my $dir = $folders_model->get($iter, 1);

    system("/usr/bin/umountloop $ENV{HOME}/$dir");
    my $mounted = is_mounted($dir);

    if ($mounted) {
	show_warning(N("Failed to unmount selected directory, still used?\nTry fuser or lsof to find where the problem is."));
    } else {
	$folders_model->set_value($iter, 2, N("not mounted"));
    }

    handle_actions_sensitivity($mounted, 0);
}

sub new_pango_label {
    my ($text) = @_;
    gtkset_markup(
                  gtkset_alignment(Gtk2::Label->new,
                                   0, 0.5),
                  qq(<span weight="bold">$text</span>)
                 );
}

sub new_table_label {
    my ($text) = @_;
    gtkset_alignment(Gtk2::Label->new($text), 0, 0.5);
}

sub create_and_save_new_directory {
    my ($dir, $encryption, $size, $password, $auto) = @_;

    my $encdir = $ENV{HOME} . "/$dir";
    
    my $encfile = "$encdir/encfile";

    $auto = bool2yesno($auto);
    
    eval { mkdir_p($encdir) };
    if ($@)
    {
	show_warning(N("Failed to create directory %s", $dir));
	return 1;
    }

    if (system("dd if=/dev/zero of=$encfile bs=1M count=${size}"))
    {
	show_warning(N("Failed to create file %s", $encfile));
	rmrf($dir);
	return 1;
    }
    
    my $f;
    if (-x '/usr/bin/encsetup' && open $f, qq(| /usr/bin/encsetup "$encryption" "$encfile"))
    {
	print $f $password;
	close $f;
    } else {
        show_warning(N("Failed to prepare %s", $encfile));
	rmrf($dir);
        return 1;
    }
    
    eval { append_to_file($CFGFILE, "$encryption $dir/encfile $dir $auto\n") };
    if ($@) {
	    show_warning(N("Failed to save new directory in %s", $CFGFILE));
	    rmrf($dir);
	    return 1;
    }

    return 0;
}

sub handle_about_event() {
    my $w = new_dialog(N("About..."));

    gtkpack($w->vbox, gtkpack(gtkset_border_width(Gtk2::VBox->new, 12),
			      gtkset_markup(gtkset_alignment(Gtk2::Label->new, 0.5, 0.5),
					    qq(<span weight="bold">) . N("Drakloop") . qq(</span>\n) .
					    N("Encrypted Folders Management Tool") . qq(\n\n) .
					    "(C) 2002-2005 Mandriva SA" . qq(\n)),
			      create_packtable({ col_spacings => 12, row_spacings => 6 },
					       [ N("Written by:"), "Frederic Lepied" ],
					       [ N("Enhanced by:"), "Robert Vojta" ],
					       )
			      ));

    $w->add_buttons('gtk-close', 'close');
    $w->show_all;
    $w->run;
    $w->destroy;
}

sub rmrf {
    eval { rm_rf(@_) };
    return $@ ? 1 : 0;
}

sub mountloop {
    my ($enc, $dir, $pass) = @_;
    my $f;
    $dir = $ENV{HOME} . "/$dir";
    my $file = "$dir/encfile";
    print "file=$file eol\n";
    open $f, qq(| /usr/bin/mountloop "$enc" "$file" "$dir");
    print $f $pass;
    close $f;
}

sub handle_new_event() {
    my $w = new_dialog(N("New Encrypted Directory"));

    my $dir = Gtk2::Entry->new;
    $dir->set_width_chars(20);

    my $pass = Gtk2::Entry->new;
    $pass->set_width_chars(15);
    $pass->set_visibility(0);

    my $repass = Gtk2::Entry->new;
    $repass->set_activates_default(1);
    $repass->set_width_chars(15);
    $repass->set_visibility(0);

    my $size = Gtk2::Entry->new;
    $size->set_width_chars(10);

    my $enc = Gtk2::OptionMenu->new_with_strings(\@ENCRYPTIONS);
    my $auto = Gtk2::OptionMenu->new_with_strings(\@YESNO);

            
    gtkpack($w->vbox,
	    gtkpack(gtkset_border_width(Gtk2::VBox->new(0, 6), 12),
		    new_pango_label(N("Directory Settings")),
		    gtkpack(Gtk2::HBox->new(0, 0),
			    new Gtk2::Label("    "),
                      create_packtable({ col_spacings => 12, row_spacings => 6 },
                                       [ new_table_label(N("Directory:")), $dir ],
                                       [ new_table_label(N("Size (in MB):")), $size ],
                                       [ new_table_label(N("Encryption:")), $enc ],
                                       [ new_table_label(N("Password:")), $pass ],
                                       [ new_table_label(N("Confirmation:")), $repass ],
                                       [ new_table_label(N("On boot")), $auto ],
                                      ),
                     ),
		    )
	    );

    $w->add_buttons('gtk-cancel', 'cancel', 'gtk-ok', 'ok');
    $w->set_default_response('ok');
    $w->show_all;

    while ($w->run eq 'ok')
    {
	if (length($dir->get_text) <= 0)
	{
	    show_warning(N("Directory name is empty."));
	    next;
	}

	if (-d $dir->get_text)
	{
	    show_warning(N("Directory already exists."));
	    next;
	}

	if (length($size->get_text) <= 0 || $size->get_text <= 0)
	{
	    show_warning(N("Wrong directory size."));
	    next;
	}

	if ($size->get_text > (MDK::Common::System::df("/home"))[1] / 1024)
	{
	    show_warning(N("Not enough disk space."));
	    next;
	}

	if (length($pass->get_text) < $MINPASSLEN)
	{
	    show_warning(N("Password is too short, should be %s characters at least.", $MINPASSLEN));
	    next;
	}

	if ($pass->get_text ne $repass->get_text)
	{
	    show_warning(N("Passwords mismatch."));
	    next;
	}

	# Everything looks good, so, go and create encrypted directory
	if (!create_and_save_new_directory($dir->get_text, $enc->entry->get_text, $size->get_text, $pass->get_text, $auto->get_text eq N("Yes")))
	{
	    mountloop($enc->entry->get_text, $dir->get_text, $pass->get_text);
	    
	    my $mounted = is_mounted($dir->get_text);
	    
	    $folders_model->append_set([ 0 => $enc->entry->get_text,
					 1 => $dir->get_text,
					 2 => $mounted ? N("mounted") : N("not mounted"),
					 3 => $auto->get_text,
					 ]);
	}

	last;
    }

    $w->destroy;
}

sub handle_remove_event() {
    my (undef, $iter) = $folders_tree->get_selection->get_selected;
    my $rdir = $folders_model->get($iter, 1);
    my $dir = "$ENV{HOME}/$rdir";

    my $w = new_dialog(N("Warning"));

    gtkpack($w->vbox,
	    gtkpack(gtkset_border_width(Gtk2::VBox->new(0, 6), 12),
		    Gtk2::Label->new(N("Following directory will be removed, do you want to continue?")),
		    Gtk2::Label->new($dir)
		    ));

    $w->add_buttons('gtk-no', 'no', 'gtk-yes', 'yes');

    $w->show_all;
    if ($w->run eq 'yes')
    {
	$folders_model->remove($iter);
	
	if (rmrf($dir))
	{
	    show_warning(N("Failed to remove directory %s.", $dir));
	}

	my @lines = grep { !m!$rdir/encfile! } cat_($CFGFILE);
	output($CFGFILE, @lines);
    }
    $w->destroy;
}
