build_zone 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. #!/usr/bin/env perl
  2. =head1 build_zone
  3. Build the basic zone for a tCMS site and import it into powerdns.
  4. Otherwise, make it a post so you can edit it in the config backend.
  5. In general this should not be called outside of Installer.mk.
  6. =head2 OPTIONS
  7. =head3 subdomain
  8. Specify a subdomain, such as 'foo' to add to the domain.
  9. May be passed multiple times.
  10. =head3 gsv
  11. Google site verification string goes into TXT record
  12. =head3 cname
  13. Specify a cname, such as 'bar' to add to the domain.
  14. By default, the cnames 'www', 'mail' and 'chat' are set up, as these are essential tCMS services setup by the makefile before this.
  15. May be passed multiple times.
  16. =cut
  17. use strict;
  18. use warnings;
  19. no warnings qw{experimental};
  20. use feature qw{signatures state};
  21. use FindBin::libs;
  22. use Trog::Config();
  23. use Trog::Zone();
  24. use Trog::Auth;
  25. use DNS::Unbound;
  26. use Net::DNS::Packet;
  27. use Text::Xslate;
  28. use Net::IP;
  29. use Getopt::Long qw{GetOptionsFromArray};
  30. $ENV{NOHUP} = 1;
  31. exit main(@ARGV) unless caller;
  32. sub main(@args) {
  33. my %options;
  34. GetOptionsFromArray(\@args,
  35. 'subdomain=s@' => \$options{subdomains},
  36. 'gsv=s' => \$options{gsv},
  37. 'cname=s@' => \$options{cnames},
  38. );
  39. # Paranoia, some versions of getopt don't do this
  40. $options{cnames} //= [];
  41. $options{subdomains} //=[];
  42. my $domain = Trog::Config->get()->param('general.hostname');
  43. die "Hostname not set in tCMS configuration. Please set this first." unless $domain;
  44. my $user = Trog::Auth::primary_user;
  45. die "Primary tCMS user not yet set up" unless $user;
  46. # Get a flesh start
  47. Trog::Zone::delzone($domain);
  48. my ($ip) = domain2ips($domain, 'A');
  49. my ($ip6) = domain2ips($domain, 'AAAA');
  50. my $data = {
  51. ip => $ip,
  52. ip6 => $ip6,
  53. ip_reversed => Net::IP->new($ip)->reverse_ip(),
  54. ip6_reversed => Net::IP->new($ip6)->reverse_ip(),
  55. title => $domain,
  56. nameservers => ["ns1.$domain"],
  57. subdomains => [map { { name => $_, ip => domain2ips("$_.$domain", "A"), "ip6" => domain2ips("$_.$domain", "AAAA"), nameservers => ["ns1.$_.$domain"] } } @{$options{subdomains}}],
  58. cnames => [(qw{www mail chat},@{$options{cnames}})],
  59. gsv_string => $options{gsv} // '',
  60. version => 0,
  61. dkim_pkey => extract_pkey($domain),
  62. acme_challenge => get_dns_dcv_string( $domain ),
  63. visibility => 'private',
  64. acls => [qw{admin}],
  65. aliases => [],
  66. tags => ['zone'],
  67. form => 'dns.tx',
  68. callback => "Trog::Routes::TXT::zone",
  69. id => undef,
  70. created => undef,
  71. local_href => "/text/zone/$domain",
  72. href => "/text/zone/$domain",
  73. user => $user,
  74. };
  75. my $zone = Trog::Zone::addzone($data);
  76. print $data->{data};
  77. return 0;
  78. }
  79. sub extract_pkey ( $domain ) {
  80. open(my $fh, '<', "/etc/opendkim/keys/$domain/mail.public");
  81. my @lines = map { chomp $_; $_ } readline $fh;
  82. close $fh;
  83. shift @lines;
  84. pop @lines;
  85. return join('', @lines);
  86. }
  87. sub get_dns_dcv_string( $domain ) {
  88. return "TODO";
  89. }
  90. sub domain2ips( $domain, $type ) {
  91. # XXX would be great to use state here, but felipe
  92. my $resolver = DNS::Unbound->new();
  93. my $p = $resolver->resolve( $domain, $type )->answer_packet();
  94. my @rrs = Net::DNS::Packet->new( \$p )->answer;
  95. my @addr = map { $_->address } @rrs;
  96. @addr=(get_local_ip($type)) unless @addr;
  97. return @addr;
  98. }
  99. my $addrout='';
  100. sub get_local_ip( $type ) {
  101. $addrout //=qx{ip addr};
  102. return $type eq 'A' ? _ipv4() : _ipv6();
  103. }
  104. sub _ipv4 {
  105. state $ip;
  106. return $ip if $ip;
  107. ($ip) = $addrout =~ m{inet\s+([\d|\.|/]+)\s+scope\s+global}gmx;
  108. return $ip;
  109. }
  110. sub _ipv6 {
  111. state $ip6;
  112. return $ip6 if $ip6;
  113. ($ip6) = $addrout =~ m{inet6\s+([a-f|\d|:|/]+)\s+scope\s+global\s+dynamic\s+mngtmpaddr}gmx;
  114. # We have to strip the CIDR off of it, or it breaks Net::IP's brain.
  115. $ip6 =~ s|/\d+$||;
  116. return $ip6;
  117. }