diff --git a/.gitignore b/.gitignore index 1b09627..c16dfbb 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ *.swp sessions +init/bind9.cfg.tar.gz +init/varnamed.tar.gz diff --git a/www/MANIFEST b/MANIFEST similarity index 66% rename from www/MANIFEST rename to MANIFEST index 94e4168..c66174f 100644 --- a/www/MANIFEST +++ b/MANIFEST @@ -1,19 +1,23 @@ MANIFEST -bin/app.pl +cpanfile +Makefile.PL config.yml +MANIFEST.SKIP environments/development.yml environments/production.yml -views/index.tt -views/layouts/main.tt -MANIFEST.SKIP -lib/DNSManager.pm -public/css/style.css -public/css/error.css +t/001_base.t +t/002_index_route.t +public/500.html +public/favicon.ico +public/dispatch.cgi public/404.html public/dispatch.fcgi -public/js/jquery.min.js -public/dispatch.cgi -public/500.html -t/002_index_route.t -t/001_base.t -Makefile.PL +public/images/perldancer-bg.jpg +public/images/perldancer.jpg +public/javascripts/jquery.js +public/css/error.css +public/css/style.css +bin/app.psgi +views/index.tt +views/layouts/main.tt +lib/MyWeb/App.pm diff --git a/www/MANIFEST.SKIP b/MANIFEST.SKIP similarity index 75% rename from www/MANIFEST.SKIP rename to MANIFEST.SKIP index 8fd3d29..0ecdc1e 100644 --- a/www/MANIFEST.SKIP +++ b/MANIFEST.SKIP @@ -11,3 +11,7 @@ Makefile$ ^cover_db ^.*\.log ^.*\.swp$ +MYMETA.* +^.gitignore +^.svn\/ +^MyWeb-App- diff --git a/www/Makefile.PL b/Makefile.PL similarity index 52% rename from www/Makefile.PL rename to Makefile.PL index d1cd3b4..e56985f 100644 --- a/www/Makefile.PL +++ b/Makefile.PL @@ -2,20 +2,25 @@ use strict; use warnings; use ExtUtils::MakeMaker; +# Normalize version strings like 6.30_02 to 6.3002, +# so that we can do numerical comparisons on it. +my $eumm_version = $ExtUtils::MakeMaker::VERSION; +$eumm_version =~ s/_//; + WriteMakefile( - NAME => 'DNSManager', + NAME => 'MyWeb::App', AUTHOR => q{YOUR NAME }, - VERSION_FROM => 'lib/DNSManager.pm', + VERSION_FROM => 'lib/MyWeb/App.pm', ABSTRACT => 'YOUR APPLICATION ABSTRACT', - ($ExtUtils::MakeMaker::VERSION >= 6.3002 + ($eumm_version >= 6.3001 ? ('LICENSE'=> 'perl') : ()), PL_FILES => {}, PREREQ_PM => { 'Test::More' => 0, 'YAML' => 0, - 'Dancer' => 1.311, + 'Dancer2' => 0.161000, }, dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, - clean => { FILES => 'DNSManager-*' }, + clean => { FILES => 'MyWeb-App-*' }, ); diff --git a/app/app.pm b/app/app.pm deleted file mode 100644 index f0ab12a..0000000 --- a/app/app.pm +++ /dev/null @@ -1,271 +0,0 @@ -#!/usr/bin/env perl - -use v5.14; -use DBI; - -use lib '../'; -use app::zone::interface; -use app::zone::edit; -use app::zone::rndc_interface; -use app::bdd::management; -use app::bdd::admin; -use app::bdd::lambda; - -package app; -use Moose; - -has dbh => ( is => 'rw', builder => '_void'); -has dnsi => ( is => 'rw', builder => '_void'); -has dnsisec => ( is => 'rw', builder => '_void'); -has um => ( is => 'rw', builder => '_void'); -has [ qw/zdir dbname dbhost dbport dbuser dbpass sgbd dnsapp dnsappsec sshhost sshhostsec sshuser sshusersec sshport sshportsec nsmasterv4 nsmasterv6 nsslavev4 nsslavev6 dnsslavekey/ ] => qw/is ro required 1/; -sub _void { my $x = ''; \$x; } - -### users - -sub init { - my ($self) = @_; - - my $success; - - my $dsn = 'dbi:' . $self->sgbd - . ':database=' . $self->dbname - . ';host=' . $self->dbhost - . ';port=' . $self->dbport; - - ${$self->dbh} = DBI->connect($dsn - , $self->dbuser - , $self->dbpass) - || die "Could not connect to database: $DBI::errstr"; - - ($success, ${$self->dnsi}) = app::zone::interface ->new() - ->get_interface($self->dnsapp, $self); - - die("zone interface") unless $success; - - ($success, ${$self->dnsisec}) = app::zone::interface ->new() - ->get_interface($self->dnsappsec, $self); - - die("zone interface (secondary ns)") unless $success; - - ${$self->um} = app::bdd::management->new(dbh => ${$self->dbh}); -} - -sub auth { - my ($self, $login, $passwd) = @_; - ${$self->um}->auth($login, $passwd); -} - -sub register_user { - my ($self, $login, $passwd) = @_; - ${$self->um}->register_user($login, $passwd); -} - -sub set_admin { - my ($self, $login, $val) = @_; - ${$self->um}->set_admin($login, $val); -} - -sub update_passwd { - my ($self, $login, $new) = @_; - my ($success, $user, $isadmin) = ${$self->um}->get_user($login); - $user->passwd($new); -} - -sub delete_user { - my ($self, $login) = @_; - my ($success, @domains) = $self->get_domains($login); - - if($success) { - $self->delete_domain($login, $_) foreach(@domains); - ${$self->um}->delete_user($login); - } -} - -### domains - -sub _get_zone_edit { - my ($self, $domain) = @_; - - return app::zone::edit->new( - zname => $domain - , data => $self ); -} - -# return yes or no -sub add_domain { - my ($self, $login, $domain) = @_; - my ($success, $user, $isadmin) = ${$self->um}->get_user($login); - - unless($success) { - return 0; - } - - unless ($user->add_domain($domain)) { - return 0; - } - - my $ze = $self->_get_zone_edit($domain); - $ze->addzone(); -} - -sub delete_domain { - my ($self, $login, $domain) = @_; - - my ($success, $user, $isadmin) = ${$self->um}->get_user($login); - - return 0 unless $success; - return 0 unless $user->delete_domain($domain); - - my $ze = $self->_get_zone_edit($domain); - $ze->del(); - - 1; -} - -sub update_domain_raw { - my ($self, $zone, $domain) = @_; - - my $ze = $self->_get_zone_edit($domain); - $ze->update_raw($zone); -} - -sub update_domain { - my ($self, $zone, $domain) = @_; - my $ze = $self->_get_zone_edit($domain); - $ze->update($zone); -} - -sub get_domain { - my ($self, $domain) = @_; - my $ze = $self->_get_zone_edit($domain); - $ze->get(); -} - -sub get_domains { - my ($self, $login) = @_; - ${$self->um}->get_domains($login); -} - -sub get_all_domains { - my ($self) = @_; - # % domain login - ${$self->um}->get_all_domains; -} - -sub get_all_users { - my ($self) = @_; - # % login admin - ${$self->um}->get_all_users; -} - -sub new_tmp { - my ($self, $domain) = @_; - my $ze = $self->_get_zone_edit($domain); - $ze->new_tmp(); -} - -sub _mod_entry { - my ($self, $domain, $entryToDelete, $action, $newEntry) = @_; - - my $name = $entryToDelete->{'name'}; - my $type = $entryToDelete->{'type'}; - my $ttl = $entryToDelete->{'ttl'}; - my $host = $entryToDelete->{'host'}; - my $priority = $entryToDelete->{'priority'}; - - my $new_name = $newEntry->{'newname'}; - my $new_type = $newEntry->{'newtype'}; - my $new_ttl = $newEntry->{'newttl'}; - my $new_host = $newEntry->{'newhost'}; - my $new_priority = $newEntry->{'newpriority'}; - - # say "in _mod_entry : $action"; - # say "in _mod_entry : $new_name"; - my $zone = $self->get_domain($domain); - my $dump = $zone->dump; - - my $record; - my $found = 0; - - given( lc $type ) - { - when ('a') - { - $record = $zone->a; - $found = 1; - } - when ('aaaa') - { - $record = $zone->aaaa; - $found = 1; - } - when ('cname') - { - $record = $zone->cname; - $found = 1; - } - when ('ns') - { - $record = $zone->ns; - $found = 1; - } - when ('mx') - { - $record = $zone->mx; - $found = 1; - } - when ('ptr') - { - $record = $zone->ptr; - $found = 1; - } - } - - if( $found ) - { - - foreach my $i ( 0 .. scalar @{$record}-1 ) - { - - if( $action eq 'del' ) - { - delete $record->[$i] - if( $record->[$i]->{'name'} eq $name && - $record->[$i]->{'host'} eq $host && - $record->[$i]->{'ttl'} == $ttl ); - } - if ( $action eq 'mod' ) - { - if( $record->[$i]->{'name'} eq $name && - $record->[$i]->{'host'} eq $host && - $record->[$i]->{'ttl'} == $ttl ) - { - $record->[$i]->{'name'} = $new_name; - $record->[$i]->{'host'} = $new_host; - $record->[$i]->{'ttl'} = $new_ttl; - if( defined $new_priority ) - { - $record->[$i]->{'priority'} = $new_priority - } - } - } - - } - - } - - $self->update_domain( $zone, $domain ); -} - -sub delete_entry { - my ($self, $domain, $entryToDelete) = @_; - $self->_mod_entry( $domain, $entryToDelete, 'del' ); -} - -sub modify_entry { - my ($self, $domain, $entryToDelete, $newEntry) = @_; - $self->_mod_entry( $domain, $entryToDelete, 'mod', $newEntry ); -} - -1; diff --git a/app/bdd/admin.pm b/app/bdd/admin.pm deleted file mode 100644 index 845a3bf..0000000 --- a/app/bdd/admin.pm +++ /dev/null @@ -1,33 +0,0 @@ -package app::bdd::admin; -use Moose; -extends 'app::bdd::lambda'; - -# ($success) activate_zone($domain) -sub activate_zone { - my ($self, $domain) = @_; -} - -# ($success) delete_zone($file_path) -sub delete_zone { - my ($self, $domain) = @_; -} - -# $success delete_domain -sub delete_domain { - my ($self, $domain) = @_; - my $sth; - - $sth = $self->dbh->prepare('delete from domain where domain=?'); - unless ( $sth->execute($domain) ) { - $sth->finish(); - return 0; - } - - $sth->finish(); - # delete the domain from our domains - @{ $self->domains } = grep { $_ ne $domain } @{ $self->domains }; - return 1; -} - - -1; diff --git a/app/bdd/lambda.pm b/app/bdd/lambda.pm deleted file mode 100644 index 4b793be..0000000 --- a/app/bdd/lambda.pm +++ /dev/null @@ -1,78 +0,0 @@ -use autodie; -use v5.14; -use DBI; - -use Data::Dump "dump"; - -use lib '../../'; -package app::bdd::lambda; -use Moose; - -has qw/domains is rw/; -has [ qw/login dbh/ ] => qw/is ro required 1/; -has passwd => (is => 'rw', trigger => \&_update_passwd ); -#has qw/dbh is ro required 1/; # database handler - -# $success delete_domain -sub delete_domain { - my ($self, $domain) = @_; - my $sth; - - # check if we are the owner then delete - return 0 if (grep { $domain eq $_ } @{ $self->domains }) == 0; - - $sth = $self->dbh->prepare('delete from domain where domain=?'); - unless ( $sth->execute($domain) ) { - $sth->finish(); - return 0; - } - - $sth->finish(); - # delete the domain from our domains - @{ $self->domains } = grep { $_ ne $domain } @{ $self->domains }; - return 1; -} - - -# $success add_domain -sub add_domain { - my ($self, $domain) = @_; - my ($sth); - - $sth = $self->dbh->prepare('select domain from domain where domain=?'); - unless ( $sth->execute($domain) ) { - $sth->finish(); - return 0; - } - - # if the domain already exists - if (my $ref = $sth->fetchrow_arrayref) { - $sth->finish(); - return 0; - } - - $sth = $self->dbh->prepare('insert into domain VALUES(?,?,?)'); - unless ( $sth->execute($domain, $self->login, 0) ) { - $sth->finish(); - return 0; - } - - $sth->finish(); - push @{ $self->domains }, $domain; - return 1; -} - -sub _update_passwd { - my ($self, $new) = @_; - my $sth; - - $sth = $self->dbh->prepare('update user set passwd=? where login=?'); - unless ( $sth->execute($new, $self->login) ) { - $sth->finish(); - return 0; - } - $sth->finish(); - return 1; -} - -1; diff --git a/app/bdd/management.pm b/app/bdd/management.pm deleted file mode 100644 index 0478708..0000000 --- a/app/bdd/management.pm +++ /dev/null @@ -1,194 +0,0 @@ -use Modern::Perl; -use autodie; -use v5.14; -use DBI; - -use lib '../'; -use app::bdd::lambda; -use app::bdd::admin; -use app::zone::interface; - -package app::bdd::management; -use Moose; - -has [qw/dbh/] => qw/is rw required 1/; - -# ($success, $user, $admin) auth_user($login, $passwd) -sub auth { - my ($self, $login, $passwd) = @_; - my ($sth, $success, $user, $isadmin); - - $sth = $self->dbh->prepare('SELECT * FROM user WHERE login=? and passwd=?'); - unless ($sth->execute($login, $passwd)) { - $sth->finish(); - return 0; - } - - if (my $ref = $sth->fetchrow_arrayref) { - # if this user exists and is auth - ($success, $user, $isadmin) = $self->get_user($login); - } - else { - $success = 0; - } - - $sth->finish(); - return ($success, $user, $isadmin); -} - -# ($success) register_user -sub register_user { - my ($self, $login, $pass) = @_; - - my $sth = $self->dbh->prepare('select * from user where login=?'); - unless ( $sth->execute($login) ) { - $sth->finish(); - return 0; - } - - # if an user already exists - if (my $ref = $sth->fetchrow_arrayref) { - $sth->finish(); - return 0; - } - - # if not - $sth = $self->dbh->prepare('insert into user VALUES(?,?,?)'); - unless ($sth->execute($login, $pass, 0)) { - $sth->finish(); - return 0; - } - $sth->finish(); - - return 1; -} - -# ($success) delete_user -sub delete_user { - my ($self, $login) = @_; - my $sth; - - # TODO : vérifier que ça renvoie la bonne valeur - $sth = $self->dbh->prepare('delete from user where login=?'); - unless ( $sth->execute($login) ) { - $sth->finish(); - return 0; - } - $sth->finish(); - - return 1; -} - -sub get_user { - my ($self, $login) = @_; - my ($sth, $user, @domains); - - $sth = $self->dbh->prepare('SELECT * FROM user WHERE login=?'); - unless ( $sth->execute($login)) { - $sth->finish(); - return 0; - } - - if (my $ref = $sth->fetchrow_arrayref) { - $sth = $self->dbh->prepare('SELECT domain FROM domain WHERE login=?'); - unless ( $sth->execute($login)) { - $sth->finish(); - return 0; - } - - while(my $ref2 = $sth->fetchrow_arrayref) { - push @domains, @$ref2[0]; - } - - # si admin - if(@$ref[2]) { - $user = app::bdd::admin->new(login => @$ref[0] - , passwd => @$ref[1] - , dbh => $self->dbh - , domains => [@domains]); - - } - else { - $user = app::bdd::lambda->new(login => @$ref[0] - , passwd => @$ref[1] - , dbh => $self->dbh - , domains => [@domains]); - } - - $sth->finish(); - return (1, $user, @$ref[2]); - } - - $sth->finish(); - return 0; -} - -sub get_domains { - my ($self, $login) = @_; - my ($sth, @domains); - - $sth = $self->dbh->prepare('SELECT domain FROM domain where login=?'); - unless ($sth->execute($login)) { - $sth->finish(); - return (0, @domains); - } - - while(my $ref = $sth->fetchrow_arrayref) { - push @domains, @$ref[0]; - } - - $sth->finish(); - - return (1, @domains); -} - -sub get_all_domains { - my ($self) = @_; - my ($sth, %domains); - - $sth = $self->dbh->prepare('SELECT domain, login FROM domain'); - unless ( $sth->execute()) { - $sth->finish(); - undef; - } - - while( my $ref = $sth->fetchrow_arrayref) { - $domains{@$ref[0]} = @$ref[1]; - } - - $sth->finish(); - %domains; -} - -sub get_all_users { - my ($self) = @_; - my ($sth, %users); - - $sth = $self->dbh->prepare('SELECT login, admin FROM user'); - unless ( $sth->execute()) { - $sth->finish(); - undef; - } - - while( my $ref = $sth->fetchrow_arrayref) { - $users{@$ref[0]} = @$ref[1]; - } - - $sth->finish(); - %users; -} - -sub set_admin { - my ($self, $login, $val) = @_; - - my $sth = $self->dbh->prepare('update user set admin=? where login=?'); - unless ( $sth->execute( $val, $login) ) { - $sth->finish(); - return 0; - } - - $sth->finish(); - return 1; -} - -1; diff --git a/app/zone/edit.pm b/app/zone/edit.pm deleted file mode 100644 index f64623e..0000000 --- a/app/zone/edit.pm +++ /dev/null @@ -1,196 +0,0 @@ -use Modern::Perl; -use Data::Dump "dump"; -use DNS::ZoneParse; -use File::Copy; -use Net::OpenSSH; -use Net::SSH q; -use v5.14; - -use lib '../../'; -use app::zone::interface; -package app::zone::edit; -use Moose; - -has [ qw/zname data/ ] => qw/is ro required 1/; - -sub get { - my ($self) = @_; - my $dest = '/tmp/' . $self->zname; - my $file = $self->data->zdir.'/'.$self->zname; - - $self->_scp_get($file, $dest); - DNS::ZoneParse->new($dest, $self->zname); -} - -=pod - copie du template pour créer une nouvelle zone - update du serial - ajout de la zone via dnsapp (rndc, knot…) - retourne la zone + le nom de la zone -=cut - -sub addzone { - my ($self) = @_; - - my $tpl = $self->data->zdir."/tpl.zone"; - my $tmpfile = '/tmp/'.$self->zname; - - $self->_scp_get($tpl, $tmpfile); # get the template - $self->_sed($tmpfile); # sed CHANGEMEORIGIN by the real origin - - my $zonefile = DNS::ZoneParse->new($tmpfile, $self->zname); - $zonefile->new_serial(); # update the serial number - - # write the new zone tmpfile to disk - my $newzone; - open($newzone, '>', $tmpfile) or die "error"; - print $newzone $zonefile->output(); - close $newzone; - - my $file = $self->data->zdir.'/'.$self->zname; - $self->_scp_put($tmpfile, $file); # put the final zone on the server - unlink($tmpfile); # del the temporary file - - # add new zone on the primary ns - my $prim = app::zone::interface->new() - ->get_interface($self->data->dnsapp, $self->data); - $prim->addzone($self->data->zdir, $self->zname); - - # add new zone on the secondary ns - my $sec = app::zone::interface->new() - ->get_interface($self->data->dnsappsec, $self->data); - $sec->reload_sec(); - - return $zonefile; -} - -=pod - màj du serial - push reload de la conf -=cut - -sub update { - my ($self, $zonefile) = @_; - - # update the serial number - $zonefile->new_serial(); - - my $tmpfile = '/tmp/' . $self->zname; - - # write the new zone tmpfile to disk - my $newzone; - open($newzone, '>', $tmpfile) or die "error"; - print $newzone $zonefile->output(); - close $newzone; - - my $file = $self->data->zdir.'/'.$self->zname; - $self->_scp_put($tmpfile, $file); # put the final zone on the server - unlink($tmpfile); # del the temporary file - - my $prim = app::zone::interface->new() - ->get_interface($self->data->dnsapp, $self->data); - $prim->reload($self->zname); - 1; -} - -=pod - udpate via the raw content of the zonefile -=cut - -sub update_raw { - my ($self, $zonetext) = @_; - - my $zonefile; - my $file = '/tmp/'.$self->zname; - - # write the updated zone file to disk - my $newzone; - open($newzone, '>', $file) or die "error"; - print $newzone $zonetext; - close $newzone; - - eval { $zonefile = DNS::ZoneParse->new($file, $self->zname); }; - - if( $@ ) { - unlink($file); - 0; - } - - unlink($file); - - $self->update($zonefile); -} - -# sera utile plus tard, pour l'interface -sub new_tmp { - my ($self) = @_; - - my $tpl = $self->data->zdir."/tpl.zone"; - my $file = '/tmp/'.$self->zname; - - $self->_scp($tpl, $file); - $self->_sed($file); - - my $zonefile = DNS::ZoneParse->new($file, $self->zname); - $zonefile->new_serial(); # update the serial number - - unlink($file); - - return $zonefile; -} - -sub _cp { - my ($self, $src, $dest) = @_; - - File::Copy::copy($src, $dest) or die "Copy failed: $! ($src -> $dest)"; -} - -sub _scp_put { - my ($self, $src, $dest) = @_; - - my $co = $self->data->sshuser . '@' . $self->data->sshhost . ':' . $self->data->sshport; - my $ssh = Net::OpenSSH->new($co); - $ssh->scp_put($src, $dest) or die "scp failed: " . $ssh->error; -} - -sub _scp_get { - my ($self, $src, $dest) = @_; - - my $co = $self->data->sshuser . '@' . $self->data->sshhost . ':' . $self->data->sshport; - my $ssh = Net::OpenSSH->new($co); - $ssh->scp_get($src, $dest) or die "scp failed: " . $ssh->error; -} - -sub _sed { - my ($self, $file) = @_; - my $orig = $self->zname; - my $cmd = qq[sed -i "s/CHANGEMEORIGIN/$orig/" $file 2>/dev/null 1>/dev/null]; - - system($cmd); -} - -sub del { - my ($self) = @_; - my $prim = app::zone::interface->new() - ->get_interface($self->data->dnsapp, $self->data); - $prim->delzone($self->data->zdir, $self->zname); - $prim->reconfig(); - - my $sec = app::zone::interface->new() - ->get_interface($self->data->dnsappsec, $self->data); - $sec->reload_sec(); - - my $file = $self->data->zdir.'/'.$self->zname; - my $host = $self->data->sshhost; - my $user = $self->data->sshuser; - my $cmd = "rm $file"; - - Net::SSH::sshopen2("$user\@$host", *READER, *WRITER, "$cmd") || die "ssh: $!"; - - close(READER); - close(WRITER); - - 1; -} - -1; diff --git a/app/zone/interface.pm b/app/zone/interface.pm deleted file mode 100644 index 908d429..0000000 --- a/app/zone/interface.pm +++ /dev/null @@ -1,16 +0,0 @@ -use lib '../../'; -use app::zone::rndc_interface; -use app::zone::knot_interface; -use app::zone::nsdc_interface; -package app::zone::interface; -use Moose; - -sub get_interface { - my ($self, $type, $data) = @_; - return 1, app::zone::rndc_interface->new(data => $data) if $type eq 'rndc'; - return 1, app::zone::knot_interface->new(data => $data) if $type eq 'knot'; - return 1, app::zone::nsdc_interface->new(data => $data) if $type eq 'nsdc'; - return 0; -} - -1; diff --git a/app/zone/nsdc_interface.pm b/app/zone/nsdc_interface.pm deleted file mode 100644 index ad73a7e..0000000 --- a/app/zone/nsdc_interface.pm +++ /dev/null @@ -1,117 +0,0 @@ -use v5.14; -package app::zone::nsdc_interface; -use Moose; - -has [ qw/data/ ] => qw/is ro required 1/; - -# on suppose que tout est déjà mis à jour dans le fichier -sub reload_sec { - my ($self) = @_; - - $self->_reload_conf(); - - system('ssh -p ' . $self->data->sshportsec . ' ' - . $self->data->sshusersec . '@' . $self->data->sshhostsec - . ' "sudo nsdc rebuild 2>/dev/null 1>/dev/null && sudo nsdc restart 2>/dev/null 1>/dev/null "'); -} - -sub _reload_conf { - my ($self) = @_; - - # get the file - # modify the file - # push the file - my $f = "/tmp/nsd.conf"; - - _scp_get($self->data->sshusersec - , $self->data->sshhostsec - , $self->data->sshportsec - , "/etc/nsd3/nsd.conf" - , $f); - - my %slavedzones = $self->data->get_all_domains(); - - my $data = read_file($f); - my $debut = "## BEGIN_GENERATED"; - my $nouveau = ''; - - for(keys %slavedzones) { - $nouveau .= "zone:\n\n\tname: \"$_\"\n" - . "\tzonefile: \"slave/$_\"\n\n"; - - # allow notify & request xfr, v4 & v6 - $nouveau .= - "\tallow-notify: " . $self->data->nsmasterv4 . ' ' . $self->data->dnsslavekey . "\n" - . "\trequest-xfr: " . $self->data->nsmasterv4 . ' ' . $self->data->dnsslavekey . "\n\n"; - - $nouveau .= - "\tallow-notify: " . $self->data->nsmasterv6. ' ' . $self->data->dnsslavekey . "\n" - . "\trequest-xfr: " . $self->data->nsmasterv6. ' ' . $self->data->dnsslavekey . "\n\n"; - } - - $data =~ s/$debut.*/$debut\n$nouveau/gsm; - - write_file($f, $data); - - system('ssh -p ' . $self->data->sshportsec . ' ' - . $self->data->sshusersec . '@' . $self->data->sshhostsec - . ' "sudo nsdc patch 2>/dev/null 1>/dev/null && sudo rm /var/nsd3/ixfr.db"'); - - _scp_put($self->data->sshusersec - , $self->data->sshhostsec - , $self->data->sshportsec - , $f - , "/etc/nsd3/"); -} - -sub _scp_get { - my ($user, $host, $port, $src, $dest) = @_; - - my $co = $user . '@' . $host . ':' . $port; - my $ssh = Net::OpenSSH->new($co); - $ssh->scp_get($src, $dest) or die "scp failed: " . $ssh->error; -} - -sub _scp_put { - my ($user, $host, $port, $src, $dest) = @_; - - my $co = $user . '@' . $host . ':' . $port; - my $ssh = Net::OpenSSH->new($co); - $ssh->scp_put($src, $dest) or die "scp failed: " . $ssh->error; -} - -sub reconfig { - my ($self, $zname) = @_; - die "not implemented"; - #system("nsdc reconfig 2>/dev/null 1>/dev/null"); -} - -sub delzone { - my ($self) = @_; - die "not implemented"; - #system("nsdc delzone $zname 2>/dev/null 1>/dev/null"); -} - -sub read_file { - my ($filename) = @_; - - open my $entree, '<:encoding(UTF-8)', $filename or - die "Impossible d'ouvrir '$filename' en lecture : $!"; - local $/ = undef; - my $tout = <$entree>; - close $entree; - - return $tout; -} - -sub write_file { - my ($filename, $data) = @_; - - open my $sortie, '>:encoding(UTF-8)', $filename or die "Impossible d'ouvrir '$filename' en écriture : $!"; - print $sortie $data; - close $sortie; - - return; -} - -1; diff --git a/app/zone/rndc_interface.pm b/app/zone/rndc_interface.pm deleted file mode 100644 index 9034e6f..0000000 --- a/app/zone/rndc_interface.pm +++ /dev/null @@ -1,41 +0,0 @@ -use v5.14; -package app::zone::rndc_interface; -use Moose; - -has [ qw/data/ ] => qw/is ro required 1/; - -# on suppose que tout est déjà mis à jour dans le fichier -sub reload { - my ($self, $zname) = @_; - system("rndc reload $zname 2>/dev/null 1>/dev/null"); - system("rndc notify $zname 2>/dev/null 1>/dev/null"); -} - -sub addzone { - my ($self, $zdir, $zname, $opt) = @_; - - my $command = "rndc addzone $zname "; - - if(defined $opt) { - $command .= "'$opt'"; - } - else { - $command .= "'{ type master; file \"$zdir/$zname\"; allow-transfer { ". $self->data->nsslavev4 . '; '. $self->data->nsslavev6 . "; }; notify yes; };'"; - } - - $command .= " 2>/dev/null 1>/dev/null"; - system($command); - -} - -sub reconfig { - my ($self, $zname) = @_; - system("rndc reconfig 2>/dev/null 1>/dev/null"); -} - -sub delzone { - my ($self, $zdir, $zname) = @_; - system("rndc delzone $zname 2>/dev/null 1>/dev/null"); -} - -1; diff --git a/bin/app.psgi b/bin/app.psgi new file mode 100755 index 0000000..f77d9f8 --- /dev/null +++ b/bin/app.psgi @@ -0,0 +1,8 @@ +#!/usr/bin/env perl + +use strict; +use warnings; +use FindBin; +use lib "$FindBin::Bin/../lib"; +use MyWeb::App; +MyWeb::App->to_app; diff --git a/cli/README.markdown b/cli/daemon/README.markdown similarity index 100% rename from cli/README.markdown rename to cli/daemon/README.markdown diff --git a/cli/daemon/ca.cert b/cli/daemon/ca.cert new file mode 100644 index 0000000..1e4e737 --- /dev/null +++ b/cli/daemon/ca.cert @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFUDCCAzgCCQDV/QJQr9sX7DANBgkqhkiG9w0BAQsFADBqMQswCQYDVQQGEwJG +UjEPMA0GA1UECAwGQWxzYWNlMRMwEQYDVQQHDApTdHJhc2JvdXJnMRIwEAYDVQQD +DAluZXRsaWIucmUxITAfBgkqhkiG9w0BCQEWEmthcmNobnVAa2FyY2hudS5mcjAe +Fw0xNDA4MjMwODAwNThaFw0xNTA4MjMwODAwNThaMGoxCzAJBgNVBAYTAkZSMQ8w +DQYDVQQIDAZBbHNhY2UxEzARBgNVBAcMClN0cmFzYm91cmcxEjAQBgNVBAMMCW5l +dGxpYi5yZTEhMB8GCSqGSIb3DQEJARYSa2FyY2hudUBrYXJjaG51LmZyMIICIjAN +BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAxaqx8ToKV4lxqOJa+h7WA9qh7ygk +3JGOx6EEquL29YUfZXvKHjA3fNUIAXzlUJZXferLO/w62t4M9Lha0beOuM3gQvXz +RzeGAgzeEAfDN892+GIYwf7e4a6VQFU99Bb6/cetnqFSw9PiFIC1XpG/lCSR4INd +8msGuW6YIJ30AYE2PWNhn8C44szpe4xzQrv5omJsRC4+DXe9yLAdurbvUXzEijE3 +3rii+vlj52awfsCwxGDh8oblm/ir0ISBqTpq6V/xsCD8S4L9c80HeqGzmcF1LpEl +dpcSDKLCXLszwKJz/aNiUxPq4m7IWe0av87hUGunzDT9+NUwWcsqHBdAuACQ6xK+ +j3ZtzoiZtPDKLyfdKc4kgx1heiWLn6KCSqgHmfYLQiT7o0kYpOMM7wCjfkMAFI7G +LCv5Vx3hgx2erjwnWKXCt+QiUISvzNs9kP3DhcOKtKr4bzEwW8CCML+qnftCjb4w ++TLKIQNTUlp2I+To2VCnywylFyqOGxR7NKapSjoUUKyCkjcUpNUojy3sLsc1/QeM +YRYvOyQpU+xxCGrAQjf+AE0G8QEPPry2MOLGJvg2BUJstgn2IEoYPL7uwtRSwh8b +VtXdXX5kAdKj00XBEKOEJ0rRq/ahVTsJaC5ndvcicnp5oMbb5xx0Uvhr+SjqbOo1 +VU1qXIbGq87lMu0CAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAhiIHGcruJ6kZLuFD +wmi+ZMu3V8v0MlN6v4+VhqHbu5NGsNqXVKKo+JXs9oGlgVgSYN/OaKHc56Xs0/8m +5mOzxm42IbMs8bj1twGAdngOo+HeXHmJMU87WaD7Igv4IG6bgE5pkhnrvsBxsvyn +tnbRvbX582RC40OIw1uVMmYmQGNYBpl4+i94oIEjTDZys6t8MfOsJj1yAuJdYfzn +oKZfsxfLy1Ze68u6ZOkxdNhvVmyddMGOEhhC2KgWEsFbC9aRmfPKWHS2x8pmyiwz +eUgwDVKeuLTuPn8XzYk9BW8TFGnhEfj1wrTb3jdY9NSBBsc2o4bgb8nYQfqsRv8y +F0yqzWbBiYKPhITf2n8qdUV3k5FE+uVLlqRERQOa07+kS5kUrpPHurhhgIXN2c2p +Xutz+EmDyWyLyDCXAk4kKGldiqUpIozs4faYJOxtlZmNKXmokmalSl9eN/S8tIXQ +JQ/dJnvYW9L0hvHWxF03LJ5Pee88nZfIRyN0R6olLcI3oSWCOJAfFeqklnJj5YHs +G43BeSW4DGPKCRz6x3i7Y2S5mbfqHFzg1OloU2ybbvCSJhuxyEiSxPkWv5Tl867x +ltju9/n8caTlMRAxskGhYnlmg7xsMrr474YAWxelo1OAEbwA+8mWKlbOFa40BHMt +Ih5Vwu3gN7dmX293gXRYTSnYBgs= +-----END CERTIFICATE----- diff --git a/cli/daemon.pl b/cli/daemon/daemon.pl similarity index 100% rename from cli/daemon.pl rename to cli/daemon/daemon.pl diff --git a/cli/get_domain.pl b/cli/get_domain.pl new file mode 100644 index 0000000..14b9496 --- /dev/null +++ b/cli/get_domain.pl @@ -0,0 +1,29 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +use Data::Dump qw( dump ); + +use lib './lib/'; +use configuration ':all'; +use encryption ':all'; +use app; +use utf8; + +if( @ARGV != 1 ) { + say "usage : ./$0 domain"; + exit 1; +} + +my $dom = $ARGV[0]; + +eval { + my $app = app->new(get_cfg()); + my $domain = $app->get_domain($dom); + dump($domain); +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/get_domains.pl b/cli/get_domains.pl new file mode 100644 index 0000000..ce34331 --- /dev/null +++ b/cli/get_domains.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +#use DNS::ZoneParse; +#use Config::Simple; +use Data::Dump qw( dump ); + +use lib './lib/'; +use configuration ':all'; +use app; +use utf8; + +if( @ARGV != 0 ) { + say "usage : ./$0"; + exit 1; +} + +eval { + my $app = app->new(get_cfg()); + my $domains = $app->get_all_domains(); + dump($domains); +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/get_users.pl b/cli/get_users.pl new file mode 100644 index 0000000..8f19214 --- /dev/null +++ b/cli/get_users.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use utf8; +use Modern::Perl; + +use Data::Dump qw( dump ); + +use lib './lib/'; +use configuration ':all'; +use app; + +if( @ARGV != 0 ) { + say "usage : ./$0"; + exit 1; +} + +eval { + my $app = app->new(get_cfg()); + my $users = $app->get_all_users(); + dump($users); +}; + + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/toggle_admin.pl b/cli/toggle_admin.pl new file mode 100644 index 0000000..487e22a --- /dev/null +++ b/cli/toggle_admin.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +use Data::Dump qw( dump ); + +use lib './lib/'; +use configuration ':all'; +use encryption ':all'; +use app; +use utf8; + +if( @ARGV != 1 ) { + say "usage : ./$0 login"; + exit 1; +} + +my $login = $ARGV[0]; + +eval { + my $app = app->new(get_cfg()); + $app->toggle_admin($login); +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/user_add.pl b/cli/user_add.pl new file mode 100644 index 0000000..a2eb81a --- /dev/null +++ b/cli/user_add.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +use Data::Dump qw( dump ); + +use lib './lib/'; +use configuration ':all'; +use encryption ':all'; +use app; +use utf8; + +if( @ARGV != 2 ) { + say "usage : ./$0 login passwd"; + exit 1; +} + +my ($login, $passwd) = ($ARGV[0], $ARGV[1]); + +eval { + my $app = app->new(get_cfg()); + $app->register_user($login, encrypt($passwd)); +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/user_auth.pl b/cli/user_auth.pl new file mode 100644 index 0000000..111aa9a --- /dev/null +++ b/cli/user_auth.pl @@ -0,0 +1,32 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +use Data::Dump qw( dump ); + +use lib './lib/'; +use configuration ':all'; +use encryption ':all'; +use app; +use utf8; + +if( @ARGV != 0 && @ARGV != 2 ) { + say "usage : ./$0 [ login passwd ]"; + exit 1; +} + +my ($login, $passwd) = qw/test test/; +($login, $passwd) = ($ARGV[0], $ARGV[1]) if ( @ARGV == 2 ); + +eval { + my $app = app->new(get_cfg()); + my $user = $app->auth($login, encrypt($passwd)); + dump($user); + if($$user{admin}) { say "ADMIN" } + else { say "NOT ADMIN" } +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/user_del.pl b/cli/user_del.pl new file mode 100644 index 0000000..0343713 --- /dev/null +++ b/cli/user_del.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +use Data::Dump qw( dump ); + +use lib './lib/'; +use configuration ':all'; +use app; +use utf8; + +if( @ARGV != 1 ) { + say "usage : ./$0 user"; + exit 1; +} + +my $login = $ARGV[0]; + +eval { + my $app = app->new(get_cfg()); + $app->delete_user($login); +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/user_update_passwd.pl b/cli/user_update_passwd.pl new file mode 100644 index 0000000..a981fb5 --- /dev/null +++ b/cli/user_update_passwd.pl @@ -0,0 +1,27 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +use Data::Dump qw( dump ); + +use lib './lib/'; +use configuration ':all'; +use encryption ':all'; +use app; + +if( @ARGV != 2 ) { + say "usage : ./$0 userid newpasswd"; + exit 1; +} + +my ($login, $passwd) = ($ARGV[0], $ARGV[1]); + +eval { + my $app = app->new(get_cfg()); + $app->update_passwd($login, encrypt($passwd)); +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/zone_add.pl b/cli/zone_add.pl new file mode 100644 index 0000000..280c6b7 --- /dev/null +++ b/cli/zone_add.pl @@ -0,0 +1,28 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +use lib './lib/'; +use configuration ':all'; +use encryption ':all'; +use app; +use utf8; + +if( @ARGV != 2 ) { + say "usage : ./$0 login ndd "; + exit 1; +} + +my ($login, $dom) = ($ARGV[0], $ARGV[1]); + +eval { + my $app = app->new(get_cfg()); + $app->add_domain( $login, $dom ); + my $zone = $app->get_domain($dom); + say $zone->output(); +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/cli/zone_del.pl b/cli/zone_del.pl new file mode 100644 index 0000000..f7fd6c2 --- /dev/null +++ b/cli/zone_del.pl @@ -0,0 +1,26 @@ +#!/usr/bin/perl -w +use v5.14; +use autodie; +use Modern::Perl; + +use lib './lib/'; +use configuration ':all'; +use encryption ':all'; +use app; +use utf8; + +if( @ARGV != 1 ) { + say "usage : ./$0 ndd "; + exit 1; +} + +my $dom = $ARGV[0]; + +eval { + my $app = app->new(get_cfg()); + $app->delete_domain( $dom ); +}; + +if( $@ ) { + say q{Une erreur est survenue. } . $@; +} diff --git a/conf/config.yml b/conf/config.yml new file mode 100644 index 0000000..be44ffa --- /dev/null +++ b/conf/config.yml @@ -0,0 +1,44 @@ +# TLD +# Must contains the first "." +tld: + - '.netlib.re' + - '.autre.tld' + - '.codelib.re' + +tmpdir: file:///media/fast/ + +database: + sgbd: mysql # other options : see DBI module + name: dnsmanager + host: localhost + port: 3306 + user: dnsmanageruser + passwd: "my-not-so-dummy-password" + +primarydnsserver: + app: bind9 + dnsslavekey: demokey + zonedir: ssh://root@localhost:22/var/named/zones/rndczones/ + domain: + user: root + port: 22 + host: web.loc + name: web.loc + v4: 192.168.0.60 # optional + #v6: ::1 # optional + +secondarydnsserver: + - app: nsd + cfg: ssh://root@nsdl:22/etc/nsd/nsd.conf + zonedir: ssh://root@nsdl:22/etc/nsd/ + domain: + name: nsdl + v4: 192.168.0.61 # optional + #v6: ::1 # optional + +# - app: nsd +# cfg: ssh://dnsmanager@host3:2222/etc/nsd3/nsd.conf +# domain: +# name: third.example.com +# v4: 192.0.2.3 # optional +# v6: 2001:db8::3 # optional diff --git a/www/conf/reserved.zone b/conf/reserved.zone similarity index 100% rename from www/conf/reserved.zone rename to conf/reserved.zone diff --git a/www/config.yml b/config.yml similarity index 64% rename from www/config.yml rename to config.yml index 2d40d75..791fe1b 100644 --- a/www/config.yml +++ b/config.yml @@ -1,15 +1,15 @@ -# This is the main configuration file of your Dancer app +# This is the main configuration file of your Dancer2 app # env-related settings should go to environments/$env.yml # all the settings in this file will be loaded at Dancer's startup. # Your application's name -appname: "DNSManager" +appname: "MyWeb::App" # The default layout to use for your application (located in # views/layouts/main.tt) layout: "main" -# when the charset is set to UTF-8 Dancer will handle for you +# when the charset is set to UTF-8 Dancer2 will handle for you # all the magic of encoding and decoding. You should not care # about unicode within your app when this setting is set (recommended). charset: "UTF-8" @@ -18,13 +18,17 @@ charset: "UTF-8" # simple: default and very basic template engine # template_toolkit: TT -# template: "simple" +#template: "simple" template: "template_toolkit" engines: - template_toolkit: - encoding: 'utf8' - start_tag: '<%' - end_tag: '%>' + template: + template_toolkit: + start_tag: '<%' + end_tag: '%>' -session: "Storable" +#session: "Storable" +session: "YAML" + + +logging: "console" diff --git a/cpanfile b/cpanfile new file mode 100644 index 0000000..62fe3be --- /dev/null +++ b/cpanfile @@ -0,0 +1,11 @@ +requires "Dancer2" => "0.161000"; + +recommends "YAML" => "0"; +recommends "URL::Encode::XS" => "0"; +recommends "CGI::Deurl::XS" => "0"; +recommends "HTTP::Parser::XS" => "0"; + +on "test" => sub { + requires "Test::More" => "0"; + requires "HTTP::Request::Common" => "0"; +}; diff --git a/environments/development.yml b/environments/development.yml new file mode 100644 index 0000000..b63fe64 --- /dev/null +++ b/environments/development.yml @@ -0,0 +1,23 @@ +# configuration file for development environment + +# the logger engine to use +# console: log messages to STDOUT (your console where you started the +# application server) +# file: log message to a file in log/ +logger: "console" + +# the log level for this environment +# core is the lowest, it shows Dancer2's core log messages as well as yours +# (debug, info, warning and error) +log: "core" + +# should Dancer2 consider warnings as critical errors? +warnings: 1 + +# should Dancer2 show a stacktrace when an error is caught? +# if set to yes, public/500.html will be ignored and either +# views/500.tt, 'error_template' template, or a default error template will be used. +show_errors: 1 + +# print the banner +startup_info: 1 diff --git a/www/environments/production.yml b/environments/production.yml similarity index 71% rename from www/environments/production.yml rename to environments/production.yml index 86801b4..41b436f 100644 --- a/www/environments/production.yml +++ b/environments/production.yml @@ -9,9 +9,8 @@ logger: "file" # don't consider warnings critical warnings: 0 -# hide errors +# hide errors show_errors: 0 -# cache route resolution for maximum performance -route_cache: 1 - +# disable server tokens in production environments +no_server_tokens: 1 diff --git a/init/deploiement.sh b/init/deploiement.sh new file mode 100755 index 0000000..9b0ff0d --- /dev/null +++ b/init/deploiement.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +# install applications +sudo apt-get install mysql-server # bind9 + +# Get libs +bash ./get_libs.sh + +# db install +mysql -u root --password="${PASS}" < init-create-user.sql +mysql -u root --password="${PASS}" < init-create-db.sql +mysql -u root --password="${PASS}" < init-grant-user.sql +mysql -u root --password="${PASS}" < init-tables.sql diff --git a/get_libs.sh b/init/get_libs.sh similarity index 55% rename from get_libs.sh rename to init/get_libs.sh index 123f609..0304459 100755 --- a/get_libs.sh +++ b/init/get_libs.sh @@ -1,11 +1,20 @@ #!/bin/bash -sudo apt-get update -sudo apt-get install libssl1.0.0 libssl-dev cpanminus make gcc - ## En attendant de faire de vrais paquets pour l'application -cpanm YAML +sudo apt-get update + +sudo apt-get install libssl1.0.0 libssl-dev cpanminus make gcc \ + libdbi-perl libdbd-mysql-perl + +# sudo apt-get install bind9 + +cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) + +cpanm Dancer2 +cpanm Dancer2::Plugin::Deferred +#cpanm Dancer::Plugin::FlashMessage +cpanm YAML::XS cpanm Data::Dump cpanm File::Basename cpanm Find::Lib @@ -16,17 +25,13 @@ cpanm Modern::Perl cpanm Config::Simple cpanm Crypt::Digest::SHA256 cpanm Dancer::Session::Storable - -cpanm Moose -cpanm Template -cpanm Template::Toolkit -cpanm Dancer -cpanm Dancer::Test - cpanm ExtUtils::MakeMaker cpanm Storable - cpanm Plack::Handler::FCGI cpanm Plack::Runner cpanm DNS::ZoneParse cpanm Net::OpenSSH +cpanm Template +cpanm Net::SSH +cpanm Date::Calc +# cpanm Template::Toolkit non trouvé diff --git a/init/init-create-db.sql b/init/init-create-db.sql new file mode 100644 index 0000000..f27bff2 --- /dev/null +++ b/init/init-create-db.sql @@ -0,0 +1 @@ +CREATE DATABASE IF NOT EXISTS dnsmanager; diff --git a/init/init-create-user.sql b/init/init-create-user.sql new file mode 100644 index 0000000..aad7731 --- /dev/null +++ b/init/init-create-user.sql @@ -0,0 +1,2 @@ +CREATE USER "dnsmanageruser"@'localhost'; +set password for "dnsmanageruser"@'localhost' = password('my-not-so-dummy-password'); diff --git a/init/init-grant-user.sql b/init/init-grant-user.sql new file mode 100644 index 0000000..3465d83 --- /dev/null +++ b/init/init-grant-user.sql @@ -0,0 +1 @@ +grant all on dnsmanager.* to "dnsmanageruser"@'localhost'; diff --git a/init/init-tables.sql b/init/init-tables.sql new file mode 100644 index 0000000..7cc1fd5 --- /dev/null +++ b/init/init-tables.sql @@ -0,0 +1,21 @@ +USE dnsmanager; + +CREATE TABLE IF NOT EXISTS user ( + login varchar(50) NOT NULL, + passwd varchar(100) DEFAULT NULL, + admin tinyint(1) DEFAULT 0, + PRIMARY KEY (login) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS domain ( + domain varchar(100) NOT NULL, + login varchar(50) NOT NULL, + activated tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (domain) +) ENGINE=InnoDB; + +CREATE TABLE IF NOT EXISTS tld ( + tld varchar(50) NOT NULL, + activated tinyint(1) NOT NULL DEFAULT 0, + PRIMARY KEY (tld) +) ENGINE=InnoDB; diff --git a/init/recreate.sh b/init/recreate.sh new file mode 100755 index 0000000..558f84d --- /dev/null +++ b/init/recreate.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +PASS="not-so-dummy" + +mysql -u root --password=${PASS} < remove-db.sql +mysql -u root --password=${PASS} < remove-user.sql +mysql -u root --password=${PASS} < init-create-db.sql +mysql -u root --password=${PASS} < init-create-user.sql +mysql -u root --password=${PASS} < init-grant-user.sql +mysql -u root --password=${PASS} < init-tables.sql diff --git a/init/remove-db.sql b/init/remove-db.sql new file mode 100644 index 0000000..8eacce6 --- /dev/null +++ b/init/remove-db.sql @@ -0,0 +1 @@ +DROP DATABASE dnsmanager; diff --git a/init/remove-user.sql b/init/remove-user.sql new file mode 100644 index 0000000..868b40e --- /dev/null +++ b/init/remove-user.sql @@ -0,0 +1 @@ +DROP USER "dnsmanageruser"@'localhost'; diff --git a/t/tpl.zone b/init/tpl.zone similarity index 82% rename from t/tpl.zone rename to init/tpl.zone index 23ba858..906d884 100644 --- a/t/tpl.zone +++ b/init/tpl.zone @@ -1,5 +1,5 @@ ; -; Database file tpl.zone for tpl.zone. zone. +; Database file CHANGEMEORIGIN for CHANGEMEORIGIN. zone. ; Zone version: 2014030200 ; diff --git a/lib/MyWeb/App.pm b/lib/MyWeb/App.pm new file mode 100644 index 0000000..4f036a3 --- /dev/null +++ b/lib/MyWeb/App.pm @@ -0,0 +1,196 @@ +package MyWeb::App; + +use v5.14; +use strict; +use warnings; + +use Dancer2; +use Dancer2::Plugin::Deferred; +use File::Basename; +#use Storable qw( freeze thaw ); +#$Storable::Deparse = true; +#$Storable::Eval=true; +use utf8; + +use YAML::XS; +use configuration ':all'; +use util ':all'; +use rt::root ':all'; +use rt::domain ':all'; +use rt::user ':all'; +use rt::admin ':all'; +use app; + +our $VERSION = '0.1'; + +sub what_is_next { + my ($res) = @_; + + if($$res{sessiondestroy}) { + app->destroy_session; + } + + for(keys %{$$res{deferred}}) { + deferred $_ => $$res{deferred}{$_}; + } + + for(keys %{$$res{addsession}}) { + session $_ => $$res{addsession}{$_}; + } + + for(keys %{$$res{delsession}}) { + session $_ => undef; + } + + if(exists $$res{route}) { + redirect $$res{route}; + } + elsif(exists $$res{template}) { + template $$res{template} => $$res{params}; + } else { + redirect '/'; + } +} + +sub get_param { + my $param_values; + for(@_) { + $$param_values{$_} = param "$_"; + } + $param_values; +} + +sub get_request { + my $request_values; + for(@_) { + if(/^address$/) { $$request_values{$_} = request->address; } + elsif(/^referer$/) { $$request_values{$_} = request->referer; } + } + $request_values; +} + +sub get_session { + my $session_values; + for(@_) { + $$session_values{$_} = session "$_"; + } + $session_values; +} + +get '/' => sub { + what_is_next rt_root + get_session( qw/login passwd/ ); +}; + +prefix '/domain' => sub { + + any ['post', 'get'] => '/updateraw/:domain' => sub { + what_is_next rt_dom_updateraw + get_session( qw/login passwd/ ) + , get_param( qw/domain zoneupdated/) + , get_request( qw/address referer/ ); + }; + + any ['post', 'get'] => '/update/:domain' => sub { + what_is_next rt_dom_update + get_session( qw/login passwd/ ) + , get_param( qw/type name value ttl priority domain/ ); + }; + + get '/details/:domain' => sub { + what_is_next rt_dom_details + get_session( qw/login passwd/ ) + , get_param( qw/domain expert/ ) + , get_request( qw/address referer/ ); + }; + + post '/add/' => sub { + what_is_next rt_dom_add + get_session( qw/login passwd/ ) + , get_param( qw/domain tld/ ); + }; + + get '/del/:domain' => sub { + what_is_next rt_dom_del + get_session( qw/login passwd/ ) + , get_param( qw/domain/ ) + , get_request( qw/address referer/ ); + }; + + get '/del/:domain/:name/:type/:host/:ttl' => sub { + what_is_next rt_dom_del_entry + get_session( qw/login passwd/ ) + , get_param( qw/domain name type host ttl/ ) + , get_request( qw/address referer/ ); + }; + + get '/mod/:domain/:name/:type/:host/:ttl' => sub { + what_is_next rt_dom_mod_entry + get_session( qw/login passwd/ ) + , get_param( qw/type name ttl domain name type host ttl + newpriority newtype newhost newname newttl / ) + , get_request( qw/address referer/ ); + }; + + get '/cli/:login/:pass/:domain/:name/:type/:host/:ttl/:ip' => sub { + what_is_next rt_dom_cli_mod_entry + get_session( qw/login/ ) + , get_param( qw/passwd domain name type host ttl ip/ ); + }; +}; + +any ['get', 'post'] => '/admin' => sub { + what_is_next rt_admin + get_session( qw/login passwd/ ); +}; + +prefix '/user' => sub { + + get '/home' => sub { + what_is_next rt_user_home + get_session( qw/login passwd/ ) + , get_param( qw// ) + , get_request( qw// ); + }; + + get '/logout' => sub { + app->destroy_session; + redirect '/'; + }; + + get '/del/:user' => sub { + what_is_next rt_user_del + get_session( qw/login passwd/ ) + , get_param( qw/user/ ) + , get_request( qw/referer/ ); + }; + + # add a user => registration + post '/add/' => sub { + what_is_next rt_user_add + get_session( qw// ) + , get_param( qw/login password password2/ ) + , get_request( qw// ); + }; + + get '/subscribe' => sub { + what_is_next rt_user_subscribe + get_session( qw/login/ ); + }; + + get '/toggleadmin/:user' => sub { + what_is_next rt_user_toggleadmin + get_session( qw/login passwd/ ) + , get_param( qw/user/ ) + , get_request( qw/referer/ ); + }; + + post '/login' => sub { + what_is_next rt_user_login + get_session( qw/login/ ) + , get_param( qw/login password/ ) + , get_request( qw/referer/ ); + }; +}; + +true; diff --git a/lib/README.markdown b/lib/README.markdown new file mode 100644 index 0000000..02a14b4 --- /dev/null +++ b/lib/README.markdown @@ -0,0 +1,7 @@ +# TODO + + * redesign zone.pm + * moar testz !!! Kitten will die !!! + * décider de la procédure d'init de app, pas besoin de tout charger à chaque fois + * comment passer des informations à chaque interface ? + * est-ce que ce qui est fait est pertinent ? diff --git a/lib/app.pm b/lib/app.pm new file mode 100644 index 0000000..6c92133 --- /dev/null +++ b/lib/app.pm @@ -0,0 +1,144 @@ +package app; +use v5.14; +use Moo; + +use db; +use zone; + +has db => ( is => 'rw', builder => '_void'); + +has [qw/tld tmpdir database primarydnsserver secondarydnsserver/] +=> qw/is ro required 1/; + +sub _void { my $x = ''; \$x; } + +sub BUILD { + my ($self) = @_; + $$self{db} = db->new(data => $self); + + my $db = $$self{database}; + unless(exists $$db{sgbd} && exists $$db{name} + && exists $$db{host} && exists $$db{port} + && exists $$db{user} && exists $$db{passwd}) + { + die "Unable to connect to the database.\n" + . "Check the existance of theses parameters in the config file :\n" + . "\tsgbd name host port user passwd"; + } +} + +# USER + +sub auth { + my ($self, $login, $passwd) = @_; + $self->db->auth($login, $passwd) +} + +sub update_passwd { + my ($self, $login, $newpass) = @_; + $self->db->update_passwd($login, $newpass) +} + +sub register_user { + my ($self, $login, $passwd) = @_; + $self->db->register_user($login, $passwd) +} + +sub toggle_admin { + my ($self, $login) = @_; + $self->db->toggle_admin($login) +} + +sub delete_user { + my ($self, $login) = @_; + $self->db->delete_user($login) +} + +sub get_all_users { + my ($self) = @_; + $self->db->get_all_users +} + +sub is_owning_domain { + my ($self, $login, $domain) = @_; + $self->db->is_owning_domain($login, $domain) +} + +# DOMAIN + +sub _get_zone { + my ($self, $domain) = @_; + +# say ""; +# say "GET ZONE"; +# say ""; +# say ""; +# say "domain $domain"; +# say "tmpdir $$self{tmpdir}"; +# say "tld $$self{tld}"; + + zone->new( domain => $domain + , tmpdir => $$self{tmpdir} + , tld => $$self{tld} + , primarydnsserver => $$self{primarydnsserver} + , secondarydnsserver => $$self{secondarydnsserver} + , slavedzones => $self->get_all_domains() + ) +} + +sub add_domain { + my ($self, $login, $domain) = @_; + $self->db->add_domain($login, $domain); + $self->_get_zone($domain)->addzone() +} + +sub delete_domain { + my ($self, $domain) = @_; + $self->db->delete_domain($domain); + $self->_get_zone($domain)->del() +} + +sub modify_entry { + my ($self, $domain, $entryToModify, $newEntry) = @_; + my $zone = $self->_get_zone($domain)->modify_entry( + $entryToModify, $newEntry ); + $self->update_domain($zone, $domain) +} + +sub delete_entry { + my ($self, $domain, $entryToDelete) = @_; + my $zone = $self->_get_zone($domain)->delete_entry( $entryToDelete ); + $self->update_domain($zone, $domain) +} + +sub update_domain_raw { + my ($self, $zone, $domain) = @_; + $self->_get_zone($domain)->update_raw($zone) +} + +sub update_domain { + my ($self, $zone, $domain) = @_; + $self->_get_zone($domain)->update($zone) +} + +sub get_domain { + my ($self, $domain) = @_; + $self->_get_zone($domain)->get() +} + +sub get_domains { + my ($self, $login) = @_; + $self->db->get_domains($login) +} + +sub get_all_domains { + my ($self) = @_; + $self->db->get_all_domains +} + +sub disconnect { + my ($self) = @_; + $self->db->disconnect +} + +1; diff --git a/lib/configuration.pm b/lib/configuration.pm new file mode 100644 index 0000000..337fd45 --- /dev/null +++ b/lib/configuration.pm @@ -0,0 +1,166 @@ +package configuration; +use YAML::XS; +use URI; + +use fileutil ':all'; +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/ +get_cfg is_reserved +get_zonedir_from_cfg +get_dnsslavekey_from_cfg +get_v4_from_name +get_v6_from_name +get_v4_from_cfg +get_v6_from_cfg +get_host_from_cfg +get_user_from_cfg +get_port_from_cfg +/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/ +get_cfg is_reserved +get_zonedir_from_cfg +get_dnsslavekey_from_cfg +get_v4_from_name +get_v6_from_name +get_v4_from_cfg +get_v6_from_cfg +get_host_from_cfg +get_user_from_cfg +get_port_from_cfg + /] ); + +sub is_conf_file { + my $f = shift; + + unless(-f $f) { + die "$f : not a file"; + } + + unless(-r $f) { + die "$f : not readable"; + } + + unless(-T $f) { + die "$f : not plain text"; + } +} + +sub get_cfg { + my ($cfgdir) = @_; + + $cfgdir //= './conf/'; + my $f = "$cfgdir/config.yml"; + + is_conf_file $f; + YAML::XS::LoadFile($f) +} + +sub is_reserved { + my ($domain) = @_; + + my $filename = 'conf/reserved.zone'; + is_conf_file $filename; + + my $data = read_file $filename; + $data =~ /^$domain$/m; +} + +# TODO : tests +sub get_v6_from_name { + my $name = shift; + + my $val = qx/host -t AAAA $name | grep -oE '[^[:space:]]+\$'/; + chomp $val; + + #die q{There is no available v6. TODO.} if($val =~ 'NXDOMAIN'); + return undef if($val =~ 'NXDOMAIN'); + + $val +} + +sub get_v4_from_name { + my $name = shift; + + my $val = qx/host -t A $name | grep -oE '[^[:space:]]+\$'/; + chomp $val; + + die q{There is no available v4. TODO.} if($val =~ 'NXDOMAIN'); + + $val +} + +sub get_v6_from_cfg { + my $cfg = shift; + $$cfg{domain}{v6} // get_v6_from_name($$cfg{domain}{name}) +} + +sub get_v4_from_cfg { + my $cfg = shift; + $$cfg{domain}{v4} // get_v4_from_name($$cfg{domain}{name}) +} + +sub get_zonedir_from_cfg { + my $cfg = shift; + unless($$cfg{zonedir}) { + die 'For now, the only way to get the zone path is to setup zonedir ' + . 'in the primaryserver configuration in config.yml.'; + } + URI->new($$cfg{zonedir})->path; +} + +sub get_host_from_cfg { + my $cfg = shift; + + if($$cfg{zonedir}) { + my $u = URI->new($$cfg{zonedir}); + return $u->host; + } + elsif($$cfg{domain}{name}) { + return $$cfg{domain}{name}; + } + + die "Impossible to get the host from the configuration." +} + +sub get_dnsslavekey_from_cfg { + my $cfg = shift; + + if($$cfg{dnsslavekey}) { + return $$cfg{dnsslavekey}; + } + + die "Impossible to get the dns slave key from the configuration." +} + +sub get_user_from_cfg { + my $cfg = shift; + + if($$cfg{zonedir}) { + my $u = URI->new($$cfg{zonedir}); + return $u->user; + } + elsif($$cfg{domain}{user}) { + return $$cfg{domain}{user}; + } + + die "Impossible to get the user from the configuration." +} + +sub get_port_from_cfg { + my $cfg = shift; + + if($$cfg{zonedir}) { + my $u = URI->new($$cfg{zonedir}); + return $u->port; + } + elsif($$cfg{domain}{port}) { + return $$cfg{domain}{port}; + } + + die "Impossible to get the port from the configuration." +} + +1; diff --git a/lib/copycat.pm b/lib/copycat.pm new file mode 100644 index 0000000..23c4f13 --- /dev/null +++ b/lib/copycat.pm @@ -0,0 +1,72 @@ +package copycat; +use v5.14; + +use File::Copy; +use URI; +use Net::OpenSSH; + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/copycat/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/copycat/] ); + +sub _cp { + my ($src, $dest) = @_; + say "cp $src $dest"; + File::Copy::copy($src, $dest) or die "Copy failed: $! ($src -> $dest)"; +} + +sub _scp_put { + my ($co, $src, $dest) = @_; + + my $ssh = Net::OpenSSH->new($co); + say "scp put $src $dest"; + $ssh->scp_put($src, $dest) or die "scp failed: " . $ssh->error; +} + +sub _scp_get { + my ($co, $src, $dest) = @_; + + my $ssh = Net::OpenSSH->new($co); + say "scp get $src $dest"; + $ssh->scp_get($src, $dest) or die "scp failed: " . $ssh->error; +} + +# SUPPORT +# local to local +# distant to local +# local to distant + +sub copycat { + my ($source, $destination) = @_; + + # TODO if it's not URI + + my $src = URI->new($source); + my $dest = URI->new($destination); + + if($src->scheme eq 'file' && $dest->scheme eq 'file') { + _cp $src->path, $dest->path; + } + elsif($src->scheme eq 'ssh' && $dest->scheme eq 'file') { + + my $co = $src->userinfo . '@' . $src->host . ':' . $src->port; + _scp_get $co, $src->path, $dest->path; + + } + elsif($src->scheme eq 'file' && $dest->scheme eq 'ssh') { + + my $co = $dest->userinfo . '@' . $dest->host . ':' . $dest->port; + _scp_put $co, $src->path, $dest->path; + + } + else { + + die "CopyCat : wrong arguments"; + } + +} + +1; diff --git a/lib/db.pm b/lib/db.pm new file mode 100644 index 0000000..4a153cc --- /dev/null +++ b/lib/db.pm @@ -0,0 +1,269 @@ +package db; +use v5.14; +use Moo; + +use Modern::Perl; +use autodie; +use DBI; + +use getiface ':all'; + +# db handler +has dbh => ( is => 'rw', builder => '_void'); + +sub _void { my $x = ''; \$x; } + +# reference to the application +has data => qw/is ro required 1/; + +sub BUILD { + my $self = shift; + + my $db = $$self{data}{database}; + + my $dsn = "DBI:$$db{sgbd}:database=$$db{name};" + . "host=$$db{host};port=$$db{port}"; + + $$self{dbh} = DBI->connect($dsn, $$db{user}, $$db{passwd}) + || die "Could not connect to database: $DBI::errstr"; + $$self{dbh}->{mysql_enable_utf8} = 1; + $$self{dbh}->do('SET NAMES \'utf8\';') || die; + +} + +# USER + +sub auth { + my ($self, $login, $passwd) = @_; + my $sth; + + $sth = $self->dbh->prepare('SELECT * FROM user WHERE login=? and passwd=?'); + unless ($sth->execute($login, $passwd)) { + $sth->finish(); + die q{Can't authenticate.}; + } + + # if we can't find the user with this password + unless (my $ref = $sth->fetchrow_arrayref) { + $sth->finish(); + die q{The user can't be authenticated.}; + } + $sth->finish(); + + # if this user exists and is auth + $self->get_user($login) +} + +sub register_user { + my ($self, $login, $pass) = @_; + + my $sth = $self->dbh->prepare('select * from user where login=?'); + unless ( $sth->execute($login) ) { + $sth->finish(); + die "Impossible to check if the user $login exists."; + } + + # if an user already exists + if (my $ref = $sth->fetchrow_arrayref) { + $sth->finish(); + die "The user $login already exists."; + } + + # if not + $sth = $self->dbh->prepare('insert into user VALUES(?,?,?)'); + unless ($sth->execute($login, $pass, 0)) { + $sth->finish(); + die "Impossible to register the user $login."; + } + $sth->finish(); +} + +sub delete_user { + my ($self, $login) = @_; + my $sth; + # TODO : vérifier que ça renvoie la bonne valeur + $sth = $self->dbh->prepare('delete from user where login=?'); + unless ( $sth->execute($login) ) { + $sth->finish(); + die "Impossible to delete the user $login."; + } + $sth->finish(); + $self->delete_domains_from_user($login) +} + +sub get_user { + my ($self, $login) = @_; + my ($sth, $user); + + $sth = $self->dbh->prepare('SELECT * FROM user WHERE login=?'); + unless ( $sth->execute($login)) { + $sth->finish(); + die "Impossible to check if the user $login exists."; + } + + unless ($user = $sth->fetchrow_hashref) { + $sth->finish(); + die "User $login doesn't exist."; + } + $sth->finish(); + + # the user gets all his domains + $$user{domains} = $self->get_domains($login); + $user +} + +sub get_all_users { + my ($self) = @_; + my ($sth, $users); + + $sth = $self->dbh->prepare('SELECT * FROM user'); + unless ( $sth->execute()) { + $sth->finish(); + die q{Impossible to list the users.}; + } + + while( my $ref = $sth->fetchrow_hashref) { + push @$users, $ref; + } + + $sth->finish(); + $users +} + +sub toggle_admin { + my ($self, $login) = @_; + + my $user = $self->get_user($login); + my $val = ($$user{admin}) ? 0 : 1; + + my $sth = $self->dbh->prepare('update user set admin=? where login=?'); + unless ( $sth->execute( $val, $login ) ) { + $sth->finish(); + die "Impossible to toggle admin the user $login."; + } + + $sth->finish() +} + +sub update_passwd { + my ($self, $login, $new) = @_; + my $sth; + $sth = $self->dbh->prepare('update user set passwd=? where login=?'); + unless ( $sth->execute($new, $login) ) { + $sth->finish(); + die q{The password can't be updated.}; + } + $sth->finish() +} + +# DOMAIN + +sub get_domains { + my ($self, $login) = @_; + my ($sth); + my $domains = []; + + $sth = $self->dbh->prepare('SELECT * FROM domain where login=?'); + unless ($sth->execute($login)) { + $sth->finish(); + die "Impossible to check if the user $login has domains."; + } + + while(my $ref = $sth->fetchrow_hashref) { + push @$domains, $ref; + } + + $sth->finish(); + $domains +} + +sub delete_domain { + my ($self, $domain) = @_; + my $sth; + $sth = $self->dbh->prepare('delete from domain where domain=?'); + unless ( $sth->execute($domain) ) { + $sth->finish(); + die "Impossible to delete the $domain."; + } + $sth->finish() +} + +sub delete_domains_from_user { + my ($self, $login) = @_; + my $sth; + $sth = $self->dbh->prepare('delete from domain where login=?'); + unless ( $sth->execute($login) ) { + $sth->finish(); + die "Impossible to delete the domains of the user $login."; + } + $sth->finish() +} + +# TODO check if the domain is reserved +sub add_domain { + my ($self, $login, $domain) = @_; + my ($sth); + + $sth = $self->dbh->prepare('select domain from domain where domain=?'); + unless ( $sth->execute($domain) ) { + $sth->finish(); + die 'Impossible to search if the domain already exists.'; + } + + # if the domain already exists + if (my $ref = $sth->fetchrow_arrayref) { + $sth->finish(); + die 'The domain already exists.'; + } + + $sth = $self->dbh->prepare('insert into domain VALUES(?,?,?)'); + unless ( $sth->execute($domain, $login, 0) ) { + $sth->finish(); + die 'Impossible to add a domain.'; + } + + $sth->finish(); +} + +sub get_all_domains { + my ($self) = @_; + my ($sth, $domains); + + $sth = $self->dbh->prepare('SELECT * FROM domain'); + unless ( $sth->execute()) { + $sth->finish(); + die q{Impossible to list the domains.}; + } + + while( my $ref = $sth->fetchrow_hashref) { + push @$domains, $ref; + } + + $sth->finish(); + $domains +} + +sub disconnect { + my ($self) = @_; + $$self{dbh}->disconnect() +} + +sub is_owning_domain { + my ($self, $login, $domain) = @_; + + my $sth = + $self->dbh->prepare('SELECT * FROM domain where login=? and domain=?'); + unless ($sth->execute($login, $domain)) { + $sth->finish(); + die "Impossible to check if the user $login has domains."; + } + + unless($sth->fetchrow_hashref) { + $sth->finish(); + return 0 + } + + 1 +} + +1; diff --git a/lib/encryption.pm b/lib/encryption.pm new file mode 100644 index 0000000..9b3346c --- /dev/null +++ b/lib/encryption.pm @@ -0,0 +1,16 @@ +package encryption; +use Crypt::Digest::SHA256 qw( sha256_hex ) ; + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/encrypt/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/encrypt/] ); + +sub encrypt { + my ($x) = @_; + sha256_hex($x) +} + +1; diff --git a/lib/fileutil.pm b/lib/fileutil.pm new file mode 100644 index 0000000..be60655 --- /dev/null +++ b/lib/fileutil.pm @@ -0,0 +1,46 @@ +package fileutil; +use v5.14; + +use URI; + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/read_file write_file/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/read_file write_file/] ); + +sub read_file { + my ($filename) = @_; + + if($filename =~ "://") + { + my $fileuri = URI->new($filename); + $filename = $fileuri->path; + } + + open my $entry, '<:encoding(UTF-8)', $filename or + die "Impossible d'ouvrir '$filename' en lecture : $!"; + local $/ = undef; + my $all = <$entry>; + close $entry; + + return $all; +} + +sub write_file { + my ($filename, $data) = @_; + + if($filename =~ "://") + { + my $fileuri = URI->new($filename); + $filename = $fileuri->path; + } + + open my $sortie, '>:encoding(UTF-8)', $filename or + die "Impossible d'ouvrir '$filename' en écriture : $!"; + print $sortie $data; + close $sortie; +} + +1; diff --git a/lib/getiface.pm b/lib/getiface.pm new file mode 100644 index 0000000..f77f345 --- /dev/null +++ b/lib/getiface.pm @@ -0,0 +1,27 @@ +package getiface; +use v5.14; + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/getiface/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/getiface/] ); + +use interface::bind9; +use interface::knot; +use interface::nsd3; +use interface::nsd4; + +sub getiface { + my ($type, $params) = @_; + for($type) { + if (/bind9/) { return interface::bind9->new($params) } + elsif (/knot/) { return interface::knot->new($params) } + elsif (/nsd3/) { return interface::nsd3->new($params) } + elsif (/nsd/) { return interface::nsd4->new($params) } + else { die "Interface for the $_ dns type not found."; } + } +} + +1; diff --git a/lib/interface/bind9.pm b/lib/interface/bind9.pm new file mode 100644 index 0000000..f07b0ea --- /dev/null +++ b/lib/interface/bind9.pm @@ -0,0 +1,85 @@ +package interface::bind9; +use v5.14; +use Moo; +use configuration ':all'; +use remotecmd ':all'; + +has [ qw/mycfg tmpdir primarydnsserver secondarydnsserver/ ] => qw/is ro required 1/; + +sub reload { + my ($self, $domain) = @_; + + my $cmd = "rndc reload $domain "; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + + remotecmd $user, $host, $port, $cmd; + + $cmd = "rndc notify $domain "; + remotecmd $user, $host, $port, $cmd; +} + +sub primary_addzone { + my ($self, $domain, $opt) = @_; + + my $cmd = "rndc addzone $domain "; + + if(defined $opt) { + $cmd .= "'$opt'"; + } + else { + my $dir = get_zonedir_from_cfg($$self{mycfg}); + $cmd .= "\"{ type master; file \\\"$dir/$domain\\\"; allow-transfer { "; + + my $sec = $$self{secondarydnsserver}; + for(@$sec) { + my $v4 = get_v4_from_cfg($_); + my $v6 = get_v6_from_cfg($_); + + $cmd .= $v4 . '; ' if $v4; + $cmd .= $v6 . '; ' if $v6; + } + $cmd .= " }; notify yes; };\""; + } + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + + remotecmd $user, $host, $port, $cmd; +} + +sub reconfig { + my ($self, $domain) = @_; + + my $cmd = "rndc reconfig "; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + + remotecmd $user, $host, $port, $cmd; +} + +sub delzone { + my ($self, $domain) = @_; + + my $cmd = "rndc delzone $domain "; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + + remotecmd $user, $host, $port, $cmd; + + my $file = get_zonedir_from_cfg($$self{mycfg}); + $file .= "/$domain"; + + $cmd = "rm $file"; + + remotecmd $user, $host, $port, $cmd +} + +1; diff --git a/app/zone/knot_interface.pm b/lib/interface/knot.pm similarity index 90% rename from app/zone/knot_interface.pm rename to lib/interface/knot.pm index 072a58a..e56665d 100644 --- a/app/zone/knot_interface.pm +++ b/lib/interface/knot.pm @@ -1,6 +1,6 @@ +package app::interface::knot; use v5.14; -package app::zone::knot_interface; -use Moose; +use Moo; # on suppose que tout est déjà mis à jour dans le fichier sub reload { diff --git a/lib/interface/nsd3.pm b/lib/interface/nsd3.pm new file mode 100644 index 0000000..e9fa60f --- /dev/null +++ b/lib/interface/nsd3.pm @@ -0,0 +1,90 @@ +package interface::nsd3; +use v5.14; +use Moo; +use URI; +use fileutil ':all'; +use remotecmd ':all'; +use copycat ':all'; +use configuration ':all'; + +has [ qw/mycfg tmpdir primarydnsserver secondarydnsserver/ ] => qw/is ro required 1/; + +# on suppose que tout est déjà mis à jour dans le fichier +sub reload_sec { + my ($self, $slavedzones) = @_; + + $self->_reload_conf($slavedzones); + + my $cmd = "sudo nsdc rebuild && " + . " sudo nsdc restart "; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + + remotecmd $user, $host, $port, $cmd +} + +# get, modify, push the file + +sub _reload_conf { + my ($self, $slavedzones) = @_; + + my $f = "file://$$self{tmpdir}/nsd.conf"; + my $remote = ($$self{mycfg}{cfg}) ? $$self{mycfg}{cfg} : undef; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + + $remote //= "ssh://$user". '@' . "$host/etc/nsd/nsd.conf"; + + copycat $remote, $f; + + my $data = read_file $f; + my $debut = "## BEGIN_GENERATED"; + my $nouveau = ''; + my $dnsslavekey = get_dnsslavekey_from_cfg($$self{primarydnsserver}); + + for(@{$slavedzones}) { + + $nouveau .= "zone:\n\n\tname: \"$_\"\n" + . "\tzonefile: \"slave/$_\"\n\n"; + + my $v4 = get_v4_from_cfg($$self{primarydnsserver}); + my $v6 = get_v6_from_cfg($$self{primarydnsserver}); + + if($v4) { + # allow notify & request xfr, v4 & v6 + $nouveau .= "\tallow-notify: $v4 $dnsslavekey \n" + . "\trequest-xfr: $v4 $dnsslavekey \n\n"; + } + + if($v6) { + $nouveau .= "\tallow-notify: $v6 $dnsslavekey \n" + . "\trequest-xfr: $v6 $dnsslavekey \n\n"; + } + } + + $data =~ s/$debut.*/$debut\n$nouveau/gsm; + + write_file $f, $data; + copycat $f, $remote; + + my $cmd = "sudo nsdc patch && " + . " sudo rm /var/nsd3/ixfr.db"; + + remotecmd $user, $host, $port, $cmd; +} + +sub reconfig { + my ($self, $zname) = @_; + die "nsd3 reconfig not implemented."; +} + +sub delzone { + my ($self) = @_; + die "nsd3 delzone not implemented."; +} + +1; diff --git a/lib/interface/nsd4.pm b/lib/interface/nsd4.pm new file mode 100644 index 0000000..c0e7514 --- /dev/null +++ b/lib/interface/nsd4.pm @@ -0,0 +1,125 @@ +package interface::nsd4; +use v5.14; +use Moo; +use URI; +use fileutil ':all'; +use remotecmd ':all'; +use copycat ':all'; +use configuration ':all'; + +has [ qw/mycfg tmpdir primarydnsserver secondarydnsserver/ ] => qw/is ro required 1/; + +# on suppose que tout est déjà mis à jour dans le fichier +sub reload_sec { + my ($self, $slavedzones) = @_; + + $self->_reload_conf($slavedzones); + + my $cmd = "sudo nsd-control reconfig"; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + + remotecmd $user, $host, $port, $cmd +} + +# get, modify, push the file + +sub _reload_conf { + my ($self, $slavedzones) = @_; + + my $f = "file://$$self{tmpdir}/nsd.conf"; + my $remote = ($$self{mycfg}{cfg}) ? $$self{mycfg}{cfg} : undef; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + + $remote //= "ssh://$user". '@' . "$host/etc/nsd/nsd.conf"; + + copycat $remote, $f; + + my $data = read_file $f; + + # if it's the first time we get the configuration, fresh start + $data .= "\n## BEGIN_GENERATED" if( $data !~ /BEGIN_GENERATED/); + + my $v4 = get_v4_from_cfg($$self{primarydnsserver}); + my $v6 = get_v6_from_cfg($$self{primarydnsserver}); + + my $debut = "## BEGIN_GENERATED"; + + my $nouveau = ''; + my $dnsslavekey = get_dnsslavekey_from_cfg($$self{primarydnsserver}); + +# $nouveau .= " +#remote-control: +# control-enable: yes +# control-interface: 127.0.0.1 +# control-port: 8952 +# server-key-file: '/etc/nsd/nsd_server.key' +# server-cert-file: '/etc/nsd/nsd_server.pem' +# control-key-file: '/etc/nsd/nsd_control.key' +# control-cert-file: '/etc/nsd/nsd_control.pem' +# +#key: +# +## pattern : configuration to reproduce on every slaves + $nouveau .= " +pattern: +\tname: 'slavepattern' + "; + + if($v4) { + # allow notify & request xfr, v4 & v6 + $nouveau .= "\tallow-notify: $v4 \"$dnsslavekey\" \n" + . "\trequest-xfr: $v4 \"$dnsslavekey\" \n"; + } + + if($v6) { + $nouveau .= "\tallow-notify: $v6 \"$dnsslavekey\" \n" + . "\trequest-xfr: $v6 \"$dnsslavekey\" \n"; + } + + $nouveau .= "\n"; + + for(@{$slavedzones}) { + + $nouveau .= "zone:\n\tname: \"$$_{domain}\"\n" + . "\tzonefile: \"slave/$$_{domain}\"\n"; + $nouveau .= "\tinclude-pattern: 'slavepattern'\n\n"; + } + + $data =~ s/$debut.*/$debut\n$nouveau/gsm; + + write_file $f, $data; + copycat $f, $remote; + + my $cmd = "sudo nsd-control reconfig"; + + remotecmd $user, $host, $port, $cmd +} + +sub reconfig { + my ($self, $zname) = @_; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + my $cmd = "sudo nsd-control reconfig"; + remotecmd $user, $host, $port, $cmd +} + +sub delzone { + my ($self) = @_; + + my $user = get_user_from_cfg($$self{mycfg}); + my $host = get_host_from_cfg($$self{mycfg}); + my $port = get_port_from_cfg($$self{mycfg}); + my $cmd = "sudo nsd-control reconfig"; + remotecmd $user, $host, $port, $cmd; + #die "nsd4 delzone not implemented."; +} + +1; diff --git a/lib/remotecmd.pm b/lib/remotecmd.pm new file mode 100644 index 0000000..4e6a4f5 --- /dev/null +++ b/lib/remotecmd.pm @@ -0,0 +1,35 @@ +package remotecmd; +use v5.14; + +use Net::OpenSSH; +use Net::SSH q; + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/remotecmd/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/remotecmd/] ); + +sub remotecmd { + my ($user, $host, $port, $cmd) = @_; + + #sshopen2("-p '$port' $user\@$host", *READER, *WRITER, "$cmd") + #|| die "ssh: $!"; + + #system("ssh -p '$port' '$user". '@'. "$host' '$cmd'"); + + #my $ret = ''; + #$ret .= $_ while(); + + #close(READER); + #close(WRITER); + + my $str = "ssh -p $port $user". '@' . "$host '$cmd'"; + say ""; + say "CMD : $str"; + say ""; + qx/$str/; +} + +1; diff --git a/lib/rt/admin.pm b/lib/rt/admin.pm new file mode 100644 index 0000000..a8fa04b --- /dev/null +++ b/lib/rt/admin.pm @@ -0,0 +1,46 @@ +package rt::admin; + +use configuration ':all'; +use app; +use utf8; + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/rt_admin/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/rt_admin/] ); + +sub rt_admin { + my ($session, $param, $request) = @_; + my $res; + + eval { + my $app = app->new(get_cfg()); + my $user = $app->auth($$session{login}, $$session{passwd}); + + unless ($user && $$user{admin}) { + $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; + $$res{route} = '/'; + return $res; + } + + my $alldomains = $app->get_all_domains; + my $allusers = $app->get_all_users; + my $domains = $app->get_domains($$session{login}); + + $$res{template} = 'administration'; + $$res{params} = { + login => $$session{login} + , admin => 1 # we know it, or we couldn't reach this + , domains => $domains + , alldomains => $alldomains + , allusers => $allusers + }; + $app->disconnect(); + }; + + $res +} + +1; diff --git a/lib/rt/domain.pm b/lib/rt/domain.pm new file mode 100644 index 0000000..c64bbc4 --- /dev/null +++ b/lib/rt/domain.pm @@ -0,0 +1,502 @@ +package rt::domain; + +use v5.14; +use configuration ':all'; +use encryption ':all'; +use util ':all'; +use app; +use utf8; +use Dancer ':syntax'; +use Data::Dump qw( dump ); + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/ +rt_dom_cli_mod_entry +rt_dom_mod_entry +rt_dom_del_entry +rt_dom_del +rt_dom_add +rt_dom_details +rt_dom_update +rt_dom_updateraw +/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/ +rt_dom_cli_mod_entry +rt_dom_mod_entry +rt_dom_del_entry +rt_dom_del +rt_dom_add +rt_dom_details +rt_dom_update +rt_dom_updateraw + /] ); + +sub rt_dom_cli_mod_entry { + my ($session, $param, $request) = @_; + my $res; + + eval { + my $pass = encrypt($$param{pass}); + my $app = app->new(get_cfg()); + + my $user = $app->auth($$session{login}, $pass); + + unless ( $user && ( $$user{admin} || + $app->is_owning_domain($$user{login}, $$param{domain}))) { + $app->disconnect(); + $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; + $$res{route} = '/'; + return $res; + } + + $app->modify_entry( $$param{domain} + , { + type => $$param{type} + , name => $$param{name} + , host => $$param{host} + , ttl => $$param{ttl} + } + , { + newtype => $$param{type} + , newname => $$param{name} + , newhost => $$param{ip} + , newttl => $$param{ttl} + , newpriority => '' + }); + + $app->disconnect(); + }; + + $res +} + +sub rt_dom_mod_entry { + my ($session, $param, $request) = @_; + my $res; + + $$res{route} = '/domain/details/'. $$param{domain}; + + # check if user is logged + unless( $$session{login}) { + $$res{deferred}{errmsg} = q{Vous n'êtes pas enregistré. }; + $$res{sessiondestroy} = 1; + return $res; + } + + my @missingitems; + + for(qw/type name ttl domain name type host ttl + newtype newhost newname newttl/) { + push @missingitems, $_ unless($$param{$_}); + } + + if($$param{type} eq 'MX' && ! $$param{newpriority}) { + push @missingitems, "newpriority"; + } + + if(@missingitems != 0) { + $$res{deferred}{errmsg} = "Il manque : " . join ', ', @missingitems; + return $res; + } + + for(qw/type name ttl domain name type host ttl + newpriority newtype newhost newname newttl/) { + say "$_ : $$param{$_}" if $$param{$_}; + } + + eval { + my $app = app->new(get_cfg()); + my $user = $app->auth($$session{login}, $$session{passwd}); + + unless ( $user && ( $$user{admin} || + $app->is_owning_domain($$user{login}, $$param{domain}))) { + $app->disconnect(); + $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; + return $res; + } + + unless( $$param{domain} ) { + $$res{deferred}{errmsg} = q; + $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; + return $res; + } + + $app->modify_entry( $$param{domain} + , { + type => $$param{type} + , name => $$param{name} + , host => $$param{host} + , ttl => $$param{ttl} + } + , { + newtype => $$param{newtype} + , newname => $$param{newname} + , newhost => $$param{newhost} + , newttl => $$param{newttl} + , newpriority => $$param{newpriority} + }); + $app->disconnect(); + }; + + $res +} + +sub rt_dom_del_entry { + my ($session, $param, $request) = @_; + my $res; + + eval { + # Load :domain and search for corresponding data + my $app = app->new(get_cfg()); + + my $user = $app->auth($$session{login}, $$session{passwd}); + + unless ( $user && ( $$user{admin} || + $app->is_owning_domain($$user{login}, $$param{domain}))) { + $app->disconnect(); + $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; + $$res{route} = '/'; + return $res; + } + + unless( $$param{domain} ) { + $$res{deferred}{errmsg} = q{Domaine non renseigné.}; + $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; + return $res; + } + + $app->delete_entry( $$param{domain}, { + type => $$param{type}, + name => $$param{name}, + host => $$param{host}, + ttl => $$param{ttl} + }); + $app->disconnect(); + }; + + $$res{route} = '/domain/details/'. $$param{domain}; + + $res +} + +sub rt_dom_del { + my ($session, $param, $request) = @_; + my $res; + + unless( $$param{domain} ) { + $$res{deferred}{errmsg} = q; + $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; + return $res; + } + + if( ! is_domain_name($$param{domain})) { + $$res{deferred}{errmsg} = q; + $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; + return $res; + } + + eval { + my $app = app->new(get_cfg()); + my $user = $app->auth($$session{login}, $$session{passwd}); + + unless ( $user && ( $$user{admin} || + $app->is_owning_domain($$user{login}, $$param{domain}))) { + $app->disconnect(); + $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; + $$res{route} = '/'; + return $res; + } + + $app->delete_domain($$param{domain}); + $app->disconnect(); + }; + + if($@) { + $$res{deferred}{errmsg} = q{Impossible de supprimer le domaine. } . $@; + $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; + return $res; + } + + if( $$request{referer} =~ "/domain/details" ) { + $$res{route} = '/user/home'; + } + else { + $$res{route} = $$request{referer}; + } + + $res +} + +sub rt_dom_add { + my ($session, $param) = @_; + my $res; + + $$res{route} = '/user/home'; + + # check if user is logged + unless( $$session{login}) { + $$res{deferred}{errmsg} = q{Vous n'êtes pas enregistré. }; + $$res{sessiondestroy} = 1; + $$res{route} = '/'; + return $res; + } + + # check if domain parameter is set + unless( $$param{domain} && length $$param{domain} > 0) { + $$res{deferred}{errmsg} = + q{Domaine personnel non renseigné correctement. }; + return $res; + } + + # check if tld parameter is set + unless( $$param{tld} && length $$param{tld} > 0) { + $$res{deferred}{errmsg} = q{Choix du domaine non fait. }; + return $res; + } + + if(is_reserved($$param{domain})) { + $$res{deferred}{errmsg} = q{Nom de domaine réservé. }; + } + elsif ( ! is_domain_name($$param{domain}) ) { + $$res{deferred}{errmsg} = + q{Nom de domaine choisi comportant des caractères invalides. }; + } + elsif ( ! is_valid_tld($$param{tld}) ) { + $$res{deferred}{errmsg} = + q{Mauvais choix de domaine. }; + } + else { + + my $domain = $$param{domain} . $$param{tld}; + + eval { + my $app = app->new(get_cfg()); + my $user = $app->auth($$session{login}, $$session{passwd}); + $app->add_domain( $$user{login}, $domain ); + + $$res{addsession}{domainName} = $$param{domain}; + $$res{deferred}{succmsg} = + q{Le nom de domaine a bien été réservé ! }; + + $app->disconnect(); + }; + + if( $@ ) { + $$res{deferred}{errmsg} = q{Une erreur est survenue. } . $@; + } + + } + + $res +} + +sub rt_dom_details { + my ($session, $param, $request) = @_; + my $res; + + # check if user is logged & if domain parameter is set + unless($$session{login}) { + $$res{deferred}{errmsg} = q{Session inactive.}; + $$res{route} = '/'; + return $res; + } + + unless($$param{domain}) { + $$res{deferred}{errmsg} = q{Domaine non renseigné.}; + $$res{route} = '/'; + return $res; + } + + my $app; + eval { + $app = app->new(get_cfg()); + + my $user = $app->auth($$session{login}, $$session{passwd}); + + unless ( $user && ( $$user{admin} || + $app->is_owning_domain($$user{login}, $$param{domain}))) { + $app->disconnect(); + $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; + $$res{route} = '/'; + return $res; + } + + my $zone = $app->get_domain($$param{domain}); + + $app->disconnect(); + + $$res{template} = 'details'; + $$res{params} = { + login => $$session{login} + , admin => $$user{admin} + , domain => $$param{domain} + , domain_zone => $zone->output() + , user_ip => $$request{address} + }; + + if($$param{expert}) { + $$res{params}{expert} = 1; + } + else { + $$res{params}{a} = $zone->a(); + $$res{params}{aaaa} = $zone->aaaa(); + $$res{params}{cname} = $zone->cname(); + $$res{params}{ptr} = $zone->ptr(); + $$res{params}{mx} = $zone->mx(); + $$res{params}{ns} = $zone->ns(); + + for(qw/a aaaa cname ptr mx ns/) { + my $t = $_; + map { $$_{type} = uc $t } @{$$res{params}{$t}}; + } + } + }; + + if($@) { + $app->disconnect() if $app; + $$res{deferred}{errmsg} = $@; + $$res{route} = '/'; + return $res; + } + + $res +} + +sub rt_dom_update { + my ($session, $param) = @_; + my $res; + + unless( $$session{login} && $$param{domain} ) { + $$res{route} = '/'; + return $res; + } + + $$res{route} = '/domain/details/'. $$param{domain}; + + my @missingitems; + + for(qw/type name value ttl domain/) { + push @missingitems, $_ unless($$param{$_}); + } + + if($$param{type} eq 'MX' && ! $$param{priority}) { + push @missingitems, "priority"; + } + + if(@missingitems != 0) { + $$res{deferred}{errmsg} = "Il manque : " . join ', ', @missingitems; + return $res; + } + + eval { + my $app = app->new(get_cfg()); + my $user = $app->auth($$session{login}, $$session{passwd}); + + unless ( $user && ( $$user{admin} || + $app->is_owning_domain($$user{login}, $$param{domain}))) { + $app->disconnect(); + $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; + $$res{route} = '/'; + return $res; + } + + my $zone = $app->get_domain( $$param{domain} ); + + # TODO better naming convention + my $entries; + for( $$param{type} ) { + if($_ eq 'A') { $entries = $zone->a } + elsif( $_ eq 'AAAA') { $entries = $zone->aaaa } + elsif( $_ eq 'CNAME') { $entries = $zone->cname } + elsif( $_ eq 'MX') { $entries = $zone->mx } + elsif( $_ eq 'PTR') { $entries = $zone->ptr } + elsif( $_ eq 'NS') { $entries = $zone->ns } + elsif( $_ eq 'TXT') { $entries = $zone->txt } # TODO verify this + } + + my $new_entry = { + name => $$param{name} + , class => "IN" + , host => $$param{value} + , ttl => $$param{ttl} + , ORIGIN => $zone->origin + }; + + $$new_entry{priority} = $$param{priority} if $$param{type} eq 'MX'; + push @$entries, $new_entry; + + $zone->new_serial(); + + $app->update_domain( $zone , $$param{domain} ); + $app->disconnect(); + }; + + if ( $@ ) { + $$res{deferred}{errmsg} = q{Problème de mise à jour du domaine. }. $@; + } + + $res +} + +sub rt_dom_updateraw { + my ($session, $param, $request) = @_; + my $res; + + # check if user is logged & if domain parameter is set + unless($$session{login} && $$param{domain}) { + $$res{sessiondestroy} = 1; + $$res{route} = '/'; + return $res; + } + + my @missingitems; + + for(qw/domain zoneupdated/) { + push @missingitems, $_ unless($$param{$_}); + } + + if(@missingitems != 0) { + $$res{deferred}{errmsg} = "Il manque : " . join ', ', @missingitems; + $$res{route} = '/user/home'; + return $res; + } + + eval { + my $app = app->new(get_cfg()); + my $user = $app->auth($$session{login}, $$session{passwd}); + + # if the user exists and if + # he is admin or he owns the requested domain + unless ( $user && ( $$user{admin} || + $app->is_owning_domain($$user{login}, $$param{domain}))) { + $app->disconnect(); + $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; + $$res{route} = '/'; + return $res; + } + else { + my $success = + $app->update_domain_raw($$param{zoneupdated}, $$param{domain}); + + unless($success) { + $$res{deferred}{errmsg} = q{Problème de mise à jour du domaine.}; + } + + $$res{route} = '/domain/details/' . $$param{domain}; + } + + $app->disconnect(); + }; + + if($@) { + $$res{deferred}{errmsg} = $@; + $$res{route} = '/user/home'; + } + + $res +} + +1; diff --git a/lib/rt/root.pm b/lib/rt/root.pm new file mode 100644 index 0000000..20ee162 --- /dev/null +++ b/lib/rt/root.pm @@ -0,0 +1,45 @@ +package rt::root; + +use configuration ':all'; +use app; +use utf8; + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/rt_root/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/rt_root/] ); + +sub rt_root { + my ($session) = @_; + my $res; + + $$res{template} = 'index'; + + if( exists $$session{login} && length $$session{login} > 0) { + eval { + my $app = app->new(get_cfg()); + my $user = $app->auth($$session{login}, $$session{passwd}); + + if( $user ) { + $$res{params} = { + login => $$session{login} + , admin => $$user{admin} + , domains => $$user{domains} + }; + } + $app->disconnect(); + }; + + if( $@ ) { + $$res{deferred}{errmsg} = q{Une erreur est survenue. } . $@; + $$res{sessiondestroy} = 1; + } + + } + + $res +} + +1; diff --git a/lib/rt/user.pm b/lib/rt/user.pm new file mode 100644 index 0000000..2711f23 --- /dev/null +++ b/lib/rt/user.pm @@ -0,0 +1,255 @@ +package rt::user; + +use v5.14; +use configuration ':all'; +use encryption ':all'; +use app; +use utf8; + +use YAML::XS; + +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/ +rt_user_login +rt_user_del +rt_user_toggleadmin +rt_user_subscribe +rt_user_add +rt_user_home +/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/ + rt_user_login + rt_user_del + rt_user_toggleadmin + rt_user_subscribe + rt_user_add + rt_user_home + /] ); + +sub rt_user_login { + my ($session, $param, $request) = @_; + my $res; + + # Check if user is already logged + if ( exists $$session{login} && length $$session{login} > 0 ) { + $$res{deferred}{errmsg} = q{Vous êtes déjà connecté.}; + $$res{route} = '/'; + return $res; + } + + # Check user login and password + unless ( exists $$param{login} + && exists $$param{password} + && length $$param{login} > 0 + && length $$param{password} > 0 ) { + $$res{deferred}{errmsg} = q{Vous n'avez pas renseigné tous les paramètres.}; + $$res{route} = '/'; + return $res; + } + + eval { + my $app = app->new(get_cfg()); + my $pass = encrypt($$param{password}); + my $user = $app->auth($$param{login}, $pass); + + unless( $user ) { + $$res{deferred}{errmsg} = + q{Impossible de se connecter (login ou mot de passe incorrect).}; + $$res{route} = '/'; + return $res; + } + + $$res{addsession}{login} = $$param{login}; + $$res{addsession}{passwd} = $pass; + # TODO adds a freeze feature, not used for now + # $$res{addsession}{user} = freeze( $user ); + + if( $$user{admin} ) { + $$res{route} = '/admin'; + } + else { + $$res{route} = '/user/home'; + } + + $app->disconnect(); + }; + + if( $@ ) { + $$res{deferred}{errmsg} = q{Impossible de se connecter ! } . $@; + $$res{sessiondestroy} = 1; + $$res{route} = '/'; + } + + $res +} + +sub rt_user_del { + my ($session, $param, $request) = @_; + my $res; + + unless ( $$param{user} ) { + $$res{deferred}{errmsg} = q{Le nom d'utilisateur n'est pas renseigné.}; + return $res; + } + + eval { + my $app = app->new(get_cfg()); + + my $user = $app->auth($$session{login}, $$session{passwd}); + + if ( $user && $$user{admin} || $$session{login} eq $$param{user} ) { + $app->delete_user($$param{user}); + } + $app->disconnect(); + }; + + if ( $@ ) { + $$res{deferred}{errmsg} = + "L'utilisateur $$res{user} n'a pas pu être supprimé. $@"; + } + + if( $$request{referer} ) { + $$res{route} = $$request{referer}; + } + else { + $$res{route} = '/'; + } + + $res +} + +sub rt_user_toggleadmin { + my ($session, $param, $request) = @_; + my $res; + + unless( $$param{user} ) { + $$res{deferred}{errmsg} = q{L'utilisateur n'est pas défini.}; + $$res{route} = $$request{referer}; + return $res; + } + + eval { + my $app = app->new(get_cfg()); + + my $user = $app->auth($$session{login}, $$session{passwd}); + + unless ( $user && $$user{admin} ) { + $$res{deferred}{errmsg} = q{Vous n'êtes pas administrateur.}; + return $res; + } + + $app->toggle_admin($$param{user}); + $app->disconnect(); + }; + + if( $$request{referer} =~ '/admin' ) { + $$res{route} = $$request{referer}; + } + else { + $$res{route} = '/'; + } + + $res +} + +sub rt_user_subscribe { + my ($session, $param, $request) = @_; + my $res; + + if( $$session{login} ) { + $$res{route} = '/user/home'; + } + else { + $$res{template} = 'subscribe'; + } + + $res +} + +sub rt_user_add { + my ($session, $param, $request) = @_; + my $res; + + unless ( $$param{login} && $$param{password} && $$param{password2} ) { + $$res{deferred}{errmsg} = q{Identifiant ou mot de passe non renseigné.}; + $$res{route} = '/user/subscribe'; + return $res; + } + + unless ( $$param{password} eq $$param{password2} ) { + $$res{deferred}{errmsg} = q{Les mots de passes ne sont pas identiques.}; + $$res{route} = '/user/subscribe'; + return $res; + } + + + eval { + my $pass = encrypt($$param{password}); + + my $app = app->new(get_cfg()); + + $app->register_user($$param{login}, $pass); + $app->disconnect(); + + $$res{addsession}{login} = $$param{login}; + $$res{addsession}{passwd} = $pass; + $$res{route} = '/user/home'; + }; + + if($@) { + $$res{deferred}{errmsg} = q{Ce pseudo est déjà pris.} . $@; + $$res{route} = '/user/subscribe'; + return $res; + } + + $res +} + +sub rt_user_home { + my ($session, $param, $request) = @_; + my $res; + + $$res{template} = 'home'; + + eval { + my $app = app->new(get_cfg()); + + my $user = $app->auth($$session{login}, $$session{passwd}); + + unless( $user ) { + $$res{deferred}{errmsg} = q{Problème de connexion à votre compte.}; + $$res{sessiondestroy} = 1; + $$res{route} = '/'; + return $res; + } + + my $domains = $app->get_domains($$session{login}); + + my $dn = $$session{domainName}; + + #$$res{delsession}{domainName}; + + $$res{params} = { + login => $$session{login} + , admin => $$user{admin} + , domains => $domains + , provideddomains => $$app{tld} + , domainName => $dn + }; + + $app->disconnect(); + }; + + if( $@ ) { + $$res{sessiondestroy} = 1; + $$res{deferred}{errmsg} = q{On a chié quelque-part.} . $@; + $$res{route} = '/'; + } + + $res +} + +1; diff --git a/lib/testapp.pl b/lib/testapp.pl new file mode 100644 index 0000000..5ae03b2 --- /dev/null +++ b/lib/testapp.pl @@ -0,0 +1,113 @@ +#!/usr/bin/perl -w +use v5.14; +use strict; +use warnings; + +use File::Basename; +use utf8; +use YAML::XS; +use configuration ':all'; +use util ':all'; +use app; + +use rt::root ':all'; +use rt::domain ':all'; +use rt::user ':all'; +use rt::admin ':all'; + +#my $test_updateraw = sub { +# rt_dom_updateraw +# get_session( qw/login passwd/ ) +# , get_param( qw/domain zoneupdated/ ); # TODO verify this +#}; + +my $test_update = sub { + rt_dom_update + { qw/login test passwd test/ } + , { qw/type A + name www + value 10.0.0.1 + ttl 100 + priority 1 + domain test.netlib.re./ }; +}; + +#my $test_detail = sub { +# rt_dom_details +# get_session( qw/login passwd/ ) +# , get_param( qw/domain expert/ ) +# , get_request( qw/address referer/ ); +#}; + +my $test_add_domain = sub { + rt_dom_add + { qw/login test passwd test/} + , { qw/domain test tld .netlib.re./ }; +}; + +my $test_del_domain = sub { + rt_dom_del + { qw/login test passwd test/ } + , { qw/domain test.netlib.re./ } + , { qw/address referer/ }; # TODO +}; + +#my $test_del_entry = sub { +# rt_dom_del_entry +# get_session( qw/login passwd/ ) +# , get_param( qw/domain name type host ttl/ ) +# , get_request( qw/address referer/ ); +#}; + +#my $test_mod_entry = sub { +# rt_dom_mod_entry +# get_session( qw/login passwd/ ) +# , get_param( qw/domain name type host ttl/ ) +# , get_request( qw/address referer/ ); +#}; + +my $test_cli_mod_entry = sub { + rt_dom_cli_mod_entry + get_session( qw/login/ ) + , get_param( qw/passwd domain name type host ttl ip/ ); +}; + +#any ['get', 'post'] => '/admin' => sub { +# rt_admin +# get_session( qw/login passwd/ ); +#}; + +#get '/home' => sub { +# rt_user_home +# get_session( qw/login passwd/ ) +# , get_param( qw// ) +# , get_request( qw// ); +#}; + +my $test_del_user = sub { + rt_user_del + get_session( qw/login passwd/ ) + , { qw/user test/ } + , { qw/referer/ }; +}; + +my $test_add_user = sub { + rt_user_add + { qw// } + , { qw/login test password test password2 test/ } + , { qw// }; +}; + +say "Tests - "; + +# get '/subscribe' => sub { +# rt_user_subscribe +# get_session( qw/login/ ); +# }; + +#my $test_toggle_admin = sub { +# rt_user_toggleadmin +# { qw/login passwd/ } +# , get_param( qw/user/ ) +# , get_request( qw/referer/ ); +#}; diff --git a/lib/util.pm b/lib/util.pm new file mode 100644 index 0000000..3da3c37 --- /dev/null +++ b/lib/util.pm @@ -0,0 +1,31 @@ +package util; +use v5.10; + +use configuration ':all'; +use YAML::XS; +use Exporter 'import'; +# what we want to export eventually +our @EXPORT_OK = qw/is_domain_name is_valid_tld/; + +# bundle of exports (tags) +our %EXPORT_TAGS = ( all => [qw/is_domain_name is_valid_tld/] ); + +# TODO we can check if dn matches our domain name +sub is_domain_name { + my ($dn) = @_; + my $ndd = qr/^ + ([a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]*[.])* + [a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]([.])? + $/x; + return $dn =~ $ndd; +} + +sub is_valid_tld { + my ($tld) = @_; + + my $cfg = get_cfg; + + grep { $_ eq $tld } @{$$cfg{tld}}; +} + +1; diff --git a/lib/zone.pm b/lib/zone.pm new file mode 100644 index 0000000..8bd0fca --- /dev/null +++ b/lib/zone.pm @@ -0,0 +1,284 @@ +package zone; +use v5.14; +use Moo; + +use Modern::Perl; + +# TODO all this file is to redesign + +use getiface ':all'; +use copycat ':all'; +use fileutil ':all'; +use configuration ':all'; +use Data::Dump qw( dump ); + +use zonefile; + +# primary dns interface +has dnsi => ( is => 'rw', builder => '_void_arr'); + +# dns interface for secondary name servers +has dnsisec => ( is => 'rw', builder => '_void'); + +has [ qw/tld tmpdir domain primarydnsserver secondarydnsserver slavedzones/ ] +=> qw/is ro required 1/; + +sub _void { my $x = ''; \$x; } +sub _void_arr { [] } + +sub get_ztmp_file_ {my $s = shift; "$$s{tmpdir}/$$s{domain}" } +sub get_ztpl_dir_ {my $s = shift; "$$s{dnsi}{mycfg}{zonedir}" } +sub get_ztpl_file_ { + my $s = shift; + + # for each TLD + for(@{$$s{tld}}) { + # if our domain is part of this TLD, get the right template + if($$s{domain} =~ $_) { + return $s->get_ztpl_dir_() . '/' . $_ . '.tpl'; + } + } + + die "There is no template for $$s{domain}."; +} + +sub get_dnsserver_interface { + my ($self, $dnsserver) = @_; + my $cfg = { + mycfg => $dnsserver + , primarydnsserver => $$self{primarydnsserver} + , secondarydnsserver => $$self{secondarydnsserver} + , tmpdir => $$self{tmpdir} + }; + + getiface $$dnsserver{app}, $cfg +} + +sub get_dns_server_interfaces { + my $self = shift; + my $primary = $$self{primarydnsserver}; + my $s = $$self{secondarydnsserver}; + + my $prim = $self->get_dnsserver_interface($primary); + + my @sec; + for(@{$s}) { + push @sec, $self->get_dnsserver_interface($_); + } + + ($prim, [ @sec ]) +} + +sub BUILD { + my $self = shift; + ($$self{dnsi}, $$self{dnsisec}) = $self->get_dns_server_interfaces() +} + +# change the origin in a zone file template +sub mod_orig_template { + my ($file, $domain) = @_; + say "s/CHANGEMEORIGIN/$domain/ on $file"; + qx[sed -i "s/CHANGEMEORIGIN/$domain/" $file]; +} + +sub get_remote_zf_ { + my $self = shift; + "$$self{dnsi}{mycfg}{zonedir}/$$self{domain}" +} + +sub are_same_records_ { + my ($a, $b) = @_; + + #debug({ a => $a }); + #debug({ b => $b }); + + #$a->{priority} eq $b->{priority} && + ( $$a{name} eq $$b{name} && + $$a{host} eq $$b{host} && + $$a{ttl} == $$b{ttl} ) +} + +# returns the lists of domains of a certain type +sub get_records_ { + my ($zone, $entry) = @_; + + for( lc $$entry{type} ) { + if ($_ eq 'a') { return $zone->a } + elsif ($_ eq 'aaaa') { return $zone->aaaa } + elsif ($_ eq 'cname') { return $zone->cname } + elsif ($_ eq 'ns') { return $zone->ns } + elsif ($_ eq 'mx') { return $zone->mx } + elsif ($_ eq 'ptr') { return $zone->ptr } + } + + die 'Impossible to get the entry type.' +} + +sub reload_secondary_dns_servers { + my $self = shift; + $_->reload_sec($$self{slavedzones}) for(@{$$self{dnsisec}}) +} + +sub delete_entry { + my ($self, $entryToDelete) = @_; + + my $zone = $self->get(); + + my $records = get_records_ $zone, $entryToDelete; + + if( defined $records ) { + foreach my $i ( 0 .. scalar @{$records}-1 ) { + if(are_same_records_($records->[$i], $entryToDelete)) { + delete $records->[$i]; + } + } + } + + $zone +} + +sub modify_entry { + my ($self, $entryToModify, $newEntry) = @_; + + my $zone = $self->get(); + + my $records = get_records_ $zone, $entryToModify; + + if( defined $records ) { + + foreach my $i ( 0 .. scalar @{$records}-1 ) { + + if(are_same_records_($records->[$i], $entryToModify)) { + + say "ENTRY TO MODIFY"; + + say $records->[$i]->{name} . ' = ' . $newEntry->{newname}; + say $records->[$i]->{host} . ' = ' . $newEntry->{newhost}; + say $records->[$i]->{ttl} . ' = ' . $newEntry->{newttl}; + #say $records->[$i]->{type} . ' = ' . $newEntry->{newtype}; + + $records->[$i]->{name} = $newEntry->{newname}; + $records->[$i]->{host} = $newEntry->{newhost}; + $records->[$i]->{ttl} = $newEntry->{newttl}; + #$records->[$i]->{type} = $newEntry->{newtype}; + + if( $$newEntry{newtype} eq 'MX' ) { + say + $records->[$i]->{priority}.' = '.$newEntry->{newpriority}; + $records->[$i]->{priority} = $newEntry->{newpriority}; + } + } + } + } + + dump($records); + + $zone +} + +sub get { + my $self = shift; + my $file = $self->get_remote_zf_(); + my $dest = $self->get_ztmp_file_(); + + copycat ($file, $dest); + + zonefile->new(domain => $$self{domain}, zonefile => $dest); +} + +=pod + copie du template pour créer une nouvelle zone + update du serial + ajout de la zone via dnsapp (rndc, knot…) + retourne la zone + le nom de la zone +=cut + +sub addzone { + my ($self) = @_; + + my $tpl = $self->get_ztpl_file_(); + my $tmpfile = $self->get_ztmp_file_(); + + copycat ($tpl, $tmpfile); # get the template + + # get the file path + my $f = URI->new($tmpfile); + + # sed CHANGEMEORIGIN by the real origin + mod_orig_template ($f->path, $$self{domain}); + + my $zonefile = zonefile->new(zonefile => $f->path + , domain => $$self{domain}); + $zonefile->new_serial(); # update the serial number + + # write the new zone tmpfile to disk + write_file $f->path, $zonefile->output(); + + my $file = $self->get_remote_zf_(); + copycat ($tmpfile, $file); # put the final zone on the server + unlink($f->path); # del the temporary file + + # add new zone on the primary ns + $self->dnsi->primary_addzone($$self{domain}); + + # add new zone on secondary ns + $self->reload_secondary_dns_servers() +} + +=pod + màj du serial + push reload de la conf +=cut + +sub update { + my ($self, $zonefile) = @_; + + # update the serial number + $zonefile->new_serial(); + + my $tmpfile = $self->get_ztmp_file_(); + + # write the new zone tmpfile to disk + write_file $tmpfile, $zonefile->output(); + + my $file = $self->get_remote_zf_(); + copycat ($tmpfile, $file); # put the final zone on the server + unlink($tmpfile); # del the temporary file + + $self->dnsi->reload($$self{domain}); +} + +=pod + udpate via the raw content of the zonefile +=cut + +sub update_raw { + my ($self, $zonetext) = @_; + + my $zonefile; + my $file = $self->get_ztmp_file_(); + + # write the updated zone file to disk + write_file $file, $zonetext; + + eval { $zonefile = zonefile->new(zonefile => $file + , domain => $$self{domain}); }; + + if( $@ ) { + unlink($file); + die 'zone update_raw, zonefile->new error. ' . $@; + } + + unlink($file); + + $self->update($zonefile) +} + +sub del { + my ($self) = @_; + $self->dnsi->delzone($$self{domain}); + $self->dnsi->reconfig(); + $self->reload_secondary_dns_servers() +} + +1; diff --git a/lib/zonefile.pm b/lib/zonefile.pm new file mode 100644 index 0000000..87e553a --- /dev/null +++ b/lib/zonefile.pm @@ -0,0 +1,52 @@ +package zonefile; +use v5.14; +use Moo; +use DNS::ZoneParse; + +has zone => qw/is rw/ ; +has [ qw/domain/ ] => qw/ is ro required 1/; +has [ qw/zonefile/ ] => qw/ is rw required 1/; + +sub BUILD { + my ($self) = @_; + + my $filename = $$self{zonefile}; + if($filename =~ "://") + { + my $fileuri = URI->new($filename); + $filename = $fileuri->path; + } + + $$self{zone} = DNS::ZoneParse->new($filename, $$self{domain}); +} + +sub new_serial { + my $self = shift; + $self->zone->new_serial(); +} + +sub origin { + my $self = shift; + $self->zone->origin(); +} + +sub output { + my $self = shift; + $self->zone->output(); +} + +sub dump { + my $self = shift; + $self->zone->dump(); +} + +# better encapsulation +sub a { my $self = shift; $self->zone->a } +sub aaaa { my $self = shift; $self->zone->aaaa } +sub cname { my $self = shift; $self->zone->cname } +sub ns { my $self = shift; $self->zone->ns } +sub mx { my $self = shift; $self->zone->mx } +sub ptr { my $self = shift; $self->zone->ptr } +sub txt { my $self = shift; $self->zone->txt } # TODO TEST THIS + +1; diff --git a/www/notes.txt b/notes.txt similarity index 100% rename from www/notes.txt rename to notes.txt diff --git a/www/public/404.html b/public/404.html similarity index 88% rename from www/public/404.html rename to public/404.html index fc3e1c4..c214958 100644 --- a/www/public/404.html +++ b/public/404.html @@ -12,7 +12,7 @@

Page Not Found

Sorry, this is the void.

diff --git a/www/public/500.html b/public/500.html similarity index 88% rename from www/public/500.html rename to public/500.html index 232dde9..2878db6 100644 --- a/www/public/500.html +++ b/public/500.html @@ -12,7 +12,7 @@

Internal Server Error

Wooops, something went wrong

diff --git a/www/public/css/bootstrap-theme.min.css b/public/css/bootstrap-theme.min.css similarity index 100% rename from www/public/css/bootstrap-theme.min.css rename to public/css/bootstrap-theme.min.css diff --git a/www/public/css/bootstrap.min.css b/public/css/bootstrap.min.css similarity index 100% rename from www/public/css/bootstrap.min.css rename to public/css/bootstrap.min.css diff --git a/public/css/error.css b/public/css/error.css new file mode 100644 index 0000000..003ee2a --- /dev/null +++ b/public/css/error.css @@ -0,0 +1,70 @@ +body { + font-family: Lucida,sans-serif; +} + +h1 { + color: #AA0000; + border-bottom: 1px solid #444; +} + +h2 { color: #444; } + +pre { + font-family: "lucida console","monaco","andale mono","bitstream vera sans mono","consolas",monospace; + font-size: 12px; + border-left: 2px solid #777; + padding-left: 1em; +} + +footer { + font-size: 10px; +} + +span.key { + color: #449; + font-weight: bold; + width: 120px; + display: inline; +} + +span.value { + color: #494; +} + +/* these are for the message boxes */ + +pre.content { + background-color: #eee; + color: #000; + padding: 1em; + margin: 0; + border: 1px solid #aaa; + border-top: 0; + margin-bottom: 1em; +} + +div.title { + font-family: "lucida console","monaco","andale mono","bitstream vera sans mono","consolas",monospace; + font-size: 12px; + background-color: #aaa; + color: #444; + font-weight: bold; + padding: 3px; + padding-left: 10px; +} + +pre.content span.nu { + color: #889; + margin-right: 10px; +} + +pre.error { + background: #334; + color: #ccd; + padding: 1em; + border-top: 1px solid #000; + border-left: 1px solid #000; + border-right: 1px solid #eee; + border-bottom: 1px solid #eee; +} + diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 0000000..706c3e5 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,189 @@ + +body { +margin: 0; +margin-bottom: 25px; +padding: 0; +background-color: #ddd; +background-image: url("/images/perldancer-bg.jpg"); +background-repeat: no-repeat; +background-position: top left; + +font-family: "Lucida Grande", "Bitstream Vera Sans", "Verdana"; +font-size: 13px; +color: #333; +} + +h1 { +font-size: 28px; +color: #000; +} + +a {color: #03c} +a:hover { +background-color: #03c; +color: white; +text-decoration: none; +} + +#page { +background-color: #ddd; +width: 750px; +margin: auto; +margin-left: auto; +padding-left: 0px; +margin-right: auto; +} + +#content { +background-color: white; +border: 3px solid #aaa; +border-top: none; +padding: 25px; +width: 500px; +} + +#sidebar { +float: right; +width: 175px; +} + +#header, #about, #getting-started { +padding-left: 75px; +padding-right: 30px; +} + + +#header { +background-image: url("/images/perldancer.jpg"); +background-repeat: no-repeat; +background-position: top left; +height: 64px; +} +#header h1, #header h2 {margin: 0} +#header h2 { +color: #888; +font-weight: normal; +font-size: 16px; +} + +#about h3 { +margin: 0; +margin-bottom: 10px; +font-size: 14px; +} + +#about-content { +background-color: #ffd; +border: 1px solid #fc0; +margin-left: -11px; +} +#about-content table { +margin-top: 10px; +margin-bottom: 10px; +font-size: 11px; +border-collapse: collapse; +} +#about-content td { +padding: 10px; +padding-top: 3px; +padding-bottom: 3px; +} +#about-content td.name {color: #555} +#about-content td.value {color: #000} + +#about-content.failure { +background-color: #fcc; +border: 1px solid #f00; +} +#about-content.failure p { +margin: 0; +padding: 10px; +} + +#getting-started { +border-top: 1px solid #ccc; +margin-top: 25px; +padding-top: 15px; +} +#getting-started h1 { +margin: 0; +font-size: 20px; +} +#getting-started h2 { +margin: 0; +font-size: 14px; +font-weight: normal; +color: #333; +margin-bottom: 25px; +} +#getting-started ol { +margin-left: 0; +padding-left: 0; +} +#getting-started li { +font-size: 18px; +color: #888; +margin-bottom: 25px; +} +#getting-started li h2 { +margin: 0; +font-weight: normal; +font-size: 18px; +color: #333; +} +#getting-started li p { +color: #555; +font-size: 13px; +} + +#search { +margin: 0; +padding-top: 10px; +padding-bottom: 10px; +font-size: 11px; +} +#search input { +font-size: 11px; +margin: 2px; +} +#search-text {width: 170px} + +#sidebar ul { +margin-left: 0; +padding-left: 0; +} +#sidebar ul h3 { +margin-top: 25px; +font-size: 16px; +padding-bottom: 10px; +border-bottom: 1px solid #ccc; +} +#sidebar li { +list-style-type: none; +} +#sidebar ul.links li { +margin-bottom: 5px; +} + +h1, h2, h3, h4, h5 { +font-family: sans-serif; +margin: 1.2em 0 0.6em 0; +} + +p { +line-height: 1.5em; +margin: 1.6em 0; +} + +code, tt { + font-family: 'Andale Mono', Monaco, 'Liberation Mono', 'Bitstream Vera Sans Mono', 'DejaVu Sans Mono', monospace; +} + +#footer { +clear: both; +padding-top: 2em; +text-align: center; +padding-right: 160px; +font-family: sans-serif; +font-size: 10px; +} diff --git a/www/public/dispatch.cgi b/public/dispatch.cgi similarity index 62% rename from www/public/dispatch.cgi rename to public/dispatch.cgi index 3bb7f2a..32c5d96 100755 --- a/www/public/dispatch.cgi +++ b/public/dispatch.cgi @@ -1,15 +1,16 @@ #!/usr/bin/env perl -use Dancer ':syntax'; +BEGIN { $ENV{DANCER_APPHANDLER} = 'PSGI';} +use Dancer2; use FindBin '$RealBin'; use Plack::Runner; # For some reason Apache SetEnv directives dont propagate -# correctly to the dispatchers, so forcing PSGI and env here +# correctly to the dispatchers, so forcing PSGI and env here # is safer. set apphandler => 'PSGI'; set environment => 'production'; -my $psgi = path($RealBin, '..', 'bin', 'app.pl'); +my $psgi = path($RealBin, '..', 'bin', 'app.psgi'); die "Unable to read startup script: $psgi" unless -r $psgi; Plack::Runner->run($psgi); diff --git a/www/public/dispatch.fcgi b/public/dispatch.fcgi similarity index 67% rename from www/public/dispatch.fcgi rename to public/dispatch.fcgi index 8c42e3a..e802223 100755 --- a/www/public/dispatch.fcgi +++ b/public/dispatch.fcgi @@ -1,15 +1,16 @@ #!/usr/bin/env perl -use Dancer ':syntax'; +BEGIN { $ENV{DANCER_APPHANDLER} = 'PSGI';} +use Dancer2; use FindBin '$RealBin'; use Plack::Handler::FCGI; # For some reason Apache SetEnv directives dont propagate -# correctly to the dispatchers, so forcing PSGI and env here +# correctly to the dispatchers, so forcing PSGI and env here # is safer. set apphandler => 'PSGI'; set environment => 'production'; -my $psgi = path($RealBin, '..', 'bin', 'app.pl'); +my $psgi = path($RealBin, '..', 'bin', 'app.psgi'); my $app = do($psgi); die "Unable to read startup script: $@" if $@; my $server = Plack::Handler::FCGI->new(nproc => 5, detach => 1); diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..96c7465 Binary files /dev/null and b/public/favicon.ico differ diff --git a/www/public/fonts/glyphicons-halflings-regular.eot b/public/fonts/glyphicons-halflings-regular.eot similarity index 100% rename from www/public/fonts/glyphicons-halflings-regular.eot rename to public/fonts/glyphicons-halflings-regular.eot diff --git a/www/public/fonts/glyphicons-halflings-regular.svg b/public/fonts/glyphicons-halflings-regular.svg similarity index 100% rename from www/public/fonts/glyphicons-halflings-regular.svg rename to public/fonts/glyphicons-halflings-regular.svg diff --git a/www/public/fonts/glyphicons-halflings-regular.ttf b/public/fonts/glyphicons-halflings-regular.ttf similarity index 100% rename from www/public/fonts/glyphicons-halflings-regular.ttf rename to public/fonts/glyphicons-halflings-regular.ttf diff --git a/www/public/fonts/glyphicons-halflings-regular.woff b/public/fonts/glyphicons-halflings-regular.woff similarity index 100% rename from www/public/fonts/glyphicons-halflings-regular.woff rename to public/fonts/glyphicons-halflings-regular.woff diff --git a/public/images/perldancer-bg.jpg b/public/images/perldancer-bg.jpg new file mode 100644 index 0000000..6ee6b77 Binary files /dev/null and b/public/images/perldancer-bg.jpg differ diff --git a/public/images/perldancer.jpg b/public/images/perldancer.jpg new file mode 100644 index 0000000..3f9e718 Binary files /dev/null and b/public/images/perldancer.jpg differ diff --git a/www/public/js/jquery.min.js b/public/javascripts/jquery.js similarity index 100% rename from www/public/js/jquery.min.js rename to public/javascripts/jquery.js diff --git a/www/public/js/bootstrap.min.js b/public/js/bootstrap.min.js similarity index 100% rename from www/public/js/bootstrap.min.js rename to public/js/bootstrap.min.js diff --git a/public/js/jquery.min.js b/public/js/jquery.min.js new file mode 100644 index 0000000..2add4ad --- /dev/null +++ b/public/js/jquery.min.js @@ -0,0 +1,23 @@ +/* + * jQuery JavaScript Library v1.7.2 + * http://jquery.com/ + * + * Copyright 2011, John Resig + * Dual licensed under the MIT or GPL Version 2 licenses. + * http://jquery.org/license + * + * Includes Sizzle.js + * http://sizzlejs.com/ + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * + * Date: Mon Jun 4 13:11:32 UTC 2012 + */ +(function(bd,L){var av=bd.document,bu=bd.navigator,bm=bd.location;var b=(function(){var bF=function(b0,b1){return new bF.fn.init(b0,b1,bD)},bU=bd.jQuery,bH=bd.$,bD,bY=/^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,bM=/\S/,bI=/^\s+/,bE=/\s+$/,bA=/^<(\w+)\s*\/?>(?:<\/\1>)?$/,bN=/^[\],:{}\s]*$/,bW=/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,bP=/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,bJ=/(?:^|:|,)(?:\s*\[)+/g,by=/(webkit)[ \/]([\w.]+)/,bR=/(opera)(?:.*version)?[ \/]([\w.]+)/,bQ=/(msie) ([\w.]+)/,bS=/(mozilla)(?:.*? rv:([\w.]+))?/,bB=/-([a-z]|[0-9])/ig,bZ=/^-ms-/,bT=function(b0,b1){return(b1+"").toUpperCase()},bX=bu.userAgent,bV,bC,e,bL=Object.prototype.toString,bG=Object.prototype.hasOwnProperty,bz=Array.prototype.push,bK=Array.prototype.slice,bO=String.prototype.trim,bv=Array.prototype.indexOf,bx={};bF.fn=bF.prototype={constructor:bF,init:function(b0,b4,b3){var b2,b5,b1,b6;if(!b0){return this}if(b0.nodeType){this.context=this[0]=b0;this.length=1;return this}if(b0==="body"&&!b4&&av.body){this.context=av;this[0]=av.body;this.selector=b0;this.length=1;return this}if(typeof b0==="string"){if(b0.charAt(0)==="<"&&b0.charAt(b0.length-1)===">"&&b0.length>=3){b2=[null,b0,null]}else{b2=bY.exec(b0)}if(b2&&(b2[1]||!b4)){if(b2[1]){b4=b4 instanceof bF?b4[0]:b4;b6=(b4?b4.ownerDocument||b4:av);b1=bA.exec(b0);if(b1){if(bF.isPlainObject(b4)){b0=[av.createElement(b1[1])];bF.fn.attr.call(b0,b4,true)}else{b0=[b6.createElement(b1[1])]}}else{b1=bF.buildFragment([b2[1]],[b6]);b0=(b1.cacheable?bF.clone(b1.fragment):b1.fragment).childNodes}return bF.merge(this,b0)}else{b5=av.getElementById(b2[2]);if(b5&&b5.parentNode){if(b5.id!==b2[2]){return b3.find(b0)}this.length=1;this[0]=b5}this.context=av;this.selector=b0;return this}}else{if(!b4||b4.jquery){return(b4||b3).find(b0)}else{return this.constructor(b4).find(b0)}}}else{if(bF.isFunction(b0)){return b3.ready(b0)}}if(b0.selector!==L){this.selector=b0.selector;this.context=b0.context}return bF.makeArray(b0,this)},selector:"",jquery:"1.7.2",length:0,size:function(){return this.length},toArray:function(){return bK.call(this,0)},get:function(b0){return b0==null?this.toArray():(b0<0?this[this.length+b0]:this[b0])},pushStack:function(b1,b3,b0){var b2=this.constructor();if(bF.isArray(b1)){bz.apply(b2,b1)}else{bF.merge(b2,b1)}b2.prevObject=this;b2.context=this.context;if(b3==="find"){b2.selector=this.selector+(this.selector?" ":"")+b0}else{if(b3){b2.selector=this.selector+"."+b3+"("+b0+")"}}return b2},each:function(b1,b0){return bF.each(this,b1,b0)},ready:function(b0){bF.bindReady();bC.add(b0);return this},eq:function(b0){b0=+b0;return b0===-1?this.slice(b0):this.slice(b0,b0+1)},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},slice:function(){return this.pushStack(bK.apply(this,arguments),"slice",bK.call(arguments).join(","))},map:function(b0){return this.pushStack(bF.map(this,function(b2,b1){return b0.call(b2,b1,b2)}))},end:function(){return this.prevObject||this.constructor(null)},push:bz,sort:[].sort,splice:[].splice};bF.fn.init.prototype=bF.fn;bF.extend=bF.fn.extend=function(){var b9,b2,b0,b1,b6,b7,b5=arguments[0]||{},b4=1,b3=arguments.length,b8=false;if(typeof b5==="boolean"){b8=b5;b5=arguments[1]||{};b4=2}if(typeof b5!=="object"&&!bF.isFunction(b5)){b5={}}if(b3===b4){b5=this;--b4}for(;b40){return}bC.fireWith(av,[bF]);if(bF.fn.trigger){bF(av).trigger("ready").off("ready")}}},bindReady:function(){if(bC){return}bC=bF.Callbacks("once memory");if(av.readyState==="complete"){return setTimeout(bF.ready,1)}if(av.addEventListener){av.addEventListener("DOMContentLoaded",e,false);bd.addEventListener("load",bF.ready,false)}else{if(av.attachEvent){av.attachEvent("onreadystatechange",e);bd.attachEvent("onload",bF.ready);var b0=false;try{b0=bd.frameElement==null}catch(b1){}if(av.documentElement.doScroll&&b0){bw()}}}},isFunction:function(b0){return bF.type(b0)==="function"},isArray:Array.isArray||function(b0){return bF.type(b0)==="array"},isWindow:function(b0){return b0!=null&&b0==b0.window},isNumeric:function(b0){return !isNaN(parseFloat(b0))&&isFinite(b0)},type:function(b0){return b0==null?String(b0):bx[bL.call(b0)]||"object"},isPlainObject:function(b2){if(!b2||bF.type(b2)!=="object"||b2.nodeType||bF.isWindow(b2)){return false}try{if(b2.constructor&&!bG.call(b2,"constructor")&&!bG.call(b2.constructor.prototype,"isPrototypeOf")){return false}}catch(b1){return false}var b0;for(b0 in b2){}return b0===L||bG.call(b2,b0)},isEmptyObject:function(b1){for(var b0 in b1){return false}return true},error:function(b0){throw new Error(b0)},parseJSON:function(b0){if(typeof b0!=="string"||!b0){return null}b0=bF.trim(b0);if(bd.JSON&&bd.JSON.parse){return bd.JSON.parse(b0)}if(bN.test(b0.replace(bW,"@").replace(bP,"]").replace(bJ,""))){return(new Function("return "+b0))()}bF.error("Invalid JSON: "+b0)},parseXML:function(b2){if(typeof b2!=="string"||!b2){return null}var b0,b1;try{if(bd.DOMParser){b1=new DOMParser();b0=b1.parseFromString(b2,"text/xml")}else{b0=new ActiveXObject("Microsoft.XMLDOM");b0.async="false";b0.loadXML(b2)}}catch(b3){b0=L}if(!b0||!b0.documentElement||b0.getElementsByTagName("parsererror").length){bF.error("Invalid XML: "+b2)}return b0},noop:function(){},globalEval:function(b0){if(b0&&bM.test(b0)){(bd.execScript||function(b1){bd["eval"].call(bd,b1)})(b0)}},camelCase:function(b0){return b0.replace(bZ,"ms-").replace(bB,bT)},nodeName:function(b1,b0){return b1.nodeName&&b1.nodeName.toUpperCase()===b0.toUpperCase()},each:function(b3,b6,b2){var b1,b4=0,b5=b3.length,b0=b5===L||bF.isFunction(b3);if(b2){if(b0){for(b1 in b3){if(b6.apply(b3[b1],b2)===false){break}}}else{for(;b40&&b0[0]&&b0[b1-1])||b1===0||bF.isArray(b0));if(b3){for(;b21?aK.call(arguments,0):bG;if(!(--bw)){bC.resolveWith(bC,bx)}}}function bz(bF){return function(bG){bB[bF]=arguments.length>1?aK.call(arguments,0):bG;bC.notifyWith(bE,bB)}}if(e>1){for(;bv
a";bH=bv.getElementsByTagName("*");bE=bv.getElementsByTagName("a")[0];if(!bH||!bH.length||!bE){return{}}bF=av.createElement("select");bx=bF.appendChild(av.createElement("option"));bD=bv.getElementsByTagName("input")[0];bI={leadingWhitespace:(bv.firstChild.nodeType===3),tbody:!bv.getElementsByTagName("tbody").length,htmlSerialize:!!bv.getElementsByTagName("link").length,style:/top/.test(bE.getAttribute("style")),hrefNormalized:(bE.getAttribute("href")==="/a"),opacity:/^0.55/.test(bE.style.opacity),cssFloat:!!bE.style.cssFloat,checkOn:(bD.value==="on"),optSelected:bx.selected,getSetAttribute:bv.className!=="t",enctype:!!av.createElement("form").enctype,html5Clone:av.createElement("nav").cloneNode(true).outerHTML!=="<:nav>",submitBubbles:true,changeBubbles:true,focusinBubbles:false,deleteExpando:true,noCloneEvent:true,inlineBlockNeedsLayout:false,shrinkWrapBlocks:false,reliableMarginRight:true,pixelMargin:true};b.boxModel=bI.boxModel=(av.compatMode==="CSS1Compat");bD.checked=true;bI.noCloneChecked=bD.cloneNode(true).checked;bF.disabled=true;bI.optDisabled=!bx.disabled;try{delete bv.test}catch(bB){bI.deleteExpando=false}if(!bv.addEventListener&&bv.attachEvent&&bv.fireEvent){bv.attachEvent("onclick",function(){bI.noCloneEvent=false});bv.cloneNode(true).fireEvent("onclick")}bD=av.createElement("input");bD.value="t";bD.setAttribute("type","radio");bI.radioValue=bD.value==="t";bD.setAttribute("checked","checked");bD.setAttribute("name","t");bv.appendChild(bD);bC=av.createDocumentFragment();bC.appendChild(bv.lastChild);bI.checkClone=bC.cloneNode(true).cloneNode(true).lastChild.checked;bI.appendChecked=bD.checked;bC.removeChild(bD);bC.appendChild(bv);if(bv.attachEvent){for(by in {submit:1,change:1,focusin:1}){bA="on"+by;bw=(bA in bv);if(!bw){bv.setAttribute(bA,"return;");bw=(typeof bv[bA]==="function")}bI[by+"Bubbles"]=bw}}bC.removeChild(bv);bC=bF=bx=bv=bD=null;b(function(){var bM,bV,bW,bU,bO,bP,bR,bL,bK,bQ,bN,e,bT,bS=av.getElementsByTagName("body")[0];if(!bS){return}bL=1;bT="padding:0;margin:0;border:";bN="position:absolute;top:0;left:0;width:1px;height:1px;";e=bT+"0;visibility:hidden;";bK="style='"+bN+bT+"5px solid #000;";bQ="
";bM=av.createElement("div");bM.style.cssText=e+"width:0;height:0;position:static;top:0;margin-top:"+bL+"px";bS.insertBefore(bM,bS.firstChild);bv=av.createElement("div");bM.appendChild(bv);bv.innerHTML="
t
";bz=bv.getElementsByTagName("td");bw=(bz[0].offsetHeight===0);bz[0].style.display="";bz[1].style.display="none";bI.reliableHiddenOffsets=bw&&(bz[0].offsetHeight===0);if(bd.getComputedStyle){bv.innerHTML="";bR=av.createElement("div");bR.style.width="0";bR.style.marginRight="0";bv.style.width="2px";bv.appendChild(bR);bI.reliableMarginRight=(parseInt((bd.getComputedStyle(bR,null)||{marginRight:0}).marginRight,10)||0)===0}if(typeof bv.style.zoom!=="undefined"){bv.innerHTML="";bv.style.width=bv.style.padding="1px";bv.style.border=0;bv.style.overflow="hidden";bv.style.display="inline";bv.style.zoom=1;bI.inlineBlockNeedsLayout=(bv.offsetWidth===3);bv.style.display="block";bv.style.overflow="visible";bv.innerHTML="
";bI.shrinkWrapBlocks=(bv.offsetWidth!==3)}bv.style.cssText=bN+e;bv.innerHTML=bQ;bV=bv.firstChild;bW=bV.firstChild;bO=bV.nextSibling.firstChild.firstChild;bP={doesNotAddBorder:(bW.offsetTop!==5),doesAddBorderForTableAndCells:(bO.offsetTop===5)};bW.style.position="fixed";bW.style.top="20px";bP.fixedPosition=(bW.offsetTop===20||bW.offsetTop===15);bW.style.position=bW.style.top="";bV.style.overflow="hidden";bV.style.position="relative";bP.subtractsBorderForOverflowNotVisible=(bW.offsetTop===-5);bP.doesNotIncludeMarginInBodyOffset=(bS.offsetTop!==bL);if(bd.getComputedStyle){bv.style.marginTop="1%";bI.pixelMargin=(bd.getComputedStyle(bv,null)||{marginTop:0}).marginTop!=="1%"}if(typeof bM.style.zoom!=="undefined"){bM.style.zoom=1}bS.removeChild(bM);bR=bv=bM=null;b.extend(bI,bP)});return bI})();var aT=/^(?:\{.*\}|\[.*\])$/,aA=/([A-Z])/g;b.extend({cache:{},uuid:0,expando:"jQuery"+(b.fn.jquery+Math.random()).replace(/\D/g,""),noData:{embed:true,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",applet:true},hasData:function(e){e=e.nodeType?b.cache[e[b.expando]]:e[b.expando];return !!e&&!S(e)},data:function(bx,bv,bz,by){if(!b.acceptData(bx)){return}var bG,bA,bD,bE=b.expando,bC=typeof bv==="string",bF=bx.nodeType,e=bF?b.cache:bx,bw=bF?bx[bE]:bx[bE]&&bE,bB=bv==="events";if((!bw||!e[bw]||(!bB&&!by&&!e[bw].data))&&bC&&bz===L){return}if(!bw){if(bF){bx[bE]=bw=++b.uuid}else{bw=bE}}if(!e[bw]){e[bw]={};if(!bF){e[bw].toJSON=b.noop}}if(typeof bv==="object"||typeof bv==="function"){if(by){e[bw]=b.extend(e[bw],bv)}else{e[bw].data=b.extend(e[bw].data,bv)}}bG=bA=e[bw];if(!by){if(!bA.data){bA.data={}}bA=bA.data}if(bz!==L){bA[b.camelCase(bv)]=bz}if(bB&&!bA[bv]){return bG.events}if(bC){bD=bA[bv];if(bD==null){bD=bA[b.camelCase(bv)]}}else{bD=bA}return bD},removeData:function(bx,bv,by){if(!b.acceptData(bx)){return}var bB,bA,bz,bC=b.expando,bD=bx.nodeType,e=bD?b.cache:bx,bw=bD?bx[bC]:bC;if(!e[bw]){return}if(bv){bB=by?e[bw]:e[bw].data;if(bB){if(!b.isArray(bv)){if(bv in bB){bv=[bv]}else{bv=b.camelCase(bv);if(bv in bB){bv=[bv]}else{bv=bv.split(" ")}}}for(bA=0,bz=bv.length;bA1,null,false)},removeData:function(e){return this.each(function(){b.removeData(this,e)})}});function a6(bx,bw,by){if(by===L&&bx.nodeType===1){var bv="data-"+bw.replace(aA,"-$1").toLowerCase();by=bx.getAttribute(bv);if(typeof by==="string"){try{by=by==="true"?true:by==="false"?false:by==="null"?null:b.isNumeric(by)?+by:aT.test(by)?b.parseJSON(by):by}catch(bz){}b.data(bx,bw,by)}else{by=L}}return by}function S(bv){for(var e in bv){if(e==="data"&&b.isEmptyObject(bv[e])){continue}if(e!=="toJSON"){return false}}return true}function bj(by,bx,bA){var bw=bx+"defer",bv=bx+"queue",e=bx+"mark",bz=b._data(by,bw);if(bz&&(bA==="queue"||!b._data(by,bv))&&(bA==="mark"||!b._data(by,e))){setTimeout(function(){if(!b._data(by,bv)&&!b._data(by,e)){b.removeData(by,bw,true);bz.fire()}},0)}}b.extend({_mark:function(bv,e){if(bv){e=(e||"fx")+"mark";b._data(bv,e,(b._data(bv,e)||0)+1)}},_unmark:function(by,bx,bv){if(by!==true){bv=bx;bx=by;by=false}if(bx){bv=bv||"fx";var e=bv+"mark",bw=by?0:((b._data(bx,e)||1)-1);if(bw){b._data(bx,e,bw)}else{b.removeData(bx,e,true);bj(bx,bv,"mark")}}},queue:function(bv,e,bx){var bw;if(bv){e=(e||"fx")+"queue";bw=b._data(bv,e);if(bx){if(!bw||b.isArray(bx)){bw=b._data(bv,e,b.makeArray(bx))}else{bw.push(bx)}}return bw||[]}},dequeue:function(by,bx){bx=bx||"fx";var bv=b.queue(by,bx),bw=bv.shift(),e={};if(bw==="inprogress"){bw=bv.shift()}if(bw){if(bx==="fx"){bv.unshift("inprogress")}b._data(by,bx+".run",e);bw.call(by,function(){b.dequeue(by,bx)},e)}if(!bv.length){b.removeData(by,bx+"queue "+bx+".run",true);bj(by,bx,"queue")}}});b.fn.extend({queue:function(e,bv){var bw=2;if(typeof e!=="string"){bv=e;e="fx";bw--}if(arguments.length1)},removeAttr:function(e){return this.each(function(){b.removeAttr(this,e)})},prop:function(e,bv){return b.access(this,b.prop,e,bv,arguments.length>1)},removeProp:function(e){e=b.propFix[e]||e;return this.each(function(){try{this[e]=L;delete this[e]}catch(bv){}})},addClass:function(by){var bA,bw,bv,bx,bz,bB,e;if(b.isFunction(by)){return this.each(function(bC){b(this).addClass(by.call(this,bC,this.className))})}if(by&&typeof by==="string"){bA=by.split(ag);for(bw=0,bv=this.length;bw-1){return true}}return false},val:function(bx){var e,bv,by,bw=this[0];if(!arguments.length){if(bw){e=b.valHooks[bw.type]||b.valHooks[bw.nodeName.toLowerCase()];if(e&&"get" in e&&(bv=e.get(bw,"value"))!==L){return bv}bv=bw.value;return typeof bv==="string"?bv.replace(aV,""):bv==null?"":bv}return}by=b.isFunction(bx);return this.each(function(bA){var bz=b(this),bB;if(this.nodeType!==1){return}if(by){bB=bx.call(this,bA,bz.val())}else{bB=bx}if(bB==null){bB=""}else{if(typeof bB==="number"){bB+=""}else{if(b.isArray(bB)){bB=b.map(bB,function(bC){return bC==null?"":bC+""})}}}e=b.valHooks[this.type]||b.valHooks[this.nodeName.toLowerCase()];if(!e||!("set" in e)||e.set(this,bB,"value")===L){this.value=bB}})}});b.extend({valHooks:{option:{get:function(e){var bv=e.attributes.value;return !bv||bv.specified?e.value:e.text}},select:{get:function(e){var bA,bv,bz,bx,by=e.selectedIndex,bB=[],bC=e.options,bw=e.type==="select-one";if(by<0){return null}bv=bw?by:0;bz=bw?by+1:bC.length;for(;bv=0});if(!e.length){bv.selectedIndex=-1}return e}}},attrFn:{val:true,css:true,html:true,text:true,data:true,width:true,height:true,offset:true},attr:function(bA,bx,bB,bz){var bw,e,by,bv=bA.nodeType;if(!bA||bv===3||bv===8||bv===2){return}if(bz&&bx in b.attrFn){return b(bA)[bx](bB)}if(typeof bA.getAttribute==="undefined"){return b.prop(bA,bx,bB)}by=bv!==1||!b.isXMLDoc(bA);if(by){bx=bx.toLowerCase();e=b.attrHooks[bx]||(ao.test(bx)?aZ:bf)}if(bB!==L){if(bB===null){b.removeAttr(bA,bx);return}else{if(e&&"set" in e&&by&&(bw=e.set(bA,bB,bx))!==L){return bw}else{bA.setAttribute(bx,""+bB);return bB}}}else{if(e&&"get" in e&&by&&(bw=e.get(bA,bx))!==null){return bw}else{bw=bA.getAttribute(bx);return bw===null?L:bw}}},removeAttr:function(by,bA){var bz,bB,bw,e,bv,bx=0;if(bA&&by.nodeType===1){bB=bA.toLowerCase().split(ag);e=bB.length;for(;bx=0)}}})});var be=/^(?:textarea|input|select)$/i,n=/^([^\.]*)?(?:\.(.+))?$/,J=/(?:^|\s)hover(\.\S+)?\b/,aP=/^key/,bg=/^(?:mouse|contextmenu)|click/,T=/^(?:focusinfocus|focusoutblur)$/,U=/^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/,Y=function(e){var bv=U.exec(e);if(bv){bv[1]=(bv[1]||"").toLowerCase();bv[3]=bv[3]&&new RegExp("(?:^|\\s)"+bv[3]+"(?:\\s|$)")}return bv},j=function(bw,e){var bv=bw.attributes||{};return((!e[1]||bw.nodeName.toLowerCase()===e[1])&&(!e[2]||(bv.id||{}).value===e[2])&&(!e[3]||e[3].test((bv["class"]||{}).value)))},bt=function(e){return b.event.special.hover?e:e.replace(J,"mouseenter$1 mouseleave$1")};b.event={add:function(bx,bC,bJ,bA,by){var bD,bB,bK,bI,bH,bF,e,bG,bv,bz,bw,bE;if(bx.nodeType===3||bx.nodeType===8||!bC||!bJ||!(bD=b._data(bx))){return}if(bJ.handler){bv=bJ;bJ=bv.handler;by=bv.selector}if(!bJ.guid){bJ.guid=b.guid++}bK=bD.events;if(!bK){bD.events=bK={}}bB=bD.handle;if(!bB){bD.handle=bB=function(bL){return typeof b!=="undefined"&&(!bL||b.event.triggered!==bL.type)?b.event.dispatch.apply(bB.elem,arguments):L};bB.elem=bx}bC=b.trim(bt(bC)).split(" ");for(bI=0;bI=0){bG=bG.slice(0,-1);bw=true}if(bG.indexOf(".")>=0){bx=bG.split(".");bG=bx.shift();bx.sort()}if((!bA||b.event.customEvent[bG])&&!b.event.global[bG]){return}bv=typeof bv==="object"?bv[b.expando]?bv:new b.Event(bG,bv):new b.Event(bG);bv.type=bG;bv.isTrigger=true;bv.exclusive=bw;bv.namespace=bx.join(".");bv.namespace_re=bv.namespace?new RegExp("(^|\\.)"+bx.join("\\.(?:.*\\.)?")+"(\\.|$)"):null;by=bG.indexOf(":")<0?"on"+bG:"";if(!bA){e=b.cache;for(bC in e){if(e[bC].events&&e[bC].events[bG]){b.event.trigger(bv,bD,e[bC].handle.elem,true)}}return}bv.result=L;if(!bv.target){bv.target=bA}bD=bD!=null?b.makeArray(bD):[];bD.unshift(bv);bF=b.event.special[bG]||{};if(bF.trigger&&bF.trigger.apply(bA,bD)===false){return}bB=[[bA,bF.bindType||bG]];if(!bJ&&!bF.noBubble&&!b.isWindow(bA)){bI=bF.delegateType||bG;bH=T.test(bI+bG)?bA:bA.parentNode;bz=null;for(;bH;bH=bH.parentNode){bB.push([bH,bI]);bz=bH}if(bz&&bz===bA.ownerDocument){bB.push([bz.defaultView||bz.parentWindow||bd,bI])}}for(bC=0;bCbC){bv.push({elem:this,matches:bD.slice(bC)})}for(bJ=0;bJ0?this.on(e,null,bx,bw):this.trigger(e)};if(b.attrFn){b.attrFn[e]=true}if(aP.test(e)){b.event.fixHooks[e]=b.event.keyHooks}if(bg.test(e)){b.event.fixHooks[e]=b.event.mouseHooks}}); +/* + * Sizzle CSS Selector Engine + * Copyright 2011, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){var bH=/((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,bC="sizcache"+(Math.random()+"").replace(".",""),bI=0,bL=Object.prototype.toString,bB=false,bA=true,bK=/\\/g,bO=/\r\n/g,bQ=/\W/;[0,0].sort(function(){bA=false;return 0});var by=function(bV,e,bY,bZ){bY=bY||[];e=e||av;var b1=e;if(e.nodeType!==1&&e.nodeType!==9){return[]}if(!bV||typeof bV!=="string"){return bY}var bS,b3,b6,bR,b2,b5,b4,bX,bU=true,bT=by.isXML(e),bW=[],b0=bV;do{bH.exec("");bS=bH.exec(b0);if(bS){b0=bS[3];bW.push(bS[1]);if(bS[2]){bR=bS[3];break}}}while(bS);if(bW.length>1&&bD.exec(bV)){if(bW.length===2&&bE.relative[bW[0]]){b3=bM(bW[0]+bW[1],e,bZ)}else{b3=bE.relative[bW[0]]?[e]:by(bW.shift(),e);while(bW.length){bV=bW.shift();if(bE.relative[bV]){bV+=bW.shift()}b3=bM(bV,b3,bZ)}}}else{if(!bZ&&bW.length>1&&e.nodeType===9&&!bT&&bE.match.ID.test(bW[0])&&!bE.match.ID.test(bW[bW.length-1])){b2=by.find(bW.shift(),e,bT);e=b2.expr?by.filter(b2.expr,b2.set)[0]:b2.set[0]}if(e){b2=bZ?{expr:bW.pop(),set:bF(bZ)}:by.find(bW.pop(),bW.length===1&&(bW[0]==="~"||bW[0]==="+")&&e.parentNode?e.parentNode:e,bT);b3=b2.expr?by.filter(b2.expr,b2.set):b2.set;if(bW.length>0){b6=bF(b3)}else{bU=false}while(bW.length){b5=bW.pop();b4=b5;if(!bE.relative[b5]){b5=""}else{b4=bW.pop()}if(b4==null){b4=e}bE.relative[b5](b6,b4,bT)}}else{b6=bW=[]}}if(!b6){b6=b3}if(!b6){by.error(b5||bV)}if(bL.call(b6)==="[object Array]"){if(!bU){bY.push.apply(bY,b6)}else{if(e&&e.nodeType===1){for(bX=0;b6[bX]!=null;bX++){if(b6[bX]&&(b6[bX]===true||b6[bX].nodeType===1&&by.contains(e,b6[bX]))){bY.push(b3[bX])}}}else{for(bX=0;b6[bX]!=null;bX++){if(b6[bX]&&b6[bX].nodeType===1){bY.push(b3[bX])}}}}}else{bF(b6,bY)}if(bR){by(bR,b1,bY,bZ);by.uniqueSort(bY)}return bY};by.uniqueSort=function(bR){if(bJ){bB=bA;bR.sort(bJ);if(bB){for(var e=1;e0};by.find=function(bX,e,bY){var bW,bS,bU,bT,bV,bR;if(!bX){return[]}for(bS=0,bU=bE.order.length;bS":function(bW,bR){var bV,bU=typeof bR==="string",bS=0,e=bW.length;if(bU&&!bQ.test(bR)){bR=bR.toLowerCase();for(;bS=0)){if(!bS){e.push(bV)}}else{if(bS){bR[bU]=false}}}}return false},ID:function(e){return e[1].replace(bK,"")},TAG:function(bR,e){return bR[1].replace(bK,"").toLowerCase()},CHILD:function(e){if(e[1]==="nth"){if(!e[2]){by.error(e[0])}e[2]=e[2].replace(/^\+|\s*/g,"");var bR=/(-?)(\d*)(?:n([+\-]?\d*))?/.exec(e[2]==="even"&&"2n"||e[2]==="odd"&&"2n+1"||!/\D/.test(e[2])&&"0n+"+e[2]||e[2]);e[2]=(bR[1]+(bR[2]||1))-0;e[3]=bR[3]-0}else{if(e[2]){by.error(e[0])}}e[0]=bI++;return e},ATTR:function(bU,bR,bS,e,bV,bW){var bT=bU[1]=bU[1].replace(bK,"");if(!bW&&bE.attrMap[bT]){bU[1]=bE.attrMap[bT]}bU[4]=(bU[4]||bU[5]||"").replace(bK,"");if(bU[2]==="~="){bU[4]=" "+bU[4]+" "}return bU},PSEUDO:function(bU,bR,bS,e,bV){if(bU[1]==="not"){if((bH.exec(bU[3])||"").length>1||/^\w/.test(bU[3])){bU[3]=by(bU[3],null,null,bR)}else{var bT=by.filter(bU[3],bR,bS,true^bV);if(!bS){e.push.apply(e,bT)}return false}}else{if(bE.match.POS.test(bU[0])||bE.match.CHILD.test(bU[0])){return true}}return bU},POS:function(e){e.unshift(true);return e}},filters:{enabled:function(e){return e.disabled===false&&e.type!=="hidden"},disabled:function(e){return e.disabled===true},checked:function(e){return e.checked===true},selected:function(e){if(e.parentNode){e.parentNode.selectedIndex}return e.selected===true},parent:function(e){return !!e.firstChild},empty:function(e){return !e.firstChild},has:function(bS,bR,e){return !!by(e[3],bS).length},header:function(e){return(/h\d/i).test(e.nodeName)},text:function(bS){var e=bS.getAttribute("type"),bR=bS.type;return bS.nodeName.toLowerCase()==="input"&&"text"===bR&&(e===bR||e===null)},radio:function(e){return e.nodeName.toLowerCase()==="input"&&"radio"===e.type},checkbox:function(e){return e.nodeName.toLowerCase()==="input"&&"checkbox"===e.type},file:function(e){return e.nodeName.toLowerCase()==="input"&&"file"===e.type},password:function(e){return e.nodeName.toLowerCase()==="input"&&"password"===e.type},submit:function(bR){var e=bR.nodeName.toLowerCase();return(e==="input"||e==="button")&&"submit"===bR.type},image:function(e){return e.nodeName.toLowerCase()==="input"&&"image"===e.type},reset:function(bR){var e=bR.nodeName.toLowerCase();return(e==="input"||e==="button")&&"reset"===bR.type},button:function(bR){var e=bR.nodeName.toLowerCase();return e==="input"&&"button"===bR.type||e==="button"},input:function(e){return(/input|select|textarea|button/i).test(e.nodeName)},focus:function(e){return e===e.ownerDocument.activeElement}},setFilters:{first:function(bR,e){return e===0},last:function(bS,bR,e,bT){return bR===bT.length-1},even:function(bR,e){return e%2===0},odd:function(bR,e){return e%2===1},lt:function(bS,bR,e){return bRe[3]-0},nth:function(bS,bR,e){return e[3]-0===bR},eq:function(bS,bR,e){return e[3]-0===bR}},filter:{PSEUDO:function(bS,bX,bW,bY){var e=bX[1],bR=bE.filters[e];if(bR){return bR(bS,bW,bX,bY)}else{if(e==="contains"){return(bS.textContent||bS.innerText||bw([bS])||"").indexOf(bX[3])>=0}else{if(e==="not"){var bT=bX[3];for(var bV=0,bU=bT.length;bV=0)}}},ID:function(bR,e){return bR.nodeType===1&&bR.getAttribute("id")===e},TAG:function(bR,e){return(e==="*"&&bR.nodeType===1)||!!bR.nodeName&&bR.nodeName.toLowerCase()===e},CLASS:function(bR,e){return(" "+(bR.className||bR.getAttribute("class"))+" ").indexOf(e)>-1},ATTR:function(bV,bT){var bS=bT[1],e=by.attr?by.attr(bV,bS):bE.attrHandle[bS]?bE.attrHandle[bS](bV):bV[bS]!=null?bV[bS]:bV.getAttribute(bS),bW=e+"",bU=bT[2],bR=bT[4];return e==null?bU==="!=":!bU&&by.attr?e!=null:bU==="="?bW===bR:bU==="*="?bW.indexOf(bR)>=0:bU==="~="?(" "+bW+" ").indexOf(bR)>=0:!bR?bW&&e!==false:bU==="!="?bW!==bR:bU==="^="?bW.indexOf(bR)===0:bU==="$="?bW.substr(bW.length-bR.length)===bR:bU==="|="?bW===bR||bW.substr(0,bR.length+1)===bR+"-":false},POS:function(bU,bR,bS,bV){var e=bR[2],bT=bE.setFilters[e];if(bT){return bT(bU,bS,bR,bV)}}}};var bD=bE.match.POS,bx=function(bR,e){return"\\"+(e-0+1)};for(var bz in bE.match){bE.match[bz]=new RegExp(bE.match[bz].source+(/(?![^\[]*\])(?![^\(]*\))/.source));bE.leftMatch[bz]=new RegExp(/(^(?:.|\r|\n)*?)/.source+bE.match[bz].source.replace(/\\(\d+)/g,bx))}bE.match.globalPOS=bD;var bF=function(bR,e){bR=Array.prototype.slice.call(bR,0);if(e){e.push.apply(e,bR);return e}return bR};try{Array.prototype.slice.call(av.documentElement.childNodes,0)[0].nodeType}catch(bP){bF=function(bU,bT){var bS=0,bR=bT||[];if(bL.call(bU)==="[object Array]"){Array.prototype.push.apply(bR,bU)}else{if(typeof bU.length==="number"){for(var e=bU.length;bS";e.insertBefore(bR,e.firstChild);if(av.getElementById(bS)){bE.find.ID=function(bU,bV,bW){if(typeof bV.getElementById!=="undefined"&&!bW){var bT=bV.getElementById(bU[1]);return bT?bT.id===bU[1]||typeof bT.getAttributeNode!=="undefined"&&bT.getAttributeNode("id").nodeValue===bU[1]?[bT]:L:[]}};bE.filter.ID=function(bV,bT){var bU=typeof bV.getAttributeNode!=="undefined"&&bV.getAttributeNode("id");return bV.nodeType===1&&bU&&bU.nodeValue===bT}}e.removeChild(bR);e=bR=null})();(function(){var e=av.createElement("div");e.appendChild(av.createComment(""));if(e.getElementsByTagName("*").length>0){bE.find.TAG=function(bR,bV){var bU=bV.getElementsByTagName(bR[1]);if(bR[1]==="*"){var bT=[];for(var bS=0;bU[bS];bS++){if(bU[bS].nodeType===1){bT.push(bU[bS])}}bU=bT}return bU}}e.innerHTML="";if(e.firstChild&&typeof e.firstChild.getAttribute!=="undefined"&&e.firstChild.getAttribute("href")!=="#"){bE.attrHandle.href=function(bR){return bR.getAttribute("href",2)}}e=null})();if(av.querySelectorAll){(function(){var e=by,bT=av.createElement("div"),bS="__sizzle__";bT.innerHTML="

";if(bT.querySelectorAll&&bT.querySelectorAll(".TEST").length===0){return}by=function(b4,bV,bZ,b3){bV=bV||av;if(!b3&&!by.isXML(bV)){var b2=/^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec(b4);if(b2&&(bV.nodeType===1||bV.nodeType===9)){if(b2[1]){return bF(bV.getElementsByTagName(b4),bZ)}else{if(b2[2]&&bE.find.CLASS&&bV.getElementsByClassName){return bF(bV.getElementsByClassName(b2[2]),bZ)}}}if(bV.nodeType===9){if(b4==="body"&&bV.body){return bF([bV.body],bZ)}else{if(b2&&b2[3]){var bY=bV.getElementById(b2[3]);if(bY&&bY.parentNode){if(bY.id===b2[3]){return bF([bY],bZ)}}else{return bF([],bZ)}}}try{return bF(bV.querySelectorAll(b4),bZ)}catch(b0){}}else{if(bV.nodeType===1&&bV.nodeName.toLowerCase()!=="object"){var bW=bV,bX=bV.getAttribute("id"),bU=bX||bS,b6=bV.parentNode,b5=/^\s*[+~]/.test(b4);if(!bX){bV.setAttribute("id",bU)}else{bU=bU.replace(/'/g,"\\$&")}if(b5&&b6){bV=bV.parentNode}try{if(!b5||b6){return bF(bV.querySelectorAll("[id='"+bU+"'] "+b4),bZ)}}catch(b1){}finally{if(!bX){bW.removeAttribute("id")}}}}}return e(b4,bV,bZ,b3)};for(var bR in e){by[bR]=e[bR]}bT=null})()}(function(){var e=av.documentElement,bS=e.matchesSelector||e.mozMatchesSelector||e.webkitMatchesSelector||e.msMatchesSelector;if(bS){var bU=!bS.call(av.createElement("div"),"div"),bR=false;try{bS.call(av.documentElement,"[test!='']:sizzle")}catch(bT){bR=true}by.matchesSelector=function(bW,bY){bY=bY.replace(/\=\s*([^'"\]]*)\s*\]/g,"='$1']");if(!by.isXML(bW)){try{if(bR||!bE.match.PSEUDO.test(bY)&&!/!=/.test(bY)){var bV=bS.call(bW,bY);if(bV||!bU||bW.document&&bW.document.nodeType!==11){return bV}}}catch(bX){}}return by(bY,null,null,[bW]).length>0}}})();(function(){var e=av.createElement("div");e.innerHTML="
";if(!e.getElementsByClassName||e.getElementsByClassName("e").length===0){return}e.lastChild.className="e";if(e.getElementsByClassName("e").length===1){return}bE.order.splice(1,0,"CLASS");bE.find.CLASS=function(bR,bS,bT){if(typeof bS.getElementsByClassName!=="undefined"&&!bT){return bS.getElementsByClassName(bR[1])}};e=null})();function bv(bR,bW,bV,bZ,bX,bY){for(var bT=0,bS=bZ.length;bT0){bU=e;break}}}e=e[bR]}bZ[bT]=bU}}}if(av.documentElement.contains){by.contains=function(bR,e){return bR!==e&&(bR.contains?bR.contains(e):true)}}else{if(av.documentElement.compareDocumentPosition){by.contains=function(bR,e){return !!(bR.compareDocumentPosition(e)&16)}}else{by.contains=function(){return false}}}by.isXML=function(e){var bR=(e?e.ownerDocument||e:0).documentElement;return bR?bR.nodeName!=="HTML":false};var bM=function(bS,e,bW){var bV,bX=[],bU="",bY=e.nodeType?[e]:e;while((bV=bE.match.PSEUDO.exec(bS))){bU+=bV[0];bS=bS.replace(bE.match.PSEUDO,"")}bS=bE.relative[bS]?bS+"*":bS;for(var bT=0,bR=bY.length;bT0){for(bB=bA;bB=0:b.filter(e,this).length>0:this.filter(e).length>0)},closest:function(by,bx){var bv=[],bw,e,bz=this[0];if(b.isArray(by)){var bB=1;while(bz&&bz.ownerDocument&&bz!==bx){for(bw=0;bw-1:b.find.matchesSelector(bz,by)){bv.push(bz);break}else{bz=bz.parentNode;if(!bz||!bz.ownerDocument||bz===bx||bz.nodeType===11){break}}}}bv=bv.length>1?b.unique(bv):bv;return this.pushStack(bv,"closest",by)},index:function(e){if(!e){return(this[0]&&this[0].parentNode)?this.prevAll().length:-1}if(typeof e==="string"){return b.inArray(this[0],b(e))}return b.inArray(e.jquery?e[0]:e,this)},add:function(e,bv){var bx=typeof e==="string"?b(e,bv):b.makeArray(e&&e.nodeType?[e]:e),bw=b.merge(this.get(),bx);return this.pushStack(B(bx[0])||B(bw[0])?bw:b.unique(bw))},andSelf:function(){return this.add(this.prevObject)}});function B(e){return !e||!e.parentNode||e.parentNode.nodeType===11}b.each({parent:function(bv){var e=bv.parentNode;return e&&e.nodeType!==11?e:null},parents:function(e){return b.dir(e,"parentNode")},parentsUntil:function(bv,e,bw){return b.dir(bv,"parentNode",bw)},next:function(e){return b.nth(e,2,"nextSibling")},prev:function(e){return b.nth(e,2,"previousSibling")},nextAll:function(e){return b.dir(e,"nextSibling")},prevAll:function(e){return b.dir(e,"previousSibling")},nextUntil:function(bv,e,bw){return b.dir(bv,"nextSibling",bw)},prevUntil:function(bv,e,bw){return b.dir(bv,"previousSibling",bw)},siblings:function(e){return b.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return b.sibling(e.firstChild)},contents:function(e){return b.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:b.makeArray(e.childNodes)}},function(e,bv){b.fn[e]=function(by,bw){var bx=b.map(this,bv,by);if(!ab.test(e)){bw=by}if(bw&&typeof bw==="string"){bx=b.filter(bw,bx)}bx=this.length>1&&!ay[e]?b.unique(bx):bx;if((this.length>1||bb.test(bw))&&aq.test(e)){bx=bx.reverse()}return this.pushStack(bx,e,P.call(arguments).join(","))}});b.extend({filter:function(bw,e,bv){if(bv){bw=":not("+bw+")"}return e.length===1?b.find.matchesSelector(e[0],bw)?[e[0]]:[]:b.find.matches(bw,e)},dir:function(bw,bv,by){var e=[],bx=bw[bv];while(bx&&bx.nodeType!==9&&(by===L||bx.nodeType!==1||!b(bx).is(by))){if(bx.nodeType===1){e.push(bx)}bx=bx[bv]}return e},nth:function(by,e,bw,bx){e=e||1;var bv=0;for(;by;by=by[bw]){if(by.nodeType===1&&++bv===e){break}}return by},sibling:function(bw,bv){var e=[];for(;bw;bw=bw.nextSibling){if(bw.nodeType===1&&bw!==bv){e.push(bw)}}return e}});function aH(bx,bw,e){bw=bw||0;if(b.isFunction(bw)){return b.grep(bx,function(bz,by){var bA=!!bw.call(bz,by,bz);return bA===e})}else{if(bw.nodeType){return b.grep(bx,function(bz,by){return(bz===bw)===e})}else{if(typeof bw==="string"){var bv=b.grep(bx,function(by){return by.nodeType===1});if(bp.test(bw)){return b.filter(bw,bv,!e)}else{bw=b.filter(bw,bv)}}}}return b.grep(bx,function(bz,by){return(b.inArray(bz,bw)>=0)===e})}function a(e){var bw=aS.split("|"),bv=e.createDocumentFragment();if(bv.createElement){while(bw.length){bv.createElement(bw.pop())}}return bv}var aS="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",ah=/ jQuery\d+="(?:\d+|null)"/g,ar=/^\s+/,R=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig,d=/<([\w:]+)/,v=/]","i"),o=/checked\s*(?:[^=]|=\s*.checked.)/i,bn=/\/(java|ecma)script/i,aO=/^\s*",""],legend:[1,"
","
"],thead:[1,"","
"],tr:[2,"","
"],td:[3,"","
"],col:[2,"","
"],area:[1,"",""],_default:[0,"",""]},ac=a(av);ax.optgroup=ax.option;ax.tbody=ax.tfoot=ax.colgroup=ax.caption=ax.thead;ax.th=ax.td;if(!b.support.htmlSerialize){ax._default=[1,"div
","
"]}b.fn.extend({text:function(e){return b.access(this,function(bv){return bv===L?b.text(this):this.empty().append((this[0]&&this[0].ownerDocument||av).createTextNode(bv))},null,e,arguments.length)},wrapAll:function(e){if(b.isFunction(e)){return this.each(function(bw){b(this).wrapAll(e.call(this,bw))})}if(this[0]){var bv=b(e,this[0].ownerDocument).eq(0).clone(true);if(this[0].parentNode){bv.insertBefore(this[0])}bv.map(function(){var bw=this;while(bw.firstChild&&bw.firstChild.nodeType===1){bw=bw.firstChild}return bw}).append(this)}return this},wrapInner:function(e){if(b.isFunction(e)){return this.each(function(bv){b(this).wrapInner(e.call(this,bv))})}return this.each(function(){var bv=b(this),bw=bv.contents();if(bw.length){bw.wrapAll(e)}else{bv.append(e)}})},wrap:function(e){var bv=b.isFunction(e);return this.each(function(bw){b(this).wrapAll(bv?e.call(this,bw):e)})},unwrap:function(){return this.parent().each(function(){if(!b.nodeName(this,"body")){b(this).replaceWith(this.childNodes)}}).end()},append:function(){return this.domManip(arguments,true,function(e){if(this.nodeType===1){this.appendChild(e)}})},prepend:function(){return this.domManip(arguments,true,function(e){if(this.nodeType===1){this.insertBefore(e,this.firstChild)}})},before:function(){if(this[0]&&this[0].parentNode){return this.domManip(arguments,false,function(bv){this.parentNode.insertBefore(bv,this)})}else{if(arguments.length){var e=b.clean(arguments);e.push.apply(e,this.toArray());return this.pushStack(e,"before",arguments)}}},after:function(){if(this[0]&&this[0].parentNode){return this.domManip(arguments,false,function(bv){this.parentNode.insertBefore(bv,this.nextSibling)})}else{if(arguments.length){var e=this.pushStack(this,"after",arguments);e.push.apply(e,b.clean(arguments));return e}}},remove:function(e,bx){for(var bv=0,bw;(bw=this[bv])!=null;bv++){if(!e||b.filter(e,[bw]).length){if(!bx&&bw.nodeType===1){b.cleanData(bw.getElementsByTagName("*"));b.cleanData([bw])}if(bw.parentNode){bw.parentNode.removeChild(bw)}}}return this},empty:function(){for(var e=0,bv;(bv=this[e])!=null;e++){if(bv.nodeType===1){b.cleanData(bv.getElementsByTagName("*"))}while(bv.firstChild){bv.removeChild(bv.firstChild)}}return this},clone:function(bv,e){bv=bv==null?false:bv;e=e==null?bv:e;return this.map(function(){return b.clone(this,bv,e)})},html:function(e){return b.access(this,function(by){var bx=this[0]||{},bw=0,bv=this.length;if(by===L){return bx.nodeType===1?bx.innerHTML.replace(ah,""):null}if(typeof by==="string"&&!ae.test(by)&&(b.support.leadingWhitespace||!ar.test(by))&&!ax[(d.exec(by)||["",""])[1].toLowerCase()]){by=by.replace(R,"<$1>");try{for(;bw1&&bw0?this.clone(true):this).get();b(bC[bA])[bv](by);bz=bz.concat(by)}return this.pushStack(bz,e,bC.selector)}}});function bh(e){if(typeof e.getElementsByTagName!=="undefined"){return e.getElementsByTagName("*")}else{if(typeof e.querySelectorAll!=="undefined"){return e.querySelectorAll("*")}else{return[]}}}function az(e){if(e.type==="checkbox"||e.type==="radio"){e.defaultChecked=e.checked}}function D(e){var bv=(e.nodeName||"").toLowerCase();if(bv==="input"){az(e)}else{if(bv!=="script"&&typeof e.getElementsByTagName!=="undefined"){b.grep(e.getElementsByTagName("input"),az)}}}function am(e){var bv=av.createElement("div");ac.appendChild(bv);bv.innerHTML=e.outerHTML;return bv.firstChild}b.extend({clone:function(by,bA,bw){var e,bv,bx,bz=b.support.html5Clone||b.isXMLDoc(by)||!ai.test("<"+by.nodeName+">")?by.cloneNode(true):am(by);if((!b.support.noCloneEvent||!b.support.noCloneChecked)&&(by.nodeType===1||by.nodeType===11)&&!b.isXMLDoc(by)){aj(by,bz);e=bh(by);bv=bh(bz);for(bx=0;e[bx];++bx){if(bv[bx]){aj(e[bx],bv[bx])}}}if(bA){s(by,bz);if(bw){e=bh(by);bv=bh(bz);for(bx=0;e[bx];++bx){s(e[bx],bv[bx])}}}e=bv=null;return bz},clean:function(bI,bw,bv,bx){var bA,bH,bD,bJ=[];bw=bw||av;if(typeof bw.createElement==="undefined"){bw=bw.ownerDocument||bw[0]&&bw[0].ownerDocument||av}for(var bE=0,bG;(bG=bI[bE])!=null;bE++){if(typeof bG==="number"){bG+=""}if(!bG){continue}if(typeof bG==="string"){if(!W.test(bG)){bG=bw.createTextNode(bG)}else{bG=bG.replace(R,"<$1>");var bN=(d.exec(bG)||["",""])[1].toLowerCase(),bz=ax[bN]||ax._default,bK=bz[0],bB=bw.createElement("div"),bL=ac.childNodes,bM;if(bw===av){ac.appendChild(bB)}else{a(bw).appendChild(bB)}bB.innerHTML=bz[1]+bG+bz[2];while(bK--){bB=bB.lastChild}if(!b.support.tbody){var by=v.test(bG),e=bN==="table"&&!by?bB.firstChild&&bB.firstChild.childNodes:bz[1]===""&&!by?bB.childNodes:[];for(bD=e.length-1;bD>=0;--bD){if(b.nodeName(e[bD],"tbody")&&!e[bD].childNodes.length){e[bD].parentNode.removeChild(e[bD])}}}if(!b.support.leadingWhitespace&&ar.test(bG)){bB.insertBefore(bw.createTextNode(ar.exec(bG)[0]),bB.firstChild)}bG=bB.childNodes;if(bB){bB.parentNode.removeChild(bB);if(bL.length>0){bM=bL[bL.length-1];if(bM&&bM.parentNode){bM.parentNode.removeChild(bM)}}}}}var bF;if(!b.support.appendChecked){if(bG[0]&&typeof(bF=bG.length)==="number"){for(bD=0;bD1)};b.extend({cssHooks:{opacity:{get:function(bw,bv){if(bv){var e=Z(bw,"opacity");return e===""?"1":e}else{return bw.style.opacity}}}},cssNumber:{fillOpacity:true,fontWeight:true,lineHeight:true,opacity:true,orphans:true,widows:true,zIndex:true,zoom:true},cssProps:{"float":b.support.cssFloat?"cssFloat":"styleFloat"},style:function(bx,bw,bD,by){if(!bx||bx.nodeType===3||bx.nodeType===8||!bx.style){return}var bB,bC,bz=b.camelCase(bw),bv=bx.style,bE=b.cssHooks[bz];bw=b.cssProps[bz]||bz;if(bD!==L){bC=typeof bD;if(bC==="string"&&(bB=I.exec(bD))){bD=(+(bB[1]+1)*+bB[2])+parseFloat(b.css(bx,bw));bC="number"}if(bD==null||bC==="number"&&isNaN(bD)){return}if(bC==="number"&&!b.cssNumber[bz]){bD+="px"}if(!bE||!("set" in bE)||(bD=bE.set(bx,bD))!==L){try{bv[bw]=bD}catch(bA){}}}else{if(bE&&"get" in bE&&(bB=bE.get(bx,false,by))!==L){return bB}return bv[bw]}},css:function(by,bx,bv){var bw,e;bx=b.camelCase(bx);e=b.cssHooks[bx];bx=b.cssProps[bx]||bx;if(bx==="cssFloat"){bx="float"}if(e&&"get" in e&&(bw=e.get(by,true,bv))!==L){return bw}else{if(Z){return Z(by,bx)}}},swap:function(by,bx,bz){var e={},bw,bv;for(bv in bx){e[bv]=by.style[bv];by.style[bv]=bx[bv]}bw=bz.call(by);for(bv in bx){by.style[bv]=e[bv]}return bw}});b.curCSS=b.css;if(av.defaultView&&av.defaultView.getComputedStyle){aJ=function(bA,bw){var bv,bz,e,by,bx=bA.style;bw=bw.replace(y,"-$1").toLowerCase();if((bz=bA.ownerDocument.defaultView)&&(e=bz.getComputedStyle(bA,null))){bv=e.getPropertyValue(bw);if(bv===""&&!b.contains(bA.ownerDocument.documentElement,bA)){bv=b.style(bA,bw)}}if(!b.support.pixelMargin&&e&&aE.test(bw)&&a1.test(bv)){by=bx.width;bx.width=bv;bv=e.width;bx.width=by}return bv}}if(av.documentElement.currentStyle){aY=function(bz,bw){var bA,e,by,bv=bz.currentStyle&&bz.currentStyle[bw],bx=bz.style;if(bv==null&&bx&&(by=bx[bw])){bv=by}if(a1.test(bv)){bA=bx.left;e=bz.runtimeStyle&&bz.runtimeStyle.left;if(e){bz.runtimeStyle.left=bz.currentStyle.left}bx.left=bw==="fontSize"?"1em":bv;bv=bx.pixelLeft+"px";bx.left=bA;if(e){bz.runtimeStyle.left=e}}return bv===""?"auto":bv}}Z=aJ||aY;function af(by,bw,bv){var bz=bw==="width"?by.offsetWidth:by.offsetHeight,bx=bw==="width"?1:0,e=4;if(bz>0){if(bv!=="border"){for(;bx=1&&b.trim(bw.replace(al,""))===""){bx.removeAttribute("filter");if(bv&&!bv.filter){return}}bx.filter=al.test(bw)?bw.replace(al,e):bw+" "+e}}}b(function(){if(!b.support.reliableMarginRight){b.cssHooks.marginRight={get:function(bv,e){return b.swap(bv,{display:"inline-block"},function(){if(e){return Z(bv,"margin-right")}else{return bv.style.marginRight}})}}}});if(b.expr&&b.expr.filters){b.expr.filters.hidden=function(bw){var bv=bw.offsetWidth,e=bw.offsetHeight;return(bv===0&&e===0)||(!b.support.reliableHiddenOffsets&&((bw.style&&bw.style.display)||b.css(bw,"display"))==="none")};b.expr.filters.visible=function(e){return !b.expr.filters.hidden(e)}}b.each({margin:"",padding:"",border:"Width"},function(e,bv){b.cssHooks[e+bv]={expand:function(by){var bx,bz=typeof by==="string"?by.split(" "):[by],bw={};for(bx=0;bx<4;bx++){bw[e+G[bx]+bv]=bz[bx]||bz[bx-2]||bz[0]}return bw}}});var k=/%20/g,ap=/\[\]$/,bs=/\r?\n/g,bq=/#.*$/,aD=/^(.*?):[ \t]*([^\r\n]*)\r?$/mg,a0=/^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,aN=/^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,aR=/^(?:GET|HEAD)$/,c=/^\/\//,M=/\?/,a7=/)<[^<]*)*<\/script>/gi,p=/^(?:select|textarea)/i,h=/\s+/,br=/([?&])_=[^&]*/,K=/^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+))?)?/,z=b.fn.load,aa={},q={},aF,r,aW=["*/"]+["*"];try{aF=bm.href}catch(aw){aF=av.createElement("a");aF.href="";aF=aF.href}r=K.exec(aF.toLowerCase())||[];function f(e){return function(by,bA){if(typeof by!=="string"){bA=by;by="*"}if(b.isFunction(bA)){var bx=by.toLowerCase().split(h),bw=0,bz=bx.length,bv,bB,bC;for(;bw=0){var e=bw.slice(by,bw.length);bw=bw.slice(0,by)}var bx="GET";if(bz){if(b.isFunction(bz)){bA=bz;bz=L}else{if(typeof bz==="object"){bz=b.param(bz,b.ajaxSettings.traditional);bx="POST"}}}var bv=this;b.ajax({url:bw,type:bx,dataType:"html",data:bz,complete:function(bC,bB,bD){bD=bC.responseText;if(bC.isResolved()){bC.done(function(bE){bD=bE});bv.html(e?b("
").append(bD.replace(a7,"")).find(e):bD)}if(bA){bv.each(bA,[bD,bB,bC])}}});return this},serialize:function(){return b.param(this.serializeArray())},serializeArray:function(){return this.map(function(){return this.elements?b.makeArray(this.elements):this}).filter(function(){return this.name&&!this.disabled&&(this.checked||p.test(this.nodeName)||a0.test(this.type))}).map(function(e,bv){var bw=b(this).val();return bw==null?null:b.isArray(bw)?b.map(bw,function(by,bx){return{name:bv.name,value:by.replace(bs,"\r\n")}}):{name:bv.name,value:bw.replace(bs,"\r\n")}}).get()}});b.each("ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split(" "),function(e,bv){b.fn[bv]=function(bw){return this.on(bv,bw)}});b.each(["get","post"],function(e,bv){b[bv]=function(bw,by,bz,bx){if(b.isFunction(by)){bx=bx||bz;bz=by;by=L}return b.ajax({type:bv,url:bw,data:by,success:bz,dataType:bx})}});b.extend({getScript:function(e,bv){return b.get(e,L,bv,"script")},getJSON:function(e,bv,bw){return b.get(e,bv,bw,"json")},ajaxSetup:function(bv,e){if(e){an(bv,b.ajaxSettings)}else{e=bv;bv=b.ajaxSettings}an(bv,e);return bv},ajaxSettings:{url:aF,isLocal:aN.test(r[1]),global:true,type:"GET",contentType:"application/x-www-form-urlencoded; charset=UTF-8",processData:true,async:true,accepts:{xml:"application/xml, text/xml",html:"text/html",text:"text/plain",json:"application/json, text/javascript","*":aW},contents:{xml:/xml/,html:/html/,json:/json/},responseFields:{xml:"responseXML",text:"responseText"},converters:{"* text":bd.String,"text html":true,"text json":b.parseJSON,"text xml":b.parseXML},flatOptions:{context:true,url:true}},ajaxPrefilter:f(aa),ajaxTransport:f(q),ajax:function(bz,bx){if(typeof bz==="object"){bx=bz;bz=L}bx=bx||{};var bD=b.ajaxSetup({},bx),bS=bD.context||bD,bG=bS!==bD&&(bS.nodeType||bS instanceof b)?b(bS):b.event,bR=b.Deferred(),bN=b.Callbacks("once memory"),bB=bD.statusCode||{},bC,bH={},bO={},bQ,by,bL,bE,bI,bA=0,bw,bK,bJ={readyState:0,setRequestHeader:function(bT,bU){if(!bA){var e=bT.toLowerCase();bT=bO[e]=bO[e]||bT;bH[bT]=bU}return this},getAllResponseHeaders:function(){return bA===2?bQ:null},getResponseHeader:function(bT){var e;if(bA===2){if(!by){by={};while((e=aD.exec(bQ))){by[e[1].toLowerCase()]=e[2]}}e=by[bT.toLowerCase()]}return e===L?null:e},overrideMimeType:function(e){if(!bA){bD.mimeType=e}return this},abort:function(e){e=e||"abort";if(bL){bL.abort(e)}bF(0,e);return this}};function bF(bZ,bU,b0,bW){if(bA===2){return}bA=2;if(bE){clearTimeout(bE)}bL=L;bQ=bW||"";bJ.readyState=bZ>0?4:0;var bT,b4,b3,bX=bU,bY=b0?bk(bD,bJ,b0):L,bV,b2;if(bZ>=200&&bZ<300||bZ===304){if(bD.ifModified){if((bV=bJ.getResponseHeader("Last-Modified"))){b.lastModified[bC]=bV}if((b2=bJ.getResponseHeader("Etag"))){b.etag[bC]=b2}}if(bZ===304){bX="notmodified";bT=true}else{try{b4=F(bD,bY);bX="success";bT=true}catch(b1){bX="parsererror";b3=b1}}}else{b3=bX;if(!bX||bZ){bX="error";if(bZ<0){bZ=0}}}bJ.status=bZ;bJ.statusText=""+(bU||bX);if(bT){bR.resolveWith(bS,[b4,bX,bJ])}else{bR.rejectWith(bS,[bJ,bX,b3])}bJ.statusCode(bB);bB=L;if(bw){bG.trigger("ajax"+(bT?"Success":"Error"),[bJ,bD,bT?b4:b3])}bN.fireWith(bS,[bJ,bX]);if(bw){bG.trigger("ajaxComplete",[bJ,bD]);if(!(--b.active)){b.event.trigger("ajaxStop")}}}bR.promise(bJ);bJ.success=bJ.done;bJ.error=bJ.fail;bJ.complete=bN.add;bJ.statusCode=function(bT){if(bT){var e;if(bA<2){for(e in bT){bB[e]=[bB[e],bT[e]]}}else{e=bT[bJ.status];bJ.then(e,e)}}return this};bD.url=((bz||bD.url)+"").replace(bq,"").replace(c,r[1]+"//");bD.dataTypes=b.trim(bD.dataType||"*").toLowerCase().split(h);if(bD.crossDomain==null){bI=K.exec(bD.url.toLowerCase());bD.crossDomain=!!(bI&&(bI[1]!=r[1]||bI[2]!=r[2]||(bI[3]||(bI[1]==="http:"?80:443))!=(r[3]||(r[1]==="http:"?80:443))))}if(bD.data&&bD.processData&&typeof bD.data!=="string"){bD.data=b.param(bD.data,bD.traditional)}aX(aa,bD,bx,bJ);if(bA===2){return false}bw=bD.global;bD.type=bD.type.toUpperCase();bD.hasContent=!aR.test(bD.type);if(bw&&b.active++===0){b.event.trigger("ajaxStart")}if(!bD.hasContent){if(bD.data){bD.url+=(M.test(bD.url)?"&":"?")+bD.data;delete bD.data}bC=bD.url;if(bD.cache===false){var bv=b.now(),bP=bD.url.replace(br,"$1_="+bv);bD.url=bP+((bP===bD.url)?(M.test(bD.url)?"&":"?")+"_="+bv:"")}}if(bD.data&&bD.hasContent&&bD.contentType!==false||bx.contentType){bJ.setRequestHeader("Content-Type",bD.contentType)}if(bD.ifModified){bC=bC||bD.url;if(b.lastModified[bC]){bJ.setRequestHeader("If-Modified-Since",b.lastModified[bC])}if(b.etag[bC]){bJ.setRequestHeader("If-None-Match",b.etag[bC])}}bJ.setRequestHeader("Accept",bD.dataTypes[0]&&bD.accepts[bD.dataTypes[0]]?bD.accepts[bD.dataTypes[0]]+(bD.dataTypes[0]!=="*"?", "+aW+"; q=0.01":""):bD.accepts["*"]);for(bK in bD.headers){bJ.setRequestHeader(bK,bD.headers[bK])}if(bD.beforeSend&&(bD.beforeSend.call(bS,bJ,bD)===false||bA===2)){bJ.abort();return false}for(bK in {success:1,error:1,complete:1}){bJ[bK](bD[bK])}bL=aX(q,bD,bx,bJ);if(!bL){bF(-1,"No Transport")}else{bJ.readyState=1;if(bw){bG.trigger("ajaxSend",[bJ,bD])}if(bD.async&&bD.timeout>0){bE=setTimeout(function(){bJ.abort("timeout")},bD.timeout)}try{bA=1;bL.send(bH,bF)}catch(bM){if(bA<2){bF(-1,bM)}else{throw bM}}}return bJ},param:function(e,bw){var bv=[],by=function(bz,bA){bA=b.isFunction(bA)?bA():bA;bv[bv.length]=encodeURIComponent(bz)+"="+encodeURIComponent(bA)};if(bw===L){bw=b.ajaxSettings.traditional}if(b.isArray(e)||(e.jquery&&!b.isPlainObject(e))){b.each(e,function(){by(this.name,this.value)})}else{for(var bx in e){u(bx,e[bx],bw,by)}}return bv.join("&").replace(k,"+")}});function u(bw,by,bv,bx){if(b.isArray(by)){b.each(by,function(bA,bz){if(bv||ap.test(bw)){bx(bw,bz)}else{u(bw+"["+(typeof bz==="object"?bA:"")+"]",bz,bv,bx)}})}else{if(!bv&&b.type(by)==="object"){for(var e in by){u(bw+"["+e+"]",by[e],bv,bx)}}else{bx(bw,by)}}}b.extend({active:0,lastModified:{},etag:{}});function bk(bD,bC,bz){var bv=bD.contents,bB=bD.dataTypes,bw=bD.responseFields,by,bA,bx,e;for(bA in bw){if(bA in bz){bC[bw[bA]]=bz[bA]}}while(bB[0]==="*"){bB.shift();if(by===L){by=bD.mimeType||bC.getResponseHeader("content-type")}}if(by){for(bA in bv){if(bv[bA]&&bv[bA].test(by)){bB.unshift(bA);break}}}if(bB[0] in bz){bx=bB[0]}else{for(bA in bz){if(!bB[0]||bD.converters[bA+" "+bB[0]]){bx=bA;break}if(!e){e=bA}}bx=bx||e}if(bx){if(bx!==bB[0]){bB.unshift(bx)}return bz[bx]}}function F(bH,bz){if(bH.dataFilter){bz=bH.dataFilter(bz,bH.dataType)}var bD=bH.dataTypes,bG={},bA,bE,bw=bD.length,bB,bC=bD[0],bx,by,bF,bv,e;for(bA=1;bA=bw.duration+this.startTime){this.now=this.end;this.pos=this.state=1;this.update();bw.animatedProperties[this.prop]=true;for(bA in bw.animatedProperties){if(bw.animatedProperties[bA]!==true){e=false}}if(e){if(bw.overflow!=null&&!b.support.shrinkWrapBlocks){b.each(["","X","Y"],function(bC,bD){bz.style["overflow"+bD]=bw.overflow[bC]})}if(bw.hide){b(bz).hide()}if(bw.hide||bw.show){for(bA in bw.animatedProperties){b.style(bz,bA,bw.orig[bA]);b.removeData(bz,"fxshow"+bA,true);b.removeData(bz,"toggle"+bA,true)}}bv=bw.complete;if(bv){bw.complete=false;bv.call(bz)}}return false}else{if(bw.duration==Infinity){this.now=bx}else{bB=bx-this.startTime;this.state=bB/bw.duration;this.pos=b.easing[bw.animatedProperties[this.prop]](this.state,bB,0,1,bw.duration);this.now=this.start+((this.end-this.start)*this.pos)}this.update()}return true}};b.extend(b.fx,{tick:function(){var bw,bv=b.timers,e=0;for(;e").appendTo(e),bw=bv.css("display");bv.remove();if(bw==="none"||bw===""){if(!ba){ba=av.createElement("iframe");ba.frameBorder=ba.width=ba.height=0}e.appendChild(ba);if(!m||!ba.createElement){m=(ba.contentWindow||ba.contentDocument).document;m.write((b.support.boxModel?"":"")+"");m.close()}bv=m.createElement(bx);m.body.appendChild(bv);bw=b.css(bv,"display");e.removeChild(ba)}Q[bx]=bw}return Q[bx]}var a8,V=/^t(?:able|d|h)$/i,ad=/^(?:body|html)$/i;if("getBoundingClientRect" in av.documentElement){a8=function(by,bH,bw,bB){try{bB=by.getBoundingClientRect()}catch(bF){}if(!bB||!b.contains(bw,by)){return bB?{top:bB.top,left:bB.left}:{top:0,left:0}}var bC=bH.body,bD=aL(bH),bA=bw.clientTop||bC.clientTop||0,bE=bw.clientLeft||bC.clientLeft||0,bv=bD.pageYOffset||b.support.boxModel&&bw.scrollTop||bC.scrollTop,bz=bD.pageXOffset||b.support.boxModel&&bw.scrollLeft||bC.scrollLeft,bG=bB.top+bv-bA,bx=bB.left+bz-bE;return{top:bG,left:bx}}}else{a8=function(bz,bE,bx){var bC,bw=bz.offsetParent,bv=bz,bA=bE.body,bB=bE.defaultView,e=bB?bB.getComputedStyle(bz,null):bz.currentStyle,bD=bz.offsetTop,by=bz.offsetLeft;while((bz=bz.parentNode)&&bz!==bA&&bz!==bx){if(b.support.fixedPosition&&e.position==="fixed"){break}bC=bB?bB.getComputedStyle(bz,null):bz.currentStyle;bD-=bz.scrollTop;by-=bz.scrollLeft;if(bz===bw){bD+=bz.offsetTop;by+=bz.offsetLeft;if(b.support.doesNotAddBorder&&!(b.support.doesAddBorderForTableAndCells&&V.test(bz.nodeName))){bD+=parseFloat(bC.borderTopWidth)||0;by+=parseFloat(bC.borderLeftWidth)||0}bv=bw;bw=bz.offsetParent}if(b.support.subtractsBorderForOverflowNotVisible&&bC.overflow!=="visible"){bD+=parseFloat(bC.borderTopWidth)||0;by+=parseFloat(bC.borderLeftWidth)||0}e=bC}if(e.position==="relative"||e.position==="static"){bD+=bA.offsetTop;by+=bA.offsetLeft}if(b.support.fixedPosition&&e.position==="fixed"){bD+=Math.max(bx.scrollTop,bA.scrollTop);by+=Math.max(bx.scrollLeft,bA.scrollLeft)}return{top:bD,left:by}}}b.fn.offset=function(e){if(arguments.length){return e===L?this:this.each(function(bx){b.offset.setOffset(this,e,bx)})}var bv=this[0],bw=bv&&bv.ownerDocument;if(!bw){return null}if(bv===bw.body){return b.offset.bodyOffset(bv)}return a8(bv,bw,bw.documentElement)};b.offset={bodyOffset:function(e){var bw=e.offsetTop,bv=e.offsetLeft;if(b.support.doesNotIncludeMarginInBodyOffset){bw+=parseFloat(b.css(e,"marginTop"))||0;bv+=parseFloat(b.css(e,"marginLeft"))||0}return{top:bw,left:bv}},setOffset:function(bx,bG,bA){var bB=b.css(bx,"position");if(bB==="static"){bx.style.position="relative"}var bz=b(bx),bv=bz.offset(),e=b.css(bx,"top"),bE=b.css(bx,"left"),bF=(bB==="absolute"||bB==="fixed")&&b.inArray("auto",[e,bE])>-1,bD={},bC={},bw,by;if(bF){bC=bz.position();bw=bC.top;by=bC.left}else{bw=parseFloat(e)||0;by=parseFloat(bE)||0}if(b.isFunction(bG)){bG=bG.call(bx,bA,bv)}if(bG.top!=null){bD.top=(bG.top-bv.top)+bw}if(bG.left!=null){bD.left=(bG.left-bv.left)+by}if("using" in bG){bG.using.call(bx,bD)}else{bz.css(bD)}}};b.fn.extend({position:function(){if(!this[0]){return null}var bw=this[0],bv=this.offsetParent(),bx=this.offset(),e=ad.test(bv[0].nodeName)?{top:0,left:0}:bv.offset();bx.top-=parseFloat(b.css(bw,"marginTop"))||0;bx.left-=parseFloat(b.css(bw,"marginLeft"))||0;e.top+=parseFloat(b.css(bv[0],"borderTopWidth"))||0;e.left+=parseFloat(b.css(bv[0],"borderLeftWidth"))||0;return{top:bx.top-e.top,left:bx.left-e.left}},offsetParent:function(){return this.map(function(){var e=this.offsetParent||av.body;while(e&&(!ad.test(e.nodeName)&&b.css(e,"position")==="static")){e=e.offsetParent}return e})}});b.each({scrollLeft:"pageXOffset",scrollTop:"pageYOffset"},function(bw,bv){var e=/Y/.test(bv);b.fn[bw]=function(bx){return b.access(this,function(by,bB,bA){var bz=aL(by);if(bA===L){return bz?(bv in bz)?bz[bv]:b.support.boxModel&&bz.document.documentElement[bB]||bz.document.body[bB]:by[bB]}if(bz){bz.scrollTo(!e?bA:b(bz).scrollLeft(),e?bA:b(bz).scrollTop())}else{by[bB]=bA}},bw,bx,arguments.length,null)}});function aL(e){return b.isWindow(e)?e:e.nodeType===9?e.defaultView||e.parentWindow:false}b.each({Height:"height",Width:"width"},function(bw,bx){var bv="client"+bw,e="scroll"+bw,by="offset"+bw;b.fn["inner"+bw]=function(){var bz=this[0];return bz?bz.style?parseFloat(b.css(bz,bx,"padding")):this[bx]():null};b.fn["outer"+bw]=function(bA){var bz=this[0];return bz?bz.style?parseFloat(b.css(bz,bx,bA?"margin":"border")):this[bx]():null};b.fn[bx]=function(bz){return b.access(this,function(bC,bB,bD){var bF,bE,bG,bA;if(b.isWindow(bC)){bF=bC.document;bE=bF.documentElement[bv];return b.support.boxModel&&bE||bF.body&&bF.body[bv]||bE}if(bC.nodeType===9){bF=bC.documentElement;if(bF[bv]>=bF[e]){return bF[bv]}return Math.max(bC.body[e],bF[e],bC.body[by],bF[by])}if(bD===L){bG=b.css(bC,bB);bA=parseFloat(bG);return b.isNumeric(bA)?bA:bG}b(bC).css(bB,bD)},bx,bz,arguments.length,null)}});bd.jQuery=bd.$=b;if(typeof define==="function"&&define.amd&&define.amd.jQuery){define("jquery",[],function(){return b})}})(window); \ No newline at end of file diff --git a/readme.markdown b/readme.markdown index 1c45de7..08bc47f 100644 --- a/readme.markdown +++ b/readme.markdown @@ -6,16 +6,17 @@ Ce qui permet d'être un remplaçant de DynDNS. ## Outils - * [Dancer](http://perldancer.org/) - * [DNS::ZoneParse](http://search.cpan.org/~mschilli/DNS-ZoneParse-1.10/lib/DNS/ZoneParse.pm) + * [Dancer2](http://perldancer.org/) + * [DNS::ZoneParse](https://metacpan.org/pod/DNS::ZoneParse) * [Bootstrap](http://twitter.github.io/bootstrap/) * [DBD::mysql](https://metacpan.org/module/DBD::mysql) - * [Moose](https://metacpan.org/module/ETHER/Moose-2.0802/lib/Moose.pm) - * [Crypt::Digest::SHA256](http://search.cpan.org/~mik/CryptX-0.021/lib/Crypt/Digest/SHA256.pm) + * [Moo](https://metacpan.org/pod/Moo) + * [Crypt::Digest::SHA256](https://metacpan.org/pod/Crypt::Digest::SHA256) ## TODO * captcha * demander confirmation avant suppression d'une zone - * rajouter les types de RR manquants dans l'interface + * rajouter les types de RR manquants dans l'interface (remplacement de + DNS::ZoneParse, ou amélioration) * déléguer les zones diff --git a/www/t/001_base.t b/t/001_base.t similarity index 72% rename from www/t/001_base.t rename to t/001_base.t index 99b3017..1bdc05c 100644 --- a/www/t/001_base.t +++ b/t/001_base.t @@ -1,5 +1,5 @@ -use Test::More tests => 1; use strict; use warnings; -use_ok 'DNSManager'; +use Test::More tests => 1; +use_ok 'MyWeb::App'; diff --git a/t/002_index_route.t b/t/002_index_route.t new file mode 100644 index 0000000..bb3a6eb --- /dev/null +++ b/t/002_index_route.t @@ -0,0 +1,15 @@ +use strict; +use warnings; + +use MyWeb::App; +use Test::More tests => 2; +use Plack::Test; +use HTTP::Request::Common; + +my $app = MyWeb::App->to_app; +is( ref $app, 'CODE', 'Got app' ); + +my $test = Plack::Test->create($app); +my $res = $test->request( GET '/' ); + +ok( $res->is_success, '[GET /] successful' ); diff --git a/t/003_basic_functions.t b/t/003_basic_functions.t new file mode 100644 index 0000000..8ba637f --- /dev/null +++ b/t/003_basic_functions.t @@ -0,0 +1,23 @@ +use Test::More; +use Modern::Perl; +use lib 'lib'; +use util ':all'; + +chdir 'lib'; # TODO hack at 2am + +map { + ok + ( ( is_domain_name $_ ), "is '$_' a domain name" ) +} qw( foo.bar bar localhost. localhost ); + +map { + ok + ( ( is_valid_tld $_ ), "is '$_' a tld in the cfg file" ) +} qw( .netlib.re ); + +map { + ok + ( ( ! is_valid_tld $_ ), "is '$_' a tld in the cfg file" ) +} qw( example.com ); + +done_testing; diff --git a/t/004_filutil.t b/t/004_filutil.t new file mode 100644 index 0000000..c6c25fc --- /dev/null +++ b/t/004_filutil.t @@ -0,0 +1,28 @@ +use Test::More; +use Modern::Perl; +use URI; +use lib 'lib'; +use fileutil ':all'; + +my $f1 = "lib/fileutil.pm"; +my $p1 = "read_file"; +my $f2 = "/tmp/test"; +my $d2 = "DATA"; + +sub t_read_file { + my ($f, $pattern) = @_; + my $data = read_file $f; + $data =~ /$pattern/; +} + +sub t_write_file { + my ($f, $data) = @_; + write_file $f, $data; + my $d = read_file $f; + $d =~ /$data/; +} + +ok ((t_read_file $f1, $p1) , "read_file avec $f1 / $p1" ); +ok ((t_write_file $f2, $d2) , "write_file" ); + +done_testing; diff --git a/t/005_copycat.t b/t/005_copycat.t new file mode 100644 index 0000000..0f2c00b --- /dev/null +++ b/t/005_copycat.t @@ -0,0 +1,22 @@ +use Test::More; +use Modern::Perl; +use URI; +use lib 'lib'; +use fileutil ':all'; +use copycat ':all'; + +my $l1 = "file:///etc/hosts"; +my $l2 = "file:///tmp/truc"; + +sub t_local_local { + my ($f1, $f2) = @_; + copycat $f1, $f2; + + my $file = URI->new($f2); + my $d = read_file $file->path; + $d =~ /localhost/; +} + +ok ((t_local_local $l1, $l2) , "copycat local local avec $l1 / $l2" ); + +done_testing; diff --git a/t/006_remotecmd.t b/t/006_remotecmd.t new file mode 100644 index 0000000..8fb894e --- /dev/null +++ b/t/006_remotecmd.t @@ -0,0 +1,23 @@ +use Test::More; +use Modern::Perl; +use URI; +use lib 'lib'; +use remotecmd ':all'; + +my $port = 22; +my $user = 'karchnu'; +my $host = "karchnu.fr"; +my $cmd = "ls /"; +my $pattern = qr/etc/; + +sub t_remotecmd { + my ($user, $host, $port, $cmd, $pattern) = @_; + + my $ret = remotecmd $user, $host, $port, $cmd; + + $ret =~ $pattern; +} + +ok ((t_remotecmd $user, $host, $port, $cmd, $pattern) , "remote cmd" ); + +done_testing; diff --git a/t/007_get_iface.t b/t/007_get_iface.t new file mode 100644 index 0000000..2ccd965 --- /dev/null +++ b/t/007_get_iface.t @@ -0,0 +1,9 @@ +use Test::More; +use Modern::Perl; +use YAML::XS; +use URI; +use lib 'lib'; +use getiface':all'; + +my $x = getiface("bind9", { mycfg => '', data => '' }); +say Dump $x; diff --git a/t/00x_dump_cfg_file.t b/t/00x_dump_cfg_file.t new file mode 100644 index 0000000..3290cc3 --- /dev/null +++ b/t/00x_dump_cfg_file.t @@ -0,0 +1,22 @@ +use Test::More; +use Modern::Perl; +use YAML::XS; +use URI; +use lib 'lib'; +use configuration ':all'; + +my $x = get_cfg(); +#say Dump $x; + +say $$x{database}{host}; + +for($$x{secondarydnsserver}) +{ + for(@$_) + { + while( my ($k, $v) = each %$_) + { + say $k . ' ' . $v ; + } + } +} diff --git a/t/auth.pl b/t/auth.pl deleted file mode 100755 index 8e1ac76..0000000 --- a/t/auth.pl +++ /dev/null @@ -1,40 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; -use Config::Simple; -use Data::Dump qw( dump ); - -use lib '../'; -use app::app; - -sub initco { - - my $cfg = new Config::Simple('./config.ini'); - my $app = app->new( zdir => $cfg->param('zones_path'), - dbname => $cfg->param('dbname'), - dbhost => $cfg->param('host'), - dbport => $cfg->param('port'), - dbuser => $cfg->param('user'), - dbpass => $cfg->param('passwd'), - sgbd => $cfg->param('sgbd'), - dnsapp => $cfg->param('dnsapp') ); - - $app->init(); - - return $app; -} - -if( @ARGV < 2) { - say "usage : ./auth.pl login mdp"; - exit 1; -} - -my $app = initco(); -my ($auth_ok, $user, $isadmin) = $app->auth($ARGV[0], $ARGV[1]); - -say "auth $auth_ok"; -say "isadmin $isadmin"; diff --git a/t/config.ini b/t/config.ini deleted file mode 100644 index d22e6a2..0000000 --- a/t/config.ini +++ /dev/null @@ -1,13 +0,0 @@ -dbname = dnsmanager -host = localhost -# other options : see DBI module -sgbd = mysql -# default port for mysql -port = 3306 -user = bla -passwd = HardPass4bla -# possible options for dnsserver : bind rndc -dnsapp = rndc -zones_path = "/srv/named/" -sshhost = pizza -sshuser = karchnu diff --git a/t/get_all_domains.pl b/t/get_all_domains.pl deleted file mode 100755 index fdc4f2f..0000000 --- a/t/get_all_domains.pl +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; -use Config::Simple; -use Data::Dump qw( dump ); - -use lib '../'; -use app::app; -use initco; - -if( @ARGV != 0 ) { - say "usage : ./get_all_domains.pl"; - exit 1; -} - -my $app = initco::initco(); - -my %domains = $app->get_all_domains(); - -dump(%domains); - -#if( $domains ) { -# if( scalar(@$domains) != 0) { -# say join ", ", @{$domains}; -# } -# else { -# say "tableau vide"; -# } -#} -#else { -# say "domains undef"; -#} diff --git a/t/get_all_users.pl b/t/get_all_users.pl deleted file mode 100755 index 7c8d9f4..0000000 --- a/t/get_all_users.pl +++ /dev/null @@ -1,36 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; -use Config::Simple; -use Data::Dump qw( dump ); - -use lib '../'; -use app::app; -use initco; - -if( @ARGV != 0 ) { - say "usage : ./get_all_domains.pl"; - exit 1; -} - -my $app = initco::initco(); - -my %users = $app->get_all_users(); - -dump(%users); - -#if( $domains ) { -# if( scalar(@$domains) != 0) { -# say join ", ", @{$domains}; -# } -# else { -# say "tableau vide"; -# } -#} -#else { -# say "domains undef"; -#} diff --git a/t/get_domains.pl b/t/get_domains.pl deleted file mode 100755 index cba903d..0000000 --- a/t/get_domains.pl +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; -use Config::Simple; -use Data::Dump qw( dump ); - -use lib '../'; -use app::app; -use initco; - -if( @ARGV < 2) { - say "usage : ./auth.pl login mdp"; - exit 1; -} - -my $app = initco::initco(); -my ($auth_ok, $user, $isadmin) = $app->auth($ARGV[0], $ARGV[1]); - -if($auth_ok) { - say "auth $auth_ok"; - say "isadmin $isadmin"; -} - -my ($success, $domains) = $app->get_domains( $ARGV[0] ); - -say "success $success"; -dump($domains); - -#if( $domains ) { -# if( scalar(@$domains) != 0) { -# say join ", ", @{$domains}; -# } -# else { -# say "tableau vide"; -# } -#} -#else { -# say "domains undef"; -#} diff --git a/t/get_error.pl b/t/get_error.pl deleted file mode 100755 index 2c170f0..0000000 --- a/t/get_error.pl +++ /dev/null @@ -1,29 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; -use Config::Simple; -use Data::Dump qw( dump ); - -use lib '../'; -use app::app; - -use initco; - -if( @ARGV != 0 ) { - say "usage : ./get_all_domains.pl"; - exit 1; -} - -my $app = initco::initco(); - -my ($success) = $app->register_user("bla", 'password'); -die "erreur de nom, déjà pris" unless $success; - -($success) = $app->register_user("bla", 'password'); -die "erreur de nom, déjà pris" unless $success; - -say "fin"; diff --git a/t/initco.pm b/t/initco.pm deleted file mode 100644 index 22586da..0000000 --- a/t/initco.pm +++ /dev/null @@ -1,32 +0,0 @@ -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use Config::Simple; - -package initco; - -sub initco { - my ($cfgfile) = @_; - - $cfgfile = defined $cfgfile ? $cfgfile : './config.ini'; - - my $cfg = new Config::Simple($cfgfile); - my $app = app->new( zdir => $cfg->param('zones_path') - , dbname => $cfg->param('dbname') - , dbhost => $cfg->param('host') - , dbport => $cfg->param('port') - , dbuser => $cfg->param('user') - , dbpass => $cfg->param('passwd') - , sgbd => $cfg->param('sgbd') - , sshhost => $cfg->param('sshhost') - , sshuser => $cfg->param('sshuser') - , dnsapp => $cfg->param('dnsapp') ); - - $app->init(); - - return $app; -} - -1; diff --git a/t/scp.pl b/t/scp.pl deleted file mode 100755 index 8487ce9..0000000 --- a/t/scp.pl +++ /dev/null @@ -1,22 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use Net::OpenSSH; - -my $hostname = "ns0.arn-fai.net"; -my $username = "dnsmanager"; - -my $co = "$username\@$hostname:2222"; - -say $co; - -my $ssh = Net::OpenSSH->new($co); -$ssh->scp_put("tpl.zone", "/home/$username/") or die "scp failed: " . $ssh->error; - -#use Net::SCP; # ne fonctionne pas avec des ports :/ -#my $scp = Net::SCP->new( { host => $hostname, user => $username, port => 2222} ); -#$scp->put("tpl.zone", "lolwat") or die $scp->{errstr}; -# $scp->put("filename") or die $scp->{errstr}; diff --git a/t/ssh.pl b/t/ssh.pl deleted file mode 100644 index ea5e7bb..0000000 --- a/t/ssh.pl +++ /dev/null @@ -1,26 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use Data::Dump qw( dump ); -use DNS::ZoneParse; - -my $hostname = "pizza"; -my $username = "karchnu"; - -use Net::SSH2; - -my $ssh = Net::SSH2->new(); - -$ssh->connect($hostname); -$ssh->auth( username => $username); - -my $chan = $ssh->channel(); -$chan->exec('ls /'); - -my $buf = ''; -say $buf while $chan->read($buf, 1500); - -$ssh->disconnect(); diff --git a/t/ssh1.pl b/t/ssh1.pl deleted file mode 100644 index b1d412c..0000000 --- a/t/ssh1.pl +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use Data::Dump qw( dump ); -use DNS::ZoneParse; -use Net::SSH q; - -my $host = "pizza"; -my $user = "karchnu"; -my $cmd = "ls /"; - -sshopen2("$user\@$host", *READER, *WRITER, "$cmd") || die "ssh: $!"; - -while () { - chomp(); - print "$_\n"; -} - -close(READER); -close(WRITER); - diff --git a/t/update_domains.pl b/t/update_domains.pl deleted file mode 100755 index e86b788..0000000 --- a/t/update_domains.pl +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; -use Config::Simple; -use Data::Dump qw( dump ); - -use lib '../'; -use app::app; - -sub initco { - - my $cfg = new Config::Simple('./config.ini'); - my $app = app->new( zdir => $cfg->param('zones_path'), - dbname => $cfg->param('dbname'), - dbhost => $cfg->param('host'), - dbport => $cfg->param('port'), - dbuser => $cfg->param('user'), - dbpass => $cfg->param('passwd'), - sgbd => $cfg->param('sgbd'), - dnsapp => $cfg->param('dnsapp') ); - - $app->init(); - - return $app; -} - -if( @ARGV < 2) { - say "usage : ./auth.pl login domain"; - exit 1; -} - -my $app = initco(); -my ($auth_ok, $user, $isadmin) = $app->auth($ARGV[0], $ARGV[1]); - -if($auth_ok) { - say "auth $auth_ok"; - say "isadmin $isadmin"; -} -else { - say "erreur connexion"; - exit(0); -} - -# TODO -my $zone = $app->get_domain($ARGV[0], $ARGV[1]); - -say "origin : " . $zone->origin; - -my $zcontent = $zone->output() . "\nwww IN A 10.0.0.2"; - -$app->update_domain_raw( $ARGV[0], $zcontent , $ARGV[1] ); diff --git a/t/zone_add.pl b/t/zone_add.pl deleted file mode 100755 index 4604843..0000000 --- a/t/zone_add.pl +++ /dev/null @@ -1,28 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; - -use lib '../'; -use app::zone::rndc_interface; -use app::zone::edit; - -my $nom = $ARGV[0]; -my $zdir = "/srv/named/"; - -my $ed = app::zone::edit->new(zdir => $zdir, zname => $nom); - -my $zonefile = $ed->addzone(); - -my $a_records = $zonefile->a(); - -push (@$a_records, { name => 'www' - , class => 'IN' - , host => '192.168.0.190' - , ttl => '' - , ORIGIN => $zonefile->origin }); - -$ed->update($zonefile); diff --git a/t/zone_del.pl b/t/zone_del.pl deleted file mode 100755 index 96b0279..0000000 --- a/t/zone_del.pl +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; - -use lib '../'; -use app::zone::rndc_interface; -use app::zone::edit; - -my $ed = app::zone::edit->new( zname => $ARGV[0], zdir => "/srv/named/"); - -say "suppression de ". $ARGV[0]; - -$ed->del(); diff --git a/t/zone_rndc.pl b/t/zone_rndc.pl deleted file mode 100755 index b669659..0000000 --- a/t/zone_rndc.pl +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; - -use lib '../'; -use app::zone::rndc_interface; -use app::zone::edit; - -my $nom = $ARGV[0]; -my $zdir = "/srv/named/"; - -my $ed = app::zone::edit->new(zdir => $zdir, zname => $nom); - -my $zonefile = $ed->get(); - -my $a_records = $zonefile->a(); - -push (@$a_records, { name => 'web' - , class => 'IN' - , host => '192.168.0.190' - , ttl => '3600' - , ORIGIN => $zonefile->origin }); - -$ed->update($zonefile); - -$zonefile = $ed->get(); - -print $zonefile->output(); diff --git a/t/zone_tmp.pl b/t/zone_tmp.pl deleted file mode 100755 index 42e9f37..0000000 --- a/t/zone_tmp.pl +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/perl -w -use strict; -use warnings; -use v5.14; -use autodie; -use Modern::Perl; -use DNS::ZoneParse; - -use lib '../'; -use app::zone::rndc_interface; -use app::zone::edit; - -my $nom = $ARGV[0]; -my $zdir = "/srv/named/"; - -my $ed = app::zone::edit->new(zdir => $zdir, zname => $nom); - -my $zonefile = $ed->new_tmp(); - -print $zonefile->output(); diff --git a/www/views/administration.tt b/views/administration.tt similarity index 86% rename from www/views/administration.tt rename to views/administration.tt index 059748f..658a8fa 100644 --- a/www/views/administration.tt +++ b/views/administration.tt @@ -53,11 +53,13 @@
diff --git a/views/details.tt b/views/details.tt new file mode 100644 index 0000000..1fadb52 --- /dev/null +++ b/views/details.tt @@ -0,0 +1,179 @@ +<% include header.tt %> +<% include sidebar.tt %> +<% include error.tt %> + +
+ +

Fichier de zone de <% domain %>

+ + <% IF expert %> + +
+ + + + + <% ELSE %> + +
+ <% IF pair.value == 1 %> - + <% ELSE %> - + <% END %> +
+ + + + + + + + <% FOREACH address in ns %> + + + + + + + + + + <% END %> + + <% FOREACH address in a %> + + + + + + + + + + <% END %> + + <% FOREACH address in aaaa %> + + + + + + + + + + <% END %> + + <% FOREACH address in cname %> + + + + + + + + + + <% END %> + + <% FOREACH address in ptr %> + + + + + + + + + + <% END %> + + <% FOREACH address in mx %> + + + + + + + + + + <% END %> +
+
NameClassTypeHostTTL
<% address.class %> + +
<% address.class %> + +
<% address.class %> + +
<% address.class %> + +
<% address.class %> + +
<% address.class %> + +
+ + + +
+
+ +
+ Ajout d'un enregistrement + +
+ +
+ +
+
+ +
+ +
+ + +
+
+ +
+ +
+ +
+
+ +
+ +
+ +
+ Votre adresse IP : <% user_ip %> +
+ +
+ +
+ +
+
+
+ +
+
+ +
+
+ +
+ + + <% END %> + + + + + diff --git a/views/error.tt b/views/error.tt new file mode 100644 index 0000000..76c40b1 --- /dev/null +++ b/views/error.tt @@ -0,0 +1,20 @@ +<% IF deferred.errmsg %> +
+
+

Oh ! + <% deferred.errmsg %> + +

+
+
+<% END %> + + +<% IF deferred.succmsg %> +
+

Bien ! + <% deferred.succmsg %> + +

+
+<% END %> diff --git a/www/views/header.tt b/views/header.tt similarity index 100% rename from www/views/header.tt rename to views/header.tt diff --git a/www/views/home.tt b/views/home.tt similarity index 60% rename from www/views/home.tt rename to views/home.tt index 6f8145b..14d3771 100644 --- a/www/views/home.tt +++ b/views/home.tt @@ -4,29 +4,19 @@
- - <% IF creationSuccess.defined && creationSuccess.length > 0 %> -
-

Bien ! - <% creationSuccess %> - -

-
- <% END %> - <% IF domains && domains.size %>

Vos domaines :

<% FOREACH domain in domains %> - + <% END %> @@ -47,6 +37,11 @@ <% ELSE %> <% END %> + diff --git a/www/views/index.tt b/views/index.tt similarity index 100% rename from www/views/index.tt rename to views/index.tt diff --git a/www/views/layouts/main.tt b/views/layouts/main.tt similarity index 100% rename from www/views/layouts/main.tt rename to views/layouts/main.tt diff --git a/views/sidebar.tt b/views/sidebar.tt new file mode 100644 index 0000000..c053623 --- /dev/null +++ b/views/sidebar.tt @@ -0,0 +1,57 @@ +
+
+ +
+
+

Navigation

+
+ +
+ <% IF login.defined %> + + Salut <% login %> ! +
+ +
+ Déconnexion + Ma page + + <% IF admin == 1 %> + Administration + <% END %> + + <% IF domain.defined %> + Supprimer la zone + <% END %> +
+ + <% IF domains.defined && domains.size > 0 %> +
+

Mes domaines

+ <% FOREACH domain IN domains %> + <% domain.domain %>
+ <% END %> + <% END %> + + <% ELSE %> +
+
+
+ Se connecter : + + + + + + +
+ +
+
+ + + <% END %> +
+
+
+
diff --git a/www/views/subscribe.tt b/views/subscribe.tt similarity index 100% rename from www/views/subscribe.tt rename to views/subscribe.tt diff --git a/www/bin/app.pl b/www/bin/app.pl deleted file mode 100755 index 251b454..0000000 --- a/www/bin/app.pl +++ /dev/null @@ -1,4 +0,0 @@ -#!/usr/bin/env perl -use Dancer; -use DNSManager; -dance; diff --git a/www/conf/config.ini b/www/conf/config.ini deleted file mode 100644 index af7f354..0000000 --- a/www/conf/config.ini +++ /dev/null @@ -1,42 +0,0 @@ -# TLD -# Must contains the first "." -tld = ".netlib.re" - -# Database Options -sgbd = mysql -dbname = dnsmanager -host = localhost - -# default port for mysql -port = 3306 -user = monutilisateur -passwd = motdepasse -# other options : see DBI module - -# possible options for dnsserver : -# rndc (bind) -# knot -# then secondary nameserver -dnsapp = rndc -dnsappsec = nsdc -zones_path = "/var/named/rndczones/" - -dnsslavekey = key-slave.netlib.re - -# to access zones on the server -sshhost = host -sshuser = dnsmanager -sshport = 2222 - -# name the IP of the primary named server -nsmasterv4 = 192.0.2.1 -nsmasterv6 = 2001:db8::1 - -# name the IP of the slave named server -nsslavev4 = 192.0.2.2 -nsslavev6 = 2001:db8::2 - -# to access to the slave DNS server -sshhostsec = host -sshusersec = dnsmanager -sshportsec = 2222 diff --git a/www/environments/development.yml b/www/environments/development.yml deleted file mode 100644 index 1107437..0000000 --- a/www/environments/development.yml +++ /dev/null @@ -1,27 +0,0 @@ -# configuration file for development environment - -# the logger engine to use -# console: log messages to STDOUT (your console where you started the -# application server) -# file: log message to a file in log/ -logger: "console" - -# the log level for this environment -# core is the lowest, it shows Dancer's core log messages as well as yours -# (debug, info, warning and error) -log: "core" - -# should Dancer consider warnings as critical errors? -warnings: 1 - -# should Dancer show a stacktrace when an error is caught? -show_errors: 1 - -# auto_reload is a development and experimental feature -# you should enable it by yourself if you want it -# Module::Refresh is needed -# -# Be aware it's unstable and may cause a memory leak. -# DO NOT EVER USE THIS FEATURE IN PRODUCTION -# OR TINY KITTENS SHALL DIE WITH LOTS OF SUFFERING -auto_reload: 0 diff --git a/www/lib/DNSManager.pm b/www/lib/DNSManager.pm deleted file mode 100755 index fa70591..0000000 --- a/www/lib/DNSManager.pm +++ /dev/null @@ -1,783 +0,0 @@ -package DNSManager; - -use Dancer ':syntax'; -use strict; -use warnings; -use v5.14; -use Modern::Perl; -use Data::Dump qw( dump ); -use Data::Structure::Util qw ( unbless ); -use File::Basename; -use Config::Simple; -use Crypt::Digest::SHA256 qw( sha256_hex ) ; -use Storable qw( freeze thaw ); -$Storable::Deparse = true; -$Storable::Eval=true; -use encoding 'utf-8'; # TODO check if this works well - -# Include other libs relative to current path -use Find::Lib '../../'; # TODO remove it when it won't be usefull anymore -use app::app; - -our $VERSION = '0.1'; - -# TODO we can check if dn matches our domain name -sub is_domain_name { - my ($dn) = @_; - my $ndd = qr/^([a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]*.)*[a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]$/; - return $dn =~ $ndd; -} - -sub is_reserved { - my ($domain) = @_; - - my $filename = "conf/reserved.zone"; - open my $entree, '<:encoding(UTF-8)', $filename or - die "Impossible d'ouvrir '$filename' en lecture : $!"; - - while(<$entree>) { - if(m/^$domain$/) { - return 1; - } - } - - return 0; -} - -# eventually change place -sub initco { - - my $cfg = new Config::Simple(dirname(__FILE__).'/../conf/config.ini'); - my $app = app->new( zdir => $cfg->param('zones_path') - , dbname => $cfg->param('dbname') - , dbhost => $cfg->param('host') - , dbport => $cfg->param('port') - , dbuser => $cfg->param('user') - , dbpass => $cfg->param('passwd') - , sgbd => $cfg->param('sgbd') - , nsmasterv4 => $cfg->param('nsmasterv4') - , nsmasterv6 => $cfg->param('nsmasterv6') - , nsslavev4 => $cfg->param('nsslavev4') - , nsslavev6 => $cfg->param('nsslavev6') - , sshhost => $cfg->param('sshhost') - , sshhostsec => $cfg->param('sshhostsec') - , sshuser => $cfg->param('sshuser') - , sshusersec => $cfg->param('sshusersec') - , sshport => $cfg->param('sshport') - , sshportsec => $cfg->param('sshportsec') - , dnsslavekey => $cfg->param('dnsslavekey') - , dnsapp => $cfg->param('dnsapp') - , dnsappsec => $cfg->param('dnsappsec') ); - - $app->init(); - - return $app; -} - -sub get_errmsg { - my $err = session 'errmsg'; - session errmsg => ''; - $err; -} - -# TODO check if the referer was from our website -sub get_route { - my $route = '/'; - $route = request->referer if (defined request->referer); - $route; -} - -get '/' => sub { - if( session('login') ) - { - - my $app = initco(); - my ($success, @domains) = $app->get_domains( session('login') ); - - if( $success ) { - - template index => { - login => session('login') - , admin => session('admin') - , errmsg => get_errmsg - , domains => [ @domains ] }; - } - else { - session->destroy; - template 'index'; - } - - } - else - { - - template 'index' => { - errmsg => get_errmsg - }; - } -}; - -prefix '/domain' => sub { - - any ['post', 'get'] => '/updateraw/:domain' => sub { - - # check if user is logged & if domain parameter is set - unless( session('login') && param('domain')) - { - redirect '/'; - } - else - { - - my $app = initco(); - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - if($auth_ok && ($isadmin || grep { $_ eq param('domain') } - @{$user->domains}) ) { - - my $success = $app->update_domain_raw( param('zoneupdated') - , param('domain')); - - unless($success) { - session errmsg => q{Problème de mise à jour du domaine.}; - } - - redirect '/domain/details/' . param('domain'); - } - else { - session errmsg => q{Donnée privée, petit coquin. ;) }; - redirect '/'; - } - } - - }; - - any ['post', 'get'] => '/update/:domain' => sub { - - unless( session('login') && param('domain') ) - { - redirect '/'; - } - else - { - - my $type = param('type'); - my $name = param('name'); - my $value = param('value'); - my $ttl = param('ttl'); - my $priority = param('priority'); - - my $app = initco(); - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless($auth_ok && ($isadmin || grep { $_ eq param('domain') } - @{$user->domains}) ) { - - session errmsg => q{Donnée privée, petit coquin. ;) }; - redirect '/'; - return; - } - - my $zone = $app->get_domain( param('domain') ); - given( $type ) - { - - when ('A') { - my $a = $zone->a(); - push( @$a, {name => $name - , class => "IN" - , host => $value - , ttl => $ttl - , ORIGIN => $zone->origin} ); - } - - when ('AAAA') { - my $aaaa = $zone->aaaa; - push(@$aaaa, {name => $name - , class => "IN" - , host => $value - , ttl => $ttl - , ORIGIN => $zone->origin} ); - } - - when ('CNAME') { - my $cname = $zone->cname; - push(@$cname, - {name => $name - , class => "IN" - , host => $value - , ttl => $ttl - , ORIGIN => $zone->origin} ); - } - - when ('MX') { - my $mx = $zone->mx; - push(@$mx, { name => $name - , class => "IN" - , host => $value - , priority => $priority - , ttl => $ttl - , ORIGIN => $zone->origin} ); - } - - when ('PTR') { - my $ptr = $zone->ptr; - push(@$ptr, {name => $name - , class => "IN" - , host => $value - , ttl => $ttl - , ORIGIN => $zone->origin} ); - } - - when ('NS') { - my $ns = $zone->ns; - push(@$ns, {name => $name - , class => "IN" - , host => $value - , ttl => $ttl - , ORIGIN => $zone->origin} ); - } - - } - - $zone->new_serial(); - dump($zone); - - $app->update_domain( $zone , param('domain')); - - redirect '/domain/details/' . param('domain'); - } - }; - - get '/details/:domain' => sub { - - # check if user is logged & if domain parameter is set - unless( session('login') && param('domain')) - { - redirect '/'; - } - else - { - my $app = initco(); - - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless ( $auth_ok && ( $isadmin - || grep { $_ =~ param('domain') } @{$user->domains})) { - - session errmsg => q{Auth non OK.}; - redirect '/ '; - return; - - } - - my $zone = $app->get_domain(param('domain')); - - if( param( 'expert' ) ) - { - template details => { - login => session('login') - , admin => session('admin') - , domain => param('domain') - , domain_zone => $zone->output() - , user_ip => request->address - , expert => true }; - } - else - { - # say dump( $zone->cname()); - template details => { - login => session('login') - , admin => session('admin') - , domain => param('domain') - , domain_zone => $zone->output() - , a => $zone->a() - , aaaa => $zone->aaaa() - , cname => $zone->cname() - , ptr => $zone->ptr() - , mx => $zone->mx() - , ns => $zone->ns() - , user_ip => request->address }; - } - - } - - }; - - post '/add/' => sub { - - # check if user is logged & if domain parameter is set - unless( session('login') && param('domain')) - { - redirect '/'; - } - else - { - - my $creationSuccess = ''; - - if(is_reserved(param('domain'))) { - session errmsg => - q{Le nom de domaine est réservé}; - } - elsif( param('domain') =~ /^[a-zA-Z0-9]+[a-zA-Z0-9-]+[a-zA-Z0-9]+$|^[a-zA-Z0-9]+$/) - { - - my $cfg = new Config::Simple(dirname(__FILE__).'/../conf/config.ini'); - my $domain = param('domain').$cfg->param('tld'); - my $app = initco(); - my ($success) = $app->add_domain( session('login'), $domain ); - - if ($success) { - $creationSuccess = q{Le nom de domaine a bien été réservé ! }; - } - else { - session errmsg => q{Le nom de domaine est déjà pris.}; - } - - } - else - { - session errmsg => - q{Le nom de domaine entré contient des caractères invalides}; - } - - session creationSuccess => $creationSuccess; - session domainName => param('domain'); - redirect '/user/home'; - - } - - }; - - get '/del/:domain' => sub { - - my $app = initco(); - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless ( $auth_ok && ( $isadmin - || grep { $_ =~ param('domain') } @{$user->domains})) { - - session errmsg => q{Auth non OK.}; - redirect '/ '; - return; - } - - unless( defined param('domain') ) { - session errmsg => q; - redirect get_route; - return; - } - - if( ! is_domain_name(param('domain'))) { - session errmsg => q; - redirect get_route; - return; - } - - my $success = $app->delete_domain(session('login'), param('domain')); - - unless($success) { - session errmsg => q{Impossible de supprimer le domaine.}; - } - - if( request->referer =~ "/domain/details" ) { - redirect '/user/home'; - } - else { - redirect request->referer; - } - - }; - - get '/del/:domain/:name/:type/:host/:ttl' => sub { - - # Load :domain and search for corresponding data - my $app = initco(); - - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless ( $auth_ok && ( $isadmin - || grep { $_ =~ param('domain') } @{$user->domains})) { - - session errmsg => q{Auth non OK.}; - redirect '/ '; - return; - } - - unless( session( 'user' ) and defined param('domain') ) { - session errmsg => q; - redirect get_route; - return; - } - - $app->delete_entry( param('domain'), - { - type => param('type'), - name => param('name'), - host => param('host'), - ttl => param('ttl') - }); - - redirect '/domain/details/'. param('domain'); - }; - - get '/mod/:domain/:name/:type/:host/:ttl' => sub { - - my $app = initco(); - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless ( $auth_ok && ( $isadmin - || grep { $_ =~ param('domain') } @{$user->domains})) { - - session errmsg => q{Auth non OK.}; - redirect '/ '; - return; - } - - unless( session( 'user' ) and defined param('domain') ) { - session errmsg => q; - redirect get_route; - return; - } - - $app->modify_entry( param('domain'), - { - type => param('type'), - name => param('name'), - host => param('host'), - ttl => param('ttl') - }, - { - newtype => param('newtype'), - newname => param('newname'), - newhost => param('newhost'), - newttl => param('newttl'), - newpriority => param('newpriority') - }); - - redirect '/domain/details/'. param('domain'); - }; - - get '/cli/:login/:pass/:domain/:name/:type/:host/:ttl/:ip' => sub { - - my $pass = sha256_hex(param('pass')); - my $app = initco(); - my ($auth_ok, $user, $isadmin) = $app->auth(param('login'), $pass); - - unless ( $auth_ok && ( $isadmin - || grep { $_ =~ param('domain') } @{$user->domains})) { - - say "ERROR"; - return; - } - - my $name = param('name'); - my $domain = param('domain'); - my $type = param('type'); - my $host = param('host'); - my $ttl = param('ttl'); - my $ip = param('ip'); - - $app->modify_entry( param('domain'), - { - type => $type - , name => $name - , host => $host - , ttl => $ttl - }, - { - newtype => $type - , newname => $name - , newhost => $ip - , newttl => $ttl - , newpriority => '' - }); - - say "OK"; - }; -}; - -any ['get', 'post'] => '/admin' => sub { - - unless( session('login') ) - { - redirect '/'; - return; - } - - my $app = initco(); - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless ( $auth_ok && $isadmin ) { - session errmsg => q{Donnée privée, petit coquin. ;) }; - redirect '/ '; - return; - } - - my %alldomains = $app->get_all_domains; - my %allusers = $app->get_all_users; - my ($success, @domains) = $app->get_domains( session('login') ); - - template administration => { - login => session('login') - , admin => session('admin') - , errmsg => get_errmsg - , domains => [ @domains ] - , alldomains => { %alldomains } - , allusers => { %allusers } }; -}; - -prefix '/user' => sub { - - get '/home' => sub { - - unless( session('login') ) { - redirect '/'; - return; - } - - my $app = initco(); - - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless( $auth_ok ) { - session errmsg => q/problème de connexion à votre compte/; - redirect '/'; - return; - } - - my ($success, @domains) = $app->get_domains( session('login') ); - - if( $success ) { - - my $cs = session('creationSuccess'); - my $dn = session('domainName'); - - session creationSuccess => ''; - session domainName => ''; - - template home => { - login => session('login') - , admin => session('admin') - , domains => [@domains] - , creationSuccess => $cs - , errmsg => get_errmsg - , domainName => $dn }; - - } - else { - session->destroy; - redirect '/ '; - } - - }; - - - get '/logout' => sub { - session->destroy; - redirect '/'; - }; - - # add a user => registration - post '/add/' => sub { - - unless ( param('login') && param('password') && param('password2') ) { - session errmsg => q/Identifiant ou mot de passe non renseigné./; - redirect '/user/subscribe'; - return; - } - - unless ( param('password') eq param('password2')) { - session errmsg => q/Les mots de passes ne sont pas identiques./; - redirect '/user/subscribe'; - return; - } - - my $pass = sha256_hex(param('password')); - - my $app = initco(); - my ($success) = $app->register_user(param('login'), $pass); - - if($success) { - session login => param('login'); - session password => $pass; - redirect '/user/home'; - } - else { - session errmsg => q/Ce pseudo est déjà pris./; - redirect '/user/subscribe'; - } - - }; - - get '/subscribe' => sub { - - if( defined session('login') ) { - redirect '/user/home'; - } - else { - - template subscribe => { - errmsg => get_errmsg - , admin => session('admin') - }; - } - - }; - - get '/unsetadmin/:user' => sub { - - unless( defined param('user') ) { - - session errmsg => "L'administrateur n'est pas défini." ; - redirect request->referer; - return; - - } - - if(! defined session('login') ) { - - session errmsg => "Vous n'êtes pas connecté." ; - redirect '/'; - return; - } - - my $app = initco(); - - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless ( $auth_ok && $isadmin ) { - session errmsg => q/Vous n'êtes pas administrateur./; - } - else { - $app->set_admin(param('user'), 0); - } - - if( request->referer =~ "/admin" ) { - redirect request->referer; - } - else { - redirect '/'; - } - - }; - - get '/setadmin/:user' => sub { - - unless( defined param('user') ) { - - session errmsg => "L'utilisateur n'est pas défini." ; - redirect request->referer; - return; - } - - if(! defined session('login') ) { - - session errmsg => "Vous n'êtes pas connecté." ; - redirect '/'; - return; - } - - my $app = initco(); - - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - unless ( $auth_ok && $isadmin ) { - session errmsg => q/Vous n'êtes pas administrateur./; - } - else { - $app->set_admin(param('user'), 1); - } - - if( request->referer =~ "/admin" ) { - redirect request->referer; - } - else { - redirect '/'; - } - - }; - - get '/del/:user' => sub { - - if(defined param 'user') { - - my $app = initco(); - - my ($auth_ok, $user, $isadmin) = $app->auth(session('login'), - session('password') ); - - if ( $auth_ok && $isadmin || session('login') eq param('user')) { - unless ( $app->delete_user(param('user'))) { - session errmsg => "L'utilisateur " - . param 'user' - . " n'a pas pu être supprimé."; - } - } - } - else { - session errmsg => q{Le nom d'utilisateur n'est pas renseigné.}; - } - - if( defined request->referer) { - redirect request->referer; - } - else { - redirect '/'; - } - - }; - - post '/login' => sub { - - # Check if user is already logged - unless ( session('login') ) - { - # Check user login and password - if ( param('login') && param('password') ) - { - - my $app = initco(); - my $pass = sha256_hex(param('password')); - my ($auth_ok, $user, $isadmin) = $app->auth(param('login'), - $pass ); - - if( $auth_ok ) - { - - session login => param('login'); - session password => $pass; - session user => freeze( $user ); - session admin => $isadmin; - - if( $isadmin ) { - redirect '/admin'; - return; - } - - } - else - { - - session errmsg => q; - redirect '/'; - - } - } - } - - redirect '/user/home'; - - }; -}; diff --git a/www/public/javascripts/jquery.js b/www/public/javascripts/jquery.js deleted file mode 120000 index b77fd86..0000000 --- a/www/public/javascripts/jquery.js +++ /dev/null @@ -1 +0,0 @@ -/usr/share/javascript/jquery/jquery.js \ No newline at end of file diff --git a/www/t/002_index_route.t b/www/t/002_index_route.t deleted file mode 100644 index e114cd3..0000000 --- a/www/t/002_index_route.t +++ /dev/null @@ -1,10 +0,0 @@ -use Test::More tests => 2; -use strict; -use warnings; - -# the order is important -use DNSManager; -use Dancer::Test; - -route_exists [GET => '/'], 'a route handler is defined for /'; -response_status_is ['GET' => '/'], 200, 'response status is 200 for /'; diff --git a/www/views/details.tt b/www/views/details.tt deleted file mode 100644 index 296fb86..0000000 --- a/www/views/details.tt +++ /dev/null @@ -1,179 +0,0 @@ -<% include header.tt %> -<% include sidebar.tt %> -<% include error.tt %> - -
- -

Fichier de zone de <% domain %>

- - <% IF expert %> - -
- - - - - <% ELSE %> - -
<% domain %><% domain.domain %> - + - +
- - - - - - - - <% FOREACH address in ns %> - - - - - - - - - - <% END %> - - <% FOREACH address in a %> - - - - - - - - - - <% END %> - - <% FOREACH address in aaaa %> - - - - - - - - - - <% END %> - - <% FOREACH address in cname %> - - - - - - - - - - <% END %> - - <% FOREACH address in ptr %> - - - - - - - - - - <% END %> - - <% FOREACH address in mx %> - - - - - - - - - - <% END %> -
-
NameClassTypeHostTTL
<% address.class %>NS - -
<% address.class %>A - -
<% address.class %>AAAA - -
<% address.class %>CNAME - -
<% address.class %>PTR - -
<% address.class %>MX - -
- - - -
-
- -
- Ajout d'un enregistrement - -
- -
- -
-
- -
- -
- - -
-
- -
- -
- -
-
- -
- -
- -
- Votre adresse IP : <% user_ip %> -
- -
- -
- -
-
-
- -
-
- -
-
- -
- - - <% END %> - -
- - - diff --git a/www/views/error.tt b/www/views/error.tt deleted file mode 100644 index 1e13d46..0000000 --- a/www/views/error.tt +++ /dev/null @@ -1,10 +0,0 @@ -<% IF errmsg.defined && errmsg.length > 0 %> -
-
-

Oh ! - <% errmsg %> - -

-
-
-<% END %> diff --git a/www/views/sidebar.tt b/www/views/sidebar.tt deleted file mode 100644 index 96097e4..0000000 --- a/www/views/sidebar.tt +++ /dev/null @@ -1,57 +0,0 @@ -
-
- -
-
-

Navigation

-
- -
- <% IF login.defined %> - - Salut <% login %> ! -
- -
- Déconnexion - Ma page - - <% IF admin == 1 %> - Administration - <% END %> - - <% IF domain.defined %> - Supprimer la zone - <% END %> -
- - <% IF domains.defined && domains.size > 0 %> -
-

Mes domaines

- <% FOREACH domain IN domains %> - <% domain %>
- <% END %> - <% END %> - - <% ELSE %> -
-
-
- Se connecter : - - - - - - -
- -
-
-
- - <% END %> -
-
-
-