From 527313614ca5b25c2f3166683d54faa3658521db Mon Sep 17 00:00:00 2001 From: karchnu Date: Tue, 13 Oct 2015 19:06:37 +0200 Subject: [PATCH] simplification du code, gestion plus claire des erreurs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Le code a été simplifié de plusieurs manières : du code plus générique, dans la gestion des utilisateurs et des routes, mais aussi côté gestion des zones et serveurs de zones ajout de commandes en cli pour gérer les zones et utilisateurs, et faire des tests du code plus simplement qu'en passant par l'interface web Le projet est passé à Dancer2. Le dépôt est désormais moins éparpillé également, ce qui est visible est directement le projet Dancer. Le code concernant l'init reste pas top, idem pour la gestion de dépendances (un simple script), il faudrait utiliser les outils du cpan. --- .gitignore | 2 + www/MANIFEST => MANIFEST | 30 +- www/MANIFEST.SKIP => MANIFEST.SKIP | 4 + www/Makefile.PL => Makefile.PL | 15 +- app/app.pm | 271 ------ app/bdd/admin.pm | 33 - app/bdd/lambda.pm | 78 -- app/bdd/management.pm | 194 ----- app/zone/edit.pm | 196 ----- app/zone/interface.pm | 16 - app/zone/nsdc_interface.pm | 117 --- app/zone/rndc_interface.pm | 41 - bin/app.psgi | 8 + cli/{ => daemon}/README.markdown | 0 cli/daemon/ca.cert | 31 + cli/{ => daemon}/daemon.pl | 0 cli/get_domain.pl | 29 + cli/get_domains.pl | 28 + cli/get_users.pl | 27 + cli/toggle_admin.pl | 28 + cli/user_add.pl | 28 + cli/user_auth.pl | 32 + cli/user_del.pl | 27 + cli/user_update_passwd.pl | 27 + cli/zone_add.pl | 28 + cli/zone_del.pl | 26 + conf/config.yml | 44 + {www/conf => conf}/reserved.zone | 0 www/config.yml => config.yml | 22 +- cpanfile | 11 + environments/development.yml | 23 + .../production.yml | 7 +- init/deploiement.sh | 13 + get_libs.sh => init/get_libs.sh | 29 +- init/init-create-db.sql | 1 + init/init-create-user.sql | 2 + init/init-grant-user.sql | 1 + init/init-tables.sql | 21 + init/recreate.sh | 10 + init/remove-db.sql | 1 + init/remove-user.sql | 1 + {t => init}/tpl.zone | 2 +- lib/MyWeb/App.pm | 196 +++++ lib/README.markdown | 7 + lib/app.pm | 144 ++++ lib/configuration.pm | 166 ++++ lib/copycat.pm | 72 ++ lib/db.pm | 269 ++++++ lib/encryption.pm | 16 + lib/fileutil.pm | 46 + lib/getiface.pm | 27 + lib/interface/bind9.pm | 85 ++ .../interface/knot.pm | 4 +- lib/interface/nsd3.pm | 90 ++ lib/interface/nsd4.pm | 125 +++ lib/remotecmd.pm | 35 + lib/rt/admin.pm | 46 + lib/rt/domain.pm | 502 +++++++++++ lib/rt/root.pm | 45 + lib/rt/user.pm | 255 ++++++ lib/testapp.pl | 113 +++ lib/util.pm | 31 + lib/zone.pm | 284 +++++++ lib/zonefile.pm | 52 ++ www/notes.txt => notes.txt | 0 {www/public => public}/404.html | 2 +- {www/public => public}/500.html | 2 +- .../css/bootstrap-theme.min.css | 0 {www/public => public}/css/bootstrap.min.css | 0 public/css/error.css | 70 ++ public/css/style.css | 189 +++++ {www/public => public}/dispatch.cgi | 7 +- {www/public => public}/dispatch.fcgi | 7 +- public/favicon.ico | Bin 0 -> 1406 bytes .../fonts/glyphicons-halflings-regular.eot | Bin .../fonts/glyphicons-halflings-regular.svg | 0 .../fonts/glyphicons-halflings-regular.ttf | Bin .../fonts/glyphicons-halflings-regular.woff | Bin public/images/perldancer-bg.jpg | Bin 0 -> 7125 bytes public/images/perldancer.jpg | Bin 0 -> 2240 bytes .../javascripts/jquery.js | 0 {www/public => public}/js/bootstrap.min.js | 0 public/js/jquery.min.js | 23 + readme.markdown | 11 +- {www/t => t}/001_base.t | 4 +- t/002_index_route.t | 15 + t/003_basic_functions.t | 23 + t/004_filutil.t | 28 + t/005_copycat.t | 22 + t/006_remotecmd.t | 23 + t/007_get_iface.t | 9 + t/00x_dump_cfg_file.t | 22 + t/auth.pl | 40 - t/config.ini | 13 - t/get_all_domains.pl | 36 - t/get_all_users.pl | 36 - t/get_domains.pl | 43 - t/get_error.pl | 29 - t/initco.pm | 32 - t/scp.pl | 22 - t/ssh.pl | 26 - t/ssh1.pl | 24 - t/update_domains.pl | 55 -- t/zone_add.pl | 28 - t/zone_del.pl | 17 - t/zone_rndc.pl | 32 - t/zone_tmp.pl | 20 - {www/views => views}/administration.tt | 6 +- views/details.tt | 179 ++++ views/error.tt | 20 + {www/views => views}/header.tt | 0 {www/views => views}/home.tt | 21 +- {www/views => views}/index.tt | 0 {www/views => views}/layouts/main.tt | 0 views/sidebar.tt | 57 ++ {www/views => views}/subscribe.tt | 0 www/bin/app.pl | 4 - www/conf/config.ini | 42 - www/environments/development.yml | 27 - www/lib/DNSManager.pm | 783 ------------------ www/public/javascripts/jquery.js | 1 - www/t/002_index_route.t | 10 - www/views/details.tt | 179 ---- www/views/error.tt | 10 - www/views/sidebar.tt | 57 -- 125 files changed, 3832 insertions(+), 2588 deletions(-) rename www/MANIFEST => MANIFEST (66%) rename www/MANIFEST.SKIP => MANIFEST.SKIP (75%) rename www/Makefile.PL => Makefile.PL (52%) delete mode 100644 app/app.pm delete mode 100644 app/bdd/admin.pm delete mode 100644 app/bdd/lambda.pm delete mode 100644 app/bdd/management.pm delete mode 100644 app/zone/edit.pm delete mode 100644 app/zone/interface.pm delete mode 100644 app/zone/nsdc_interface.pm delete mode 100644 app/zone/rndc_interface.pm create mode 100755 bin/app.psgi rename cli/{ => daemon}/README.markdown (100%) create mode 100644 cli/daemon/ca.cert rename cli/{ => daemon}/daemon.pl (100%) create mode 100644 cli/get_domain.pl create mode 100644 cli/get_domains.pl create mode 100644 cli/get_users.pl create mode 100644 cli/toggle_admin.pl create mode 100644 cli/user_add.pl create mode 100644 cli/user_auth.pl create mode 100644 cli/user_del.pl create mode 100644 cli/user_update_passwd.pl create mode 100644 cli/zone_add.pl create mode 100644 cli/zone_del.pl create mode 100644 conf/config.yml rename {www/conf => conf}/reserved.zone (100%) rename www/config.yml => config.yml (64%) create mode 100644 cpanfile create mode 100644 environments/development.yml rename {www/environments => environments}/production.yml (71%) create mode 100755 init/deploiement.sh rename get_libs.sh => init/get_libs.sh (55%) create mode 100644 init/init-create-db.sql create mode 100644 init/init-create-user.sql create mode 100644 init/init-grant-user.sql create mode 100644 init/init-tables.sql create mode 100755 init/recreate.sh create mode 100644 init/remove-db.sql create mode 100644 init/remove-user.sql rename {t => init}/tpl.zone (82%) create mode 100644 lib/MyWeb/App.pm create mode 100644 lib/README.markdown create mode 100644 lib/app.pm create mode 100644 lib/configuration.pm create mode 100644 lib/copycat.pm create mode 100644 lib/db.pm create mode 100644 lib/encryption.pm create mode 100644 lib/fileutil.pm create mode 100644 lib/getiface.pm create mode 100644 lib/interface/bind9.pm rename app/zone/knot_interface.pm => lib/interface/knot.pm (90%) create mode 100644 lib/interface/nsd3.pm create mode 100644 lib/interface/nsd4.pm create mode 100644 lib/remotecmd.pm create mode 100644 lib/rt/admin.pm create mode 100644 lib/rt/domain.pm create mode 100644 lib/rt/root.pm create mode 100644 lib/rt/user.pm create mode 100644 lib/testapp.pl create mode 100644 lib/util.pm create mode 100644 lib/zone.pm create mode 100644 lib/zonefile.pm rename www/notes.txt => notes.txt (100%) rename {www/public => public}/404.html (88%) rename {www/public => public}/500.html (88%) rename {www/public => public}/css/bootstrap-theme.min.css (100%) rename {www/public => public}/css/bootstrap.min.css (100%) create mode 100644 public/css/error.css create mode 100644 public/css/style.css rename {www/public => public}/dispatch.cgi (62%) rename {www/public => public}/dispatch.fcgi (67%) create mode 100644 public/favicon.ico rename {www/public => public}/fonts/glyphicons-halflings-regular.eot (100%) rename {www/public => public}/fonts/glyphicons-halflings-regular.svg (100%) rename {www/public => public}/fonts/glyphicons-halflings-regular.ttf (100%) rename {www/public => public}/fonts/glyphicons-halflings-regular.woff (100%) create mode 100644 public/images/perldancer-bg.jpg create mode 100644 public/images/perldancer.jpg rename www/public/js/jquery.min.js => public/javascripts/jquery.js (100%) rename {www/public => public}/js/bootstrap.min.js (100%) create mode 100644 public/js/jquery.min.js rename {www/t => t}/001_base.t (72%) create mode 100644 t/002_index_route.t create mode 100644 t/003_basic_functions.t create mode 100644 t/004_filutil.t create mode 100644 t/005_copycat.t create mode 100644 t/006_remotecmd.t create mode 100644 t/007_get_iface.t create mode 100644 t/00x_dump_cfg_file.t delete mode 100755 t/auth.pl delete mode 100644 t/config.ini delete mode 100755 t/get_all_domains.pl delete mode 100755 t/get_all_users.pl delete mode 100755 t/get_domains.pl delete mode 100755 t/get_error.pl delete mode 100644 t/initco.pm delete mode 100755 t/scp.pl delete mode 100644 t/ssh.pl delete mode 100644 t/ssh1.pl delete mode 100755 t/update_domains.pl delete mode 100755 t/zone_add.pl delete mode 100755 t/zone_del.pl delete mode 100755 t/zone_rndc.pl delete mode 100755 t/zone_tmp.pl rename {www/views => views}/administration.tt (86%) create mode 100644 views/details.tt create mode 100644 views/error.tt rename {www/views => views}/header.tt (100%) rename {www/views => views}/home.tt (60%) rename {www/views => views}/index.tt (100%) rename {www/views => views}/layouts/main.tt (100%) create mode 100644 views/sidebar.tt rename {www/views => views}/subscribe.tt (100%) delete mode 100755 www/bin/app.pl delete mode 100644 www/conf/config.ini delete mode 100644 www/environments/development.yml delete mode 100755 www/lib/DNSManager.pm delete mode 120000 www/public/javascripts/jquery.js delete mode 100644 www/t/002_index_route.t delete mode 100644 www/views/details.tt delete mode 100644 www/views/error.tt delete mode 100644 www/views/sidebar.tt 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 0000000000000000000000000000000000000000..96c746534efbc258304d25171912ebf5493bc5ed GIT binary patch literal 1406 zcmdVZ_g9lw7zgmLZEevms-gmFt;np(3`4-k3QJtHT191V$`B-g2!af$0wP*y!9tNG z2!uU?U;?DPs310s$Sf(tf`S}RwbmrP`2+mPpZebOx!?DB?oZFT=K?Fpl9dJ7W#F?C zSPwvrAT4Yqr2V&jGBT?mw{9)uH^@OzVIwvx%45qW1t@Rd1{Fe8SxO0NDoRjSRfYzk zuBMEg8miFJ+5uf{P3TGK>>@NlBlLB(K%?ovh^7q#eO(wy8R){;P!Dty1MD#|BxwY? zsnl**(2ZebZVHA49oCj~*bvs1dtu8k$37cNIM`Vc3^>>`aL~aPhxgmT@qm;)jvqaQ zlgAw4>~sX4u1@fBJpnIwXZU)Yg13hYd_A4v-~ zNBAQm)E~^y07QlbfEgA7=9wUzWdXJ#LKKn{;*gRQkMxu{q+d=(W@-YFh%2cVaXmc=8EKc0otc8%tTYtlWTNOsCW`a2 zP*RYMQlhLd7v;sds4UJwRcRhrxAMU%%SY|)n`o#iLL;k~C_!^|3F>Pqz^*L?yRHmP zbroo>FGpKL1)3YG!D+4_D#2~3MhBY(F1s3?tt^7ntu^4bHG$V&hwi%#=;1Vi;xyqN zm;84$qmS2u{%$rNQg^`bX~p2ZHazI*0{=b-0}t8>E{1xEJ}!p)I!JaBT@dsW1HG6S zx{p!8?+`xj#h8GP*|CS1938^+*dvm|m>uuO?8GC?5mV#C5Kaj2WRj1+{@_D6IfD7A z0n7^rut3ZU2T2a%>7PTG5sqU{IDu!=LwGtpOj3ZwnPDu=N(u0sSekuIjN_W#eV<41I8=(2vC+PtC=QRJC|>vV4cccD5`)y0n>xv9cZk9zR`QwS z7pvk|XLj;9d5SfK#VyrQ&S|%^KWXDqo6>d79Nz!-vsEgVcHufy*3JeC>(!c{nx5FI z^N!1jCW zE%iS3jqfehh+spu%-YtC^(FRUH#Hj7zNUY8W2OAn^_3N0bWz(fl+;Z&UwBV0>GRst zdK9O8O-%I9uCd|$TiI7T0y3_+Z`pN6T1w~5 RaWvTN)J&1_sCT`<{{RlP*^2-G literal 0 HcmV?d00001 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 0000000000000000000000000000000000000000..6ee6b77394657a70de11dbda4f0c8ceffb07bd92 GIT binary patch literal 7125 zcmd5=c|4Ts+keIq2{nnbAA~GZF(%oCkVK`%GFgs&OeSj#Vdx|(L#44xSti-S*cBaV z(pZvZGDdbKO!jr~KKgae@BH51@_FC)ulJhI%=OIuT+el1*Y{eU`@Xk7Z4Uwa%#6<% z0}u!VFaxXU}e4=-&PNp?jfF0U@!20tZ9{ zq0mE8heX81B_$;V4oV-EmN+aXAt|w=1Oh(FwTp|No10(a0Q7*w|C+WN0Vp?Q5wge* zkp|eH5OyeJ`wburKmc~o*uM}PI|qatyrr}c0-}iwB$|4ME`p|EOa}-OecVA2k0Mr31X|pkgRH6wm_}pZ#Ux|NG^{5aY(p?DWos zU87^-VvHNL|1(t_i`G{w;={8_+bC3>a!XI^6DcdZ3S0}NG^VCIYJ3h_;FgX39l}09 z6k5kCykl;X;*S+4fUwGsq#JAni-sx?;}!>T9)B0 zz&v=RFuu!|utlfjM zMa)>}tClXoqM0mYBGQ9=tXPru-ahT)E0z5`m^)pcg^amZl5pR#J;^fSGoQNvKbMla z`cK(I_Z;EmY;j$PWWJ^6ZQa6%WZM?Yc|Y2aVEdWn{3X5lDq;f{EF5Cp6CZp#JWnt9 zw>70Lr!mqR-f)%lSuFih723?#xPni$m_Q5B`=h2ol|ZiCK;)sgWm|JNw!G6DZSGscy!V-wVU;B-NHJYb z@$<7AP!iy$39a|nzKefJQynreI*`kgh|G5g>%*uSa-gA{WJpwMYi|(Wp|UrNL|km_w;}a= z)n#oVmhP!^m?%Er@B06K1+lnNJ1Nd+OR|^J7l6aNrR6-%cgBmO#dod;3!SMNJk z-tQ5$qh%m9l_>-zNmXuZF?tXV1%-;-*5`t0#?_#VqActJ`Llx=Y>4{r1@&DWPg9dw zHQT^9{$^6zX>T|>)?{Ob9+UvXeSXqc)nDybT1TxU*m?5MyL28JJQV2E;JmTWwrb(s z1dhVqh zx1$(pw&Xiy-C^wQMpVrmw_CyrYMBKk1ZUjynQKBD*F+NBEX60vh*zu}O8d^A_&DeJ z^`RgPkJIFnSF}?fS4CX|J;iK4;Zncl4gtW|| zixm|6j)3s#%GIdSt3<}KJqQI}$)?at>H`4ybyj~=BPlxZ+XYM!;~J%OVAV3++rVf= z&iBGP>dN%K%sQ0~lHj*-OnwRnMzfw;nd#--Z{=T|)V@r-=?xQ# z``tXa1o61Z-u<8R1R_SfL-U(%&%Rny(sr#;26K+&H)cHEysA)A+ep+rt<-kZcz$r! z&O>%d{7Im*k_cW}TIT!|F~hj;i_Xe4pNu-KVQ77Y#ctXm{AECMqHu*X`@V`w=Ld+f zd2i8#%!^~fuU>8gT%eSQ@wJkO*Gu15cim5$8k}tqYb&VXh~%wIrTOf>H|Vep+yqB| zC>;LOp*!?JvDm94@V<&pT+p+?Tw^S)Q{l`X?{TX7M$)yY5i^)9A zmCeOn75*a3-J>lCVyYcXG+MS^rvmGk;1zAYRajBxvE(Wk6+G49eN7+V@YQ5P_g=&c zD{=blx=+DZ%7#4wVb5l^u&QkRxKS7Y?9DG+z0sxV_bE@y;1>}V zJw9x$CnPCmK041q<~`)%rTKWeg4KIHCpzd43Fcnjakm-m)!UFXQQ*-Fli^ulI}(ZZqhiftgLj$HT(+6Yy;H zTYCh*%3_1?v zE8;EqBd7)bxlr&P6*R}fm-64J>9f++@!z3#}4>!-tK0$*F{%g zUZFpzmCwOxXZjJ&n()tEXhzi0UUf}hcM;~?ET7FfQYuNW0Or&3xF-Qwb$dzb;$?Dg z5I|QALrEeVdu8qfnWmU>;#fjT9Yef$cE`Pw$`|e)ww6e5Ne$JP$z=3inqEqRXkVx^ zbkXB;#Qoc_1Ke&U2>J2jf-ad}=j8h`=cwpgpM@i^zKHIH8&Rq4d9sz|)xV;LXKKG5 z{L^b0A`VSXoP#fa75-9~8?7FEDmyw3>nh%+MMetsB1Eh#>kzrYpt#IVglCd=g3Zrf zDxi}kCUD0t>|wDaF5$9W8|i${o%=QlHiXWcA@Lun-Xs@+JY^$Gs9)}8lm7lh@7rGR z3%M?7CSW4w_fhH>FvvC~dU#a;D67uvnV<{BSwuD`=eOzQVuEo!WWED&2Kww^aQV#Z zO%mC1z~GWEk`lvTlYf}bX*-&O8hYFy)K!ly=b2qjy{XwMQ1Ijd!dt_(ds_6i(kMUv zr;of0bLI@ZFJ771c=K{N1FjeC^>&;jZ|(~?5z+^h&NqwX$bq8VgKurV)VZ;a+mP)!k&uRW(7k@+7xv5g5}6&21^-+JaY$DtbbDTR)q?NM9dtXEs=IxDw( z%M=5e{eukEZyv7jm~~4)HBlIeOYh_P22L(Ttp7lO;~Lm^2P!+OyR?TYHyRZ@hjl)z z3$5L%R#5x=+Vf|$ij2*X6%)=*!p#i^gU7@6+-Ou3+T3&F^&)HmDAuYNp1q|9jyH(; z(Jt-=Yp;~IBF*|duk+xOYstE|UO64~*?RCs1&KLl=0wdzxSg||R8Rg`0^a2CZ7<@x zxAfYRY(A;IGG3fr4cl>B`(R4O*_zU}FTPpsf6ns^1%-6opBNuNxAFuDP`T96Dq??QZIhO0C9-eWs7MWYWbxqdfrUO z*5vm5*=>Nf4P@2tNXSZviSlLy=XKR7znmf<#9u21+%bMR zP~q%mLaX{cc)o2WA8-3N)3VbJk0y6}J8lZC{$XuCK{{;MO|`i$>dFsKzk&#`C(SH7Z8~H7o4*;l{sWG@naU3bDhDviR-aFK?A2@8JvK=c{58Y%Rv&BX zmuf%FrYum|*lNe*olxr4<;PtIRuaT)qout?EfF%Pm=B7%dFA4EliEG~0+xIAu#tZC z3oQRV2j&F2^joKce0DLcY7NX*aa)L;@;0c>u;}%bIR8x4w|#OyA%g29SR%{_z-`5= zX^6_~x!t&L#5qLqZacZ>fizm#k00P~ZC!D^DQSUGYI4AsG@V;_Nsf2AoOJXR{aH#% zX3i2Ssv>1dlv61<;v|Y{IXfd=H)+o%<{0eZIkK5(;=}#5(+ugmd?94MJu-X`6QPuH zyuHlO2K0#AZSI@{u3KNjBH5NycY6I-502T8gxYT*+{Ax#j5H?#jmp*Wfu9$-nL&ZyFzIDDWa7*B0e-?zU$)jwDD7^ z*368^*a*I{C*YS4U?UvsGYWES-Z(-SqTTMgrEO_`_rZ~t)2Z9m;BoZbF0rNj~@)ot1o=QI$H4AEuYSZ zT`yFQ4u_pqs&+gtVl&@91G}DKC|I8(N@whv2)rLV$HwxCmsoQ z&Rf2s8OmTg;dRJ?s8HRyv|jHo(e|33@mO8t+58iq;LQ8HZQ7Qe?hyCACz0h``9Lqh z(xOFXXHxn2!ykk8;rDCy?BaNE~#ciDq;iAs$(^VZ6!{;K@i zzCVJ`)TQvWi^C)ytR-Rw(hVuG;98ElOrvHVI%RA-uzb&WEOFMD$#*FZDgF~6kh!#o zkh2By>05HIX50C!Rz_ljIIGTuK56O?Exf<%MtgDWUnNYFv86>TOD|mwj3w{cYR3y^ zTSB@sQH=8EXAiG3;glX1#c4P;X@Z1PAm_E^Z z7aLyKH=Cd@?5CxwBlUH5fN4TX0zoimG<^0Qjp(ePGXIwCvt;+B8F%L(fB~ZOR?8pB zyOTRuk!I5KT$Xx*Y>VT3rItv*0pIZVGPPSfiNsl17|&?^MZ~u~lTPXoLW6#p(gBM3r03r)RPP+KW=55o$)CjGXM%7) zG$WD{J%N?3&0s~EO``bp2J3O*Z|OROku^TjSrregS}Vo8GRp=&BHg_^-t%~PdX z%EhG~kK)(&Nzoaj?vGe%iyx%GZggnVY^!TTJAv}edYO+nwVx8)L#9*T7#H&lm+*$& zBNH%al5CC26hqMWAmf`(VjaAu4_d>;@kRV;U9$ec*_G~jR&mQ@AECU!yBKjJWNjCM zrT#!zh2B6?8B4VofxPh82A&S?BWjn2dO3;hbfiF*V_6K7FL*RK1tyY$X_|z)#n}k? zbkWZ7Cd=DO6@qIWBCbS`KvhK%>#Xk}1VSOHI0Zp>OIS_O5sK{x<>9Hi!0GN1KAh(t zZ^9i=O=NKe3P6)*_axoCxBnL7v>q9L^YT+w7e-JJUzJGaYyszzW@gJ60p(A7aTJ>S zw38h9vullCA#Ve~dnRd^M0`9H6Yup^(1ilm%8XO_C>FD3DhzLD67)`blC8opWu@m9 z^KyhtoIk5h>O@9&=A)V{2XY_Ug?7?UI}a%3b6&Abtp`)lTm4S!QZb{@%J;daQ2vO^ z41MexRHgR>Pq5c_rio6K`yHdx;h(x>SvgSqyANJ^nCV|l`{-h&Tq7+#0KE=*QxIxR zE-cm!VQMN>xR>`rvc*RadtI6+fi3O!>8vyBZ*~u%IlZgmN{5Y;RR)Y7|?Xt5d;(Ih@V-3C~cG_M6VjCz5je@gM z5Yrx2Fx;<4dKIyu<#Zg$=?>$-MCFzsxL3jK>}gLO|08dAID~3EYM!D*^*$j*Oz+uc zd{6x@jo7*%k|CH<$b{UuCcX{qua)<$wiXaZuNf0lQN)4BosGgD#8&5$OTs`?-$X&9 z37cYQziK-*(=ue2_rHAgVkUmSr9zpy3o#X=i7#VDY%tc&d1A8 zPbA}i=!%;WjJufv6D%3$y}Y&T_b9DDbs)Xt;d-M~Dyw(0&!(JDqo~2jwyEp0bkJSMzslMWsfDcQDY_fEUD2omff(+(pwys}aT{Yp<{=xdpQTPB)Wvx|ju z4iY@E_&MR9jNcq94oN$q5X(@+W)`lMhBodjKdL=ri|v+}twyaDLkBlMeVGvY*y;JT zK*FY~-^2S%;ljmJKF?E}Lr}FJKt-I^DHD@@Zb4ar+%#3M(f6km$VQW&zLn&lp1+GT z(KkGd{V?4togvop3QJhJ6UY%!K%C*<2CQMDz;yd!<$>jlqj;`kqn*6|TAZML>_jqB z^L@(863s06hjO(hw#MX?jgq6y7=k+XTiJM80HKk1<)wyKUP$#L@c2w$!zLxe5UK2I zGgxz1?4q91pRWD&0z8XEO*u?22ngh34dqUf*`-@??l0xb)G1ya*cf(X{sYZ`#Bi4X z>IDH6y0w*hMV62?%zMM;Ip*wY`~5Am>v4_81g(`Luy>U_Ge7cw1q3OHg=#!SS{Y{V zm%n-GigN}n{5B(6kfktKUK!^lrJs`(f*Vd=F57a7gUBi)?7p}`-e1}t}u=r@rbEGQc z)Qd{u`wNYXpX`ACF9|%`txZo38xH0@QAi2)tprbVe@`1;!CMI>&nV@m(Ota2mF4h0 zuUHCpJ=V{+Bcv}waf}=qH855}et&@&HkG6F*nU0tU~XNbys&~UbHRFI-&nIAf7r>b za-hn0{@Y_x1V++QeuMM(%qxRm$EZ5fd(tQ3A31_Uy6GFAg#5cU4dBFA16S4qR+15S zUS^458VLS6Fmdq*W<8K7b^c-6xzwd7&i?lVEP5SWi@rvvm|jB*`PT&O`JHB9i2m9|xEg zM#5EP56$Sd7>lylNY>U|@81s>va2E8LzRm~Es-%X@gAq_eUUn~g#`Q`^ZzH#o*I8! r-=Em9&yO}D4v{cpLCshO`WjTn7!4P2pzX)z`9CT6|8Z~ccHe&ia0D~m literal 0 HcmV?d00001 diff --git a/public/images/perldancer.jpg b/public/images/perldancer.jpg new file mode 100644 index 0000000000000000000000000000000000000000..3f9e718c1cde297047ece800fdd8252f9a26e2c7 GIT binary patch literal 2240 zcmb7^XHe7W7RLV}2?=F^5Q;<+x^%o01tCCyg-|roq=YCUgt7?IF+eOxQ(3?emMTR< z1f;i6br%#|T+kp*nv|s%r7Ec8#(QV>e!kC{IWuQ|=RD86UtZ1_X9f_lwX(4SKp+rc z%`Jd)1RyQK$X*d-AKB=D2!C0;%{hC{1YiL`!4L=p4COW`6v_(|gu}QJ77*YQLNc>x>;03PsG6eje7486m zA^={Ps2m(AuWcso;1zmXOhQq|-0^{eZdi%M74LZNp(F_S@BTloKT^0r8u0^g1OEp= zJP1)at`%n-5aco*1Q-FB0`FrgCt1VC!%Dsew!=Tk{fqVlL-A6zK6n`0y1CZ80GVia zho|!&SBqSd2ocTDiG09#o!MizaP5*T@swrVqYk7yztipJm?Kx|H zmXWthKloGo+kDl8`I_Swdel_Tb>=t)o!JT~HtIFGW4gCDB<{TbYa8ewf9<`kacoQ0 zR`>NU(P`o7v<#t~i7;Qb-!+@;W~~b2Y*C$~!5v{GGe8;pIz*ZQ@U89az8#oqyC3vR zig<@vSp#j$>=%t#Of*(yYX+RTP!KmzRw_@*hrJMD@2Y;ByLMue5>t*0%vI@aKNQfC zdF{41Tm|jT#>;3j}OuGHU^Z69so)Js3pUBF19*dq} zC4s9_>+3zCyF@E25{&WBzuwTKjO{sgXTGO?{Wk zOG?fKc!rYt6MdS=FDY_CZG&C3;QS?T&7><}q3H(Z} zw~wh}sPK++fQ{=iCZ0FSj68>}O4nEKy(%;JxDt1nVg+)&uY53csIc*DG`219NP(ze zi8CSH&PH z7F{aST|h9J1CU*C{CkTFJyc2^Q`q}NizoS_$NJ&Oz`@g0)}pnhrjm+NK`#FrRRbbE zyE^Z9%{6DoPsoT~#*_Py=gs6SmQ+SsWKP8{rgiX5{fWbI_S*8kVbs*n#h!VB+(BP_ z0-k7o?-OZhaT~mQuxuyw>ncKD*|&m~61q-w%5Qo-qY74Wh~NMhNM96~CKyVO-r8JX zRM3`g!@Fp*ZKFkolU~{1+1;tpxnFktY}bsdbbR~gPJMF|mLDjJXj0QH$(#-oZ^`Tn z9r9s(=gpOlnF_^xD3G0IVxP>Jh2Gs6Pi2RWV9$1VI8r71@Ch?}D9=BN-r+wIkF{q9 z*_F||Fhb1~(UQYjE0a$ADDGhz3)1qS=6ZntcEQcyXM*AydnA3B?#*6@ZB_P7A^j@C zw~5t`(kp+Y{7F44*6-aKo+~R#35&U&Oj-$Q#@88asOX9hoY~j|eVP>kMxr=FlbgGm=GWLl~dQ##X#= zL%qpvyAXvCV(=xDS$zwoT-R}DYm)xXi!XD^kO9M?v1YQnE5-dS)TRkdmRlG5qVwT& z?~3tEfN7tH&EaZqLUh>#7D(93o<*10eX5;1!{VTdTf7+eyTk`xc?C=EmwqD&wOZ}oTGs1qwhz5 zgHAn<(D#pW8KW(#Rf!)+q+5-|(dO`H3!tF%(2?iEgtdu?d}i)p?BUqHh$lhf-79_> z?M+j(Z6N>9vfOz;o{?i&7nNA!vdGtq1z&q}Mx|Us+*JceeNL6-FO=!=#URBjO3umU>Lf9?@S?}jRr)(B; zfD}-Z_hP9`T4j}v7K(3{{po0xM6YX}@6HrwIkN0u4WMlTEQf1nn!zep^n3>=&M_`4$zJ{`&fe+5+hJ|j^UxIg YF`UKK`df5{a0sfXY&amGz?Ad;-=8MrLjV8( literal 0 HcmV?d00001 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 %> -
-
-
-