Write

#!/usr/local/cpanel/3rdparty/bin/perl
#
# Cloudflare DNS Manager - WHM Plugin v1.1
# Features: Zone transfer, mass delete, record editing
#

use strict;
use warnings;

BEGIN {
    unshift @INC, '/usr/local/cpanel/3rdparty/perl/542/cpanel-lib';
    unshift @INC, '/var/cpanel/plugins/cloudflare_dns/lib';
}

use CGI;
use JSON;
use LWP::UserAgent;
use HTTP::Request;

# Config file path
my $CONFIG_FILE = '/var/cpanel/plugins/cloudflare_dns/config.json';

# Load or initialize config
sub load_config {
    if (-f $CONFIG_FILE) {
        open my $fh, '<', $CONFIG_FILE or return {};
        local $/;
        my $json = <$fh>;
        close $fh;
        return eval { decode_json($json) } || {};
    }
    return {};
}

sub save_config {
    my ($config) = @_;
    open my $fh, '>', $CONFIG_FILE or die "Cannot write config: $!";
    print $fh encode_json($config);
    close $fh;
    chmod 0600, $CONFIG_FILE;
}

# Cloudflare API
sub cf_api {
    my ($token, $method, $endpoint, $data) = @_;

    my $ua = LWP::UserAgent->new(timeout => 30);
    my $url = "https://api.cloudflare.com/client/v4" . $endpoint;

    my $req = HTTP::Request->new($method => $url);
    $req->header('Authorization' => "Bearer $token");
    $req->header('Content-Type' => 'application/json');

    if ($data) {
        $req->content(encode_json($data));
    }

    my $res = $ua->request($req);

    if ($res->is_success) {
        return decode_json($res->content);
    } else {
        return { success => 0, errors => [{ message => $res->status_line }] };
    }
}

# WHM API - Get zone from local DNS
sub whm_get_zone {
    my ($domain) = @_;

    # Use cPanel's DNS functions
    my $zonefile = "/var/named/$domain.db";
    return undef unless -f $zonefile;

    open my $fh, '<', $zonefile or return undef;
    my @lines = <$fh>;
    close $fh;

    my @records;
    my $origin = $domain;

    for my $line (@lines) {
        chomp $line;
        next if $line =~ /^\s*;/;  # Skip comments
        next if $line =~ /^\s*$/;  # Skip empty lines
        next if $line =~ /^\$TTL/; # Skip TTL directive

        if ($line =~ /^\$ORIGIN\s+(\S+)/) {
            $origin = $1;
            $origin =~ s/\.$//;
            next;
        }

        # Parse DNS records: name ttl class type content
        if ($line =~ /^(\S+)\s+(\d+)?\s*IN\s+(A|AAAA|CNAME|TXT|MX|SRV|NS)\s+(.+)$/i) {
            my ($name, $ttl, $type, $content) = ($1, $2 || 3600, uc($3), $4);

            # Normalize name
            $name = $origin if $name eq '@';
            $name = "$name.$origin" unless $name =~ /\.$/;
            $name =~ s/\.$//;

            # Clean content
            $content =~ s/\s+$//;
            $content =~ s/^"(.*)"$/$1/ if $type eq 'TXT';

            # Skip NS records for the domain itself (Cloudflare manages these)
            next if $type eq 'NS' && $name eq $domain;

            push @records, {
                name => $name,
                type => $type,
                content => $content,
                ttl => int($ttl)
            };
        }
    }

    return \@records;
}

# WHM API - Remove zone or add marker
sub whm_mark_zone_transferred {
    my ($domain, $remove) = @_;

    if ($remove) {
        # Use WHM API to remove zone
        my $result = `whmapi1 killdns domain=$domain 2>&1`;
        return $result !~ /error/i;
    } else {
        # Add marker TXT record
        my $marker = "v=cloudflare-dns; managed=true; transferred=" . time();
        system("whmapi1 addzonerecord domain=$domain name=_cloudflare type=TXT txtdata=\"$marker\" 2>&1");
        return 1;
    }
}

# Get list of WHM zones
sub whm_list_zones {
    my $result = `whmapi1 listzones 2>&1`;
    my @zones;

    while ($result =~ /domain:\s*(\S+)/g) {
        push @zones, $1;
    }

    return \@zones;
}

# Check if zone has Cloudflare marker
sub whm_has_cf_marker {
    my ($domain) = @_;
    my $result = `dig +short TXT _cloudflare.$domain \@localhost 2>/dev/null`;
    return $result =~ /cloudflare-dns/;
}

# Main
my $cgi = CGI->new;
my $action = $cgi->param('action') || 'home';

# Print header
print $cgi->header('text/html');

# Load config
my $config = load_config();
my $api_token = $config->{api_token} || '';

# HTML header
print_html_header();

# Route actions
if ($action eq 'home') {
    show_home($cgi, $config, $api_token);
}
elsif ($action eq 'save_token') {
    save_token($cgi, $config);
}
elsif ($action eq 'list_zones') {
    list_zones($api_token);
}
elsif ($action eq 'view_zone') {
    view_zone($cgi, $api_token);
}
elsif ($action eq 'add_record') {
    add_record($cgi, $api_token);
}
elsif ($action eq 'edit_record') {
    edit_record($cgi, $api_token);
}
elsif ($action eq 'save_record') {
    save_record($cgi, $api_token);
}
elsif ($action eq 'delete_record') {
    delete_record($cgi, $api_token);
}
elsif ($action eq 'mass_delete') {
    mass_delete($cgi, $api_token);
}
elsif ($action eq 'add_m365') {
    add_m365($cgi, $api_token);
}
elsif ($action eq 'import_zone') {
    import_zone($cgi, $api_token);
}
elsif ($action eq 'transfer_zone') {
    transfer_zone($cgi, $api_token);
}
elsif ($action eq 'do_transfer') {
    do_transfer($cgi, $api_token);
}
elsif ($action eq 'whm_zones') {
    whm_zones($cgi, $api_token);
}
else {
    print "
Unknown action: $action
";
}

print_html_footer();

# ============ Actions ============

sub show_home {
    my ($cgi, $config, $token) = @_;

    print '
Cloudflare DNS Manager
';

    if (!$token) {
        print '
';
        print 'Setup Required: Please configure your Cloudflare API token.';
        print '
';
        show_token_form($config);
        return;
    }

    my $verify = cf_api($token, 'GET', '/user/tokens/verify');
    if (!$verify->{success}) {
        print '
API Token is invalid or expired.
';
        show_token_form($config);
        return;
    }

    print '
✓ Connected to Cloudflare
';

    print '
';
    print 'Cloudflare Zones ';
    print 'WHM Zones ';
    print 'Import New Zone ';
    print '<button onclick="document.getElementById(\'token-form\').style.display=\'block\'" class="btn btn-secondary">Update Token</button>';
    print '
';

    print '
';
    show_token_form($config);
    print '
';

    # Quick stats
    my $cf_zones = cf_api($token, 'GET', '/zones?per_page=50');
    if ($cf_zones->{success}) {
        my $active = scalar grep { $_->{status} eq 'active' } @{$cf_zones->{result}};
        my $pending = scalar grep { $_->{status} eq 'pending' } @{$cf_zones->{result}};
        print '
';
        print "
Cloudflare Zones: $active active, $pending pending
";
        print '
';
    }
}

sub show_token_form {
    my ($config) = @_;
    my $current = $config->{api_token} ? '********' . substr($config->{api_token}, -4) : 'Not set';

    print '<form method="post" action="?action=save_token" style="margin: 20px 0; padding: 20px; background: #f5f5f5; border-radius: 5px;">';
    print '
Cloudflare API Token
';
    print "
Current: $current
";
    print '
';
    print 'API Token:
';
    print '<input type="password" name="api_token" class="form-control" style="width: 400px;" placeholder="Enter Cloudflare API token">';
    print '
';
    print '<button type="submit" class="btn btn-success">Save Token</button>';
    print '
Required: Zone:Read, Zone:Edit, DNS:Read, DNS:Edit
';
    print '</form>';
}

sub save_token {
    my ($cgi, $config) = @_;
    my $token = $cgi->param('api_token');

    if ($token) {
        $config->{api_token} = $token;
        save_config($config);
        print '
API Token saved successfully.
';
        print '
Return to Home
';
    } else {
        print '
No token provided.
';
    }
}

sub list_zones {
    my ($token) = @_;

    print '
Cloudflare Zones
';
    print '
← Back
';

    my $result = cf_api($token, 'GET', '/zones?per_page=50');

    if (!$result->{success}) {
        print '
Failed to load zones
';
        return;
    }

    print '
';
    print '

Domain	Status	Plan	Actions

';
    print '
';

    for my $zone (sort { $a->{name} cmp $b->{name} } @{$result->{result}}) {
        my $status_class = $zone->{status} eq 'active' ? 'success' : 'warning';
        print "
";
        print "$zone->{name}	";
        print "$zone->{status}	";
        print "$zone->{plan}{name}	";
        print "";
        print "{id}&domain=$zone->{name}' class='btn btn-sm btn-primary'>DNS ";
        print "{id}&domain=$zone->{name}' class='btn btn-sm btn-info'>+M365";
        print "	";
        print "
";
    }

    print '

';
}

# NEW: WHM Zones list with transfer option
sub whm_zones {
    my ($cgi, $token) = @_;

    print '
WHM DNS Zones
';
    print '
← Back
';
    print '
Transfer zones from WHM to Cloudflare for centralized DNS management.
';

    my $zones = whm_list_zones();

    # Get Cloudflare zones for comparison
    my $cf_result = cf_api($token, 'GET', '/zones?per_page=100');
    my %cf_zones;
    if ($cf_result->{success}) {
        $cf_zones{$_->{name}} = $_->{status} for @{$cf_result->{result}};
    }

    print '
';
    print '

Domain	WHM Status	Cloudflare Status	Actions

';
    print '
';

    for my $domain (sort @$zones) {
        my $has_marker = whm_has_cf_marker($domain);
        my $cf_status = $cf_zones{$domain} || 'Not in Cloudflare';

        my $whm_status = $has_marker ? 'Transferred' : 'Active';
        my $cf_label = $cf_status eq 'active' ? 'Active' :
                       $cf_status eq 'pending' ? 'Pending' :
                       '-';

        print "
";
        print "$domain	";
        print "$whm_status	";
        print "$cf_label	";
        print "";

        if (!exists $cf_zones{$domain}) {
            print "Transfer to CF";
        } elsif ($cf_status eq 'active' && !$has_marker) {
            print "Mark Transferred";
        } else {
            print "-";
        }

        print "	";
        print "
";
    }

    print '

';
}

# NEW: Transfer zone from WHM to Cloudflare
sub transfer_zone {
    my ($cgi, $token) = @_;

    my $domain = $cgi->param('domain');
    my $mark_only = $cgi->param('mark_only');

    print "
Transfer Zone: $domain
";
    print '
← Back to WHM Zones
';

    if ($mark_only) {
        # Just mark it, zone already in Cloudflare
        print '
Zone already exists in Cloudflare. Choose how to handle WHM zone:
';
    } else {
        # Preview records to transfer
        my $records = whm_get_zone($domain);

        if (!$records || !@$records) {
            print '
Could not read zone file or no records found.
';
            return;
        }

        print '
Records to Transfer
';
        print '
';
        print '

Type	Name	Content	TTL

';
        print '
';

        for my $rec (@$records) {
            my $content = length($rec->{content}) > 60 ? substr($rec->{content}, 0, 57) . '...' : $rec->{content};
            print "
$rec->{type}	$rec->{name}	$content	$rec->{ttl}
";
        }

        print '

';
        print "
Total: " . scalar(@$records) . " records
";
    }

    print '<form method="post" action="?action=do_transfer" style="margin-top: 20px; padding: 20px; background: #f5f5f5; border-radius: 5px;">';
    print "<input type='hidden' name='domain' value='$domain'>";
    print "<input type='hidden' name='mark_only' value='$mark_only'>";

    print '
After Transfer Options
';
    print '
';
    print '<input type="radio" name="whm_action" value="marker" checked> Add marker record (keep zone in WHM, add _cloudflare TXT)
';
    print '<input type="radio" name="whm_action" value="remove"> Remove zone from WHM (delete local zone file)
';
    print '<input type="radio" name="whm_action" value="none"> Do nothing (keep both)';
    print '
';

    unless ($mark_only) {
        print '
';
        print 'Important: After transfer, you must update nameservers at the registrar to point to Cloudflare.';
        print '
';
    }

    print '<button type="submit" class="btn btn-success">Proceed with Transfer</button>';
    print ' Cancel';
    print '</form>';
}

# NEW: Execute zone transfer
sub do_transfer {
    my ($cgi, $token) = @_;

    my $domain = $cgi->param('domain');
    my $mark_only = $cgi->param('mark_only');
    my $whm_action = $cgi->param('whm_action') || 'marker';

    print "
Transferring: $domain
";

    my $zone_id;
    my @results;

    unless ($mark_only) {
        # Step 1: Create zone in Cloudflare
        print '
Step 1: Create Zone in Cloudflare
';
        my $create_result = cf_api($token, 'POST', '/zones', { name => $domain, jump_start => \0 });

        if ($create_result->{success}) {
            $zone_id = $create_result->{result}{id};
            print "
Zone created! ID: $zone_id
";
            print '
Nameservers:

';
            print "
$_
" for @{$create_result->{result}{name_servers}};
            print '
';
        } elsif ($create_result->{errors}[0]{code} == 1061) {
            # Zone already exists
            my $existing = cf_api($token, 'GET', "/zones?name=$domain");
            if ($existing->{success} && @{$existing->{result}}) {
                $zone_id = $existing->{result}[0]{id};
                print "
Zone already exists in Cloudflare. ID: $zone_id
";
            }
        } else {
            print "
Failed: $create_result->{errors}[0]{message}
";
            return;
        }

        # Step 2: Import records
        print '
Step 2: Import DNS Records
';
        my $records = whm_get_zone($domain);

        print '
';
        print '

Record	Status

';
        print '
';

        for my $rec (@$records) {
            my $data = {
                type => $rec->{type},
                name => $rec->{name},
                content => $rec->{content},
                ttl => $rec->{ttl} > 0 ? $rec->{ttl} : 1
            };

            # Handle MX priority
            if ($rec->{type} eq 'MX' && $rec->{content} =~ /^(\d+)\s+(.+)/) {
                $data->{priority} = int($1);
                $data->{content} = $2;
            }

            my $result = cf_api($token, 'POST', "/zones/$zone_id/dns_records", $data);
            my $status = $result->{success} ? 'Added' : ($result->{errors}[0]{message} || 'Failed');
            my $class = $result->{success} ? 'success' : 'warning';

            print "
$rec->{type} $rec->{name}	$status
";
            push @results, { record => "$rec->{type} $rec->{name}", success => $result->{success} };
        }

        print '

';
    } else {
        print '
Skipped zone creation (already in Cloudflare)
';
    }

    # Step 3: Handle WHM zone
    print '
Step 3: WHM Zone Action
';

    if ($whm_action eq 'marker') {
        whm_mark_zone_transferred($domain, 0);
        print '
Added _cloudflare marker record to WHM zone.
';
    } elsif ($whm_action eq 'remove') {
        if (whm_mark_zone_transferred($domain, 1)) {
            print '
Removed zone from WHM.
';
        } else {
            print '
Could not remove zone from WHM (may require manual removal).
';
        }
    } else {
        print '
No changes made to WHM zone.
';
    }

    print '
';
    print 'Next Step: Update nameservers at your registrar to Cloudflare\'s nameservers.';
    print '
';

    print '
';
    print 'View Cloudflare Zones ';
    print 'View WHM Zones';
    print '
';
}

sub view_zone {
    my ($cgi, $token) = @_;

    my $zone_id = $cgi->param('zone_id');
    my $domain = $cgi->param('domain');

    print "
DNS Records: $domain
";
    print '
← Back to Zones
';

    # Add record form
    print '
';
    print '
Add Record
';
    print '<form method="post" action="?action=add_record">';
    print "<input type='hidden' name='zone_id' value='$zone_id'>";
    print "<input type='hidden' name='domain' value='$domain'>";
    print '<select name="type" class="form-control" style="width:100px; display:inline-block;">';
    print '<option>A</option><option>AAAA</option><option>CNAME</option><option>TXT</option><option>MX</option>';
    print '</select> ';
    print '<input type="text" name="name" placeholder="Name" class="form-control" style="width: 150px; display:inline-block;"> ';
    print '<input type="text" name="content" placeholder="Content" class="form-control" style="width: 300px; display:inline-block;"> ';
    print '<input type="checkbox" name="proxied"> Proxied ';
    print '<button type="submit" class="btn btn-success">Add</button>';
    print '</form>
';

    my $result = cf_api($token, 'GET', "/zones/$zone_id/dns_records?per_page=100");

    if (!$result->{success}) {
        print '
Failed to load records
';
        return;
    }

    # Mass delete form wrapper
    print '<form method="post" action="?action=mass_delete" id="mass-delete-form">';
    print "<input type='hidden' name='zone_id' value='$zone_id'>";
    print "<input type='hidden' name='domain' value='$domain'>";

    print '
';
    print '<button type="button" onclick="toggleAll()" class="btn btn-sm btn-secondary">Select All</button> ';
    print '<button type="submit" class="btn btn-sm btn-danger" onclick="return confirmMassDelete()">Delete Selected</button>';
    print '
';

    print '
';
    print '

<input type="checkbox" id="select-all" onclick="toggleAll()">	Type	Name	Content	TTL	Proxied	Actions

';
    print '
';

    for my $rec (sort { $a->{type} cmp $b->{type} || $a->{name} cmp $b->{name} } @{$result->{result}}) {
        my $content = length($rec->{content}) > 40 ? substr($rec->{content}, 0, 37) . '...' : $rec->{content};
        my $ttl = $rec->{ttl} == 1 ? 'Auto' : $rec->{ttl};
        my $proxied = $rec->{proxied} ? '✓' : '';

        print "
";
        print "<input type='checkbox' name='record_ids' value='$rec->{id}' class='record-checkbox'>	";
        print "$rec->{type}	";
        print "$rec->{name}	";
        print "{content}' style='font-size:12px;'>$content	";
        print "$ttl	";
        print "$proxied	";
        print "";
        print "{id}' class='btn btn-sm btn-info'>Edit ";
        print "{id}' class='btn btn-sm btn-danger' onclick='return confirm(\"Delete?\")'>Del";
        print "	";
        print "
";
    }

    print '

';
    print '</form>';

    # JavaScript for select all
    print <<'JS';

JS
}

# NEW: Edit record form
sub edit_record {
    my ($cgi, $token) = @_;

    my $zone_id = $cgi->param('zone_id');
    my $domain = $cgi->param('domain');
    my $record_id = $cgi->param('record_id');

    # Get current record
    my $result = cf_api($token, 'GET', "/zones/$zone_id/dns_records/$record_id");

    if (!$result->{success}) {
        print '
Record not found
';
        return;
    }

    my $rec = $result->{result};

    print "
Edit Record
";
    print "
← Back to $domain
";

    print '<form method="post" action="?action=save_record" style="max-width: 600px;">';
    print "<input type='hidden' name='zone_id' value='$zone_id'>";
    print "<input type='hidden' name='domain' value='$domain'>";
    print "<input type='hidden' name='record_id' value='$record_id'>";
    print "<input type='hidden' name='type' value='$rec->{type}'>";

    print '
';
    print "Type: $rec->{type}";
    print '
';

    print '
';
    print 'Name:
';
    print "<input type='text' name='name' value='$rec->{name}' class='form-control' style='width:100%;'>";
    print '
';

    print '
';
    print 'Content:
';
    if ($rec->{type} eq 'TXT') {
        print "<textarea name='content' class='form-control' style='width:100%; height:100px;'>$rec->{content}</textarea>";
    } else {
        print "<input type='text' name='content' value='$rec->{content}' class='form-control' style='width:100%;'>";
    }
    print '
';

    print '
';
    print 'TTL:
';
    print '<select name="ttl" class="form-control" style="width:150px;">';
    my @ttls = (1, 60, 120, 300, 600, 900, 1800, 3600, 7200, 18000, 43200, 86400);
    my %ttl_labels = (1 => 'Auto', 60 => '1 min', 120 => '2 min', 300 => '5 min', 600 => '10 min',
                      900 => '15 min', 1800 => '30 min', 3600 => '1 hour', 7200 => '2 hours',
                      18000 => '5 hours', 43200 => '12 hours', 86400 => '1 day');
    for my $t (@ttls) {
        my $sel = $rec->{ttl} == $t ? 'selected' : '';
        print "<option value='$t' $sel>$ttl_labels{$t}</option>";
    }
    print '</select>';
    print '
';

    if ($rec->{type} =~ /^(A|AAAA|CNAME)$/) {
        my $checked = $rec->{proxied} ? 'checked' : '';
        print '
';
        print "<input type='checkbox' name='proxied' $checked> Proxied (orange cloud)";
        print '
';
    }

    print '<button type="submit" class="btn btn-success">Save Changes</button>';
    print " Cancel";
    print '</form>';
}

# NEW: Save edited record
sub save_record {
    my ($cgi, $token) = @_;

    my $zone_id = $cgi->param('zone_id');
    my $domain = $cgi->param('domain');
    my $record_id = $cgi->param('record_id');
    my $type = $cgi->param('type');
    my $name = $cgi->param('name');
    my $content = $cgi->param('content');
    my $ttl = $cgi->param('ttl') || 1;
    my $proxied = $cgi->param('proxied') ? \1 : \0;

    my $data = {
        type => $type,
        name => $name,
        content => $content,
        ttl => int($ttl),
        proxied => $proxied
    };

    my $result = cf_api($token, 'PUT', "/zones/$zone_id/dns_records/$record_id", $data);

    if ($result->{success}) {
        print '
Record updated successfully.
';
    } else {
        my $err = $result->{errors}[0]{message} || 'Unknown error';
        print "
Failed: $err
";
    }

    print "
Return to DNS Records
";
}

# NEW: Mass delete records
sub mass_delete {
    my ($cgi, $token) = @_;

    my $zone_id = $cgi->param('zone_id');
    my $domain = $cgi->param('domain');
    my @record_ids = $cgi->multi_param('record_ids');

    print "
Mass Delete Results
";

    if (!@record_ids) {
        print '
No records selected.
';
        print "
Back to DNS Records
";
        return;
    }

    print '
';
    print '

Record ID	Status

';
    print '
';

    my $success = 0;
    my $failed = 0;

    for my $record_id (@record_ids) {
        my $result = cf_api($token, 'DELETE', "/zones/$zone_id/dns_records/$record_id");

        if ($result->{success}) {
            print "
$record_id	Deleted
";
            $success++;
        } else {
            print "
$record_id	Failed
";
            $failed++;
        }
    }

    print '

';
    print "
Deleted: $success, Failed: $failed
";
    print "
Return to DNS Records
";
}

sub add_record {
    my ($cgi, $token) = @_;

    my $zone_id = $cgi->param('zone_id');
    my $domain = $cgi->param('domain');
    my $type = $cgi->param('type');
    my $name = $cgi->param('name');
    my $content = $cgi->param('content');
    my $proxied = $cgi->param('proxied') ? \1 : \0;

    my $data = {
        type => $type,
        name => $name,
        content => $content,
        ttl => 1,
        proxied => $proxied
    };

    my $result = cf_api($token, 'POST', "/zones/$zone_id/dns_records", $data);

    if ($result->{success}) {
        print '
Record added successfully.
';
    } else {
        my $err = $result->{errors}[0]{message} || 'Unknown error';
        print "
Failed: $err
";
    }

    print "
Return to DNS Records
";
}

sub delete_record {
    my ($cgi, $token) = @_;

    my $zone_id = $cgi->param('zone_id');
    my $domain = $cgi->param('domain');
    my $record_id = $cgi->param('record_id');

    my $result = cf_api($token, 'DELETE', "/zones/$zone_id/dns_records/$record_id");

    if ($result->{success}) {
        print '
Record deleted.
';
    } else {
        print '
Failed to delete record.
';
    }

    print "
Return to DNS Records
";
}

sub add_m365 {
    my ($cgi, $token) = @_;

    my $zone_id = $cgi->param('zone_id');
    my $domain = $cgi->param('domain');
    my $tenant = $cgi->param('tenant');

    if (!$tenant) {
        print "
Add M365 Records: $domain
";
        print '
← Back
';

        print '<form method="post" action="?action=add_m365" style="max-width: 500px;">';
        print "<input type='hidden' name='zone_id' value='$zone_id'>";
        print "<input type='hidden' name='domain' value='$domain'>";
        print '
';
        print 'Microsoft 365 Tenant Name
';
        print '<input type="text" name="tenant" class="form-control" placeholder="e.g., contoso (from contoso.onmicrosoft.com)">';
        print '
Enter the prefix of your .onmicrosoft.com domain
';
        print '
';
        print '
Records that will be added:
';
        print '
';
        print '
MX → mail.protection.outlook.com
';
        print '
SPF (TXT) → include:spf.protection.outlook.com
';
        print '
DMARC (TXT) → v=DMARC1; p=reject
';
        print '
DKIM selector1 & selector2 (CNAME)
';
        print '
Autodiscover (CNAME)
';
        print '
';
        print '<button type="submit" class="btn btn-primary">Add M365 Records</button>';
        print '</form>';
        return;
    }

    print "
Adding M365 Records: $domain
";

    my $domain_clean = $domain;
    $domain_clean =~ s/\./-/g;

    my $tenant_clean = lc($tenant);
    $tenant_clean =~ s/\.//g;

    my @records = (
        { type => 'MX', name => $domain, content => "$domain.mail.protection.outlook.com", priority => 0 },
        { type => 'TXT', name => $domain, content => "v=spf1 include:spf.protection.outlook.com -all" },
        { type => 'TXT', name => "_dmarc.$domain", content => "v=DMARC1; p=reject; rua=mailto:admin\@$domain" },
        { type => 'CNAME', name => "selector1._domainkey.$domain", content => "selector1-${domain_clean}._domainkey.${tenant_clean}.onmicrosoft.com" },
        { type => 'CNAME', name => "selector2._domainkey.$domain", content => "selector2-${domain_clean}._domainkey.${tenant_clean}.onmicrosoft.com" },
        { type => 'CNAME', name => "autodiscover.$domain", content => 'autodiscover.outlook.com' },
    );

    print '
';
    print '

Record	Status

';
    print '
';

    for my $rec (@records) {
        my $result = cf_api($token, 'POST', "/zones/$zone_id/dns_records", $rec);
        my $status = $result->{success} ? 'Added' : ($result->{errors}[0]{message} || 'Failed');
        my $class = $result->{success} ? 'success' : 'warning';
        print "
$rec->{type} $rec->{name}	$status
";
    }

    print '

';
    print '
Remember to enable DKIM signing in Microsoft 365 Defender portal.
';
    print "
View DNS Records
";
}

sub import_zone {
    my ($cgi, $token) = @_;

    my $domain = $cgi->param('domain');

    if (!$domain) {
        print '
Import Zone to Cloudflare
';
        print '
← Back
';

        print '<form method="post" action="?action=import_zone" style="max-width: 400px;">';
        print '
';
        print 'Domain Name
';
        print '<input type="text" name="domain" class="form-control" placeholder="example.com">';
        print '
';
        print '<button type="submit" class="btn btn-primary">Import Zone</button>';
        print '</form>';
        return;
    }

    print "
Importing: $domain
";

    my $result = cf_api($token, 'POST', '/zones', { name => $domain, jump_start => \1 });

    if ($result->{success}) {
        my $zone = $result->{result};
        print '
Zone added successfully!
';
        print "
Zone ID: $zone->{id}
";
        print "
Status: $zone->{status}
";
        print '
Update Nameservers
';
        print '
Change nameservers at your registrar to:
';
        print '
';
        print "
$_
" for @{$zone->{name_servers}};
        print '
';
    } else {
        my $err = $result->{errors}[0]{message} || 'Unknown error';
        print "
Failed: $err
";
    }

    print '
View All Zones
';
}

# ============ HTML Templates ============

sub print_html_header {
    print <<'HTML';
<!DOCTYPE html>

    <title>Cloudflare DNS Manager</title>

HTML
}

sub print_html_footer {
    print <<'HTML';

Cloudflare DNS Manager v1.1 | AZ Computer Guru

HTML
}