374 lines
15 KiB
TypeScript
374 lines
15 KiB
TypeScript
// ============================================================================
|
|
// DNS/SSL API - Consultas via APIs publicas
|
|
// ============================================================================
|
|
// Usa APIs gratuitas para consultar registros DNS, SSL, MX, SPF, DKIM, DMARC
|
|
// ============================================================================
|
|
|
|
import type { ResultadoFerramenta } from './FerramentaLayout';
|
|
|
|
// Helper para chamar a API do Google DNS
|
|
async function queryDNS(name: string, type: string): Promise<any> {
|
|
try {
|
|
const res = await fetch(`https://dns.google/resolve?name=${encodeURIComponent(name)}&type=${type}`);
|
|
return await res.json();
|
|
} catch {
|
|
return { Status: 2, Answer: [] };
|
|
}
|
|
}
|
|
|
|
// Verificar SPF
|
|
export async function checkSPF(dominio: string): Promise<ResultadoFerramenta> {
|
|
const data = await queryDNS(dominio, 'TXT');
|
|
const records = (data.Answer || []).filter((r: any) => r.data && r.data.includes('v=spf1'));
|
|
|
|
if (records.length === 0) {
|
|
return {
|
|
status: 'error',
|
|
titulo: 'SPF Nao Encontrado',
|
|
mensagem: `O dominio ${dominio} nao possui um registro SPF configurado. Sem SPF, qualquer pessoa pode enviar e-mails em nome do seu dominio, o que facilita ataques de phishing e spoofing.`,
|
|
detalhes: { Dominio: dominio, Registro: 'Nao encontrado' },
|
|
recomendacoes: [
|
|
'Crie um registro TXT no DNS com a politica SPF',
|
|
'Exemplo: v=spf1 include:_spf.google.com ~all (para Microsoft 365/Google Workspace)',
|
|
'Use o mecanismo ~all (softfail) inicialmente para testes',
|
|
'Monitore os logs antes de usar -all (hardfail)',
|
|
'Consulte a Avanzato para configuracao personalizada',
|
|
],
|
|
score: 0,
|
|
};
|
|
}
|
|
|
|
const spf = records[0].data.replace(/"/g, '');
|
|
const hasAll = spf.includes('-all') || spf.includes('~all') || spf.includes('?all');
|
|
const isHardFail = spf.includes('-all');
|
|
const includes = spf.match(/include:[^\s]+/g) || [];
|
|
const ip4 = spf.match(/ip4:[^\s]+/g) || [];
|
|
const ip6 = spf.match(/ip6:[^\s]+/g) || [];
|
|
|
|
let status: 'success' | 'warning' | 'error' = 'warning';
|
|
let score = 60;
|
|
let recomendacoes: string[] = [];
|
|
|
|
if (isHardFail) { status = 'success'; score = 90; }
|
|
else if (spf.includes('~all')) { status = 'warning'; score = 70; recomendacoes.push('Considere mudar ~all para -all para protecao mais forte'); }
|
|
else if (!hasAll) { status = 'error'; score = 30; recomendacoes.push('Adicione ~all ou -all ao final do seu SPF'); }
|
|
|
|
if (includes.length > 10) { score -= 20; recomendacoes.push('Voce tem muitos includes. Mais de 10 lookups pode quebrar o SPF'); }
|
|
|
|
return {
|
|
status,
|
|
titulo: status === 'success' ? 'SPF Configurado Corretamente' : 'SPF Configurado com Ressalvas',
|
|
mensagem: `O dominio ${dominio} possui um registro SPF ${isHardFail ? 'com protecao rigida (-all)' : hasAll ? 'mas sem protecao rigida' : 'mas sem mecanismo de falha'}.`,
|
|
detalhes: {
|
|
Dominio: dominio,
|
|
Registro: spf.substring(0, 100),
|
|
Mecanismo: isHardFail ? '-all (hardfail)' : spf.includes('~all') ? '~all (softfail)' : 'Sem all',
|
|
Includes: String(includes.length),
|
|
'IPs v4': String(ip4.length),
|
|
'IPs v6': String(ip6.length),
|
|
},
|
|
recomendacoes: recomendacoes.length > 0 ? recomendacoes : ['Seu SPF esta bem configurado!', 'Monitore regularmente para garantir que todos os servidores legitimos estejam incluidos'],
|
|
score,
|
|
};
|
|
}
|
|
|
|
// Verificar DKIM
|
|
export async function checkDKIM(dominio: string): Promise<ResultadoFerramenta> {
|
|
const selectors = ['default', 'google', 'selector1', 'selector2', 'mail', 'dkim'];
|
|
let found = false;
|
|
let validSelector = '';
|
|
let dkimRecord = '';
|
|
|
|
for (const selector of selectors) {
|
|
const data = await queryDNS(`${selector}._domainkey.${dominio}`, 'TXT');
|
|
if (data.Answer && data.Answer.length > 0) {
|
|
const record = data.Answer[0].data;
|
|
if (record.includes('v=DKIM1') || record.includes('k=rsa')) {
|
|
found = true;
|
|
validSelector = selector;
|
|
dkimRecord = record.replace(/"/g, '').substring(0, 80);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!found) {
|
|
return {
|
|
status: 'warning',
|
|
titulo: 'DKIM Nao Encontrado',
|
|
mensagem: `Nao foi possivel encontrar um registro DKIM valido para ${dominio} com os selectors comuns. Isso nao significa que nao existe, mas pode indicar que nao esta configurado ou usa um selector personalizado.`,
|
|
detalhes: { Dominio: dominio, 'Selectors testados': selectors.join(', ') },
|
|
recomendacoes: [
|
|
'Verifique qual selector seu servidor de e-mail usa',
|
|
'Para Microsoft 365: selector1 e selector2',
|
|
'Para Google Workspace: google',
|
|
'Entre em contato com seu administrador de e-mail',
|
|
'A Avanzato pode configurar DKIM para seu dominio',
|
|
],
|
|
score: 30,
|
|
};
|
|
}
|
|
|
|
return {
|
|
status: 'success',
|
|
titulo: 'DKIM Configurado',
|
|
mensagem: `DKIM encontrado com o selector "${validSelector}". Seu dominio possui assinatura digital para autenticacao de e-mails.`,
|
|
detalhes: {
|
|
Dominio: dominio,
|
|
Selector: validSelector,
|
|
Registro: dkimRecord + '...',
|
|
},
|
|
recomendacoes: [
|
|
'DKIM esta configurado corretamente!',
|
|
'Certifique-se de que todos os servidores de e-mail assinam com DKIM',
|
|
'Renove as chaves DKIM periodicamente',
|
|
],
|
|
score: 85,
|
|
};
|
|
}
|
|
|
|
// Verificar DMARC
|
|
export async function checkDMARC(dominio: string): Promise<ResultadoFerramenta> {
|
|
const data = await queryDNS(`_dmarc.${dominio}`, 'TXT');
|
|
const records = (data.Answer || []).filter((r: any) => r.data && r.data.includes('v=DMARC1'));
|
|
|
|
if (records.length === 0) {
|
|
return {
|
|
status: 'error',
|
|
titulo: 'DMARC Nao Encontrado',
|
|
mensagem: `O dominio ${dominio} nao possui DMARC configurado. Sem DMARC, voce nao recebe relatorios sobre falhas de autenticacao e nao pode instruir provedores a rejeitar e-mails falsos.`,
|
|
detalhes: { Dominio: dominio, Registro: 'Nao encontrado' },
|
|
recomendacoes: [
|
|
'Crie um registro TXT em _dmarc.seudominio.com',
|
|
'Exemplo: v=DMARC1; p=quarantine; rua=mailto:dmarc@seudominio.com',
|
|
'Comece com p=none para monitorar, depois mude para quarantine ou reject',
|
|
'Configure rua para receber relatorios agregados',
|
|
'Consulte a Avanzato para configuracao completa',
|
|
],
|
|
score: 0,
|
|
};
|
|
}
|
|
|
|
const dmarc = records[0].data.replace(/"/g, '');
|
|
const policy = dmarc.match(/p=(\w+)/)?.[1] || 'none';
|
|
const rua = dmarc.match(/rua=([^;]+)/)?.[1] || 'Nao configurado';
|
|
|
|
let status: 'success' | 'warning' | 'error' = 'warning';
|
|
let score = 50;
|
|
|
|
if (policy === 'reject') { status = 'success'; score = 100; }
|
|
else if (policy === 'quarantine') { status = 'warning'; score = 75; }
|
|
else { status = 'error'; score = 40; }
|
|
|
|
return {
|
|
status,
|
|
titulo: policy === 'reject' ? 'DMARC com Protecao Maxima' : 'DMARC Configurado',
|
|
mensagem: `DMARC encontrado com politica "${policy}". ${policy === 'reject' ? 'E-mails que falham na autenticacao serao rejeitados.' : policy === 'quarantine' ? 'E-mails suspeitos vao para spam.' : 'Apenas monitorando, sem acao sobre e-mails falsos.'}`,
|
|
detalhes: {
|
|
Dominio: dominio,
|
|
Politica: policy,
|
|
'Relatorios (rua)': rua,
|
|
Registro: dmarc.substring(0, 80) + '...',
|
|
},
|
|
recomendacoes: policy === 'reject'
|
|
? ['Excelente! Sua protecao DMARC esta no nivel maximo']
|
|
: [`Considere mudar p=${policy} para p=reject para protecao total`, 'Monitore os relatorios rua antes de mudar para reject'],
|
|
score,
|
|
};
|
|
}
|
|
|
|
// MX Lookup
|
|
export async function checkMX(dominio: string): Promise<ResultadoFerramenta> {
|
|
const data = await queryDNS(dominio, 'MX');
|
|
const records = (data.Answer || []).sort((a: any, b: any) => a.preference - b.preference);
|
|
|
|
if (records.length === 0) {
|
|
return {
|
|
status: 'error',
|
|
titulo: 'Registros MX Nao Encontrados',
|
|
mensagem: `O dominio ${dominio} nao possui registros MX. Sem MX, o dominio nao pode receber e-mails.`,
|
|
detalhes: { Dominio: dominio, Status: 'Sem MX' },
|
|
recomendacoes: [
|
|
'Configure registros MX no DNS do seu dominio',
|
|
'Para Microsoft 365: siga o assistente de configuracao',
|
|
'Para Google Workspace: adicione os MX do Google',
|
|
],
|
|
score: 0,
|
|
};
|
|
}
|
|
|
|
const mxProviders = records.map((r: any) => r.data).join(', ');
|
|
const isMicrosoft = mxProviders.toLowerCase().includes('microsoft') || mxProviders.toLowerCase().includes('outlook') || mxProviders.toLowerCase().includes('protection');
|
|
const isGoogle = mxProviders.toLowerCase().includes('google') || mxProviders.toLowerCase().includes('aspmx');
|
|
const provider = isMicrosoft ? 'Microsoft 365 / Exchange Online' : isGoogle ? 'Google Workspace' : 'Outro / Servidor Proprio';
|
|
|
|
return {
|
|
status: 'success',
|
|
titulo: `${records.length} Servidor(es) MX Encontrado(s)`,
|
|
mensagem: `O dominio ${dominio} esta configurado para receber e-mails via ${provider}.`,
|
|
detalhes: {
|
|
Dominio: dominio,
|
|
'Servidores MX': String(records.length),
|
|
Provedor: provider,
|
|
'Prioridade mais alta': records[0].data,
|
|
},
|
|
recomendacoes: [
|
|
'Sua infraestrutura de e-mail esta configurada',
|
|
`Voce esta usando: ${provider}`,
|
|
'Mantenha pelo menos 2 servidores MX para redundancia',
|
|
],
|
|
score: 90,
|
|
};
|
|
}
|
|
|
|
// SSL Checker
|
|
export async function checkSSL(dominio: string): Promise<ResultadoFerramenta> {
|
|
try {
|
|
// Usar a API do crt.sh para verificar certificados
|
|
const res = await fetch(`https://crt.sh/?q=${encodeURIComponent(dominio)}&output=json`);
|
|
const data = await res.json();
|
|
|
|
if (!data || data.length === 0) {
|
|
return {
|
|
status: 'warning',
|
|
titulo: 'Certificado SSL Nao Encontrado',
|
|
mensagem: `Nao foi possivel encontrar informacoes de certificado SSL para ${dominio} no banco de dados publico. Isso pode significar que o site nao usa HTTPS ou o certificado nao e publico.`,
|
|
score: 30,
|
|
};
|
|
}
|
|
|
|
const cert = data[0];
|
|
const issuer = cert.issuer_name || 'Desconhecido';
|
|
const notAfter = cert.not_after ? new Date(cert.not_after) : null;
|
|
const diasRestantes = notAfter ? Math.floor((notAfter.getTime() - Date.now()) / (1000 * 60 * 60 * 24)) : 0;
|
|
const isExpired = diasRestantes < 0;
|
|
const isExpiringSoon = diasRestantes < 30 && diasRestantes > 0;
|
|
|
|
let status: 'success' | 'warning' | 'error' = 'success';
|
|
let score = 90;
|
|
|
|
if (isExpired) { status = 'error'; score = 0; }
|
|
else if (isExpiringSoon) { status = 'warning'; score = 50; }
|
|
|
|
return {
|
|
status,
|
|
titulo: isExpired ? 'Certificado SSL Expirado!' : isExpiringSoon ? 'Certificado SSL Vence em Breve' : 'Certificado SSL Valido',
|
|
mensagem: isExpired
|
|
? `O certificado SSL de ${dominio} EXPIROU em ${notAfter?.toLocaleDateString('pt-BR')}. Seu site esta inseguro!`
|
|
: `Certificado SSL de ${dominio} emitido por ${issuer.split('=').pop()}. ${isExpiringSoon ? `Vence em ${diasRestantes} dias.` : `Valido ate ${notAfter?.toLocaleDateString('pt-BR')}.`}`,
|
|
detalhes: {
|
|
Dominio: dominio,
|
|
Emissor: issuer.split('=').pop() || issuer,
|
|
'Valido ate': notAfter?.toLocaleDateString('pt-BR') || 'Desconhecido',
|
|
'Dias restantes': String(diasRestantes),
|
|
},
|
|
recomendacoes: isExpired
|
|
? ['RENOVE IMEDIATAMENTE seu certificado SSL', 'Use Let\'s Encrypt (gratuito) ou contrate um certificado pago', 'Entre em contato com a Avanzato para renovacao urgente']
|
|
: isExpiringSoon
|
|
? [`Renove seu certificado nos proximos ${diasRestantes} dias`, 'Configure renovacao automatica', 'Considere usar Let\'s Encrypt com auto-renew']
|
|
: ['Seu certificado SSL esta valido!', 'Configure renovacao automatica para evitar expiracao', 'Monitore a data de vencimento'],
|
|
score,
|
|
};
|
|
} catch {
|
|
return {
|
|
status: 'info',
|
|
titulo: 'Verificacao de SSL',
|
|
mensagem: `Nao foi possivel verificar o SSL de ${dominio} externamente. Verifique diretamente no navegador clicando no cadeado ao lado da URL.`,
|
|
score: 50,
|
|
};
|
|
}
|
|
}
|
|
|
|
// Email Security Score
|
|
export async function checkEmailSecurity(dominio: string): Promise<ResultadoFerramenta> {
|
|
const [spf, dkim, dmarc, mx] = await Promise.all([
|
|
checkSPF(dominio),
|
|
checkDKIM(dominio),
|
|
checkDMARC(dominio),
|
|
checkMX(dominio),
|
|
]);
|
|
|
|
const scores = [spf.score || 0, dkim.score || 0, dmarc.score || 0, mx.score || 0];
|
|
const media = Math.floor(scores.reduce((a, b) => a + b, 0) / scores.length);
|
|
|
|
let status: 'success' | 'warning' | 'error' = 'success';
|
|
if (media < 50) status = 'error';
|
|
else if (media < 80) status = 'warning';
|
|
|
|
return {
|
|
status,
|
|
titulo: `Email Security Score: ${media}/100`,
|
|
mensagem: media >= 80
|
|
? `A seguranca de e-mail de ${dominio} esta boa! Score: ${media}/100.`
|
|
: media >= 50
|
|
? `A seguranca de e-mail de ${dominio} precisa de atencao. Score: ${media}/100.`
|
|
: `A seguranca de e-mail de ${dominio} esta critica! Score: ${media}/100.`,
|
|
detalhes: {
|
|
Dominio: dominio,
|
|
'Score Total': `${media}/100`,
|
|
'SPF': `${spf.score}/100 (${spf.status === 'success' ? 'OK' : 'Problema'})`,
|
|
'DKIM': `${dkim.score}/100 (${dkim.status === 'success' ? 'OK' : 'Atenção'})`,
|
|
'DMARC': `${dmarc.score}/100 (${dmarc.status === 'success' ? 'OK' : 'Problema'})`,
|
|
'MX': `${mx.score}/100 (${mx.status === 'success' ? 'OK' : 'Problema'})`,
|
|
},
|
|
recomendacoes: [
|
|
...spf.recomendacoes || [],
|
|
...dmarc.recomendacoes || [],
|
|
...(dkim.score && dkim.score < 80 ? dkim.recomendacoes || [] : []),
|
|
].filter((v, i, a) => a.indexOf(v) === i).slice(0, 6),
|
|
score: media,
|
|
};
|
|
}
|
|
|
|
// Microsoft 365 Analyzer
|
|
export async function checkMicrosoft365(dominio: string): Promise<ResultadoFerramenta> {
|
|
const [mx, spf, dmarc, dkim] = await Promise.all([
|
|
checkMX(dominio),
|
|
checkSPF(dominio),
|
|
checkDMARC(dominio),
|
|
checkDKIM(dominio),
|
|
]);
|
|
|
|
const isMicrosoft365 = mx.detalhes?.Provedor?.includes('Microsoft');
|
|
const m365Score = isMicrosoft365 ? 40 : 0;
|
|
const spfScore = spf.score || 0;
|
|
const dmarcScore = dmarc.score || 0;
|
|
const dkimScore = dkim.score || 0;
|
|
const total = Math.min(100, m365Score + Math.floor((spfScore + dmarcScore + dkimScore) / 3 * 0.6));
|
|
|
|
let status: 'success' | 'warning' | 'error' = 'success';
|
|
if (total < 40) status = 'error';
|
|
else if (total < 70) status = 'warning';
|
|
|
|
return {
|
|
status,
|
|
titulo: `Microsoft 365 Analyzer: ${total}/100`,
|
|
mensagem: isMicrosoft365
|
|
? `O dominio ${dominio} esta configurado com Microsoft 365. Score de seguranca: ${total}/100.`
|
|
: `O dominio ${dominio} nao parece usar Microsoft 365 como servidor de e-mail principal. Score de seguranca: ${total}/100.`,
|
|
detalhes: {
|
|
Dominio: dominio,
|
|
'Usa Microsoft 365': isMicrosoft365 ? 'Sim' : 'Nao detectado',
|
|
'Score Total': `${total}/100`,
|
|
'SPF': `${spfScore}/100`,
|
|
'DKIM': `${dkimScore}/100`,
|
|
'DMARC': `${dmarcScore}/100`,
|
|
},
|
|
recomendacoes: isMicrosoft365
|
|
? [
|
|
...(total < 80 ? ['Configure DKIM corretamente para Microsoft 365'] : []),
|
|
...(dmarcScore < 80 ? ['Fortaleca a politica DMARC'] : []),
|
|
'Habilite ATP (Advanced Threat Protection) no Microsoft 365',
|
|
'Configure regras de anti-spam e anti-malware',
|
|
'Ative a autenticacao de dois fatores para todos os usuarios',
|
|
]
|
|
: [
|
|
'Se voce usa Microsoft 365, verifique se os registros MX estao corretos',
|
|
'Configure SPF com include:_spf.microsoft.com',
|
|
'Ative DKIM no portal do Microsoft 365',
|
|
'Configure DMARC para monitoramento',
|
|
],
|
|
score: total,
|
|
};
|
|
}
|