├── .gitignore ├── LICENSE ├── MANIFEST ├── MANIFEST.SKIP ├── Makefile.PL ├── bin └── app.psgi ├── cli ├── daemon │ ├── README.markdown │ └── daemon.pl ├── get_domain.pl ├── get_domains.pl ├── get_users.pl ├── toggle_admin.pl ├── user_add.pl ├── user_auth.pl ├── user_del.pl ├── user_update_passwd.pl ├── users_del.pl ├── zone_add.pl ├── zone_del.pl └── zones_del.pl ├── conf ├── config.yml └── reserved.zone ├── config.yml ├── cpanfile ├── environments ├── development.yml └── production.yml ├── init ├── dependancies.ubuntu ├── deploiement.sh ├── perlmodules ├── sql │ ├── init-create-db.sql │ ├── init-create-user.sql │ ├── init-grant-user.sql │ ├── init-tables.sql │ ├── remove-db.sql │ └── remove-user.sql └── tpl.zone ├── lib ├── MyWeb │ └── App.pm ├── README.markdown ├── app.pm ├── configuration.pm ├── copycat.pm ├── db.pm ├── encryption.pm ├── fileutil.pm ├── getiface.pm ├── interface │ ├── bind9.pm │ ├── knot.pm │ ├── nsd3.pm │ └── nsd4.pm ├── remotecmd.pm ├── rt │ ├── admin.pm │ ├── adminfake.pm │ ├── domain.pm │ ├── domainfake.pm │ ├── root.pm │ ├── rootfake.pm │ ├── user.pm │ └── userfake.pm ├── testapp.pl ├── util.pm ├── zone.pm └── zonefile.pm ├── notes.txt ├── public ├── 404.html ├── 500.html ├── css │ ├── bootstrap-theme.css │ ├── bootstrap-theme.css.map │ ├── bootstrap-theme.min.css │ ├── bootstrap-theme.min.css.map │ ├── bootstrap.css │ ├── bootstrap.css.map │ ├── bootstrap.min.css │ ├── bootstrap.min.css.map │ └── dnsmanager.css ├── dispatch.cgi ├── dispatch.fcgi ├── favicon.ico ├── fonts │ ├── glyphicons-halflings-regular.eot │ ├── glyphicons-halflings-regular.svg │ ├── glyphicons-halflings-regular.ttf │ ├── glyphicons-halflings-regular.woff │ └── glyphicons-halflings-regular.woff2 ├── images │ ├── perldancer-bg.jpg │ └── perldancer.jpg └── js │ ├── bootstrap.js │ ├── bootstrap.min.js │ ├── jquery.min.js │ └── npm.js ├── readme.markdown ├── t ├── 001_base.t ├── 002_index_route.t ├── 003_basic_functions.t ├── 004_filutil.t ├── 005_copycat.t ├── 006_remotecmd.t ├── 007_get_iface.t ├── 008_zonefile.t ├── 00x_dump_cfg_file.t └── zonefile.txt └── views ├── administration.tt ├── details.tt ├── error.tt ├── header.tt ├── home.tt ├── index.tt ├── layouts └── main.tt ├── sidebar.tt └── subscribe.tt /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | sessions 3 | init/bind9.cfg.tar.gz 4 | init/varnamed.tar.gz 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Philippe PITTOLI and Julien SIMONET 2 | 3 | Permission to use, copy, modify, and/or distribute this software for any 4 | purpose with or without fee is hereby granted, provided that the above 5 | copyright notice and this permission notice appear in all copies. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 8 | WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 9 | MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 10 | ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 11 | WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 12 | ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 13 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 14 | -------------------------------------------------------------------------------- /MANIFEST: -------------------------------------------------------------------------------- 1 | MANIFEST 2 | cpanfile 3 | Makefile.PL 4 | config.yml 5 | MANIFEST.SKIP 6 | environments/development.yml 7 | environments/production.yml 8 | t/001_base.t 9 | t/002_index_route.t 10 | public/500.html 11 | public/favicon.ico 12 | public/dispatch.cgi 13 | public/404.html 14 | public/dispatch.fcgi 15 | public/images/perldancer-bg.jpg 16 | public/images/perldancer.jpg 17 | public/javascripts/jquery.js 18 | public/css/error.css 19 | public/css/style.css 20 | bin/app.psgi 21 | views/index.tt 22 | views/layouts/main.tt 23 | lib/MyWeb/App.pm 24 | -------------------------------------------------------------------------------- /MANIFEST.SKIP: -------------------------------------------------------------------------------- 1 | ^\.git\/ 2 | maint 3 | ^tags$ 4 | .last_cover_stats 5 | Makefile$ 6 | ^blib 7 | ^pm_to_blib 8 | ^.*.bak 9 | ^.*.old 10 | ^t.*sessions 11 | ^cover_db 12 | ^.*\.log 13 | ^.*\.swp$ 14 | MYMETA.* 15 | ^.gitignore 16 | ^.svn\/ 17 | ^MyWeb-App- 18 | -------------------------------------------------------------------------------- /Makefile.PL: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | use ExtUtils::MakeMaker; 4 | 5 | # Normalize version strings like 6.30_02 to 6.3002, 6 | # so that we can do numerical comparisons on it. 7 | my $eumm_version = $ExtUtils::MakeMaker::VERSION; 8 | $eumm_version =~ s/_//; 9 | 10 | WriteMakefile( 11 | NAME => 'MyWeb::App', 12 | AUTHOR => q{YOUR NAME }, 13 | VERSION_FROM => 'lib/MyWeb/App.pm', 14 | ABSTRACT => 'YOUR APPLICATION ABSTRACT', 15 | ($eumm_version >= 6.3001 16 | ? ('LICENSE'=> 'perl') 17 | : ()), 18 | PL_FILES => {}, 19 | PREREQ_PM => { 20 | 'Test::More' => 0, 21 | 'YAML' => 0, 22 | 'Dancer2' => 0.161000, 23 | }, 24 | dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, 25 | clean => { FILES => 'MyWeb-App-*' }, 26 | ); 27 | -------------------------------------------------------------------------------- /bin/app.psgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | use FindBin; 6 | use lib "$FindBin::Bin/../lib"; 7 | use MyWeb::App; 8 | MyWeb::App->to_app; 9 | -------------------------------------------------------------------------------- /cli/daemon/README.markdown: -------------------------------------------------------------------------------- 1 | # mise à jour automatique (façon DynDNS) 2 | 3 | Pour mettre à jour automatiquement une adresse IP d'un nom de domaine, il faut 4 | changer les quelques valeurs du fichier daemon.pl (en haut). 5 | -------------------------------------------------------------------------------- /cli/daemon/daemon.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use strict; 3 | use warnings; 4 | use v5.14; 5 | 6 | use MIME::Base64 qw(encode_base64); 7 | 8 | ################# 9 | # CONFIGURATION # 10 | ################# 11 | 12 | # the website sending your current IP address 13 | our $checkip = "http://t.karchnu.fr/ip.php"; 14 | 15 | # Domain name of the service provider (like netlib.re) 16 | our $nddservice = "netlib.re"; 17 | 18 | # Your domain 19 | our $domain = ""; # Example: "home.netlib.re" 20 | 21 | # Login and password to connect to the website 22 | our $login = ""; 23 | our $pass = ""; 24 | 25 | # The name of the actual machine in your domain to be updated. 26 | # Updated record: $machine.$domain 27 | # Following the examples, updated record will be: www.home.netlib.re 28 | # You can put "@" to change your $type record on $domain directly. 29 | our $machine = ""; # example: www 30 | our $type = 'A'; # could also be AAAA (IPv6) 31 | 32 | # Should we force a secure connection to netlib.re? 33 | # In case you don't have an updated list of certificates 34 | # from certification authorities, you _could_ change this to "0". 35 | # This would imply: any man-in-the-middle attack with a wrong certificate 36 | # could see a hash of your password. Please, consider updating your OS before 37 | # trying this option. 38 | our $is_secure = 1; 39 | 40 | # Saving our previous IP to update only on change 41 | # (you can just ignore those lines) 42 | our $filename = 'saved_ip.txt'; 43 | our $saved_ip = "0.0.0.0"; 44 | 45 | # FOR DEBUG PURPOSES 46 | # In case you want always to try to update the IP address, even in case it 47 | # didn't change from the last time you run the script. 48 | our $ignore_saved_address = 0; 49 | 50 | ######################## 51 | # END OF CONFIGURATION # 52 | ######################## 53 | 54 | # Test the configuration. 55 | die "You did not enter your domain. (ex: home.netlib.re)" if $domain eq ""; 56 | die "You did not enter your machine name. (ex: www)" if $machine eq ""; 57 | 58 | die "You did not enter your login." if $login eq ""; 59 | die "You did not enter your password." if $pass eq ""; 60 | 61 | 62 | # Test the environment. 63 | our $wget = `which wget`; chomp $wget; 64 | die "There is no wget on this computer." unless $wget; 65 | 66 | our $ip_version_opt = ($type =~ /AAAA/) ? '-6' : '-4'; 67 | sub get_ip { 68 | my $cmd = "wget $ip_version_opt -nv -O - $checkip"; 69 | say "get your current IP: $cmd"; 70 | for (split "\n", `$cmd 2>/dev/null`) { 71 | /^[0-9.]+$/ || /^[0-9a-f:]+$/ and return $_ 72 | } 73 | undef 74 | } 75 | 76 | # Saving IP to file 77 | sub save_ip { 78 | my ($ip) = @_; 79 | open(my $fhw, '>', $filename) or die "Could not open file '$filename' $!"; 80 | print $fhw "$ip"; 81 | close $fhw; 82 | } 83 | 84 | # Loading IP from file 85 | sub load_ip { 86 | if (open(my $fho, '<:encoding(UTF-8)', $filename)) { 87 | $saved_ip = <$fho>; 88 | } 89 | else { 90 | say "no $filename -> default IP is $saved_ip"; 91 | $saved_ip; 92 | }; 93 | } 94 | 95 | sub update { 96 | my $ip = get_ip; 97 | die "Can't get your IP address !" unless $ip; 98 | 99 | load_ip; 100 | if ($saved_ip ne $ip || $ignore_saved_address) { 101 | say "DEBUG: ignoring saved address" if $ignore_saved_address; 102 | say "UPDATE :: domain $machine.$domain"; 103 | say " old ip ($type): $saved_ip"; 104 | say " new ip ($type): $ip"; 105 | my $passb64 = encode_base64($pass); 106 | chomp $passb64; 107 | 108 | my $opts = ""; 109 | $opts = "--no-check-certificate" unless $is_secure; 110 | my $cmd = "$wget $opts -O - "; 111 | $cmd .= "https://$nddservice/domain/cliup/"; 112 | $cmd .= "$login/$passb64/$domain/$machine/$type/$ip"; 113 | say "CMD :: $cmd"; 114 | `$cmd`; 115 | save_ip $ip; 116 | } 117 | } 118 | 119 | update; 120 | -------------------------------------------------------------------------------- /cli/get_domain.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use lib './lib/'; 11 | use configuration ':all'; 12 | use encryption ':all'; 13 | use app; 14 | 15 | if( @ARGV != 1 ) { 16 | say "usage : ./$0 domain"; 17 | exit 1; 18 | } 19 | 20 | my $dom = $ARGV[0]; 21 | 22 | eval { 23 | my $app = app->new(get_cfg()); 24 | my $domain = $app->get_domain($dom); 25 | dump($domain); 26 | }; 27 | 28 | if( $@ ) { 29 | say q{Une erreur est survenue. } . $@; 30 | } 31 | -------------------------------------------------------------------------------- /cli/get_domains.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | #use DNS::ZoneParse; 9 | #use Config::Simple; 10 | use Data::Dump qw( dump ); 11 | 12 | use lib './lib/'; 13 | use configuration ':all'; 14 | use app; 15 | 16 | if( @ARGV != 0 ) { 17 | say "usage : ./$0"; 18 | exit 1; 19 | } 20 | 21 | eval { 22 | my $app = app->new(get_cfg()); 23 | my $domains = $app->get_all_domains(); 24 | dump($domains); 25 | }; 26 | 27 | if( $@ ) { 28 | say q{Une erreur est survenue. } . $@; 29 | } 30 | -------------------------------------------------------------------------------- /cli/get_users.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use lib './lib/'; 11 | use configuration ':all'; 12 | use app; 13 | 14 | if( @ARGV != 0 ) { 15 | say "usage : ./$0"; 16 | exit 1; 17 | } 18 | 19 | eval { 20 | my $app = app->new(get_cfg()); 21 | my $users = $app->get_all_users(); 22 | dump($users); 23 | }; 24 | 25 | 26 | if( $@ ) { 27 | say q{Une erreur est survenue. } . $@; 28 | } 29 | -------------------------------------------------------------------------------- /cli/toggle_admin.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use lib './lib/'; 11 | use configuration ':all'; 12 | use encryption ':all'; 13 | use app; 14 | 15 | if( @ARGV != 1 ) { 16 | say "usage : ./$0 login"; 17 | exit 1; 18 | } 19 | 20 | my $login = $ARGV[0]; 21 | 22 | eval { 23 | my $app = app->new(get_cfg()); 24 | $app->toggle_admin($login); 25 | }; 26 | 27 | if( $@ ) { 28 | say q{Une erreur est survenue. } . $@; 29 | } 30 | -------------------------------------------------------------------------------- /cli/user_add.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use lib './lib/'; 11 | use configuration ':all'; 12 | use encryption ':all'; 13 | use app; 14 | 15 | if( @ARGV != 2 ) { 16 | say "usage : ./$0 login passwd"; 17 | exit 1; 18 | } 19 | 20 | my ($login, $passwd) = ($ARGV[0], $ARGV[1]); 21 | 22 | eval { 23 | my $app = app->new(get_cfg()); 24 | $app->register_user($login, encrypt($passwd)); 25 | }; 26 | 27 | if( $@ ) { 28 | say q{Une erreur est survenue. } . $@; 29 | } 30 | -------------------------------------------------------------------------------- /cli/user_auth.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use lib './lib/'; 11 | use configuration ':all'; 12 | use encryption ':all'; 13 | use app; 14 | 15 | if( @ARGV != 0 && @ARGV != 2 ) { 16 | say "usage : ./$0 [ login passwd ]"; 17 | exit 1; 18 | } 19 | 20 | my ($login, $passwd) = qw/test test/; 21 | ($login, $passwd) = ($ARGV[0], $ARGV[1]) if ( @ARGV == 2 ); 22 | 23 | eval { 24 | my $app = app->new(get_cfg()); 25 | my $user = $app->auth($login, encrypt($passwd)); 26 | dump($user); 27 | if($$user{admin}) { say "ADMIN" } 28 | else { say "NOT ADMIN" } 29 | }; 30 | 31 | if( $@ ) { 32 | say q{Une erreur est survenue. } . $@; 33 | } 34 | -------------------------------------------------------------------------------- /cli/user_del.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use lib './lib/'; 11 | use configuration ':all'; 12 | use app; 13 | 14 | if( @ARGV != 1 ) { 15 | say "usage : ./$0 user"; 16 | exit 1; 17 | } 18 | 19 | my $login = $ARGV[0]; 20 | 21 | eval { 22 | my $app = app->new(get_cfg()); 23 | $app->delete_user($login); 24 | }; 25 | 26 | if( $@ ) { 27 | say q{Une erreur est survenue. } . $@; 28 | } 29 | -------------------------------------------------------------------------------- /cli/user_update_passwd.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use lib './lib/'; 11 | use configuration ':all'; 12 | use encryption ':all'; 13 | use app; 14 | 15 | if( @ARGV != 2 ) { 16 | say "usage : ./$0 userid newpasswd"; 17 | exit 1; 18 | } 19 | 20 | my ($login, $passwd) = ($ARGV[0], $ARGV[1]); 21 | 22 | eval { 23 | my $app = app->new(get_cfg()); 24 | $app->update_passwd($login, encrypt($passwd)); 25 | }; 26 | 27 | if( $@ ) { 28 | say q{Une erreur est survenue. } . $@; 29 | } 30 | -------------------------------------------------------------------------------- /cli/users_del.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use lib './lib/'; 11 | use configuration ':all'; 12 | use app; 13 | 14 | if( @ARGV != 0 ) { 15 | say "usage : echo user | ./$0"; 16 | exit 1; 17 | } 18 | 19 | eval { 20 | my $app = app->new(get_cfg()); 21 | 22 | while (<>) { 23 | chomp; 24 | say "delete user: $_"; 25 | $app->delete_user($_); 26 | } 27 | }; 28 | 29 | if( $@ ) { 30 | say q{Une erreur est survenue. } . $@; 31 | } 32 | -------------------------------------------------------------------------------- /cli/zone_add.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use lib './lib/'; 9 | use configuration ':all'; 10 | use encryption ':all'; 11 | use app; 12 | 13 | if( @ARGV != 2 ) { 14 | say "usage : ./$0 login ndd "; 15 | exit 1; 16 | } 17 | 18 | my ($login, $dom) = ($ARGV[0], $ARGV[1]); 19 | 20 | eval { 21 | my $app = app->new(get_cfg()); 22 | $app->add_domain( $login, $dom ); 23 | my $zone = $app->get_domain($dom); 24 | say $zone->output(); 25 | }; 26 | 27 | if( $@ ) { 28 | say q{Une erreur est survenue. } . $@; 29 | } 30 | -------------------------------------------------------------------------------- /cli/zone_del.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use lib './lib/'; 9 | use configuration ':all'; 10 | use encryption ':all'; 11 | use app; 12 | 13 | if( @ARGV != 1 ) { 14 | say "usage : ./$0 ndd "; 15 | exit 1; 16 | } 17 | 18 | my $dom = $ARGV[0]; 19 | 20 | eval { 21 | my $app = app->new(get_cfg()); 22 | $app->delete_domain( $dom ); 23 | }; 24 | 25 | if( $@ ) { 26 | say q{Une erreur est survenue. } . $@; 27 | } 28 | -------------------------------------------------------------------------------- /cli/zones_del.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use autodie; 4 | use utf8; 5 | use open qw/:std :utf8/; 6 | use Modern::Perl; 7 | 8 | use lib './lib/'; 9 | use configuration ':all'; 10 | use encryption ':all'; 11 | use app; 12 | 13 | if( @ARGV != 0 ) { 14 | say "usage : echo ndd | ./$0"; 15 | exit 1; 16 | } 17 | 18 | eval { 19 | my $app = app->new(get_cfg()); 20 | 21 | while (<>) { 22 | chomp ; 23 | say "zone to delete: $_"; 24 | 25 | $app->delete_domain( $_ ); 26 | } 27 | }; 28 | 29 | if( $@ ) { 30 | say q{Une erreur est survenue. } . $@; 31 | } 32 | -------------------------------------------------------------------------------- /conf/config.yml: -------------------------------------------------------------------------------- 1 | # TLD 2 | # Must contains the first "." 3 | tld: 4 | - '.netlib.re' 5 | - '.autre.tld' 6 | - '.codelib.re' 7 | 8 | tmpdir: file:///media/fast/ 9 | 10 | # uncomment it if you want to only test the application views 11 | #isviewtest: true 12 | 13 | database: 14 | sgbd: mysql # other options : see DBI module 15 | name: dnsmanager 16 | host: localhost 17 | port: 3306 18 | user: dnsmanageruser 19 | passwd: "my-not-so-dummy-password" 20 | 21 | primarydnsserver: 22 | app: bind9 23 | dnsslavekey: demokey 24 | zonedir: ssh://root@localhost:22/var/named/zones/rndczones/ 25 | domain: 26 | user: root 27 | port: 22 28 | host: web.loc 29 | name: web.loc 30 | v4: 192.168.0.60 # optional 31 | #v6: ::1 # optional 32 | 33 | secondarydnsserver: 34 | - app: nsd 35 | cfg: ssh://root@nsdl:22/etc/nsd/nsd.conf 36 | zonedir: ssh://root@nsdl:22/etc/nsd/ 37 | domain: 38 | name: nsdl 39 | v4: 192.168.0.61 # optional 40 | #v6: ::1 # optional 41 | 42 | # - app: nsd 43 | # cfg: ssh://dnsmanager@host3:2222/etc/nsd3/nsd.conf 44 | # domain: 45 | # name: third.example.com 46 | # v4: 192.0.2.3 # optional 47 | # v6: 2001:db8::3 # optional 48 | -------------------------------------------------------------------------------- /conf/reserved.zone: -------------------------------------------------------------------------------- 1 | www 2 | -------------------------------------------------------------------------------- /config.yml: -------------------------------------------------------------------------------- 1 | # This is the main configuration file of your Dancer2 app 2 | # env-related settings should go to environments/$env.yml 3 | # all the settings in this file will be loaded at Dancer's startup. 4 | 5 | # Your application's name 6 | appname: "MyWeb::App" 7 | 8 | # The default layout to use for your application (located in 9 | # views/layouts/main.tt) 10 | layout: "main" 11 | 12 | # when the charset is set to UTF-8 Dancer2 will handle for you 13 | # all the magic of encoding and decoding. You should not care 14 | # about unicode within your app when this setting is set (recommended). 15 | charset: "UTF-8" 16 | 17 | # template engine 18 | # simple: default and very basic template engine 19 | # template_toolkit: TT 20 | 21 | #template: "simple" 22 | 23 | template: "template_toolkit" 24 | engines: 25 | template: 26 | template_toolkit: 27 | start_tag: '<%' 28 | end_tag: '%>' 29 | 30 | #session: "Storable" 31 | session: "YAML" 32 | 33 | 34 | logging: "console" 35 | -------------------------------------------------------------------------------- /cpanfile: -------------------------------------------------------------------------------- 1 | requires "Dancer2" => "0.161000"; 2 | 3 | recommends "YAML" => "0"; 4 | recommends "URL::Encode::XS" => "0"; 5 | recommends "CGI::Deurl::XS" => "0"; 6 | recommends "HTTP::Parser::XS" => "0"; 7 | 8 | on "test" => sub { 9 | requires "Test::More" => "0"; 10 | requires "HTTP::Request::Common" => "0"; 11 | }; 12 | -------------------------------------------------------------------------------- /environments/development.yml: -------------------------------------------------------------------------------- 1 | # configuration file for development environment 2 | 3 | # the logger engine to use 4 | # console: log messages to STDOUT (your console where you started the 5 | # application server) 6 | # file: log message to a file in log/ 7 | logger: "console" 8 | 9 | # the log level for this environment 10 | # core is the lowest, it shows Dancer2's core log messages as well as yours 11 | # (debug, info, warning and error) 12 | log: "core" 13 | 14 | # should Dancer2 consider warnings as critical errors? 15 | warnings: 1 16 | 17 | # should Dancer2 show a stacktrace when an error is caught? 18 | # if set to yes, public/500.html will be ignored and either 19 | # views/500.tt, 'error_template' template, or a default error template will be used. 20 | show_errors: 1 21 | 22 | # print the banner 23 | startup_info: 1 24 | -------------------------------------------------------------------------------- /environments/production.yml: -------------------------------------------------------------------------------- 1 | # configuration file for production environment 2 | 3 | # only log warning and error messsages 4 | log: "warning" 5 | 6 | # log message to a file in logs/ 7 | logger: "file" 8 | 9 | # don't consider warnings critical 10 | warnings: 0 11 | 12 | # hide errors 13 | show_errors: 0 14 | 15 | # disable server tokens in production environments 16 | no_server_tokens: 1 17 | -------------------------------------------------------------------------------- /init/dependancies.ubuntu: -------------------------------------------------------------------------------- 1 | cpanminus 2 | libssl1.0.0 3 | libssl-dev 4 | make 5 | gcc 6 | libdbi-perl 7 | libdbd-mysql-perl 8 | mysql-server 9 | -------------------------------------------------------------------------------- /init/deploiement.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | CDIR=`dirname $0` 4 | 5 | usage() { 6 | echo "usage : $0 cmd 7 | 8 | cmd in : 9 | 10 | installdep : install packages from your distribution 11 | perlmodules : install cpan modules 12 | dbinstall : install the database with a password provided by \$PATH 13 | dbreinstall : reinstall the database with a password provided by \$PATH 14 | all : do the full installation 15 | " 2>&1 16 | 17 | exit 1 18 | } 19 | 20 | if [ $# -lt 1 ] ; then 21 | usage 22 | fi 23 | 24 | # install required applications 25 | installdep_f() { 26 | sudo apt-get update 27 | cat ${CDIR}/dependancies.ubuntu | xargs sudo apt-get install 28 | } 29 | 30 | # install Perl modules 31 | perlmodules_f() { 32 | cpanm --local-lib=~/perl5 local::lib && eval $(perl -I ~/perl5/lib/perl5/ -Mlocal::lib) 33 | cat ${CDIR}/perlmodules | xargs cpanm 34 | } 35 | 36 | # SQL 37 | dbinstall_core_f() { 38 | mysql -u root --password="${PASS}" < ${CDIR}/sql/init-create-user.sql 39 | mysql -u root --password="${PASS}" < ${CDIR}/sql/init-create-db.sql 40 | mysql -u root --password="${PASS}" < ${CDIR}/sql/init-grant-user.sql 41 | mysql -u root --password="${PASS}" < ${CDIR}/sql/init-tables.sql 42 | } 43 | 44 | dbinstall_f() { 45 | PASS=${PASS-notsodummy} 46 | dbinstall_core_f 47 | } 48 | 49 | dbreinstall_f() { 50 | PASS=${PASS-notsodummy} 51 | mysql -u root --password="${PASS}" < ${CDIR}/sql/remove-db.sql 52 | mysql -u root --password="${PASS}" < ${CDIR}/sql/remove-user.sql 53 | dbinstall_core_f 54 | } 55 | 56 | case $1 in 57 | installdep) installdep_f ;; 58 | perlmodules) perlmodules_f ;; 59 | dbinstall) dbinstall_f ;; 60 | dbreinstall) dbreinstall_f ;; 61 | all) 62 | installdep_f 63 | perlmodules_f 64 | dbinstall_f 65 | ;; 66 | *) usage ;; 67 | esac 68 | -------------------------------------------------------------------------------- /init/perlmodules: -------------------------------------------------------------------------------- 1 | Dancer2 2 | Dancer2::Plugin::Deferred 3 | YAML::XS 4 | Data::Dump 5 | File::Basename 6 | Find::Lib 7 | Test::More 8 | String::ShellQuote 9 | Data::Structure::Util 10 | Modern::Perl 11 | Config::Simple 12 | Crypt::Digest::SHA256 13 | Dancer::Session::Storable 14 | ExtUtils::MakeMaker 15 | Storable 16 | Plack::Handler::FCGI 17 | Plack::Runner 18 | DNS::ZoneParse 19 | Net::OpenSSH 20 | Template 21 | Net::SSH 22 | Date::Calc 23 | Data::Validate::IP 24 | -------------------------------------------------------------------------------- /init/sql/init-create-db.sql: -------------------------------------------------------------------------------- 1 | CREATE DATABASE IF NOT EXISTS dnsmanager; 2 | -------------------------------------------------------------------------------- /init/sql/init-create-user.sql: -------------------------------------------------------------------------------- 1 | CREATE USER "dnsmanageruser"@'localhost'; 2 | set password for "dnsmanageruser"@'localhost' = password('my-not-so-dummy-password'); 3 | -------------------------------------------------------------------------------- /init/sql/init-grant-user.sql: -------------------------------------------------------------------------------- 1 | grant all on dnsmanager.* to "dnsmanageruser"@'localhost'; 2 | -------------------------------------------------------------------------------- /init/sql/init-tables.sql: -------------------------------------------------------------------------------- 1 | USE dnsmanager; 2 | 3 | CREATE TABLE IF NOT EXISTS user ( 4 | login varchar(50) NOT NULL, 5 | passwd varchar(100) DEFAULT NULL, 6 | admin tinyint(1) DEFAULT 0, 7 | PRIMARY KEY (login) 8 | ) ENGINE=InnoDB; 9 | 10 | CREATE TABLE IF NOT EXISTS domain ( 11 | domain varchar(100) NOT NULL, 12 | login varchar(50) NOT NULL, 13 | activated tinyint(1) NOT NULL DEFAULT 0, 14 | PRIMARY KEY (domain) 15 | ) ENGINE=InnoDB; 16 | 17 | CREATE TABLE IF NOT EXISTS tld ( 18 | tld varchar(50) NOT NULL, 19 | activated tinyint(1) NOT NULL DEFAULT 0, 20 | PRIMARY KEY (tld) 21 | ) ENGINE=InnoDB; 22 | -------------------------------------------------------------------------------- /init/sql/remove-db.sql: -------------------------------------------------------------------------------- 1 | DROP DATABASE dnsmanager; 2 | -------------------------------------------------------------------------------- /init/sql/remove-user.sql: -------------------------------------------------------------------------------- 1 | DROP USER "dnsmanageruser"@'localhost'; 2 | -------------------------------------------------------------------------------- /init/tpl.zone: -------------------------------------------------------------------------------- 1 | ; 2 | ; Database file CHANGEMEORIGIN for CHANGEMEORIGIN. zone. 3 | ; Zone version: 2014030200 4 | ; 5 | 6 | $ORIGIN CHANGEMEORIGIN. 7 | 8 | 9 | $TTL 3600 10 | @ 3600 IN SOA ns0.netlib.re. postmaster.netlib.re. ( 11 | 2014030200 ; serial number 12 | 3600 ; refresh 13 | 600 ; retry 14 | 86400 ; expire 15 | 600 ; minimum TTL 16 | ) 17 | ; 18 | ; Zone NS Records 19 | 20 | @ IN NS ns0.arn-fai.net. 21 | -------------------------------------------------------------------------------- /lib/MyWeb/App.pm: -------------------------------------------------------------------------------- 1 | package MyWeb::App; 2 | 3 | use v5.14; 4 | use strict; 5 | use warnings; 6 | use utf8; 7 | use open qw/:std :utf8/; 8 | 9 | use Dancer2; 10 | use Dancer2::Plugin::Deferred; 11 | use File::Basename; 12 | #use Storable qw( freeze thaw ); 13 | #$Storable::Deparse = true; 14 | #$Storable::Eval=true; 15 | 16 | use YAML::XS; 17 | use configuration ':all'; 18 | use util ':all'; 19 | 20 | use rt::root ':all'; 21 | use rt::domain ':all'; 22 | use rt::user ':all'; 23 | use rt::admin ':all'; 24 | 25 | use rt::rootfake ':all'; 26 | use rt::domainfake ':all'; 27 | use rt::userfake ':all'; 28 | use rt::adminfake ':all'; 29 | use app; 30 | 31 | our $isviewtest = is_view_test(get_cfg()); 32 | 33 | our $VERSION = '0.1'; 34 | 35 | get '/info' => sub { 36 | my $str = "This is : " . config->{appname} . "
"; 37 | $str .= "environment : " . config->{environment} . "
"; 38 | }; 39 | 40 | sub what_is_next { 41 | my ($res) = @_; 42 | 43 | if($$res{sessiondestroy}) { 44 | app->destroy_session; 45 | } 46 | 47 | for(keys %{$$res{deferred}}) { 48 | deferred $_ => $$res{deferred}{$_}; 49 | } 50 | 51 | for(keys %{$$res{addsession}}) { 52 | say "ajout de la session $_ : $$res{addsession}{$_}"; 53 | session $_ => $$res{addsession}{$_}; 54 | } 55 | 56 | for(keys %{$$res{delsession}}) { 57 | session $_ => undef; 58 | } 59 | 60 | if(exists $$res{route}) { 61 | redirect $$res{route}; 62 | } 63 | elsif(exists $$res{template}) { 64 | template $$res{template} => $$res{params}; 65 | } else { 66 | redirect '/'; 67 | } 68 | } 69 | 70 | sub get_param { 71 | my $param_values; 72 | for(@_) { 73 | $$param_values{$_} = param "$_"; 74 | } 75 | $param_values; 76 | } 77 | 78 | sub get_request { 79 | my $request_values; 80 | for(@_) { 81 | if(/^address$/) { $$request_values{$_} = request->address; } 82 | elsif(/^referer$/) { $$request_values{$_} = request->referer; } 83 | } 84 | $request_values; 85 | } 86 | 87 | sub get_session { 88 | my $session_values; 89 | for(@_) { 90 | $$session_values{$_} = session "$_"; 91 | } 92 | $session_values; 93 | } 94 | 95 | get '/' => sub { 96 | return what_is_next rt_root_fake if $isviewtest; 97 | what_is_next rt_root 98 | get_session( qw/login passwd/ ); 99 | }; 100 | 101 | prefix '/domain' => sub { 102 | 103 | post '/updateraw/:domain' => sub { 104 | return what_is_next rt_dom_updateraw_fake 105 | "" , "" , get_request( qw/address referer/ ) 106 | if $isviewtest; 107 | what_is_next rt_dom_updateraw 108 | get_session( qw/login passwd/ ) 109 | , get_param( qw/domain zoneupdated/) 110 | , get_request( qw/address referer/ ); 111 | }; 112 | 113 | post '/update/:domain' => sub { 114 | return what_is_next rt_dom_add_entry_fake 115 | "" , "", get_request( qw/referer/ ) 116 | if $isviewtest; 117 | what_is_next rt_dom_add_entry 118 | get_session( qw/login passwd/ ) 119 | , get_param( qw/domain type name ttl priority weight port rdata/ ) 120 | , get_request( qw/referer/ ); 121 | }; 122 | 123 | get '/details/:domain' => sub { 124 | return what_is_next rt_dom_details_fake "" 125 | , get_param( qw/expert/ ) 126 | , get_request( qw/address referer/ ) if $isviewtest; 127 | what_is_next rt_dom_details 128 | get_session( qw/login passwd/ ) 129 | , get_param( qw/domain expert/ ) 130 | , get_request( qw/address referer/ ); 131 | }; 132 | 133 | post '/add/' => sub { 134 | return what_is_next rt_dom_add_fake if $isviewtest; 135 | what_is_next rt_dom_add 136 | get_session( qw/login passwd/ ) 137 | , get_param( qw/domain tld/ ); 138 | }; 139 | 140 | get '/del/:domain' => sub { 141 | return what_is_next rt_dom_del_fake 142 | "" , "", get_request( qw/address referer/ ) 143 | if $isviewtest; 144 | what_is_next rt_dom_del 145 | get_session( qw/login passwd/ ) 146 | , get_param( qw/domain/ ) 147 | , get_request( qw/address referer/ ); 148 | }; 149 | 150 | get '/del/:domain/:name/:ttl/:type/:priority/:rdata' => sub { 151 | return what_is_next rt_dom_del_entry_fake 152 | "" , "", get_request( qw/address referer/ ) 153 | if $isviewtest; 154 | what_is_next rt_dom_del_entry 155 | get_session( qw/login passwd/ ) 156 | , get_param( qw/domain name ttl type priority rdata/ ) 157 | , get_request( qw/address referer/ ); 158 | }; 159 | 160 | get '/del/:domain/:name/:ttl/:type/:priority/:weight/:port/:rdata' => sub { 161 | return what_is_next rt_dom_del_entry_fake 162 | "" , "", get_request( qw/address referer/ ) 163 | if $isviewtest; 164 | what_is_next rt_dom_del_entry 165 | get_session( qw/login passwd/ ) 166 | , get_param( qw/domain name ttl type priority weight port rdata/ ) 167 | , get_request( qw/address referer/ ); 168 | }; 169 | 170 | get '/del/:domain/:name/:ttl/:type/:rdata' => sub { 171 | return what_is_next rt_dom_del_entry_fake 172 | "" , "", get_request( qw/address referer/ ) 173 | if $isviewtest; 174 | what_is_next rt_dom_del_entry 175 | get_session( qw/login passwd/ ) 176 | , get_param( qw/domain name type ttl rdata/ ) 177 | , get_request( qw/address referer/ ); 178 | }; 179 | 180 | post '/mod/:domain' => sub { 181 | return what_is_next rt_dom_mod_entry_fake 182 | "" , "", get_request( qw/address referer/ ) 183 | if $isviewtest; 184 | what_is_next rt_dom_mod_entry 185 | get_session( qw/login passwd/ ) 186 | , get_param( qw/domain type 187 | oldpriority oldname oldttl oldrdata oldweight oldport 188 | newpriority newname newttl newrdata newweight newport/ ) 189 | , get_request( qw/address referer/ ); 190 | }; 191 | 192 | get '/cliup/:login/:pass/:domain/:name/:type/:rdata' => sub { 193 | return what_is_next rt_dom_cli_autoupdate_fake if $isviewtest; 194 | what_is_next rt_dom_cli_autoupdate 195 | get_session( qw// ) 196 | , get_param( qw/login pass domain name type rdata/ ); 197 | }; 198 | 199 | get '/cli/:login/:pass/:domain/:name/:type/:rdata/:ttl/:ip' => sub { 200 | return what_is_next rt_dom_cli_mod_entry_fake if $isviewtest; 201 | what_is_next rt_dom_cli_mod_entry 202 | get_session( qw// ) 203 | , get_param( qw/login pass domain name type rdata ttl ip/ ); 204 | }; 205 | }; 206 | 207 | any ['get', 'post'] => '/admin' => sub { 208 | return what_is_next rt_admin_fake if $isviewtest; 209 | what_is_next rt_admin 210 | get_session( qw/login passwd/ ); 211 | }; 212 | 213 | prefix '/user' => sub { 214 | 215 | get '/home' => sub { 216 | return what_is_next rt_user_home_fake if $isviewtest; 217 | what_is_next rt_user_home 218 | get_session( qw/login passwd/ ) 219 | , get_param( qw// ) 220 | , get_request( qw// ); 221 | }; 222 | 223 | get '/logout' => sub { 224 | app->destroy_session; 225 | redirect '/'; 226 | }; 227 | 228 | get '/del/:user' => sub { 229 | return what_is_next rt_user_del_fake 230 | "" , "", get_request( qw/address referer/ ) 231 | if $isviewtest; 232 | what_is_next rt_user_del 233 | get_session( qw/login passwd/ ) 234 | , get_param( qw/user/ ) 235 | , get_request( qw/referer/ ); 236 | }; 237 | 238 | # add a user => registration 239 | post '/add/' => sub { 240 | return what_is_next rt_user_add_fake if $isviewtest; 241 | what_is_next rt_user_add 242 | get_session( qw// ) 243 | , get_param( qw/login password password2/ ) 244 | , get_request( qw// ); 245 | }; 246 | 247 | get '/subscribe' => sub { 248 | return what_is_next rt_user_subscribe_fake if $isviewtest; 249 | what_is_next rt_user_subscribe 250 | get_session( qw/login/ ); 251 | }; 252 | 253 | post '/changepasswd' => sub { 254 | return what_is_next rt_user_changepasswd_fake if $isviewtest; 255 | what_is_next rt_user_changepasswd 256 | get_session( qw/login/ ) 257 | , get_param( qw/password/ ); 258 | }; 259 | 260 | get '/toggleadmin/:user' => sub { 261 | return what_is_next rt_user_toggleadmin_fake 262 | "" , "", get_request( qw/referer/ ) 263 | if $isviewtest; 264 | what_is_next rt_user_toggleadmin 265 | get_session( qw/login passwd/ ) 266 | , get_param( qw/user/ ) 267 | , get_request( qw/referer/ ); 268 | }; 269 | 270 | post '/login' => sub { 271 | return what_is_next rt_user_login_fake 272 | "" , "", get_request( qw/referer/ ) 273 | if $isviewtest; 274 | what_is_next rt_user_login 275 | get_session( qw/login/ ) 276 | , get_param( qw/login password/ ) 277 | , get_request( qw/referer/ ); 278 | }; 279 | }; 280 | 281 | true; 282 | -------------------------------------------------------------------------------- /lib/README.markdown: -------------------------------------------------------------------------------- 1 | # TODO 2 | 3 | * redesign zone.pm 4 | * moar testz !!! Kitten will die !!! 5 | * décider de la procédure d'init de app, pas besoin de tout charger à chaque fois 6 | * comment passer des informations à chaque interface ? 7 | * est-ce que ce qui est fait est pertinent ? 8 | -------------------------------------------------------------------------------- /lib/app.pm: -------------------------------------------------------------------------------- 1 | package app; 2 | use v5.14; 3 | use Moo; 4 | 5 | use db; 6 | use zone; 7 | use configuration ':all'; 8 | 9 | has db => ( is => 'rw', builder => '_void'); 10 | 11 | has [qw/tld tmpdir database primarydnsserver secondarydnsserver/] 12 | => qw/is ro required 1/; 13 | 14 | sub _void { my $x = ''; \$x; } 15 | 16 | sub BUILD { 17 | my ($self) = @_; 18 | $$self{db} = db->new(data => $self); 19 | 20 | my $tmpdir = get_tmpdir_from_uri($$self{tmpdir}); 21 | -f $tmpdir || qx/mkdir -p $tmpdir/; 22 | 23 | my $db = $$self{database}; 24 | unless(exists $$db{sgbd} && exists $$db{name} 25 | && exists $$db{host} && exists $$db{port} 26 | && exists $$db{user} && exists $$db{passwd}) 27 | { 28 | die "Unable to connect to the database.\n" 29 | . "Check the existance of theses parameters in the config file :\n" 30 | . "\tsgbd name host port user passwd"; 31 | } 32 | } 33 | 34 | # USER 35 | 36 | sub auth { 37 | my ($self, $login, $passwd) = @_; 38 | $self->db->auth($login, $passwd) 39 | } 40 | 41 | sub update_passwd { 42 | my ($self, $login, $newpass) = @_; 43 | $self->db->update_passwd($login, $newpass) 44 | } 45 | 46 | sub register_user { 47 | my ($self, $login, $passwd) = @_; 48 | $self->db->register_user($login, $passwd) 49 | } 50 | 51 | sub toggle_admin { 52 | my ($self, $login) = @_; 53 | $self->db->toggle_admin($login) 54 | } 55 | 56 | sub delete_user { 57 | my ($self, $login) = @_; 58 | $self->db->delete_user($login) 59 | } 60 | 61 | sub get_all_users { 62 | my ($self) = @_; 63 | $self->db->get_all_users 64 | } 65 | 66 | sub is_owning_domain { 67 | my ($self, $login, $domain) = @_; 68 | $self->db->is_owning_domain($login, $domain) 69 | } 70 | 71 | # DOMAIN 72 | 73 | sub get_zone { 74 | my ($self, $domain) = @_; 75 | 76 | # say ""; 77 | # say "GET ZONE"; 78 | # say ""; 79 | # say ""; 80 | # say "domain $domain"; 81 | # say "tmpdir $$self{tmpdir}"; 82 | # say "tld $$self{tld}"; 83 | 84 | zone->new( domain => $domain 85 | , tmpdir => $$self{tmpdir} 86 | , tld => $$self{tld} 87 | , primarydnsserver => $$self{primarydnsserver} 88 | , secondarydnsserver => $$self{secondarydnsserver} 89 | , slavedzones => $self->get_all_domains() 90 | ) 91 | } 92 | 93 | sub add_domain { 94 | my ($self, $login, $domain) = @_; 95 | $self->db->add_domain($login, $domain); 96 | $self->get_zone($domain)->addzone() 97 | } 98 | 99 | sub delete_domain { 100 | my ($self, $domain) = @_; 101 | $self->db->delete_domain($domain); 102 | $self->get_zone($domain)->del() 103 | } 104 | 105 | sub get_domains { 106 | my ($self, $login) = @_; 107 | $self->db->get_domains($login) 108 | } 109 | 110 | sub get_all_domains { 111 | my ($self) = @_; 112 | $self->db->get_all_domains 113 | } 114 | 115 | sub disconnect { 116 | my ($self) = @_; 117 | $self->db->disconnect 118 | } 119 | 120 | 1; 121 | -------------------------------------------------------------------------------- /lib/configuration.pm: -------------------------------------------------------------------------------- 1 | package configuration; 2 | use YAML::XS; 3 | use URI; 4 | 5 | use fileutil ':all'; 6 | use Exporter 'import'; 7 | # what we want to export eventually 8 | our @EXPORT_OK = qw/ 9 | get_cfg is_reserved 10 | get_zonedir_from_cfg 11 | get_dnsslavekey_from_cfg 12 | get_v4_from_name 13 | get_v6_from_name 14 | get_v4_from_cfg 15 | get_v6_from_cfg 16 | get_host_from_cfg 17 | get_user_from_cfg 18 | get_port_from_cfg 19 | get_tmpdir_from_uri 20 | is_view_test 21 | /; 22 | 23 | # bundle of exports (tags) 24 | our %EXPORT_TAGS = ( all => [qw/ 25 | get_cfg is_reserved 26 | get_zonedir_from_cfg 27 | get_dnsslavekey_from_cfg 28 | get_v4_from_name 29 | get_v6_from_name 30 | get_v4_from_cfg 31 | get_v6_from_cfg 32 | get_host_from_cfg 33 | get_user_from_cfg 34 | get_port_from_cfg 35 | get_tmpdir_from_uri 36 | is_view_test 37 | /] ); 38 | 39 | sub is_conf_file { 40 | my $f = shift; 41 | 42 | unless(-f $f) { 43 | die "$f : not a file"; 44 | } 45 | 46 | unless(-r $f) { 47 | die "$f : not readable"; 48 | } 49 | 50 | unless(-T $f) { 51 | die "$f : not plain text"; 52 | } 53 | } 54 | 55 | sub get_cfg { 56 | my ($cfgdir) = @_; 57 | 58 | $cfgdir //= './conf/'; 59 | my $f = "$cfgdir/config.yml"; 60 | 61 | is_conf_file $f; 62 | YAML::XS::LoadFile($f) 63 | } 64 | 65 | sub is_reserved { 66 | my ($domain) = @_; 67 | 68 | my $filename = 'conf/reserved.zone'; 69 | is_conf_file $filename; 70 | 71 | my $data = read_file $filename; 72 | $data =~ /^$domain$/m; 73 | } 74 | 75 | # TODO : tests 76 | sub get_v6_from_name { 77 | my $name = shift; 78 | 79 | my $val = qx/host -t AAAA $name | grep -oE '[^[:space:]]+\$'/; 80 | chomp $val; 81 | 82 | #die q{There is no available v6. TODO.} if($val =~ 'NXDOMAIN'); 83 | return undef if($val =~ 'NXDOMAIN'); 84 | 85 | $val 86 | } 87 | 88 | sub get_v4_from_name { 89 | my $name = shift; 90 | 91 | my $val = qx/host -t A $name | grep -oE '[^[:space:]]+\$'/; 92 | chomp $val; 93 | 94 | die q{There is no available v4. TODO.} if($val =~ 'NXDOMAIN'); 95 | 96 | $val 97 | } 98 | 99 | sub get_v6_from_cfg { 100 | my $cfg = shift; 101 | $$cfg{domain}{v6} // get_v6_from_name($$cfg{domain}{name}) 102 | } 103 | 104 | sub get_v4_from_cfg { 105 | my $cfg = shift; 106 | $$cfg{domain}{v4} // get_v4_from_name($$cfg{domain}{name}) 107 | } 108 | 109 | sub get_tmpdir_from_uri { 110 | my $tmpdir = shift; 111 | unless($tmpdir) { 112 | die 'There is no tmpdir'; 113 | } 114 | URI->new($tmpdir)->path; 115 | } 116 | 117 | sub get_zonedir_from_cfg { 118 | my $cfg = shift; 119 | unless($$cfg{zonedir}) { 120 | die 'For now, the only way to get the zone path is to setup zonedir ' 121 | . 'in the primaryserver configuration in config.yml.'; 122 | } 123 | URI->new($$cfg{zonedir})->path; 124 | } 125 | 126 | # in production by default, get the isviewtest value elsewise 127 | sub is_view_test { 128 | my $cfg = shift; 129 | $$cfg{isviewtest} 130 | } 131 | 132 | sub get_host_from_cfg { 133 | my $cfg = shift; 134 | 135 | if($$cfg{zonedir}) { 136 | my $u = URI->new($$cfg{zonedir}); 137 | return $u->host; 138 | } 139 | elsif($$cfg{domain}{name}) { 140 | return $$cfg{domain}{name}; 141 | } 142 | 143 | die "Impossible to get the host from the configuration." 144 | } 145 | 146 | sub get_dnsslavekey_from_cfg { 147 | my $cfg = shift; 148 | 149 | if($$cfg{dnsslavekey}) { 150 | return $$cfg{dnsslavekey}; 151 | } 152 | 153 | die "Impossible to get the dns slave key from the configuration." 154 | } 155 | 156 | sub get_user_from_cfg { 157 | my $cfg = shift; 158 | 159 | if($$cfg{zonedir}) { 160 | my $u = URI->new($$cfg{zonedir}); 161 | return $u->user; 162 | } 163 | elsif($$cfg{domain}{user}) { 164 | return $$cfg{domain}{user}; 165 | } 166 | 167 | die "Impossible to get the user from the configuration." 168 | } 169 | 170 | sub get_port_from_cfg { 171 | my $cfg = shift; 172 | 173 | if($$cfg{zonedir}) { 174 | my $u = URI->new($$cfg{zonedir}); 175 | return $u->port; 176 | } 177 | elsif($$cfg{domain}{port}) { 178 | return $$cfg{domain}{port}; 179 | } 180 | 181 | die "Impossible to get the port from the configuration." 182 | } 183 | 184 | 1; 185 | -------------------------------------------------------------------------------- /lib/copycat.pm: -------------------------------------------------------------------------------- 1 | package copycat; 2 | use v5.14; 3 | 4 | use File::Copy; 5 | use URI; 6 | use Net::OpenSSH; 7 | 8 | use Exporter 'import'; 9 | # what we want to export eventually 10 | our @EXPORT_OK = qw/copycat/; 11 | 12 | # bundle of exports (tags) 13 | our %EXPORT_TAGS = ( all => [qw/copycat/] ); 14 | 15 | sub _cp { 16 | my ($src, $dest) = @_; 17 | say "cp $src $dest"; 18 | File::Copy::copy($src, $dest) or die "Copy failed: $! ($src -> $dest)"; 19 | } 20 | 21 | sub _scp_put { 22 | my ($co, $src, $dest) = @_; 23 | 24 | my $ssh = Net::OpenSSH->new($co); 25 | say "scp put $src $dest"; 26 | $ssh->scp_put($src, $dest) or die "scp failed: " . $ssh->error; 27 | undef $ssh; 28 | } 29 | 30 | sub _scp_get { 31 | my ($co, $src, $dest) = @_; 32 | 33 | my $ssh = Net::OpenSSH->new($co); 34 | say "scp get $src $dest"; 35 | $ssh->scp_get($src, $dest) or die "scp failed: " . $ssh->error; 36 | undef $ssh; 37 | } 38 | 39 | # SUPPORT 40 | # local to local 41 | # distant to local 42 | # local to distant 43 | 44 | sub copycat { 45 | my ($source, $destination) = @_; 46 | 47 | # TODO if it's not URI 48 | 49 | my $src = URI->new($source); 50 | my $dest = URI->new($destination); 51 | 52 | if($src->scheme eq 'file' && $dest->scheme eq 'file') { 53 | _cp $src->path, $dest->path; 54 | } 55 | elsif($src->scheme eq 'ssh' && $dest->scheme eq 'file') { 56 | 57 | my $co = $src->userinfo . '@' . $src->host . ':' . $src->port; 58 | _scp_get $co, $src->path, $dest->path; 59 | 60 | } 61 | elsif($src->scheme eq 'file' && $dest->scheme eq 'ssh') { 62 | 63 | my $co = $dest->userinfo . '@' . $dest->host . ':' . $dest->port; 64 | _scp_put $co, $src->path, $dest->path; 65 | 66 | } 67 | else { 68 | 69 | die "CopyCat : wrong arguments"; 70 | } 71 | 72 | } 73 | 74 | 1; 75 | -------------------------------------------------------------------------------- /lib/db.pm: -------------------------------------------------------------------------------- 1 | package db; 2 | use v5.14; 3 | use Moo; 4 | 5 | use Modern::Perl; 6 | use autodie; 7 | use DBI; 8 | 9 | use getiface ':all'; 10 | 11 | # db handler 12 | has dbh => ( is => 'rw', builder => '_void'); 13 | 14 | sub _void { my $x = ''; \$x; } 15 | 16 | # reference to the application 17 | has data => qw/is ro required 1/; 18 | 19 | sub BUILD { 20 | my $self = shift; 21 | 22 | my $db = $$self{data}{database}; 23 | 24 | my $dsn = "DBI:$$db{sgbd}:database=$$db{name};" 25 | . "host=$$db{host};port=$$db{port}"; 26 | 27 | $$self{dbh} = DBI->connect($dsn, $$db{user}, $$db{passwd}) 28 | || die "Could not connect to database: $DBI::errstr"; 29 | $$self{dbh}->{mysql_enable_utf8} = 1; 30 | $$self{dbh}->do('SET NAMES \'utf8\';') || die; 31 | 32 | } 33 | 34 | # USER 35 | 36 | sub auth { 37 | my ($self, $login, $passwd) = @_; 38 | my $sth; 39 | 40 | $sth = $self->dbh->prepare('SELECT * FROM user WHERE login=? and passwd=?'); 41 | unless ($sth->execute($login, $passwd)) { 42 | $sth->finish(); 43 | die q{Can't authenticate.}; 44 | } 45 | 46 | # if we can't find the user with this password 47 | unless (my $ref = $sth->fetchrow_arrayref) { 48 | $sth->finish(); 49 | die q{The user can't be authenticated.}; 50 | } 51 | $sth->finish(); 52 | 53 | # if this user exists and is auth 54 | $self->get_user($login) 55 | } 56 | 57 | sub register_user { 58 | my ($self, $login, $pass) = @_; 59 | 60 | my $sth = $self->dbh->prepare('select * from user where login=?'); 61 | unless ( $sth->execute($login) ) { 62 | $sth->finish(); 63 | die "Impossible to check if the user $login exists."; 64 | } 65 | 66 | # if an user already exists 67 | if (my $ref = $sth->fetchrow_arrayref) { 68 | $sth->finish(); 69 | die "The user $login already exists."; 70 | } 71 | 72 | # if not 73 | $sth = $self->dbh->prepare('insert into user VALUES(?,?,?)'); 74 | unless ($sth->execute($login, $pass, 0)) { 75 | $sth->finish(); 76 | die "Impossible to register the user $login."; 77 | } 78 | $sth->finish(); 79 | } 80 | 81 | sub delete_user { 82 | my ($self, $login) = @_; 83 | my $sth; 84 | # TODO : vérifier que ça renvoie la bonne valeur 85 | $sth = $self->dbh->prepare('delete from user where login=?'); 86 | unless ( $sth->execute($login) ) { 87 | $sth->finish(); 88 | die "Impossible to delete the user $login."; 89 | } 90 | $sth->finish(); 91 | $self->delete_domains_from_user($login) 92 | } 93 | 94 | sub get_user { 95 | my ($self, $login) = @_; 96 | my ($sth, $user); 97 | 98 | $sth = $self->dbh->prepare('SELECT * FROM user WHERE login=?'); 99 | unless ( $sth->execute($login)) { 100 | $sth->finish(); 101 | die "Impossible to check if the user $login exists."; 102 | } 103 | 104 | unless ($user = $sth->fetchrow_hashref) { 105 | $sth->finish(); 106 | die "User $login doesn't exist."; 107 | } 108 | $sth->finish(); 109 | 110 | # the user gets all his domains 111 | $$user{domains} = $self->get_domains($login); 112 | $user 113 | } 114 | 115 | sub get_all_users { 116 | my ($self) = @_; 117 | my ($sth, $users); 118 | 119 | $sth = $self->dbh->prepare('SELECT * FROM user'); 120 | unless ( $sth->execute()) { 121 | $sth->finish(); 122 | die q{Impossible to list the users.}; 123 | } 124 | 125 | while( my $ref = $sth->fetchrow_hashref) { 126 | push @$users, $ref; 127 | } 128 | 129 | $sth->finish(); 130 | $users 131 | } 132 | 133 | sub toggle_admin { 134 | my ($self, $login) = @_; 135 | 136 | my $user = $self->get_user($login); 137 | my $val = ($$user{admin}) ? 0 : 1; 138 | 139 | my $sth = $self->dbh->prepare('update user set admin=? where login=?'); 140 | unless ( $sth->execute( $val, $login ) ) { 141 | $sth->finish(); 142 | die "Impossible to toggle admin the user $login."; 143 | } 144 | 145 | $sth->finish() 146 | } 147 | 148 | sub update_passwd { 149 | my ($self, $login, $new) = @_; 150 | my $sth; 151 | $sth = $self->dbh->prepare('update user set passwd=? where login=?'); 152 | unless ( $sth->execute($new, $login) ) { 153 | $sth->finish(); 154 | die q{The password can't be updated.}; 155 | } 156 | $sth->finish() 157 | } 158 | 159 | # DOMAIN 160 | 161 | sub get_domains { 162 | my ($self, $login) = @_; 163 | my ($sth); 164 | my $domains = []; 165 | 166 | $sth = $self->dbh->prepare('SELECT * FROM domain where login=?'); 167 | unless ($sth->execute($login)) { 168 | $sth->finish(); 169 | die "Impossible to check if the user $login has domains."; 170 | } 171 | 172 | while(my $ref = $sth->fetchrow_hashref) { 173 | push @$domains, $ref; 174 | } 175 | 176 | $sth->finish(); 177 | $domains 178 | } 179 | 180 | sub delete_domain { 181 | my ($self, $domain) = @_; 182 | my $sth; 183 | $sth = $self->dbh->prepare('delete from domain where domain=?'); 184 | unless ( $sth->execute($domain) ) { 185 | $sth->finish(); 186 | die "Impossible to delete the $domain."; 187 | } 188 | $sth->finish() 189 | } 190 | 191 | sub delete_domains_from_user { 192 | my ($self, $login) = @_; 193 | my $sth; 194 | $sth = $self->dbh->prepare('delete from domain where login=?'); 195 | unless ( $sth->execute($login) ) { 196 | $sth->finish(); 197 | die "Impossible to delete the domains of the user $login."; 198 | } 199 | $sth->finish() 200 | } 201 | 202 | # TODO check if the domain is reserved 203 | sub add_domain { 204 | my ($self, $login, $domain) = @_; 205 | my ($sth); 206 | 207 | $sth = $self->dbh->prepare('select domain from domain where domain=?'); 208 | unless ( $sth->execute($domain) ) { 209 | $sth->finish(); 210 | die 'Impossible to search if the domain already exists.'; 211 | } 212 | 213 | # if the domain already exists 214 | if (my $ref = $sth->fetchrow_arrayref) { 215 | $sth->finish(); 216 | die 'The domain already exists.'; 217 | } 218 | 219 | $sth = $self->dbh->prepare('insert into domain VALUES(?,?,?)'); 220 | unless ( $sth->execute($domain, $login, 0) ) { 221 | $sth->finish(); 222 | die 'Impossible to add a domain.'; 223 | } 224 | 225 | $sth->finish(); 226 | } 227 | 228 | sub get_all_domains { 229 | my ($self) = @_; 230 | my ($sth, $domains); 231 | 232 | $sth = $self->dbh->prepare('SELECT * FROM domain'); 233 | unless ( $sth->execute()) { 234 | $sth->finish(); 235 | die q{Impossible to list the domains.}; 236 | } 237 | 238 | while( my $ref = $sth->fetchrow_hashref) { 239 | push @$domains, $ref; 240 | } 241 | 242 | $sth->finish(); 243 | $domains 244 | } 245 | 246 | sub disconnect { 247 | my ($self) = @_; 248 | $$self{dbh}->disconnect() 249 | } 250 | 251 | sub is_owning_domain { 252 | my ($self, $login, $domain) = @_; 253 | 254 | my $sth = 255 | $self->dbh->prepare('SELECT * FROM domain where login=? and domain=?'); 256 | unless ($sth->execute($login, $domain)) { 257 | $sth->finish(); 258 | die "Impossible to check if the user $login has domains."; 259 | } 260 | 261 | unless($sth->fetchrow_hashref) { 262 | $sth->finish(); 263 | return 0 264 | } 265 | 266 | 1 267 | } 268 | 269 | 1; 270 | -------------------------------------------------------------------------------- /lib/encryption.pm: -------------------------------------------------------------------------------- 1 | package encryption; 2 | use Crypt::Digest::SHA256 qw( sha256_hex ) ; 3 | 4 | use Exporter 'import'; 5 | # what we want to export eventually 6 | our @EXPORT_OK = qw/encrypt/; 7 | 8 | # bundle of exports (tags) 9 | our %EXPORT_TAGS = ( all => [qw/encrypt/] ); 10 | 11 | sub encrypt { 12 | my ($x) = @_; 13 | sha256_hex($x) 14 | } 15 | 16 | 1; 17 | -------------------------------------------------------------------------------- /lib/fileutil.pm: -------------------------------------------------------------------------------- 1 | package fileutil; 2 | use v5.14; 3 | 4 | use URI; 5 | 6 | use Exporter 'import'; 7 | # what we want to export eventually 8 | our @EXPORT_OK = qw/read_file write_file/; 9 | 10 | # bundle of exports (tags) 11 | our %EXPORT_TAGS = ( all => [qw/read_file write_file/] ); 12 | 13 | sub read_file { 14 | my ($filename) = @_; 15 | 16 | if($filename =~ "://") 17 | { 18 | my $fileuri = URI->new($filename); 19 | $filename = $fileuri->path; 20 | } 21 | 22 | open my $entry, '<:encoding(UTF-8)', $filename or 23 | die "Impossible d'ouvrir '$filename' en lecture : $!"; 24 | local $/ = undef; 25 | my $all = <$entry>; 26 | close $entry; 27 | 28 | return $all; 29 | } 30 | 31 | sub write_file { 32 | my ($filename, $data) = @_; 33 | 34 | if($filename =~ "://") 35 | { 36 | my $fileuri = URI->new($filename); 37 | $filename = $fileuri->path; 38 | } 39 | 40 | open my $sortie, '>:encoding(UTF-8)', $filename or 41 | die "Impossible d'ouvrir '$filename' en écriture : $!"; 42 | print $sortie $data; 43 | close $sortie; 44 | } 45 | 46 | 1; 47 | -------------------------------------------------------------------------------- /lib/getiface.pm: -------------------------------------------------------------------------------- 1 | package getiface; 2 | use v5.14; 3 | 4 | use Exporter 'import'; 5 | # what we want to export eventually 6 | our @EXPORT_OK = qw/getiface/; 7 | 8 | # bundle of exports (tags) 9 | our %EXPORT_TAGS = ( all => [qw/getiface/] ); 10 | 11 | use interface::bind9; 12 | use interface::knot; 13 | use interface::nsd3; 14 | use interface::nsd4; 15 | 16 | sub getiface { 17 | my ($type, $params) = @_; 18 | for($type) { 19 | if (/bind9/) { return interface::bind9->new($params) } 20 | elsif (/knot/) { return interface::knot->new($params) } 21 | elsif (/nsd3/) { return interface::nsd3->new($params) } 22 | elsif (/nsd/) { return interface::nsd4->new($params) } 23 | else { die "Interface for the $_ dns type not found."; } 24 | } 25 | } 26 | 27 | 1; 28 | -------------------------------------------------------------------------------- /lib/interface/bind9.pm: -------------------------------------------------------------------------------- 1 | package interface::bind9; 2 | use v5.14; 3 | use Moo; 4 | use configuration ':all'; 5 | use remotecmd ':all'; 6 | 7 | has [ qw/mycfg tmpdir primarydnsserver secondarydnsserver/ ] => qw/is ro required 1/; 8 | 9 | sub reload { 10 | my ($self, $domain) = @_; 11 | 12 | my $cmd = "rndc reload $domain"; 13 | say "CMD: $cmd"; 14 | qx/$cmd/; 15 | $cmd = "rndc notify $domain"; 16 | say "CMD: $cmd"; 17 | qx/$cmd/; 18 | 19 | #my $cmd = "rndc reload $domain "; 20 | #my $user = get_user_from_cfg($$self{mycfg}); 21 | #my $host = get_host_from_cfg($$self{mycfg}); 22 | #my $port = get_port_from_cfg($$self{mycfg}); 23 | 24 | #remotecmd $user, $host, $port, $cmd; 25 | 26 | #$cmd = "rndc notify $domain "; 27 | #remotecmd $user, $host, $port, $cmd; 28 | } 29 | 30 | sub primary_addzone { 31 | my ($self, $domain, $opt) = @_; 32 | 33 | my $cmd = "rndc addzone $domain "; 34 | 35 | if(defined $opt) { 36 | $cmd .= "'$opt'"; 37 | } 38 | else { 39 | my $dir = get_zonedir_from_cfg($$self{mycfg}); 40 | $cmd .= "\"{ type master; file \\\"$dir/$domain\\\"; allow-transfer { "; 41 | 42 | my $sec = $$self{secondarydnsserver}; 43 | for(@$sec) { 44 | my $v4 = get_v4_from_cfg($_); 45 | my $v6 = get_v6_from_cfg($_); 46 | 47 | $cmd .= $v4 . '; ' if $v4; 48 | $cmd .= $v6 . '; ' if $v6; 49 | } 50 | $cmd .= " }; notify yes; };\""; 51 | } 52 | 53 | # if remote rndc 54 | #my $user = get_user_from_cfg($$self{mycfg}); 55 | #my $host = get_host_from_cfg($$self{mycfg}); 56 | #my $port = get_port_from_cfg($$self{mycfg}); 57 | 58 | #remotecmd $user, $host, $port, $cmd; 59 | 60 | qx/$cmd/; 61 | } 62 | 63 | sub reconfig { 64 | my ($self, $domain) = @_; 65 | 66 | my $cmd = "rndc reconfig "; 67 | 68 | #my $user = get_user_from_cfg($$self{mycfg}); 69 | #my $host = get_host_from_cfg($$self{mycfg}); 70 | #my $port = get_port_from_cfg($$self{mycfg}); 71 | 72 | #remotecmd $user, $host, $port, $cmd; 73 | 74 | qx/$cmd/; 75 | } 76 | 77 | sub delzone { 78 | my ($self, $domain) = @_; 79 | 80 | my $cmd = "rndc delzone $domain "; 81 | 82 | my $user = get_user_from_cfg($$self{mycfg}); 83 | my $host = get_host_from_cfg($$self{mycfg}); 84 | my $port = get_port_from_cfg($$self{mycfg}); 85 | 86 | #remotecmd $user, $host, $port, $cmd; 87 | qx/$cmd/; 88 | 89 | my $file = get_zonedir_from_cfg($$self{mycfg}); 90 | $file .= "/$domain"; 91 | 92 | $cmd = "rm $file"; 93 | 94 | remotecmd $user, $host, $port, $cmd 95 | } 96 | 97 | 1; 98 | -------------------------------------------------------------------------------- /lib/interface/knot.pm: -------------------------------------------------------------------------------- 1 | package app::interface::knot; 2 | use v5.14; 3 | use Moo; 4 | 5 | # on suppose que tout est déjà mis à jour dans le fichier 6 | sub reload { 7 | my ($self, $zname) = @_; 8 | die "knot ns not implemented yet"; 9 | } 10 | 11 | sub addzone { 12 | die "knot primary ns not implemented yet"; 13 | } 14 | 15 | # add a domain on a secondary ns 16 | sub addzone_sec { 17 | my ($self, $zdir, $zname, $opt) = @_; 18 | die "knot secondary ns not implemented yet"; 19 | } 20 | 21 | sub delzone { 22 | my ($self, $zdir, $zname) = @_; 23 | die "knot ns not implemented yet"; 24 | } 25 | 26 | 1; 27 | -------------------------------------------------------------------------------- /lib/interface/nsd3.pm: -------------------------------------------------------------------------------- 1 | package interface::nsd3; 2 | use v5.14; 3 | use Moo; 4 | use URI; 5 | use fileutil ':all'; 6 | use remotecmd ':all'; 7 | use copycat ':all'; 8 | use configuration ':all'; 9 | 10 | has [ qw/mycfg tmpdir primarydnsserver secondarydnsserver/ ] => qw/is ro required 1/; 11 | 12 | # on suppose que tout est déjà mis à jour dans le fichier 13 | sub reload_sec { 14 | my ($self, $slavedzones) = @_; 15 | 16 | $self->_reload_conf($slavedzones); 17 | 18 | my $cmd = "sudo nsdc rebuild && sudo nsdc restart && sudo nsdc patch "; 19 | 20 | my $user = get_user_from_cfg($$self{mycfg}); 21 | my $host = get_host_from_cfg($$self{mycfg}); 22 | my $port = get_port_from_cfg($$self{mycfg}); 23 | 24 | remotecmd $user, $host, $port, $cmd 25 | } 26 | 27 | # get, modify, push the file 28 | 29 | sub _reload_conf { 30 | my ($self, $slavedzones) = @_; 31 | 32 | my $f = "file://$$self{tmpdir}/nsd.conf"; 33 | my $remote = ($$self{mycfg}{cfg}) ? $$self{mycfg}{cfg} : undef; 34 | 35 | my $user = get_user_from_cfg($$self{mycfg}); 36 | my $host = get_host_from_cfg($$self{mycfg}); 37 | my $port = get_port_from_cfg($$self{mycfg}); 38 | 39 | $remote //= "ssh://$user". '@' . "$host/etc/nsd/nsd.conf"; 40 | 41 | copycat $remote, $f; 42 | 43 | my $data = read_file $f; 44 | 45 | # if it's the first time we get the configuration, fresh start 46 | $data .= "\n## BEGIN_GENERATED" if( $data !~ /BEGIN_GENERATED/); 47 | 48 | my $v4 = get_v4_from_cfg($$self{primarydnsserver}); 49 | my $v6 = get_v6_from_cfg($$self{primarydnsserver}); 50 | 51 | my $debut = "## BEGIN_GENERATED"; 52 | 53 | my $nouveau = ''; 54 | my $dnsslavekey = get_dnsslavekey_from_cfg($$self{primarydnsserver}); 55 | 56 | for(@{$slavedzones}) { 57 | 58 | $nouveau .= "zone:\n\tname: \"$$_{domain}\"\n" 59 | . "\tzonefile: \"slave/$$_{domain}\"\n"; 60 | 61 | say "domain : $$_{domain}"; 62 | 63 | if($v4) { 64 | # allow notify & request xfr, v4 & v6 65 | $nouveau .= "\tallow-notify: $v4 \"$dnsslavekey\" \n" 66 | . "\trequest-xfr: $v4 \"$dnsslavekey\" \n"; 67 | } 68 | 69 | if($v6) { 70 | $nouveau .= "\tallow-notify: $v6 \"$dnsslavekey\" \n" 71 | . "\trequest-xfr: $v6 \"$dnsslavekey\" \n"; 72 | } 73 | $nouveau .= "\n"; 74 | } 75 | 76 | $data =~ s/$debut.*/$debut\n$nouveau/gsm; 77 | 78 | write_file $f, $data; 79 | copycat $f, $remote; 80 | 81 | my $cmd = "sudo nsdc patch && sudo rm /var/nsd3/ixfr.db"; 82 | 83 | remotecmd $user, $host, $port, $cmd 84 | } 85 | 86 | sub reconfig { 87 | my ($self, $zname) = @_; 88 | die "nsd3 reconfig not implemented."; 89 | } 90 | 91 | sub delzone { 92 | my ($self) = @_; 93 | die "nsd3 delzone not implemented."; 94 | } 95 | 96 | 1; 97 | -------------------------------------------------------------------------------- /lib/interface/nsd4.pm: -------------------------------------------------------------------------------- 1 | package interface::nsd4; 2 | use v5.14; 3 | use Moo; 4 | use URI; 5 | use fileutil ':all'; 6 | use remotecmd ':all'; 7 | use copycat ':all'; 8 | use configuration ':all'; 9 | 10 | has [ qw/mycfg tmpdir primarydnsserver secondarydnsserver/ ] => qw/is ro required 1/; 11 | 12 | # on suppose que tout est déjà mis à jour dans le fichier 13 | sub reload_sec { 14 | my ($self, $slavedzones) = @_; 15 | 16 | $self->_reload_conf($slavedzones); 17 | 18 | my $cmd = "sudo nsd-control reconfig"; 19 | 20 | my $user = get_user_from_cfg($$self{mycfg}); 21 | my $host = get_host_from_cfg($$self{mycfg}); 22 | my $port = get_port_from_cfg($$self{mycfg}); 23 | 24 | remotecmd $user, $host, $port, $cmd 25 | } 26 | 27 | # get, modify, push the file 28 | 29 | sub _reload_conf { 30 | my ($self, $slavedzones) = @_; 31 | 32 | my $f = "file://$$self{tmpdir}/nsd.conf"; 33 | my $remote = ($$self{mycfg}{cfg}) ? $$self{mycfg}{cfg} : undef; 34 | 35 | my $user = get_user_from_cfg($$self{mycfg}); 36 | my $host = get_host_from_cfg($$self{mycfg}); 37 | my $port = get_port_from_cfg($$self{mycfg}); 38 | 39 | $remote //= "ssh://$user". '@' . "$host/etc/nsd/nsd.conf"; 40 | 41 | copycat $remote, $f; 42 | 43 | my $data = read_file $f; 44 | 45 | # if it's the first time we get the configuration, fresh start 46 | $data .= "\n## BEGIN_GENERATED" if( $data !~ /BEGIN_GENERATED/); 47 | 48 | my $v4 = get_v4_from_cfg($$self{primarydnsserver}); 49 | my $v6 = get_v6_from_cfg($$self{primarydnsserver}); 50 | 51 | my $debut = "## BEGIN_GENERATED"; 52 | 53 | my $nouveau = ''; 54 | my $dnsslavekey = get_dnsslavekey_from_cfg($$self{primarydnsserver}); 55 | 56 | # $nouveau .= " 57 | #remote-control: 58 | # control-enable: yes 59 | # control-interface: 127.0.0.1 60 | # control-port: 8952 61 | # server-key-file: '/etc/nsd/nsd_server.key' 62 | # server-cert-file: '/etc/nsd/nsd_server.pem' 63 | # control-key-file: '/etc/nsd/nsd_control.key' 64 | # control-cert-file: '/etc/nsd/nsd_control.pem' 65 | # 66 | #key: 67 | # 68 | ## pattern : configuration to reproduce on every slaves 69 | $nouveau .= " 70 | pattern: 71 | \tname: 'slavepattern' 72 | "; 73 | 74 | if($v4) { 75 | # allow notify & request xfr, v4 & v6 76 | $nouveau .= "\tallow-notify: $v4 \"$dnsslavekey\" \n" 77 | . "\trequest-xfr: $v4 \"$dnsslavekey\" \n"; 78 | } 79 | 80 | if($v6) { 81 | $nouveau .= "\tallow-notify: $v6 \"$dnsslavekey\" \n" 82 | . "\trequest-xfr: $v6 \"$dnsslavekey\" \n"; 83 | } 84 | 85 | $nouveau .= "\n"; 86 | 87 | for(@{$slavedzones}) { 88 | 89 | $nouveau .= "zone:\n\tname: \"$$_{domain}\"\n" 90 | . "\tzonefile: \"slave/$$_{domain}\"\n"; 91 | $nouveau .= "\tinclude-pattern: 'slavepattern'\n\n"; 92 | } 93 | 94 | $data =~ s/$debut.*/$debut\n$nouveau/gsm; 95 | 96 | write_file $f, $data; 97 | copycat $f, $remote; 98 | 99 | my $cmd = "sudo nsd-control reconfig"; 100 | 101 | remotecmd $user, $host, $port, $cmd 102 | } 103 | 104 | sub reconfig { 105 | my ($self, $zname) = @_; 106 | 107 | my $user = get_user_from_cfg($$self{mycfg}); 108 | my $host = get_host_from_cfg($$self{mycfg}); 109 | my $port = get_port_from_cfg($$self{mycfg}); 110 | my $cmd = "sudo nsd-control reconfig"; 111 | remotecmd $user, $host, $port, $cmd 112 | } 113 | 114 | sub delzone { 115 | my ($self) = @_; 116 | 117 | my $user = get_user_from_cfg($$self{mycfg}); 118 | my $host = get_host_from_cfg($$self{mycfg}); 119 | my $port = get_port_from_cfg($$self{mycfg}); 120 | my $cmd = "sudo nsd-control reconfig"; 121 | remotecmd $user, $host, $port, $cmd; 122 | #die "nsd4 delzone not implemented."; 123 | } 124 | 125 | 1; 126 | -------------------------------------------------------------------------------- /lib/remotecmd.pm: -------------------------------------------------------------------------------- 1 | package remotecmd; 2 | use v5.14; 3 | 4 | use Net::OpenSSH; 5 | use Net::SSH q; 6 | 7 | use Exporter 'import'; 8 | # what we want to export eventually 9 | our @EXPORT_OK = qw/remotecmd/; 10 | 11 | # bundle of exports (tags) 12 | our %EXPORT_TAGS = ( all => [qw/remotecmd/] ); 13 | 14 | sub remotecmd { 15 | my ($user, $host, $port, $cmd) = @_; 16 | 17 | #sshopen2("-p '$port' $user\@$host", *READER, *WRITER, "$cmd") 18 | #|| die "ssh: $!"; 19 | 20 | #system("ssh -p '$port' '$user". '@'. "$host' '$cmd'"); 21 | 22 | #my $ret = ''; 23 | #$ret .= $_ while(); 24 | 25 | #close(READER); 26 | #close(WRITER); 27 | 28 | my $str = "ssh -p $port $user". '@' . "$host '$cmd'"; 29 | say ""; 30 | say "CMD : $str"; 31 | say ""; 32 | qx/$str/; 33 | } 34 | 35 | 1; 36 | -------------------------------------------------------------------------------- /lib/rt/admin.pm: -------------------------------------------------------------------------------- 1 | package rt::admin; 2 | 3 | use configuration ':all'; 4 | use app; 5 | use utf8; 6 | use open qw/:std :utf8/; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use Exporter 'import'; 11 | # what we want to export eventually 12 | our @EXPORT_OK = qw/rt_admin/; 13 | 14 | # bundle of exports (tags) 15 | our %EXPORT_TAGS = ( all => [qw/rt_admin/] ); 16 | 17 | sub rt_admin { 18 | my ($session, $param, $request) = @_; 19 | my $res; 20 | 21 | eval { 22 | my $app = app->new(get_cfg()); 23 | my $user = $app->auth($$session{login}, $$session{passwd}); 24 | 25 | unless ($user && $$user{admin}) { 26 | $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; 27 | $$res{route} = '/'; 28 | return $res; 29 | } 30 | 31 | my $alldomains = $app->get_all_domains; 32 | my $allusers = $app->get_all_users; 33 | my $domains = $app->get_domains($$session{login}); 34 | 35 | $$res{template} = 'administration'; 36 | $$res{params} = { 37 | login => $$session{login} 38 | , admin => $$user{admin} 39 | , domains => $domains 40 | , alldomains => $alldomains 41 | , allusers => $allusers 42 | }; 43 | $app->disconnect(); 44 | }; 45 | 46 | 47 | $res 48 | } 49 | 50 | 1; 51 | -------------------------------------------------------------------------------- /lib/rt/adminfake.pm: -------------------------------------------------------------------------------- 1 | package rt::adminfake; 2 | 3 | use configuration ':all'; 4 | use app; 5 | use utf8; 6 | use open qw/:std :utf8/; 7 | 8 | use Data::Dump qw( dump ); 9 | 10 | use Exporter 'import'; 11 | # what we want to export eventually 12 | our @EXPORT_OK = qw/rt_admin_fake/; 13 | 14 | # bundle of exports (tags) 15 | our %EXPORT_TAGS = ( all => [qw/rt_admin_fake/] ); 16 | 17 | sub rt_admin_fake { 18 | my ($session, $param, $request) = @_; 19 | my $res; 20 | my $alldomains = [ { qw/domain toto.netlib.re login toto/ } ]; 21 | my $allusers = [ { qw/ login toto admin 0 / } 22 | , { qw/login bidule admin 1/ } 23 | , { qw/login machin admin 0 / } ]; 24 | my $domains = [ { qw/toto.netlib.re/ } ]; 25 | $$res{template} = 'administration'; 26 | $$res{params} = { 27 | login => "toto" 28 | , admin => 1 29 | , domains => $domains 30 | , alldomains => $alldomains 31 | , allusers => $allusers 32 | }; 33 | $res 34 | } 35 | 36 | 1; 37 | -------------------------------------------------------------------------------- /lib/rt/domain.pm: -------------------------------------------------------------------------------- 1 | package rt::domain; 2 | 3 | use v5.14; 4 | use configuration ':all'; 5 | use encryption ':all'; 6 | use util ':all'; 7 | use app; 8 | use utf8; 9 | use open qw/:std :utf8/; 10 | use Dancer ':syntax'; 11 | use Data::Dump qw( dump ); 12 | use Data::Validate::IP qw(is_ipv4 is_ipv6); 13 | use MIME::Base64 qw(encode_base64 decode_base64); 14 | 15 | use Exporter 'import'; 16 | # what we want to export eventually 17 | our @EXPORT_OK = qw/ 18 | rt_dom_cli_mod_entry 19 | rt_dom_cli_autoupdate 20 | rt_dom_mod_entry 21 | rt_dom_del_entry 22 | rt_dom_del 23 | rt_dom_add 24 | rt_dom_details 25 | rt_dom_add_entry 26 | rt_dom_updateraw 27 | /; 28 | 29 | # bundle of exports (tags) 30 | our %EXPORT_TAGS = ( all => [qw/ 31 | rt_dom_cli_mod_entry 32 | rt_dom_cli_autoupdate 33 | rt_dom_mod_entry 34 | rt_dom_del_entry 35 | rt_dom_del 36 | rt_dom_add 37 | rt_dom_details 38 | rt_dom_add_entry 39 | rt_dom_updateraw 40 | /] ); 41 | 42 | sub rt_dom_cli_autoupdate { 43 | my ($session, $param, $request) = @_; 44 | my $res; 45 | 46 | my @missingitems; 47 | my @items = qw/login pass domain name type rdata/; 48 | 49 | for(@items) { 50 | push @missingitems, $_ unless($$param{$_}); 51 | } 52 | 53 | if(@missingitems != 0) { 54 | say "Il manque : " . join ', ', @missingitems; 55 | return $res; 56 | } 57 | 58 | for(@items) { 59 | say "::::::::: $_ : $$param{$_}" if $$param{$_}; 60 | } 61 | 62 | if(! is_ipv4($$param{rdata}) && ! is_ipv6($$param{rdata})) { 63 | say "Attention, ceci n'est pas une adresse IP : $$param{rdata}."; 64 | return $res; 65 | } 66 | 67 | eval { 68 | my $pass = encrypt($$param{pass}); 69 | my $app = app->new(get_cfg()); 70 | 71 | my $user; 72 | 73 | eval { 74 | $user = $app->auth($$param{login}, $pass); 75 | }; 76 | 77 | # if the mdp is in base64 78 | # useful for cli and http GET messages 79 | if( $@ ) { 80 | my $passb64 = decode_base64($$param{pass}); 81 | $pass = encrypt($passb64); 82 | $user = $app->auth($$param{login}, $pass); 83 | } 84 | 85 | unless ( $user && ( $$user{admin} || 86 | $app->is_owning_domain($$user{login}, $$param{domain}))) { 87 | $app->disconnect(); 88 | say q{Donnée privée, petit coquin. ;) }; 89 | return $res; 90 | } 91 | 92 | my $zone = $app->get_zone( $$param{domain} ); 93 | my $zf = $zone->get_zonefile(); 94 | 95 | my $name = $$param{name}; 96 | 97 | $name =~ s/@/$$param{domain}./; 98 | 99 | if($name =~ /$$param{domain}$/) { 100 | $name .= '.'; 101 | } 102 | 103 | if($name !~ /\.$/) { 104 | $name .= ".$$param{domain}." 105 | } 106 | 107 | my $rr_list = $zf->rr_search($name, $$param{type}); 108 | my $rr; 109 | if(@$rr_list) { 110 | $rr = pop @$rr_list; 111 | } 112 | else { 113 | say "Pas d'entrée au nom $name de type $$param{type} trouvée."; 114 | return $res; 115 | } 116 | 117 | my $str_old = "$$rr{name} $$rr{ttl} $$rr{type} $$rr{rdata}"; 118 | my $str_new = "$$rr{name} $$rr{ttl} $$rr{type} $$param{rdata}"; 119 | 120 | say "old : $str_old"; 121 | say "new : $str_new"; 122 | if($$rr{rdata} eq $$param{rdata}) { 123 | say "SAME"; 124 | } 125 | else { 126 | $zf->rr_mod($str_old, $str_new); 127 | $zone->update( $zf ); 128 | } 129 | 130 | $app->disconnect(); 131 | }; 132 | 133 | if ($@) { 134 | say "Problème : $@"; 135 | } 136 | 137 | $res 138 | } 139 | 140 | sub rt_dom_cli_mod_entry { 141 | my ($session, $param, $request) = @_; 142 | my $res; 143 | 144 | eval { 145 | my $pass = encrypt($$param{pass}); 146 | my $app = app->new(get_cfg()); 147 | 148 | my $user = $app->auth($$session{login}, $pass); 149 | 150 | unless ( $user && ( $$user{admin} || 151 | $app->is_owning_domain($$user{login}, $$param{domain}))) { 152 | $app->disconnect(); 153 | $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; 154 | $$res{route} = '/'; 155 | return $res; 156 | } 157 | 158 | my $zone = $app->get_zone( $$param{domain} ); 159 | my $zf = $zone->get_zonefile(); 160 | $zf->rr_mod( 161 | "$$param{name} $$param{ttl} $$param{type} $$param{rdata}" 162 | , "$$param{name} $$param{ttl} $$param{type} $$param{ip}" 163 | ); 164 | $zone->update( $zf ); 165 | 166 | $app->disconnect(); 167 | }; 168 | 169 | $res 170 | } 171 | 172 | sub rt_dom_mod_entry { 173 | my ($session, $param, $request) = @_; 174 | my $res; 175 | 176 | $$res{route} = '/domain/details/'. $$param{domain}; 177 | 178 | # check if user is logged 179 | unless( $$session{login}) { 180 | $$res{deferred}{errmsg} = q{Vous n'êtes pas enregistré. }; 181 | $$res{sessiondestroy} = 1; 182 | return $res; 183 | } 184 | 185 | my @missingitems; 186 | my @items = qw/domain type 187 | oldname oldrdata oldttl 188 | newname newrdata newttl/; 189 | 190 | if($$param{type} && $$param{type} eq 'MX') { 191 | push @items, qw/oldpriority newpriority/; 192 | } 193 | 194 | if($$param{type} && $$param{type} eq 'SRV') { 195 | push @items, qw/ 196 | oldpriority oldweight oldport 197 | newpriority newweight newport/; 198 | } 199 | 200 | for(@items) { 201 | push @missingitems, $_ unless($$param{$_}); 202 | } 203 | 204 | if(@missingitems != 0) { 205 | $$res{deferred}{errmsg} = "Il manque : " . join ', ', @missingitems; 206 | return $res; 207 | } 208 | 209 | for(@items) { 210 | say "::::::::: $_ : $$param{$_}" if $$param{$_}; 211 | } 212 | 213 | eval { 214 | 215 | unless( $$param{domain} ) { 216 | $$res{deferred}{errmsg} = q; 217 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 218 | return $res; 219 | } 220 | 221 | my $oldname = $$param{oldname}; 222 | my $newname = $$param{newname}; 223 | my $oldrdata = $$param{oldrdata}; 224 | my $newrdata = $$param{newrdata}; 225 | 226 | $oldname =~ s/@/$$param{domain}./g; 227 | $newname =~ s/@/$$param{domain}./g; 228 | 229 | if ($$param{type} eq 'A' && ! is_ipv4($newrdata)) { 230 | $$res{deferred}{errmsg} = 231 | "Il faut une adresse IPv4 pour un enregistrement de type A." 232 | . " Ceci n'est pas une adresse IPv4 : $newrdata"; 233 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 234 | return $res; 235 | } 236 | 237 | if ($$param{type} eq 'AAAA' && ! is_ipv6($newrdata)) { 238 | $$res{deferred}{errmsg} = 239 | "Il faut une adresse IPv6 pour un enregistrement de type AAAA." 240 | . " Ceci n'est pas une adresse IPv6 : $newrdata"; 241 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 242 | return $res; 243 | } 244 | 245 | # si le type est A, AAAA, SRV, TXT, CNAME, MX, NS 246 | # le name doit être un domaine 247 | # si ce domaine n'est pas absolu, rajouter ".domain." 248 | if($$param{type} =~ /^(A|AAAA|SRV|TXT|CNAME|MX|NS)$/) { 249 | $newname .= ".$$param{domain}." if($newname !~ /\.$/); 250 | $oldname .= ".$$param{domain}." if($oldname !~ /\.$/); 251 | } 252 | 253 | # si le type est CNAME, MX, NS ou PTR 254 | # le rdata doit être un domaine 255 | # si ce domaine n'est pas absolu, rajouter ".domain." 256 | if($$param{type} =~ /^(CNAME|MX|NS|PTR)$/) { 257 | $oldrdata =~ s/@/$$param{domain}./; 258 | $newrdata =~ s/@/$$param{domain}./; 259 | $oldrdata .= ".$$param{domain}." if($oldrdata !~ /\.$/); 260 | $newrdata .= ".$$param{domain}." if($newrdata !~ /\.$/); 261 | } 262 | 263 | if ($$param{type} =~ /^(CNAME|MX|NS|PTR|SRV)$/i 264 | && ! is_domain_name ($newrdata)) 265 | { 266 | $$res{deferred}{errmsg} = 267 | "Une entrée $$param{type} doit avoir un nom de domaine " 268 | . "(pas une URL, pas de http://) : '$newrdata' n'est pas correct."; 269 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 270 | return $res; 271 | } 272 | 273 | my $str_old = "$oldname $$param{oldttl} $$param{type} "; 274 | my $str_new = "$newname $$param{newttl} $$param{type} "; 275 | 276 | if($$param{type} eq "MX") { 277 | $str_old .= "$$param{oldpriority} $oldrdata"; 278 | $str_new .= "$$param{newpriority} $newrdata"; 279 | } 280 | elsif ($$param{type} eq "SRV") { 281 | $str_old .= "$$param{oldpriority} $$param{oldweight} " 282 | ."$$param{oldport} $oldrdata"; 283 | $str_new .= "$$param{newpriority} $$param{newweight} " 284 | ."$$param{newport} $newrdata"; 285 | } 286 | else { 287 | $str_old .= "$oldrdata"; 288 | $str_new .= "$newrdata"; 289 | } 290 | 291 | say "::: ___ str_old : $str_old"; 292 | say "::: ___ str_new : $str_new"; 293 | 294 | # Do the modification of the entry 295 | 296 | my $app = app->new(get_cfg()); 297 | my $user = $app->auth($$session{login}, $$session{passwd}); 298 | 299 | unless ( $user && ( $$user{admin} || 300 | $app->is_owning_domain($$user{login}, $$param{domain}))) { 301 | $app->disconnect(); 302 | $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; 303 | return $res; 304 | } 305 | 306 | my $zone = $app->get_zone( $$param{domain} ); 307 | my $zf = $zone->get_zonefile(); 308 | 309 | $zf->rr_mod( $str_old, $str_new); 310 | $zone->update( $zf ); 311 | 312 | $app->disconnect(); 313 | }; 314 | 315 | if($@) { 316 | $$res{deferred}{errmsg} = q{Modification impossible. } . $@; 317 | return $res; 318 | } 319 | 320 | $res 321 | } 322 | 323 | sub rt_dom_del_entry { 324 | my ($session, $param, $request) = @_; 325 | my $res; 326 | 327 | eval { 328 | # Load :domain and search for corresponding data 329 | my $app = app->new(get_cfg()); 330 | 331 | my $user = $app->auth($$session{login}, $$session{passwd}); 332 | 333 | unless ( $user && ( $$user{admin} || 334 | $app->is_owning_domain($$user{login}, $$param{domain}))) { 335 | $app->disconnect(); 336 | $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; 337 | $$res{route} = '/'; 338 | return $res; 339 | } 340 | 341 | my @missingitems; 342 | my @items = qw/domain name ttl type rdata/; 343 | 344 | if ($$param{type} && $$param{type} eq 'SRV') { 345 | push @items, qw/priority weight port/; 346 | } 347 | elsif ($$param{type} && $$param{type} eq 'MX') { 348 | push @items, qw/priority/; 349 | } 350 | 351 | for(@items) { 352 | push @missingitems, $_ unless($$param{$_}); 353 | } 354 | 355 | for(@items) { 356 | say "::::::::: $_ : $$param{$_}" if $$param{$_}; 357 | } 358 | 359 | if(@missingitems != 0) { 360 | $$res{deferred}{errmsg} = "Il manque : " . join ', ', @missingitems; 361 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 362 | return $res; 363 | } 364 | 365 | my $rdata = $$param{rdata}; 366 | my $name = $$param{name}; 367 | 368 | $name =~ s/@/$$param{domain}./; 369 | 370 | # si le type est A, AAAA, SRV, TXT, CNAME, MX, NS 371 | # le name doit être un domaine 372 | # si ce domaine n'est pas absolu, rajouter ".domain." 373 | if($$param{type} =~ /^(A|AAAA|SRV|TXT|CNAME|MX|NS)$/ && $name !~ /\.$/){ 374 | $name .= ".$$param{domain}."; 375 | } 376 | 377 | # si le type est CNAME, MX, NS ou PTR 378 | # le rdata doit être un domaine 379 | # si ce domaine n'est pas absolu, rajouter ".domain." 380 | if($$param{type} =~ /^(CNAME|SRV|MX|NS|PTR)$/) { 381 | $rdata =~ s/@/$$param{domain}./; 382 | $rdata .= ".$$param{domain}." if $rdata !~ /\.$/; 383 | } 384 | 385 | my $zone = $app->get_zone( $$param{domain} ); 386 | my $zf = $zone->get_zonefile(); 387 | 388 | my $str_del = "$name $$param{ttl} $$param{type} "; 389 | 390 | if( $$param{type} eq 'SRV') { 391 | $str_del .= 392 | "$$param{priority} $$param{weight} $$param{port} $rdata"; 393 | } 394 | elsif ($$param{type} eq 'MX') { 395 | $str_del .= "$$param{priority} $rdata"; 396 | } 397 | else { 398 | $str_del .= "$rdata"; 399 | } 400 | 401 | $zf->rr_del_raw( $str_del ); 402 | $zone->update( $zf ); 403 | 404 | $app->disconnect(); 405 | }; 406 | 407 | $$res{route} = '/domain/details/'. $$param{domain}; 408 | 409 | $res 410 | } 411 | 412 | sub rt_dom_del { 413 | my ($session, $param, $request) = @_; 414 | my $res; 415 | 416 | unless( $$param{domain} ) { 417 | $$res{deferred}{errmsg} = q; 418 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 419 | return $res; 420 | } 421 | 422 | if( ! is_domain_name($$param{domain})) { 423 | $$res{deferred}{errmsg} = q; 424 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 425 | return $res; 426 | } 427 | 428 | eval { 429 | my $app = app->new(get_cfg()); 430 | my $user = $app->auth($$session{login}, $$session{passwd}); 431 | 432 | unless ( $user && ( $$user{admin} || 433 | $app->is_owning_domain($$user{login}, $$param{domain}))) { 434 | $app->disconnect(); 435 | $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; 436 | $$res{route} = '/'; 437 | return $res; 438 | } 439 | 440 | $app->delete_domain($$param{domain}); 441 | $app->disconnect(); 442 | }; 443 | 444 | if($@) { 445 | $$res{deferred}{errmsg} = q{Impossible de supprimer le domaine. } . $@; 446 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 447 | return $res; 448 | } 449 | 450 | if( $$request{referer} =~ "/domain/details" ) { 451 | $$res{route} = '/user/home'; 452 | } 453 | else { 454 | $$res{route} = $$request{referer}; 455 | } 456 | 457 | $res 458 | } 459 | 460 | sub rt_dom_add { 461 | my ($session, $param) = @_; 462 | my $res; 463 | 464 | $$res{route} = '/user/home'; 465 | 466 | # check if user is logged 467 | unless( $$session{login}) { 468 | $$res{deferred}{errmsg} = q{Vous n'êtes pas enregistré. }; 469 | $$res{sessiondestroy} = 1; 470 | $$res{route} = '/'; 471 | return $res; 472 | } 473 | 474 | # check if domain parameter is set 475 | unless( $$param{domain} && length $$param{domain} > 0) { 476 | $$res{deferred}{errmsg} = 477 | q{Domaine personnel non renseigné correctement. }; 478 | return $res; 479 | } 480 | 481 | # check if tld parameter is set 482 | unless( $$param{tld} && length $$param{tld} > 0) { 483 | $$res{deferred}{errmsg} = q{Choix du domaine non fait. }; 484 | return $res; 485 | } 486 | 487 | if(is_reserved($$param{domain})) { 488 | $$res{deferred}{errmsg} = q{Nom de domaine réservé. }; 489 | } 490 | elsif ( ! is_domain_name($$param{domain}) ) { 491 | $$res{deferred}{errmsg} = 492 | q{Nom de domaine choisi comportant des caractères invalides. }; 493 | } 494 | elsif ( ! is_valid_tld($$param{tld}) ) { 495 | $$res{deferred}{errmsg} = 496 | q{Mauvais choix de domaine. }; 497 | } 498 | else { 499 | 500 | my $domain = $$param{domain} . $$param{tld}; 501 | 502 | eval { 503 | my $app = app->new(get_cfg()); 504 | my $user = $app->auth($$session{login}, $$session{passwd}); 505 | $app->add_domain( $$user{login}, $domain ); 506 | 507 | $$res{addsession}{domainName} = $$param{domain}; 508 | $$res{deferred}{succmsg} = 509 | q{Le nom de domaine a bien été réservé ! }; 510 | 511 | $app->disconnect(); 512 | }; 513 | 514 | if( $@ ) { 515 | $$res{deferred}{errmsg} = q{Une erreur est survenue. } . $@; 516 | } 517 | 518 | } 519 | 520 | $res 521 | } 522 | 523 | sub rt_dom_details { 524 | my ($session, $param, $request) = @_; 525 | my $res; 526 | 527 | # check if user is logged & if domain parameter is set 528 | unless($$session{login}) { 529 | $$res{deferred}{errmsg} = q{Session inactive.}; 530 | $$res{route} = '/'; 531 | return $res; 532 | } 533 | 534 | unless($$param{domain}) { 535 | $$res{deferred}{errmsg} = q{Domaine non renseigné.}; 536 | $$res{route} = '/'; 537 | return $res; 538 | } 539 | 540 | my $app; 541 | eval { 542 | $app = app->new(get_cfg()); 543 | 544 | my $user = $app->auth($$session{login}, $$session{passwd}); 545 | 546 | unless ( $user && ( $$user{admin} || 547 | $app->is_owning_domain($$user{login}, $$param{domain}))) { 548 | $app->disconnect(); 549 | $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; 550 | $$res{route} = '/'; 551 | return $res; 552 | } 553 | 554 | my $zone = $app->get_zone( $$param{domain} ); 555 | my $zf = $zone->get_zonefile(); 556 | 557 | $app->disconnect(); 558 | 559 | $$res{template} = 'details'; 560 | $$res{params} = { 561 | login => $$session{login} 562 | , admin => $$user{admin} 563 | , domain => $$param{domain} 564 | , domain_zone => $zf->dump() 565 | , user_ip => $$request{address} 566 | }; 567 | 568 | if($$param{expert}) { 569 | $$res{params}{expert} = 1; 570 | } 571 | else { 572 | $$res{params}{zone} = $zf->rr_array_to_array_stripped(); 573 | } 574 | }; 575 | 576 | if($@) { 577 | $app->disconnect() if $app; 578 | $$res{deferred}{errmsg} = q{Une erreur est survenue. } . $@; 579 | $$res{route} = '/'; 580 | return $res; 581 | } 582 | 583 | $res 584 | } 585 | 586 | sub rt_dom_add_entry { 587 | my ($session, $param, $request) = @_; 588 | my $res; 589 | 590 | unless( $$session{login} && $$param{domain} ) { 591 | $$res{route} = '/'; 592 | return $res; 593 | } 594 | 595 | $$res{route} = '/domain/details/'. $$param{domain}; 596 | 597 | my @missingitems; 598 | my @items = qw/domain type name ttl rdata/; 599 | 600 | if($$param{type} && $$param{type} eq 'MX') { 601 | push @items, qw/priority/; 602 | } 603 | 604 | if($$param{type} && $$param{type} eq 'SRV') { 605 | push @items, qw/priority weight port/; 606 | } 607 | 608 | for(@items) { 609 | push @missingitems, $_ unless($$param{$_}); 610 | } 611 | 612 | if(@missingitems != 0) { 613 | $$res{deferred}{errmsg} = "Il manque : " . join ', ', @missingitems; 614 | return $res; 615 | } 616 | 617 | eval { 618 | # Perform tests on the different entries 619 | 620 | my $name = $$param{name}; 621 | 622 | $name =~ s/@/$$param{domain}./; 623 | 624 | if($name =~ /$$param{domain}$/) { 625 | $name .= '.'; 626 | } 627 | 628 | if($name !~ /\.$/) { 629 | $name .= ".$$param{domain}." 630 | } 631 | 632 | my $str_new = "$name $$param{ttl} $$param{type} "; 633 | my $rdata = $$param{rdata}; 634 | 635 | if($$param{type} =~ /^(CNAME|MX|NS|PTR|SRV)$/) { 636 | $rdata =~ s/@/$$param{domain}./; 637 | $rdata .= ".$$param{domain}." if( $rdata !~ /\.$/); 638 | } 639 | 640 | if ($$param{type} =~ /^(CNAME|MX|NS|PTR|SRV)$/i 641 | && ! is_domain_name ($rdata)) 642 | { 643 | $$res{deferred}{errmsg} = 644 | "Une entrée $$param{type} doit avoir un nom de domaine " 645 | . "(pas une URL, pas de http://) : '$rdata' n'est pas correct."; 646 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 647 | return $res; 648 | } 649 | 650 | if ($$param{type} eq 'A' && ! is_ipv4($rdata)) { 651 | $$res{deferred}{errmsg} = 652 | "Il faut une adresse IPv4 pour un enregistrement de type A." 653 | . " Ceci n'est pas une adresse IPv4 : $rdata"; 654 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 655 | return $res; 656 | } 657 | 658 | if ($$param{type} eq 'AAAA' && ! is_ipv6($rdata)) { 659 | $$res{deferred}{errmsg} = 660 | "Il faut une adresse IPv6 pour un enregistrement de type AAAA." 661 | . " Ceci n'est pas une adresse IPv6 : $rdata"; 662 | $$res{route} = ($$request{referer}) ? $$request{referer} : '/'; 663 | return $res; 664 | } 665 | 666 | if($$param{type} eq "MX") { 667 | $str_new .= "$$param{priority} $rdata"; 668 | } 669 | elsif ($$param{type} eq "SRV") { 670 | $str_new .= 671 | "$$param{priority} $$param{weight} $$param{port} $rdata"; 672 | } 673 | else { 674 | $str_new .= "$rdata"; 675 | } 676 | 677 | # Add the entry 678 | 679 | my $app = app->new(get_cfg()); 680 | my $user = $app->auth($$session{login}, $$session{passwd}); 681 | 682 | unless ( $user && ( $$user{admin} || 683 | $app->is_owning_domain($$user{login}, $$param{domain}))) { 684 | $app->disconnect(); 685 | $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; 686 | $$res{route} = '/'; 687 | return $res; 688 | } 689 | 690 | my $zone = $app->get_zone( $$param{domain} ); 691 | my $zf = $zone->get_zonefile(); 692 | $zf->rr_add_raw($str_new); 693 | $zf->new_serial(); 694 | $zone->update( $zf ); 695 | 696 | $app->disconnect(); 697 | }; 698 | 699 | if ( $@ ) { 700 | $$res{deferred}{errmsg} = q{Problème à l'ajout d'une entrée. }. $@; 701 | } 702 | 703 | $res 704 | } 705 | 706 | sub rt_dom_updateraw { 707 | my ($session, $param, $request) = @_; 708 | my $res; 709 | 710 | # check if user is logged & if domain parameter is set 711 | unless($$session{login} && $$param{domain}) { 712 | $$res{sessiondestroy} = 1; 713 | $$res{route} = '/'; 714 | return $res; 715 | } 716 | 717 | my @missingitems; 718 | 719 | for(qw/domain zoneupdated/) { 720 | push @missingitems, $_ unless($$param{$_}); 721 | } 722 | 723 | if(@missingitems != 0) { 724 | $$res{deferred}{errmsg} = "Il manque : " . join ', ', @missingitems; 725 | $$res{route} = '/user/home'; 726 | return $res; 727 | } 728 | 729 | eval { 730 | my $app = app->new(get_cfg()); 731 | my $user = $app->auth($$session{login}, $$session{passwd}); 732 | 733 | # if the user exists and if 734 | # he is admin or he owns the requested domain 735 | unless ( $user && ( $$user{admin} || 736 | $app->is_owning_domain($$user{login}, $$param{domain}))) { 737 | $app->disconnect(); 738 | $$res{deferred}{errmsg} = q{Donnée privée, petit coquin. ;) }; 739 | $$res{route} = '/'; 740 | return $res; 741 | } 742 | else { 743 | my $zone = $app->get_zone( $$param{domain} ); 744 | my $zf = $zone->update_raw( $$param{zoneupdated} ); 745 | $zone->update( $zf ); 746 | $$res{route} = '/domain/details/' . $$param{domain}; 747 | } 748 | 749 | $app->disconnect(); 750 | }; 751 | 752 | if($@) { 753 | $$res{deferred}{errmsg} = $@; 754 | $$res{route} = '/user/home'; 755 | } 756 | 757 | $res 758 | } 759 | 760 | 1; 761 | -------------------------------------------------------------------------------- /lib/rt/domainfake.pm: -------------------------------------------------------------------------------- 1 | package rt::domainfake; 2 | 3 | use v5.14; 4 | use configuration ':all'; 5 | use encryption ':all'; 6 | use util ':all'; 7 | use app; 8 | use utf8; 9 | use open qw/:std :utf8/; 10 | use Dancer ':syntax'; 11 | use Data::Dump qw( dump ); 12 | use Data::Validate::IP qw(is_ipv4 is_ipv6); 13 | use MIME::Base64 qw(encode_base64 decode_base64); 14 | 15 | use Exporter 'import'; 16 | # what we want to export eventually 17 | our @EXPORT_OK = qw/ 18 | rt_dom_cli_mod_entry_fake 19 | rt_dom_cli_autoupdate_fake 20 | rt_dom_mod_entry_fake 21 | rt_dom_del_entry_fake 22 | rt_dom_del_fake 23 | rt_dom_add_fake 24 | rt_dom_details_fake 25 | rt_dom_add_entry_fake 26 | rt_dom_updateraw_fake 27 | /; 28 | 29 | # bundle of exports (tags) 30 | our %EXPORT_TAGS = ( all => [qw/ 31 | rt_dom_cli_mod_entry_fake 32 | rt_dom_cli_autoupdate_fake 33 | rt_dom_mod_entry_fake 34 | rt_dom_del_entry_fake 35 | rt_dom_del_fake 36 | rt_dom_add_fake 37 | rt_dom_details_fake 38 | rt_dom_add_entry_fake 39 | rt_dom_updateraw_fake 40 | /] ); 41 | 42 | sub rt_dom_cli_autoupdate_fake { 43 | my ($session, $param, $request) = @_; 44 | my $res; 45 | $res 46 | } 47 | 48 | sub rt_dom_cli_mod_entry_fake { 49 | my ($session, $param, $request) = @_; 50 | my $res; 51 | $res 52 | } 53 | 54 | sub rt_dom_mod_entry_fake { 55 | my ($session, $param, $request) = @_; 56 | my $res; 57 | $$res{route} = '/domain/details/toto.netlib.re'; 58 | $res 59 | } 60 | 61 | sub rt_dom_del_entry_fake { 62 | my ($session, $param, $request) = @_; 63 | my $res; 64 | $$res{route} = '/domain/details/toto.netlib.re'; 65 | $res 66 | } 67 | 68 | sub rt_dom_del_fake { 69 | my ($session, $param, $request) = @_; 70 | my $res; 71 | $$res{route} = $$request{referer}; 72 | $res 73 | } 74 | 75 | sub rt_dom_add_fake { 76 | my ($session, $param) = @_; 77 | my $res; 78 | $$res{route} = '/user/home'; 79 | $res 80 | } 81 | 82 | sub rt_dom_details_fake { 83 | my ($session, $param, $request) = @_; 84 | my $res; 85 | 86 | $$res{template} = 'details'; 87 | $$res{params} = { 88 | login => "toto" 89 | , admin => 1 90 | , domain => "toto.netlib.re." 91 | , domain_zone => " 92 | example.com. IN SOA ns.example.com. username.example.com. ( 2007120710 1d 2h 4w 1h ) 93 | example.com. 3600 IN NS ns 94 | example.com. 3600 IN NS ns.somewhere.example. 95 | example.com. 3600 IN MX 10 mail.example.com. 96 | @ 3600 IN MX 20 mail2.example.com. 97 | @ 3600 IN MX 50 mail3 98 | example.com. 3600 IN A 192.0.2.1 99 | example.com 3600 IN AAAA 3600 2001:db8:10::1 100 | ns 3600 IN A 192.0.2.2 101 | example.com 3600 IN AAAA 2001:db8:10::2 102 | www 3600 IN CNAME example.com. 103 | wwwtest 3600 IN CNAME www 104 | mail 3600 IN A 192.0.2.3 105 | mail2 3600 IN A 192.0.2.4 106 | mail3 3600 IN A 192.0.2.5 107 | " 108 | , user_ip => $$request{address} 109 | }; 110 | 111 | $$res{params}{zone} =[ 112 | { qw/type A name bla ttl 30 rdata 10.0.0.1/ } 113 | ,{ qw/type AAAA name www ttl 36 rdata fe80::de4a:3eff:fe01:3b44/ } 114 | ,{ qw/type CNAME name web ttl 36 rdata www/ } 115 | ,{ qw/type MX name mail ttl 3600 priority 10 rdata web/ } 116 | ,{ qw/type SRV name _sip._tcp.example.com. ttl 86400 priority 0 117 | weight 5 port 5060 rdata sipserver.example.com./ } 118 | ]; 119 | 120 | if($$param{expert}) { 121 | $$res{params}{expert} = 1; 122 | } 123 | 124 | $res 125 | } 126 | 127 | sub rt_dom_add_entry_fake { 128 | my ($session, $param, $request) = @_; 129 | my $res; 130 | $$res{route} = '/domain/details/toto.netlib.re'; 131 | $res 132 | } 133 | 134 | sub rt_dom_updateraw_fake { 135 | my ($session, $param, $request) = @_; 136 | my $res; 137 | $$res{route} = '/domain/details/toto.netlib.re'; 138 | $res 139 | } 140 | 141 | 1; 142 | -------------------------------------------------------------------------------- /lib/rt/root.pm: -------------------------------------------------------------------------------- 1 | package rt::root; 2 | 3 | use configuration ':all'; 4 | use app; 5 | use utf8; 6 | use open qw/:std :utf8/; 7 | 8 | use Exporter 'import'; 9 | # what we want to export eventually 10 | our @EXPORT_OK = qw/rt_root/; 11 | 12 | # bundle of exports (tags) 13 | our %EXPORT_TAGS = ( all => [qw/rt_root/] ); 14 | 15 | sub rt_root { 16 | my ($session) = @_; 17 | my $res; 18 | 19 | $$res{template} = 'index'; 20 | 21 | if( exists $$session{login} && length $$session{login} > 0) { 22 | eval { 23 | my $app = app->new(get_cfg()); 24 | my $user = $app->auth($$session{login}, $$session{passwd}); 25 | 26 | if( $user ) { 27 | $$res{params} = { 28 | login => $$session{login} 29 | , admin => $$user{admin} 30 | , domains => $$user{domains} 31 | }; 32 | } 33 | $app->disconnect(); 34 | }; 35 | 36 | if( $@ ) { 37 | $$res{deferred}{errmsg} = q{Une erreur est survenue. } . $@; 38 | $$res{sessiondestroy} = 1; 39 | } 40 | 41 | } 42 | 43 | $res 44 | } 45 | 46 | 1; 47 | -------------------------------------------------------------------------------- /lib/rt/rootfake.pm: -------------------------------------------------------------------------------- 1 | package rt::rootfake; 2 | 3 | use configuration ':all'; 4 | use app; 5 | use utf8; 6 | use open qw/:std :utf8/; 7 | 8 | use Exporter 'import'; 9 | # what we want to export eventually 10 | our @EXPORT_OK = qw/rt_root_fake/; 11 | 12 | # bundle of exports (tags) 13 | our %EXPORT_TAGS = ( all => [qw/rt_root_fake/] ); 14 | 15 | sub rt_root_fake { 16 | my ($session) = @_; 17 | my $res; 18 | 19 | $$res{template} = 'index'; 20 | $$res{params} = { 21 | login => "toto" 22 | , admin => 1 23 | , domains => qw/toto.netlib.re/ 24 | }; 25 | 26 | $res 27 | } 28 | 29 | 1; 30 | -------------------------------------------------------------------------------- /lib/rt/user.pm: -------------------------------------------------------------------------------- 1 | package rt::user; 2 | 3 | use v5.14; 4 | use configuration ':all'; 5 | use encryption ':all'; 6 | use app; 7 | use utf8; 8 | use open qw/:std :utf8/; 9 | 10 | use YAML::XS; 11 | 12 | use Exporter 'import'; 13 | # what we want to export eventually 14 | our @EXPORT_OK = qw/ 15 | rt_user_login 16 | rt_user_del 17 | rt_user_toggleadmin 18 | rt_user_subscribe 19 | rt_user_changepasswd 20 | rt_user_add 21 | rt_user_home 22 | /; 23 | 24 | # bundle of exports (tags) 25 | our %EXPORT_TAGS = ( all => [qw/ 26 | rt_user_login 27 | rt_user_del 28 | rt_user_toggleadmin 29 | rt_user_subscribe 30 | rt_user_changepasswd 31 | rt_user_add 32 | rt_user_home 33 | /] ); 34 | 35 | sub rt_user_login { 36 | my ($session, $param, $request) = @_; 37 | my $res; 38 | 39 | # Check if user is already logged 40 | if ( exists $$session{login} && length $$session{login} > 0 ) { 41 | $$res{deferred}{errmsg} = q{Vous êtes déjà connecté.}; 42 | $$res{route} = '/'; 43 | return $res; 44 | } 45 | 46 | # Check user login and password 47 | unless ( exists $$param{login} 48 | && exists $$param{password} 49 | && length $$param{login} > 0 50 | && length $$param{password} > 0 ) { 51 | $$res{deferred}{errmsg} = q{Vous n'avez pas renseigné tous les paramètres.}; 52 | $$res{route} = '/'; 53 | return $res; 54 | } 55 | 56 | eval { 57 | my $app = app->new(get_cfg()); 58 | my $pass = encrypt($$param{password}); 59 | my $user = $app->auth($$param{login}, $pass); 60 | 61 | unless( $user ) { 62 | $$res{deferred}{errmsg} = 63 | q{Impossible de se connecter (login ou mot de passe incorrect).}; 64 | $$res{route} = '/'; 65 | return $res; 66 | } 67 | 68 | $$res{addsession}{login} = $$param{login}; 69 | $$res{addsession}{passwd} = $pass; 70 | # TODO adds a freeze feature, not used for now 71 | # $$res{addsession}{user} = freeze( $user ); 72 | 73 | if( $$user{admin} ) { 74 | $$res{route} = '/admin'; 75 | } 76 | else { 77 | $$res{route} = '/user/home'; 78 | } 79 | 80 | $app->disconnect(); 81 | }; 82 | 83 | if( $@ ) { 84 | $$res{deferred}{errmsg} = q{Impossible de se connecter ! }; 85 | $$res{sessiondestroy} = 1; 86 | $$res{route} = '/'; 87 | } 88 | 89 | $res 90 | } 91 | 92 | sub rt_user_del { 93 | my ($session, $param, $request) = @_; 94 | my $res; 95 | 96 | unless ( $$param{user} ) { 97 | $$res{deferred}{errmsg} = q{Le nom d'utilisateur n'est pas renseigné.}; 98 | return $res; 99 | } 100 | 101 | eval { 102 | my $app = app->new(get_cfg()); 103 | 104 | my $user = $app->auth($$session{login}, $$session{passwd}); 105 | 106 | if ( $user && $$user{admin} || $$session{login} eq $$param{user} ) { 107 | $app->delete_user($$param{user}); 108 | } 109 | $app->disconnect(); 110 | }; 111 | 112 | if ( $@ ) { 113 | $$res{deferred}{errmsg} = 114 | "L'utilisateur $$res{user} n'a pas pu être supprimé. $@"; 115 | } 116 | 117 | if( $$request{referer} ) { 118 | $$res{route} = $$request{referer}; 119 | } 120 | else { 121 | $$res{route} = '/'; 122 | } 123 | 124 | $res 125 | } 126 | 127 | sub rt_user_toggleadmin { 128 | my ($session, $param, $request) = @_; 129 | my $res; 130 | 131 | unless( $$param{user} ) { 132 | $$res{deferred}{errmsg} = q{L'utilisateur n'est pas défini.}; 133 | $$res{route} = $$request{referer}; 134 | return $res; 135 | } 136 | 137 | eval { 138 | my $app = app->new(get_cfg()); 139 | 140 | my $user = $app->auth($$session{login}, $$session{passwd}); 141 | 142 | unless ( $user && $$user{admin} ) { 143 | $$res{deferred}{errmsg} = q{Vous n'êtes pas administrateur.}; 144 | return $res; 145 | } 146 | 147 | $app->toggle_admin($$param{user}); 148 | $app->disconnect(); 149 | }; 150 | 151 | if( $$request{referer} =~ '/admin' ) { 152 | $$res{route} = $$request{referer}; 153 | } 154 | else { 155 | $$res{route} = '/'; 156 | } 157 | 158 | $res 159 | } 160 | 161 | sub rt_user_subscribe { 162 | my ($session, $param, $request) = @_; 163 | my $res; 164 | 165 | if( $$session{login} ) { 166 | $$res{route} = '/user/home'; 167 | } 168 | else { 169 | $$res{template} = 'subscribe'; 170 | } 171 | 172 | $res 173 | } 174 | 175 | sub rt_user_changepasswd { 176 | my ($session, $param, $request) = @_; 177 | my $res; 178 | 179 | unless ( $$session{login} && $$param{password} ) { 180 | $$res{deferred}{errmsg} = q{Identifiant ou mot de passe non renseigné.}; 181 | $$res{route} = '/user/home'; 182 | return $res; 183 | } 184 | 185 | eval { 186 | my $pass = encrypt($$param{password}); 187 | my $app = app->new(get_cfg()); 188 | 189 | $app->update_passwd($$session{login}, $pass); 190 | $app->disconnect(); 191 | 192 | $$res{deferred}{succmsg} = q{Changement de mot de passe effectué !}; 193 | $$res{addsession}{passwd} = $pass; 194 | $$res{route} = '/user/home'; 195 | }; 196 | 197 | if($@) { 198 | $$res{deferred}{errmsg} = q{Changement de mot de passe impossible !.}; 199 | $$res{route} = '/user/subscribe'; 200 | return $res; 201 | } 202 | 203 | $res 204 | } 205 | 206 | sub rt_user_add { 207 | my ($session, $param, $request) = @_; 208 | my $res; 209 | 210 | unless ( $$param{login} && $$param{password} && $$param{password2} ) { 211 | $$res{deferred}{errmsg} = q{Identifiant ou mot de passe non renseigné.}; 212 | $$res{route} = '/user/subscribe'; 213 | return $res; 214 | } 215 | 216 | unless ( $$param{password} eq $$param{password2} ) { 217 | $$res{deferred}{errmsg} = q{Les mots de passes ne sont pas identiques.}; 218 | $$res{route} = '/user/subscribe'; 219 | return $res; 220 | } 221 | 222 | 223 | eval { 224 | my $pass = encrypt($$param{password}); 225 | 226 | my $app = app->new(get_cfg()); 227 | 228 | $app->register_user($$param{login}, $pass); 229 | $app->disconnect(); 230 | 231 | $$res{addsession}{login} = $$param{login}; 232 | $$res{addsession}{passwd} = $pass; 233 | $$res{route} = '/user/home'; 234 | }; 235 | 236 | if($@) { 237 | $$res{deferred}{errmsg} = q{Ce pseudo est déjà pris.}; 238 | $$res{route} = '/user/subscribe'; 239 | return $res; 240 | } 241 | 242 | $res 243 | } 244 | 245 | sub rt_user_home { 246 | my ($session, $param, $request) = @_; 247 | my $res; 248 | 249 | $$res{template} = 'home'; 250 | 251 | eval { 252 | my $app = app->new(get_cfg()); 253 | 254 | my $user = $app->auth($$session{login}, $$session{passwd}); 255 | 256 | unless( $user ) { 257 | $$res{deferred}{errmsg} = q{Problème de connexion à votre compte.}; 258 | $$res{sessiondestroy} = 1; 259 | $$res{route} = '/'; 260 | return $res; 261 | } 262 | 263 | my $domains = $app->get_domains($$session{login}); 264 | 265 | my $dn = $$session{domainName}; 266 | 267 | #$$res{delsession}{domainName}; 268 | 269 | $$res{params} = { 270 | login => $$session{login} 271 | , admin => $$user{admin} 272 | , domains => $domains 273 | , provideddomains => $$app{tld} 274 | , domainName => $dn 275 | }; 276 | 277 | $app->disconnect(); 278 | }; 279 | 280 | if( $@ ) { 281 | $$res{sessiondestroy} = 1; 282 | $$res{deferred}{errmsg} = q{Problème d'authentification.} . $@; 283 | $$res{route} = '/'; 284 | } 285 | 286 | $res 287 | } 288 | 289 | 1; 290 | -------------------------------------------------------------------------------- /lib/rt/userfake.pm: -------------------------------------------------------------------------------- 1 | package rt::userfake; 2 | 3 | use v5.14; 4 | use configuration ':all'; 5 | use encryption ':all'; 6 | use app; 7 | use utf8; 8 | use open qw/:std :utf8/; 9 | 10 | use YAML::XS; 11 | 12 | use Exporter 'import'; 13 | # what we want to export eventually 14 | our @EXPORT_OK = qw/ 15 | rt_user_login_fake 16 | rt_user_del_fake 17 | rt_user_toggleadmin_fake 18 | rt_user_subscribe_fake 19 | rt_user_changepasswd_fake 20 | rt_user_add_fake 21 | rt_user_home_fake 22 | /; 23 | 24 | # bundle of exports (tags) 25 | our %EXPORT_TAGS = ( all => [qw/ 26 | rt_user_login_fake 27 | rt_user_del_fake 28 | rt_user_toggleadmin_fake 29 | rt_user_subscribe_fake 30 | rt_user_changepasswd_fake 31 | rt_user_add_fake 32 | rt_user_home_fake 33 | /] ); 34 | 35 | sub rt_user_login_fake { 36 | my ($session, $param, $request) = @_; 37 | my $res; 38 | $$res{route} = '/admin'; 39 | $res 40 | } 41 | 42 | sub rt_user_del_fake { 43 | my ($session, $param, $request) = @_; 44 | my $res; 45 | $$res{route} = $$request{referer}; 46 | $res 47 | } 48 | 49 | sub rt_user_toggleadmin_fake { 50 | my ($session, $param, $request) = @_; 51 | my $res; 52 | $$res{route} = $$request{referer}; 53 | $res 54 | } 55 | 56 | sub rt_user_subscribe_fake { 57 | my ($session, $param, $request) = @_; 58 | my $res; 59 | $$res{route} = '/user/home'; 60 | $res 61 | } 62 | 63 | sub rt_user_changepasswd_fake { 64 | my ($session, $param, $request) = @_; 65 | my $res; 66 | $$res{route} = '/user/home'; 67 | $res 68 | } 69 | 70 | sub rt_user_add_fake { 71 | my ($session, $param, $request) = @_; 72 | my $res; 73 | $$res{route} = '/user/home'; 74 | $res 75 | } 76 | 77 | sub rt_user_home_fake { 78 | my ($session, $param, $request) = @_; 79 | my $res; 80 | $$res{template} = 'home'; 81 | $$res{params} = { 82 | login => "toto" 83 | , admin => 1 84 | , domains => [ {qw/domain toto.netlib.re/} ] 85 | , provideddomains => [ qw/netlib.re. codelib.re./ ] 86 | , domainName => '' 87 | }; 88 | $res 89 | } 90 | 91 | 1; 92 | -------------------------------------------------------------------------------- /lib/testapp.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use v5.14; 3 | use strict; 4 | use warnings; 5 | use utf8; 6 | use open qw/:std :utf8/; 7 | 8 | use File::Basename; 9 | use YAML::XS; 10 | use configuration ':all'; 11 | use util ':all'; 12 | use app; 13 | 14 | use rt::root ':all'; 15 | use rt::domain ':all'; 16 | use rt::user ':all'; 17 | use rt::admin ':all'; 18 | 19 | #my $test_updateraw = sub { 20 | # rt_dom_updateraw 21 | # get_session( qw/login passwd/ ) 22 | # , get_param( qw/domain zoneupdated/ ); # TODO verify this 23 | #}; 24 | 25 | my $test_update = sub { 26 | rt_dom_update 27 | { qw/login test passwd test/ } 28 | , { qw/type A 29 | name www 30 | value 10.0.0.1 31 | ttl 100 32 | priority 1 33 | domain test.netlib.re./ }; 34 | }; 35 | 36 | #my $test_detail = sub { 37 | # rt_dom_details 38 | # get_session( qw/login passwd/ ) 39 | # , get_param( qw/domain expert/ ) 40 | # , get_request( qw/address referer/ ); 41 | #}; 42 | 43 | my $test_add_domain = sub { 44 | rt_dom_add 45 | { qw/login test passwd test/} 46 | , { qw/domain test tld .netlib.re./ }; 47 | }; 48 | 49 | my $test_del_domain = sub { 50 | rt_dom_del 51 | { qw/login test passwd test/ } 52 | , { qw/domain test.netlib.re./ } 53 | , { qw/address referer/ }; # TODO 54 | }; 55 | 56 | #my $test_del_entry = sub { 57 | # rt_dom_del_entry 58 | # get_session( qw/login passwd/ ) 59 | # , get_param( qw/domain name type host ttl/ ) 60 | # , get_request( qw/address referer/ ); 61 | #}; 62 | 63 | #my $test_mod_entry = sub { 64 | # rt_dom_mod_entry 65 | # get_session( qw/login passwd/ ) 66 | # , get_param( qw/domain name type host ttl/ ) 67 | # , get_request( qw/address referer/ ); 68 | #}; 69 | 70 | my $test_cli_mod_entry = sub { 71 | rt_dom_cli_mod_entry 72 | get_session( qw/login/ ) 73 | , get_param( qw/passwd domain name type host ttl ip/ ); 74 | }; 75 | 76 | #any ['get', 'post'] => '/admin' => sub { 77 | # rt_admin 78 | # get_session( qw/login passwd/ ); 79 | #}; 80 | 81 | #get '/home' => sub { 82 | # rt_user_home 83 | # get_session( qw/login passwd/ ) 84 | # , get_param( qw// ) 85 | # , get_request( qw// ); 86 | #}; 87 | 88 | my $test_del_user = sub { 89 | rt_user_del 90 | get_session( qw/login passwd/ ) 91 | , { qw/user test/ } 92 | , { qw/referer/ }; 93 | }; 94 | 95 | my $test_add_user = sub { 96 | rt_user_add 97 | { qw// } 98 | , { qw/login test password test password2 test/ } 99 | , { qw// }; 100 | }; 101 | 102 | say "Tests - "; 103 | 104 | # get '/subscribe' => sub { 105 | # rt_user_subscribe 106 | # get_session( qw/login/ ); 107 | # }; 108 | 109 | #my $test_toggle_admin = sub { 110 | # rt_user_toggleadmin 111 | # { qw/login passwd/ } 112 | # , get_param( qw/user/ ) 113 | # , get_request( qw/referer/ ); 114 | #}; 115 | -------------------------------------------------------------------------------- /lib/util.pm: -------------------------------------------------------------------------------- 1 | package util; 2 | use v5.10; 3 | 4 | use configuration ':all'; 5 | use YAML::XS; 6 | use Exporter 'import'; 7 | # what we want to export eventually 8 | our @EXPORT_OK = qw/is_domain_name is_valid_tld/; 9 | 10 | # bundle of exports (tags) 11 | our %EXPORT_TAGS = ( all => [qw/is_domain_name is_valid_tld/] ); 12 | 13 | # TODO we can check if dn matches our domain name 14 | sub is_domain_name { 15 | my ($dn) = @_; 16 | my $ndd = qr/^ 17 | ([a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]*[.])* 18 | [a-zA-Z0-9]+[a-zA-Z0-9-]*[a-zA-Z0-9]([.])? 19 | $/x; 20 | return $dn =~ $ndd; 21 | } 22 | 23 | sub is_valid_tld { 24 | my ($tld) = @_; 25 | 26 | my $cfg = get_cfg; 27 | 28 | grep { $_ eq $tld } @{$$cfg{tld}}; 29 | } 30 | 31 | 1; 32 | -------------------------------------------------------------------------------- /lib/zone.pm: -------------------------------------------------------------------------------- 1 | package zone; 2 | use v5.14; 3 | use Moo; 4 | 5 | use Modern::Perl; 6 | 7 | # TODO all this file is to redesign 8 | 9 | use getiface ':all'; 10 | use copycat ':all'; 11 | use fileutil ':all'; 12 | use configuration ':all'; 13 | #use Data::Dump qw( dump ); 14 | 15 | use zonefile; 16 | 17 | # primary dns interface 18 | has dnsi => ( is => 'rw', builder => '_void_arr'); 19 | 20 | # dns interface for secondary name servers 21 | has dnsisec => ( is => 'rw', builder => '_void'); 22 | 23 | has [ qw/tld tmpdir domain primarydnsserver secondarydnsserver slavedzones/ ] 24 | => qw/is ro required 1/; 25 | 26 | sub _void { my $x = ''; \$x; } 27 | sub _void_arr { [] } 28 | 29 | sub get_ztmp_file_ {my $s = shift; "$$s{tmpdir}/$$s{domain}" } 30 | sub get_ztpl_dir_ {my $s = shift; "$$s{dnsi}{mycfg}{zonedir}" } 31 | sub get_ztpl_file_ { 32 | my $s = shift; 33 | 34 | # for each TLD 35 | for(@{$$s{tld}}) { 36 | # if our domain is part of this TLD, get the right template 37 | if($$s{domain} =~ $_) { 38 | return $s->get_ztpl_dir_() . '/' . $_ . '.tpl'; 39 | } 40 | } 41 | 42 | die "There is no template for $$s{domain}."; 43 | } 44 | 45 | sub get_dnsserver_interface { 46 | my ($self, $dnsserver) = @_; 47 | my $cfg = { 48 | mycfg => $dnsserver 49 | , primarydnsserver => $$self{primarydnsserver} 50 | , secondarydnsserver => $$self{secondarydnsserver} 51 | , tmpdir => $$self{tmpdir} 52 | }; 53 | 54 | getiface $$dnsserver{app}, $cfg 55 | } 56 | 57 | sub get_dns_server_interfaces { 58 | my $self = shift; 59 | my $primary = $$self{primarydnsserver}; 60 | my $s = $$self{secondarydnsserver}; 61 | 62 | my $prim = $self->get_dnsserver_interface($primary); 63 | 64 | my @sec; 65 | for(@{$s}) { 66 | push @sec, $self->get_dnsserver_interface($_); 67 | } 68 | 69 | ($prim, [ @sec ]) 70 | } 71 | 72 | sub BUILD { 73 | my $self = shift; 74 | ($$self{dnsi}, $$self{dnsisec}) = $self->get_dns_server_interfaces() 75 | } 76 | 77 | # change the origin in a zone file template 78 | sub mod_orig_template { 79 | my ($file, $domain) = @_; 80 | say "s/CHANGEMEORIGIN/$domain/ on $file"; 81 | qx[sed -i "s/CHANGEMEORIGIN/$domain/" $file]; 82 | } 83 | 84 | sub get_remote_zf_ { 85 | my $self = shift; 86 | "$$self{dnsi}{mycfg}{zonedir}/$$self{domain}" 87 | } 88 | 89 | sub reload_secondary_dns_servers { 90 | my $self = shift; 91 | $_->reload_sec($$self{slavedzones}) for(@{$$self{dnsisec}}) 92 | } 93 | 94 | sub get_zonefile { 95 | my $self = shift; 96 | my $file = $self->get_remote_zf_(); 97 | my $dest = $self->get_ztmp_file_(); 98 | 99 | my $path = $dest; 100 | 101 | # dest is the filename 102 | if($dest =~ "://") { 103 | my $fileuri = URI->new($dest); 104 | $path = $fileuri->path; 105 | } 106 | 107 | if( -f $path ) { 108 | say "FILE $path already exists : do not copy from ns server"; 109 | } 110 | else { 111 | copycat ($file, $dest); 112 | } 113 | 114 | zonefile->new(domain => $$self{domain}, zonefile => $dest); 115 | } 116 | 117 | =pod 118 | copie du template pour créer une nouvelle zone 119 | update du serial 120 | ajout de la zone via dnsapp (rndc, knot…) 121 | retourne la zone + le nom de la zone 122 | =cut 123 | 124 | sub addzone { 125 | my ($self) = @_; 126 | 127 | my $tpl = $self->get_ztpl_file_(); 128 | my $tmpfile = $self->get_ztmp_file_(); 129 | 130 | copycat ($tpl, $tmpfile); # get the template 131 | 132 | # get the file path 133 | my $f = URI->new($tmpfile); 134 | 135 | # sed CHANGEMEORIGIN by the real origin 136 | mod_orig_template ($f->path, $$self{domain}); 137 | 138 | my $zonefile = zonefile->new(zonefile => $f->path 139 | , domain => $$self{domain}); 140 | $zonefile->new_serial(); # update the serial number 141 | 142 | # write the new zone tmpfile to disk 143 | write_file $f->path, $zonefile->dump(); 144 | 145 | my $file = $self->get_remote_zf_(); 146 | copycat ($tmpfile, $file); # put the final zone on the server 147 | unlink($f->path); # del the temporary file 148 | 149 | # add new zone on the primary ns 150 | $self->dnsi->primary_addzone($$self{domain}); 151 | 152 | # add new zone on secondary ns 153 | $self->reload_secondary_dns_servers() 154 | } 155 | 156 | =pod 157 | màj du serial 158 | push reload de la conf 159 | =cut 160 | 161 | sub update { 162 | my ($self, $zonefile) = @_; 163 | 164 | # update the serial number 165 | $zonefile->new_serial(); 166 | 167 | my $tmpfile = $self->get_ztmp_file_(); 168 | 169 | # write the new zone tmpfile to disk 170 | write_file $tmpfile, $zonefile->dump(); 171 | 172 | my $file = $self->get_remote_zf_(); 173 | copycat ($tmpfile, $file); # put the final zone on the server 174 | unlink($tmpfile); # del the temporary file 175 | 176 | $self->dnsi->reload($$self{domain}); 177 | } 178 | 179 | =pod 180 | udpate via the raw content of the zonefile 181 | =cut 182 | 183 | sub update_raw { 184 | my ($self, $zonetext) = @_; 185 | 186 | my $zonefile; 187 | my $file = $self->get_ztmp_file_(); 188 | 189 | # write the updated zone file to disk 190 | write_file $file, $zonetext; 191 | 192 | eval { $zonefile = zonefile->new(zonefile => $file); }; 193 | 194 | if( $@ ) { 195 | unlink($file); 196 | die 'zone update_raw, zonefile->new error. ' . $@; 197 | } 198 | 199 | unlink($file); 200 | 201 | $zonefile 202 | } 203 | 204 | sub del { 205 | my ($self) = @_; 206 | $self->dnsi->delzone($$self{domain}); 207 | $self->dnsi->reconfig(); 208 | $self->reload_secondary_dns_servers() 209 | } 210 | 211 | 1; 212 | -------------------------------------------------------------------------------- /lib/zonefile.pm: -------------------------------------------------------------------------------- 1 | package zonefile; 2 | use v5.14; 3 | use Net::DNS::RR; 4 | use Net::DNS::ZoneFile; 5 | use Moo; 6 | use utf8; 7 | use URI; 8 | use Data::Dumper; 9 | 10 | has zone => qw/is rw/ ; 11 | has [ qw/zonefile/ ] => qw/ is rw required 1/; 12 | 13 | # Simple functions to manipulate lists of Net::DNS::RR 14 | 15 | sub rr_array_del { 16 | my ($zones, $rr) = @_; 17 | my $todel = $rr->plain; 18 | utf8::decode($todel); 19 | [grep { my $v = $_->plain; utf8::decode($v); $v ne $rr->plain } @$zones] 20 | } 21 | 22 | sub rr_array_add { 23 | my ($zone, $rr) = @_; 24 | my @already_present = grep { $_->plain eq $rr->plain } @$zone; 25 | push @$zone, $rr unless @already_present; 26 | $zone 27 | } 28 | 29 | sub rr_array_new_serial { 30 | my $zones = shift; 31 | 32 | for(@{$zones}) { 33 | if($_->type =~ /SOA/) { 34 | my $serial = $_->serial; 35 | $_->serial($serial + 1); 36 | } 37 | } 38 | 39 | $zones 40 | } 41 | 42 | sub rr_array_serial { 43 | my $zones = shift; 44 | 45 | for(@{$zones}) { 46 | if($_->type =~ /SOA/) { 47 | return $_->serial; 48 | } 49 | } 50 | 51 | die "Impossible to get the zone serial." 52 | } 53 | 54 | sub rr_array_dump { 55 | my $zone = shift; 56 | my $dump = ''; 57 | 58 | # write the SOA record first 59 | for(@{$zone}) { 60 | if($_->type =~ /SOA/i) { 61 | $dump .= $_->string . "\n"; 62 | } 63 | } 64 | 65 | for(@{$zone}) { 66 | if($_->type !~ /SOA/i) { 67 | $dump .= $_->string . "\n"; 68 | } 69 | } 70 | 71 | utf8::decode($dump); 72 | 73 | $dump 74 | } 75 | 76 | 77 | sub BUILD { 78 | my ($self) = @_; 79 | 80 | my $path = $$self{zonefile}; 81 | 82 | # zonefile is the filename 83 | if($$self{zonefile} =~ "://") { 84 | my $fileuri = URI->new($$self{zonefile}); 85 | $path = $fileuri->path; 86 | } 87 | 88 | my $zonefile = Net::DNS::ZoneFile->new( $path ); 89 | my @zone = $zonefile->read; 90 | $$self{zone} = [ @zone ]; 91 | } 92 | 93 | sub new_serial { 94 | my $self = shift; 95 | $$self{zone} = rr_array_new_serial $$self{zone} 96 | } 97 | 98 | sub dump { 99 | my $self = shift; 100 | rr_array_dump $$self{zone} 101 | } 102 | 103 | sub serial { 104 | my ($self, $rr) = @_; 105 | rr_array_serial $$self{zone} 106 | } 107 | 108 | # remove a raw line that represents the RR 109 | sub rr_del_raw { 110 | my ($self, $rrline) = @_; 111 | utf8::decode($rrline); 112 | say "to delete raw : $rrline"; 113 | my $rr = Net::DNS::RR->new($rrline); 114 | say "to delete reformed : " . $rr->plain; 115 | $self->rr_del($rr) 116 | } 117 | 118 | sub rr_del { 119 | my ($self, $rr) = @_; 120 | $$self{zone} = rr_array_del $$self{zone}, $rr 121 | } 122 | 123 | # add a raw line that represents the RR 124 | sub rr_add_raw { 125 | my ($self, $rrline) = @_; 126 | utf8::decode($rrline); 127 | say "to add raw : $rrline"; 128 | my $rr = Net::DNS::RR->new($rrline); 129 | say "to add reformed : " . $rr->plain; 130 | $self->rr_add($rr) 131 | } 132 | 133 | sub rr_add { 134 | my ($self, $rr) = @_; 135 | $$self{zone} = rr_array_add $$self{zone}, $rr 136 | } 137 | 138 | sub rr_mod { 139 | my ($self, $rrline_old, $rrline_new) = @_; 140 | $self->rr_del_raw($rrline_old); 141 | $self->rr_add_raw($rrline_new); 142 | } 143 | 144 | sub rr_search { 145 | my ($self, $name, $type) = @_; 146 | my $rrlisttmp = $self->rr_array_to_array(); 147 | [ grep { $$_{name} eq $name && $$_{type} eq $type } @$rrlisttmp ] 148 | } 149 | 150 | sub search_domain { 151 | my ($self) = @_; 152 | my $rr_list = $self->rr_array_to_array(); 153 | 154 | my $soa = [ grep { $$_{type} eq "SOA" } @$rr_list ]; 155 | 156 | for(@$soa) { 157 | return $$_{name}; 158 | } 159 | 160 | die "No SOA in this domain."; 161 | } 162 | 163 | # to get all the records in relative 164 | sub rr_array_to_array_stripped { 165 | my $self = shift; 166 | my $rr_list = $self->rr_array_to_array(); 167 | 168 | my $domain = $self->search_domain(); 169 | 170 | for (@$rr_list) { 171 | $$_{name} =~ s/\.?$domain$//; 172 | $$_{name} = "@" if($$_{name} =~ /^$/); 173 | 174 | if($$_{type} =~ /^(CNAME|SRV|MX)$/) { 175 | $$_{rdata} =~ s/\.?$domain$//; 176 | $$_{rdata} = "@" if($$_{rdata} =~ /^$/); 177 | } 178 | } 179 | 180 | $rr_list; 181 | } 182 | 183 | sub rr_array_to_array { 184 | my ($self) = shift; 185 | my $rr_list; 186 | 187 | for(@{$$self{zone}}) { 188 | 189 | my @list = split / /, $_->plain; 190 | 191 | my $rr; 192 | $$rr{name} = $list[0]; 193 | $$rr{ttl} = $list[1]; 194 | $$rr{class} = $list[2]; 195 | $$rr{type} = $list[3]; 196 | 197 | utf8::decode($$rr{name}); 198 | utf8::decode($$rr{ttl}); 199 | utf8::decode($$rr{class}); 200 | utf8::decode($$rr{type}); 201 | 202 | if($list[3] =~ /SOA/) { 203 | $$rr{ns} = $list[4]; 204 | $$rr{postmaster} = $list[5]; 205 | $$rr{serial} = $list[6]; 206 | $$rr{refresh} = $list[7]; 207 | $$rr{retry} = $list[8]; 208 | $$rr{expire} = $list[9]; 209 | $$rr{minimum} = $list[10]; 210 | 211 | utf8::decode($$rr{ns}); 212 | utf8::decode($$rr{postmaster}); 213 | utf8::decode($$rr{serial}); 214 | utf8::decode($$rr{refresh}); 215 | utf8::decode($$rr{retry}); 216 | utf8::decode($$rr{expire}); 217 | utf8::decode($$rr{minimum}); 218 | } 219 | elsif($list[3] =~ /^(A(AAA)?|CNAME|NS)$/) { 220 | $$rr{rdata} = $list[4]; 221 | utf8::decode($$rr{rdata}); 222 | } 223 | elsif($list[3] =~ /^MX$/) { 224 | $$rr{priority} = $list[4]; 225 | $$rr{rdata} = $list[5]; 226 | 227 | utf8::decode($$rr{priority}); 228 | utf8::decode($$rr{rdata}); 229 | } 230 | elsif($list[3] =~ /^TXT$/) { 231 | $$rr{rdata} = $_->rdstring; 232 | utf8::decode($$rr{rdata}); 233 | } 234 | elsif($list[3] =~ /^SRV$/) { 235 | # _service._proto.name. TTL class SRV priority weight port target. 236 | # _sip._tcp.example.com. 86400 IN SRV 10 60 5060 bigbox.example.com. 237 | $$rr{priority} = $list[4]; 238 | $$rr{weight} = $list[5]; 239 | $$rr{port} = $list[6]; 240 | $$rr{rdata} = $list[7]; 241 | 242 | utf8::decode($$rr{priority}); 243 | utf8::decode($$rr{weight}); 244 | utf8::decode($$rr{port}); 245 | utf8::decode($$rr{rdata}); 246 | } 247 | elsif($list[3] =~ /^CAA$/) { 248 | # domain_name 10800 IN CAA 128 issue "letsencrypt.org" 249 | $$rr{weight} = $list[4]; 250 | $$rr{issue} = $list[5]; 251 | $$rr{certificateAutority} = $list[6]; 252 | utf8::decode($$rr{weight}); 253 | utf8::decode($$rr{issue}); 254 | utf8::decode($$rr{certificateAutority}); 255 | } 256 | else { 257 | $$rr{rdata} = $_->rdstring; 258 | utf8::decode($$rr{rdata}); 259 | say "This RR is not available : " . $_->plain; 260 | } 261 | 262 | push @$rr_list, $rr; 263 | } 264 | 265 | $rr_list 266 | } 267 | 268 | 1; 269 | -------------------------------------------------------------------------------- /notes.txt: -------------------------------------------------------------------------------- 1 | Développement web : 2 | 3 | 1. Créer la page d'accueil 4 | - doit fournir un lien vers la page de login ; 5 | - permet la recherche d'un nom de domaine ; 6 | - s'il n'est pas disponible, proposer des choix alternatifs (secondaire) ; 7 | 8 | 2. Page de login 9 | - doit vérifier si l'utilisateur existe bien dans la base de donnée ; 10 | - cf méthode dnsmanager::auth ; 11 | - si l'utilisateur est un administrateur, fournir un lien supplémentaire vers la page d'administration ; 12 | - cf lire le résultat renvoyé par auth (contient la liste des données de l'utilisateur stockés en bdd ; 13 | - la page d'administration doit refuser un non administrateur ; 14 | 15 | 3. Permettre à l'utilisateur de pouvoir visualiser ses zones, d'en demander de nouvelles ; 16 | - cf get_zone function ; 17 | - add_zone (doit envoyer une demande aux admins) ; 18 | 19 | 4. Créer la page d'administration 20 | - doit fournir la possibilité de valider la création d'un utilisateur ; 21 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Error 404 6 | 7 | 8 | 9 | 10 |

Error 404

11 |
12 |

Page Not Found

Sorry, this is the void.

13 |
14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/500.html: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | Error 500 6 | 7 | 8 | 9 | 10 |

Error 500

11 |
12 |

Internal Server Error

Wooops, something went wrong

13 |
14 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /public/css/bootstrap-theme.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Bootstrap v3.3.6 (http://getbootstrap.com) 3 | * Copyright 2011-2015 Twitter, Inc. 4 | * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) 5 | */.btn-danger,.btn-default,.btn-info,.btn-primary,.btn-success,.btn-warning{text-shadow:0 -1px 0 rgba(0,0,0,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 1px rgba(0,0,0,.075)}.btn-danger.active,.btn-danger:active,.btn-default.active,.btn-default:active,.btn-info.active,.btn-info:active,.btn-primary.active,.btn-primary:active,.btn-success.active,.btn-success:active,.btn-warning.active,.btn-warning:active{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-danger.disabled,.btn-danger[disabled],.btn-default.disabled,.btn-default[disabled],.btn-info.disabled,.btn-info[disabled],.btn-primary.disabled,.btn-primary[disabled],.btn-success.disabled,.btn-success[disabled],.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-danger,fieldset[disabled] .btn-default,fieldset[disabled] .btn-info,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-success,fieldset[disabled] .btn-warning{-webkit-box-shadow:none;box-shadow:none}.btn-danger .badge,.btn-default .badge,.btn-info .badge,.btn-primary .badge,.btn-success .badge,.btn-warning .badge{text-shadow:none}.btn.active,.btn:active{background-image:none}.btn-default{text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-o-linear-gradient(top,#fff 0,#e0e0e0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#e0e0e0));background-image:linear-gradient(to bottom,#fff 0,#e0e0e0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#dbdbdb;border-color:#ccc}.btn-default:focus,.btn-default:hover{background-color:#e0e0e0;background-position:0 -15px}.btn-default.active,.btn-default:active{background-color:#e0e0e0;border-color:#dbdbdb}.btn-default.disabled,.btn-default.disabled.active,.btn-default.disabled.focus,.btn-default.disabled:active,.btn-default.disabled:focus,.btn-default.disabled:hover,.btn-default[disabled],.btn-default[disabled].active,.btn-default[disabled].focus,.btn-default[disabled]:active,.btn-default[disabled]:focus,.btn-default[disabled]:hover,fieldset[disabled] .btn-default,fieldset[disabled] .btn-default.active,fieldset[disabled] .btn-default.focus,fieldset[disabled] .btn-default:active,fieldset[disabled] .btn-default:focus,fieldset[disabled] .btn-default:hover{background-color:#e0e0e0;background-image:none}.btn-primary{background-image:-webkit-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-o-linear-gradient(top,#337ab7 0,#265a88 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#265a88));background-image:linear-gradient(to bottom,#337ab7 0,#265a88 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#245580}.btn-primary:focus,.btn-primary:hover{background-color:#265a88;background-position:0 -15px}.btn-primary.active,.btn-primary:active{background-color:#265a88;border-color:#245580}.btn-primary.disabled,.btn-primary.disabled.active,.btn-primary.disabled.focus,.btn-primary.disabled:active,.btn-primary.disabled:focus,.btn-primary.disabled:hover,.btn-primary[disabled],.btn-primary[disabled].active,.btn-primary[disabled].focus,.btn-primary[disabled]:active,.btn-primary[disabled]:focus,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary,fieldset[disabled] .btn-primary.active,fieldset[disabled] .btn-primary.focus,fieldset[disabled] .btn-primary:active,fieldset[disabled] .btn-primary:focus,fieldset[disabled] .btn-primary:hover{background-color:#265a88;background-image:none}.btn-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#419641 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#419641));background-image:linear-gradient(to bottom,#5cb85c 0,#419641 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#3e8f3e}.btn-success:focus,.btn-success:hover{background-color:#419641;background-position:0 -15px}.btn-success.active,.btn-success:active{background-color:#419641;border-color:#3e8f3e}.btn-success.disabled,.btn-success.disabled.active,.btn-success.disabled.focus,.btn-success.disabled:active,.btn-success.disabled:focus,.btn-success.disabled:hover,.btn-success[disabled],.btn-success[disabled].active,.btn-success[disabled].focus,.btn-success[disabled]:active,.btn-success[disabled]:focus,.btn-success[disabled]:hover,fieldset[disabled] .btn-success,fieldset[disabled] .btn-success.active,fieldset[disabled] .btn-success.focus,fieldset[disabled] .btn-success:active,fieldset[disabled] .btn-success:focus,fieldset[disabled] .btn-success:hover{background-color:#419641;background-image:none}.btn-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#2aabd2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#2aabd2));background-image:linear-gradient(to bottom,#5bc0de 0,#2aabd2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#28a4c9}.btn-info:focus,.btn-info:hover{background-color:#2aabd2;background-position:0 -15px}.btn-info.active,.btn-info:active{background-color:#2aabd2;border-color:#28a4c9}.btn-info.disabled,.btn-info.disabled.active,.btn-info.disabled.focus,.btn-info.disabled:active,.btn-info.disabled:focus,.btn-info.disabled:hover,.btn-info[disabled],.btn-info[disabled].active,.btn-info[disabled].focus,.btn-info[disabled]:active,.btn-info[disabled]:focus,.btn-info[disabled]:hover,fieldset[disabled] .btn-info,fieldset[disabled] .btn-info.active,fieldset[disabled] .btn-info.focus,fieldset[disabled] .btn-info:active,fieldset[disabled] .btn-info:focus,fieldset[disabled] .btn-info:hover{background-color:#2aabd2;background-image:none}.btn-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#eb9316 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#eb9316));background-image:linear-gradient(to bottom,#f0ad4e 0,#eb9316 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#e38d13}.btn-warning:focus,.btn-warning:hover{background-color:#eb9316;background-position:0 -15px}.btn-warning.active,.btn-warning:active{background-color:#eb9316;border-color:#e38d13}.btn-warning.disabled,.btn-warning.disabled.active,.btn-warning.disabled.focus,.btn-warning.disabled:active,.btn-warning.disabled:focus,.btn-warning.disabled:hover,.btn-warning[disabled],.btn-warning[disabled].active,.btn-warning[disabled].focus,.btn-warning[disabled]:active,.btn-warning[disabled]:focus,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning,fieldset[disabled] .btn-warning.active,fieldset[disabled] .btn-warning.focus,fieldset[disabled] .btn-warning:active,fieldset[disabled] .btn-warning:focus,fieldset[disabled] .btn-warning:hover{background-color:#eb9316;background-image:none}.btn-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c12e2a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c12e2a));background-image:linear-gradient(to bottom,#d9534f 0,#c12e2a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-color:#b92c28}.btn-danger:focus,.btn-danger:hover{background-color:#c12e2a;background-position:0 -15px}.btn-danger.active,.btn-danger:active{background-color:#c12e2a;border-color:#b92c28}.btn-danger.disabled,.btn-danger.disabled.active,.btn-danger.disabled.focus,.btn-danger.disabled:active,.btn-danger.disabled:focus,.btn-danger.disabled:hover,.btn-danger[disabled],.btn-danger[disabled].active,.btn-danger[disabled].focus,.btn-danger[disabled]:active,.btn-danger[disabled]:focus,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger,fieldset[disabled] .btn-danger.active,fieldset[disabled] .btn-danger.focus,fieldset[disabled] .btn-danger:active,fieldset[disabled] .btn-danger:focus,fieldset[disabled] .btn-danger:hover{background-color:#c12e2a;background-image:none}.img-thumbnail,.thumbnail{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.dropdown-menu>li>a:focus,.dropdown-menu>li>a:hover{background-color:#e8e8e8;background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.dropdown-menu>.active>a,.dropdown-menu>.active>a:focus,.dropdown-menu>.active>a:hover{background-color:#2e6da4;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.navbar-default{background-image:-webkit-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-o-linear-gradient(top,#fff 0,#f8f8f8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fff),to(#f8f8f8));background-image:linear-gradient(to bottom,#fff 0,#f8f8f8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075);box-shadow:inset 0 1px 0 rgba(255,255,255,.15),0 1px 5px rgba(0,0,0,.075)}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-o-linear-gradient(top,#dbdbdb 0,#e2e2e2 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dbdbdb),to(#e2e2e2));background-image:linear-gradient(to bottom,#dbdbdb 0,#e2e2e2 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.075);box-shadow:inset 0 3px 9px rgba(0,0,0,.075)}.navbar-brand,.navbar-nav>li>a{text-shadow:0 1px 0 rgba(255,255,255,.25)}.navbar-inverse{background-image:-webkit-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-o-linear-gradient(top,#3c3c3c 0,#222 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#3c3c3c),to(#222));background-image:linear-gradient(to bottom,#3c3c3c 0,#222 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);background-repeat:repeat-x;border-radius:4px}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.open>a{background-image:-webkit-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-o-linear-gradient(top,#080808 0,#0f0f0f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#080808),to(#0f0f0f));background-image:linear-gradient(to bottom,#080808 0,#0f0f0f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);background-repeat:repeat-x;-webkit-box-shadow:inset 0 3px 9px rgba(0,0,0,.25);box-shadow:inset 0 3px 9px rgba(0,0,0,.25)}.navbar-inverse .navbar-brand,.navbar-inverse .navbar-nav>li>a{text-shadow:0 -1px 0 rgba(0,0,0,.25)}.navbar-fixed-bottom,.navbar-fixed-top,.navbar-static-top{border-radius:0}@media (max-width:767px){.navbar .navbar-nav .open .dropdown-menu>.active>a,.navbar .navbar-nav .open .dropdown-menu>.active>a:focus,.navbar .navbar-nav .open .dropdown-menu>.active>a:hover{color:#fff;background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}}.alert{text-shadow:0 1px 0 rgba(255,255,255,.2);-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05);box-shadow:inset 0 1px 0 rgba(255,255,255,.25),0 1px 2px rgba(0,0,0,.05)}.alert-success{background-image:-webkit-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#c8e5bc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#c8e5bc));background-image:linear-gradient(to bottom,#dff0d8 0,#c8e5bc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);background-repeat:repeat-x;border-color:#b2dba1}.alert-info{background-image:-webkit-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#b9def0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#b9def0));background-image:linear-gradient(to bottom,#d9edf7 0,#b9def0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);background-repeat:repeat-x;border-color:#9acfea}.alert-warning{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#f8efc0 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#f8efc0));background-image:linear-gradient(to bottom,#fcf8e3 0,#f8efc0 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);background-repeat:repeat-x;border-color:#f5e79e}.alert-danger{background-image:-webkit-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-o-linear-gradient(top,#f2dede 0,#e7c3c3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#e7c3c3));background-image:linear-gradient(to bottom,#f2dede 0,#e7c3c3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);background-repeat:repeat-x;border-color:#dca7a7}.progress{background-image:-webkit-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#ebebeb 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#ebebeb),to(#f5f5f5));background-image:linear-gradient(to bottom,#ebebeb 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x}.progress-bar{background-image:-webkit-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-o-linear-gradient(top,#337ab7 0,#286090 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#286090));background-image:linear-gradient(to bottom,#337ab7 0,#286090 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);background-repeat:repeat-x}.progress-bar-success{background-image:-webkit-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-o-linear-gradient(top,#5cb85c 0,#449d44 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5cb85c),to(#449d44));background-image:linear-gradient(to bottom,#5cb85c 0,#449d44 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);background-repeat:repeat-x}.progress-bar-info{background-image:-webkit-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-o-linear-gradient(top,#5bc0de 0,#31b0d5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#5bc0de),to(#31b0d5));background-image:linear-gradient(to bottom,#5bc0de 0,#31b0d5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);background-repeat:repeat-x}.progress-bar-warning{background-image:-webkit-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-o-linear-gradient(top,#f0ad4e 0,#ec971f 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f0ad4e),to(#ec971f));background-image:linear-gradient(to bottom,#f0ad4e 0,#ec971f 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);background-repeat:repeat-x}.progress-bar-danger{background-image:-webkit-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-o-linear-gradient(top,#d9534f 0,#c9302c 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9534f),to(#c9302c));background-image:linear-gradient(to bottom,#d9534f 0,#c9302c 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);background-repeat:repeat-x}.progress-bar-striped{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:-o-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.list-group{border-radius:4px;-webkit-box-shadow:0 1px 2px rgba(0,0,0,.075);box-shadow:0 1px 2px rgba(0,0,0,.075)}.list-group-item.active,.list-group-item.active:focus,.list-group-item.active:hover{text-shadow:0 -1px 0 #286090;background-image:-webkit-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2b669a 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2b669a));background-image:linear-gradient(to bottom,#337ab7 0,#2b669a 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);background-repeat:repeat-x;border-color:#2b669a}.list-group-item.active .badge,.list-group-item.active:focus .badge,.list-group-item.active:hover .badge{text-shadow:none}.panel{-webkit-box-shadow:0 1px 2px rgba(0,0,0,.05);box-shadow:0 1px 2px rgba(0,0,0,.05)}.panel-default>.panel-heading{background-image:-webkit-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-o-linear-gradient(top,#f5f5f5 0,#e8e8e8 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f5f5f5),to(#e8e8e8));background-image:linear-gradient(to bottom,#f5f5f5 0,#e8e8e8 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);background-repeat:repeat-x}.panel-primary>.panel-heading{background-image:-webkit-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-o-linear-gradient(top,#337ab7 0,#2e6da4 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#337ab7),to(#2e6da4));background-image:linear-gradient(to bottom,#337ab7 0,#2e6da4 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);background-repeat:repeat-x}.panel-success>.panel-heading{background-image:-webkit-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-o-linear-gradient(top,#dff0d8 0,#d0e9c6 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#dff0d8),to(#d0e9c6));background-image:linear-gradient(to bottom,#dff0d8 0,#d0e9c6 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);background-repeat:repeat-x}.panel-info>.panel-heading{background-image:-webkit-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-o-linear-gradient(top,#d9edf7 0,#c4e3f3 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#d9edf7),to(#c4e3f3));background-image:linear-gradient(to bottom,#d9edf7 0,#c4e3f3 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);background-repeat:repeat-x}.panel-warning>.panel-heading{background-image:-webkit-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-o-linear-gradient(top,#fcf8e3 0,#faf2cc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#fcf8e3),to(#faf2cc));background-image:linear-gradient(to bottom,#fcf8e3 0,#faf2cc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);background-repeat:repeat-x}.panel-danger>.panel-heading{background-image:-webkit-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-o-linear-gradient(top,#f2dede 0,#ebcccc 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#f2dede),to(#ebcccc));background-image:linear-gradient(to bottom,#f2dede 0,#ebcccc 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);background-repeat:repeat-x}.well{background-image:-webkit-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-o-linear-gradient(top,#e8e8e8 0,#f5f5f5 100%);background-image:-webkit-gradient(linear,left top,left bottom,from(#e8e8e8),to(#f5f5f5));background-image:linear-gradient(to bottom,#e8e8e8 0,#f5f5f5 100%);filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);background-repeat:repeat-x;border-color:#dcdcdc;-webkit-box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 3px rgba(0,0,0,.05),0 1px 0 rgba(255,255,255,.1)} 6 | /*# sourceMappingURL=bootstrap-theme.min.css.map */ -------------------------------------------------------------------------------- /public/css/bootstrap-theme.min.css.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["less/theme.less","less/mixins/vendor-prefixes.less","less/mixins/gradients.less","less/mixins/reset-filter.less"],"names":[],"mappings":";;;;AAmBA,YAAA,aAAA,UAAA,aAAA,aAAA,aAME,YAAA,EAAA,KAAA,EAAA,eC2CA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBDvCR,mBAAA,mBAAA,oBAAA,oBAAA,iBAAA,iBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBAAA,oBCsCA,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBDlCR,qBAAA,sBAAA,sBAAA,uBAAA,mBAAA,oBAAA,sBAAA,uBAAA,sBAAA,uBAAA,sBAAA,uBAAA,+BAAA,gCAAA,6BAAA,gCAAA,gCAAA,gCCiCA,mBAAA,KACQ,WAAA,KDlDV,mBAAA,oBAAA,iBAAA,oBAAA,oBAAA,oBAuBI,YAAA,KAyCF,YAAA,YAEE,iBAAA,KAKJ,aErEI,YAAA,EAAA,IAAA,EAAA,KACA,iBAAA,iDACA,iBAAA,4CAAA,iBAAA,qEAEA,iBAAA,+CCnBF,OAAA,+GH4CA,OAAA,0DACA,kBAAA,SAuC2C,aAAA,QAA2B,aAAA,KArCtE,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAgBN,aEtEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAiBN,aEvEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAkBN,UExEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,gBAAA,gBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,iBAAA,iBAEE,iBAAA,QACA,aAAA,QAMA,mBAAA,0BAAA,yBAAA,0BAAA,yBAAA,yBAAA,oBAAA,2BAAA,0BAAA,2BAAA,0BAAA,0BAAA,6BAAA,oCAAA,mCAAA,oCAAA,mCAAA,mCAME,iBAAA,QACA,iBAAA,KAmBN,aEzEI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,mBAAA,mBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,oBAAA,oBAEE,iBAAA,QACA,aAAA,QAMA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,uBAAA,8BAAA,6BAAA,8BAAA,6BAAA,6BAAA,gCAAA,uCAAA,sCAAA,uCAAA,sCAAA,sCAME,iBAAA,QACA,iBAAA,KAoBN,YE1EI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDAEA,OAAA,+GCnBF,OAAA,0DH4CA,kBAAA,SACA,aAAA,QAEA,kBAAA,kBAEE,iBAAA,QACA,oBAAA,EAAA,MAGF,mBAAA,mBAEE,iBAAA,QACA,aAAA,QAMA,qBAAA,4BAAA,2BAAA,4BAAA,2BAAA,2BAAA,sBAAA,6BAAA,4BAAA,6BAAA,4BAAA,4BAAA,+BAAA,sCAAA,qCAAA,sCAAA,qCAAA,qCAME,iBAAA,QACA,iBAAA,KA2BN,eAAA,WClCE,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBD2CV,0BAAA,0BE3FI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GF0FF,kBAAA,SAEF,yBAAA,+BAAA,+BEhGI,iBAAA,QACA,iBAAA,oDACA,iBAAA,+CAAA,iBAAA,wEACA,iBAAA,kDACA,OAAA,+GFgGF,kBAAA,SASF,gBE7GI,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SH+HA,cAAA,ICjEA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,iBD6DV,sCAAA,oCE7GI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,iBD0EV,cAAA,iBAEE,YAAA,EAAA,IAAA,EAAA,sBAIF,gBEhII,iBAAA,iDACA,iBAAA,4CACA,iBAAA,qEAAA,iBAAA,+CACA,OAAA,+GACA,OAAA,0DCnBF,kBAAA,SHkJA,cAAA,IAHF,sCAAA,oCEhII,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SD2CF,mBAAA,MAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBDgFV,8BAAA,iCAYI,YAAA,EAAA,KAAA,EAAA,gBAKJ,qBAAA,kBAAA,mBAGE,cAAA,EAqBF,yBAfI,mDAAA,yDAAA,yDAGE,MAAA,KE7JF,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,UFqKJ,OACE,YAAA,EAAA,IAAA,EAAA,qBC3HA,mBAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,MAAA,EAAA,IAAA,EAAA,sBAAA,EAAA,IAAA,IAAA,gBDsIV,eEtLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAKF,YEvLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAMF,eExLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAOF,cEzLI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF8KF,aAAA,QAeF,UEjMI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFuMJ,cE3MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFwMJ,sBE5MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyMJ,mBE7MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0MJ,sBE9MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2MJ,qBE/MI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF+MJ,sBElLI,iBAAA,yKACA,iBAAA,oKACA,iBAAA,iKFyLJ,YACE,cAAA,IC9KA,mBAAA,EAAA,IAAA,IAAA,iBACQ,WAAA,EAAA,IAAA,IAAA,iBDgLV,wBAAA,8BAAA,8BAGE,YAAA,EAAA,KAAA,EAAA,QEnOE,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFiOF,aAAA,QALF,+BAAA,qCAAA,qCAQI,YAAA,KAUJ,OCnME,mBAAA,EAAA,IAAA,IAAA,gBACQ,WAAA,EAAA,IAAA,IAAA,gBD4MV,8BE5PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFyPJ,8BE7PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF0PJ,8BE9PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF2PJ,2BE/PI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF4PJ,8BEhQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SF6PJ,6BEjQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFoQJ,MExQI,iBAAA,oDACA,iBAAA,+CACA,iBAAA,wEAAA,iBAAA,kDACA,OAAA,+GACA,kBAAA,SFsQF,aAAA,QC3NA,mBAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA,qBACQ,WAAA,MAAA,EAAA,IAAA,IAAA,gBAAA,EAAA,IAAA,EAAA"} -------------------------------------------------------------------------------- /public/css/dnsmanager.css: -------------------------------------------------------------------------------- 1 | .input-menu-mobile { 2 | width: 200px; 3 | margin: 0 0 10px 10px; 4 | } 5 | 6 | .sidebar-group { 7 | width: 100%; 8 | } 9 | 10 | .sidebar-group > a > button { 11 | margin-bottom: 10px !important; 12 | } 13 | -------------------------------------------------------------------------------- /public/dispatch.cgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | BEGIN { $ENV{DANCER_APPHANDLER} = 'PSGI';} 3 | use Dancer2; 4 | use FindBin '$RealBin'; 5 | use Plack::Runner; 6 | 7 | # For some reason Apache SetEnv directives dont propagate 8 | # correctly to the dispatchers, so forcing PSGI and env here 9 | # is safer. 10 | set apphandler => 'PSGI'; 11 | set environment => 'production'; 12 | 13 | my $psgi = path($RealBin, '..', 'bin', 'app.psgi'); 14 | die "Unable to read startup script: $psgi" unless -r $psgi; 15 | 16 | Plack::Runner->run($psgi); 17 | -------------------------------------------------------------------------------- /public/dispatch.fcgi: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | BEGIN { $ENV{DANCER_APPHANDLER} = 'PSGI';} 3 | use Dancer2; 4 | use FindBin '$RealBin'; 5 | use Plack::Handler::FCGI; 6 | 7 | # For some reason Apache SetEnv directives dont propagate 8 | # correctly to the dispatchers, so forcing PSGI and env here 9 | # is safer. 10 | set apphandler => 'PSGI'; 11 | set environment => 'production'; 12 | 13 | my $psgi = path($RealBin, '..', 'bin', 'app.psgi'); 14 | my $app = do($psgi); 15 | die "Unable to read startup script: $@" if $@; 16 | my $server = Plack::Handler::FCGI->new(nproc => 5, detach => 1); 17 | 18 | $server->run($app); 19 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaneRoot/dnsmanager/6426413465a36a4b3cac2f34df20c254f11f439b/public/favicon.ico -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaneRoot/dnsmanager/6426413465a36a4b3cac2f34df20c254f11f439b/public/fonts/glyphicons-halflings-regular.eot -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaneRoot/dnsmanager/6426413465a36a4b3cac2f34df20c254f11f439b/public/fonts/glyphicons-halflings-regular.ttf -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaneRoot/dnsmanager/6426413465a36a4b3cac2f34df20c254f11f439b/public/fonts/glyphicons-halflings-regular.woff -------------------------------------------------------------------------------- /public/fonts/glyphicons-halflings-regular.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaneRoot/dnsmanager/6426413465a36a4b3cac2f34df20c254f11f439b/public/fonts/glyphicons-halflings-regular.woff2 -------------------------------------------------------------------------------- /public/images/perldancer-bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaneRoot/dnsmanager/6426413465a36a4b3cac2f34df20c254f11f439b/public/images/perldancer-bg.jpg -------------------------------------------------------------------------------- /public/images/perldancer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KaneRoot/dnsmanager/6426413465a36a4b3cac2f34df20c254f11f439b/public/images/perldancer.jpg -------------------------------------------------------------------------------- /public/js/npm.js: -------------------------------------------------------------------------------- 1 | // This file is autogenerated via the `commonjs` Grunt task. You can require() this file in a CommonJS environment. 2 | require('../../js/transition.js') 3 | require('../../js/alert.js') 4 | require('../../js/button.js') 5 | require('../../js/carousel.js') 6 | require('../../js/collapse.js') 7 | require('../../js/dropdown.js') 8 | require('../../js/modal.js') 9 | require('../../js/tooltip.js') 10 | require('../../js/popover.js') 11 | require('../../js/scrollspy.js') 12 | require('../../js/tab.js') 13 | require('../../js/affix.js') -------------------------------------------------------------------------------- /readme.markdown: -------------------------------------------------------------------------------- 1 | # DNSmanager (en) 2 | 3 | This project is about managing DNS zones with a simple website, providing a 4 | name to anyone on the Internet. It is bound to the [netlib.re][netlibre] 5 | project. This service let you manage your dynamic IP address with your domain so 6 | you don't need DynDNS anymore, and it's all libre software ! 7 | 8 | The association managing the infrastructure behind this service is [Alsace 9 | Réseau Neutre][arn], which is an ethical ISP based in Alsace, France. Don't be 10 | shy, go ask your questions ! 11 | 12 | ## Tools 13 | 14 | * [Dancer2](http://perldancer.org/) 15 | * [Net::DNS](https://metacpan.org/pod/Net::DNS) 16 | * [Bootstrap](http://twitter.github.io/bootstrap/) 17 | * [DBD::mysql](https://metacpan.org/module/DBD::mysql) 18 | * [Moo](https://metacpan.org/pod/Moo) 19 | * [Crypt::Digest::SHA256](https://metacpan.org/pod/Crypt::Digest::SHA256) 20 | 21 | ## TODO 22 | 23 | * zone delegation 24 | * REST API 25 | * captcha ? 26 | 27 | # Installation (base) 28 | 29 | I suggest using [perlbrew][perlbrew] and [cpanm][cpanm] for the installation, 30 | to not change your current environment. So install them then : 31 | 32 | perlbrew install perl-5.18.0 33 | perlbrew switch perl-5.18.0 34 | perlbrew exec sh init/deploiement.sh all 35 | 36 | # Contribution (but only to the user interface) 37 | 38 | If you want to contribute only on the application interface, you don't need to install and configure all the applications involved in the production release of dnsmanager. 39 | First, uncomment "isviewtest" on **conf/config.yml** then : 40 | 41 | perlbrew install perl-5.18.0 42 | perlbrew switch perl-5.18.0 43 | perlbrew exec sh init/deploiement.sh installdep 44 | perlbrew exec sh init/deploiement.sh perlmodules 45 | 46 | Finally, to run the application with fake views : 47 | 48 | perlbrew exec plackup --port 3000 bin/app.psgi 49 | 50 | 51 | # DNSmanager (fr) 52 | 53 | Ce projet est un programme de gestion de zones DNS à partir d'un site web 54 | simple, permettant à chacun d'avoir un nom sur **Internet**. Il est lié au 55 | service en ligne [netlib.re][netlibre]. Ce service en ligne peut 56 | remplacer avantageusement DynDNS puisqu'il est basé sur du code libre, et une 57 | association s'occupe de son maintien ([Alsace Réseau 58 | Neutre][arn], éthique++). 59 | 60 | De manière factuelle : 61 | 62 | - des utilisateurs peuvent s'enregistrer puis 63 | - ajouter, supprimer, modifier des zones DNS 64 | - mettre à jour un enregistrement A ou AAAA automatiquement via un script 65 | 66 | - des administrateurs sont là pour 67 | - supprimer des zones, des utilisateurs 68 | - vous aider sur IRC (#arn sur irc.geeknode.org) ! \o/ 69 | 70 | # installation (base) 71 | 72 | L'installation de l'application se fait de préférence via 73 | [perlbrew][perlbrew] et [cpanm][cpanm] ce qui permet d'installer les 74 | bibliothèques sans toucher à votre installation de Perl. Installez ces 75 | programmes puis faites : 76 | 77 | perlbrew install perl-5.18.0 78 | perlbrew switch perl-5.18.0 79 | perlbrew exec sh init/deploiement.sh all 80 | 81 | # Contribuer (uniquement à l'interface) 82 | 83 | Si vous souhaitez contribuer à *l'interface*, il suffit de décommenter la ligne 84 | indiquant "isviewtest" dans le fichier de configuration **conf/config.yml**. 85 | À partir de là, vous pouvez installer l'application comme ceci : 86 | 87 | perlbrew install perl-5.18.0 88 | perlbrew switch perl-5.18.0 89 | perlbrew exec sh init/deploiement.sh installdep 90 | perlbrew exec sh init/deploiement.sh perlmodules 91 | 92 | Puis pour faire vos tests : 93 | 94 | perlbrew exec plackup --port 3000 bin/app.psgi 95 | 96 | ## Ce qu'il reste à faire 97 | 98 | * délégation de zone 99 | * API REST 100 | * captcha ? 101 | 102 | # Un point sur le code 103 | 104 | Le code de dnsmanager est composé de la manière suivante : 105 | 106 | * `cli/` : regroupe les commandes à utiliser en ligne de commande, afin 107 | d'administrer facilement la base de donnée et les zones, et faire des tests. 108 | Il y a également le code du démon qui tourne sur les machines clientes pour 109 | gérer la mise à jour de l'adresse IP dynamique. 110 | 111 | * `conf/` : contient la configuration du serveur, à savoir `conf.yml` pour 112 | indiquer où sont les serveurs de nom, comment y accéder pour mettre à 113 | jour les zones, quel est le répertoire à utiliser pour mettre les fichiers 114 | temporaires… et `reserved.zone` qui indique quelles sont les zones réservées 115 | par l'administrateur (ex: on ne veut pas que quelqu'un enregistre www.NDD). 116 | 117 | * `init/` : regroupe les scripts permettant de mettre en place le serveur et la 118 | base de données. 119 | 120 | * `lib/` : contient la majorité du code, qui est découpée en MVC, à savoir : 121 | 122 | * `lib/MyWeb/App.pm` : le contrôleur, s'occupe des routes 123 | * `lib/rt/App.pm` : les routes, qui font office de modèles, effectuent les 124 | actions à entreprendre (ajout, suppression, modification de zone et 125 | d'utilisateur) 126 | 127 | Mais `lib` contient également toute la bibliothèque logicielle qui effectue les 128 | actions, avec principalement une interface à l'authentification, la 129 | récupération, modification et la suppression de zones et d'utilisateurs avec le 130 | fichier `lib/app.pm`. 131 | 132 | * `lib/configuration.pm` : gère la configuration du serveur 133 | * `lib/copycat.pm` : copie des fichiers entre les serveurs 134 | * `lib/db.pm` : gère les interactions avec la base de données 135 | * `lib/encryption.pm` : chiffre les données 136 | * `lib/fileutil.pm` : fonctions utilitaires pour gérer les fichiers 137 | * `lib/getiface.pm` : récupère l'interface logicielle pour gérer un type de 138 | serveur de noms (Bind9, NSD, Knot…), les interfaces sont dans `lib/interface/` 139 | * `lib/remotecmd.pm` : effectue une commande à distance (comme recharger un 140 | fichier de configuration dans Bind9) 141 | * `lib/util.pm` : quelques fonctions utilitaires 142 | * `lib/zone.pm` : gère les zones DNS (en général, s'occupe de recharger les 143 | configuration, mettre à jour les zones, etc) 144 | * `lib/zonefile.pm` : gère les fichiers de zone 145 | 146 | * `public/` : les fichers statics du serveur web 147 | * `views/` : les différentes vues de l'application 148 | 149 | # La délégation de zone 150 | 151 | Le principe de délégation est de laisser une personne héberger elle-même 152 | son serveur DNS. 153 | Elle va gérer elle-même sa zone, on aura juste un enregistrement de sa zone 154 | "bla.netlib.re." qui pointera vers son adresse IP via un enregistrement de 155 | type NS et un autre de type A. 156 | Ces enregistrements devront se faire directement dans notre zone DNS 157 | (netlib.re. ou codelib.re.). 158 | 159 | Pour gérer la délégation de zone, il l faut : 160 | * mémoriser que la personne a une zone déléguée, non gérée par notre interface 161 | actuelle mais par une autre interface à faire, ce qui implique de toucher à la 162 | BDD 163 | * qu'elle puisse ajouter son (ses) adresse(s) IP pour la délégation 164 | * ajouter un enregistrement de type NS dans notre zone (net|code)lib.re 165 | * mettre à jour le numéro de série de notre zone 166 | * indiquer à notre serveur primaire de reload la zone 167 | 168 | 169 | [netlibre]: https://netlib.re/ 170 | [arn]: https://www.arn-fai.net 171 | [perlbrew]: http://perlbrew.pl/ 172 | [cpanm]: http://search.cpan.org/~miyagawa/App-cpanminus-1.7040/bin/cpanm 173 | -------------------------------------------------------------------------------- /t/001_base.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use Test::More tests => 1; 5 | use_ok 'MyWeb::App'; 6 | -------------------------------------------------------------------------------- /t/002_index_route.t: -------------------------------------------------------------------------------- 1 | use strict; 2 | use warnings; 3 | 4 | use MyWeb::App; 5 | use Test::More tests => 2; 6 | use Plack::Test; 7 | use HTTP::Request::Common; 8 | 9 | my $app = MyWeb::App->to_app; 10 | is( ref $app, 'CODE', 'Got app' ); 11 | 12 | my $test = Plack::Test->create($app); 13 | my $res = $test->request( GET '/' ); 14 | 15 | ok( $res->is_success, '[GET /] successful' ); 16 | -------------------------------------------------------------------------------- /t/003_basic_functions.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Modern::Perl; 3 | use lib 'lib'; 4 | use util ':all'; 5 | 6 | chdir 'lib'; # TODO hack at 2am 7 | 8 | map { 9 | ok 10 | ( ( is_domain_name $_ ), "is '$_' a domain name" ) 11 | } qw( foo.bar bar localhost. localhost ); 12 | 13 | map { 14 | ok 15 | ( ( is_valid_tld $_ ), "is '$_' a tld in the cfg file" ) 16 | } qw( .netlib.re ); 17 | 18 | map { 19 | ok 20 | ( ( ! is_valid_tld $_ ), "is '$_' a tld in the cfg file" ) 21 | } qw( example.com ); 22 | 23 | done_testing; 24 | -------------------------------------------------------------------------------- /t/004_filutil.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Modern::Perl; 3 | use URI; 4 | use lib 'lib'; 5 | use fileutil ':all'; 6 | 7 | my $f1 = "lib/fileutil.pm"; 8 | my $p1 = "read_file"; 9 | my $f2 = "/tmp/test"; 10 | my $d2 = "DATA"; 11 | 12 | sub t_read_file { 13 | my ($f, $pattern) = @_; 14 | my $data = read_file $f; 15 | $data =~ /$pattern/; 16 | } 17 | 18 | sub t_write_file { 19 | my ($f, $data) = @_; 20 | write_file $f, $data; 21 | my $d = read_file $f; 22 | $d =~ /$data/; 23 | } 24 | 25 | ok ((t_read_file $f1, $p1) , "read_file avec $f1 / $p1" ); 26 | ok ((t_write_file $f2, $d2) , "write_file" ); 27 | 28 | done_testing; 29 | -------------------------------------------------------------------------------- /t/005_copycat.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Modern::Perl; 3 | use URI; 4 | use lib 'lib'; 5 | use fileutil ':all'; 6 | use copycat ':all'; 7 | 8 | my $l1 = "file:///etc/hosts"; 9 | my $l2 = "file:///tmp/truc"; 10 | 11 | sub t_local_local { 12 | my ($f1, $f2) = @_; 13 | copycat $f1, $f2; 14 | 15 | my $file = URI->new($f2); 16 | my $d = read_file $file->path; 17 | $d =~ /localhost/; 18 | } 19 | 20 | ok ((t_local_local $l1, $l2) , "copycat local local avec $l1 / $l2" ); 21 | 22 | done_testing; 23 | -------------------------------------------------------------------------------- /t/006_remotecmd.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Modern::Perl; 3 | use URI; 4 | use lib 'lib'; 5 | use remotecmd ':all'; 6 | 7 | my $port = 22; 8 | my $user = 'karchnu'; 9 | my $host = "karchnu.fr"; 10 | my $cmd = "ls /"; 11 | my $pattern = qr/etc/; 12 | 13 | sub t_remotecmd { 14 | my ($user, $host, $port, $cmd, $pattern) = @_; 15 | 16 | my $ret = remotecmd $user, $host, $port, $cmd; 17 | 18 | $ret =~ $pattern; 19 | } 20 | 21 | ok ((t_remotecmd $user, $host, $port, $cmd, $pattern) , "remote cmd" ); 22 | 23 | done_testing; 24 | -------------------------------------------------------------------------------- /t/007_get_iface.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Modern::Perl; 3 | use YAML::XS; 4 | use URI; 5 | use lib 'lib'; 6 | use getiface':all'; 7 | 8 | my $x = getiface("bind9", { mycfg => '', data => '' }); 9 | say Dump $x; 10 | -------------------------------------------------------------------------------- /t/008_zonefile.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Modern::Perl; 3 | use lib 'lib'; 4 | use util ':all'; 5 | use zonefile; 6 | 7 | chdir 'lib'; # TODO hack at 2am 8 | 9 | #map { 10 | # ok 11 | # ( ( is_domain_name $_ ), "is '$_' a domain name" ) 12 | #} qw( foo.bar bar localhost. localhost ); 13 | # 14 | #done_testing; 15 | 16 | my $zf = zonefile->new( zonefile => "../t/zonefile.txt" ); 17 | $zf->new_serial(); 18 | print $zf->dump(); 19 | -------------------------------------------------------------------------------- /t/00x_dump_cfg_file.t: -------------------------------------------------------------------------------- 1 | use Test::More; 2 | use Modern::Perl; 3 | use YAML::XS; 4 | use URI; 5 | use lib 'lib'; 6 | use configuration ':all'; 7 | 8 | my $x = get_cfg(); 9 | #say Dump $x; 10 | 11 | say $$x{database}{host}; 12 | 13 | for($$x{secondarydnsserver}) 14 | { 15 | for(@$_) 16 | { 17 | while( my ($k, $v) = each %$_) 18 | { 19 | say $k . ' ' . $v ; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /t/zonefile.txt: -------------------------------------------------------------------------------- 1 | $ORIGIN example.com. ; designates the start of this zone file in the namespace 2 | $TTL 1h ; default expiration time of all resource records without their own TTL value 3 | example.com. IN SOA ns.example.com. username.example.com. ( 2007120710 1d 2h 4w 1h ) 4 | example.com. IN NS ns ; ns.example.com is a nameserver for example.com 5 | example.com. IN NS ns.somewhere.example. ; ns.somewhere.example is a backup nameserver for example.com 6 | example.com. IN MX 10 mail.example.com. ; mail.example.com is the mailserver for example.com 7 | @ IN MX 20 mail2.example.com. ; equivalent to above line, "@" represents zone origin 8 | @ IN MX 50 mail3 ; equivalent to above line, but using a relative host name 9 | example.com. IN A 192.0.2.1 ; IPv4 address for example.com 10 | IN AAAA 2001:db8:10::1 ; IPv6 address for example.com 11 | ns IN A 192.0.2.2 ; IPv4 address for ns.example.com 12 | IN AAAA 2001:db8:10::2 ; IPv6 address for ns.example.com 13 | www IN CNAME example.com. ; www.example.com is an alias for example.com 14 | wwwtest IN CNAME www ; wwwtest.example.com is another alias for www.example.com 15 | mail IN A 192.0.2.3 ; IPv4 address for mail.example.com 16 | mail2 IN A 192.0.2.4 ; IPv4 address for mail2.example.com 17 | mail3 IN A 192.0.2.5 ; IPv4 address for mail3.example.com 18 | -------------------------------------------------------------------------------- /views/administration.tt: -------------------------------------------------------------------------------- 1 | <% include header.tt %> 2 | <% include sidebar.tt %> 3 | <% include error.tt %> 4 | 5 |
6 | 7 |
8 | 9 |

Domaines gérés

10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | <% FOREACH d IN alldomains %> 19 | 20 | 21 | 22 | 23 | 26 | 27 | 30 | 31 | 32 | <% END %> 33 |
domaineutilisateursupprimer domainesupprimer utilisateur
<% d.domain %> <% d.login %> 24 | 25 | 28 | 29 |
34 | 35 |
36 | 37 |
38 | 39 |

Utilisateurs enregistrés

40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | <% FOREACH u IN allusers %> 48 | 49 | 50 | 51 | 54 | 55 | 64 | 65 | 66 | <%END%> 67 |
utilisateursupprimeradministrateur
<% u.login %> 52 | 53 | 56 | 57 | <% IF u.admin == 1 %> 58 | 59 | <% ELSE %> 60 | 61 | <% END %> 62 | 63 |
68 | 69 |
70 | 71 |
72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /views/details.tt: -------------------------------------------------------------------------------- 1 | <% include header.tt %> 2 | <% include sidebar.tt %> 3 | <% include error.tt %> 4 | 5 |
6 | 7 |

Fichier de zone de <% domain %>

8 | 9 | <% IF expert %> 10 | 11 |
12 | 13 | 14 |
15 | 16 | <% ELSE %> 17 | 18 | 19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | <% FOREACH rr in zone %> 29 | 30 | <% SWITCH rr.type %> 31 | <% CASE [ "A", "AAAA", "TXT", "NS", "CNAME", "PTR" ] %> 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 52 | 53 | 54 | <% CASE "MX" %> 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 68 | 69 | 70 | 77 | 78 | 79 | <% CASE "SRV" %> 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 97 | 98 | 99 | 106 | 107 | <% CASE %> 108 | 109 | <% END %> 110 | 111 | 112 | <% END %> 113 |
20 |
DomaineTypeTTLCible
<% rr.type %> 46 | 47 | 50 | 51 |
<% rr.type %> 67 | Priorité 71 | 72 | 75 | 76 |
<% rr.type %> 94 | Priorité 95 | Poids 96 | Port 100 | 101 | 104 | 105 | Resource Record non pris en charge : <% rr.type %>
114 | 115 | 116 | 117 |
118 |
119 |
120 |
121 | 122 |
123 | Ajout d'un enregistrement 124 | 125 |
126 | 127 |
128 | 136 |
137 |
138 | 139 |
140 | 141 |
142 | 143 |
144 |
145 | 146 |
147 | 148 |
149 | 150 |
151 |
152 | 153 | 156 | 157 |
158 | 159 |
160 | 161 |
162 |
163 | 164 |
165 | 166 |
167 |
168 | 169 |
170 |
171 | 172 |
173 |
174 | 175 |
176 |
177 | 178 |
179 | Ajout d'un enregistrement SRV 180 | 181 |
182 | 183 |
184 | 185 |
186 |
187 | 188 | 189 | 190 |
191 | 192 |
193 | 194 |
195 |
196 | 197 |
198 | 199 |
200 | 201 |
202 |
203 | 204 |
205 | 206 |
207 | 208 |
209 |
210 | 211 |
212 | 213 |
214 | 215 |
216 |
217 | 218 |
219 | 220 |
221 | 222 |
223 |
224 | 225 |
226 | 227 |
228 |
229 | 230 |
231 |
232 |
233 |
234 | 235 |
236 |
237 | 238 |
239 | Ajout d'un enregistrement MX 240 | 241 |
242 | 243 |
244 | 245 |
246 |
247 | 248 | 249 | 250 |
251 | 252 |
253 | 254 |
255 |
256 | 257 |
258 | 259 |
260 | 261 |
262 |
263 | 264 |
265 | 266 |
267 | 268 |
269 |
270 | 271 |
272 | 273 |
274 |
275 | 276 |
277 |
278 |
279 |
280 | 281 |
282 | 283 | <% END %> 284 | 285 |
286 | 287 | 288 | 289 | -------------------------------------------------------------------------------- /views/error.tt: -------------------------------------------------------------------------------- 1 | <% IF deferred.errmsg %> 2 | 3 | 7 | <% END %> 8 | 9 | 10 | <% IF deferred.succmsg %> 11 | 17 | <% END %> 18 | -------------------------------------------------------------------------------- /views/header.tt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 41 | 42 | 43 |
44 |
45 |
46 |
47 | -------------------------------------------------------------------------------- /views/home.tt: -------------------------------------------------------------------------------- 1 | <% include header.tt %> 2 | <% include sidebar.tt %> 3 | <% include error.tt %> 4 | 5 |
6 | 7 | 8 |
9 |
10 | <% IF domains && domains.size %> 11 |

Vos domaines

12 | 13 | <% FOREACH domain in domains %> 14 | 15 | 16 | 17 | 20 | 21 | 28 | 29 | <% END %> 30 |
<% domain.domain %> 18 | 19 | 22 | 23 | 26 | 27 |
31 | 32 |
33 | 34 | <% ELSE %> 35 |

Pas encore de nom de domaine ?

36 |

Réservez-en un ! \o/

37 | <% END %> 38 |
39 |
40 | 41 |
42 |
43 |

Nouveau domaine

44 |
45 |
46 |
47 | <% IF domainName.defined %> 48 | 49 | <% ELSE %> 50 | 51 | <% END %> 52 | 53 | 58 |
59 | 61 |
62 |
63 |
64 | 65 |
66 | 67 | 68 |
69 |
70 |

Nouveau mot de passe

71 |
72 |
73 |
74 | 75 | 77 |
78 |
79 |
80 |
81 | 82 |
83 | 84 |
85 |
86 |

Rappels sur l'administration d'un domaine

87 |
    88 |
  • 89 | Ne supprimez jamais les entrées de type NS ou SOA. 90 | Si vous ne savez pas ce que vous faites, ne les touchez pas. 91 |
  • 92 |
  • Ce n'est pas grave de se tromper !
  • 93 |
  • 94 | Si vous avez un doute, n'hésitez pas à nous contacter : 95 | 98 |
  • 99 |
100 |
101 | 102 |
103 |

Les mises à jour automatiques

104 |

Si vous souhaitez une mise à jour de l'adresse IP indiquée dans votre zone, aucun problème ! 105 | Téléchargez l'application (clic droit, enregistrer sous), modifiez le fichier pour y mettre vos informations puis appelez ce programme régulièrement. 106 |

109 | Il est préférable de mettre à jour une fois par heure maximum. 110 | Voici un exemple de configuration de cron : $ crontab -e 111 | 114 |
115 |
116 | 117 |
118 | 119 | 120 | 121 | -------------------------------------------------------------------------------- /views/index.tt: -------------------------------------------------------------------------------- 1 | <% include header.tt %> 2 | <% include sidebar.tt %> 3 | <% include error.tt %> 4 | 5 |
6 | 7 | 8 |
9 |
10 |

Bienvenue sur netlib.re !

11 |

Netlib.re vous donne un nom de domaine, gratuitement et simplement.

12 |

S'enregistrer »

13 |
14 |
15 | 16 |
17 | 18 |
19 | 20 |
21 |
22 | 23 |

Ce qui est proposé

24 | 25 |

26 | Vous pouvez réserver un nom de domaine et l'administrer sur ce site. 27 | Gratuitement, sans contrepartie. 28 | Nous ne fournissons pas d'hébergement, juste un nom. 29 |

30 | 31 |
32 |
33 |

Mises à jour automatiques

34 | 35 |

36 | Vous pouvez mettre à jour l'adresse IP d'un nom de domaine de façon dynamique (style dyndns). 37 | Venez découvrir ça ! 38 | Téléchargez ce script, et c'est en place en 2 minutes ! 39 |

40 | 41 |
42 |
43 | 44 |
45 |
46 | 47 |

Pourquoi ?

48 |

49 | Internet doit rester un lieu où l'utilisateur crée le contenu. 50 | Ce projet vous donne un nom, ne restez pas dans l'ombre, créez ! 51 |

52 | 53 |

54 | Aucune connaissance requise ! 55 |

56 | 57 |
58 |
59 | 60 |

Nous contacter

61 | 62 |

Si vous êtes intéressé, n'hésitez pas à venir nous parler.

63 |

IRC : #arn sur irc.geeknode.org:6667

64 | 65 |
66 |
67 | 68 |
69 | 70 |
71 | 72 | 73 | 74 | -------------------------------------------------------------------------------- /views/layouts/main.tt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | DNSManager 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | <% content %> 23 | 24 |
25 |
26 | 30 |
31 |
32 |
33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /views/sidebar.tt: -------------------------------------------------------------------------------- 1 |
2 | 102 | 103 |
104 | -------------------------------------------------------------------------------- /views/subscribe.tt: -------------------------------------------------------------------------------- 1 | <% include header.tt %> 2 | <% include sidebar.tt %> 3 | <% include error.tt %> 4 | 5 |
6 | 7 |

S'enregistrer

8 |
9 | 10 |
11 | 12 |
13 | 14 |
15 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 |
25 | 26 |
27 | 28 |
29 | 30 |
31 |
32 | 33 |
34 |
35 | 36 |
37 |
38 | 39 |
40 | 41 |
42 | 43 |
44 |
45 | --------------------------------------------------------------------------------