diff --git a/backend/example_scripts/dns_rewrite_example_to_pwned-requirements.txt b/backend/example_scripts/dns_rewrite_example_to_pwned-requirements.txt new file mode 100644 index 0000000..c7e7a53 --- /dev/null +++ b/backend/example_scripts/dns_rewrite_example_to_pwned-requirements.txt @@ -0,0 +1,2 @@ +netfilterqueue +scapy diff --git a/backend/example_scripts/dns_rewrite_example_to_pwned.py b/backend/example_scripts/dns_rewrite_example_to_pwned.py new file mode 100644 index 0000000..33d5e4b --- /dev/null +++ b/backend/example_scripts/dns_rewrite_example_to_pwned.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python3 +"""Minimal NFQUEUE example: rewrite DNS queries from example.com to pwned.com.""" + +import signal +import sys + +from netfilterqueue import NetfilterQueue +from scapy.all import DNS, DNSQR, IP, UDP + +SOURCE_QNAME = b"example.com." +TARGET_QNAME = b"pwned.com." + +nfq = NetfilterQueue() + + +def _handle_packet(packet) -> None: + try: + ip = IP(packet.get_payload()) + if ip.haslayer(UDP) and ip.haslayer(DNS) and ip.haslayer(DNSQR): + dns = ip[DNS] + query = ip[DNSQR] + + # Only touch DNS requests for exactly example.com. + if dns.qr == 0 and query.qname == SOURCE_QNAME: + query.qname = TARGET_QNAME + del ip.len + del ip.chksum + del ip[UDP].len + del ip[UDP].chksum + packet.set_payload(bytes(ip)) + except Exception: + pass + + packet.accept() + + +def _stop(_sig, _frame) -> None: + raise SystemExit(0) + + +def main() -> int: + if len(sys.argv) != 2: + print("Usage: dns_rewrite_example_to_pwned.py ", file=sys.stderr) + return 1 + + try: + qnum = int(sys.argv[1]) + except ValueError: + print("qnum must be an integer", file=sys.stderr) + return 1 + + signal.signal(signal.SIGINT, _stop) + signal.signal(signal.SIGTERM, _stop) + + nfq.bind(qnum, _handle_packet) + try: + nfq.run() + except KeyboardInterrupt: + pass + finally: + nfq.unbind() + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/frontend/src/components/ScriptManager.tsx b/frontend/src/components/ScriptManager.tsx index 0bcd4af..70810c6 100644 --- a/frontend/src/components/ScriptManager.tsx +++ b/frontend/src/components/ScriptManager.tsx @@ -2,6 +2,7 @@ import { DeleteOutlined, DownloadOutlined, EditOutlined, + EyeOutlined, FileAddOutlined, PauseCircleOutlined, PlayCircleOutlined, @@ -72,6 +73,7 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c const [editorModalVisible, setEditorModalVisible] = useState(false); const [editorValue, setEditorValue] = useState(''); const [currentEditingName, setCurrentEditingName] = useState(null); + const [editorReadOnly, setEditorReadOnly] = useState(false); const [useInlineReqEditor, setUseInlineReqEditor] = useState(false); const [inlineReqValue, setInlineReqValue] = useState(''); @@ -383,6 +385,7 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c const blob = await downloadScript(name); const text = await blob.text(); setEditorValue(text); + setEditorReadOnly(false); setEditorModalVisible(true); setCurrentEditingName(name); if (onOpenInEditor) onOpenInEditor(text); @@ -393,6 +396,19 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c [onOpenInEditor], ); + const openReadOnlyViewerFromServer = useCallback(async (name: string) => { + try { + const blob = await downloadScript(name); + const text = await blob.text(); + setEditorValue(text); + setEditorReadOnly(true); + setEditorModalVisible(true); + setCurrentEditingName(name); + } catch (err: any) { + notification.error({ message: 'Failed to open', description: err?.message ?? String(err) }); + } + }, []); + const handleSaveFromEditorAs = useCallback( async (name: string) => { try { @@ -608,6 +624,13 @@ export default function ScriptsManager({ onOpenInEditor }: { onOpenInEditor?: (c onClick={() => handleDownload(record.name)} icon={} /> + {record.is_protected_example ? ( + openReadOnlyViewerFromServer(record.name)} + icon={} + /> + ) : null} {!record.is_protected_example ? ( setEditorModalVisible(false)} + title={ + currentEditingName + ? `${editorReadOnly ? 'Viewing' : 'Editing'} — ${currentEditingName}` + : editorReadOnly + ? 'Viewer' + : 'Editor' + } + onCancel={() => { + setEditorModalVisible(false); + setEditorReadOnly(false); + }} width={900} footer={ - - + editorReadOnly ? ( - - - ), - icon: null, - okButtonProps: { style: { display: 'none' } }, - cancelButtonProps: { style: { display: 'none' } }, - }) - } - > - Save as... - - - + ) : ( + + + + + + ), + icon: null, + okButtonProps: { style: { display: 'none' } }, + cancelButtonProps: { style: { display: 'none' } }, + }) + } + > + Save as... + + + + ) } > - +