From a455e319a714ad51c03be6b4e76ae5c91dc8a0f7 Mon Sep 17 00:00:00 2001 From: Swofty Date: Thu, 2 Feb 2023 00:02:41 +1100 Subject: [PATCH 01/11] Added an is equal to check for the minimum player filter --- .../me/replydev/qubo/CommandLineArgs.java | 10 ++++--- .../java/me/replydev/qubo/PingRunnable.java | 29 +++++++++---------- .../java/me/replydev/qubo/QuboInstance.java | 16 ++++++++-- .../java/me/replydev/utils/SearchFilter.java | 15 ++++++++++ 4 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 src/main/java/me/replydev/utils/SearchFilter.java diff --git a/src/main/java/me/replydev/qubo/CommandLineArgs.java b/src/main/java/me/replydev/qubo/CommandLineArgs.java index 53e3319..a1e338a 100644 --- a/src/main/java/me/replydev/qubo/CommandLineArgs.java +++ b/src/main/java/me/replydev/qubo/CommandLineArgs.java @@ -16,7 +16,7 @@ public class CommandLineArgs { int timeout; String filterVersion; String filterMotd; - int minimumPlayers; + int filterPlayers; int count; @NonFinal @@ -34,9 +34,11 @@ public CommandLineArgs(String[] command) throws NumberFormatException { portRange = new PortList(cmd.getOptionValue("p")); skipCommon = !cmd.hasOption("all"); timeout = Integer.parseInt(cmd.getOptionValue("t")); - filterVersion = cmd.getOptionValue("v", ""); - filterMotd = cmd.getOptionValue("m", ""); - minimumPlayers = Integer.parseInt(cmd.getOptionValue("o", "-1")); + + filterVersion = cmd.getOptionValue("v", null); + filterMotd = cmd.getOptionValue("m", null); + filterPlayers = Integer.parseInt(cmd.getOptionValue("o", "-")); + count = Integer.parseInt(cmd.getOptionValue("c", "1")); } diff --git a/src/main/java/me/replydev/qubo/PingRunnable.java b/src/main/java/me/replydev/qubo/PingRunnable.java index 7354833..32a0f9b 100644 --- a/src/main/java/me/replydev/qubo/PingRunnable.java +++ b/src/main/java/me/replydev/qubo/PingRunnable.java @@ -4,6 +4,7 @@ import java.util.concurrent.atomic.AtomicInteger; import lombok.Builder; import lombok.extern.slf4j.Slf4j; +import me.replydev.utils.SearchFilter; import org.replydev.mcping.MCPinger; import org.replydev.mcping.PingOptions; import org.replydev.mcping.model.ServerResponse; @@ -13,13 +14,11 @@ public class PingRunnable implements Runnable { private final PingOptions pingOptions; + private final SearchFilter filter; private final int count; private final CommandLineArgs commandLineArgs; private final AtomicInteger foundServers; private final AtomicInteger unfilteredFoundServers; - private final String filterVersion; - private final String filterMotd; - private final int minPlayer; public void run() { for (int i = 0; i < count; i++) { @@ -55,22 +54,20 @@ private String buildEntry(ServerResponse serverResponse) { ); } + /** + * If checks placed in order of commonality of argument + * + * @author Swofty#0001 + */ private boolean isFiltered(ServerResponse serverResponse) { - if ( - filterVersion != null && !serverResponse.getVersion().getName().contains(filterVersion) - ) { - return false; + if (serverResponse.getPlayers().getOnline() <= filter.getMinimumPlayers()) { + return true; } - if (minPlayer > serverResponse.getPlayers().getOnline()) { - return false; + + if (serverResponse.getDescription().getText().contains(filter.getMotd())) { + return true; } - return ( - filterMotd == null || - ( - !filterMotd.isBlank() && - serverResponse.getDescription().getText().contains(filterMotd) - ) - ); + return serverResponse.getVersion().getName().contains(filter.getVersion()); } } diff --git a/src/main/java/me/replydev/qubo/QuboInstance.java b/src/main/java/me/replydev/qubo/QuboInstance.java index 5fb28b4..c199570 100644 --- a/src/main/java/me/replydev/qubo/QuboInstance.java +++ b/src/main/java/me/replydev/qubo/QuboInstance.java @@ -13,6 +13,7 @@ import lombok.extern.slf4j.Slf4j; import me.replydev.utils.IpList; import me.replydev.utils.PortList; +import me.replydev.utils.SearchFilter; import org.replydev.mcping.PingOptions; @Slf4j @@ -42,10 +43,21 @@ public class QuboInstance { @Getter private final AtomicInteger unfilteredFoundServers; + @Getter + private final SearchFilter filter; + public QuboInstance(CommandLineArgs commandLineArgs) { this.commandLineArgs = commandLineArgs; this.foundServers = new AtomicInteger(); this.unfilteredFoundServers = new AtomicInteger(); + + SearchFilter filter = new SearchFilter(); + + if (commandLineArgs.getFilterPlayers() != 0) filter.setMinimumPlayers(commandLineArgs.getFilterPlayers()); + if (commandLineArgs.getFilterMotd() != null) filter.setMotd(commandLineArgs.getFilterMotd()); + if (commandLineArgs.getFilterVersion() != null) filter.setMotd(commandLineArgs.getFilterVersion()); + + this.filter = filter; } public void run() { @@ -96,9 +108,7 @@ private boolean checkServersExecutor() throws InterruptedException, NumberFormat .foundServers(foundServers) .unfilteredFoundServers(unfilteredFoundServers) .count(commandLineArgs.getCount()) - .filterMotd(commandLineArgs.getFilterMotd()) - .filterVersion(commandLineArgs.getFilterVersion()) - .minPlayer(commandLineArgs.getMinimumPlayers()) + .filter(filter) .build(); checkService.execute(pingJob); diff --git a/src/main/java/me/replydev/utils/SearchFilter.java b/src/main/java/me/replydev/utils/SearchFilter.java new file mode 100644 index 0000000..7ad137f --- /dev/null +++ b/src/main/java/me/replydev/utils/SearchFilter.java @@ -0,0 +1,15 @@ +package me.replydev.utils; + +import lombok.Getter; +import lombok.Setter; + +/** + * @author Swofty#0001 + */ +@Getter +@Setter +public class SearchFilter { + private int minimumPlayers = -1; + private String version = null; + private String motd = null; +} From fb75697edcbb6511032172d5999193e21fc3b885 Mon Sep 17 00:00:00 2001 From: Swofty Date: Thu, 23 Nov 2023 15:26:29 +1100 Subject: [PATCH 02/11] Added tons of documentation and made other small miscellaneous changes --- pom.xml | 4 +- src/main/java/me/replydev/qubo/CLI.java | 91 +++++++++------ .../me/replydev/qubo/CommandLineArgs.java | 98 +++++++--------- src/main/java/me/replydev/qubo/Info.java | 9 -- src/main/java/me/replydev/qubo/Main.java | 12 +- .../java/me/replydev/qubo/PingRunnable.java | 81 +++++++------ .../java/me/replydev/qubo/QuboInstance.java | 108 +++++++++--------- .../{utils => qubo}/SearchFilter.java | 4 +- src/main/java/me/replydev/utils/IpList.java | 96 ++++++++++------ src/main/java/me/replydev/utils/PortList.java | 61 +++++----- 10 files changed, 305 insertions(+), 259 deletions(-) delete mode 100644 src/main/java/me/replydev/qubo/Info.java rename src/main/java/me/replydev/{utils => qubo}/SearchFilter.java (76%) diff --git a/pom.xml b/pom.xml index f95ac81..07ba9de 100644 --- a/pom.xml +++ b/pom.xml @@ -99,8 +99,8 @@ 19 --enable-preview - 19 - 19 + 18 + 18 diff --git a/src/main/java/me/replydev/qubo/CLI.java b/src/main/java/me/replydev/qubo/CLI.java index 54442d2..6b2a743 100644 --- a/src/main/java/me/replydev/qubo/CLI.java +++ b/src/main/java/me/replydev/qubo/CLI.java @@ -5,68 +5,87 @@ import lombok.experimental.UtilityClass; import lombok.extern.slf4j.Slf4j; +/** + * The CLI class handles the command line interface functionality, including + * initializing the application, printing the logo, and starting the standard + * run process. + * @author ReplyDev + */ @Slf4j @UtilityClass public class CLI { + private static final String VERSION = "1.1.0"; + + private static final String LOGO_TEMPLATE = """ + ____ _ _____ \s + / __ \\ | | / ____| \s + | | | |_ _| |__ ___| (___ ___ __ _ _ __ _ __ ___ _ __\s + | | | | | | | '_ \\ / _ \\\\___ \\ / __/ _` | '_ \\| '_ \\ / _ \\ '__| + | |__| | |_| | |_) | (_) |___) | (_| (_| | | | | | | | __/ | \s + \\___\\_\\\\__,_|_.__/ \\___/_____/ \\___\\__,_|_| |_|_| |_|\\___|_| \s + + By @replydev on Telegram + Version %s + """; + private QuboInstance quboInstance; + /** + * Initializes the application with given command line arguments. + * @param args The command line arguments. + */ void init(String[] args) { printLogo(); checkEncodingParameter(); standardRun(args); - log.info( - "Scan terminated - " + - quboInstance.getFoundServers().get() + - " (" + - quboInstance.getUnfilteredFoundServers().get() + - " in total)" + log.info("Scan terminated - {} ({}) in total", + quboInstance.getFoundServers().get(), + quboInstance.getUnfilteredFoundServers().get() ); System.exit(0); } - private void checkEncodingParameter() { - if (!isUTF8Mode()) { - log.info("The scanner isn't running in UTF-8 mode!"); - log.info( - "Put \"-Dfile.encoding=UTF-8\" in JVM args in order to run the program correctly!" - ); - System.exit(-1); - } - } - + /** + * Prints the application logo with the current version. + */ private void printLogo() { - log.info( - String.format( - """ - ____ _ _____ \s - / __ \\ | | / ____| \s - | | | |_ _| |__ ___| (___ ___ __ _ _ __ _ __ ___ _ __\s - | | | | | | | '_ \\ / _ \\\\___ \\ / __/ _` | '_ \\| '_ \\ / _ \\ '__| - | |__| | |_| | |_) | (_) |___) | (_| (_| | | | | | | | __/ | \s - \\___\\_\\\\__,_|_.__/ \\___/_____/ \\___\\__,_|_| |_|_| |_|\\___|_| \s - - By @replydev on Telegram - Version %s - """, - Info.VERSION - ) - ); + log.info(LOGO_TEMPLATE, VERSION); } + /** + * Executes the standard application run process. + * @param args The command line arguments. + */ private void standardRun(String[] args) { - CommandLineArgs commandLineArgs = new CommandLineArgs(args); - quboInstance = new QuboInstance(commandLineArgs); try { + CommandLineArgs commandLineArgs = new CommandLineArgs(args); + quboInstance = new QuboInstance(commandLineArgs); quboInstance.run(); } catch (NumberFormatException e) { - commandLineArgs.showHelpAndExit(); + log.error("There was an error parsing the numbers.", e); + new CommandLineArgs(args).showHelpAndExit(); + } + } + + /** + * Checks if the JVM is running in UTF-8 encoding mode and exits if not. + */ + private void checkEncodingParameter() { + if (!isUTF8Mode()) { + log.error("The scanner isn't running in UTF-8 mode!"); + log.error("Put \"-Dfile.encoding=UTF-8\" in JVM args in order to run the program correctly!"); + System.exit(-1); } } + /** + * Checks if UTF-8 mode is enabled for file encoding. + * @return true if UTF-8 mode is set, false otherwise. + */ private boolean isUTF8Mode() { List arguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); - return arguments.contains("-Dfile.encoding=UTF-8"); + return arguments.stream().anyMatch(arg -> arg.equals("-Dfile.encoding=UTF-8")); } } diff --git a/src/main/java/me/replydev/qubo/CommandLineArgs.java b/src/main/java/me/replydev/qubo/CommandLineArgs.java index 6754e25..a0d71b2 100644 --- a/src/main/java/me/replydev/qubo/CommandLineArgs.java +++ b/src/main/java/me/replydev/qubo/CommandLineArgs.java @@ -1,26 +1,36 @@ package me.replydev.qubo; +import lombok.Getter; +import lombok.Setter; import lombok.Value; import lombok.experimental.NonFinal; import me.replydev.utils.IpList; import me.replydev.utils.PortList; -import me.replydev.utils.SearchFilter; import org.apache.commons.cli.*; -@Value +/** + * This class is responsible for parsing the command line arguments. + * @author ReplyDev, Swofty + */ +@Getter public class CommandLineArgs { - Options options; - IpList ipList; - PortList portRange; - boolean skipCommon; - int timeout; - SearchFilter searchFilter; - int count; - - @NonFinal - CommandLine cmd; - + private final Options options; + private final IpList ipList; + private final PortList portRange; + private final boolean skipCommon; + private final int timeout; + private final SearchFilter searchFilter; + private final int count; + + @Setter + private CommandLine cmd; + + /** + * Constructor for CommandLineArgs. + * @param command The array of command line arguments to be parsed. + * @throws NumberFormatException If parsing of numeric values fails. + */ public CommandLineArgs(String[] command) throws NumberFormatException { options = buildOptions(); CommandLineParser parser = new DefaultParser(); @@ -45,57 +55,33 @@ public CommandLineArgs(String[] command) throws NumberFormatException { count = Integer.parseInt(cmd.getOptionValue("c", "1")); } + /** + * Builds the command line options. + * @see Options + * @return Options The command line options. + */ private static Options buildOptions() { - Option iprange = new Option("i", "iprange", true, "The IP range to scan"); - iprange.setRequired(true); - - Option portrange = new Option("p", "portrange", true, "The range of ports to scan"); - portrange.setRequired(true); - - Option timeout = new Option("t", "timeout", true, "TCP connection timeout"); - timeout.setRequired(true); - - Option count = new Option("c", "pingcount", true, "Number of ping retries"); - count.setRequired(false); - - Option all = new Option("a", false, "Force Qubo to scan broadcast IPs and common ports"); - all.setRequired(false); - - Option filterVersion = new Option( - "v", - "filterversion", - true, - "Show only hits with given version" - ); - filterVersion.setRequired(false); - - Option filterMotd = new Option("m", "filtermotd", true, "Show only hits with given motd"); - filterMotd.setRequired(false); - - Option filterOn = new Option( - "o", - "minonline", - true, - "Show only hits with at least players online" - ); - filterOn.setRequired(false); - Options options = new Options(); - options.addOption(iprange); - options.addOption(portrange); - options.addOption(timeout); - options.addOption(count); - options.addOption(all); - options.addOption(filterVersion); - options.addOption(filterMotd); - options.addOption(filterOn); + + options.addRequiredOption("i", "iprange", true, "The IP range to scan"); + options.addRequiredOption("p", "portrange", true, "The range of ports to scan"); + options.addRequiredOption("t", "timeout", true, "TCP connection timeout"); + options.addOption("c", "pingcount", true, "Number of ping retries"); + options.addOption("a", "all", false, "Force to scan broadcast IPs and common ports"); + options.addOption("v", "filterversion", true, "Show only hits with given version"); + options.addOption("m", "filtermotd", true, "Show only hits with given motd"); + options.addOption("o", "minonline", true, "Show only hits with at least players online"); return options; } + /** + * Prints help information using the command line options and exits the program + * with exit code -1. + */ public void showHelpAndExit() { HelpFormatter formatter = new HelpFormatter(); - formatter.printHelp("-range -ports -th -ti ", options); + formatter.printHelp("Usage: -i -p -t [-c ] [...]", options); System.exit(-1); } } diff --git a/src/main/java/me/replydev/qubo/Info.java b/src/main/java/me/replydev/qubo/Info.java deleted file mode 100644 index bdc3937..0000000 --- a/src/main/java/me/replydev/qubo/Info.java +++ /dev/null @@ -1,9 +0,0 @@ -package me.replydev.qubo; - -import lombok.experimental.UtilityClass; - -@UtilityClass -public class Info { - - public final String VERSION = "1.0.0"; -} diff --git a/src/main/java/me/replydev/qubo/Main.java b/src/main/java/me/replydev/qubo/Main.java index 32084f3..28f32bf 100644 --- a/src/main/java/me/replydev/qubo/Main.java +++ b/src/main/java/me/replydev/qubo/Main.java @@ -1,8 +1,18 @@ package me.replydev.qubo; +/** + * The Main class is the entry point of Qubo. + * It delegates the start of the application to the CLI class. + * @see CLI + */ public class Main { + /** + * The main method that is executed when the program is started. + * It calls the init method of the CLI class, passing along any command line arguments. + * @param args Command line arguments passed to the program. + */ public static void main(String[] args) { CLI.init(args); } -} +} \ No newline at end of file diff --git a/src/main/java/me/replydev/qubo/PingRunnable.java b/src/main/java/me/replydev/qubo/PingRunnable.java index 5d50d9f..1db2f5f 100644 --- a/src/main/java/me/replydev/qubo/PingRunnable.java +++ b/src/main/java/me/replydev/qubo/PingRunnable.java @@ -5,11 +5,15 @@ import java.util.concurrent.atomic.AtomicInteger; import lombok.Builder; import lombok.extern.slf4j.Slf4j; -import me.replydev.utils.SearchFilter; import org.replydev.mcping.MCPinger; import org.replydev.mcping.PingOptions; import org.replydev.mcping.model.ServerResponse; +/** + * The PingRunnable class is designed to perform a ping operation on a server + * multiple times, as defined by the count, and update the found server counts. + * @author ReplyDev, Swofty + */ @Builder @Slf4j public class PingRunnable implements Runnable { @@ -21,62 +25,65 @@ public class PingRunnable implements Runnable { private final AtomicInteger foundServers; private final AtomicInteger unfilteredFoundServers; + /** + * Executes the ping operation the specified number of times. + */ + @Override public void run() { for (int i = 0; i < count; i++) { - MCPinger mcPinger = MCPinger.builder().pingOptions(pingOptions).build(); try { + MCPinger mcPinger = MCPinger.builder().pingOptions(pingOptions).build(); ServerResponse serverResponse = mcPinger.fetchData(); - unfilteredFoundServers.incrementAndGet(); - if (!isFiltered(serverResponse)) { - foundServers.incrementAndGet(); - log.info(buildEntry(serverResponse)); - } + processServerResponse(serverResponse); } catch (IOException ignored) { - // Connection has failed, no need to log + // Connection has failed, no need to log. } } } + /** + * Processes the server response and updates server counts accordingly. + * @param serverResponse The response from the server. + */ + private void processServerResponse(ServerResponse serverResponse) { + unfilteredFoundServers.incrementAndGet(); + if (!isFiltered(serverResponse)) { + foundServers.incrementAndGet(); + log.info(buildEntry(serverResponse)); + } + } + + /** + * Builds a log entry string for a server response. + * @param serverResponse The server response to build the log entry for. + * @return A string representation of the log entry. + */ private String buildEntry(ServerResponse serverResponse) { - return ( - pingOptions.getHostname() + - ':' + - pingOptions.getPort() + - " -> " + - '(' + - serverResponse.getVersion().getName() + - ") - (" + - serverResponse.getPlayers().getOnline() + - '/' + - serverResponse.getPlayers().getMax() + - ") - (" + - serverResponse.getDescription().getText() + - ')' - ); + return String.format("%s:%d -> (%s) - (%d/%d) - (%s)", + pingOptions.getHostname(), + pingOptions.getPort(), + serverResponse.getVersion().getName(), + serverResponse.getPlayers().getOnline(), + serverResponse.getPlayers().getMax(), + serverResponse.getDescription().getText()); } /** - * If checks placed in order of commonality of argument - * - * @author Swofty#0001 + * Checks if the server response meets the filter criteria. + * @param serverResponse The server response to check. + * @return True if the response does not meet the criteria (is filtered out), false otherwise. */ private boolean isFiltered(ServerResponse serverResponse) { - if (serverResponse.getPlayers().getOnline() <= filter.getMinimumPlayers()) { + if (filter.getMinimumPlayers() > 0 && serverResponse.getPlayers().getOnline() < filter.getMinimumPlayers()) { return true; } - if ( - serverResponse - .getDescription() - .getText() - .contains(Optional.ofNullable(filter.getMotd()).orElse("")) - ) { + String motdFilter = Optional.ofNullable(filter.getMotd()).orElse(""); + if (!motdFilter.isEmpty() && !serverResponse.getDescription().getText().contains(motdFilter)) { return true; } - return serverResponse - .getVersion() - .getName() - .contains(Optional.ofNullable(filter.getVersion()).orElse("")); + String versionFilter = Optional.ofNullable(filter.getVersion()).orElse(""); + return !versionFilter.isEmpty() && !serverResponse.getVersion().getName().contains(versionFilter); } } diff --git a/src/main/java/me/replydev/qubo/QuboInstance.java b/src/main/java/me/replydev/qubo/QuboInstance.java index 8788238..2103587 100644 --- a/src/main/java/me/replydev/qubo/QuboInstance.java +++ b/src/main/java/me/replydev/qubo/QuboInstance.java @@ -15,51 +15,61 @@ import me.replydev.utils.PortList; import org.replydev.mcping.PingOptions; +/** + * QuboInstance handles the scanning process for the application. + * @author ReplyDev, Swofty + */ @Slf4j public class QuboInstance { - private static final Set COMMON_PORTS = Set.of( - 25, - 80, - 443, - 20, - 21, - 22, - 23, - 143, - 3306, - 3389, - 53, - 67, - 68, - 110 + private static final Set STANDARDIZED_PORTS = Set.of( + 20, // File Transfer Protocol (FTP) data transfer + 21, // File Transfer Protocol (FTP) command control + 22, // Secure Shell (SSH) protocol for secure logins, file transfers, and port forwarding + 23, // Telnet protocol for unencrypted text communications + 25, // Simple Mail Transfer Protocol (SMTP) for email routing between mail servers + 53, // Domain Name System (DNS) service for translating domain names to IP addresses + 67, // Bootstrap Protocol (BOOTP) Server; often used by DHCP + 68, // Bootstrap Protocol (BOOTP) Client; often used by DHCP + 80, // Hypertext Transfer Protocol (HTTP) used for unsecured web traffic + 110, // Post Office Protocol (POP3) used by email clients to retrieve messages from a server + 143, // Internet Message Access Protocol (IMAP) for email retrieval + 443, // Hypertext Transfer Protocol Secure (HTTPS) for secure web traffic + 3306, // Default port for the MySQL database management system + 3389, // Remote Desktop Protocol (RDP) for Windows-based systems remote management + 6379 // Redis data structure store ); + public final CommandLineArgs commandLineArgs; @Getter - private final AtomicInteger foundServers; - + private final AtomicInteger foundServers = new AtomicInteger(); @Getter - private final AtomicInteger unfilteredFoundServers; + private final AtomicInteger unfilteredFoundServers = new AtomicInteger(); + /** + * Constructs a QuboInstance with the given command line arguments. + * @param commandLineArgs The command line arguments for the scan configuration. + */ public QuboInstance(CommandLineArgs commandLineArgs) { this.commandLineArgs = commandLineArgs; - this.foundServers = new AtomicInteger(); - this.unfilteredFoundServers = new AtomicInteger(); } + /** + * Starts the scanning process. + */ public void run() { ZonedDateTime start = ZonedDateTime.now(); try { if (!checkServersExecutor()) { - log.error("Something has gone wrong in thread termination awaiting..."); + log.error("Something went wrong awaiting thread termination."); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); - throw new RuntimeException(e); + log.error("Thread was interrupted during execution.", e); } ZonedDateTime end = ZonedDateTime.now(); - log.info(getScanTime(start, end)); + log.info(calculateScanDuration(start, end)); } private boolean checkServersExecutor() throws InterruptedException, NumberFormatException { @@ -79,7 +89,7 @@ private boolean checkServersExecutor() throws InterruptedException, NumberFormat PortList portRange = commandLineArgs.getPortRange(); for (int port : portRange) { - if (commandLineArgs.isSkipCommon() && isCommonPort(port)) { + if (commandLineArgs.isSkipCommon() && isStandardizedPort(port)) { continue; } @@ -107,8 +117,8 @@ private boolean checkServersExecutor() throws InterruptedException, NumberFormat } } - private boolean isCommonPort(int port) { - return !commandLineArgs.isSkipCommon() || COMMON_PORTS.contains(port); + private boolean isStandardizedPort(int port) { + return !commandLineArgs.isSkipCommon() || STANDARDIZED_PORTS.contains(port); } private boolean isLikelyBroadcast(InetAddress address) { @@ -116,34 +126,22 @@ private boolean isLikelyBroadcast(InetAddress address) { return bytes[bytes.length - 1] == 0 || bytes[bytes.length - 1] == (byte) 0xFF; } - public String getScanTime(ZonedDateTime start, ZonedDateTime end) { - ZonedDateTime tempDateTime = ZonedDateTime.from(start); - - long years = tempDateTime.until(end, ChronoUnit.YEARS); - tempDateTime = tempDateTime.plusYears(years); - - long months = tempDateTime.until(end, ChronoUnit.MONTHS); - tempDateTime = tempDateTime.plusMonths(months); - - long days = tempDateTime.until(end, ChronoUnit.DAYS); - tempDateTime = tempDateTime.plusDays(days); - - long hours = tempDateTime.until(end, ChronoUnit.HOURS); - tempDateTime = tempDateTime.plusHours(hours); - - long minutes = tempDateTime.until(end, ChronoUnit.MINUTES); - tempDateTime = tempDateTime.plusMinutes(minutes); - - long seconds = tempDateTime.until(end, ChronoUnit.SECONDS); - - StringBuilder builder = new StringBuilder(); - if (seconds != 0) builder.append(seconds).append(" seconds"); - if (minutes != 0) builder.insert(0, minutes + " minutes, "); - if (hours != 0) builder.insert(0, minutes + " hours, "); - if (days != 0) builder.insert(0, minutes + " days, "); - if (months != 0) builder.insert(0, minutes + " months, "); - if (years != 0) builder.insert(0, minutes + " years, "); - builder.insert(0, "Scan time: "); - return builder.toString(); + /** + * Calculates the duration of the scan. + * + * @param start The start time of the scan. + * @param end The end time of the scan. + * @return A string representation of the scan duration. + */ + private String calculateScanDuration(ZonedDateTime start, ZonedDateTime end) { + long seconds = ChronoUnit.SECONDS.between(start, end); + long minutes = seconds / 60; + long hours = minutes / 60; + long days = hours / 24; + long months = days / 30; // Approximation + long years = days / 365; // Approximation + + return String.format("Scan time: %d years, %d months, %d days, %d hours, %d minutes, %d seconds", + years, months % 12, days % 30, hours % 24, minutes % 60, seconds % 60); } } diff --git a/src/main/java/me/replydev/utils/SearchFilter.java b/src/main/java/me/replydev/qubo/SearchFilter.java similarity index 76% rename from src/main/java/me/replydev/utils/SearchFilter.java rename to src/main/java/me/replydev/qubo/SearchFilter.java index 03d728a..4b4f156 100644 --- a/src/main/java/me/replydev/utils/SearchFilter.java +++ b/src/main/java/me/replydev/qubo/SearchFilter.java @@ -1,10 +1,10 @@ -package me.replydev.utils; +package me.replydev.qubo; import lombok.Builder; import lombok.Value; /** - * @author Swofty#0001 + * @author Swofty */ @Value @Builder diff --git a/src/main/java/me/replydev/utils/IpList.java b/src/main/java/me/replydev/utils/IpList.java index 7d0bfdd..e1c49ef 100644 --- a/src/main/java/me/replydev/utils/IpList.java +++ b/src/main/java/me/replydev/utils/IpList.java @@ -7,6 +7,9 @@ import java.util.StringTokenizer; import java.util.regex.Pattern; +/** + * Represents a range of IP addresses defined by a start and end IP. + */ public class IpList implements Iterable { private static final Pattern IP_RANGE_PATTERN = Pattern.compile( @@ -14,86 +17,109 @@ public class IpList implements Iterable { ); private static final int[] EMPTY_INT_ARRAY = new int[0]; - private final long start; - private final long end; + private final long startIpLong; + private final long endIpLong; + /** + * Constructs an IpList from a given IP range string. + * @param ipRange A string representing the IP range. + */ public IpList(String ipRange) { if (!validRange(ipRange)) { - throw new IllegalArgumentException(ipRange + " is not a valid ip address"); + throw new IllegalArgumentException("The IP range " + ipRange + " is not valid."); } IPAddressSeqRange range = new IPAddressString(ipRange).getSequentialRange(); if (range == null) { - throw new IllegalArgumentException(ipRange + " is not a valid ip address"); + throw new IllegalArgumentException("The IP range " + ipRange + " cannot be resolved to a valid range."); } - String ipStart = range.getLower().toString(); - String ipEnd = range.getUpper().toString(); - this.start = hostnameToLong(ipStart); - this.end = hostnameToLong(ipEnd); + this.startIpLong = ipToLong(range.getLower().toString()); + this.endIpLong = ipToLong(range.getUpper().toString()); } + /** + * Checks if the IP range string is valid. + * @param ip A string representing the IP range. + * @return A boolean indicating if the IP range is valid. + */ private static boolean validRange(String ip) { return ip != null && IP_RANGE_PATTERN.matcher(ip).matches(); } - private static long hostnameToLong(String host) { - long ip = 0; - if (!Character.isDigit(host.charAt(0))) return -1; - int[] addr = ipv4ToIntArray(host); - if (addr.length == 0) { + /** + * Converts an IP address string to its long representation. + * @param ip A string representation of an IP address. + * @return A long representing the IP address. + */ + private static long ipToLong(String ip) { + long result = 0; + if (!Character.isDigit(ip.charAt(0))) return -1; + int[] octets = ipv4ToIntArray(ip); + if (octets.length == 0) { return -1; } - for (int i = 0; i < addr.length; ++i) { - ip += ((long) (Math.max(addr[i], 0))) << 8 * (3 - i); + for (int i = 0; i < octets.length; ++i) { + result += ((long) (Math.max(octets[i], 0))) << 8 * (3 - i); } - return ip; + return result; } - private static int[] ipv4ToIntArray(String host) { - int[] address = { -1, -1, -1, -1 }; - int i = 0; - StringTokenizer tokens = new StringTokenizer(host, "."); - if (tokens.countTokens() > 4) { + /** + * Converts an IPv4 address string to an array of integers. + * @param ip A string representation of an IPv4 address. + * @return An array of integers representing the IPv4 address. + */ + private static int[] ipv4ToIntArray(String ip) { + int[] octets = { -1, -1, -1, -1 }; + int index = 0; + StringTokenizer tokenizer = new StringTokenizer(ip, "."); + if (tokenizer.countTokens() > 4) { return EMPTY_INT_ARRAY; } - while (tokens.hasMoreTokens()) { + while (tokenizer.hasMoreTokens()) { try { - address[i++] = Integer.parseInt(tokens.nextToken()) & 0xFF; - } catch (NumberFormatException nfe) { + octets[index++] = Integer.parseInt(tokenizer.nextToken()) & 0xFF; + } catch (NumberFormatException e) { return EMPTY_INT_ARRAY; } } - return address; + return octets; } - private static String longToIpv4(long address) { - StringBuilder sb = new StringBuilder(); + /** + * Converts a long representation of an IP address to a string. + * + * @param ipLong A long representing the IP address. + * @return A string representation of the IP address. + */ + private static String longToIpv4(long ipLong) { + StringBuilder ipBuilder = new StringBuilder(); for (int i = 0, shift = 24; i < 4; i++, shift -= 8) { - long value = (address >> shift) & 0xff; - sb.append(value); + long value = (ipLong >> shift) & 0xff; + ipBuilder.append(value); if (i != 3) { - sb.append('.'); + ipBuilder.append('.'); } } - return sb.toString(); + return ipBuilder.toString(); } @Override public Iterator iterator() { return new Iterator<>() { - private long index = start; + private long currentIndex = startIpLong; @Override public boolean hasNext() { - return index <= end; + return currentIndex <= endIpLong; } @Override public String next() { if (!hasNext()) { - throw new NoSuchElementException("No more elements in list"); + throw new NoSuchElementException("No more IP addresses in range."); } - return longToIpv4(index++); + return longToIpv4(currentIndex++); } }; } diff --git a/src/main/java/me/replydev/utils/PortList.java b/src/main/java/me/replydev/utils/PortList.java index dd9389d..74b6ea3 100644 --- a/src/main/java/me/replydev/utils/PortList.java +++ b/src/main/java/me/replydev/utils/PortList.java @@ -6,62 +6,71 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.Iterator; +import java.util.NoSuchElementException; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a range of ports, potentially a single port or a range from an initial to a final port. + */ public final class PortList implements Iterable { private static final Pattern PORT_RANGE_PATTERN = Pattern.compile( - "^(0|[1-9]\\d{0,4})(?:-([1-9]\\d{0,4}))?$" + "^(0|[1-9]\\d{0,4})(?:-([1-9]\\d{0,4}))?$" ); - private final int initialPort; - private final Optional finalPort; + private final int startPort; + private final Optional endPort; - public PortList(String portString) { - if (portString == null) { - throw new IllegalArgumentException("Invalid null port range"); + /** + * Constructs a PortList from a given port range string. + * @param portRangeString A string representing the port range. + */ + public PortList(String portRangeString) { + if (portRangeString == null) { + throw new IllegalArgumentException("Port range cannot be null."); } - Matcher match = PORT_RANGE_PATTERN.matcher(portString); - if (!match.find()) { - throw new IllegalArgumentException("Invalid port range: " + portString); + Matcher matcher = PORT_RANGE_PATTERN.matcher(portRangeString); + if (!matcher.find()) { + throw new IllegalArgumentException("Invalid port range format: " + portRangeString); } - initialPort = Integer.parseInt(match.group(1)); - finalPort = Optional.ofNullable(match.group(2)).map(Integer::parseInt); - finalPort.ifPresent(f -> { - if (initialPort > f) { + + startPort = Integer.parseInt(matcher.group(1)); + endPort = Optional.ofNullable(matcher.group(2)).map(Integer::parseInt); + + endPort.ifPresent(end -> { + if (startPort > end) { throw new IllegalArgumentException( - String.format( - "Initial range is major than final range: %d > %d", - initialPort, - f - ) + String.format("Start port %d cannot be greater than end port %d.", startPort, end) ); } }); - if (initialPort < 0 || initialPort > 65535 || finalPort.orElse(-1) > 65535) { - throw new IllegalArgumentException("Invalid port range: " + portString); + if (startPort < 0 || startPort > 65535 || endPort.orElse(-1) > 65535) { + throw new IllegalArgumentException("Port range must be between 0 and 65535: " + portRangeString); } } @Override public Iterator iterator() { return new Iterator<>() { - private int currentPort = initialPort; + private int currentPort = startPort; @Override public boolean hasNext() { - return finalPort - .map(integer -> currentPort <= integer) - .orElseGet(() -> currentPort == initialPort); + return endPort.map(end -> currentPort <= end) .orElseGet(() -> currentPort == startPort); } @Override public Integer next() { if (!hasNext()) { - throw new NoSuchElementException("No more elements to extract"); + throw new NoSuchElementException("No more ports in the range."); } return currentPort++; } }; } -} +} \ No newline at end of file From b53e7689a9fde005f7b960219ac2d4af7051543a Mon Sep 17 00:00:00 2001 From: Swofty Date: Thu, 23 Nov 2023 15:29:56 +1100 Subject: [PATCH 03/11] Fixed pom --- pom.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 07ba9de..f95ac81 100644 --- a/pom.xml +++ b/pom.xml @@ -99,8 +99,8 @@ 19 --enable-preview - 18 - 18 + 19 + 19 From fb8a81445c92b30bdb9fcc0a6d2e5b3761935cba Mon Sep 17 00:00:00 2001 From: Swofty Date: Thu, 23 Nov 2023 15:35:45 +1100 Subject: [PATCH 04/11] Fixed version tag --- src/main/java/me/replydev/qubo/CLI.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/me/replydev/qubo/CLI.java b/src/main/java/me/replydev/qubo/CLI.java index 6b2a743..0499275 100644 --- a/src/main/java/me/replydev/qubo/CLI.java +++ b/src/main/java/me/replydev/qubo/CLI.java @@ -51,7 +51,7 @@ void init(String[] args) { * Prints the application logo with the current version. */ private void printLogo() { - log.info(LOGO_TEMPLATE, VERSION); + log.info(LOGO_TEMPLATE.replace("%s", VERSION)); } /** From 351ca6dc02aa8fc2c0e7fbd0b5cc6605f8a4f6f2 Mon Sep 17 00:00:00 2001 From: Swofty Date: Thu, 23 Nov 2023 16:18:59 +1100 Subject: [PATCH 05/11] Fixed players online stuff and moved to Java 21 so that there's no more preview flags --- pom.xml | 13 ++++++------- src/main/java/me/replydev/qubo/PingRunnable.java | 8 +++++++- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/pom.xml b/pom.xml index f95ac81..7f6cacd 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ 4.0.0 - 19 - 19 + 21 + 21 UTF-8 UTF-8 @@ -43,7 +43,7 @@ org.projectlombok lombok - 1.18.24 + 1.18.30 provided @@ -97,10 +97,9 @@ maven-compiler-plugin 3.10.1 - 19 - --enable-preview - 19 - 19 + 21 + 21 + 21 diff --git a/src/main/java/me/replydev/qubo/PingRunnable.java b/src/main/java/me/replydev/qubo/PingRunnable.java index 1db2f5f..9d2eee7 100644 --- a/src/main/java/me/replydev/qubo/PingRunnable.java +++ b/src/main/java/me/replydev/qubo/PingRunnable.java @@ -74,15 +74,21 @@ private String buildEntry(ServerResponse serverResponse) { * @return True if the response does not meet the criteria (is filtered out), false otherwise. */ private boolean isFiltered(ServerResponse serverResponse) { - if (filter.getMinimumPlayers() > 0 && serverResponse.getPlayers().getOnline() < filter.getMinimumPlayers()) { + int minimumPlayers = filter.getMinimumPlayers(); + int onlinePlayers = serverResponse.getPlayers().getOnline(); + + // Check for minimum players + if (minimumPlayers > 0 && onlinePlayers < minimumPlayers) { return true; } + // Check for MOTD filter String motdFilter = Optional.ofNullable(filter.getMotd()).orElse(""); if (!motdFilter.isEmpty() && !serverResponse.getDescription().getText().contains(motdFilter)) { return true; } + // Check for server version filter String versionFilter = Optional.ofNullable(filter.getVersion()).orElse(""); return !versionFilter.isEmpty() && !serverResponse.getVersion().getName().contains(versionFilter); } From 77235187270849bf92654a647832b5996f672bba Mon Sep 17 00:00:00 2001 From: replydev Date: Tue, 10 Oct 2023 23:22:48 +0200 Subject: [PATCH 06/11] Re arrange code --- src/main/java/me/replydev/qubo/QuboInstance.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/me/replydev/qubo/QuboInstance.java b/src/main/java/me/replydev/qubo/QuboInstance.java index 2103587..711c784 100644 --- a/src/main/java/me/replydev/qubo/QuboInstance.java +++ b/src/main/java/me/replydev/qubo/QuboInstance.java @@ -39,8 +39,7 @@ public class QuboInstance { 3389, // Remote Desktop Protocol (RDP) for Windows-based systems remote management 6379 // Redis data structure store ); - - public final CommandLineArgs commandLineArgs; + private final CommandLineArgs commandLineArgs; @Getter private final AtomicInteger foundServers = new AtomicInteger(); @@ -89,7 +88,7 @@ private boolean checkServersExecutor() throws InterruptedException, NumberFormat PortList portRange = commandLineArgs.getPortRange(); for (int port : portRange) { - if (commandLineArgs.isSkipCommon() && isStandardizedPort(port)) { + if (commandLineArgs.isSkipCommon() && STANDARDIZED_PORTS.contains(port)) { continue; } @@ -117,11 +116,7 @@ private boolean checkServersExecutor() throws InterruptedException, NumberFormat } } - private boolean isStandardizedPort(int port) { - return !commandLineArgs.isSkipCommon() || STANDARDIZED_PORTS.contains(port); - } - - private boolean isLikelyBroadcast(InetAddress address) { + private static boolean isLikelyBroadcast(InetAddress address) { byte[] bytes = address.getAddress(); return bytes[bytes.length - 1] == 0 || bytes[bytes.length - 1] == (byte) 0xFF; } From 79bb40636bc94ec3fd2ee4f92f13be0ccd6f9a94 Mon Sep 17 00:00:00 2001 From: replydev Date: Tue, 10 Oct 2023 23:28:47 +0200 Subject: [PATCH 07/11] PortList refactors --- src/main/java/me/replydev/utils/PortList.java | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/src/main/java/me/replydev/utils/PortList.java b/src/main/java/me/replydev/utils/PortList.java index 74b6ea3..e1c4988 100644 --- a/src/main/java/me/replydev/utils/PortList.java +++ b/src/main/java/me/replydev/utils/PortList.java @@ -22,7 +22,7 @@ public final class PortList implements Iterable { ); private final int startPort; - private final Optional endPort; + private final int endPort; /** * Constructs a PortList from a given port range string. @@ -37,19 +37,20 @@ public PortList(String portRangeString) { if (!matcher.find()) { throw new IllegalArgumentException("Invalid port range format: " + portRangeString); } - startPort = Integer.parseInt(matcher.group(1)); - endPort = Optional.ofNullable(matcher.group(2)).map(Integer::parseInt); + endPort = Optional.ofNullable(matcher.group(2)).map(Integer::parseInt).orElse(startPort); - endPort.ifPresent(end -> { - if (startPort > end) { - throw new IllegalArgumentException( - String.format("Start port %d cannot be greater than end port %d.", startPort, end) - ); - } - }); + if (startPort > endPort) { + throw new IllegalArgumentException( + String.format( + "Initial range is major than final range: %d > %d", + startPort, + endPort + ) + ); + } - if (startPort < 0 || startPort > 65535 || endPort.orElse(-1) > 65535) { + if (startPort < 0 || startPort > 65535 || endPort > 65535) { throw new IllegalArgumentException("Port range must be between 0 and 65535: " + portRangeString); } } @@ -61,7 +62,7 @@ public Iterator iterator() { @Override public boolean hasNext() { - return endPort.map(end -> currentPort <= end) .orElseGet(() -> currentPort == startPort); + return currentPort <= endPort; } @Override From b852ecb4de1b43b44c70a7154782ffae13934076 Mon Sep 17 00:00:00 2001 From: replydev Date: Thu, 30 Nov 2023 22:26:09 +0100 Subject: [PATCH 08/11] Use Java 21 in CI --- .github/workflows/build.yml | 4 ++-- .github/workflows/release.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a0c9974..9386316 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -21,10 +21,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 19 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: '19' + java-version: '21' distribution: 'temurin' cache: maven - name: Build with Maven diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4a19d96..61995ea 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,10 +11,10 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Set up JDK 19 + - name: Set up JDK 21 uses: actions/setup-java@v3 with: - java-version: "19" + java-version: "21" distribution: "temurin" cache: maven - name: Build with Maven From 899b79cc35caf37ed86848724c30177fc5fb9ca4 Mon Sep 17 00:00:00 2001 From: replydev Date: Thu, 30 Nov 2023 22:31:23 +0100 Subject: [PATCH 09/11] Remove UTF-8 check as it's the default in Java 21 --- src/main/java/me/replydev/qubo/CLI.java | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/src/main/java/me/replydev/qubo/CLI.java b/src/main/java/me/replydev/qubo/CLI.java index 0499275..eba6a21 100644 --- a/src/main/java/me/replydev/qubo/CLI.java +++ b/src/main/java/me/replydev/qubo/CLI.java @@ -37,7 +37,6 @@ public class CLI { */ void init(String[] args) { printLogo(); - checkEncodingParameter(); standardRun(args); log.info("Scan terminated - {} ({}) in total", @@ -68,24 +67,4 @@ private void standardRun(String[] args) { new CommandLineArgs(args).showHelpAndExit(); } } - - /** - * Checks if the JVM is running in UTF-8 encoding mode and exits if not. - */ - private void checkEncodingParameter() { - if (!isUTF8Mode()) { - log.error("The scanner isn't running in UTF-8 mode!"); - log.error("Put \"-Dfile.encoding=UTF-8\" in JVM args in order to run the program correctly!"); - System.exit(-1); - } - } - - /** - * Checks if UTF-8 mode is enabled for file encoding. - * @return true if UTF-8 mode is set, false otherwise. - */ - private boolean isUTF8Mode() { - List arguments = ManagementFactory.getRuntimeMXBean().getInputArguments(); - return arguments.stream().anyMatch(arg -> arg.equals("-Dfile.encoding=UTF-8")); - } } From 96c247d8cd435d9d76a092472ea6385906e45f77 Mon Sep 17 00:00:00 2001 From: replydev Date: Thu, 30 Nov 2023 22:45:03 +0100 Subject: [PATCH 10/11] Bump version and dependencies --- pom.xml | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/pom.xml b/pom.xml index 7f6cacd..6bf6fcc 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ me.replydev quboscanner - 1.0.0 + 1.0.1 @@ -27,7 +27,7 @@ org.junit junit-bom - 5.9.2 + 5.10.1 pom import @@ -38,7 +38,7 @@ commons-cli commons-cli - 1.5.0 + 1.6.0 org.projectlombok @@ -49,17 +49,17 @@ com.github.seancfoley ipaddress - 5.3.4 + 5.4.0 org.tinylog slf4j-tinylog - 2.6.0 + 2.7.0-M1 org.tinylog tinylog-impl - 2.6.0 + 2.7.0-M1 com.github.replydev @@ -77,7 +77,7 @@ maven-surefire-plugin - 3.0.0-M7 + 3.2.2 maven-assembly-plugin @@ -95,12 +95,7 @@ org.apache.maven.plugins maven-compiler-plugin - 3.10.1 - - 21 - 21 - 21 - + 3.11.0 From b991a9c6595e73876e25ee49bd968c765d937c02 Mon Sep 17 00:00:00 2001 From: replydev Date: Thu, 30 Nov 2023 22:45:15 +0100 Subject: [PATCH 11/11] Add default for timeout option --- src/main/java/me/replydev/qubo/CommandLineArgs.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/me/replydev/qubo/CommandLineArgs.java b/src/main/java/me/replydev/qubo/CommandLineArgs.java index a0d71b2..b5bd3e1 100644 --- a/src/main/java/me/replydev/qubo/CommandLineArgs.java +++ b/src/main/java/me/replydev/qubo/CommandLineArgs.java @@ -42,7 +42,7 @@ public CommandLineArgs(String[] command) throws NumberFormatException { ipList = new IpList(cmd.getOptionValue("i")); portRange = new PortList(cmd.getOptionValue("p")); skipCommon = !cmd.hasOption("all"); - timeout = Integer.parseInt(cmd.getOptionValue("t")); + timeout = Integer.parseInt(cmd.getOptionValue("t", "1000")); searchFilter = SearchFilter @@ -65,7 +65,7 @@ private static Options buildOptions() { options.addRequiredOption("i", "iprange", true, "The IP range to scan"); options.addRequiredOption("p", "portrange", true, "The range of ports to scan"); - options.addRequiredOption("t", "timeout", true, "TCP connection timeout"); + options.addOption("t", "timeout", true, "TCP connection timeout"); options.addOption("c", "pingcount", true, "Number of ping retries"); options.addOption("a", "all", false, "Force to scan broadcast IPs and common ports"); options.addOption("v", "filterversion", true, "Show only hits with given version");