diff --git a/api/mail-config.js b/api/mail-config.js new file mode 100644 index 0000000..f575d40 --- /dev/null +++ b/api/mail-config.js @@ -0,0 +1,79 @@ +const dns = require('dns').promises; +const URL = require('url-parse'); + +exports.handler = async (event, context) => { + try { + let domain = event.queryStringParameters.url; + const parsedUrl = new URL(domain); + domain = parsedUrl.hostname || parsedUrl.pathname; + + // Get MX records + const mxRecords = await dns.resolveMx(domain); + + // Get TXT records + const txtRecords = await dns.resolveTxt(domain); + + // Filter for only email related TXT records (SPF, DKIM, DMARC, and certain provider verifications) + const emailTxtRecords = txtRecords.filter(record => { + const recordString = record.join(''); + return ( + recordString.startsWith('v=spf1') || + recordString.startsWith('v=DKIM1') || + recordString.startsWith('v=DMARC1') || + recordString.startsWith('protonmail-verification=') || + recordString.startsWith('google-site-verification=') || // Google Workspace + recordString.startsWith('MS=') || // Microsoft 365 + recordString.startsWith('zoho-verification=') || // Zoho + recordString.startsWith('titan-verification=') || // Titan + recordString.includes('bluehost.com') // BlueHost + ); + }); + + // Identify specific mail services + const mailServices = emailTxtRecords.map(record => { + const recordString = record.join(''); + if (recordString.startsWith('protonmail-verification=')) { + return { provider: 'ProtonMail', value: recordString.split('=')[1] }; + } else if (recordString.startsWith('google-site-verification=')) { + return { provider: 'Google Workspace', value: recordString.split('=')[1] }; + } else if (recordString.startsWith('MS=')) { + return { provider: 'Microsoft 365', value: recordString.split('=')[1] }; + } else if (recordString.startsWith('zoho-verification=')) { + return { provider: 'Zoho', value: recordString.split('=')[1] }; + } else if (recordString.startsWith('titan-verification=')) { + return { provider: 'Titan', value: recordString.split('=')[1] }; + } else if (recordString.includes('bluehost.com')) { + return { provider: 'BlueHost', value: recordString }; + } else { + return null; + } + }).filter(record => record !== null); + + // Check MX records for Yahoo + const yahooMx = mxRecords.filter(record => record.exchange.includes('yahoodns.net')); + if (yahooMx.length > 0) { + mailServices.push({ provider: 'Yahoo', value: yahooMx[0].exchange }); + } + + return { + statusCode: 200, + body: JSON.stringify({ + mxRecords, + txtRecords: emailTxtRecords, + mailServices, + }), + }; + } catch (error) { + if (error.code === 'ENOTFOUND' || error.code === 'ENODATA') { + return { + statusCode: 200, + body: JSON.stringify({ skipped: 'No mail server in use on this domain' }), + }; + } else { + return { + statusCode: 500, + body: JSON.stringify({ error: error.message }), + }; + } + } +}; diff --git a/src/components/Results/MailConfig.tsx b/src/components/Results/MailConfig.tsx new file mode 100644 index 0000000..b69c16d --- /dev/null +++ b/src/components/Results/MailConfig.tsx @@ -0,0 +1,45 @@ + +import { Card } from 'components/Form/Card'; +import Row from 'components/Form/Row'; +import Heading from 'components/Form/Heading'; +import colors from 'styles/colors'; + +const cardStyles = ``; + +const MailConfigCard = (props: {data: any, title: string, actionButtons: any }): JSX.Element => { + const mailServer = props.data; + const txtRecords = (mailServer.txtRecords || []).join('').toLowerCase() || ''; + return ( + + Mail Security Checklist + + + + + + { mailServer.mxRecords && MX Records} + { mailServer.mxRecords && mailServer.mxRecords.map((record: any) => ( + + {record.exchange} + {record.priority ? `Priority: ${record.priority}` : ''} + + )) + } + { mailServer.mailServices.length > 0 && External Mail Services} + { mailServer.mailServices && mailServer.mailServices.map((service: any) => ( + + )) + } + + { mailServer.txtRecords && Mail-related TXT Records} + { mailServer.txtRecords && mailServer.txtRecords.map((record: any) => ( + + {record} + + )) + } + + ); +} + +export default MailConfigCard; diff --git a/src/components/misc/ProgressBar.tsx b/src/components/misc/ProgressBar.tsx index 90d1a9f..9803205 100644 --- a/src/components/misc/ProgressBar.tsx +++ b/src/components/misc/ProgressBar.tsx @@ -194,6 +194,7 @@ const jobNames = [ 'hosts', 'quality', 'cookies', + 'ssl', // 'server-info', 'redirects', 'robots-txt', @@ -207,7 +208,7 @@ const jobNames = [ 'security-txt', 'social-tags', 'linked-pages', - 'mail-server', + 'mail-config', // 'whois', 'features', 'carbon', diff --git a/src/pages/Results.tsx b/src/pages/Results.tsx index 4ba37c2..5042b91 100644 --- a/src/pages/Results.tsx +++ b/src/pages/Results.tsx @@ -47,6 +47,7 @@ import TechStackCard from 'components/Results/TechStack'; import SecurityTxtCard from 'components/Results/SecurityTxt'; import ContentLinksCard from 'components/Results/ContentLinks'; import SocialTagsCard from 'components/Results/SocialTags'; +import MailConfigCard from 'components/Results/MailConfig'; import keys from 'utils/get-keys'; import { determineAddressType, AddressType } from 'utils/address-type-checker'; @@ -397,6 +398,14 @@ const Results = (): JSX.Element => { fetchRequest: () => fetch(`${api}/content-links?url=${address}`).then(res => parseJson(res)), }); + // Get mail config for server, based on DNS records + const [mailConfigResults, updateMailConfigResults] = useMotherHook({ + jobId: 'mail-config', + updateLoadingJobs, + addressInfo: { address, addressType, expectedAddressTypes: urlTypeOnly }, + fetchRequest: () => fetch(`${api}/mail-config?url=${address}`).then(res => parseJson(res)), + }); + /* Cancel remaining jobs after 10 second timeout */ useEffect(() => { const checkJobs = () => { @@ -443,6 +452,7 @@ const Results = (): JSX.Element => { { id: 'txt-records', title: 'TXT Records', result: txtRecordResults, Component: TxtRecordCard, refresh: updateTxtRecordResults }, { id: 'hsts', title: 'HSTS Check', result: hstsResults, Component: HstsCard, refresh: updateHstsResults }, { id: 'whois', title: 'Domain Info', result: whoIsResults, Component: WhoIsCard, refresh: updateWhoIsResults }, + { id: 'mail-config', title: 'Email Configuration', result: mailConfigResults, Component: MailConfigCard, refresh: updateMailConfigResults }, { id: 'dns-server', title: 'DNS Server', result: dnsServerResults, Component: DnsServerCard, refresh: updateDnsServerResults }, { id: 'social-tags', title: 'Social Tags', result: socialTagResults, Component: SocialTagsCard, refresh: updateSocialTagResults }, { id: 'linked-pages', title: 'Linked Pages', result: linkedPagesResults, Component: ContentLinksCard, refresh: updateLinkedPagesResults },