Changeset 1049


Ignore:
Timestamp:
02/27/26 18:19:50 (2 hours ago)
Author:
Kris Deugau
Message:

/branches/stable

Rollup merge that should finish covering everything but:

  • BIND stuff
  • ALIAS upgrade for AAAA support
  • CNAME collision branch not yet merged back to /trunk
Location:
branches/stable
Files:
2 edited
1 copied

Legend:

Unmodified
Added
Removed
  • branches/stable

  • branches/stable/DNSDB.pm

    r1048 r1049  
    27172717
    27182718
     2719## DNSDB::addRDNS
     2720# Adds a reverse DNS zone
     2721# Takes a database handle, CIDR block, reverse DNS pattern, numeric group,
     2722# and boolean(ish) state (active/inactive)
     2723# Returns a status code and message
     2724sub addRDNS {
     2725  my $self = shift;
     2726  my $dbh = $self->{dbh};
     2727  my $zone = shift;
     2728
     2729  # Autodetect formal .arpa zones
     2730  if ($zone =~ /\.arpa\.?$/) {
     2731    my $code;
     2732    ($code,$zone) = _zone2cidr($zone);
     2733    return ('FAIL', $zone) if $code eq 'FAIL';
     2734  }
     2735  $zone = NetAddr::IP->new($zone);
     2736
     2737  return ('FAIL',"Zone name must be a valid CIDR netblock") unless ($zone && $zone->addr !~ /^0/);
     2738  my $revpatt = shift;  # construct a custom (A/AAAA+)? PTR template record
     2739  my $group = shift;
     2740  my $state = shift;
     2741  my $defloc = shift || '';
     2742
     2743  $state = 1 if $state =~ /^active$/;
     2744  $state = 1 if $state =~ /^on$/;
     2745  $state = 0 if $state =~ /^inactive$/;
     2746  $state = 0 if $state =~ /^off$/;
     2747
     2748  return ('FAIL',"Invalid zone status") if $state !~ /^\d+$/;
     2749
     2750# quick check to start to see if we've already got one
     2751  my ($rdns_id) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet = ? AND default_location = ?",
     2752        undef, ("$zone", $defloc));
     2753
     2754  return ('FAIL', "Zone already exists") if $rdns_id;
     2755
     2756  # Allow transactions, and raise an exception on errors so we can catch it later.
     2757  # Use local to make sure these get "reset" properly on exiting this block
     2758  local $dbh->{AutoCommit} = 0;
     2759  local $dbh->{RaiseError} = 1;
     2760
     2761  my $warnstr = '';
     2762  my $defttl = 3600;    # 1 hour should be reasonable.  And unless things have gone horribly
     2763                        # wrong, we should have a value to override this anyway.
     2764
     2765  # Wrap all the SQL in a transaction
     2766  eval {
     2767    # insert the zone...
     2768    $dbh->do("INSERT INTO revzones (revnet,group_id,status,default_location,zserial) VALUES (?,?,?,?,?)", undef,
     2769        ($zone, $group, $state, $defloc, scalar(time()) ) );
     2770
     2771    # get the ID...
     2772    ($rdns_id) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
     2773
     2774    $self->_log(rdns_id => $rdns_id, group_id => $group,
     2775        entry => "Added ".($state ? 'active' : 'inactive')." reverse zone $zone");
     2776
     2777    # ... and now we construct the standard records from the default set.  NB:  group should be variable.
     2778    my $sth = $dbh->prepare("SELECT host,type,val,ttl FROM default_rev_records WHERE group_id=?");
     2779    my $sth_in = $dbh->prepare("INSERT INTO records (rdns_id,domain_id,host,type,val,ttl,location)".
     2780        " VALUES ($rdns_id,?,?,?,?,?,?)");
     2781    $sth->execute($group);
     2782    while (my ($host,$type,$val,$ttl) = $sth->fetchrow_array()) {
     2783      # Silently skip v4/v6 mismatches.  This is not an error, this is expected.
     2784      if ($zone->{isv6}) {
     2785        next if ($type == 65280 || $type == 65283);
     2786      } else {
     2787        next if ($type == 65281 || $type == 65284);
     2788      }
     2789
     2790      $host =~ s/ADMINDOMAIN/$self->{domain}/g;
     2791
     2792      # Check to make sure the IP stubs will fit in the zone.  Under most usage failures here should be rare.
     2793      # On failure, tack a note on to a warning string and continue without adding this record.
     2794      # While we're at it, we substitute $zone for ZONE in the value.
     2795      if ($val eq 'ZONE') {
     2796        # If we've got a pattern, we skip the default record version on (A+)PTR-template types
     2797        next if $revpatt && ($type == 65282 || $type == 65283);
     2798##fixme?  do we care if we have multiple whole-zone templates?
     2799        $val = $zone->network;
     2800      } elsif ($val =~ /ZONE/) {
     2801        my $tmpval = $val;
     2802        $tmpval =~ s/ZONE//;
     2803        # Bend the rules and allow single-trailing-number PTR or PTR template records to be inserted
     2804        # as either v4 or v6.  May make this an off-by-default config flag
     2805        # Note that the origin records that may trigger this **SHOULD** already have ZONE,\d
     2806        if ($type == 12 || $type == 65282) {
     2807          $tmpval =~ s/[,.]/::/ if ($tmpval =~ /^[,.]\d+$/ && $zone->{isv6});
     2808          $tmpval =~ s/[,:]+/./ if ($tmpval =~ /^(?:,|::)\d+$/ && !$zone->{isv6});
     2809        }
     2810        my $addr;
     2811        if ($self->_ipparent('n', 'y', \$tmpval, $rdns_id, \$addr)) {
     2812          $val = $addr->addr;
     2813        } else {
     2814          $warnstr .= "\nDefault record '$val $typemap{$type} $host' doesn't fit in $zone, skipping";
     2815          next;
     2816        }
     2817      }
     2818
     2819      # Substitute $zone for ZONE in the hostname, but only for non-NS records.
     2820      # NS records get this substitution on the value instead.
     2821      $host = _ZONE($zone, $host) if $type != 2;
     2822
     2823      # Fill in the forward domain ID if we can find it, otherwise:
     2824      # Coerce type down to PTR or PTR template if we can't
     2825      my $domid = 0;
     2826      if ($type >= 65280) {
     2827        if (!($domid = $self->_hostparent($host))) {
     2828          $warnstr .= "\nRecord added as PTR instead of $typemap{$type};  domain not found for $host";
     2829          $type = $reverse_typemap{PTR};
     2830          $domid = 0;   # just to be explicit.
     2831        }
     2832      }
     2833
     2834      _caseclean(\$type, \$host, \$val, 'n', 'y') if $self->{lowercase};
     2835
     2836      $sth_in->execute($domid,$host,$type,$val,$ttl,$defloc);
     2837
     2838      if ($typemap{$type} eq 'SOA') {
     2839        my @tmp1 = split /:/, $host;
     2840        my @tmp2 = split /:/, $val;
     2841        $self->_log(rdns_id => $rdns_id, group_id => $group,
     2842                entry => "[new $zone] Added SOA record [contact $tmp1[0]] [master $tmp1[1]] ".
     2843                "[refresh $tmp2[0]] [retry $tmp2[1]] [expire $tmp2[2]] [minttl $tmp2[3]], TTL $ttl");
     2844        $defttl = $tmp2[3];
     2845      } else {
     2846        my $logentry = "[new $zone] Added record '$host $typemap{$type} $val', TTL $ttl";
     2847        $logentry .= ", default location ".$self->getLoc($defloc)->{description} if $defloc;
     2848        $self->_log(rdns_id => $rdns_id, domain_id => $domid, group_id => $group, entry => $logentry);
     2849      }
     2850    }
     2851
     2852    # Generate record based on provided pattern. 
     2853    if ($revpatt) {
     2854      my $host;
     2855      my $type = ($zone->{isv6} ? 65284 : 65283);
     2856      my $val = $zone->network;
     2857
     2858      # Substitute $zone for ZONE in the hostname.
     2859      $host = _ZONE($zone, $revpatt);
     2860
     2861      my $domid = 0;
     2862      if (!($domid = $self->_hostparent($host))) {
     2863        $warnstr .= "\nDefault pattern added as PTR template instead of $typemap{$type};  domain not found for $host";
     2864        $type = 65282;
     2865        $domid = 0;     # just to be explicit.
     2866      }
     2867
     2868      $sth_in->execute($domid,$host,$type,$val,$defttl,$defloc);
     2869      my $logentry = "[new $zone] Added record '$host $typemap{$type}";
     2870      $self->_log(rdns_id => $rdns_id, domain_id => $domid, group_id => $group,
     2871        entry => $logentry." $val', TTL $defttl from pattern");
     2872    }
     2873
     2874    # If there are warnings (presumably about default records skipped for cause) log them
     2875    $self->_log(rdns_id => $rdns_id, group_id => $group, entry => "Warning(s) adding $zone:$warnstr")
     2876        if $warnstr;
     2877
     2878    # once we get here, we should have suceeded.
     2879    $dbh->commit;
     2880  }; # end eval
     2881
     2882  if ($@) {
     2883    my $msg = $@;
     2884    eval { $dbh->rollback; };
     2885    $self->_log(group_id => $group, entry => "Failed adding reverse zone $zone ($msg)")
     2886        if $self->{log_failures};
     2887    $dbh->commit;       # since we enabled transactions earlier
     2888    return ('FAIL',$msg);
     2889  } else {
     2890    my $retcode = 'OK';
     2891    if ($warnstr) {
     2892      $resultstr = $warnstr;
     2893      $retcode = 'WARN';
     2894    }
     2895    return ($retcode, $rdns_id);
     2896  }
     2897
     2898} # end addRDNS()
     2899
     2900
    27192901## DNSDB::delZone()
    27202902# Delete a forward or reverse zone.
     
    29143096  return $revid if $revid;
    29153097} # end revID()
    2916 
    2917 
    2918 ## DNSDB::addRDNS
    2919 # Adds a reverse DNS zone
    2920 # Takes a database handle, CIDR block, reverse DNS pattern, numeric group,
    2921 # and boolean(ish) state (active/inactive)
    2922 # Returns a status code and message
    2923 sub addRDNS {
    2924   my $self = shift;
    2925   my $dbh = $self->{dbh};
    2926   my $zone = shift;
    2927 
    2928   # Autodetect formal .arpa zones
    2929   if ($zone =~ /\.arpa\.?$/) {
    2930     my $code;
    2931     ($code,$zone) = _zone2cidr($zone);
    2932     return ('FAIL', $zone) if $code eq 'FAIL';
    2933   }
    2934   $zone = NetAddr::IP->new($zone);
    2935 
    2936   return ('FAIL',"Zone name must be a valid CIDR netblock") unless ($zone && $zone->addr !~ /^0/);
    2937   my $revpatt = shift;  # construct a custom (A/AAAA+)? PTR template record
    2938   my $group = shift;
    2939   my $state = shift;
    2940   my $defloc = shift || '';
    2941 
    2942   $state = 1 if $state =~ /^active$/;
    2943   $state = 1 if $state =~ /^on$/;
    2944   $state = 0 if $state =~ /^inactive$/;
    2945   $state = 0 if $state =~ /^off$/;
    2946 
    2947   return ('FAIL',"Invalid zone status") if $state !~ /^\d+$/;
    2948 
    2949 # quick check to start to see if we've already got one
    2950   my ($rdns_id) = $dbh->selectrow_array("SELECT rdns_id FROM revzones WHERE revnet = ? AND default_location = ?",
    2951         undef, ("$zone", $defloc));
    2952 
    2953   return ('FAIL', "Zone already exists") if $rdns_id;
    2954 
    2955   # Allow transactions, and raise an exception on errors so we can catch it later.
    2956   # Use local to make sure these get "reset" properly on exiting this block
    2957   local $dbh->{AutoCommit} = 0;
    2958   local $dbh->{RaiseError} = 1;
    2959 
    2960   my $warnstr = '';
    2961   my $defttl = 3600;    # 1 hour should be reasonable.  And unless things have gone horribly
    2962                         # wrong, we should have a value to override this anyway.
    2963 
    2964   # Wrap all the SQL in a transaction
    2965   eval {
    2966     # insert the zone...
    2967     $dbh->do("INSERT INTO revzones (revnet,group_id,status,default_location,zserial) VALUES (?,?,?,?,?)", undef,
    2968         ($zone, $group, $state, $defloc, scalar(time()) ) );
    2969 
    2970     # get the ID...
    2971     ($rdns_id) = $dbh->selectrow_array("SELECT currval('revzones_rdns_id_seq')");
    2972 
    2973     $self->_log(rdns_id => $rdns_id, group_id => $group,
    2974         entry => "Added ".($state ? 'active' : 'inactive')." reverse zone $zone");
    2975 
    2976     # ... and now we construct the standard records from the default set.  NB:  group should be variable.
    2977     my $sth = $dbh->prepare("SELECT host,type,val,ttl FROM default_rev_records WHERE group_id=?");
    2978     my $sth_in = $dbh->prepare("INSERT INTO records (rdns_id,domain_id,host,type,val,ttl,location)".
    2979         " VALUES ($rdns_id,?,?,?,?,?,?)");
    2980     $sth->execute($group);
    2981     while (my ($host,$type,$val,$ttl) = $sth->fetchrow_array()) {
    2982       # Silently skip v4/v6 mismatches.  This is not an error, this is expected.
    2983       if ($zone->{isv6}) {
    2984         next if ($type == 65280 || $type == 65283);
    2985       } else {
    2986         next if ($type == 65281 || $type == 65284);
    2987       }
    2988 
    2989       $host =~ s/ADMINDOMAIN/$self->{domain}/g;
    2990 
    2991       # Check to make sure the IP stubs will fit in the zone.  Under most usage failures here should be rare.
    2992       # On failure, tack a note on to a warning string and continue without adding this record.
    2993       # While we're at it, we substitute $zone for ZONE in the value.
    2994       if ($val eq 'ZONE') {
    2995         # If we've got a pattern, we skip the default record version on (A+)PTR-template types
    2996         next if $revpatt && ($type == 65282 || $type == 65283);
    2997 ##fixme?  do we care if we have multiple whole-zone templates?
    2998         $val = $zone->network;
    2999       } elsif ($val =~ /ZONE/) {
    3000         my $tmpval = $val;
    3001         $tmpval =~ s/ZONE//;
    3002         # Bend the rules and allow single-trailing-number PTR or PTR template records to be inserted
    3003         # as either v4 or v6.  May make this an off-by-default config flag
    3004         # Note that the origin records that may trigger this **SHOULD** already have ZONE,\d
    3005         if ($type == 12 || $type == 65282) {
    3006           $tmpval =~ s/[,.]/::/ if ($tmpval =~ /^[,.]\d+$/ && $zone->{isv6});
    3007           $tmpval =~ s/[,:]+/./ if ($tmpval =~ /^(?:,|::)\d+$/ && !$zone->{isv6});
    3008         }
    3009         my $addr;
    3010         if ($self->_ipparent('n', 'y', \$tmpval, $rdns_id, \$addr)) {
    3011           $val = $addr->addr;
    3012         } else {
    3013           $warnstr .= "\nDefault record '$val $typemap{$type} $host' doesn't fit in $zone, skipping";
    3014           next;
    3015         }
    3016       }
    3017 
    3018       # Substitute $zone for ZONE in the hostname, but only for non-NS records.
    3019       # NS records get this substitution on the value instead.
    3020       $host = _ZONE($zone, $host) if $type != 2;
    3021 
    3022       # Fill in the forward domain ID if we can find it, otherwise:
    3023       # Coerce type down to PTR or PTR template if we can't
    3024       my $domid = 0;
    3025       if ($type >= 65280) {
    3026         if (!($domid = $self->_hostparent($host))) {
    3027           $warnstr .= "\nRecord added as PTR instead of $typemap{$type};  domain not found for $host";
    3028           $type = $reverse_typemap{PTR};
    3029           $domid = 0;   # just to be explicit.
    3030         }
    3031       }
    3032 
    3033       _caseclean(\$type, \$host, \$val, 'n', 'y') if $self->{lowercase};
    3034 
    3035       $sth_in->execute($domid,$host,$type,$val,$ttl,$defloc);
    3036 
    3037       if ($typemap{$type} eq 'SOA') {
    3038         my @tmp1 = split /:/, $host;
    3039         my @tmp2 = split /:/, $val;
    3040         $self->_log(rdns_id => $rdns_id, group_id => $group,
    3041                 entry => "[new $zone] Added SOA record [contact $tmp1[0]] [master $tmp1[1]] ".
    3042                 "[refresh $tmp2[0]] [retry $tmp2[1]] [expire $tmp2[2]] [minttl $tmp2[3]], TTL $ttl");
    3043         $defttl = $tmp2[3];
    3044       } else {
    3045         my $logentry = "[new $zone] Added record '$host $typemap{$type} $val', TTL $ttl";
    3046         $logentry .= ", default location ".$self->getLoc($defloc)->{description} if $defloc;
    3047         $self->_log(rdns_id => $rdns_id, domain_id => $domid, group_id => $group, entry => $logentry);
    3048       }
    3049     }
    3050 
    3051     # Generate record based on provided pattern. 
    3052     if ($revpatt) {
    3053       my $host;
    3054       my $type = ($zone->{isv6} ? 65284 : 65283);
    3055       my $val = $zone->network;
    3056 
    3057       # Substitute $zone for ZONE in the hostname.
    3058       $host = _ZONE($zone, $revpatt);
    3059 
    3060       my $domid = 0;
    3061       if (!($domid = $self->_hostparent($host))) {
    3062         $warnstr .= "\nDefault pattern added as PTR template instead of $typemap{$type};  domain not found for $host";
    3063         $type = 65282;
    3064         $domid = 0;     # just to be explicit.
    3065       }
    3066 
    3067       $sth_in->execute($domid,$host,$type,$val,$defttl,$defloc);
    3068       my $logentry = "[new $zone] Added record '$host $typemap{$type}";
    3069       $self->_log(rdns_id => $rdns_id, domain_id => $domid, group_id => $group,
    3070         entry => $logentry." $val', TTL $defttl from pattern");
    3071     }
    3072 
    3073     # If there are warnings (presumably about default records skipped for cause) log them
    3074     $self->_log(rdns_id => $rdns_id, group_id => $group, entry => "Warning(s) adding $zone:$warnstr")
    3075         if $warnstr;
    3076 
    3077     # once we get here, we should have suceeded.
    3078     $dbh->commit;
    3079   }; # end eval
    3080 
    3081   if ($@) {
    3082     my $msg = $@;
    3083     eval { $dbh->rollback; };
    3084     $self->_log(group_id => $group, entry => "Failed adding reverse zone $zone ($msg)")
    3085         if $self->{log_failures};
    3086     $dbh->commit;       # since we enabled transactions earlier
    3087     return ('FAIL',$msg);
    3088   } else {
    3089     my $retcode = 'OK';
    3090     if ($warnstr) {
    3091       $resultstr = $warnstr;
    3092       $retcode = 'WARN';
    3093     }
    3094     return ($retcode, $rdns_id);
    3095   }
    3096 
    3097 } # end addRDNS()
    30983098
    30993099
     
    47044704
    47054705  return ('FAIL', "expires must be 1, 't', or 'until',  or 0, 'f', or 'after'")
    4706         if ($stamp && !$expires)
     4706        if ($stamp && !defined($expires))
    47074707        || ($stamp && $expires ne '0' && $expires ne '1' && $expires ne 't' && $expires ne 'f');
    47084708
     
    47484748
    47494749  return ($retcode,$retmsg) if $retcode eq 'FAIL';
     4750
     4751  # Minor cleanup of invalid DNS labels
     4752##fixme: seems like map should be used here to be decently Perlish, but any time I stick
     4753# a s/// inside the block map only returns the match/replace count instead of whatever
     4754# the changed $_ is for some reason
     4755  my @hbits = split /\./, $$host;
     4756  foreach (@hbits) {
     4757    s/^-+//;
     4758    s/-+$//;
     4759  }
     4760  $$host = join '.', @hbits;
    47504761
    47514762  # Set up database fields and bind parameters
     
    48644875
    48654876  return ('FAIL', "expires must be 1, 't', or 'until',  or 0, 'f', or 'after'")
    4866         if ($stamp && !$expires)
     4877        if ($stamp && !defined($expires))
    48674878        || ($stamp && $expires ne '0' && $expires ne '1' && $expires ne 't' && $expires ne 'f');
    48684879
     
    49154926
    49164927  return ($retcode,$retmsg) if $retcode eq 'FAIL';
     4928
     4929  # Minor cleanup of invalid DNS labels
     4930##fixme: seems like map should be used here to be decently Perlish, but any time I stick
     4931# a s/// inside the block map only returns the match/replace count instead of whatever
     4932# the changed $_ is for some reason
     4933  my @hbits = split /\./, $$host;
     4934  foreach (@hbits) {
     4935    s/^-+//;
     4936    s/-+$//;
     4937  }
     4938  $$host = join '.', @hbits;
    49174939
    49184940  # Set up database fields and bind parameters.  Note only the optional fields
Note: See TracChangeset for help on using the changeset viewer.