Prüfung / Lint, Typen, Tests (push) Canceled after 0s
Prüfung / Images bauen (pdf) (push) Canceled after 0s
Prüfung / Images bauen (web) (push) Canceled after 0s
Prüfung / Images bauen (worker) (push) Canceled after 0s
Images / bauen (pdf, docker/pdf.Dockerfile) (push) Canceled after 0s
Images / bauen (web, docker/web.Dockerfile) (push) Canceled after 0s
Images / bauen (worker, docker/worker.Dockerfile) (push) Canceled after 0s
375 Vorkommen in 210 Dateien. Betrifft Kommentare, Oberflaechentexte und die README; an keiner Stelle Bedeutung, nur Zeichensetzung.
94 lines
2.9 KiB
TypeScript
94 lines
2.9 KiB
TypeScript
import net from 'node:net'
|
|
|
|
/**
|
|
* Anbindung an clamd über den Unix-Socket im geteilten Volume.
|
|
*
|
|
* Es wird **INSTREAM** benutzt, nicht SCAN mit einem Pfad: dann muss der
|
|
* Scanner die Datei nicht sehen können, und die Anbindung funktioniert auch,
|
|
* wenn clamd anderswo läuft. Das Protokoll ist simpel - Länge als 4 Byte,
|
|
* Nutzdaten, dann eine Null-Länge als Abschluss.
|
|
*/
|
|
|
|
export type ScanResult =
|
|
| { ok: true; clean: true }
|
|
| { ok: true; clean: false; signature: string }
|
|
/** Scanner nicht erreichbar, Zeitüberschreitung, unverständliche Antwort. */
|
|
| { ok: false; reason: string }
|
|
|
|
const CHUNK = 64 * 1024
|
|
|
|
export function scanBuffer(
|
|
socketPath: string,
|
|
content: Uint8Array,
|
|
timeoutMs = 30_000,
|
|
): Promise<ScanResult> {
|
|
return new Promise((resolve) => {
|
|
let settled = false
|
|
let antwort = ''
|
|
|
|
const socket = net.createConnection(socketPath)
|
|
const done = (result: ScanResult) => {
|
|
if (settled) return
|
|
settled = true
|
|
socket.destroy()
|
|
resolve(result)
|
|
}
|
|
|
|
socket.setTimeout(timeoutMs)
|
|
socket.on('timeout', () => done({ ok: false, reason: 'Zeitüberschreitung beim Scannen' }))
|
|
socket.on('error', (error) =>
|
|
done({ ok: false, reason: `clamd nicht erreichbar: ${error.message}` }),
|
|
)
|
|
|
|
socket.on('connect', () => {
|
|
socket.write('zINSTREAM\0')
|
|
for (let offset = 0; offset < content.length; offset += CHUNK) {
|
|
const teil = content.subarray(offset, offset + CHUNK)
|
|
const laenge = Buffer.alloc(4)
|
|
laenge.writeUInt32BE(teil.length, 0)
|
|
socket.write(laenge)
|
|
socket.write(teil)
|
|
}
|
|
socket.write(Buffer.from([0, 0, 0, 0]))
|
|
})
|
|
|
|
socket.on('data', (chunk) => {
|
|
antwort += chunk.toString('utf8')
|
|
})
|
|
|
|
socket.on('close', () => {
|
|
const text = antwort.replace(/\0/g, '').trim()
|
|
if (text.endsWith('OK')) return done({ ok: true, clean: true })
|
|
if (text.endsWith('FOUND')) {
|
|
const signature = text.replace(/^stream:\s*/, '').replace(/\s*FOUND$/, '')
|
|
return done({ ok: true, clean: false, signature })
|
|
}
|
|
// "size limit exceeded", "ERROR" oder gar nichts.
|
|
done({ ok: false, reason: text || 'keine Antwort von clamd' })
|
|
})
|
|
})
|
|
}
|
|
|
|
/**
|
|
* Fragt clamd über den Unix-Socket. Der Socket liegt im geteilten Volume
|
|
* clamav_socket, siehe compose.yaml. Antwort auf zPING ist "PONG".
|
|
*/
|
|
export function pingClamav(socketPath: string, timeoutMs = 2000): Promise<boolean> {
|
|
return new Promise((resolve) => {
|
|
let settled = false
|
|
const done = (ok: boolean) => {
|
|
if (settled) return
|
|
settled = true
|
|
socket.destroy()
|
|
resolve(ok)
|
|
}
|
|
|
|
const socket = net.createConnection(socketPath)
|
|
socket.setTimeout(timeoutMs)
|
|
socket.on('connect', () => socket.write('zPING\0'))
|
|
socket.on('data', (chunk) => done(chunk.toString().includes('PONG')))
|
|
socket.on('timeout', () => done(false))
|
|
socket.on('error', () => done(false))
|
|
})
|
|
}
|