#! /usr/bin/perl
# Universal script for MJohn
# 1) Wrapper part
# By default it works like wrapper around John the Ripper
# Sends attack descriptions to the server
# 2) Daemon part
# Daemon to report progress
# Collects and sends attack progress to the server
# Daemon should run to upload results. Start it and do not stop.
# 3) Server daemon part
# Server daemon to import data into request tracker

# !!! Experimental: this all is subject to change very soon !!!

# Dependencies: ssh, john, perl, unix (linux, *bsd, cygwin or any other)
# TODO: versions.
# TODO: list of perl modules (and packages).
# Server dependencies: sshd, perl, unix, request tracker.

# LICENSE
# Copyright � 2012 Aleksey Cherepanov <aleksey.4erepanov@gmail.com>.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted.
# END OF LICENSE
#
# Written with great help of Frank Dittrich

# Limitations: order of parameters should not affect behaviour, and
# many others not found yet. TODO

# Configuration
# TODO: review options.
my %C = qw'
              remote        con2
              remote_in_dir /home/aleksey/wrapper/shared
              remote_in     con2:/home/aleksey/wrapper/shared
              remote_out_dir /home/aleksey/wrapper/shared
              remote_out    con2:/home/aleksey/wrapper/shared
              user      alekseytest

              hash_files %s.john-uncracked

              conf      ~/.john/mjohn.conf
              store     ~/.john/mjohn-store
              john      ~/desktop/wrapper/echojohn.sh

              role      wrapper
              attack_name_generator q/name/.time()

              john_conf ~/desktop/magnum-jumbo/run/john.conf
              lair      no_lair
              action    start
              show      nothing
              server_delay          10
              daemon_delay          30
              auto_start_daemon     0
              transparent_wrapper   0
      ';
# TODO: remote, remote_in_dir, remote_in: one may be dropped.
# TODO: option to limit folder from which files could shared to avoid
# mistakes.
# TODO: support $JOHN/john.conf syntax for john_conf.
# TODO: add separate delay for server daemon. It should be smaller.
# TODO: enable auto_start_daemon. Disabled for debugging.
# TODO: role seems to be an abuse for config. Though it is nice to be
# able to set default behaviour in any value. For instance if have
# copies of this script with different names. Though it maybe useful
# to see argv[0] to determine name and adjust behaviour accordingly
# (just like busybox).

use strict;
use warnings;

use IPC::Run qw(run timeout);

use File::Copy;
use YAML qw(Dump LoadFile);

use Getopt::Long qw(:config pass_through);

# TODO: other better? Windows support? Does this one lock pidfile?
#use Proc::Daemon;

use Config::Simple;

use Digest::SHA qw(sha1_hex);

######################################################################
# Functions

# NOTE: .rec should be first to avoid race conditions on its removal.
my @extensions_for_run = qw/rec status pot log/;

# # TODO: daemons should be subscribed onto changes in their folder(s).
# sub user_daemon {
#     # TODO: option to not detach.
#     Proc::Daemon->new(
#         work_dir => $C{store},
#         child_STDOUT => '>>daemon.out',
#         child_STDERR => '>>daemon.err',
#         pid_file => 'daemon.pid'
#     );
# }
# sub start_user_daemon {
#     my $d = user_daemon();
#     # TODO: should not we check that daemon is already run?
#     # TODO: nice messages.
#     my $p = $d->Init;
#     unless ($p) {
#         die "not implemented";
#     }
# }
# sub stop_user_daemon {
#     my $d = user_daemon();
#     # TODO: send TERM for the first time.
#     $d->Kill_Daemon;
#     # TODO: some informative output?
# }

# sub server_daemon {
#     # TODO: option to not detach.
#     Proc::Daemon->new(
#         work_dir => $C{store},
#         child_STDOUT => '>>daemon.out',
#         child_STDERR => '>>daemon.err',
#         pid_file => 'daemon.pid'
#     );
# }
# sub start_server_daemon {
#     my $d = server_daemon();
#     my $p = $d->Init;
#     unless ($p) {
#         die "not implemented";
#     }
# }
# sub stop_server_daemon {
#     my $d = server_daemon();
#     # TODO: send TERM for the first time.
#     $d->Kill_Daemon;
#     # TODO: some informative output?
# }

sub expand_tilde {
    # TODO: windows portability.
    # TODO: logdir?
    $_[0] =~ s!^~([^/]*)!$1 ? (getpwnam($1))[7] : $ENV{HOME} || $ENV{LOGDIR}!e;
}


sub check_attack_name {
    unless (shift =~ /^([a-z0-9_-]+)\/([a-z0-9_-]+)\z/) {
        die "bad " . shift . "attack name, make it non-empty and use only a..z, 0..9, _, -, stopped"
    }
}

# Regexp to match sha1
my $sha1re = '[0-9a-f]{40}';
# TODO: errors?
# Returns: table of pairs sha1 => type, where type is file or aref
sub get_sha1_table {
    my %sha1;
    # TODO: scp maybe more preferable.
    #system 'scp', "$C{remote_in}/files", "$C{remote_in}/attacks", ".";
    # TODO: if we want to let shell expand tildes we should avoid quoting...
    # TODO: check for errors of ssh and ls.
    for (`ssh "$C{remote}" 'ls $C{remote_in_dir}/files.d'`) {
        if (/^($sha1re)\.(file|aref)$/) {
            # TODO: potentially we could have one sha1 with different extensions.
            $sha1{$1} = $2;
        } else {
            warn "could not parse line '$_' as sha1 reference";
        }
    }
    %sha1;
}

# Functions end
######################################################################

# Options handling

warn "ARGV: @ARGV";

# TODO: Usage info, in pod, me thinks.
if ($#ARGV == -1) {
    if ($C{transparent_wrapper}) {
        system $C{john};
    } else {
        # NOTE: transparent_wrapper is not very user friendly because
        # it means wrapper behaves exactly like john if there are no
        # specific arguments so user could be confused.
        # TODO: show usage info.
    }
}

# Parse wrapper's only options
# Defaults + Config file + Options = Resulting configuration

# Get path to config
GetOptions("config-conf=s", \$C{conf});
# Read config
expand_tilde($C{conf});
Config::Simple->import_from($C{conf}, \%C) or warn Config::Simple->error();

my %options;
for (keys %C) {
    # TODO: smarter types for options?
    # TODO: maybe --config name=value is better?
    $options{"config-$_=s"} = \$C{$_};
}
# TODO: config file option. --config is used by john.

# Attack name from user
# While attack_name_generator could provide name this option is a
# convenient way to assign meaningful name to attack.
# (attack_name_generator is passed to eval, so explicit name needs a
# quoting while this option does not it).
# TODO: usage info.
my $user_supplied_attack_name = '';
$options{"aname|attack_name=s"} = \$user_supplied_attack_name;

my $old_attack_name = '';
$options{"oaname|old_attack_name=s"} = \$old_attack_name;

# TODO: we could take some john's options if we use short form. Though
# there are no such options now.
GetOptions(%options);

# Expand tildes in config expect remote folders.
for (keys %C) {
    # TODO: we could want to expand tildes like on remote.
    expand_tilde $C{$_} unless /remote/
}
# TODO: expand relative paths.

# TODO: error on '--option-show='.
if ($C{show} ne 'nothing') {
    print "$C{$C{show}}";
    # TODO: error code on non-existence. Message on stderr.
    exit;
}

# Check config sanity

# TODO: check that on server side (too). For user names there could fs
# permissions but attack names it is not easy to do limitation: we
# could check on server and yell, but there is a time between folder
# creation and check so we need either other architecture (with
# staging) or clients should such folder on their own.

# TODO: more checks.
# NOTE: attack name could not be checked here.

# User name could contain only small Latin letters and could not be
# empty.
# TODO: there is a user with 0 in name.
unless ($C{user} =~ /^[a-z]+\z/) {
    die "use only small letters for user name";
}


# TODO: set store up if there is not one. Or at least exit if so.
# Create dir (with path) if there are no one. Clone remote repo there.
# TODO: if there is a store check if its remote address equals to our.
# TODO: check toplevel structure of the store.

# Other options is for john, remember them.
my @args = @ARGV;


# Distinguish roles

if ($C{role} ne 'wrapper' && @args) {
    # Error: unexpected args
    # TODO: usage.
}


# New name of attack
# TODO: easier name, maybe with incremental counter.
# TODO: this does not reflect real user+attack_name.
my $attack_name = "$C{user}/";
if ($user_supplied_attack_name ne '') {
    $attack_name .= $user_supplied_attack_name
} else {
    $attack_name .= eval($C{attack_name_generator});
}
# TODO: check that on server side too.
check_attack_name $attack_name, 'new';

# Each wrapper has its own store.
my $pwd = $ENV{PWD};
# TODO: set store up if there is not one.
chdir $C{store} or die "cannot cd to the store: $!, stopped";
sub get_full_path {
    # TODO: windows? Use library.
    my $arg = shift;
    expand_tilde $arg;
    $arg =~ /^\// ? $arg : "$pwd/$arg"
}


sub upload_run_files {
    my $session = shift;
    my $folder;
    if ($session =~ m!^[^/]+/[^/]+/!) {
        $folder = $&;
    } else {
        die "could not parse folder name";
    }
    system 'scp', (map { "$session.$_" } @extensions_for_run), "$C{remote_out}/$folder"
        and warn "scp fails: $!";
    unless (-r "$session.rec") {
        system "ssh '$C{remote}' 'rm $C{remote_out_dir}/$session.rec'";
    }
}

if ($C{role} eq 'wrapper') {

    # TODO: Parse options for john

    # TODO: warning if autostart is disabled and daemon is not runned.
    die "start daemon yourself" if $C{auto_start_daemon};
    #start_user_daemon if $C{auto_start_daemon};

    # Get list of attacks and files
    my %sha1 = get_sha1_table;

    sub download_file_by_sha1 {
        my $ref = shift;
        my $type = $sha1{$ref};
        if ($type eq 'file') {
            system "scp", "$C{remote_in}/files.d/$ref.file", "files.d/" and die "scp download failed";
        } else {
            die "unsupported sha1 reference type '$ref' for file, stopped";
        }
    }

    my %original_names;
    # Gets parameter's argument, returns path in store or false.
    sub replace_filename {
        # Argument is either filename, regular argument or sha1.
        # TODO: is it correct to use slashes everywhere?
        my $arg = shift;
        my $full_path = get_full_path $arg;
        my $new_path = "";
        if (-r $full_path) {
            if (`sha1sum -b "$full_path"` =~ /^($sha1re) /) {
                my $sha1sum = $1;
                # TODO: separate files by type: wordlists, configs, and so on.
                $new_path = "files.d/$sha1sum.file";
                if (-r $new_path) {
                    # TODO: add byte to byte comparison here if you're paranoid.
                    warn "file '$full_path' ($sha1sum) is already in the store";
                } else {
                    warn "file '$full_path' ($sha1sum) is about to be copied to the store";
                    # Copy file to store
                    # TODO: do files in parameters for wrapper need copies?
                    # TODO: fifos? ('copy' converts fifo into regular file)
                    # TODO: what if our file already in the store?
                    # TODO: use hardlinks if possible.
                    # TODO: as of we do not use git, we could avoid copying. Or use symlinks.
                    copy $full_path, $new_path or die "$!";
                    $original_names{$new_path} = $full_path;
                }
                # TODO: store symlink-like file into attack's subdir.
            }
        } elsif ($arg =~ /^$sha1re\z/) {
            # TODO: maybe some prefix for sha1 references? To make completion easier...
            $new_path = "files.d/$arg.file";
            if (-r $new_path) {
                # Nothing
            } elsif (exists $sha1{$arg}) {
                # TODO: we die on fail. Better/other design?
                download_file_by_sha1 $arg;
            } else {
                die "no file with such sha1 '$arg', stopped"
            }
        } else {
            # TODO: debug output, make an option to control this.
            # TODO: distinguish readability and existence.
            warn "file '$full_path' does not exist";
            return $arg;
        }
        $new_path
    }

    # Dump john's version and usage
    # TODO: john's version could be changed between runs. Lock? Copy?
    # TODO: it could be overwritten by other wrapper.
    #my $usage = "$attack_name/john.usage";
    # TODO: there is no $attack_name folder here.
    my $usage = "john.usage";
    system "$C{john} > $usage" and die "$!";
    # NOTE: we do note die on error because some johns may not support --list option.
    # TODO: die if you want. It is possible to limit support now.
    system "$C{john} --list=hidden-options >> $usage" and warn "$!";

    # Build expansion table for john's parameters
    my %expansion;
    {
        my %full_options;
        open my $f, '<', $usage;
        while (<$f>) {
            # TODO: not listed --make_check needs _ .
            # NOTE: --stdin and --pipe do not start at the begin of line.
            # TODO: check all other options to be matched by that pattern.
            while (/--[a-z-]+/g) {
                # NOTE: --wordlist is mentioned twice.
                # TODO: if one option is shorter than other (for
                # instance -a vs. -a-b) shorter one is dropped.
                unless (exists $full_options{$&}) {
                    $full_options{$&} = $&;
                    for (reverse 3 .. length $&) {
                        my $s = substr $&, 0, $_;
                        if (exists $expansion{$s}) {
                            $expansion{$s} = '';
                        } else {
                            $expansion{$s} = $&;
                        }
                    }
                }
            }
        }
        close $f;
    }


    expand_tilde $_ for @args;
    my @possible_hashfiles = grep { !/^-/ } @args;
    die "too many files with hashes" if @possible_hashfiles > 1;
    # TODO: $hashfile represents hash type.
    @args = grep { /^-/ } @args;
    die "hash type/file is mandatory" unless @possible_hashfiles;
    my $hashfile = shift @possible_hashfiles;


    for (@args) {
        # We use only one parameter-value separator (=).
        # John accepts only = and : as separater for parameters.
        # Also we use only "--".
        s/^--?/--/;
        s/^(--.*?)[=:]/$1=/;
    }
    # Expand short options.
    # TODO: retest after option expansion and sort?
    {
        for (@args) {
            if (/^--[a-z-]+/) {
                if (exists $expansion{$&}) {
                    my $expanded = $expansion{$&};
                    if ($expanded eq '') {
                        # If option could be expanded then test run should
                        # fail. If we are here then our code work not like
                        # john.
                        die "bug";
                    }
                    s/^\Q$&\E/$expanded/;
                } else {
                    # NOTE: wrapper could not pass not listed options to john.
                    die "Unknown option '$&', stopped";
                }
            }
        }
    }



    # TODO: suitable only for mini contest
    my @formats = qw(bf dynamic_22 mssql05 phps bsdi dynamic_23 mysql-sha1 raw-md5 des dynamic_28 nt raw-sha1 dynamic_12 md5 oracle11 raw-sha512 dynamic_16 mscash2 phpass salted-sha1);
    die "unknown format '$hashfile', use one of: @formats; stopped" unless $hashfile ~~ @formats;

    # Separate cosmetic arguments from others
    # TODO: check they are returned back in both test and real run.
    my @exclude_parameters = qw(dupe-suppression save-memory mem-file-size crack-status mkpc length fix-state-delay);
    # TODO: some of these parameters are used for our own purposes
    # while others just could not be handled right.
    my @deprecate_parameters = qw(config stdin pipe restore session make-charset status show stdout test users groups shells salts pot format list nolog max-run-time plugin help subformat field-separator-char log-stderr);
    # TODO: we do not handle default value for loopback, like any other default values.
    my @attack_if_have_any = qw(single wordlist loopback incremental markov external);
    my $is_attack = 0;
    my @cosmetic;
    for (@args) {
        my $a = $_;
        for (@attack_if_have_any) {
            if ($a =~ /^--\Q$_\E/) {
                $is_attack = 1;
            }
        }
        for (@deprecate_parameters) {
            if ($a =~ /^--\Q$_\E/) {
                die "could not handle '$a' argument";
            }
        }
        for (@exclude_parameters) {
            if ($a =~ /^--\Q$_\E/) {
                push @cosmetic, $a;
            }
        }
    }
    @args = grep { not $_ ~~ @cosmetic } @args;
    # TODO: just should not create attack, just run without tracking.
    # TODO: move it somewhere to avoid additional check $old_attack_name eq ''.
    if (!$is_attack and $old_attack_name eq '') {
        die "not an attack or batch mode used, specify attack mode";
    }


    # TODO: we abort on failed commands while it could be useful to
    # share them to provide newbies with help. Now there is lesser noise.
    # TODO: yet another place that differs for attack reuse.
    my $run_name = $old_attack_name eq '' ? $attack_name : $old_attack_name;
    $run_name .= "/$C{user}" . time();
    sub run_john {
        # TODO: system { $a[0] } @a ???
        system $C{john}, @_
            and warn "john failed";
    }
    # TODO: copy-pasting is evil!
    sub test_run_john {
        my @cmd = ($C{john}, @_);
        my ($in, $out, $err);
        run \@cmd, \$in, \$out, \$err, timeout(100);
        print "\ntest run output: \n'''$out'''\ntest run errors: \n'''$err'''\n";
        # TODO: put some condition here.
        if ($?) {
            unless ($err =~ /^Session stopped \(max run-time reached\)$/m) {
                die "test run of john failed";
            }
        }
    }

    # NOTE: it is not really cosmetic but should not be included into attack.
    push @cosmetic, "--format=$hashfile";
    my $file_to_crack = "$run_name.john.$hashfile";

    sub get_uncracked {
        # TODO: reduce download and check sha1 before? Though server should calculate sha1 once
        system 'scp', "$C{remote}:" . sprintf($C{hash_files}, $hashfile), $file_to_crack
            and die "scp failed on hashfile";
    }


    if ($old_attack_name eq '') {
        # New attack

        # Store parameters into file

        # Each attack has its own dir in store.
        # TODO: check if this attack already exists.
        mkdir "$C{user}" or warn "could not mkdir for user";
        mkdir "$attack_name" or die "could not mkdir for attack, try another name, stopped";
        # Check that all files are available and copy them into the store.
        # TODO: all paths should become relative to the store's root. To
        # make .rec files portable.
        for (@args) {
            if (/^(--.*?=)(.*)/) {
                # TODO: expand tildes.
                # TODO: handle config file too.
                # TODO: respect type of parameter! Do not copy file named 'all'
                # from original dir if we just call --incremental=all .
                # TODO: --make-charset does not need a copy.
                # Add file to the store and overwrite args with path in the store.
                # TODO: is it correct to use slashes everywhere?
                my ($parameter, $value) = ($1, $2);
                $_ = $parameter . replace_filename($value);
            }
            # TODO: we could need a copy of file with hashes to keep track
            # what hashes were attacked and what were not. Remember about
            # selectors like --users.
        }

        # TODO: use new patch for john to make smallest single-file config.
        # TODO: disabled as of broken.
        # my $john_conf = '';
        # if (-r $C{john_conf}) {
        #     # TODO: includes.
        #     # TODO: it could be useful to split config file into smaller
        #     # files to keep meaningful and common parts separated.
        #     $john_conf = replace_filename $C{john_conf}
        # } else {
        #     die "config is not readable, use --config-john_conf to specify config, stopped";
        # }

        # TODO: inhibit batch mode.


        # Check attack for existence
        my $attack_sha1 = sha1_hex join "\0", @args;
        warn "attack's sha1 is $attack_sha1";
        if (exists $sha1{$attack_sha1}) {
            if ($sha1{$attack_sha1} eq 'aref') {
                # Attack exists.
                # TODO: maybe use `ssh 'cat aref'` instead?
                system 'scp', "$C{remote_in}/files.d/$attack_sha1.aref", 'files.d'
                    and die "scp failed on .aref download";
                open my $f, '<', "files.d/$attack_sha1.aref" or die "could not read .aref";
                my $aref = <$f>;
                close $f;
                die "this attack is already defined, its name is '$aref'. Use --oaname=$aref to use it"
            } else {
                die "sha1 should aref"
            }
        }
        # Attack does not exist. Define new after test.

        {
            my $attack_parameters = "$attack_name/parameters";
            my $f;
            open $f, '>', $attack_parameters or die "could not open file $attack_parameters";
            # TODO: do we need to dump wrapper's config?
            # TODO: what if some options are doubled?
            # TODO: drop decorative parameters.
            print $f Dump sort @args;
            close $f;
        }

        get_uncracked;

        push @args, "--session=$run_name";
        push @args, "--pot=$run_name.pot";
        push @args, $file_to_crack;
        # TODO: we could not restore cosmetic options.
        push @args, @cosmetic;


        # Short test run
        # TODO: when we start attack by name we should run tests too.
        #if ($hashfile) {
        if (1) {
            # TODO: reduce .pot to one hash for test. Dump line by line
            # till we load it. Then load the whole file if it is not
            # possible to load one line.
            #open my $i, '<', get_full_path $hashfile or die "$!";
            open my $i, '<', $file_to_crack or die "$!";
            # TODO: comment lines?
            my $l = <$i>;
            close $i;
            # TODO: temp file.
            my $test_file = "$attack_name/test" . time();
            open my $o, '>', $test_file or die "$!";
            print $o $l;
            close $o;
            eval {
                # NOTE: forced exit by time gives error code.
                # TODO: it is the same as for all other errors. Patch john?
                test_run_john @args, $test_file,  "--max-run-time=1";
            };
            # TODO: better error handling.
            my $e = $@;
            unlink $test_file or die "$! ($e)";
            # TODO: should die here. Waits fix of exit code for --max-run-time.
            die $e if $e;
        } else {
            # Run as is because there is no hashfile.
            # TODO: 0 may be enough. It seems that 0 means "while time < 1".
            test_run_john @args, "--max-run-time=1";
        }

        # As soon as attack tested it is eligible to be shared.
        {
            my $aref = "files.d/$attack_sha1.aref";
            open my $f, '>', $aref;
            print $f $attack_name;
            close $f;
            system 'scp', $aref, "$C{remote_out}/files.d"
                and die "scp failed, could not upload .aref";
        }


    } else {
        # Old attack

        unless ($old_attack_name =~ /\//) {
            $old_attack_name = "$C{user}/$old_attack_name";
        }
        check_attack_name $old_attack_name, 'old';
        # TODO: do not we overwrite something?
        $attack_name = $old_attack_name;

        # TODO: offline mode?
        if (`ssh $C{remote} 'ls -d "$C{remote_in_dir}/$old_attack_name"'` eq '') {
            die "there is no such old attack '$old_attack_name', stopped";
        }

        my ($user, $attack) = split /\//, $old_attack_name;
        mkdir $user or warn "could not create user's subdir '$user' in store, not a problem";
        system 'scp', '-r', "$C{remote_in}/$old_attack_name", $user and die "scp failed";
        my @remote_files = `ssh '$C{remote}' 'cd $C{remote_in_dir} && find $old_attack_name'`;
        for (@remote_files) {
            # TODO: maybe chomp instead of \n?
            if (/^(.*?)\.log$/ && not "$1.rec\n" ~~ @remote_files) {
                unlink "$1.rec" or warn "tried to unlink '$1.rec' but failed";
            }
        }

        # TODO: remove this, restore does not need keys. Though we need to download files.
        # my @loaded_args = LoadFile("$old_attack_name/parameters");
        # for (@loaded_args) {
        #     # TODO: reg exp for parameter start (--.*?=) is used in other places too.
        #     s/^(--.*?=).*?($sha1re).file/$1$2/;
        # }
        # push @args, @loaded_args;

        # Pick best run files, copy and continue with them
        # TODO: finished attacks.
        my $max_candidates = 0;
        my $best_session = '';
        my %L = ('' => 1, 'K' => 1_000, 'M' => 1_000_000, 'G' => 1_000_000_000);
        for (<$attack_name/*.john.$hashfile>) {
            s/\.john\.$hashfile$//;
            if (-r "$_.rec") {
                my $t = $_;
                my $f;
                unless (open $f, '<', "$_.status") {
                    warn "could not open '$_.status'";
                    next;
                }
                my %l = ('' => 1, 'K' => 1_000, 'M' => 1_000_000, 'G' => 1_000_000_000);
                while (<$f>) {
                    if (/time: (\d+):(\d\d):(\d\d):(\d\d) /) {
                        # NOTE: not accurate but enough good.
                        my $time = $4 + $3 * 60 + $2 * 60 * 60 + $1 * 60 * 60 * 24;
                        # TODO: magic number of seconds that restart is reasonable after.
                        if ($time > 5) {
                            if (/c\/s: (\d+)(|K|M|G)/) {
                                my $candidates = $time * $1 * $L{$2};
                                if ($candidates > $max_candidates) {
                                    $max_candidates = $candidates;
                                    $best_session = $t;
                                }
                            }
                        }
                    }
                }
                close $f;
            } else {
                die "attack '$attack_name' is already finished for '$hashfile', aborted"
            }
        }

        if ($best_session ne '') {
            # Copy session
            for (@extensions_for_run) {
                copy "$best_session.$_", "$run_name.$_" or die "could not copy run file";
            }
            # TODO: I do not need to copy file with hashes. Delete
            # this. Session file refers old file. It is even better.
            # Though we'd be able to get newest file with uncracked
            # hashes and get speed up.
            # my @hashes = <$best_session.john.*>;
            # # TODO: 0 is ok?
            # die "too many .john files" if @hashes > 1;
            # if (@hashes) {
            #     $file_to_crack = "$run_name.john.$hashfile";
            #     copy $hashes[0], $file_to_crack or die "could not copy .john file";
            # }

            # Patch session file...
            # TODO: avoid touching session file. Use separate folders
            # for runs and cd into them. So session is isolated and
            # could be just copied.
            {
                my $f;
                open $f, '<', "$run_name.rec" or die "could not read";
                my @lines = <$f>;
                close $f;
                for (@lines) {
                    s/^(--session=|--pot=)\Q$best_session\E/$1$run_name/;
                }
                # TODO: could not I do it in one open?
                open $f, '>', "$run_name.rec" or die "could not write";
                print $f @lines;
                close $f;
            }

            @args = "--restore=$run_name";
        } else {
            # Create new session.
            get_uncracked;
            push @args, "--session=$run_name";
        }
    }

    # TODO: check that we do not dupe efforts.
    # TODO: force for duplicated attack.

    # Start john.

    # Push changes to server.
    # TODO: Pushing to server could be async.
    # TODO: quoting or restrictions on user/attack names: double quotes
    # are not allowed for user and attack names, also backslashes are not
    # allowed.
    # TODO: remember that file names could start with dash. Check all places.
    # TODO: one scp is better.
    system 'scp', '-r', $attack_name, "$C{remote_out}/$C{user}" and die "scp upload attack failed";
    for (keys %original_names) {
        # TODO: condition does not seem to be right.
        unless (-r $_) {
            system 'scp', $_, "$C{remote_out}/files.d" and die "scp upload failed";
        }
    }


    # Really run john.
    # TODO: do not count john without args or with help parameters as attack.
    # TODO: It would be nice to say what we are going to run.
    # TODO: pipes?
    # TODO: pipes and remote execution: does perl have a safe restricted mode?
    # TODO: it would be better to craft args list from saved parameters.
    # TODO: check that --session and --pot were not used before.
    # TODO: time is not a good unique number. Though uuid could be overkill.
    # TODO: add session and .pot files to git. The daemon could do it, though.
    # TODO: interactive status does not work.
    # TODO: track status: syntax ok, all hashes loaded, some hashes cracked.
    run_john @args;

    # John finished or aborted.
    # TODO: Report finish or abort

    upload_run_files $run_name;

} elsif ($C{role} eq 'daemon') {
    if ($C{action} eq 'start') {

        # We traverse our folders in the store.
        while (1) {
            # TODO: should not we pull here?
            # NOTE: do not chdir. Work in store's root to save file links in .pot's.
            for (<*/*/$C{user}*.log>) {
                s/\.log$//;
                my $session = $_;
                # Capture/calculate speed
                # TODO: is it all/enough?
                # TODO: should not we parse --status output right here?
                # TODO: dirty. Use other way to redirect output.
                # TODO: use john used for command, not just from config.
                # TODO: do not we need to pass --pot and --log options here?
                system qq/"$C{john}" --status="$session" 2> "$session.status"/;
                # TODO: look into logs.
                # TODO: see diff on .pot file.
                # TODO: capture speed, eta, what else?
                # Upload progress info: .status, .pot, .rec, .log
                # TODO: real quoting.
                # TODO: are all that file in all our dirs? Think about it.
                # TODO: failed commands does not have pot/rec/log files.
                # TODO: there could not be .rec file. Finished session does not have it.
                # TODO: $! does not seem to provide meaningful message here.
                upload_run_files $session;
            }
            # TODO: probably we should sleep delay minus time spent on work.
            warn "going to sleep at " . time();
            sleep $C{daemon_delay};
        }

    } elsif ($C{action} eq 'stop') {
        die "not implemented";
        #stop_user_daemon;
    } elsif ($C{action} eq 'status') {
        die "not implemented";
        # TODO
    } else {
        die "not implemented";
        # TODO: error: Unknown daemon action.
    }
} elsif ($C{role} eq 'server-worker') {
    if ($C{action} eq 'start') {
        # TODO: This daemon shares architecture with client daemon.

        # We traverse all folders in the lair.
        while (1) {
            `git pull`;
            for (<*/>) {
                # TODO: skip 'files.d'.
                chdir $_;
                # We add new attack.
                my $attack_name = $_;
                # Check if we already added that attack.
                # TODO: remember to export vars with credentials to access rt.
                # TODO: inefficient. We could get list of all once. Or we
                # could store status in file near, though it could become not
                # synced with rt.
                # TODO: 'No matches found' could be in field. Add ^ and $ and
                # test. Is there a machine readable interface?
                # TODO: rt ls 'subject=user1_name1341851801/'
                # -> Query:subject='user1_name1341851801'/ . "/" is in wrong place.
                if (`rt ls "subject='$attack_name'"` =~ 'No matches found') {
                    # No such attack. Add it.
                    # TODO: cats are nice pets. Do not use them so.
                    # TODO: text is not for parameters. Use custom fields instead!
                    # TODO: should not I add a ticket from user which did this attack?
                    `rt create -t ticket set 'subject=$attack_name' "text=\$(cat parameters)"`;
                }
                # TODO: We update speeds.
                chdir $C{lair};
            }
            # TODO: probably we should sleep during delay minus time spent on work.
            sleep $C{server_delay};
        }

    } elsif ($C{action} eq 'stop') {
        die "not implemented";
        #stop_server_daemon;
    } elsif ($C{action} eq 'status') {
        die "not implemented";
        # TODO
    } else {
        die "not implemented";
        # TODO: error: Unknown daemon action.
    }
} else {
    # Error: unknown role
    # TODO: usage
}

# TODO: right shebang.
# TODO: avoid backticks where possible.
# TODO: make sure system() does not call shell.

# TODO: pod with usage below.
__END__