Autarch/web/templates/email_sec.html

754 lines
35 KiB
HTML
Raw Normal View History

{% extends "base.html" %}
{% block title %}AUTARCH — Email Security{% endblock %}
{% block content %}
<div class="page-header">
<h1>Email Security</h1>
<p style="margin:0;font-size:0.85rem;color:var(--text-secondary)">
DMARC, SPF, DKIM analysis, email header forensics, phishing detection, and mailbox inspection.
</p>
</div>
<!-- Tab Bar -->
<div class="tab-bar">
<button class="tab active" data-tab-group="es" data-tab="analyze" onclick="showTab('es','analyze')">Analyze</button>
<button class="tab" data-tab-group="es" data-tab="headers" onclick="showTab('es','headers')">Headers</button>
<button class="tab" data-tab-group="es" data-tab="mailbox" onclick="showTab('es','mailbox')">Mailbox</button>
</div>
<!-- ==================== ANALYZE TAB ==================== -->
<div class="tab-content active" data-tab-group="es" data-tab="analyze">
<div class="section">
<h2>Domain Email Security Check</h2>
<div class="form-row">
<div class="form-group" style="flex:1">
<label>Domain</label>
<input type="text" id="es-domain" placeholder="example.com" onkeypress="if(event.key==='Enter')esDomainAnalyze()">
</div>
<div class="form-group" style="align-self:flex-end">
<button id="btn-es-domain" class="btn btn-primary" onclick="esDomainAnalyze()">Analyze Domain</button>
</div>
</div>
</div>
<!-- Results Dashboard (hidden until analysis runs) -->
<div id="es-results" style="display:none">
<!-- Overall Score -->
<div class="section" style="display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap">
<div class="score-display" style="min-width:140px;text-align:center">
<div class="score-value" id="es-grade" style="font-size:3rem">--</div>
<div class="score-label">Email Security Grade</div>
<div id="es-score-num" style="font-size:0.85rem;color:var(--text-secondary);margin-top:4px">--/100</div>
</div>
<div style="flex:1;min-width:250px">
<table class="data-table" style="max-width:400px">
<thead><tr><th>Check</th><th>Status</th></tr></thead>
<tbody>
<tr><td>SPF</td><td id="es-sum-spf">--</td></tr>
<tr><td>DMARC</td><td id="es-sum-dmarc">--</td></tr>
<tr><td>DKIM</td><td id="es-sum-dkim">--</td></tr>
<tr><td>MX / STARTTLS</td><td id="es-sum-mx">--</td></tr>
</tbody>
</table>
</div>
</div>
<!-- SPF Card -->
<div class="section">
<h2>SPF Record</h2>
<pre class="output-panel" id="es-spf-record" style="margin-bottom:12px"></pre>
<div id="es-spf-mechanisms"></div>
<div id="es-spf-findings"></div>
</div>
<!-- DMARC Card -->
<div class="section">
<h2>DMARC Record</h2>
<pre class="output-panel" id="es-dmarc-record" style="margin-bottom:12px"></pre>
<table class="data-table" style="max-width:500px">
<tbody>
<tr><td>Policy</td><td id="es-dmarc-policy">--</td></tr>
<tr><td>Subdomain Policy</td><td id="es-dmarc-sp">--</td></tr>
<tr><td>Percentage</td><td id="es-dmarc-pct">--</td></tr>
<tr><td>SPF Alignment</td><td id="es-dmarc-aspf">--</td></tr>
<tr><td>DKIM Alignment</td><td id="es-dmarc-adkim">--</td></tr>
<tr><td>Aggregate Reports (rua)</td><td id="es-dmarc-rua">--</td></tr>
<tr><td>Forensic Reports (ruf)</td><td id="es-dmarc-ruf">--</td></tr>
</tbody>
</table>
<div id="es-dmarc-findings" style="margin-top:12px"></div>
</div>
<!-- DKIM Card -->
<div class="section">
<h2>DKIM Selectors</h2>
<div id="es-dkim-selectors"></div>
<div id="es-dkim-findings"></div>
</div>
<!-- MX Card -->
<div class="section">
<h2>MX Records</h2>
<table class="data-table">
<thead><tr><th>Priority</th><th>Host</th><th>IP</th><th>STARTTLS</th><th>Banner</th></tr></thead>
<tbody id="es-mx-rows">
<tr><td colspan="5" class="empty-state">No data</td></tr>
</tbody>
</table>
<div id="es-mx-findings" style="margin-top:12px"></div>
</div>
</div>
<!-- Blacklist Check -->
<div class="section">
<h2>Blacklist Check</h2>
<div class="form-row">
<div class="form-group" style="flex:1">
<label>IP Address or Domain</label>
<input type="text" id="es-bl-target" placeholder="1.2.3.4 or example.com" onkeypress="if(event.key==='Enter')esBlCheck()">
</div>
<div class="form-group" style="align-self:flex-end">
<button id="btn-es-bl" class="btn btn-primary" onclick="esBlCheck()">Check Blacklists</button>
</div>
</div>
<div id="es-bl-summary" style="margin:8px 0;display:none"></div>
<div style="max-height:400px;overflow-y:auto">
<table class="data-table">
<thead><tr><th>Blacklist</th><th>Status</th><th>Details</th></tr></thead>
<tbody id="es-bl-rows">
<tr><td colspan="3" class="empty-state">Enter an IP or domain above.</td></tr>
</tbody>
</table>
</div>
</div>
</div><!-- /analyze tab -->
<!-- ==================== HEADERS TAB ==================== -->
<div class="tab-content" data-tab-group="es" data-tab="headers">
<!-- Header Analysis -->
<div class="section">
<h2>Email Header Analysis</h2>
<div class="form-group">
<label>Paste raw email headers</label>
<textarea id="es-raw-headers" rows="10" placeholder="Paste full email headers here..."></textarea>
</div>
<div class="tool-actions">
<button id="btn-es-headers" class="btn btn-primary" onclick="esAnalyzeHeaders()">Analyze Headers</button>
</div>
</div>
<div id="es-hdr-results" style="display:none">
<!-- Authentication Results -->
<div class="section">
<h2>Authentication Results</h2>
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px">
<span class="badge" id="es-hdr-spf">SPF: --</span>
<span class="badge" id="es-hdr-dkim">DKIM: --</span>
<span class="badge" id="es-hdr-dmarc">DMARC: --</span>
</div>
<table class="data-table" style="max-width:600px">
<tbody>
<tr><td>From</td><td id="es-hdr-from">--</td></tr>
<tr><td>Return-Path</td><td id="es-hdr-rpath">--</td></tr>
<tr><td>Reply-To</td><td id="es-hdr-replyto">--</td></tr>
<tr><td>Subject</td><td id="es-hdr-subject">--</td></tr>
<tr><td>Date</td><td id="es-hdr-date">--</td></tr>
<tr><td>Originating IP</td><td id="es-hdr-origip">--</td></tr>
<tr><td>Message-ID</td><td id="es-hdr-msgid">--</td></tr>
</tbody>
</table>
</div>
<!-- Received Chain -->
<div class="section">
<h2>Received Chain</h2>
<div id="es-hdr-chain"></div>
</div>
<!-- Spoofing Indicators -->
<div class="section" id="es-hdr-spoof-section" style="display:none">
<h2>Spoofing Indicators</h2>
<div id="es-hdr-spoofing"></div>
</div>
<!-- Findings -->
<div class="section" id="es-hdr-findings-section" style="display:none">
<h2>Findings</h2>
<div id="es-hdr-findings"></div>
</div>
</div>
<!-- Phishing Detection -->
<div class="section">
<h2>Phishing Detection</h2>
<div class="form-group">
<label>Paste email content (body, headers, or both)</label>
<textarea id="es-phish-content" rows="8" placeholder="Paste the email body or full email content..."></textarea>
</div>
<div class="tool-actions">
<button id="btn-es-phish" class="btn btn-primary" onclick="esDetectPhishing()">Detect Phishing</button>
</div>
</div>
<div id="es-phish-results" style="display:none">
<div class="section">
<div style="display:flex;gap:24px;align-items:flex-start;flex-wrap:wrap">
<div class="score-display" style="min-width:120px;text-align:center">
<div class="score-value" id="es-phish-score" style="font-size:2.5rem">0</div>
<div class="score-label">Risk Score</div>
<div id="es-phish-level" style="margin-top:4px;font-weight:700;font-size:0.9rem">Low</div>
</div>
<div style="flex:1;min-width:250px">
<h3 style="margin-bottom:8px">Findings</h3>
<div id="es-phish-findings"></div>
<div id="es-phish-urls" style="margin-top:16px"></div>
</div>
</div>
</div>
</div>
<!-- Abuse Report -->
<div class="section">
<h2>Abuse Report Generator</h2>
<div class="form-row">
<div class="form-group">
<label>Incident Type</label>
<select id="es-abuse-type">
<option value="spam">Spam</option>
<option value="phishing">Phishing</option>
<option value="malware">Malware Distribution</option>
<option value="spoofing">Email Spoofing</option>
<option value="scam">Scam / Fraud</option>
</select>
</div>
<div class="form-group">
<label>Source IP</label>
<input type="text" id="es-abuse-ip" placeholder="Offending IP address">
</div>
<div class="form-group">
<label>Source Domain</label>
<input type="text" id="es-abuse-domain" placeholder="Offending domain">
</div>
</div>
<div class="form-group">
<label>Description</label>
<textarea id="es-abuse-desc" rows="3" placeholder="Describe the incident..."></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label>Reporter Name</label>
<input type="text" id="es-abuse-reporter" placeholder="Your name or organization">
</div>
<div class="form-group">
<label>Reporter Email</label>
<input type="text" id="es-abuse-email" placeholder="your@email.com">
</div>
</div>
<div class="form-group">
<label>Evidence — Headers (optional)</label>
<textarea id="es-abuse-headers" rows="4" placeholder="Paste relevant email headers..."></textarea>
</div>
<div class="tool-actions">
<button id="btn-es-abuse" class="btn btn-primary" onclick="esAbuseReport()">Generate Report</button>
</div>
<div id="es-abuse-output" style="display:none;margin-top:12px">
<pre class="output-panel" id="es-abuse-text" style="max-height:500px;overflow-y:auto;white-space:pre-wrap"></pre>
<button class="btn btn-small" style="margin-top:8px" onclick="esAbuseCopy()">Copy to Clipboard</button>
</div>
</div>
</div><!-- /headers tab -->
<!-- ==================== MAILBOX TAB ==================== -->
<div class="tab-content" data-tab-group="es" data-tab="mailbox">
<div class="section">
<h2>Mailbox Connection</h2>
<div class="form-row">
<div class="form-group" style="flex:2">
<label>Mail Server Host</label>
<input type="text" id="es-mb-host" placeholder="imap.example.com">
</div>
<div class="form-group" style="max-width:130px">
<label>Protocol</label>
<select id="es-mb-proto" onchange="esMbProtoChanged()">
<option value="imap">IMAP</option>
<option value="pop3">POP3</option>
</select>
</div>
<div class="form-group" style="max-width:100px">
<label>SSL</label>
<select id="es-mb-ssl">
<option value="true">Yes</option>
<option value="false">No</option>
</select>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label>Username</label>
<input type="text" id="es-mb-user" placeholder="user@example.com">
</div>
<div class="form-group">
<label>Password</label>
<input type="password" id="es-mb-pass" placeholder="Password">
</div>
</div>
</div>
<div class="section">
<h2>Search</h2>
<div class="form-row">
<div class="form-group" style="max-width:180px">
<label>Folder</label>
<input type="text" id="es-mb-folder" value="INBOX" placeholder="INBOX">
</div>
<div class="form-group" style="flex:1">
<label>Query (subject, sender email, or IMAP search)</label>
<input type="text" id="es-mb-query" placeholder="e.g. invoice, user@sender.com, or IMAP criteria"
onkeypress="if(event.key==='Enter')esMbSearch()">
</div>
<div class="form-group" style="align-self:flex-end">
<button id="btn-es-mb-search" class="btn btn-primary" onclick="esMbSearch()">Search</button>
</div>
</div>
</div>
<div class="section">
<h2>Results</h2>
<div id="es-mb-status" style="margin-bottom:8px;font-size:0.85rem;color:var(--text-secondary)"></div>
<div style="max-height:500px;overflow-y:auto">
<table class="data-table">
<thead><tr><th>Date</th><th>From</th><th>Subject</th><th>Size</th><th>Actions</th></tr></thead>
<tbody id="es-mb-rows">
<tr><td colspan="5" class="empty-state">Connect and search to see results.</td></tr>
</tbody>
</table>
</div>
</div>
<!-- Email Viewer Modal -->
<div id="es-mb-viewer" style="display:none" class="section">
<h2>Email Viewer
<button class="btn btn-small" style="float:right" onclick="document.getElementById('es-mb-viewer').style.display='none'">Close</button>
</h2>
<div style="display:flex;gap:8px;margin-bottom:12px">
<button class="btn btn-small" onclick="esViewerTab('headers')">Headers</button>
<button class="btn btn-small" onclick="esViewerTab('body')">Body</button>
<button class="btn btn-small" onclick="esViewerTab('attachments')">Attachments</button>
</div>
<div id="es-viewer-headers">
<pre class="output-panel" id="es-viewer-headers-text" style="max-height:400px;overflow-y:auto;white-space:pre-wrap"></pre>
</div>
<div id="es-viewer-body" style="display:none">
<pre class="output-panel" id="es-viewer-body-text" style="max-height:400px;overflow-y:auto;white-space:pre-wrap"></pre>
</div>
<div id="es-viewer-attachments" style="display:none">
<table class="data-table">
<thead><tr><th>Filename</th><th>Type</th><th>Size</th></tr></thead>
<tbody id="es-viewer-att-rows">
<tr><td colspan="3" class="empty-state">No attachments</td></tr>
</tbody>
</table>
</div>
</div>
</div><!-- /mailbox tab -->
<script>
/* ── helpers ── */
function esc(s){var d=document.createElement('div');d.textContent=s||'';return d.innerHTML;}
function setLoading(btn,on){if(!btn)return;btn.disabled=on;btn.dataset.origText=btn.dataset.origText||btn.textContent;btn.textContent=on?'Working...':btn.dataset.origText;}
function statusBadge(s){
var colors={pass:'var(--success,#22c55e)',warn:'#eab308',fail:'var(--danger,#ef4444)',none:'var(--text-muted)'};
var labels={pass:'PASS',warn:'WARN',fail:'FAIL',none:'N/A'};
var c=colors[s]||colors.none, l=labels[s]||s;
return '<span style="display:inline-block;padding:2px 10px;border-radius:4px;font-size:0.78rem;font-weight:700;background:'+c+'22;color:'+c+';border:1px solid '+c+'44">'+l+'</span>';
}
function renderFindings(containerId,findings){
var el=document.getElementById(containerId);if(!el)return;
if(!findings||!findings.length){el.innerHTML='';return;}
var html='';
findings.forEach(function(f){
var color=f.level==='pass'?'var(--success,#22c55e)':f.level==='warn'?'#eab308':'var(--danger,#ef4444)';
var icon=f.level==='pass'?'[+]':f.level==='warn'?'[!]':'[X]';
html+='<div style="padding:4px 0;color:'+color+';font-size:0.85rem">'+icon+' '+esc(f.message||f.detail||'')+'</div>';
});
el.innerHTML=html;
}
function fmtBytes(b){
if(!b||b<1)return '--';
if(b<1024)return b+' B';
if(b<1048576)return (b/1024).toFixed(1)+' KB';
return (b/1048576).toFixed(1)+' MB';
}
/* ---- DOMAIN ANALYSIS ---- */
function esDomainAnalyze(){
var domain=document.getElementById('es-domain').value.trim();
if(!domain)return;
var btn=document.getElementById('btn-es-domain');
setLoading(btn,true);
postJSON('/email-sec/domain',{domain:domain}).then(function(d){
setLoading(btn,false);
if(d.error){alert(d.error);return;}
document.getElementById('es-results').style.display='';
/* Grade & score */
var gradeEl=document.getElementById('es-grade');
gradeEl.textContent=d.grade||'?';
var gc={A:'#22c55e',B:'#86efac',C:'#eab308',D:'#f97316',F:'#ef4444'};
gradeEl.style.color=gc[d.grade]||'var(--text-primary)';
document.getElementById('es-score-num').textContent=(d.score||0)+'/100';
/* Summary row */
document.getElementById('es-sum-spf').innerHTML=statusBadge(d.summary.spf_status);
document.getElementById('es-sum-dmarc').innerHTML=statusBadge(d.summary.dmarc_status);
document.getElementById('es-sum-dkim').innerHTML=statusBadge(d.summary.dkim_status);
document.getElementById('es-sum-mx').innerHTML=statusBadge(d.summary.mx_status);
/* SPF */
document.getElementById('es-spf-record').textContent=d.spf.record||'(none)';
var mechHtml='';
if(d.spf.mechanisms&&d.spf.mechanisms.length){
mechHtml='<table class="data-table" style="margin-bottom:8px"><thead><tr><th>Type</th><th>Value</th><th>Qualifier</th></tr></thead><tbody>';
d.spf.mechanisms.forEach(function(m){
var qLabels={'+':'Pass','-':'Fail','~':'SoftFail','?':'Neutral'};
mechHtml+='<tr><td>'+esc(m.type)+'</td><td>'+esc(m.value)+'</td><td>'+esc(qLabels[m.qualifier]||m.qualifier)+'</td></tr>';
});
mechHtml+='</tbody></table>';
mechHtml+='<div style="font-size:0.82rem;color:var(--text-secondary)">DNS lookups: '+d.spf.dns_lookups+'/10</div>';
}
document.getElementById('es-spf-mechanisms').innerHTML=mechHtml;
renderFindings('es-spf-findings',d.spf.findings);
/* DMARC */
document.getElementById('es-dmarc-record').textContent=d.dmarc.record||'(none)';
document.getElementById('es-dmarc-policy').textContent=d.dmarc.policy||'none';
document.getElementById('es-dmarc-sp').textContent=d.dmarc.subdomain_policy||'(inherits domain)';
document.getElementById('es-dmarc-pct').textContent=(d.dmarc.pct!=null?d.dmarc.pct+'%':'--');
document.getElementById('es-dmarc-aspf').textContent=d.dmarc.aspf==='s'?'strict':'relaxed';
document.getElementById('es-dmarc-adkim').textContent=d.dmarc.adkim==='s'?'strict':'relaxed';
document.getElementById('es-dmarc-rua').textContent=d.dmarc.rua&&d.dmarc.rua.length?d.dmarc.rua.join(', '):'(none)';
document.getElementById('es-dmarc-ruf').textContent=d.dmarc.ruf&&d.dmarc.ruf.length?d.dmarc.ruf.join(', '):'(none)';
renderFindings('es-dmarc-findings',d.dmarc.findings);
/* DKIM */
var dkimHtml='';
if(d.dkim.found_selectors&&d.dkim.found_selectors.length){
dkimHtml='<table class="data-table"><thead><tr><th>Selector</th><th>Key Type</th><th>Status</th></tr></thead><tbody>';
d.dkim.found_selectors.forEach(function(s){
var st=s.revoked?'<span style="color:var(--danger)">Revoked</span>':'<span style="color:var(--success,#22c55e)">Active</span>';
dkimHtml+='<tr><td>'+esc(s.selector)+'</td><td>'+esc(s.key_type||'rsa')+'</td><td>'+st+'</td></tr>';
});
dkimHtml+='</tbody></table>';
} else {
dkimHtml='<div class="empty-state">No DKIM selectors found among common names.</div>';
}
document.getElementById('es-dkim-selectors').innerHTML=dkimHtml;
renderFindings('es-dkim-findings',d.dkim.findings);
/* MX */
var mxBody=document.getElementById('es-mx-rows');
if(d.mx.mx_records&&d.mx.mx_records.length){
var rows='';
d.mx.mx_records.forEach(function(m){
var tlsIcon=m.starttls?'<span style="color:var(--success,#22c55e);font-weight:700">Yes</span>':'<span style="color:var(--danger)">No</span>';
if(m.starttls_error)tlsIcon+=' <span style="font-size:0.75rem;color:var(--text-muted)">('+esc(m.starttls_error)+')</span>';
rows+='<tr><td>'+m.priority+'</td><td>'+esc(m.host)+'</td><td>'+esc(m.ip||'--')+'</td><td>'+tlsIcon+'</td><td style="font-size:0.78rem;color:var(--text-muted)">'+esc(m.banner||'').substring(0,60)+'</td></tr>';
});
mxBody.innerHTML=rows;
} else {
mxBody.innerHTML='<tr><td colspan="5" class="empty-state">No MX records found</td></tr>';
}
renderFindings('es-mx-findings',d.mx.findings);
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
}
/* ---- BLACKLIST CHECK ---- */
function esBlCheck(){
var target=document.getElementById('es-bl-target').value.trim();
if(!target)return;
var btn=document.getElementById('btn-es-bl');
setLoading(btn,true);
document.getElementById('es-bl-summary').style.display='none';
postJSON('/email-sec/blacklist',{ip_or_domain:target}).then(function(d){
setLoading(btn,false);
if(d.error){document.getElementById('es-bl-rows').innerHTML='<tr><td colspan="3" style="color:var(--danger)">'+esc(d.error)+'</td></tr>';return;}
var sum=document.getElementById('es-bl-summary');
sum.style.display='';
if(d.clean){
sum.innerHTML='<span style="color:var(--success,#22c55e);font-weight:700">CLEAN</span> — not listed on any of '+d.total_checked+' blacklists (IP: '+esc(d.ip)+')';
} else {
sum.innerHTML='<span style="color:var(--danger);font-weight:700">LISTED</span> on '+d.listed_count+'/'+d.total_checked+' blacklists (IP: '+esc(d.ip)+')';
}
var rows='';
d.results.forEach(function(r){
var st=r.listed?'<span style="color:var(--danger);font-weight:700">LISTED</span>':'<span style="color:var(--success,#22c55e)">Clean</span>';
rows+='<tr><td>'+esc(r.blacklist)+'</td><td>'+st+'</td><td style="font-size:0.82rem">'+esc(r.details)+'</td></tr>';
});
document.getElementById('es-bl-rows').innerHTML=rows;
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
}
/* ---- HEADER ANALYSIS ---- */
function esAnalyzeHeaders(){
var raw=document.getElementById('es-raw-headers').value.trim();
if(!raw){alert('Please paste email headers');return;}
var btn=document.getElementById('btn-es-headers');
setLoading(btn,true);
postJSON('/email-sec/headers',{raw_headers:raw}).then(function(d){
setLoading(btn,false);
if(d.error){alert(d.error);return;}
document.getElementById('es-hdr-results').style.display='';
/* Auth badges */
var authColors={pass:'var(--success,#22c55e)',fail:'var(--danger)',none:'var(--text-muted)'};
['spf','dkim','dmarc'].forEach(function(k){
var v=d.authentication[k]||'none';
var el=document.getElementById('es-hdr-'+k);
el.textContent=k.toUpperCase()+': '+v.toUpperCase();
el.style.background=(authColors[v]||authColors.none)+'22';
el.style.color=authColors[v]||authColors.none;
el.style.border='1px solid '+(authColors[v]||authColors.none)+'44';
el.style.padding='4px 14px';el.style.borderRadius='4px';el.style.fontWeight='700';el.style.fontSize='0.82rem';
});
document.getElementById('es-hdr-from').textContent=d.from||'--';
document.getElementById('es-hdr-rpath').textContent=d.return_path||'--';
document.getElementById('es-hdr-replyto').textContent=d.reply_to||'--';
document.getElementById('es-hdr-subject').textContent=d.subject||'--';
document.getElementById('es-hdr-date').textContent=d.date||'--';
document.getElementById('es-hdr-origip').textContent=d.originating_ip||'Unknown';
document.getElementById('es-hdr-msgid').textContent=d.message_id||'--';
/* Received chain */
var chainEl=document.getElementById('es-hdr-chain');
if(d.received_chain&&d.received_chain.length){
var html='<div style="position:relative;padding-left:24px">';
d.received_chain.forEach(function(hop,i){
var isLast=i===d.received_chain.length-1;
html+='<div style="position:relative;padding:8px 0 12px 0;border-left:2px solid var(--border);margin-left:6px;padding-left:20px">';
html+='<div style="position:absolute;left:-7px;top:10px;width:14px;height:14px;border-radius:50%;background:var(--accent);border:2px solid var(--bg-card)"></div>';
html+='<div style="font-weight:700;font-size:0.85rem;color:var(--text-primary)">Hop '+hop.hop+'</div>';
if(hop.from)html+='<div style="font-size:0.82rem"><span style="color:var(--text-muted)">from</span> '+esc(hop.from)+'</div>';
if(hop.by)html+='<div style="font-size:0.82rem"><span style="color:var(--text-muted)">by</span> '+esc(hop.by)+'</div>';
if(hop.ip)html+='<div style="font-size:0.82rem"><span style="color:var(--text-muted)">IP</span> <strong>'+esc(hop.ip)+'</strong></div>';
if(hop.timestamp)html+='<div style="font-size:0.75rem;color:var(--text-muted)">'+esc(hop.timestamp)+'</div>';
html+='</div>';
});
html+='</div>';
chainEl.innerHTML=html;
} else {
chainEl.innerHTML='<div class="empty-state">No Received headers found.</div>';
}
/* Spoofing */
var spoofSect=document.getElementById('es-hdr-spoof-section');
var spoofEl=document.getElementById('es-hdr-spoofing');
if(d.spoofing_indicators&&d.spoofing_indicators.length){
spoofSect.style.display='';
var sh='';
d.spoofing_indicators.forEach(function(s){
sh+='<div style="padding:6px 10px;margin-bottom:6px;background:var(--danger)11;border:1px solid var(--danger)33;border-radius:var(--radius);font-size:0.85rem">';
sh+='<strong style="color:var(--danger)">[!] '+esc(s.indicator)+'</strong><br>';
sh+='<span style="color:var(--text-secondary)">'+esc(s.detail)+'</span></div>';
});
spoofEl.innerHTML=sh;
} else {
spoofSect.style.display='none';
}
/* Findings */
var findSect=document.getElementById('es-hdr-findings-section');
if(d.findings&&d.findings.length){
findSect.style.display='';
renderFindings('es-hdr-findings',d.findings);
} else {
findSect.style.display='none';
}
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
}
/* ---- PHISHING DETECTION ---- */
function esDetectPhishing(){
var content=document.getElementById('es-phish-content').value.trim();
if(!content){alert('Please paste email content');return;}
var btn=document.getElementById('btn-es-phish');
setLoading(btn,true);
postJSON('/email-sec/phishing',{email_content:content}).then(function(d){
setLoading(btn,false);
if(d.error){alert(d.error);return;}
document.getElementById('es-phish-results').style.display='';
var scoreEl=document.getElementById('es-phish-score');
scoreEl.textContent=d.risk_score;
var levelEl=document.getElementById('es-phish-level');
levelEl.textContent=d.risk_level.toUpperCase();
var riskColors={low:'var(--success,#22c55e)',medium:'#eab308',high:'#f97316',critical:'var(--danger)'};
var rc=riskColors[d.risk_level]||riskColors.low;
scoreEl.style.color=rc;
levelEl.style.color=rc;
var fHtml='';
if(d.findings&&d.findings.length){
d.findings.forEach(function(f){
var sevC=f.severity==='high'?'var(--danger)':f.severity==='medium'?'#eab308':'var(--text-secondary)';
fHtml+='<div style="padding:6px 10px;margin-bottom:6px;background:var(--bg-input);border-radius:var(--radius);font-size:0.85rem">';
fHtml+='<strong style="color:'+sevC+'">'+esc(f.category).replace(/_/g,' ')+'</strong>';
fHtml+=' <span style="font-size:0.75rem;color:var(--text-muted)">(weight: '+f.weight+')</span><br>';
fHtml+='<span style="color:var(--text-secondary)">Matches: '+esc(f.matches.join(', '))+'</span></div>';
});
} else {
fHtml='<div style="color:var(--success,#22c55e);font-size:0.85rem">No phishing indicators detected.</div>';
}
document.getElementById('es-phish-findings').innerHTML=fHtml;
var uHtml='';
if(d.suspicious_urls&&d.suspicious_urls.length){
uHtml='<h3 style="margin-bottom:8px;font-size:0.9rem">Suspicious URLs</h3>';
d.suspicious_urls.forEach(function(u){
uHtml+='<div style="padding:6px 10px;margin-bottom:6px;background:var(--danger)11;border:1px solid var(--danger)33;border-radius:var(--radius);font-size:0.82rem">';
uHtml+='<div style="word-break:break-all;color:var(--danger)">'+esc(u.url)+'</div>';
u.reasons.forEach(function(r){uHtml+='<div style="color:var(--text-secondary);padding-left:12px">- '+esc(r)+'</div>';});
uHtml+='</div>';
});
}
document.getElementById('es-phish-urls').innerHTML=uHtml;
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
}
/* ---- ABUSE REPORT ---- */
function esAbuseReport(){
var btn=document.getElementById('btn-es-abuse');
setLoading(btn,true);
var data={
type:document.getElementById('es-abuse-type').value,
source_ip:document.getElementById('es-abuse-ip').value.trim(),
source_domain:document.getElementById('es-abuse-domain').value.trim(),
description:document.getElementById('es-abuse-desc').value.trim(),
reporter_name:document.getElementById('es-abuse-reporter').value.trim(),
reporter_email:document.getElementById('es-abuse-email').value.trim(),
headers:document.getElementById('es-abuse-headers').value.trim()
};
postJSON('/email-sec/abuse-report',{incident_data:data}).then(function(d){
setLoading(btn,false);
if(d.error){alert(d.error);return;}
document.getElementById('es-abuse-output').style.display='';
document.getElementById('es-abuse-text').textContent=d.report_text;
}).catch(function(e){setLoading(btn,false);alert('Error: '+e);});
}
function esAbuseCopy(){
var text=document.getElementById('es-abuse-text').textContent;
navigator.clipboard.writeText(text).then(function(){alert('Copied to clipboard');});
}
/* ---- MAILBOX ---- */
var _esMbConn={};
function esMbProtoChanged(){
/* Hint: change placeholder port */
}
function esMbSearch(){
var host=document.getElementById('es-mb-host').value.trim();
var user=document.getElementById('es-mb-user').value.trim();
var pass=document.getElementById('es-mb-pass').value;
if(!host||!user||!pass){alert('Host, username, and password are required');return;}
_esMbConn={host:host,username:user,password:pass,
protocol:document.getElementById('es-mb-proto').value,
ssl:document.getElementById('es-mb-ssl').value==='true'};
var btn=document.getElementById('btn-es-mb-search');
setLoading(btn,true);
document.getElementById('es-mb-status').textContent='Connecting to '+host+'...';
postJSON('/email-sec/mailbox/search',{
host:host, username:user, password:pass,
protocol:_esMbConn.protocol,
query:document.getElementById('es-mb-query').value.trim()||null,
folder:document.getElementById('es-mb-folder').value.trim()||'INBOX',
ssl:_esMbConn.ssl
}).then(function(d){
setLoading(btn,false);
if(d.error){
document.getElementById('es-mb-status').innerHTML='<span style="color:var(--danger)">Error: '+esc(d.error)+'</span>';
document.getElementById('es-mb-rows').innerHTML='<tr><td colspan="5" class="empty-state">Connection failed.</td></tr>';
return;
}
document.getElementById('es-mb-status').textContent='Found '+d.total+' messages (showing '+((d.messages||[]).length)+')';
var rows='';
if(d.messages&&d.messages.length){
d.messages.reverse().forEach(function(m){
rows+='<tr>';
rows+='<td style="white-space:nowrap;font-size:0.82rem">'+esc((m.date||'').substring(0,22))+'</td>';
rows+='<td style="max-width:200px;overflow:hidden;text-overflow:ellipsis;font-size:0.82rem">'+esc(m.from)+'</td>';
rows+='<td style="max-width:250px;overflow:hidden;text-overflow:ellipsis">'+esc(m.subject)+'</td>';
rows+='<td style="font-size:0.82rem">'+fmtBytes(m.size)+'</td>';
rows+='<td><button class="btn btn-small" onclick="esMbView(\''+esc(m.id)+'\')">View</button></td>';
rows+='</tr>';
});
} else {
rows='<tr><td colspan="5" class="empty-state">No messages found.</td></tr>';
}
document.getElementById('es-mb-rows').innerHTML=rows;
}).catch(function(e){setLoading(btn,false);document.getElementById('es-mb-status').innerHTML='<span style="color:var(--danger)">'+esc(String(e))+'</span>';});
}
function esMbView(msgId){
if(!_esMbConn.host){alert('Not connected');return;}
document.getElementById('es-mb-viewer').style.display='';
document.getElementById('es-viewer-headers-text').textContent='Loading...';
document.getElementById('es-viewer-body-text').textContent='';
document.getElementById('es-viewer-att-rows').innerHTML='<tr><td colspan="3">Loading...</td></tr>';
esViewerTab('headers');
postJSON('/email-sec/mailbox/fetch',{
host:_esMbConn.host, username:_esMbConn.username,
password:_esMbConn.password, message_id:msgId,
protocol:_esMbConn.protocol, ssl:_esMbConn.ssl
}).then(function(d){
if(d.error){
document.getElementById('es-viewer-headers-text').textContent='Error: '+d.error;
return;
}
document.getElementById('es-viewer-headers-text').textContent=d.raw_headers||'(no headers)';
document.getElementById('es-viewer-body-text').textContent=d.body||'(empty body)';
var attRows='';
if(d.attachments&&d.attachments.length){
d.attachments.forEach(function(a){
attRows+='<tr><td>'+esc(a.filename)+'</td><td>'+esc(a.content_type)+'</td><td>'+fmtBytes(a.size)+'</td></tr>';
});
} else {
attRows='<tr><td colspan="3" class="empty-state">No attachments</td></tr>';
}
document.getElementById('es-viewer-att-rows').innerHTML=attRows;
}).catch(function(e){document.getElementById('es-viewer-headers-text').textContent='Error: '+e;});
}
function esViewerTab(tab){
['headers','body','attachments'].forEach(function(t){
var el=document.getElementById('es-viewer-'+t);
if(el)el.style.display=t===tab?'':'none';
});
}
</script>
{% endblock %}