| Server IP : 202.61.199.114 / Your IP : 216.73.217.139 Web Server : nginx/1.22.1 System : Linux de.arni-solutions.de 6.1.0-49-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.1.174-1 (2026-05-26) x86_64 User : web20 ( 1018) PHP Version : 8.4.23 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /tmp/ |
Upload File : |
/**
* SafeMyIdent – IP-Check Pro (Frontend).
* Ruft den /api-Service parallel ab, führt die Daten zusammen, berechnet einen
* Risiko-Score und rendert Tabs/Karten. Vanilla JS, barrierearm.
*/
(function () {
'use strict';
var CFG = window.SMI_IPCHECK || { apiBase: '/api/v1', modules: {}, pdf: true, lang: 'de' };
var __ = (window.wp && wp.i18n && wp.i18n.__) ? wp.i18n.__ : function (s) { return s; };
function t(s) { return __(s, 'smi-ipcheck'); }
function sprintf() {
var a = arguments, i = 1;
return String(a[0]).replace(/%s/g, function () { return a[i++]; });
}
var root = document.getElementById('smi-ipcheck-app');
if (!root) { return; }
var elForm = document.getElementById('smi-ipc-form');
var elInput = document.getElementById('smi-ipc-input');
var elSelf = document.getElementById('smi-ipc-self');
var elMyip = document.getElementById('smi-ipc-myip');
var elError = document.getElementById('smi-ipc-error');
var elResults= document.getElementById('smi-ipc-results');
var state = {};
// ---- Helfer -------------------------------------------------------------
function el(tag, cls, html) {
var e = document.createElement(tag);
if (cls) { e.className = cls; }
if (html != null) { e.innerHTML = html; }
return e;
}
function esc(s) {
return String(s == null ? '' : s).replace(/[&<>"']/g, function (c) {
return { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c];
});
}
function api(path, params) {
var qs = Object.keys(params || {}).map(function (k) { return k + '=' + encodeURIComponent(params[k]); }).join('&');
return fetch(CFG.apiBase + '/' + path + (qs ? '?' + qs : ''), { headers: { 'Accept': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } })
.then(function (r) { return r.json(); })
.then(function (j) { if (!j || !j.ok) { throw new Error((j && j.error) || t('Es ist ein Fehler aufgetreten.')); } return j.data; });
}
function countryName(cc) {
if (!cc) { return ''; }
try { return new Intl.DisplayNames([CFG.lang || 'de'], { type: 'region' }).of(cc) || cc; }
catch (e) { return cc; }
}
function flag(cc) {
if (!cc || cc.length !== 2) { return ''; }
return cc.toUpperCase().replace(/./g, function (c) { return String.fromCodePoint(127397 + c.charCodeAt(0)); });
}
function badge(text, cls) { return '<span class="smi-ipc__pill ' + (cls || '') + '">' + esc(text) + '</span>'; }
function providerLabel(slug) {
var m = { 'abuseipdb': 'AbuseIPDB', 'proxycheck': 'proxycheck.io', 'ip-api': 'ip-api.com', 'abusech': 'abuse.ch', 'greynoise': 'GreyNoise', 'ipqs': 'IPQualityScore', 'ipqualityscore': 'IPQualityScore', 'shodan': 'Shodan', 'internetdb': 'Shodan InternetDB', 'shodan-internetdb': 'Shodan InternetDB', 'otx': 'AlienVault OTX', 'alienvault': 'AlienVault OTX', 'crowdsec': 'CrowdSec', 'virustotal': 'VirusTotal', 'dshield': 'SANS ISC / DShield', 'isc': 'SANS ISC / DShield', 'stopforumspam': 'StopForumSpam', 'sfs': 'StopForumSpam', 'blocklistde': 'BlockList.de', 'blocklist-de': 'BlockList.de', 'ipinfo': 'IPinfo.io', 'pulsedive': 'Pulsedive', 'scamalytics': 'Scamalytics' };
return m[String(slug).toLowerCase()] || slug;
}
function yesno(v) { return v ? badge(t('Ja'), 'is-ok') : badge(t('Nein'), 'is-bad'); }
// Key/Value-Tabelle bauen. rows: [[label, valueHTML, note?], ...]
function kv(rows) {
var h = '<dl class="smi-ipc__kv">';
rows.forEach(function (r) {
if (r === null) { return; }
h += '<div class="smi-ipc__kv-row"><dt>' + esc(r[0]) + '</dt><dd>' + (r[1] == null || r[1] === '' ? '<span class="smi-ipc__muted">—</span>' : r[1]) + (r[2] ? '<small class="smi-ipc__note">' + esc(r[2]) + '</small>' : '') + '</dd></div>';
});
return h + '</dl>';
}
function card(title, inner, icon) {
return '<div class="smi-ipc__card"><h3 class="smi-ipc__card-title">' + (icon ? '<i class="bi bi-' + icon + '" aria-hidden="true"></i> ' : '') + esc(title) + '</h3>' + inner + '</div>';
}
function explain(text, level) { return '<p class="smi-ipc__explain smi-ipc__explain--' + (level || 'info') + '">' + esc(text) + '</p>'; }
function setPanel(key, html) {
var p = document.getElementById('smi-ipc-panel-' + key);
if (p) { p.innerHTML = html; }
}
function skeleton() { return '<div class="smi-ipc__skeleton" aria-hidden="true"><span></span><span></span><span></span></div>'; }
function loading(key) { setPanel(key, skeleton()); }
// Fortschritts-/Status-Balken.
var elProg = document.getElementById('smi-ipc-progress');
var elProgFill = document.getElementById('smi-ipc-progress-fill');
var elProgText = document.getElementById('smi-ipc-progress-text');
function progressStart(label) { if (!elProg) { return; } elProg.hidden = false; elProg.classList.remove('is-done'); elProgFill.style.width = '8%'; elProgText.textContent = label || t('Analyse läuft …'); }
function progressSet(done, total, label) { if (!elProgFill) { return; } var pct = total ? Math.max(8, Math.round(done / total * 100)) : 8; elProgFill.style.width = pct + '%'; if (label != null) { elProgText.textContent = label; } }
function progressDone() { if (!elProg) { return; } elProgFill.style.width = '100%'; elProg.classList.add('is-done'); elProgText.textContent = t('Analyse abgeschlossen'); setTimeout(function () { if (elProg.classList.contains('is-done')) { elProg.hidden = true; } }, 1400); }
function progressHide() { if (elProg) { elProg.hidden = true; } }
function showError(msg) {
elError.textContent = msg;
elError.hidden = false;
}
function clearError() { elError.hidden = true; elError.textContent = ''; }
// ---- Tab-Navigation (ARIA) ---------------------------------------------
var tabs = Array.prototype.slice.call(root.querySelectorAll('.smi-ipc__tab'));
tabs.forEach(function (tab) {
tab.addEventListener('click', function () { activateTab(tab); });
tab.addEventListener('keydown', function (e) {
if (e.key !== 'ArrowRight' && e.key !== 'ArrowLeft') { return; }
e.preventDefault();
var vis = tabs.filter(function (x) { return !x.hidden; });
var idx = vis.indexOf(tab);
if (idx < 0) { return; }
var n = e.key === 'ArrowRight' ? (idx + 1) % vis.length : (idx - 1 + vis.length) % vis.length;
vis[n].focus(); activateTab(vis[n]);
});
});
function activateTab(tab) {
if (!tab || tab.hidden) { tab = tabs.filter(function (x) { return !x.hidden; })[0]; }
if (!tab) { return; }
tabs.forEach(function (x) {
var on = x === tab;
x.classList.toggle('is-active', on);
x.setAttribute('aria-selected', on ? 'true' : 'false');
x.tabIndex = on ? 0 : -1;
var panel = document.getElementById('smi-ipc-panel-' + x.dataset.tab);
if (panel) { panel.hidden = !on; panel.classList.toggle('is-active', on); }
});
}
// Tab (Button + Panel) ein-/ausblenden. Aktiven Tab bei Bedarf umschalten.
function setTabVisible(key, visible) {
var tab = document.getElementById('smi-ipc-tab-' + key);
var panel = document.getElementById('smi-ipc-panel-' + key);
if (tab) { tab.hidden = !visible; }
if (panel && !visible) { panel.hidden = true; panel.classList.remove('is-active'); }
if (!visible && tab && tab.classList.contains('is-active')) { activateTab(null); }
}
// ---- Risiko-Engine ------------------------------------------------------
function computeRisk(s) {
var score = 0, reasons = [];
function add(pts, why) { score += pts; if (why) { reasons.push({ pts: pts, text: why }); } }
if (s.blacklist && s.blacklist.supported) {
var listed = s.blacklist.listed || 0;
if (listed > 0) { add(Math.min(55, 18 + listed * 12), sprintf(t('Auf %s Blacklist(s) gelistet.'), listed)); }
}
if (s.core && s.core.detection) {
if (s.core.detection.tor) { add(35, t('Tor-Exit-Node erkannt.')); }
if (s.core.detection.is_hosting) { add(12, sprintf(t('Hosting/Rechenzentrum (%s).'), s.core.detection.provider || s.core.detection.type)); }
}
// Externe Reputation (Gateway).
var intel = s.core && s.core.intel;
if (intel && intel.ok) {
if (intel.blacklisted) { add(60, t('Auf der Reputations-Blacklist des Gateways gelistet.')); }
if (intel.verdict === 'malicious') { add(45, t('Reputationsdienst stuft die IP als bösartig ein.')); }
else if (intel.verdict === 'suspicious') { add(22, t('Reputationsdienst stuft die IP als auffällig ein.')); }
if (intel.score != null && intel.score >= 25) { add(Math.min(30, Math.round(intel.score / 3)), sprintf(t('Externer Reputations-Score: %s/100.'), intel.score)); }
}
if (s.core) {
if (!s.core.reverse_dns) { add(8, t('Kein Reverse-DNS (PTR) vorhanden.')); }
else if (s.core.fcrdns === false) { add(6, t('Vorwärts/Rückwärts-DNS stimmen nicht überein.')); }
}
if (s.mail && s.mail.has_mx) {
if (!s.mail.spf.present) { add(6, t('Kein SPF-Record.')); }
if (!s.mail.dmarc.present) { add(6, t('Kein DMARC-Record.')); }
else if (s.mail.dmarc.policy === 'none') { add(3, t('DMARC nur im Überwachungsmodus (p=none).')); }
if (s.mail.spf.too_open) { add(4, t('SPF zu offen (+all).')); }
}
if (s.url && s.url.ok) {
if (!s.url.https) { add(10, t('Keine HTTPS-Verschlüsselung.')); }
if (s.url.cert_valid === false) { add(15, t('SSL-Zertifikat ungültig/abgelaufen.')); }
if (s.url.https && !s.url.hsts) { add(3, t('Kein HSTS.')); }
}
score = Math.max(0, Math.min(100, Math.round(score)));
var level = score >= 65 ? 'red' : score >= 35 ? 'orange' : score >= 15 ? 'yellow' : 'green';
var labelMap = { green: t('Unauffällig'), yellow: t('Hinweise'), orange: t('Erhöhtes Risiko'), red: t('Kritisch') };
return { score: score, level: level, label: labelMap[level], reasons: reasons };
}
function renderScore(s) {
var r = computeRisk(s);
state.risk = r;
var num = document.getElementById('smi-ipc-score-num');
var gauge = document.getElementById('smi-ipc-gauge');
var badgeEl = document.getElementById('smi-ipc-score-badge');
var target = document.getElementById('smi-ipc-score-target');
var desc = document.getElementById('smi-ipc-score-desc');
var scoreBox = document.getElementById('smi-ipc-score');
num.textContent = r.score;
scoreBox.setAttribute('data-level', r.level);
var circ = 2 * Math.PI * 52;
gauge.style.strokeDasharray = circ;
gauge.style.strokeDashoffset = circ * (1 - r.score / 100);
badgeEl.className = 'smi-ipc__score-badge is-' + r.level;
badgeEl.textContent = r.label;
var tgt = (s.core && s.core.target_ip) || '';
var host = (s.core && s.core.input && (s.core.input.domain || s.core.input.host)) || '';
target.textContent = host && host !== tgt ? host + ' → ' + tgt : (tgt || host);
desc.innerHTML = r.reasons.length
? esc(sprintf(t('Bewertung „%s“, weil:'), r.label)) + ' ' + r.reasons.map(function (x) { return esc(x.text); }).join(' ')
: esc(t('Keine auffälligen Merkmale gefunden. Diese IP/Domain wirkt unauffällig.'));
var pdfBtn = document.getElementById('smi-ipc-pdf');
if (pdfBtn && CFG.pdf) { pdfBtn.hidden = false; }
}
// ---- Renderer je Tab ----------------------------------------------------
function highlight(label, value, cls) {
return '<div class="smi-ipc__hl"><span class="smi-ipc__hl-label">' + esc(label) + '</span><span class="smi-ipc__hl-val ' + (cls || '') + '">' + value + '</span></div>';
}
function renderOverview(s) {
var c = s.core; if (!c) { return; }
var d = c.detection || {};
var g = c.geo || {}, n = c.network || {};
var typeLabel = { cloud: t('Cloud-Anbieter'), cdn: 'CDN', hosting: t('Hosting/Rechenzentrum'), isp: t('Privat / Business (ISP)'), unknown: t('Unbekannt') }[d.type] || d.type;
// Wichtigste Kennzahlen als Kacheln.
var hs = '';
hs += highlight(t('Risiko'), (s.risk ? s.risk.score : '–') + ' / 100', s.risk ? 'is-' + s.risk.level : '');
hs += highlight(t('IP-Version'), 'IPv' + c.ip_version);
hs += highlight(t('Standort'), g.country_code ? flag(g.country_code) + ' ' + esc(g.country_code) : '—');
hs += highlight(t('Einordnung'), esc(typeLabel));
if (s.blacklist && s.blacklist.supported) { hs += highlight(t('Blacklist'), s.blacklist.listed > 0 ? (s.blacklist.listed + '×') : t('sauber'), s.blacklist.listed > 0 ? 'is-bad' : 'is-ok'); }
if (s.mail && s.mail.has_mx) { var mok = s.mail.spf.present && s.mail.dmarc.present; hs += highlight('Mail', mok ? t('abgesichert') : t('Lücken'), mok ? 'is-ok' : 'is-warn'); }
if (s.url && s.url.ok) { hs += highlight('HTTPS', s.url.https ? t('aktiv') : t('fehlt'), s.url.https ? 'is-ok' : 'is-bad'); }
if (d.tor) { hs += highlight('Tor', t('Exit-Node'), 'is-bad'); }
if (d.proxy) { hs += highlight('Proxy/VPN', t('erkannt'), 'is-warn'); }
if (intelHasData(c.intel)) { var vm = { clean: [t('Unbedenklich'), 'is-ok'], suspicious: [t('Auffällig'), 'is-warn'], malicious: [t('Bösartig'), 'is-bad'] }[c.intel.verdict] || [t('Unbekannt'), 'is-neutral']; hs += highlight(t('Reputation'), vm[0], vm[1]); }
var rows = [
[t('Geprüfte IP'), '<strong>' + esc(c.target_ip) + '</strong> ' + badge('IPv' + c.ip_version, 'is-neutral') + (c.target_ips && c.target_ips.all.length > 1 ? ' <span class="smi-ipc__muted">' + sprintf(t('(+%s weitere)'), c.target_ips.all.length - 1) + '</span>' : '')],
c.input && c.input.domain ? [t('Domain'), esc(c.input.domain)] : null,
c.input && c.input.url ? [t('URL'), '<a href="' + esc(c.input.url) + '" target="_blank" rel="noopener nofollow">' + esc(c.input.url) + '</a>'] : null,
[t('Reverse DNS'), c.reverse_dns ? esc(c.reverse_dns) + ' ' + (c.fcrdns ? badge('FCrDNS ✓', 'is-ok') : (c.fcrdns === false ? badge(t('nicht bestätigt'), 'is-warn') : '')) : '<span class="smi-ipc__muted">' + t('nicht gesetzt') + '</span>', c.reverse_dns ? '' : t('Für Webseiten meist unkritisch, für Mailserver problematisch.')],
[t('Provider / ISP'), esc(n.org || g.isp || n.as_name || '')],
[t('ASN'), n.asn ? esc(n.asn + (n.as_name ? ' · ' + n.as_name : '')) : ''],
[t('Standort'), (g.country_code) ? flag(g.country_code) + ' ' + esc(countryName(g.country_code)) + (g.city ? ', ' + esc(g.city) : '') + (g.region ? ' (' + esc(g.region) + ')' : '') : ''],
[t('Einordnung'), badge(typeLabel, d.is_hosting ? 'is-warn' : 'is-neutral') + (d.tor ? ' ' + badge('Tor-Exit', 'is-bad') : '') + (d.proxy ? ' ' + badge('Proxy/VPN', 'is-warn') : '') + (d.mobile ? ' ' + badge(t('Mobilfunk'), 'is-neutral') : '')]
];
var expl = '';
if (d.tor) { expl += explain(t('Diese IP ist ein bekannter Tor-Exit-Node. Datenverkehr wird anonymisiert – bei sicherheitskritischen Diensten oft blockiert.'), 'warn'); }
else if (d.proxy) { expl += explain(t('Diese IP wird als Proxy/VPN eingestuft. Das kann legitim sein (Datenschutz), bei manchen Diensten aber Zusatzprüfungen auslösen.'), 'info'); }
if (d.is_hosting) { expl += explain(t('Die IP gehört vermutlich zu einem Rechenzentrum/Cloud-Anbieter. Das ist nicht automatisch gefährlich, kann aber bei Logins oder Mailversand auffällig wirken.'), 'info'); }
if (!c.reverse_dns) { expl += explain(t('Reverse DNS fehlt. Für normale Webseiten meist unkritisch, für Mailserver aber problematisch.'), 'info'); }
setPanel('overview', '<div class="smi-ipc__highlights">' + hs + '</div>' + card(t('Zusammenfassung der wichtigsten Daten'), kv(rows), 'clipboard-data') + expl);
}
function renderLocation(s) {
var c = s.core; if (!c) { return; }
var g = c.geo || {};
var rows = [
[t('Kontinent'), esc(g.continent)],
[t('Land'), g.country_code ? flag(g.country_code) + ' ' + esc(countryName(g.country_code)) + ' (' + esc(g.country_code) + ')' : ''],
[t('Region / Bundesland'), esc(g.region)],
[t('Stadt'), esc(g.city)],
[t('Postleitzahl'), esc(g.postal)],
[t('Zeitzone'), esc(g.timezone)],
[t('Koordinaten'), (g.latitude != null && g.longitude != null) ? esc(g.latitude.toFixed(4) + ', ' + g.longitude.toFixed(4)) : ''],
g.map_url ? [t('Karte'), '<a href="' + esc(g.map_url) + '" target="_blank" rel="noopener noreferrer">' + t('Auf OpenStreetMap ansehen') + ' ↗</a>'] : null,
[t('ISP'), esc(g.isp || (c.network && c.network.org))],
[t('Organisation'), esc((c.network && (c.network.org || c.network.netname)) || '')],
[t('Anschlusstyp'), esc({ cloud: t('Cloud'), cdn: 'CDN', hosting: t('Hosting/Rechenzentrum'), isp: t('Privat/Business (ISP)'), unknown: t('unbekannt') }[(c.detection || {}).type] || '') + ((c.detection || {}).mobile ? ' · ' + t('Mobilfunk') : '')]
];
setPanel('location', card(t('Standort & Provider'), kv(rows), 'geo-alt') + explain(t('IP-Standorte sind nur Näherungswerte und können vom tatsächlichen Aufenthaltsort abweichen.'), 'info'));
}
function renderNetwork(s) {
var c = s.core; if (!c) { return; }
var n = c.network || {}, w = c.whois_ip || {}, ro = c.routing || {};
var rpkiBadge = n.rpki === 'valid' ? badge('RPKI ' + t('gültig'), 'is-ok') : (n.rpki === 'invalid' ? badge('RPKI ' + t('ungültig'), 'is-bad') : (n.rpki === 'unknown' ? badge('RPKI ' + t('unbekannt'), 'is-neutral') : ''));
var announced = ro.announced != null ? ro.announced : (ro.visibility != null ? ro.visibility > 0 : null);
var rows = [
[t('ASN'), n.asn ? '<strong>' + esc(n.asn) + '</strong>' : ''],
[t('AS-Name'), esc(n.as_name)],
[t('Netzbereich (CIDR)'), esc(n.prefix) + (rpkiBadge ? ' ' + rpkiBadge : '')],
[t('Netzname'), esc(n.netname || w.netname)],
[t('Beschreibung'), esc(n.descr)],
[t('Organisation'), esc(n.org || w.org)],
[t('Zuständige Registry (RIR)'), esc(n.rir_name || n.rir)],
[t('Zuteilungsdatum'), esc(n.alloc_date)],
[t('Bereich'), (w.start && w.end) ? esc(w.start + ' – ' + w.end) : ''],
[t('Angekündigt (BGP)'), announced == null ? '' : (announced ? badge(t('sichtbar'), 'is-ok') + (ro.visibility != null ? ' <span class="smi-ipc__muted">' + sprintf(t('%s Peers'), ro.visibility) + '</span>' : '') : badge(t('nicht sichtbar'), 'is-warn'))],
ro.last_seen ? [t('Zuletzt gesehen'), esc(String(ro.last_seen).replace('T', ' ').replace(/:00$/, ''))] : null,
[t('Registriert'), esc(n.created)],
[t('Zuletzt geändert'), esc(n.modified)],
[t('Admin-C'), esc(n.admin_c)],
[t('Tech-C'), esc(n.tech_c)],
[t('Verwaltet von (mnt-by)'), esc(n.mnt_by)],
[t('Abuse-Kontakt'), n.abuse ? '<a href="mailto:' + esc(n.abuse) + '">' + esc(n.abuse) + '</a>' : '']
];
var raw = '';
if (s.whoisRaw && s.whoisRaw.length) {
raw = '<details class="smi-ipc__raw"><summary>' + t('WHOIS-Rohdaten anzeigen') + '</summary><pre>' + esc(s.whoisRaw.map(function (r) { return r.key + ': ' + r.value; }).join('\n')) + '</pre></details>';
}
setPanel('network', card(t('Netzwerk & WHOIS'), kv(rows), 'diagram-3') + raw);
}
function renderDns(s) {
// Ohne Domain (reine IP) gibt es keinen DNS-Inhalt → Panel leer (Tab ist ausgeblendet).
if (!s.core || !s.core.input || !s.core.input.domain) {
setPanel('dns', '');
return;
}
var d = s.dns; if (!d) { loading('dns'); return; }
function recTable(title, recs, fmt) {
if (!recs || !recs.length) { return ''; }
var h = '<h4 class="smi-ipc__sub">' + esc(title) + '</h4><table class="smi-ipc__table"><tbody>';
recs.forEach(function (r) { h += '<tr><td>' + fmt(r) + '</td><td class="smi-ipc__ttl">' + (r.ttl != null ? 'TTL ' + r.ttl : '') + '</td></tr>'; });
return h + '</tbody></table>';
}
var rec = d.records || {};
var html = '';
html += recTable(t('A (IPv4)'), rec.A, function (r) { return esc(r.value); });
html += recTable(t('AAAA (IPv6)'), rec.AAAA, function (r) { return esc(r.value); });
html += recTable('CNAME', rec.CNAME, function (r) { return esc(r.value); });
html += recTable('MX', rec.MX, function (r) { return esc(r.value); });
html += recTable('NS', rec.NS, function (r) { return esc(r.value); });
html += recTable('TXT', rec.TXT, function (r) { return '<code>' + esc(r.value) + '</code>'; });
if (rec.SOA && rec.SOA[0]) {
var so = rec.SOA[0];
html += '<h4 class="smi-ipc__sub">SOA</h4>' + kv([[t('Primärer NS'), esc(so.primary)], [t('Verantwortlich'), esc(so.email)], ['Serial', esc(so.serial)]]);
}
var meta = kv([
['SPF', d.spf ? '<code>' + esc(d.spf) + '</code>' : badge(t('fehlt'), 'is-warn')],
['DMARC', d.dmarc ? '<code>' + esc(d.dmarc) + '</code>' : badge(t('fehlt'), 'is-warn')]
]);
var fcr = '';
if (d.fcrdns && d.fcrdns.length) {
fcr = '<h4 class="smi-ipc__sub">' + t('Reverse-DNS-Abgleich') + '</h4>' + kv(d.fcrdns.map(function (f) {
return [f.ip, f.ptr ? esc(f.ptr) + ' ' + (f.confirmed ? badge('✓', 'is-ok') : badge('≠', 'is-warn')) : badge(t('kein PTR'), 'is-warn')];
}));
}
setPanel('dns', card(t('DNS-Einträge') + ' – ' + esc(d.domain), html || explain(t('Keine DNS-Einträge gefunden.'), 'info'), 'hdd-network') + card(t('E-Mail-Absicherung (DNS)'), meta + fcr, 'shield-lock'));
}
function renderSecurity(s) {
var r = state.risk || computeRisk(s);
var reasons = r.reasons.length
? '<ul class="smi-ipc__reasons">' + r.reasons.map(function (x) { return '<li>' + esc(x.text) + '</li>'; }).join('') + '</ul>'
: explain(t('Es wurden keine negativen Reputationssignale gefunden.'), 'ok');
var pos = [];
if (s.blacklist && s.blacklist.supported && s.blacklist.listed === 0) { pos.push(t('Auf keiner der geprüften Spam-Blacklists gelistet.')); }
if (s.core && s.core.reverse_dns && s.core.fcrdns) { pos.push(t('Reverse-DNS ist gesetzt und bestätigt (FCrDNS).')); }
if (s.url && s.url.https && s.url.cert_valid) { pos.push(t('Gültiges SSL-Zertifikat, HTTPS aktiv.')); }
var posHtml = pos.length ? '<ul class="smi-ipc__reasons smi-ipc__reasons--ok">' + pos.map(function (p) { return '<li>' + esc(p) + '</li>'; }).join('') + '</ul>' : '';
var urlCard = '';
if (s.url && s.url.ok) { urlCard = renderUrlCard(s.url); }
var intelCard = renderIntelCard(s.core && s.core.intel);
setPanel('security',
card(sprintf(t('Risiko-Score: %s / 100'), r.score) + ' – ' + r.label, reasons, 'shield-exclamation') +
intelCard +
(posHtml ? card(t('Positive Signale'), posHtml, 'check-circle') : '') +
urlCard);
}
function intelHasData(intel) {
return intel && intel.ok && ((intel.providers && intel.providers.length > 0) || intel.blacklisted || (intel.verdict && intel.verdict !== 'unknown'));
}
function renderIntelCard(intel) {
if (!intelHasData(intel)) { return ''; }
var verdictMap = { clean: [t('Unbedenklich'), 'is-ok'], suspicious: [t('Auffällig'), 'is-warn'], malicious: [t('Bösartig'), 'is-bad'], unknown: [t('Unbekannt'), 'is-neutral'] };
var vm = verdictMap[intel.verdict] || verdictMap.unknown;
var head = kv([
[t('Gesamt-Verdict'), badge(vm[0], vm[1])],
intel.score != null ? [t('Reputations-Score'), '<strong>' + esc(intel.score) + '</strong> / 100'] : null,
[t('Blacklist (Gateway)'), intel.blacklisted ? badge(t('gelistet'), 'is-bad') + (intel.blacklist && intel.blacklist.reason ? ' <span class="smi-ipc__muted">' + esc(intel.blacklist.reason) + '</span>' : '') : badge(t('nicht gelistet'), 'is-ok')]
]);
var prov = '';
if (intel.providers && intel.providers.length) {
prov = '<h4 class="smi-ipc__sub">' + t('Anbieter-Signale') + '</h4><table class="smi-ipc__table"><tbody>' + intel.providers.map(function (p) {
var flags = [];
if (p.is_tor) { flags.push(badge('Tor', 'is-bad')); }
if (p.is_vpn) { flags.push(badge('VPN', 'is-warn')); }
if (p.is_proxy) { flags.push(badge('Proxy', 'is-warn')); }
if (p.is_hosting) { flags.push(badge(t('Hosting'), 'is-neutral')); }
// abuse.ch/Threat-Feeds: positiver Score = Malware-/C2-Treffer.
if (/abusech|threatfox|feodo/i.test(p.provider) && p.score != null && p.score > 0) { flags.unshift(badge(t('Malware-C2'), 'is-bad')); }
var scoreHtml = '';
if (p.score != null) {
var sc = p.score >= 75 ? 'is-bad' : (p.score >= 25 ? 'is-warn' : 'is-ok');
scoreHtml = badge(esc(p.score) + '/100', sc);
}
var val = p.error ? badge(t('nicht erreichbar'), 'is-warn') : (scoreHtml + ' ' + flags.join(' ') + (p.country ? ' ' + flag(p.country) : '')).trim();
return '<tr><td>' + esc(providerLabel(p.provider)) + '</td><td>' + (val || '<span class="smi-ipc__muted">' + t('keine Auffälligkeiten') + '</span>') + '</td></tr>';
}).join('') + '</tbody></table>';
}
return card(t('Externe IP-Reputation (VPN-geroutet)'), head + prov, 'shield-fill-check') + explain(t('Diese Werte stammen aus dem hauseigenen IP-Intel-Gateway (mehrere Reputationsdienste über VPN-Egress). Blacklist-Treffer und ein hoher Score erhöhen das Gesamt-Risiko.'), 'info');
}
function renderUrlCard(u) {
var rows = [
[t('Finale URL'), '<a href="' + esc(u.final_url) + '" target="_blank" rel="noopener noreferrer nofollow">' + esc(u.final_url) + '</a>'],
[t('HTTP-Status'), esc(u.final_status)],
[t('Weiterleitungen'), esc(u.redirects)],
['HTTPS', yesno(u.https)],
[t('SSL-Zertifikat'), u.cert && u.cert.issuer ? esc(u.cert.issuer) + (u.cert_days_left != null ? ' · ' + sprintf(t('%s Tage gültig'), u.cert_days_left) : '') + ' ' + (u.cert_valid ? badge('✓', 'is-ok') : badge('✗', 'is-bad')) : ''],
['HSTS', yesno(u.hsts)],
[t('Server'), esc(u.server)],
[t('Ladezeit'), u.time_ms != null ? esc(u.time_ms) + ' ms' : '']
];
var sec = '<h4 class="smi-ipc__sub">Security-Header</h4><table class="smi-ipc__table"><tbody>';
Object.keys(u.security).forEach(function (k) {
sec += '<tr><td><code>' + esc(k) + '</code></td><td>' + (u.security[k] ? badge('✓', 'is-ok') : badge('—', 'is-warn')) + '</td></tr>';
});
sec += '</tbody></table>';
var warn = !u.https ? explain(t('Diese Seite nutzt kein HTTPS. Eingaben können unverschlüsselt übertragen werden.'), 'warn') : '';
return card(t('URL-/HTTP-/TLS-Analyse'), kv(rows) + sec + warn, 'globe2');
}
function renderBlacklist(s) {
var b = s.blacklist;
if (!b) { loading('blacklist'); return; }
if (!b.supported) { setPanel('blacklist', card(t('Blacklist-Check'), explain(t('Blacklist-Prüfung ist nur für öffentliche IPv4-Adressen verfügbar.'), 'info'), 'list-check')); return; }
var head = '<div class="smi-ipc__blsum">' + badge(sprintf(t('%s geprüft'), b.checked), 'is-neutral')
+ ' ' + (b.listed > 0 ? badge(sprintf(t('%s gelistet'), b.listed), 'is-bad') : badge(t('nicht gelistet'), 'is-ok'))
+ (b.failed > 0 ? ' ' + badge(sprintf(t('%s fehlgeschlagen'), b.failed), 'is-warn') : '') + '</div>';
var rows = '<table class="smi-ipc__table smi-ipc__bltable"><thead><tr><th>' + t('Liste') + '</th><th>' + t('Status') + '</th></tr></thead><tbody>';
b.results.forEach(function (r) {
var st = r.status === 'listed'
? badge(t('gelistet'), 'is-bad') + (r.site ? ' <a href="' + esc(r.site) + '" target="_blank" rel="noopener noreferrer">' + t('Delisting') + ' ↗</a>' : '')
: (r.status === 'error' ? badge(t('Prüfung fehlgeschlagen'), 'is-warn') : badge(t('sauber'), 'is-ok'));
rows += '<tr><td>' + esc(r.name) + '<br><small class="smi-ipc__muted">' + esc(r.zone) + '</small></td><td>' + st + (r.reason ? '<br><small class="smi-ipc__muted">' + esc(r.reason) + '</small>' : '') + '</td></tr>';
});
rows += '</tbody></table>';
var note = b.listed > 0
? explain(t('Einträge können veraltet sein (False Positives). Nutze die Delisting-Links, um eine Entfernung zu beantragen.'), 'warn')
: explain(t('Deine/diese IP ist auf keiner der geprüften Spam-Blacklists gelistet.'), 'ok');
setPanel('blacklist', card(t('Spam-/Blacklist-Prüfung'), head + rows, 'list-check') + note);
}
function renderMail(s) {
if (!s.core || !s.core.input || !s.core.input.domain) {
setPanel('mail', '');
return;
}
var m = s.mail; if (!m) { loading('mail'); return; }
if (!m.has_mx) { setPanel('mail', card(t('Mailserver-Check'), explain(t('Diese Domain hat keinen MX-Record und empfängt vermutlich keine E-Mails.'), 'info'), 'envelope')); return; }
var mx = '<h4 class="smi-ipc__sub">MX-Server</h4><table class="smi-ipc__table"><tbody>' + m.mx.map(function (x) {
return '<tr><td>' + esc(x.pri) + ' ' + esc(x.host) + '</td><td>' + (x.resolves ? badge(t('erreichbar'), 'is-ok') : badge(t('löst nicht auf'), 'is-bad')) + '</td></tr>';
}).join('') + '</tbody></table>';
var rows = kv([
['SPF', m.spf.present ? (m.spf.too_open ? badge(t('vorhanden, aber zu offen'), 'is-warn') : badge(t('vorhanden'), 'is-ok')) : badge(t('fehlt'), 'is-bad'), m.spf.present ? '' : t('Ohne SPF ist Absender-Fälschung leichter möglich.')],
['DKIM', m.dkim.found ? badge(sprintf(t('Hinweis gefunden (Selektor %s)'), m.dkim.selector), 'is-ok') : badge(t('nicht gefunden'), 'is-warn')],
['DMARC', m.dmarc.present ? badge('p=' + (m.dmarc.policy || '?'), m.dmarc.policy === 'reject' ? 'is-ok' : (m.dmarc.policy === 'none' ? 'is-warn' : 'is-neutral')) : badge(t('fehlt'), 'is-bad'), m.dmarc.present ? '' : t('Ohne DMARC lassen sich gefälschte E-Mails schwerer verhindern.')],
['MTA-STS', yesno(m.mta_sts.present)],
['TLS-RPT', yesno(m.tls_rpt.present)],
['BIMI', yesno(m.bimi.present)],
['DNSSEC', m.dnssec == null ? '' : yesno(m.dnssec)]
]);
var warns = (m.warnings || []).map(function (w) { return explain(w.text, w.level === 'error' ? 'warn' : (w.level === 'warn' ? 'warn' : 'info')); }).join('');
setPanel('mail', card(t('Mailserver & E-Mail-Sicherheit') + ' – ' + esc(m.domain), mx + rows, 'envelope-check') + warns);
}
function renderDetails(s) {
var c = s.core || {};
var b = navigator || {};
var d = c.detection || {};
function tri(v) { return v == null ? '<span class="smi-ipc__muted">' + t('unbekannt') + '</span>' : yesno(v); }
var rows = [
[t('Zeitpunkt der Prüfung'), esc(new Date().toLocaleString(CFG.lang || 'de'))],
[t('Alle Ziel-IPs'), c.target_ips ? esc((c.target_ips.all || []).join(', ')) : ''],
[t('Proxy / VPN erkannt'), tri(d.proxy)],
[t('Mobilfunk-Anschluss'), tri(d.mobile)],
[t('Rechenzentrum / Hosting'), tri(d.hosting_flag != null ? d.hosting_flag : d.is_hosting)],
[t('Tor-Exit-Node'), tri(d.tor)],
[t('Browser-Sprache'), esc((b.language || '') + (b.languages && b.languages.length > 1 ? ' (' + b.languages.join(', ') + ')' : ''))],
[t('User-Agent'), '<code class="smi-ipc__ua">' + esc(b.userAgent || '') + '</code>'],
[t('Plattform'), esc(b.platform || '')]
];
var raw = '';
if (s.url && s.url.chain) {
raw = card(t('Redirect-Kette'), '<ol class="smi-ipc__chain">' + s.url.chain.map(function (h) { return '<li>' + esc(h.url) + ' → ' + (h.ok ? esc(h.status) : esc(h.error)) + '</li>'; }).join('') + '</ol>', 'arrow-repeat');
}
setPanel('details', card(t('Technische Details'), kv(rows), 'gear') + raw);
}
function renderAll(s) {
renderScore(s);
renderOverview(s);
renderLocation(s);
renderNetwork(s);
renderDns(s);
renderSecurity(s);
renderBlacklist(s);
renderMail(s);
renderDetails(s);
}
// ---- Orchestrierung -----------------------------------------------------
function mod(name) { return CFG.modules[name] !== false; }
function assignInto(dst, src) { if (src) { for (var k in src) { if (Object.prototype.hasOwnProperty.call(src, k)) { dst[k] = src[k]; } } } return dst; }
function resetScoreCard(target) {
var sn = document.getElementById('smi-ipc-score-num'); if (sn) { sn.textContent = '…'; }
var sb = document.getElementById('smi-ipc-score-badge'); if (sb) { sb.textContent = ''; sb.className = 'smi-ipc__score-badge'; }
var st = document.getElementById('smi-ipc-score-target'); if (st) { st.textContent = target; }
var sd = document.getElementById('smi-ipc-score-desc'); if (sd) { sd.textContent = t('Analyse läuft …'); }
var sbox = document.getElementById('smi-ipc-score'); if (sbox) { sbox.removeAttribute('data-level'); }
var g = document.getElementById('smi-ipc-gauge'); if (g) { g.style.strokeDashoffset = 2 * Math.PI * 52; }
var pdfB = document.getElementById('smi-ipc-pdf'); if (pdfB) { pdfB.hidden = true; }
}
// ---- Scan-Anzeige (animiert) --------------------------------------------
var elScan = document.getElementById('smi-ipc-scan');
var elScoreCard = document.getElementById('smi-ipc-scorecard');
function scanStart(target, checks) {
if (!elScan) { return; }
var items = checks.map(function (c) {
return '<li data-check="' + esc(c.key) + '"><span class="smi-ipc__check-ico" aria-hidden="true"></span><span>' + esc(c.label) + '</span></li>';
}).join('');
elScan.innerHTML =
'<div class="smi-ipc__scanner">' +
'<div class="smi-ipc__radar" aria-hidden="true"><span class="smi-ipc__radar-grid"></span><span class="smi-ipc__radar-sweep"></span><span class="smi-ipc__radar-dot"></span></div>' +
'<div class="smi-ipc__scan-info">' +
'<h2 class="smi-ipc__scan-title">' + t('Scanne') + ' <b>' + esc(target) + '</b> …</h2>' +
'<ul class="smi-ipc__scan-list">' + items + '</ul>' +
'</div>' +
'</div>';
elScan.hidden = false;
if (elScoreCard) { elScoreCard.hidden = true; }
}
function scanStep(key, status) {
if (!elScan) { return; }
var li = elScan.querySelector('li[data-check="' + key + '"]');
if (li) { li.classList.remove('is-running', 'is-done', 'is-failed'); li.classList.add('is-' + status); }
}
function scanEnd() {
if (elScan) { elScan.hidden = true; elScan.innerHTML = ''; }
if (elScoreCard) { elScoreCard.hidden = false; elScoreCard.classList.remove('is-reveal'); void elScoreCard.offsetWidth; elScoreCard.classList.add('is-reveal'); }
}
// ---- Orchestrierung: auflösen → parallele Einzelabfragen ----------------
function analyze(query, isSelf) {
clearError();
elResults.hidden = false;
progressStart(t('Löse Ziel auf …'));
['overview', 'location', 'network', 'dns', 'security', 'blacklist', 'mail', 'details'].forEach(loading);
setTabVisible('dns', false); setTabVisible('mail', false);
activateTab(tabs[0]);
state = {};
resetScoreCard(isSelf ? t('Deine IP-Adresse') : query);
if (elScoreCard) { elScoreCard.hidden = false; }
elResults.scrollIntoView({ behavior: 'smooth', block: 'start' });
var rp = isSelf ? { self: '1', lang: CFG.lang || 'de' } : { q: query, lang: CFG.lang || 'de' };
api('resolve.php', rp).then(function (rez) {
state.core = {
input: rez.input, target_ip: rez.target_ip, ip_version: rez.ip_version,
target_ips: rez.target_ips, reverse_dns: rez.reverse_dns, fcrdns: rez.fcrdns, is_self: rez.is_self,
geo: {}, network: {}, detection: {}, whois_ip: {}, routing: {}, intel: null
};
if (isSelf) { state.myip = rez.target_ip; }
var domain = rez.input.domain, ip = rez.target_ip, isV4 = rez.ip_version === 4;
setTabVisible('dns', !!domain); setTabVisible('mail', !!domain);
renderOverview(state); renderLocation(state); renderNetwork(state); renderDetails(state);
// Scan-Checkliste je nach Eingabetyp.
var checks = [];
if (mod('geo')) { checks.push({ key: 'geo', label: t('Standort & Provider') }); }
if (mod('asn')) { checks.push({ key: 'network', label: t('Netzwerk & ASN') }); }
if (mod('rdap')) { checks.push({ key: 'whois', label: t('WHOIS & Routing') }); }
if (CFG.intel) { checks.push({ key: 'reputation', label: t('IP-Reputation') }); }
if (mod('blacklist') && isV4) { checks.push({ key: 'blacklist', label: t('Spam-Blacklists') }); }
if (domain && mod('dns')) { checks.push({ key: 'dns', label: t('DNS-Einträge') }); }
if (domain && mod('mail')) { checks.push({ key: 'mail', label: t('Mailserver') }); }
if (rez.input.type === 'url' && rez.input.url && mod('url')) { checks.push({ key: 'url', label: t('URL & TLS') }); }
scanStart(domain || rez.input.host || ip, checks);
var total = checks.length, done = 0;
function stepDone(key, ok) { scanStep(key, ok ? 'done' : 'failed'); done++; progressSet(done, total, done >= total ? t('Auswertung …') : sprintf(t('Scanne Datenpunkte … %s/%s'), done, total)); }
var tasks = [];
function add(key, promise, onData) {
scanStep(key, 'running');
tasks.push(promise.then(function (d) { try { onData(d); } catch (e) {} stepDone(key, true); }, function () { stepDone(key, false); }));
}
if (mod('geo')) { add('geo', api('geoloc.php', { ip: ip, lang: CFG.lang || 'de' }), function (d) { state.core.geo = d.geo; assignInto(state.core.detection, d.detection); renderLocation(state); renderDetails(state); renderOverview(state); }); }
if (mod('asn')) { add('network', api('network.php', { ip: ip }), function (d) { assignInto(state.core.network, d.network); assignInto(state.core.detection, d.detection); renderNetwork(state); renderOverview(state); renderScore(state); }); }
if (mod('rdap')) { add('whois', api('ipwhois.php', { ip: ip }), function (d) { mergeWhois(d); renderNetwork(state); renderOverview(state); }); }
if (CFG.intel) { add('reputation', api('reputation.php', { ip: ip }), function (d) { state.core.intel = d.intel; applyIntelDetection(); renderScore(state); renderSecurity(state); renderOverview(state); }); }
if (mod('blacklist') && isV4) { add('blacklist', api('blacklist.php', { ip: ip }), function (d) { state.blacklist = d; renderBlacklist(state); renderScore(state); renderSecurity(state); renderOverview(state); }); }
else { setPanel('blacklist', card(t('Blacklist-Check'), explain(t('Blacklist-Prüfung ist nur für öffentliche IPv4-Adressen verfügbar.'), 'info'), 'list-check')); }
if (domain) {
if (mod('dns')) { add('dns', api('dnsx.php', { domain: domain }), function (d) { state.dns = d; renderDns(state); }); } else { renderDns(state); }
if (mod('mail')) { add('mail', api('mailsec.php', { domain: domain }), function (d) { state.mail = d; renderMail(state); renderScore(state); renderSecurity(state); renderOverview(state); }); } else { renderMail(state); }
} else { renderDns(state); renderMail(state); }
if (rez.input.type === 'url' && rez.input.url && mod('url')) { add('url', api('urlcheck.php', { url: rez.input.url }), function (d) { state.url = d; renderScore(state); renderSecurity(state); renderDetails(state); renderOverview(state); }); }
return Promise.all(tasks);
}).then(function () {
scanEnd();
renderScore(state); renderSecurity(state); renderOverview(state);
progressDone();
var pdfB = document.getElementById('smi-ipc-pdf'); if (pdfB && CFG.pdf) { pdfB.hidden = false; }
}).catch(function (e) {
progressHide(); scanEnd(); elResults.hidden = true;
showError(e.message || t('Es ist ein Fehler aufgetreten.'));
});
}
function mergeWhois(d) {
var w = d.whois || {}, c = state.core;
c.whois_ip = { netname: w.netname, org: w.org, country: w.country, start: w.start, end: w.end, abuse: w.abuse };
assignInto(c.network, {
org: w.org || c.network.org, netname: w.netname || c.network.netname, descr: w.descr,
admin_c: w.admin_c, tech_c: w.tech_c, mnt_by: w.mnt_by, created: w.created, modified: w.modified,
abuse: w.abuse || c.network.abuse, rpki: w.rpki, route: w.route, country: c.network.country || w.country
});
c.routing = d.routing || {};
}
function applyIntelDetection() {
var it = state.core.intel; if (!it || !it.ok) { return; }
(it.providers || []).forEach(function (p) {
if (p.is_tor) { state.core.detection.tor = true; }
if (p.is_vpn || p.is_proxy) { state.core.detection.proxy = true; }
if (p.is_hosting) { state.core.detection.is_hosting = true; }
});
}
// ---- Events -------------------------------------------------------------
elForm.addEventListener('submit', function (e) {
e.preventDefault();
var q = elInput.value.trim();
if (!q) { showError(t('Bitte gib eine IP-Adresse, Domain oder URL ein.')); return; }
analyze(q, false);
});
elSelf.addEventListener('click', function () {
if (state.myip) { elInput.value = state.myip; }
analyze(null, true);
});
// Eigene IP beim Laden ermitteln (nur Anzeige, schneller resolve-Endpoint).
api('resolve.php', { self: '1' }).then(function (core) {
state.myip = core.target_ip;
elMyip.innerHTML = '<i class="bi bi-hdd-network" aria-hidden="true"></i> ' + t('Deine öffentliche IP:') + ' <strong>' + esc(core.target_ip) + '</strong> ' + badge('IPv' + core.ip_version, 'is-neutral');
}).catch(function () {});
// Auto-Scan über ?scan=<ziel> (z. B. aus dem PDF-QR-Code „diese IP neu scannen").
(function () {
try {
var m = /[?&]scan=([^&]+)/.exec(location.search);
if (m) {
var q = decodeURIComponent(m[1].replace(/\+/g, ' ')).trim().slice(0, 2048);
if (q) { elInput.value = q; analyze(q, false); }
}
} catch (e) {}
})();
// PDF-Bericht: echtes PDF via jsPDF (lokal gebündelt, lazy geladen).
var pdfBtn = document.getElementById('smi-ipc-pdf');
if (pdfBtn) { pdfBtn.addEventListener('click', downloadPdf); }
function loadScript(src) {
return new Promise(function (resolve, reject) {
var el = document.createElement('script'); el.src = src;
el.onload = resolve; el.onerror = function () { reject(new Error('load ' + src)); };
document.head.appendChild(el);
});
}
var _pdfLibReady = false;
function ensurePdfLib() {
if (_pdfLibReady && window.jspdf && window.jspdf.jsPDF) { return Promise.resolve(); }
var base = CFG.vendorUrl || '';
return loadScript(base + 'jspdf.umd.min.js')
.then(function () { return loadScript(base + 'jspdf.plugin.autotable.min.js'); })
.then(function () { return loadScript(base + 'qrcode.min.js').catch(function () {}); })
.then(function () { _pdfLibReady = true; });
}
// QR-Code in ein PDF zeichnen (schwarze Module auf weißem Grund).
function drawQr(doc, text, x, y, size) {
if (!window.qrcode) { return; }
try {
var qr = window.qrcode(0, 'M'); qr.addData(String(text)); qr.make();
var n = qr.getModuleCount(), cell = size / n;
doc.setFillColor(0, 0, 0);
for (var r = 0; r < n; r++) { for (var c = 0; c < n; c++) { if (qr.isDark(r, c)) { doc.rect(x + c * cell, y + r * cell, cell + 0.03, cell + 0.03, 'F'); } } }
} catch (e) {}
}
function qrBox(doc, text, x, y, size, pad) {
pad = pad == null ? 1.6 : pad;
doc.setFillColor(255, 255, 255); doc.roundedRect(x - pad, y - pad, size + 2 * pad, size + 2 * pad, 1.2, 1.2, 'F');
drawQr(doc, text, x, y, size);
}
function toolUrl() { return location.origin + location.pathname; }
function downloadPdf() {
if (!state.core) { return; }
var b = pdfBtn, old = b ? b.innerHTML : '';
if (b) { b.disabled = true; b.innerHTML = '<i class="bi bi-hourglass-split" aria-hidden="true"></i> ' + t('PDF wird erstellt …'); }
ensurePdfLib().then(buildPdf).catch(function () { showError(t('PDF-Erstellung fehlgeschlagen.')); })
.then(function () { if (b) { b.disabled = false; b.innerHTML = old; } });
}
function typeLabelPdf(d) {
return ({ cloud: 'Cloud-Anbieter', cdn: 'CDN', hosting: 'Hosting/Rechenzentrum', isp: 'Privat/Business (ISP)', unknown: 'unbekannt' }[d.type] || d.type || '') + (d.tor ? ' · Tor-Exit' : '') + (d.proxy ? ' · Proxy/VPN' : '');
}
function buildSummary(s, r) {
var c = s.core, g = c.geo || {}, n = c.network || {}, d = c.detection || {}, parts = [];
parts.push('Die geprüfte Adresse ' + c.target_ip + ' gehört zu ' + (n.org || n.as_name || 'einem unbekannten Netz') + (g.country_code ? ' und wird ' + (g.city ? g.city + ', ' : '') + countryName(g.country_code) + ' zugeordnet' : '') + '.');
parts.push('Einordnung: ' + typeLabelPdf(d) + '.');
if (s.blacklist && s.blacklist.supported) { parts.push(s.blacklist.listed > 0 ? ('Sie ist auf ' + s.blacklist.listed + ' von ' + s.blacklist.checked + ' geprüften Spam-Blacklists gelistet.') : ('Sie ist auf keiner der ' + s.blacklist.checked + ' geprüften Spam-Blacklists gelistet.')); }
parts.push('Der Gesamt-Risiko-Score liegt bei ' + r.score + '/100 – Einstufung: ' + r.label + '.');
parts.push({ green: 'Kurz gesagt: hier ist alles im grünen Bereich.', yellow: 'Kurz gesagt: nichts Dramatisches, aber ein Blick auf die Details lohnt sich.', orange: 'Kurz gesagt: diese Adresse solltest du im Auge behalten.', red: 'Kurz gesagt: hier ist Vorsicht geboten.' }[r.level]);
return parts.join(' ');
}
function buildRecommendations(s) {
var c = s.core, d = c.detection || {}, out = [];
if (s.blacklist && s.blacklist.listed > 0) { out.push('Diese IP ist auf Spam-Blacklists gelistet. Kläre die Ursache (offener Mailserver, kompromittiertes Gerät) und nutze die Delisting-Formulare der betroffenen Listen.'); }
if (!c.reverse_dns) { out.push('Es ist kein Reverse-DNS (PTR) gesetzt. Für Mailserver unbedingt einrichten – sonst landen E-Mails schneller im Spam.'); }
if (s.mail && s.mail.has_mx) {
if (!s.mail.spf.present) { out.push('Kein SPF-Record: SPF einrichten, um Absender-Fälschung zu erschweren.'); }
if (!s.mail.dmarc.present) { out.push('Kein DMARC-Record: DMARC ergänzen (mindestens p=quarantine), um Spoofing zu verhindern.'); }
else if (s.mail.dmarc.policy === 'none') { out.push('DMARC steht auf p=none (nur Monitoring). Verschärfe die Policy auf quarantine oder reject.'); }
}
if (s.url && s.url.ok && !s.url.https) { out.push('Die geprüfte URL nutzt kein HTTPS. Stelle auf verschlüsselte Verbindungen (TLS) um.'); }
if (d.tor || d.proxy) { out.push('Die IP wird als Tor/Proxy/VPN erkannt. Bei Logins oder Mailversand kann das Zusatzprüfungen oder Blockaden auslösen.'); }
if (!out.length) { out.push('Kein akuter Handlungsbedarf – die Adresse ist unauffällig. Schütze deine eigene Identität trotzdem mit starken Passwörtern und einem VPN.'); }
out.push('Mehr kostenlose Sicherheits-Tools findest du auf www.safemyident.de.');
return out;
}
function buildPdf() {
var s = state, c = s.core, g = c.geo || {}, n = c.network || {}, d = c.detection || {};
var r = s.risk || computeRisk(s);
var doc = new window.jspdf.jsPDF({ unit: 'mm', format: 'a4' });
var W = 210, M = 14, CW = W - 2 * M;
var COL = { navy: [20, 36, 60], accent: [255, 194, 71], ink: [26, 35, 51], muted: [110, 125, 150], line: [224, 229, 238], soft: [247, 248, 251], green: [46, 190, 115], yellow: [224, 170, 30], orange: [240, 150, 20], red: [233, 70, 90] };
var lvl = { green: COL.green, yellow: COL.yellow, orange: COL.orange, red: COL.red }[r.level];
var sF = function (a) { doc.setFillColor(a[0], a[1], a[2]); };
var sT = function (a) { doc.setTextColor(a[0], a[1], a[2]); };
var sD = function (a) { doc.setDrawColor(a[0], a[1], a[2]); };
var target = (c.input && (c.input.domain || c.input.url)) || c.target_ip;
// Header-Band.
sF(COL.navy); doc.rect(0, 0, W, 30, 'F');
sT(COL.accent); doc.setFont('helvetica', 'bold'); doc.setFontSize(17); doc.text('SafeMyIdent.de', M, 14);
sT([255, 255, 255]); doc.setFont('helvetica', 'normal'); doc.setFontSize(8.5); doc.text('IP-, WHOIS-, DNS- & Sicherheitsanalyse', M, 20);
// QR oben rechts → zum IP-Checker.
var hqr = 17, hqrX = W - M - hqr, hqrY = 6;
qrBox(doc, toolUrl(), hqrX, hqrY, hqr, 1.3);
sT([255, 255, 255]); doc.setFont('helvetica', 'bold'); doc.setFontSize(11.5); doc.text('IP-SICHERHEITSBERICHT', hqrX - 4, 13, { align: 'right' });
doc.setFont('helvetica', 'normal'); doc.setFontSize(7.5); sT([180, 195, 215]); doc.text(new Date().toLocaleString('de-DE'), hqrX - 4, 19, { align: 'right' });
doc.setFontSize(6.5); doc.text('Zum Checker »', hqrX + hqr / 2, hqrY + hqr + 3, { align: 'center' });
// Clickbait-Headline.
var headlines = { green: 'Entwarnung: Alles im grünen Bereich', yellow: 'Ein paar Dinge solltest du wissen', orange: 'Achtung: erhöhtes Risiko erkannt', red: 'Kritisch! Diese Adresse fällt negativ auf' };
var y = 42;
sT(COL.ink); doc.setFont('helvetica', 'bold'); doc.setFontSize(17);
var hl = doc.splitTextToSize(headlines[r.level] || r.label, CW); doc.text(hl, M, y); y += hl.length * 7 + 2;
sT(COL.muted); doc.setFont('helvetica', 'normal'); doc.setFontSize(10.5);
doc.text('Geprüftes Ziel: ' + target, M, y); y += 8;
// Score-Hero.
var hY = y, hH = 30;
sD(COL.line); sF(COL.soft); doc.roundedRect(M, hY, CW, hH, 3, 3, 'FD');
var cx = M + 18, cy = hY + hH / 2;
sF(lvl); doc.circle(cx, cy, 12, 'F');
sT([255, 255, 255]); doc.setFont('helvetica', 'bold'); doc.setFontSize(18); doc.text(String(r.score), cx, cy + 1, { align: 'center' });
doc.setFont('helvetica', 'normal'); doc.setFontSize(6.5); doc.text('von 100', cx, cy + 6, { align: 'center' });
var tx = M + 36;
sT(COL.ink); doc.setFont('helvetica', 'bold'); doc.setFontSize(13); doc.text('Risiko: ' + r.label, tx, hY + 10);
var barY = hY + 15, barW = CW - (tx - M) - 8;
sF([230, 233, 240]); doc.roundedRect(tx, barY, barW, 4, 2, 2, 'F');
sF(lvl); doc.roundedRect(tx, barY, Math.max(4, barW * r.score / 100), 4, 2, 2, 'F');
sT(COL.muted); doc.setFont('helvetica', 'normal'); doc.setFontSize(8.5); doc.text('0 = unauffällig · 100 = kritisch', tx, barY + 9);
y = hY + hH + 7;
// Executive Summary.
sT(COL.ink); doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.text('Das Wichtigste in Kürze', M, y); y += 5;
sT([70, 80, 100]); doc.setFont('helvetica', 'normal'); doc.setFontSize(9.5);
var sum = doc.splitTextToSize(buildSummary(s, r), CW); doc.text(sum, M, y); y += sum.length * 4.6 + 5;
function section(title, rows) {
rows = rows.filter(function (x) { return x && x[1] !== '' && x[1] != null; });
if (!rows.length) { return; }
doc.autoTable({
startY: y, head: [[{ content: title, colSpan: 2 }]], body: rows, theme: 'grid',
styles: { fontSize: 9, cellPadding: 1.8, textColor: COL.ink, lineColor: COL.line, lineWidth: 0.1, overflow: 'linebreak' },
headStyles: { fillColor: COL.navy, textColor: [255, 255, 255], fontSize: 10, fontStyle: 'bold', cellPadding: 2.2, halign: 'left' },
alternateRowStyles: { fillColor: [250, 251, 253] },
columnStyles: { 0: { fontStyle: 'bold', textColor: COL.muted, cellWidth: 52 }, 1: { cellWidth: 'auto' } },
margin: { left: M, right: M }
});
y = doc.lastAutoTable.finalY + 5;
}
section('Standort & Provider', [
['Land', g.country_code ? countryName(g.country_code) + ' (' + g.country_code + ')' : ''],
['Region / Stadt', [g.region, g.city].filter(Boolean).join(' / ')],
['Zeitzone', g.timezone], ['ISP / Organisation', n.org || g.isp || n.as_name],
['Einordnung', typeLabelPdf(d)]
]);
section('Netzwerk & WHOIS', [
['Geprüfte IP', c.target_ip + ' (IPv' + c.ip_version + ')'],
['Reverse DNS', c.reverse_dns ? c.reverse_dns + (c.fcrdns ? ' (bestätigt)' : '') : 'nicht gesetzt'],
['ASN', (n.asn || '') + (n.as_name ? ' – ' + n.as_name : '')],
['Netzbereich (CIDR)', n.prefix], ['Registry (RIR)', n.rir_name || n.rir], ['Abuse-Kontakt', n.abuse]
]);
section('Sicherheit & Reputation', [
['Reputations-Score', r.score + ' / 100 – ' + r.label],
(s.blacklist && s.blacklist.supported) ? ['Blacklist', s.blacklist.listed > 0 ? (s.blacklist.listed + ' von ' + s.blacklist.checked + ' Listen') : ('auf keiner von ' + s.blacklist.checked + ' Listen')] : null,
(c.intel && c.intel.ok && c.intel.verdict !== 'unknown') ? ['Externe Reputation', c.intel.verdict + (c.intel.score != null ? ' (' + c.intel.score + '/100)' : '')] : null
]);
// Bewertungsgründe.
if (y > 250) { doc.addPage(); y = 20; }
sT(COL.ink); doc.setFont('helvetica', 'bold'); doc.setFontSize(9.5); doc.text('Bewertungsgründe:', M, y); y += 4.5;
sT([70, 80, 100]); doc.setFont('helvetica', 'normal'); doc.setFontSize(9);
(r.reasons.length ? r.reasons : [{ text: 'Keine negativen Signale gefunden.' }]).forEach(function (x) {
var ln = doc.splitTextToSize('• ' + x.text, CW - 2); doc.text(ln, M + 1, y); y += ln.length * 4.3;
});
y += 4;
if (s.mail && s.mail.has_mx) {
section('Mailserver-Sicherheit (' + (s.mail.domain || '') + ')', [
['SPF', s.mail.spf.present ? (s.mail.spf.too_open ? 'vorhanden, zu offen' : 'vorhanden') : 'fehlt'],
['DKIM', s.mail.dkim.found ? 'Hinweis gefunden' : 'nicht gefunden'],
['DMARC', s.mail.dmarc.present ? 'p=' + (s.mail.dmarc.policy || '?') : 'fehlt'],
['MTA-STS', s.mail.mta_sts.present ? 'ja' : 'nein'],
['DNSSEC', s.mail.dnssec == null ? '' : (s.mail.dnssec ? 'ja' : 'nein')]
]);
}
if (s.url && s.url.ok) {
section('URL / TLS', [
['Finale URL', s.url.final_url], ['HTTP-Status', String(s.url.final_status)],
['HTTPS', s.url.https ? 'aktiv' : 'fehlt'],
['SSL-Zertifikat', (s.url.cert && s.url.cert.issuer) ? (s.url.cert.issuer + (s.url.cert_days_left != null ? ' (' + s.url.cert_days_left + ' Tage gültig)' : '')) : ''],
['HSTS', s.url.hsts ? 'ja' : 'nein']
]);
}
// Empfehlungen-Box.
var recs = buildRecommendations(s);
var boxH = 11 + recs.reduce(function (a, rc) { return a + doc.splitTextToSize('• ' + rc, CW - 8).length * 4.6; }, 0) + 3;
if (y + boxH > 280) { doc.addPage(); y = 20; }
sF([255, 248, 230]); sD(COL.accent); doc.setLineWidth(0.3); doc.roundedRect(M, y, CW, boxH, 2, 2, 'FD');
sT(COL.ink); doc.setFont('helvetica', 'bold'); doc.setFontSize(11); doc.text('So gehst du jetzt vor', M + 4, y + 7);
var ry = y + 13; sT([60, 70, 90]); doc.setFont('helvetica', 'normal'); doc.setFontSize(9.5);
recs.forEach(function (rc) { var ln = doc.splitTextToSize('• ' + rc, CW - 8); doc.text(ln, M + 4, ry); ry += ln.length * 4.6; });
// ---- Marketing-Teaser mit Re-Scan-QR ----
var teaserH = 48;
if (y + teaserH > 284) { doc.addPage(); y = 20; }
sF(COL.navy); doc.roundedRect(M, y, CW, teaserH, 3, 3, 'F');
sF(COL.accent); doc.roundedRect(M, y, 2.6, teaserH, 1.3, 1.3, 'F');
var rq = 27, rqX = M + CW - rq - 7, rqY = y + (teaserH - rq) / 2 - 1;
qrBox(doc, toolUrl() + '?scan=' + encodeURIComponent(target), rqX, rqY, rq, 1.6);
sT([225, 232, 244]); doc.setFont('helvetica', 'bold'); doc.setFontSize(7.3); doc.text('Diese IP neu scannen', rqX + rq / 2, rqY + rq + 4, { align: 'center' });
var tX = M + 9, tW = rqX - tX - 6, yy = y + 9;
sT(COL.accent); doc.setFont('helvetica', 'bold'); doc.setFontSize(13);
var hl2 = doc.splitTextToSize('IP erneut checken? In 60 Sekunden Klarheit.', tW); doc.text(hl2, tX, yy); yy += hl2.length * 5.4 + 1.5;
sT([215, 224, 238]); doc.setFont('helvetica', 'normal'); doc.setFontSize(8.6);
var pl = doc.splitTextToSize('Deine IP-Reputation ist kein Standbild: Blacklists, Bedrohungen und Provider-Daten ändern sich täglich. Scanne genau diese Adresse jederzeit neu – 100 % kostenlos, ohne Anmeldung, in Sekunden.', tW); doc.text(pl, tX, yy); yy += pl.length * 4 + 2;
sT([150, 165, 190]); doc.setFontSize(7.4);
var pvl = doc.splitTextToSize('Datenschutz: Wir speichern deine Eingaben nicht – der Bericht lebt nur in deinem Browser und in dieser PDF. Zum Entfernen einfach die Datei löschen; für frische Ergebnisse den QR-Code scannen.', tW); doc.text(pvl, tX, yy); yy += pvl.length * 3.5 + 1;
sT(COL.accent); doc.setFont('helvetica', 'bold'); doc.setFontSize(8.8); doc.text('› Jetzt kostenlos prüfen auf SafeMyIdent.de', tX, Math.min(yy + 2.5, y + teaserH - 3.5));
y += teaserH + 6;
// Footer auf allen Seiten.
var pages = doc.internal.getNumberOfPages();
for (var p = 1; p <= pages; p++) {
doc.setPage(p); sD(COL.line); doc.setLineWidth(0.2); doc.line(M, 287, W - M, 287);
sT(COL.muted); doc.setFont('helvetica', 'normal'); doc.setFontSize(7.5);
doc.text('Erstellt mit SafeMyIdent · Daten aus öffentlichen Quellen, können abweichen. IP-Standorte sind Näherungswerte.', M, 291);
doc.text('Seite ' + p + '/' + pages, W - M, 291, { align: 'right' });
}
doc.save('SafeMyIdent-IP-Bericht-' + String(target).replace(/[^a-z0-9.\-]/gi, '_').slice(0, 40) + '.pdf');
}
})();