normalizeEmail($request->query('emailaddress')); if (! $email) { return response('Email parameter required', 400); } $parts = explode('@', $email); if (count($parts) !== 2) { return response('Invalid email address', 400); } $domain = $parts[1]; // Check if domain is managed $emailDomain = EmailDomain::whereHas('domain', function ($query) use ($domain) { $query->where('domain_name', $domain); })->where('is_active', true)->first(); if (! $emailDomain) { return response('Domain not configured', 404); } $mailServer = $this->getMailServer($emailDomain); $displayName = Setting::get('webmail_product_name', 'Jabali Mail'); return $this->autoconfigResponse($domain, $mailServer, $displayName); } /** * Handle autoconfig for specific domain * URL: /autoconfig/domain.com/config-v1.1.xml */ public function configForDomain(Request $request, string $domain): Response { if (! $this->isValidDomain($domain)) { return response('Invalid domain', 400); } $emailDomain = EmailDomain::whereHas('domain', function ($query) use ($domain) { $query->where('domain_name', $domain); })->where('is_active', true)->first(); if (! $emailDomain) { return response('Domain not configured', 404); } $mailServer = $this->getMailServer($emailDomain); $displayName = Setting::get('webmail_product_name', 'Jabali Mail'); return $this->autoconfigResponse($domain, $mailServer, $displayName); } /** * Generate iOS/macOS mobile configuration profile * URL: /mail/profile/{domain} */ public function mobileProfile(Request $request, string $domain): Response { if (! $this->isValidDomain($domain)) { return response('Invalid domain', 400); } $email = $this->normalizeEmail($request->query('email')); if (! $email) { return response('Email parameter required', 400); } $emailDomainName = substr(strrchr($email, '@') ?: '', 1); if ($emailDomainName !== $domain) { return response('Email does not match domain', 400); } $emailDomain = EmailDomain::whereHas('domain', function ($query) use ($domain) { $query->where('domain_name', $domain); })->where('is_active', true)->first(); if (! $emailDomain) { return response('Domain not configured', 404); } $mailServer = $this->getMailServer($emailDomain); $displayName = Setting::get('webmail_product_name', 'Jabali Mail'); $escapedDomain = $this->escapeXml($domain); $escapedEmail = $this->escapeXml($email); $escapedMailServer = $this->escapeXml($mailServer); $escapedDisplayName = $this->escapeXml($displayName); $uuid = $this->generateUuid(); $payloadUuid = $this->generateUuid(); $profile = << PayloadContent EmailAccountDescription {$escapedDomain} Email EmailAccountName {$escapedDisplayName} EmailAccountType EmailTypeIMAP EmailAddress {$escapedEmail} IncomingMailServerAuthentication EmailAuthPassword IncomingMailServerHostName {$escapedMailServer} IncomingMailServerPortNumber 993 IncomingMailServerUseSSL IncomingMailServerUsername {$escapedEmail} OutgoingMailServerAuthentication EmailAuthPassword OutgoingMailServerHostName {$escapedMailServer} OutgoingMailServerPortNumber 587 OutgoingMailServerUseSSL OutgoingMailServerUsername {$escapedEmail} OutgoingPasswordSameAsIncomingPassword PayloadDescription Email account configuration for {$escapedDomain} PayloadDisplayName {$escapedDomain} Email PayloadIdentifier com.jabali.email.{$escapedDomain} PayloadType com.apple.mail.managed PayloadUUID {$payloadUuid} PayloadVersion 1 PreventAppSheet PreventMove SMIMEEnabled PayloadDescription Email configuration profile for {$escapedDomain} PayloadDisplayName {$escapedDisplayName} - {$escapedDomain} PayloadIdentifier com.jabali.mailconfig.{$escapedDomain} PayloadOrganization {$escapedDisplayName} PayloadRemovalDisallowed PayloadType Configuration PayloadUUID {$uuid} PayloadVersion 1 MOBILECONFIG; return response($profile, 200) ->header('Content-Type', 'application/x-apple-aspen-config') ->header('Content-Disposition', 'attachment; filename="email-config.mobileconfig"'); } /** * Get mail server hostname */ private function getMailServer(EmailDomain $emailDomain): string { $hostname = Setting::get('mail_hostname'); if ($hostname) { return $hostname; } return 'mail.'.$emailDomain->domain->domain_name; } /** * Generate autoconfig XML response */ private function autoconfigResponse(string $domain, string $mailServer, string $displayName): Response { $escapedDomain = $this->escapeXml($domain); $escapedMailServer = $this->escapeXml($mailServer); $escapedDisplayName = $this->escapeXml($displayName); $xml = << {$escapedDomain} {$escapedDisplayName} {$escapedDisplayName} {$escapedMailServer} 993 SSL password-cleartext %EMAILADDRESS% {$escapedMailServer} 995 SSL password-cleartext %EMAILADDRESS% {$escapedMailServer} 587 STARTTLS password-cleartext %EMAILADDRESS% Webmail access XML; return response($xml, 200) ->header('Content-Type', 'application/xml; charset=utf-8'); } private function normalizeEmail(?string $email): ?string { if ($email === null) { return null; } $email = trim($email); if ($email === '' || ! filter_var($email, FILTER_VALIDATE_EMAIL)) { return null; } return $email; } private function isValidDomain(string $domain): bool { return filter_var($domain, FILTER_VALIDATE_DOMAIN, FILTER_FLAG_HOSTNAME) !== false; } private function escapeXml(string $value): string { return htmlspecialchars($value, ENT_XML1 | ENT_QUOTES, 'UTF-8'); } /** * Generate UUID v4 */ private function generateUuid(): string { return sprintf( '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF), mt_rand(0, 0x0FFF) | 0x4000, mt_rand(0, 0x3FFF) | 0x8000, mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF), mt_rand(0, 0xFFFF) ); } }