package ru.thiflict import kotlinx.coroutines.* import ru.thiflict.DebugMode.isDebug import java.io.BufferedReader import java.io.File import java.io.InputStreamReader import kotlin.system.exitProcess import java.util.* import java.util.concurrent.* import kotlin.time.Duration.Companion.milliseconds // Конфигурация GPIO (BCM номера) object PinConfig { const val GREEN_LED = 17 const val YELLOW_LED = 27 const val RED_LED = 22 const val BUTTON = 24 } object DebugMode { var isDebug = false fun enableDebugging() { isDebug = true } } // Состояния системы enum class SystemState { IDLE, SCANNING, CLEAN, INFECTED, ERROR, INIT, SAMBA } class USBVirusScanner { private var currentState = SystemState.INIT private var scanJob: Job? = null private var indicationJob: Job? = null private var isRunning = true private lateinit var greenLed: GPIOController private lateinit var yellowLed: GPIOController private lateinit var redLed: GPIOController private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob()) fun initialize() { println(if (isDebug) "Инициализация..." else "") try { // Проверяем доступность gpiod checkLibgpiod() greenLed = GPIOController(PinConfig.GREEN_LED) yellowLed = GPIOController(PinConfig.YELLOW_LED) redLed = GPIOController(PinConfig.RED_LED) // Выключаем все светодиоды greenLed.setValue(0) yellowLed.setValue(0) redLed.setValue(0) // Запускаем индикацию состояния startStateIndication() antivirusDatabaseUpdater() Thread.sleep(1000) println(if (isDebug) "Инициализация завершена." else "") currentState = SystemState.IDLE } catch (e: Exception) { currentState = SystemState.ERROR println(if (isDebug) "❌ Ошибка инициализации: ${e.message}" else "") e.printStackTrace() throw e } } private fun checkLibgpiod() { try { val process = ProcessBuilder("gpiodetect").start() val exitCode = process.waitFor() if (exitCode != 0) { throw Exception(if (isDebug) "gpiodetect не найден" else "") } val output = process.inputStream.bufferedReader().readText() println(if (isDebug) "✅ libgpiod найден" else "") if (output.isNotBlank()) { println(if (isDebug) "Доступные чипы: ${output.trim()}" else "") } } catch (e: Exception) { throw Exception(if (isDebug) "libgpiod Не установлен!" else "") } } fun buttonPress(defaultButtonState: Boolean): Boolean { var finalButtonState = defaultButtonState println(if (isDebug) "Начало finalButtonState: $finalButtonState" else "") val buttonListener = ButtonListener() val initialState = buttonListener.startListening { isPressed -> if (isPressed) { finalButtonState = true } } println(if (isDebug) "finalButtonState: $finalButtonState" else "") return finalButtonState } private fun startStateIndication() { indicationJob = scope.launch { while (isRunning) { when (currentState) { SystemState.INIT -> { greenLed.setValue(1) yellowLed.setValue(1) redLed.setValue(1) delay(200.milliseconds) greenLed.setValue(0) yellowLed.setValue(0) redLed.setValue(0) delay(200.milliseconds) } SystemState.IDLE -> { // Зелёный медленно мигает blinkLed(greenLed, 1000) yellowLed.setValue(0) redLed.setValue(0) } SystemState.SCANNING -> { // Жёлтый быстро мигает greenLed.setValue(0) blinkLed(yellowLed, 150) redLed.setValue(0) } SystemState.CLEAN -> { // Зелёный горит greenLed.setValue(1) yellowLed.setValue(0) redLed.setValue(0) delay(100.milliseconds) } SystemState.INFECTED -> { // Красный горит greenLed.setValue(0) yellowLed.setValue(0) redLed.setValue(1) delay(100.milliseconds) } SystemState.ERROR -> { // Красный мигает greenLed.setValue(0) yellowLed.setValue(0) blinkLed(redLed, 500) } SystemState.SAMBA -> { blinkLed(greenLed, 200) blinkLed(yellowLed, 200) redLed.setValue(0) } } } } } private suspend fun blinkLed(led: GPIOController, intervalMs: Long) { led.setValue(1) delay((intervalMs / 2).milliseconds) led.setValue(0) delay((intervalMs / 2).milliseconds) } fun startScan() { if (scanJob?.isActive == true) { println(if (isDebug) "Сканирование уже выполняется" else "") return } scanJob = scope.launch { currentState = SystemState.SCANNING println(if (isDebug) "=== Начало сканирования ===" else "") try { // Поиск USB-устройств val usbDevices = findUSBDevices() if (usbDevices.isEmpty()) { println(if (isDebug) "Устройства не найдены" else "") currentState = SystemState.ERROR delay(5000.milliseconds) currentState = SystemState.IDLE return@launch } println(if (isDebug) "📀 Найдены USB-устройства:" else "") usbDevices.forEach { device -> println(" - ${device.mountPoint}") } println() var hasVirus = false val scanResults = mutableListOf() for (device in usbDevices) { println(if (isDebug) "🔍 Сканирование: ${device.mountPoint}" else "") val result = scanWithClamAV(device.mountPoint) scanResults.add(result) if (result.isInfected) { hasVirus = true println(if (isDebug) "⚠️ ВНИМАНИЕ! Найдены угрозы!" else "") result.infectedFiles.take(10).forEach { file -> println(" 🦠 ${file.take(100)}") } if (result.infectedFiles.size > 10) { println(if (isDebug) " ... и ещё ${result.infectedFiles.size - 10} файлов" else "") } unmount() } else if (result.error != null) { println(if (isDebug) "❌ Ошибка сканирования: ${result.error}" else "") } else { println(if (isDebug) "✅ Сканирование завершено: угроз не найдено" else "") } println() } // Сохраняем лог saveScanLog(scanResults) currentState = if (hasVirus) { SystemState.INFECTED } else { SystemState.CLEAN } val message = if (hasVirus) { if (isDebug) "🦠 Найдены вирусы! Красный LED горит" else "" } else { if (isDebug) "✅ Флешка чиста! Зелёный LED горит" else "" } println(if (isDebug) message else "") // Держим состояние 5 секунд delay(10000.milliseconds) if (!hasVirus) { currentState = SystemState.SAMBA sambaStartSharing() val buttonListener = ButtonListener() var isEventHappened = false buttonListener.addLongPressListener { event -> isEventHappened = true } buttonListener.startListening() while (!isEventHappened) { delay(500.milliseconds) } } currentState = SystemState.IDLE } catch (e: Exception) { println(if (isDebug) "❌ Критическая ошибка: ${e.message}" else "") e.printStackTrace() currentState = SystemState.ERROR delay(10000.milliseconds) currentState = SystemState.IDLE } println(if (isDebug) "=== Сканирование завершено ===\n" else "") } } private fun findUSBDevices(): List { val devices = mutableListOf() try { // Проверяем /media директории val mediaDirs = listOf("/media", "/media/pi") for (mediaDirPath in mediaDirs) { val mediaDir = File(mediaDirPath) if (mediaDir.exists() && mediaDir.isDirectory) { mediaDir.listFiles()?.forEach { dir -> if (dir.isDirectory && dir.canRead()) { // Проверяем, что это не системная директория val isSystemDir = dir.name.startsWith(".") || dir.name == "root" || dir.name == "boot" if (!isSystemDir && devices.none { it.mountPoint == dir.absolutePath }) { // Дополнительная проверка: есть ли файлы в директории val hasFiles = dir.listFiles()?.isNotEmpty() == true if (hasFiles) { devices.add(USBDevice("usb", dir.absolutePath)) } } } } } } // Проверяем /proc/mounts для дополнительных точек монтирования val mountsFile = File("/proc/mounts") if (mountsFile.exists()) { mountsFile.readLines().forEach { line -> val parts = line.split(" ") if (parts.size >= 2) { val mountPoint = parts[1] if ((mountPoint.startsWith("/media")) && File(mountPoint).exists() && devices.none { it.mountPoint == mountPoint } ) { devices.add(USBDevice("auto", mountPoint)) } } } } } catch (e: Exception) { println(if (isDebug) "Ошибка при поиске USB-устройств: ${e.message}" else "") } return devices.distinctBy { it.mountPoint } } private fun scanWithClamAV(path: String): ScanResult { val infectedFiles = mutableListOf() val virusNames = mutableMapOf() try { // Проверяем установлен ли ClamAV val checkProcess = ProcessBuilder("which", "clamscan").start() val checkExitCode = checkProcess.waitFor() if (checkExitCode != 0) { return ScanResult( isInfected = false, infectedFiles = emptyList(), virusNames = emptyMap(), exitCode = -1, error = if (isDebug) "ClamAV не установлен. Установите: sudo apt install clamav" else "" ) } // Проверяем существование пути val pathFile = File(path) if (!pathFile.exists() || !pathFile.isDirectory) { return ScanResult( isInfected = false, infectedFiles = emptyList(), virusNames = emptyMap(), exitCode = -1, error = if (isDebug) "Путь не существует или не является директорией: $path" else "" ) } println(if (isDebug) " Запуск clamscan (это может занять некоторое время)..." else "") // Запускаем ClamAV val processBuilder = ProcessBuilder( "clamdscan", "--multiscan", "--stdout", "--infected", path ) processBuilder.redirectErrorStream(true) val process = processBuilder.start() val output = process.inputStream.bufferedReader().readText() val exitCode = process.waitFor() // Парсим вывод output.lines().forEach { line -> if (line.contains(" FOUND")) { val colonIndex = line.indexOf(": ") if (colonIndex > 0) { val fileName = line.substring(0, colonIndex) val virusInfo = line.substringAfter(" FOUND").trim() infectedFiles.add(fileName) virusNames[fileName] = virusInfo } } } return ScanResult( isInfected = infectedFiles.isNotEmpty(), infectedFiles = infectedFiles, virusNames = virusNames, exitCode = exitCode, error = if (isDebug) (if (exitCode != 0 && exitCode != 1) "ClamAV вернул код ошибки: $exitCode" else "") else "" ) } catch (e: Exception) { println(if (isDebug) "Ошибка при выполнении clamscan: ${e.message}" else "") return ScanResult( isInfected = false, infectedFiles = emptyList(), virusNames = emptyMap(), exitCode = -1, error = if (isDebug) e.message else "" ) } } private fun saveScanLog(results: List) { try { val logDir = File("/home/${System.getProperty("user.name")}/usb-scanner-logs") if (!logDir.exists()) { logDir.mkdirs() } val timestamp = System.currentTimeMillis() val logFile = File(logDir, "scan_$timestamp.log") logFile.bufferedWriter().use { writer -> writer.write("=== USB Virus Scanner Log ===\n") writer.write("Time: ${java.time.LocalDateTime.now()}\n\n") results.forEachIndexed { index, result -> writer.write("Device ${index + 1}:\n") if (result.isInfected) { writer.write("Status: INFECTED\n") writer.write("Found ${result.infectedFiles.size} infected files:\n") result.infectedFiles.take(20).forEach { file -> writer.write(" $file: ${result.virusNames[file]}\n") } if (result.infectedFiles.size > 20) { writer.write(" ... and ${result.infectedFiles.size - 20} more files\n") } } else if (result.error != null) { writer.write("Status: ERROR\n") writer.write("Error: ${result.error}\n") } else { writer.write("Status: CLEAN\n") } writer.write("\n") } } println(if (isDebug) "📝 Лог сохранён: ${logFile.absolutePath}" else "") } catch (e: Exception) { println(if (isDebug) "Ошибка сохранения лога: ${e.message}" else "") } } suspend fun sambaStartSharing() { try { currentState = SystemState.SAMBA var process: Process? = null val sambaStartServiceCmd = listOf("systemctl", "start", "smbd") val processBuilder = ProcessBuilder(sambaStartServiceCmd) .redirectErrorStream(true) process = withContext(Dispatchers.IO) { processBuilder.start() } } catch (e: Exception) { e.printStackTrace() currentState = SystemState.ERROR delay(10000.milliseconds) } } fun sambaShutdown() { unmount() try { var process: Process? = null val sambaStopServiceCmd = listOf("systemctl", "stop", "smbd") val processBuilder = ProcessBuilder(sambaStopServiceCmd) .redirectErrorStream(true) process = processBuilder.start() scanJob?.cancel() currentState = SystemState.IDLE } catch (e: Exception) {} } fun unmount() { for (i in 0..9) { val path = if (i == 0) "/media/usb" else "/media/usb$i" try { val processBuilder = ProcessBuilder("umount", "-f", path) .redirectErrorStream(true) val process = processBuilder.start() val exitCode = process.waitFor() if (exitCode == 0) { println(if (isDebug) "Успешно размонтировано: $path" else "") } } catch (e: Exception) { println(if (isDebug) "Ошибка при размонтировании $path: ${e.message}" else "") } } } fun shutdown() { println(if (isDebug) "\nЗавершение работы..." else "") isRunning = false indicationJob?.cancel() scanJob?.cancel() scope.cancel() // Выключаем все светодиоды try { if (::greenLed.isInitialized) { greenLed.setValue(0) yellowLed.setValue(0) redLed.setValue(0) } } catch (e: Exception) { // Игнорируем ошибки при закрытии } println(if (isDebug) "✅ Система остановлена" else "") } } data class USBDevice( val device: String, val mountPoint: String ) data class ScanResult( val isInfected: Boolean, val infectedFiles: List, val virusNames: Map, val exitCode: Int, val error: String? = null ) fun scheduleUpdaterAt3AM() { val scheduler = Executors.newSingleThreadScheduledExecutor() val now = Calendar.getInstance() val targetTime = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 3) set(Calendar.MINUTE, 0) set(Calendar.SECOND, 0) set(Calendar.MILLISECOND, 0) } var initialDelay = targetTime.timeInMillis - now.timeInMillis // Если время уже прошло сегодня, планируем на завтра if (initialDelay <= 0) { targetTime.add(Calendar.DAY_OF_MONTH, 1) initialDelay = targetTime.timeInMillis - now.timeInMillis } scheduler.scheduleAtFixedRate( { antivirusDatabaseUpdater() }, initialDelay, TimeUnit.DAYS.toMillis(1), TimeUnit.MILLISECONDS ) println("Планировщик запущен. Первый запуск через ${initialDelay / 1000 / 60} минут") } fun antivirusDatabaseUpdater() { println(if (isDebug) "Обновление базы данных антивируса" else "") val processBuilder = ProcessBuilder("freshclam", "-vvv") processBuilder.redirectErrorStream(true) val process = processBuilder.start() val reader = BufferedReader(InputStreamReader(process.inputStream)) if (isDebug) { reader.useLines { lines -> lines.forEach { println("Process output: $it") } } } process.waitFor() } fun main(args: Array) { if (args.contains("--debug")) { DebugMode.enableDebugging() } scheduleUpdaterAt3AM() println(if (isDebug) "Зелёный - ${PinConfig.GREEN_LED}" else "") println(if (isDebug) "Жёлтый - ${PinConfig.YELLOW_LED}" else "") println(if (isDebug) "Красный - ${PinConfig.RED_LED}" else "") println(if (isDebug) "Кнопка - ${PinConfig.BUTTON}" else "") val scanner = USBVirusScanner() Runtime.getRuntime().addShutdownHook(Thread { scanner.shutdown() }) try { scanner.initialize() val buttonListener = ButtonListener() buttonListener.addClickListener { event -> scanner.startScan() } buttonListener.addLongPressListener { event -> scanner.sambaShutdown() } buttonListener.startListening() println( if (isDebug) """ ═══════════════════════════════════════ USB Virus Scanner готов к работе ═══════════════════════════════════════ 📌 Вставьте флешку и нажмите кнопку 📌 Зелёный LED мигает - режим ожидания 📌 Жёлтый LED мигает - идёт проверка 📌 Зелёный LED горит - флешка чиста 📌 Красный LED горит - найдены вирусы 📌 Для выхода нажмите Ctrl+C """.trimIndent() else "" ) while (true) { Thread.sleep(1000) } } catch (e: Exception) { println(if (isDebug) "❌ Критическая ошибка: ${e.message}" else "") e.printStackTrace() scanner.shutdown() exitProcess(1) } }