diff options
Diffstat (limited to 'src/main/java/com/it_jaros/jscanner')
29 files changed, 0 insertions, 1652 deletions
diff --git a/src/main/java/com/it_jaros/jscanner/App.java b/src/main/java/com/it_jaros/jscanner/App.java deleted file mode 100644 index fd38ae0..0000000 --- a/src/main/java/com/it_jaros/jscanner/App.java +++ /dev/null @@ -1,100 +0,0 @@ -package com.it_jaros.jscanner; - -import com.it_jaros.jscanner.scan.Scan; -import com.it_jaros.jscanner.scan.ScanOptions; -import com.it_jaros.jscanner.scan.Scanner; - -import java.io.IOException; -import java.util.concurrent.CountDownLatch; - -public class App { - - private static Thread shutdownHook; - private static final CountDownLatch shutdownLatch = new CountDownLatch(1); - private static int exitCode = 0; - - public static void main(String[] args) { - try { - run(args); - } catch (HelpRequested e) { - // Do nothing - } catch (Exception e) { - System.err.println(e.getMessage()); - exitCode = 1; - } finally { - shutdownLatch.countDown(); - removeShutdownHook(); - } - - System.exit(exitCode); - } - - private static void run(String[] args) throws Exception { - ScanOptions options = parseOptions(args); - try (Scanner scanner = new Scanner(options)) { - runScan(scanner, options); - } - } - - private static void runScan(Scanner scanner, ScanOptions options) throws IOException { - addShutdownHook(scanner); - Scan scan = Scan.create(options); - CliPrinter progressBar = new CliPrinter(scan, options.quiet()); - progressBar.start(); - scanner.runScan(scan, result -> progressBar.printResult(result, options.showFilteredPorts())); - progressBar.stop(); - } - - private static ScanOptions parseOptions(String[] args) { - CliParser cliParser = new CliParser(); - try { - return cliParser.parseArgs(args); - } catch (IllegalArgumentException e) { - cliParser.printUsageHelp(); - throw e; - } - } - - /** - * Notice user that the signal was received - * Start shutdown of scanner - * - * @param scanner - */ - private static void addShutdownHook(Scanner scanner) { - shutdownHook = new Thread(() -> { - System.err.printf("%nCtrl-C received. Shutting down...%n"); - shutdown(scanner); - }); - Runtime.getRuntime().addShutdownHook(shutdownHook); - } - - /** - * Try to gracefully shutdown scanner - * and wait - * - * @param scanner - */ - private static void shutdown(Scanner scanner) { - try { - scanner.cancel(); - shutdownLatch.await(); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } catch (Exception e) { - // Time ran out - } - } - - private static void removeShutdownHook() { - if (shutdownHook == null) { - return; - } - - try { - Runtime.getRuntime().removeShutdownHook(shutdownHook); - } catch (IllegalStateException ignored) { - // JVM is already shutting down - } - } -} diff --git a/src/main/java/com/it_jaros/jscanner/CliDefaults.java b/src/main/java/com/it_jaros/jscanner/CliDefaults.java deleted file mode 100644 index 655a9ed..0000000 --- a/src/main/java/com/it_jaros/jscanner/CliDefaults.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.it_jaros.jscanner; - -public class CliDefaults { - private CliDefaults() { - /* This utility class should not be instantiated */ - } - - public static final String PORTS = "1-1024"; - public static final boolean BANNER_RECOGNITION = false; - public static final boolean DISABLE_HOST_CHECK = false; - public static final boolean QUIET = false; - public static final boolean SHOW_FILTERED_PORTS = false; - public static final int DELAY_IN_MILLIS = 0; - public static final int MAX_HOSTS_LIMIT = 100; - public static final int MAX_WORKERS_PER_HOST = Integer.MAX_VALUE; - public static final int SOCKET_LIMIT = 2048; - public static final int TIMEOUT_IN_MILLIS = 3000; -} diff --git a/src/main/java/com/it_jaros/jscanner/CliParser.java b/src/main/java/com/it_jaros/jscanner/CliParser.java deleted file mode 100644 index 7e27064..0000000 --- a/src/main/java/com/it_jaros/jscanner/CliParser.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.it_jaros.jscanner; - -import com.it_jaros.jscanner.scan.ScanOptions; - -import java.util.HashSet; -import java.util.Set; - -public class CliParser { - - public ScanOptions parseArgs(String[] args) { - if (args.length < 1 || args[0].trim().isEmpty()) { - throw new IllegalArgumentException("No argument given"); - } - - Set<String> hostsArgv = new HashSet<>(); - String hostsFile = null; - String ports = CliDefaults.PORTS; - boolean bannerRecognition = CliDefaults.BANNER_RECOGNITION; - boolean disableOnlineCheck = CliDefaults.DISABLE_HOST_CHECK; - boolean quiet = CliDefaults.QUIET; - boolean showFilteredPorts = CliDefaults.SHOW_FILTERED_PORTS; - int delayInMillis = CliDefaults.DELAY_IN_MILLIS; - int hostsLimit = CliDefaults.MAX_HOSTS_LIMIT; - int workersPerHostLimit = CliDefaults.MAX_WORKERS_PER_HOST; - int socketLimit = CliDefaults.SOCKET_LIMIT; - int timeoutInMillis = CliDefaults.TIMEOUT_IN_MILLIS; - - for (int i = 0; i < args.length; i++) { - final String currentArg = args[i]; - if (!currentArg.startsWith("-")) { - hostsArgv.add(currentArg); - continue; - } - - switch (currentArg) { - case "--bannerRecognition", "-b": - bannerRecognition = true; - break; - case "--delay", "-d": - delayInMillis = parseIntOption(args, ++i, currentArg); - break; - case "--disableOnlineCheck", "-do": - disableOnlineCheck = true; - break; - case "--help", "-h": - printUsageHelp(); - throw new HelpRequested(); - case "--input", "-i": - hostsFile = getArgument(args, ++i, currentArg); - break; - case "--hostsLimit", "-hl": - hostsLimit = parseIntOption(args, ++i, currentArg); - break; - case "--workersPerHostLimit", "-whl": - workersPerHostLimit = parseIntOption(args, ++i, currentArg); - break; - case "--ports", "-p": - ports = getArgument(args, ++i, currentArg); - break; - case "--quiet", "-q": - quiet = true; - break; - case "--showFilteredPorts", "-sf": - showFilteredPorts = true; - break; - case "--socketLimit", "-sl": - socketLimit = parseIntOption(args, ++i, currentArg); - break; - case "--timeout", "-t": - timeoutInMillis = parseIntOption(args, ++i, currentArg); - break; - default: - throw new IllegalArgumentException("No such param " + currentArg); - } - } - - if (hostsArgv.isEmpty() && hostsFile == null) { - throw new IllegalArgumentException("No input provided. Check with --help for available options."); - } - - return new ScanOptions( - hostsArgv.stream().toList(), - hostsFile, - ports, - bannerRecognition, - disableOnlineCheck, - quiet, - showFilteredPorts, - delayInMillis, - hostsLimit, - workersPerHostLimit, - socketLimit, - timeoutInMillis - ); - } - - private String getArgument(String[] args, int index, String option) { - if (index >= args.length) { - throw new IllegalArgumentException(String.format("Missing argument for option %s", option)); - } - return args[index]; - } - - private int parseIntOption(String[] args, int index, String option) { - String value = getArgument(args, index, option); - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - throw new IllegalArgumentException(String.format("Invalid argument %s for option %s", value, option)); - } - } - - public void printUsageHelp() { - StringBuilder usage = new StringBuilder(); - usage.append(""" - Usage: - java -jar jscanner.jar [OPTIONS] [<host>,...] - - Options: - """); - usage.append(""" - --bannerRecognition, -b:\t\tEnable banner recognition to find out which service is running behind port (Default: %b) - --delay, -d:\t\t\tWaiting period in millis for connection attempts for a host (Default: %d) - --disableOnlineCheck, -do:\t\tDon't check if Host is reachable (Default: %b) - --input, -i:\t\t\tRead file to get host targets to scan. If set to "-" than Stdin is read (Default: null) - --hostsLimit, -hl:\t\t\tMax number of hosts that are scanned in parallel (Default: %d) - --workersPerHostLimit, -whl:\tMax workers per host. (Default: %d) - --ports, -p:\t\t\tDefine ports to be scanned (Default: %s) - --quiet, -q:\t\t\tDo not output progress bar (Default: %b) - --showFilteredPorts, -sf:\t\tShow also filtered ports (Default: %b) - --socketLimit, -sl:\t\t\tSocket limit in total. You can not scan more ports than allowed here (Default: %d) - --timeout, -t:\t\t\tTimeout in millis per connection attempt, 0 means infinite (Default: %d) - """.formatted(CliDefaults.BANNER_RECOGNITION, CliDefaults.DELAY_IN_MILLIS, CliDefaults.DISABLE_HOST_CHECK, CliDefaults.MAX_HOSTS_LIMIT, CliDefaults.MAX_WORKERS_PER_HOST, CliDefaults.PORTS, CliDefaults.QUIET, CliDefaults.SHOW_FILTERED_PORTS, CliDefaults.SOCKET_LIMIT, CliDefaults.TIMEOUT_IN_MILLIS) - ); - usage.append(""" - - Examples: - java -jar jscanner.jar localhost - java -jar jscanner.jar fd00::1 - """); - System.err.println(usage); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/CliPrinter.java b/src/main/java/com/it_jaros/jscanner/CliPrinter.java deleted file mode 100644 index 709c3dd..0000000 --- a/src/main/java/com/it_jaros/jscanner/CliPrinter.java +++ /dev/null @@ -1,258 +0,0 @@ -package com.it_jaros.jscanner; - -import com.it_jaros.jscanner.scan.Scan; -import com.it_jaros.jscanner.scan.domain.ScanFailure; -import com.it_jaros.jscanner.scan.domain.ScanResult; -import com.it_jaros.jscanner.scan.service.ServiceType; - -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.util.List; -import java.util.Map; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import java.util.stream.Collectors; - -public class CliPrinter { - - private final ScheduledExecutorService ui = Executors.newSingleThreadScheduledExecutor(); - private final Scan scan; - private final boolean quiet; - private final Object outputLock = new Object(); - - public CliPrinter(Scan scan, boolean quiet) { - this.scan = scan; - this.quiet = quiet; - } - - private void clearStatusLine() { - System.err.print("\r\033[2K"); - System.err.flush(); - } - - public void printResult(ScanResult result, boolean showFilteredPorts) { - synchronized (outputLock) { - if (result.openPorts().isEmpty() - && (!showFilteredPorts || result.filteredPorts().isEmpty()) - && result.errors().isEmpty()) { - return; - } - - clearStatusLine(); - printErrors(result); - printPortsResults(result, showFilteredPorts); - printStatusLine(); - } - } - - private void printErrors(ScanResult result) { - if (result.errors().isEmpty()) return; - - StringBuilder sb = new StringBuilder(); - sb.append(String.format( - "%s | errors: %d%n", - result.host(), - result.errors().size() - )); - String errors = result.errors().stream().map((ScanFailure error) -> String.format( - "\t%s: %s, %s", - error.port(), - error.exception().type(), - error.exception().message() - )).collect(Collectors.joining("\n")); - sb.append(errors); - sb.append("\n"); - - System.err.println(sb); - System.err.flush(); - } - - private void printPortsResults(ScanResult result, boolean showFilteredPorts) { - if (result.openPorts().isEmpty() && (!showFilteredPorts || result.filteredPorts().isEmpty())) { - return; - } - - StringBuilder sb = new StringBuilder(); - sb.append(result.host()); - sb.append("\n"); - if (!result.openPorts().isEmpty()) { - sb.append( - String.format("\t (%d) open:\t%s%n", result.openPorts().size(), map(result.openPorts().getPorts(), result.bannerRecognition())) - ); - } - if (showFilteredPorts && !result.filteredPorts().isEmpty()) { - sb.append( - String.format("\t (%d) filtered:\t%s%n", result.filteredPorts().size(), map(result.filteredPorts().getPorts())) - ); - } - sb.append("\n"); - System.out.println(sb); - System.out.flush(); - } - - private static String map(List<Integer> ports) { - return ports.stream().map(String::valueOf).collect(Collectors.joining(",")); - } - - private static String map(List<Integer> ports, Map<Integer, ServiceType> bannerRecognition) { - return ports.stream().map(port -> { - ServiceType serviceType = bannerRecognition.getOrDefault(port, ServiceType.UNKNOWN); - - if (serviceType == ServiceType.UNKNOWN) { - return String.valueOf(port); - } - - return port + "/" + serviceType.name().toLowerCase(); - }).collect(Collectors.joining(",")); - } - - private static int getColumnWidth() { - try { - Process process = new ProcessBuilder("sh", "-c", "stty size < /dev/tty").start(); - - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(process.getInputStream()))) { - String line = reader.readLine(); - if (line != null && !line.isBlank()) { - Matcher matcher = Pattern.compile("^\\d+\\s+(\\d+)").matcher(line.trim()); - if (matcher.matches()) { - return Integer.parseInt(matcher.group(1)); - } - } - } - } catch (Exception ignored) { - // does not matter why it did not work - } - - return 120; - } - - public void start() { - if (quiet) { - return; - } - - // although we don't print anything, we still need to - // gc all the done hosts - System.err.print("Scanning targets...\n\n"); - ui.scheduleAtFixedRate(() -> { - synchronized (outputLock) { - printStatusLine(); - } - }, 1, 1000, TimeUnit.MILLISECONDS); - } - - public void stop() { - ui.shutdownNow(); - try { - ui.awaitTermination(1, TimeUnit.SECONDS); - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - clearStatusLine(); - printStats(); - } - - private void printStats() { - Scan.Statistics stats = scan.getStatistics(); - System.err.println("\n--------------------"); - System.err.println("Stats:"); - System.err.println("Total hosts: " + stats.hostTotal()); - System.err.println("Total ports: " + stats.portTotal()); - System.err.println("Duration: " + formatDuration(stats.durationInMillis())); - System.err.println("Peak concurrent hosts: " + stats.hostMaxConcurrent()); - System.err.println("Peak concurrent sockets: " + stats.socketMaxConcurrent()); - System.err.println("Peak concurrent workers: " + stats.threadMaxConcurrent()); - System.err.println("--------------------\n"); - } - - private static String formatDuration(long millis) { - if (millis <= 0) return ""; - long seconds = millis / 1000 % 60; - long minutes = millis / 60000 % 60; - long hours = millis / 3600000 % 24; - long days = millis / 86400000; - - if (days > 0) return String.format("%dd %dh:%dm:%ds", days, hours, minutes, seconds); - if (hours > 0) return String.format("%dh:%dm:%ds", hours, minutes, seconds); - if (minutes > 0) return String.format("%dm:%ds", minutes, seconds); - return String.format("%ds", seconds); - } - - - private void printStatusLine() { - Scan.Statistics stats = scan.getStatistics(); - int total = stats.hostTotal(); - int running = stats.hostCurrent(); - int done = total - running; - long percent = total == 0 ? 100 : done * 100L / total; - - String hostStat = String.format( - "hosts: %d running, %d done", - running, - done - ); - String portStat = String.format( - " | ports: %d total", - stats.portTotal() - ); - String durationStat = String.format( - " | %s", - formatDuration(stats.durationInMillis()) - ); - String socketStat = String.format( - " | sockets: %d active, %d peak", - stats.socketCurrent(), - stats.socketMaxConcurrent() - ); - String workerStat = String.format( - " | workers: %d active, %d peak", - stats.threadCurrent(), - stats.threadMaxConcurrent() - ); - - int availableWidth = CliPrinter.getColumnWidth(); - String statusLine = appendIfEnoughSpace("", availableWidth, hostStat, portStat, durationStat, socketStat, workerStat); - - String barFormat = " [%s] %3d%%"; - int remainingWidth = availableWidth - statusLine.length() - barFormat.length(); - if (remainingWidth > 0) { - String progressBar = String.format( - barFormat, - progressBar(percent, remainingWidth), - percent - ); - statusLine = appendIfEnoughSpace(statusLine, availableWidth, progressBar); - } - - remainingWidth = availableWidth - statusLine.length(); - if (remainingWidth > 0) { - statusLine += " ".repeat(remainingWidth); - } - System.err.print("\r" + statusLine); - System.err.flush(); - } - - private String appendIfEnoughSpace(String current, int availableSize, String... additional) { - StringBuilder builder = new StringBuilder(current); - for (String add : additional) { - if ((builder.length() + add.length()) <= availableSize) { - builder.append(add); - } - } - - return builder.toString(); - } - - private static String progressBar(long percent, int width) { - if (width <= 0) { - return ""; - } - - int filled = (int) (percent * width / 100); - return "#".repeat(filled) + "-".repeat(width - filled); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/HelpRequested.java b/src/main/java/com/it_jaros/jscanner/HelpRequested.java deleted file mode 100644 index ec97a2a..0000000 --- a/src/main/java/com/it_jaros/jscanner/HelpRequested.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.it_jaros.jscanner; - -public class HelpRequested extends RuntimeException { - public HelpRequested() { - super(); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/Counter.java b/src/main/java/com/it_jaros/jscanner/scan/Counter.java deleted file mode 100644 index 0e1d5c9..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/Counter.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.it_jaros.jscanner.scan; - -import java.util.concurrent.atomic.AtomicInteger; - -public final class Counter { - private final AtomicInteger current = new AtomicInteger(0); - private final AtomicInteger max = new AtomicInteger(0); - private final AtomicInteger total = new AtomicInteger(0); - - public void inc() { - total.incrementAndGet(); - int now = current.incrementAndGet(); - max.accumulateAndGet(now, Math::max); - } - - public void dec() { - current.decrementAndGet(); - } - - public int current() { - return current.get(); - } - - public int max() { - return max.get(); - } - - public int total() { - return total.get(); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/ExceptionInfo.java b/src/main/java/com/it_jaros/jscanner/scan/ExceptionInfo.java deleted file mode 100644 index f62caf1..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/ExceptionInfo.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.it_jaros.jscanner.scan; - -public record ExceptionInfo( - String type, - String message -) { - public static ExceptionInfo from(Throwable t) { - return new ExceptionInfo( - t.getClass().getName(), - t.getMessage() - ); - } -}
\ No newline at end of file diff --git a/src/main/java/com/it_jaros/jscanner/scan/Scan.java b/src/main/java/com/it_jaros/jscanner/scan/Scan.java deleted file mode 100644 index 0820abc..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/Scan.java +++ /dev/null @@ -1,173 +0,0 @@ -package com.it_jaros.jscanner.scan; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.stream.Stream; - -/** - * Object holder for stateful volatile data during scan - */ -public class Scan { - private final Stream<String> hosts; - private final String ports; - private final Runnable onDone; - private final Counter hostCounter; - private final Counter portCounter; - private final Counter socketCounter; - private final Counter threadCounter; - private long start = 0; - private long stop = 0; - - private Scan(Stream<String> hosts, String ports) { - this(hosts, ports, () -> {}); - } - - private Scan(Stream<String> hosts, String ports, Runnable onDone) { - this.hosts = hosts; - this.ports = ports; - this.onDone = onDone; - this.hostCounter = new Counter(); - this.portCounter = new Counter(); - this.socketCounter = new Counter(); - this.threadCounter = new Counter(); - } - - public static Scan create(ScanOptions options) throws IOException { - if (options.hostsFile() != null) { - return create(options.hostsFile(), options.ports()); - } - return create(options.hostsArgv(), options.ports()); - } - - /** - * Create Scan based on either a source file or stdin - * - * @param sourceFile - * @param ports - * @return - * @throws IOException - */ - public static Scan create(String sourceFile, String ports) { - Stream<String> hosts; - if (sourceFile == null || sourceFile.isEmpty()) { - throw new IllegalArgumentException("Source file is null or empty"); - } - - if ("-".equals(sourceFile)) { - BufferedReader stdinReader = new BufferedReader(new InputStreamReader(System.in)); - hosts = stdinReader.lines(); - return new Scan(hosts, ports); - } - - try { - BufferedReader reader = Files.newBufferedReader(Path.of(sourceFile)); - return new Scan(reader.lines(), ports, () -> { - try { - reader.close(); - } catch (IOException ignore) { - // we ignore it because at this moment the program is shutting down anyway - } - }); - } catch (IOException e) { - throw new ScanException("Error while trying to open source File", e); - } - } - - public static Scan create(List<String> hostsArgv, String ports) { - return new Scan(hostsArgv.stream(), ports); - } - - public Stream<String> getHosts() { - return hosts; - } - - public String getPorts() { - return ports; - } - - public void stop() { - stop = System.currentTimeMillis(); - onDone.run(); - } - - public void start() { - start = System.currentTimeMillis(); - } - - private long getDurationMillis() { - if (stop == 0) { - if (start == 0) { - return 0; - } - return System.currentTimeMillis() - start; - } - return stop - start; - } - - public void portStart() { - this.portCounter.inc(); - this.threadCounter.inc(); - this.socketCounter.inc(); - } - - public void portFinish() { - this.portCounter.dec(); - this.threadCounter.dec(); - this.socketCounter.dec(); - } - - public void hostStart() { - this.hostCounter.inc(); - this.threadCounter.inc(); - } - - public void hostFinish() { - this.threadCounter.dec(); - this.hostCounter.dec(); - } - - public void producerStart() { - this.threadCounter.inc(); - } - - public void producerStop() { - this.threadCounter.dec(); - } - - /** - * This is not an atomic 100% correct snapshot but - * more a relative snapshot optimized more for performance - * than exactness - */ - public Statistics getStatistics() { - return new Statistics( - this.getDurationMillis(), - this.hostCounter.total(), - this.hostCounter.current(), - this.hostCounter.max(), - this.portCounter.total(), - this.portCounter.current(), - this.threadCounter.current(), - this.threadCounter.max(), - this.socketCounter.current(), - this.socketCounter.max() - ); - } - - public record Statistics( - long durationInMillis, - int hostTotal, - int hostCurrent, - int hostMaxConcurrent, - int portTotal, - int portCurrent, - int threadCurrent, - int threadMaxConcurrent, - int socketCurrent, - int socketMaxConcurrent - ) {} -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/ScanException.java b/src/main/java/com/it_jaros/jscanner/scan/ScanException.java deleted file mode 100644 index d667346..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/ScanException.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.it_jaros.jscanner.scan; - -public class ScanException extends java.lang.RuntimeException { - public ScanException(String message) { - super(message); - } - - public ScanException(String message, Throwable cause) { - super(message, cause); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/ScanOptions.java b/src/main/java/com/it_jaros/jscanner/scan/ScanOptions.java deleted file mode 100644 index ce0d562..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/ScanOptions.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.it_jaros.jscanner.scan; - -import java.util.List; - -public record ScanOptions( - List<String> hostsArgv, - String hostsFile, - String ports, - boolean bannerRecognition, - boolean disableOnlineCheck, - boolean quiet, - boolean showFilteredPorts, - int delayInMillis, - int maxHostsLimit, - int maxWorkersPerHost, - int socketLimit, - int timeoutInMillis -) {} diff --git a/src/main/java/com/it_jaros/jscanner/scan/Scanner.java b/src/main/java/com/it_jaros/jscanner/scan/Scanner.java deleted file mode 100644 index 4c47aef..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/Scanner.java +++ /dev/null @@ -1,108 +0,0 @@ -package com.it_jaros.jscanner.scan; - -import com.it_jaros.jscanner.scan.domain.ScanResult; -import com.it_jaros.jscanner.scan.engine.*; - -import java.time.Duration; -import java.util.concurrent.*; -import java.util.function.Consumer; - -public class Scanner implements AutoCloseable { - - private final CancelledToken cancelledToken = new CancelledToken(); - private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor(); - private final ScanOptions scanOptions; - private final Semaphore socketLimit; - - public Scanner(ScanOptions options) { - this.scanOptions = options; - this.socketLimit = new Semaphore(scanOptions.socketLimit()); - } - - /** - * Starts a given scan. - * - * @param scan - */ - public void runScan(final Scan scan, final Consumer<ScanResult> consumer) { - if (scan == null) { - throw new IllegalArgumentException("Scan argument cannot be null"); - } - - long delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, scanOptions.delayInMillis())); - ScanExecutionContext context = new ScanExecutionContext( - scanOptions, - executor, - socketLimit, - cancelledToken, - scan, - new PortScanRateLimiter(cancelledToken, delayInNanos) - ); - - scan.start(); - scan.producerStart(); - final ProducerState<ScanResult> state = new ProducerThread(context).startProducer( - scan.getHosts().iterator(), - scanOptions.maxHostsLimit(), - host -> new ScanHostTask(scan, host, context) - ); - - // the main thread is the consumer - // Let the consumer run as long as the producer runs - // or if still tasks are pending in pipeline - // we do not listen to canceled here because we want - // all results (also partial) collected for the consumer - // with whatever is there already - while (state.running().get() || state.inPipeline().get() > 0) { - try { - PollState<ScanResult> poll = getHostResult(state); - if (poll instanceof PollState.Success<ScanResult>(ScanResult value)) { - consumer.accept(value); - } else if (poll instanceof PollState.Failure<ScanResult>(Throwable error)) { - System.err.printf("runScan(): ScanHostTask() failed with error %s -> %s%n", error.getClass().getSimpleName(), error.getMessage()); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } - } - scan.producerStop(); - scan.stop(); - } - - private PollState<ScanResult> getHostResult(ProducerState<ScanResult> state) throws InterruptedException { - Future<ScanResult> finishedHost = state.completionService().poll(ProducerThread.pollInterval.toMillis(), TimeUnit.MILLISECONDS); - if (finishedHost == null) { - return new PollState.Unavailable<>(); - } - - try { - ScanResult result = finishedHost.get(); - return new PollState.Success<>(result); - } catch (ExecutionException e) { - return new PollState.Failure<>(e.getCause()); - } finally { - state.activeWorkers().release(); - state.inPipeline().decrementAndGet(); - } - } - - public boolean awaitTermination(Duration duration) throws InterruptedException { - return executor.awaitTermination(duration.toMillis(), TimeUnit.MILLISECONDS); - } - - public void cancel() { - this.cancelledToken.signal(); - executor.shutdown(); - } - - public void cancelNow() { - this.cancelledToken.signal(); - executor.shutdownNow(); - } - - @Override - public void close() throws Exception { - cancel(); - } - -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/PortList.java b/src/main/java/com/it_jaros/jscanner/scan/domain/PortList.java deleted file mode 100644 index f145700..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/domain/PortList.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.it_jaros.jscanner.scan.domain; - -import java.util.BitSet; -import java.util.List; - -/** - * Encapsulates a port scan result and is immutable - */ -public class PortList { - private final BitSet ports; - - public PortList(BitSet ports) { - this.ports = ports; - } - - public List<Integer> getPorts() { - return ports.stream().boxed().toList(); - } - - public boolean isEmpty() { - return ports.isEmpty(); - } - - public int size() { - return ports.cardinality(); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/PortRange.java b/src/main/java/com/it_jaros/jscanner/scan/domain/PortRange.java deleted file mode 100644 index 4434763..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/domain/PortRange.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.it_jaros.jscanner.scan.domain; - -import com.it_jaros.jscanner.scan.engine.PortRangeIterator; - -import java.util.BitSet; -import java.util.Iterator; - -public class PortRange { - - public static final int MIN_PORT = 1; - public static final int MAX_PORT = 65535; - private final BitSet specifiedPorts = new BitSet(MAX_PORT); - - public PortRange(String ports) { - if (ports == null || "".equals(ports)) { - specifiedPorts.set(MIN_PORT, 1024); - } else if ("all".equalsIgnoreCase(ports)) { - specifiedPorts.set(MIN_PORT, MAX_PORT + 1); - } else { - parsePortRange(ports); - } - } - - /** - * Possible values are 1,10 or 1-10 or a mix of 1,5-10 - * So we treat , stronger and handle them first and then check for - - * - * @param ports - */ - private void parsePortRange(String ports) { - String[] splitComma = ports.split(","); - for (String commaValue : splitComma) { - String[] rangeValue = commaValue.split("-"); - - // more then two values are not possible - if (rangeValue.length > 2) { - throw new IllegalArgumentException(String.format("Argument contains too many '-'' %s", commaValue)); - } - - // no range given, only single port - int port = Integer.parseInt(rangeValue[0]); - if (rangeValue.length == 1) { - checkValue(port); - specifiedPorts.set(port); - continue; - } - - // range given - int end = Integer.parseInt(rangeValue[1]); - checkValues(port, end); - specifiedPorts.set(port, end + 1); - } - } - - private void checkValues(int start, int end) { - checkValue(start); - checkValue(end); - - if (start >= end) { - throw new IllegalArgumentException( - String.format("Start value cannot be equal or bigger than end value '%s >= %s'", start, end)); - } - } - - private void checkValue(int port) { - if (port < MIN_PORT) { - throw new IllegalArgumentException( - String.format("Port value is too low: %s < %s", port, MIN_PORT)); - } - - if (port > MAX_PORT) { - throw new IllegalArgumentException( - String.format("Port value is too high: %s > %s", port, MAX_PORT)); - } - } - - public Iterator<Integer> iterator() { - return new PortRangeIterator(specifiedPorts); - } -}
\ No newline at end of file diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/PortResult.java b/src/main/java/com/it_jaros/jscanner/scan/domain/PortResult.java deleted file mode 100644 index 9e70cc5..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/domain/PortResult.java +++ /dev/null @@ -1,48 +0,0 @@ -package com.it_jaros.jscanner.scan.domain; - -import java.io.IOException; - -public class PortResult { - private int port; - private PortState state; - private byte[] banner; - private Exception exception; - - public int getPort() { - return port; - } - - public void setPort(int port) { - this.port = port; - } - - public PortState getState() { - return state; - } - - public void setState(PortState state) { - this.state = state; - } - - public byte[] getBanner() { - return banner; - } - - public void setBanner(byte[] banner) { - this.banner = banner; - } - - public void setException(IOException e) { - exception = e; - } - - public Exception getException() { - return exception; - } - - public static PortResult empty() { - PortResult portResult = new PortResult(); - portResult.setState(PortState.UNKNOWN); - return portResult; - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/PortState.java b/src/main/java/com/it_jaros/jscanner/scan/domain/PortState.java deleted file mode 100644 index 6db4b2e..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/domain/PortState.java +++ /dev/null @@ -1,8 +0,0 @@ -package com.it_jaros.jscanner.scan.domain; - -public enum PortState { - CLOSED, - FILTERED, - OPEN, - UNKNOWN -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/ScanFailure.java b/src/main/java/com/it_jaros/jscanner/scan/domain/ScanFailure.java deleted file mode 100644 index db33465..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/domain/ScanFailure.java +++ /dev/null @@ -1,9 +0,0 @@ -package com.it_jaros.jscanner.scan.domain; - -import com.it_jaros.jscanner.scan.ExceptionInfo; - -public record ScanFailure( - Integer port, - ExceptionInfo exception -) { -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/domain/ScanResult.java b/src/main/java/com/it_jaros/jscanner/scan/domain/ScanResult.java deleted file mode 100644 index e18e75d..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/domain/ScanResult.java +++ /dev/null @@ -1,26 +0,0 @@ -package com.it_jaros.jscanner.scan.domain; - -import com.it_jaros.jscanner.scan.service.ServiceType; - -import java.util.BitSet; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -public record ScanResult( - String host, - PortList openPorts, - PortList filteredPorts, - Map<Integer, ServiceType> bannerRecognition, - List<ScanFailure> errors -) { - public static ScanResult empty(String host) { - return new ScanResult( - host, - new PortList(new BitSet(PortRange.MAX_PORT)), - new PortList(new BitSet(PortRange.MAX_PORT)), - Collections.emptyMap(), - Collections.emptyList() - ); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/CancelledToken.java b/src/main/java/com/it_jaros/jscanner/scan/engine/CancelledToken.java deleted file mode 100644 index 57993e5..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/CancelledToken.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -public class CancelledToken { - private volatile boolean cancelled = false; - - public void signal() { - if (!cancelled) { - this.cancelled = true; - } - } - - public boolean isCancelled() { - return cancelled; - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/PollState.java b/src/main/java/com/it_jaros/jscanner/scan/engine/PollState.java deleted file mode 100644 index 54ec767..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/PollState.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -/** - * A sealed interface representing the three possible outcomes of a CompletionService - * poll operation: successful result, task failure, or not ready yet. - * Makes error handling explicit — Failure and Unavailable are distinct and compile-time - * required to handle via exhaustiveness checking in switch statements. - */ -public sealed interface PollState<T> { - record Success<T>(T value) implements PollState<T> {} - record Failure<T>(Throwable error) implements PollState<T> {} - record Unavailable<T>() implements PollState<T> {} -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/PortRangeIterator.java b/src/main/java/com/it_jaros/jscanner/scan/engine/PortRangeIterator.java deleted file mode 100644 index 0673f0f..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/PortRangeIterator.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -import java.util.BitSet; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import static com.it_jaros.jscanner.scan.domain.PortRange.MAX_PORT; -import static com.it_jaros.jscanner.scan.domain.PortRange.MIN_PORT; - -public class PortRangeIterator implements Iterator<Integer> { - - private int currentPortCursor; - private int done = 0; - private final BitSet availablePorts = new BitSet(MAX_PORT); - - public PortRangeIterator(BitSet specifiedPorts) { - availablePorts.or(specifiedPorts); - currentPortCursor = availablePorts.nextSetBit(MIN_PORT); - } - - @Override - public Integer next() { - int p = availablePorts.nextSetBit(currentPortCursor); - if (p > 0) { - availablePorts.clear(p); - currentPortCursor = p + 1; - done++; - return p; - } - - throw new NoSuchElementException("Reached end of port range"); - } - - @Override - public boolean hasNext() { - return availablePorts.nextSetBit(currentPortCursor) > 0; - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/PortResultAccumulator.java b/src/main/java/com/it_jaros/jscanner/scan/engine/PortResultAccumulator.java deleted file mode 100644 index 0cc26b0..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/PortResultAccumulator.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -import com.it_jaros.jscanner.scan.ExceptionInfo; -import com.it_jaros.jscanner.scan.domain.*; -import com.it_jaros.jscanner.scan.service.ServiceDetector; -import com.it_jaros.jscanner.scan.service.ServiceType; - -import java.util.*; - -final class PortResultAccumulator { - private final String host; - private final BitSet openPorts = new BitSet(PortRange.MAX_PORT); - private final BitSet filteredPorts = new BitSet(PortRange.MAX_PORT); - private final Map<Integer, ServiceType> serviceTypes = new HashMap<>(); - private final List<ScanFailure> scanFailures = new ArrayList<>(); - - PortResultAccumulator(String host) { - this.host = host; - } - - void add(PortResult portResult) { - switch (portResult.getState()) { - case OPEN -> { - openPorts.set(portResult.getPort()); - serviceTypes.put(portResult.getPort(), ServiceDetector.detect(portResult.getBanner())); - } - case FILTERED -> { - filteredPorts.set(portResult.getPort()); - } - default -> { - // intentional no-op for uncovered port states - } - } - Exception e = portResult.getException(); - if (e != null) { - scanFailures.add(new ScanFailure(portResult.getPort(), ExceptionInfo.from(e))); - } - } - - ScanResult build() { - return new ScanResult( - host, - new PortList(openPorts), - new PortList(filteredPorts), - Collections.unmodifiableMap(serviceTypes), - Collections.unmodifiableList(scanFailures) - ); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/PortScanRateLimiter.java b/src/main/java/com/it_jaros/jscanner/scan/engine/PortScanRateLimiter.java deleted file mode 100644 index 92b3bcb..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/PortScanRateLimiter.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -import java.util.concurrent.locks.LockSupport; - -/** - * Per-host rate limiter. Each ScanHostTask creates its own instance and - * shares it with all its ScanPortTasks via constructor. - * - * Java allows one inner class to access another's private members, so this works. - */ -public class PortScanRateLimiter { - private final Object lock = new Object(); - private volatile long nextAllowedTime; - private final long delayInNanos; - private final CancelledToken cancelledToken; - - public PortScanRateLimiter(CancelledToken cancelledToken, long delayInNanos) { - this.cancelledToken = cancelledToken; - this.delayInNanos = delayInNanos; - } - - void apply() { if (delayInNanos <= 0) { - return; - } - synchronized (lock) { - if (cancelledToken.isCancelled()) { - return; - } - long now = System.nanoTime(); - if (nextAllowedTime > now) { - LockSupport.parkNanos(nextAllowedTime - now); - now = System.nanoTime(); // re-read after waking - } - nextAllowedTime = now + delayInNanos; - } - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerState.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerState.java deleted file mode 100644 index c49ea3e..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerState.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -import java.util.concurrent.CompletionService; -import java.util.concurrent.Semaphore; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; - -public record ProducerState<OUTPUT>( - AtomicBoolean running, - AtomicInteger inPipeline, - Semaphore activeWorkers, - CompletionService<OUTPUT> completionService -) {} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerThread.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerThread.java deleted file mode 100644 index e7bcbcc..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/ProducerThread.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -import java.time.Duration; -import java.util.Iterator; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.function.Function; - -public class ProducerThread { - - public static final Duration pollInterval = Duration.ofSeconds(1); - - private final ExecutorService executor; - private final CancelledToken cancelledToken; - - public ProducerThread(ScanExecutionContext context) { - this.executor = context.executorService(); - this.cancelledToken = context.cancelledToken(); - } - - /** - * This method helps to cleanup the code a bit and remove redundancy - * The producer for providing hosts and the one for providing ports - * are similar and the small differences can be handled using a function - * - * @param queue - * @param maxWorkers - * @param taskFactory - * @param <INPUT> - * @param <OUTPUT> - * @return - */ - public <INPUT, OUTPUT> ProducerState<OUTPUT> startProducer( - Iterator<INPUT> queue, - int maxWorkers, - Function<INPUT, Callable<OUTPUT>> taskFactory - ) { - final AtomicInteger inPipeline = new AtomicInteger(0); - final AtomicBoolean running = new AtomicBoolean(true); - final Semaphore activeWorkers = new Semaphore(maxWorkers); - CompletionService<OUTPUT> completionService = new ExecutorCompletionService<>(executor); - executor.submit(() -> { - try { - while (!cancelledToken.isCancelled() && queue.hasNext()) { - // get semaphore and remember if task got submitted - // so in case we fail to submit we release the semaphore - activeWorkers.acquire(); - boolean isTaskSubmitted = false; - try { - // just in case something - // changed while waiting - if (cancelledToken.isCancelled()) { - break; - } - - // get next item and create callable - // using lambda expression - final INPUT item = queue.next(); - Callable<OUTPUT> task = taskFactory.apply(item); - inPipeline.incrementAndGet(); - try { - completionService.submit(task); - isTaskSubmitted = true; - } catch (Throwable e) { - inPipeline.decrementAndGet(); - throw e; - } - } finally { - if (!isTaskSubmitted) { - activeWorkers.release(); - } - } - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } finally { - running.set(false); - } - }); - return new ProducerState<>(running, inPipeline, activeWorkers, completionService); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanExecutionContext.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ScanExecutionContext.java deleted file mode 100644 index 38e9cdb..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanExecutionContext.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -import com.it_jaros.jscanner.scan.Scan; -import com.it_jaros.jscanner.scan.ScanOptions; - -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Semaphore; - -public record ScanExecutionContext( - ScanOptions scanOptions, - ExecutorService executorService, - Semaphore socketLimit, - CancelledToken cancelledToken, - Scan scan, - PortScanRateLimiter portScanRateLimiter -) {} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanHostTask.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ScanHostTask.java deleted file mode 100644 index 7a01d54..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanHostTask.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -import com.it_jaros.jscanner.scan.Scan; -import com.it_jaros.jscanner.scan.domain.PortRange; -import com.it_jaros.jscanner.scan.domain.PortResult; -import com.it_jaros.jscanner.scan.domain.ScanResult; - -import java.io.IOException; -import java.net.InetAddress; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -public class ScanHostTask implements Callable<ScanResult> { - - private final Scan scan; - private final String host; // input parameter - private final ScanExecutionContext context; - private final CancelledToken cancelledToken; - private final boolean disableOnlineCheck; - private final int maxWorkersPerHost; - private final int timeoutInMillis; - private final long delayInNanos; - - public ScanHostTask(Scan scan, String host, ScanExecutionContext context) { - this.scan = scan; - this.host = host; - this.context = context; - this.cancelledToken = context.cancelledToken(); - this.delayInNanos = TimeUnit.MILLISECONDS.toNanos(Math.max(0, context.scanOptions().delayInMillis())); - this.disableOnlineCheck = context.scanOptions().disableOnlineCheck(); - this.maxWorkersPerHost = context.scanOptions().maxWorkersPerHost(); - this.timeoutInMillis = context.scanOptions().timeoutInMillis(); - } - - @Override - public ScanResult call() { - try { - scan.hostStart(); - if (context.cancelledToken().isCancelled()) { - return ScanResult.empty(host); - } - return scanHostPorts(); - } finally { - scan.hostFinish(); - } - } - - private ScanResult scanHostPorts() { - if (!disableOnlineCheck) { - boolean isHostOnline = checkHostOnline(); - if (!isHostOnline) { - // Unreachable host - return ScanResult.empty(host); - } - // online check also sends packets to the target system. - // in order not to violate set delay time - // we wait here too - LockSupport.parkNanos(delayInNanos); - } - - final PortRange portRange = new PortRange(scan.getPorts()); - // producer thread - scan.producerStart(); - ProducerState<PortResult> state = new ProducerThread(context).startProducer( - portRange.iterator(), - maxWorkersPerHost, - port -> new ScanPortTask(host, port, context) - ); - - // consumer is the main thread - // we run as long as the producer is running OR - // as long as things are in pipeline waiting to be processed - // ONLY exception is when cancelled is set - final PortResultAccumulator accumulator = new PortResultAccumulator(host); - while (!cancelledToken.isCancelled() && (state.running().get() || state.inPipeline().get() > 0)) { - try { - PollState<PortResult> poll = getPortResult(state); - if (poll instanceof PollState.Success<PortResult>(PortResult value)) { - accumulator.add(value); - } else if (poll instanceof PollState.Failure(Throwable error)) { - System.err.printf("scanHostPorts(%s): ScanPortTask() failed for with error %s -> %s%n", host, error.getClass().getSimpleName(), error.getMessage()); - } - } catch (InterruptedException ignored) { - Thread.currentThread().interrupt(); - } - } - scan.producerStop(); - - return accumulator.build(); - } - - private boolean checkHostOnline() { - try { - return InetAddress.getByName(host).isReachable(timeoutInMillis); - } catch (IOException e) { - // we ignore this error because it means that the host is probably not online - } - - return false; - } - - private PollState<PortResult> getPortResult(ProducerState<PortResult> state) throws InterruptedException { - Future<PortResult> portResultFuture = state.completionService().poll(ProducerThread.pollInterval.toMillis(), TimeUnit.MILLISECONDS); - if (portResultFuture == null) { - return new PollState.Unavailable<>(); - } - - PortResult portResult; - try { - portResult = portResultFuture.get(); - return new PollState.Success<>(portResult); - } catch (ExecutionException e) { - Throwable cause = e.getCause(); - return new PollState.Failure<>(cause); - } finally { - state.activeWorkers().release(); - state.inPipeline().decrementAndGet(); - } - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanPortTask.java b/src/main/java/com/it_jaros/jscanner/scan/engine/ScanPortTask.java deleted file mode 100644 index 873ec95..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/engine/ScanPortTask.java +++ /dev/null @@ -1,135 +0,0 @@ -package com.it_jaros.jscanner.scan.engine; - -import com.it_jaros.jscanner.scan.Scan; -import com.it_jaros.jscanner.scan.domain.PortResult; -import com.it_jaros.jscanner.scan.domain.PortState; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.ConnectException; -import java.net.InetSocketAddress; -import java.net.NoRouteToHostException; -import java.nio.ByteBuffer; -import java.nio.channels.SocketChannel; -import java.util.concurrent.Callable; -import java.util.concurrent.Semaphore; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.locks.LockSupport; - -public class ScanPortTask implements Callable<PortResult> { - - private static final int READ_BUFFER_SIZE = 1024; - - private final CancelledToken cancelledToken; - private final PortScanRateLimiter portScanRateLimiter; // per-host shared limiter - private final Scan scan; - private final Semaphore socketLimit; - private final String host; - private final boolean bannerRecognition; - private final int port; - private final long timeoutInNanos; - - ScanPortTask(String host, int port, ScanExecutionContext context) { - this.host = host; - this.port = port; - this.bannerRecognition = context.scanOptions().bannerRecognition(); - this.cancelledToken = context.cancelledToken(); - this.portScanRateLimiter = context.portScanRateLimiter(); - this.scan = context.scan(); - this.socketLimit = context.socketLimit(); - this.timeoutInNanos = TimeUnit.MILLISECONDS.toNanos(context.scanOptions().timeoutInMillis()); - } - - @Override - public PortResult call() throws Exception { - try { - socketLimit.acquire(); - scan.portStart(); - portScanRateLimiter.apply(); - if (cancelledToken.isCancelled()) { - return PortResult.empty(); - } - return checkPort(); - } finally { - scan.portFinish(); - socketLimit.release(); - } - } - - private PortResult checkPort() { - PortResult result = new PortResult(); - result.setPort(port); - result.setState(PortState.UNKNOWN); - try(SocketChannel socketChannel = SocketChannel.open()) { - socketChannel.configureBlocking(false); - socketChannel.connect(new InetSocketAddress(host, port)); - final long deadlineNanos = System.nanoTime() + timeoutInNanos; - final long waitInNanos = TimeUnit.MILLISECONDS.toNanos(1000); - boolean isConnected = socketChannel.finishConnect(); - while (!cancelledToken.isCancelled() && !isConnected) { - long remainingNanos = deadlineNanos - System.nanoTime(); - if (remainingNanos <= 0) { - break; - } - LockSupport.parkNanos(Math.min(waitInNanos, remainingNanos)); - isConnected = socketChannel.finishConnect(); - } - - if (cancelledToken.isCancelled()) { - return result; - } - - if (isConnected) { - result.setState(PortState.OPEN); - if (bannerRecognition) { - result.setBanner(getBanner(socketChannel)); - } - } else { - result.setState(PortState.FILTERED); - } - } catch (ConnectException ignored) { - result.setState(PortState.CLOSED); - } catch (NoRouteToHostException ignored) { - // NoRouteToHostException: this can be safely ignored because the port is closed if a host is unreachable - // Will happen a lot when scanning for open ports, so not needed - } catch (IOException e) { - result.setException(e); - } - - return result; - } - - private byte[] getBanner(SocketChannel socketChannel) { - try { - return tryReadFrom(socketChannel); - } catch (IOException ignore) { - // ignore - } - - return null; - } - - private byte[] tryReadFrom(SocketChannel socketChannel) throws IOException { - byte[] buffer = new byte[READ_BUFFER_SIZE]; - ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); - - boolean timedout = false; - long deadlineNanos = System.nanoTime() + timeoutInNanos; - while (!timedout) { - int bytesRead = socketChannel.read(ByteBuffer.wrap(buffer)); - if (bytesRead == -1) { - break; - } - if (bytesRead > 0) { - byteArrayOutputStream.write(buffer, 0, bytesRead); - } - long remaining = deadlineNanos - System.nanoTime(); - if (remaining <= 0) { - timedout = true; - } else { - LockSupport.parkNanos(Math.min(remaining, TimeUnit.MILLISECONDS.toNanos(1000))); - } - } - return byteArrayOutputStream.toByteArray(); - } -} diff --git a/src/main/java/com/it_jaros/jscanner/scan/service/ServiceDetector.java b/src/main/java/com/it_jaros/jscanner/scan/service/ServiceDetector.java deleted file mode 100644 index 93e05d4..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/service/ServiceDetector.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.it_jaros.jscanner.scan.service; - -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.regex.Pattern; - -public class ServiceDetector { - private static final List<ServicePattern> patterns = List.of( - new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bDNS\\b")), - new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bBIND\\b")), - new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bNSD\\b")), - new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bPowerDNS\\b")), - new ServicePattern(ServiceType.DNS, Pattern.compile("(?i)\\bUnbound\\b")), - new ServicePattern(ServiceType.FTP, Pattern.compile("(?i)^220.*\\bFTP\\b")), - new ServicePattern(ServiceType.HTTP, Pattern.compile("(?i)\\bServer:\\s*")), - new ServicePattern(ServiceType.HTTP, Pattern.compile("(?i)^HTTP/")), - new ServicePattern(ServiceType.IMAP, Pattern.compile("(?i)^\\* OK.*\\bIMAP\\b")), - new ServicePattern(ServiceType.POP3, Pattern.compile("(?i)^\\+OK.*\\bPOP3\\b")), - new ServicePattern(ServiceType.SMTP, Pattern.compile("(?i)^220.*\\bESMTP\\b")), - new ServicePattern(ServiceType.SMTP, Pattern.compile("(?i)^220.*\\bSMTP\\b")), - new ServicePattern(ServiceType.SSH, Pattern.compile("(?i)^SSH-")) - ); - - public static ServiceType detect(byte[] banner) { - if (banner == null || banner.length == 0) { - return ServiceType.UNKNOWN; - } - - String bannerString = new String(banner, StandardCharsets.UTF_8); - for (ServicePattern servicePattern : patterns) { - if (servicePattern.matches(bannerString)) { - return servicePattern.serviceType(); - } - } - - return ServiceType.UNKNOWN; - } - - private record ServicePattern( - ServiceType serviceType, - Pattern pattern - ) { - boolean matches(String banner) { - return pattern.matcher(banner).find(); - } - } -}
\ No newline at end of file diff --git a/src/main/java/com/it_jaros/jscanner/scan/service/ServiceType.java b/src/main/java/com/it_jaros/jscanner/scan/service/ServiceType.java deleted file mode 100644 index 86b282a..0000000 --- a/src/main/java/com/it_jaros/jscanner/scan/service/ServiceType.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.it_jaros.jscanner.scan.service; - -public enum ServiceType { - HTTP, FTP, SMTP, IMAP, POP3, UNKNOWN, DNS, SSH -} |
