HEX
Server: LiteSpeed
System: Linux s1049.use1.mysecurecloudhost.com 4.18.0-477.27.2.lve.el8.x86_64 #1 SMP Wed Oct 11 12:32:56 UTC 2023 x86_64
User: xedaptot (3356)
PHP: 8.3.31
Disabled: NONE
Upload Files
File: /home/xedaptot/hi.naniguide.com/app/Services/SendingDomainService.php
<?php

namespace App\Services;

use App\Models\Customer;
use App\Models\SendingDomain;
use Illuminate\Pagination\LengthAwarePaginator;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Str;

class SendingDomainService
{
    /**
     * Get paginated list of sending domains for a customer.
     */
    public function getPaginated(Customer $customer, array $filters = [], int $perPage = 15): LengthAwarePaginator
    {
        $workspaceId = session('active_workspace_id');
        $mustAddOwn = (bool) $customer->groupSetting('domains.sending_domains.must_add', false);
        $canUseSystem = (bool) $customer->groupSetting('servers.permissions.can_use_system_servers', false);

        $query = SendingDomain::query()
            ->when($workspaceId, function ($q) use ($workspaceId, $canUseSystem) {
                $q->where(function ($sub) use ($workspaceId, $canUseSystem) {
                    $sub->where('workspace_id', $workspaceId);
                    if ($canUseSystem) {
                        $sub->orWhereNull('workspace_id');
                    }
                });
            }, function ($q) use ($customer, $mustAddOwn, $canUseSystem) {
                $q->when($mustAddOwn, function ($sub) use ($customer) {
                    $sub->where('customer_id', $customer->id);
                }, function ($sub) use ($customer, $canUseSystem) {
                    $sub->where(function ($nested) use ($customer, $canUseSystem) {
                        $nested->where('customer_id', $customer->id);
                        if ($canUseSystem) {
                            $nested->orWhereNull('customer_id');
                        }
                    });
                });
            });

        // Apply filters
        if (isset($filters['search'])) {
            $search = $filters['search'];
            $query->where(function ($q) use ($search) {
                $q->where('domain', 'like', "%{$search}%");
            });
        }

        if (isset($filters['status'])) {
            $query->where('status', $filters['status']);
        }

        return $query->latest()->paginate($perPage);
    }

    /**
     * Create a new sending domain.
     */
    public function create(?Customer $customer, array $data): SendingDomain
    {
        $domain = $data['domain'];

        $existing = SendingDomain::withTrashed()
            ->where('domain', $domain)
            ->first();

        if ($existing && !$existing->trashed()) {
            throw new \RuntimeException('This domain has already been added.');
        }

        // Generate DKIM keys
        $dkimPrivateKey = openssl_pkey_new([
            'private_key_bits' => 2048,
            'private_key_type' => OPENSSL_KEYTYPE_RSA,
        ]);
        
        if (!$dkimPrivateKey) {
            throw new \RuntimeException('Failed to generate DKIM keys. Please ensure OpenSSL is properly configured.');
        }
        
        openssl_pkey_export($dkimPrivateKey, $privateKey);
        $keyDetails = openssl_pkey_get_details($dkimPrivateKey);
        $publicKey = $keyDetails['key'] ?? null;
        
        if (!$publicKey) {
            throw new \RuntimeException('Failed to extract DKIM public key.');
        }
        
        // Extract public key for DNS (remove headers and newlines)
        $publicKeyForDns = str_replace(['-----BEGIN PUBLIC KEY-----', '-----END PUBLIC KEY-----', "\n", "\r"], '', $publicKey);
        $publicKeyForDns = trim($publicKeyForDns);
        
        // Generate DKIM DNS record
        $dkimSelector = 'mail';
        $dkimDnsRecord = "v=DKIM1; k=rsa; p={$publicKeyForDns}";

        $spfRecord = $data['spf_record'] ?? 'v=spf1 a mx ~all';
        $dmarcRecord = $data['dmarc_record'] ?? "v=DMARC1; p=none; rua=mailto:dmarc@{$domain}; fo=1";

        $payload = [
            'customer_id' => $customer?->id,
            'domain' => $domain,
            'is_primary' => false,
            'status' => 'pending',
            'verification_token' => Str::random(32),
            'verified_at' => null,
            'spf_record' => $spfRecord,
            'dkim_public_key' => $publicKey,
            'dkim_private_key' => $privateKey,
            'dmarc_record' => $dmarcRecord,
            'dns_records' => [
                'dkim' => [
                    'selector' => $dkimSelector,
                    'public_key' => $publicKeyForDns,
                    'record' => $dkimDnsRecord,
                    'host' => "{$dkimSelector}._domainkey",
                ],
                'spf' => [
                    'host' => '@',
                    'record' => $spfRecord,
                ],
                'dmarc' => [
                    'host' => '_dmarc',
                    'record' => $dmarcRecord,
                ],
            ],
            'verification_data' => null,
            'notes' => $data['notes'] ?? null,
        ];

        if ($existing && $existing->trashed()) {
            $existing->restore();
            $existing->update($payload);
            return $existing->fresh();
        }

        return SendingDomain::create($payload);
    }

    public function setPrimary(SendingDomain $sendingDomain): SendingDomain
    {
        return DB::transaction(function () use ($sendingDomain) {
            $customerId = $sendingDomain->customer_id;

            SendingDomain::query()
                ->where('customer_id', $customerId)
                ->where('id', '!=', $sendingDomain->id)
                ->update(['is_primary' => false]);

            $sendingDomain->update(['is_primary' => true]);

            return $sendingDomain->fresh();
        });
    }

    /**
     * Update an existing sending domain.
     */
    public function update(SendingDomain $sendingDomain, array $data): SendingDomain
    {
        if (array_key_exists('spf_record', $data) || array_key_exists('dmarc_record', $data)) {
            $dnsRecords = is_array($sendingDomain->dns_records) ? $sendingDomain->dns_records : [];

            if (array_key_exists('spf_record', $data)) {
                $dnsRecords['spf'] = array_merge($dnsRecords['spf'] ?? [], [
                    'host' => $dnsRecords['spf']['host'] ?? '@',
                    'record' => $data['spf_record'],
                ]);
            }

            if (array_key_exists('dmarc_record', $data)) {
                $dnsRecords['dmarc'] = array_merge($dnsRecords['dmarc'] ?? [], [
                    'host' => $dnsRecords['dmarc']['host'] ?? '_dmarc',
                    'record' => $data['dmarc_record'],
                ]);
            }

            $data['dns_records'] = $dnsRecords;
        }

        $sendingDomain->update($data);
        return $sendingDomain->fresh();
    }

    /**
     * Delete a sending domain.
     */
    public function delete(SendingDomain $sendingDomain): bool
    {
        return $sendingDomain->delete();
    }

    /**
     * Verify a sending domain by checking DNS records.
     */
    public function verify(SendingDomain $sendingDomain): array
    {
        $results = [
            'dkim' => false,
            'spf' => false,
            'dmarc' => false,
            'errors' => [],
        ];

        // Check DKIM record
        if ($sendingDomain->dkim_public_key && isset($sendingDomain->dns_records['dkim'])) {
            $dkimSelector = $sendingDomain->dns_records['dkim']['selector'] ?? 'mail';
            $dkimHost = "{$dkimSelector}._domainkey.{$sendingDomain->domain}";
            
            try {
                $dkimRecords = @dns_get_record($dkimHost, DNS_TXT);
                
                if ($dkimRecords === false || empty($dkimRecords)) {
                    $results['errors'][] = "DKIM record not found at {$dkimHost}. Please add the TXT record to your DNS.";
                } else {
                    // Extract public key from stored record
                    $expectedPublicKey = $sendingDomain->dns_records['dkim']['public_key'] ?? null;
                    
                    if ($expectedPublicKey) {
                        // Check if any DNS record contains the public key
                        foreach ($dkimRecords as $record) {
                            if (isset($record['txt'])) {
                                $txtRecord = $record['txt'];
                                // Check if record contains v=DKIM1 and the public key
                                if (strpos($txtRecord, 'v=DKIM1') !== false && strpos($txtRecord, $expectedPublicKey) !== false) {
                                    $results['dkim'] = true;
                                    break;
                                }
                            }
                        }
                    }
                    
                    if (!$results['dkim']) {
                        $results['errors'][] = "DKIM record found at {$dkimHost} but public key doesn't match. Please verify the DNS record is correct.";
                    }
                }
            } catch (\Exception $e) {
                $results['errors'][] = "Failed to check DKIM record: " . $e->getMessage();
            }
        } else {
            $results['errors'][] = "DKIM keys not generated. Please recreate the sending domain.";
        }

        // Check SPF record (optional but recommended)
        if ($sendingDomain->spf_record) {
            try {
                $spfRecords = dns_get_record($sendingDomain->domain, DNS_TXT);

                foreach ($spfRecords as $record) {
                    if (isset($record['txt']) && strpos($record['txt'], 'v=spf1') !== false) {
                        $results['spf'] = true;
                        break;
                    }
                }

                if (!$results['spf']) {
                    $results['errors'][] = 'SPF record not found. Please add the SPF TXT record to your DNS.';
                }
            } catch (\Exception $e) {
                $results['errors'][] = "Failed to check SPF record: " . $e->getMessage();
            }
        } else {
            // SPF is optional, so mark as passed if not required
            $results['spf'] = true;
        }

        // Check DMARC record (optional)
        $dmarcHost = "_dmarc.{$sendingDomain->domain}";
        try {
            $dmarcRecords = dns_get_record($dmarcHost, DNS_TXT);
            
            foreach ($dmarcRecords as $record) {
                if (isset($record['txt']) && strpos($record['txt'], 'v=DMARC1') !== false) {
                    $results['dmarc'] = true;
                    break;
                }
            }
        } catch (\Exception $e) {
            // DMARC is optional, so we don't add to errors
        }

        // Domain is verified only if DKIM is present (required)
        $isVerified = $results['dkim'];

        // Update domain status
        $sendingDomain->update([
            'status' => $isVerified ? 'verified' : 'pending',
            'verified_at' => $isVerified ? now() : null,
            'verification_data' => [
                'last_checked' => now()->toDateTimeString(),
                'dkim_verified' => $results['dkim'],
                'spf_verified' => $results['spf'],
                'dmarc_verified' => $results['dmarc'],
                'errors' => $results['errors'],
            ],
        ]);

        return $results;
    }
}