Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add provisioning support for custom websocket ports #1263

Merged
merged 7 commits into from
May 6, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions src/qz/common/Constants.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package qz.common;

import com.github.zafarkhaja.semver.Version;
import qz.utils.ArgValue;
import qz.utils.SystemUtilities;

import java.awt.*;
Expand Down Expand Up @@ -98,7 +97,7 @@ public class Constants {
public static final String PDF_PRINT = ABOUT_TITLE + " PDF Print";
public static final String HTML_PRINT = ABOUT_TITLE + " HTML Print";

public static final Integer[] WSS_PORTS = {8181, 8282, 8383, 8484};
public static final Integer[] WS_PORTS = {8182, 8283, 8384, 8485};
public static final Integer[] DEFAULT_WSS_PORTS = {8181, 8282, 8383, 8484};
public static final Integer[] DEFAULT_WS_PORTS = {8182, 8283, 8384, 8485};
public static final Integer[] CUPS_RSS_PORTS = {8586, 8687, 8788, 8889};
}
4 changes: 2 additions & 2 deletions src/qz/common/TrayManager.java
Original file line number Diff line number Diff line change
Expand Up @@ -536,8 +536,8 @@ private void blackList(Certificate cert) {

public void setServer(Server server, int insecurePortInUse, int securePortInUse) {
if (server != null && server.getConnectors().length > 0) {
singleInstanceCheck(PrintSocketServer.INSECURE_PORTS, insecurePortInUse, false);
singleInstanceCheck(PrintSocketServer.SECURE_PORTS, securePortInUse, true);
singleInstanceCheck(PrintSocketServer.insecurePorts, insecurePortInUse, false);
singleInstanceCheck(PrintSocketServer.securePorts, securePortInUse, true);

displayInfoMessage("Server started on port(s) " + PrintSocketServer.getPorts(server));

Expand Down
30 changes: 27 additions & 3 deletions src/qz/installer/Installer.java
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ public abstract class Installer {
public static boolean IS_SILENT = "1".equals(System.getenv(DATA_DIR + "_silent"));
public static String JRE_LOCATION = SystemUtilities.isMac() ? "Contents/PlugIns/Java.runtime/Contents/Home" : "runtime";

private List<Integer> securePorts = Arrays.asList(DEFAULT_WSS_PORTS);
private List<Integer> insecurePorts = Arrays.asList(DEFAULT_WSS_PORTS);

public enum PrivilegeLevel {
USER,
SYSTEM
Expand Down Expand Up @@ -103,8 +106,8 @@ public static void install() throws Exception {
.addSharedDirectory()
.addAppLauncher()
.addStartupEntry()
.addSystemSettings()
.invokeProvisioning(Phase.INSTALL);
.invokeProvisioning(Phase.INSTALL)
.addSystemSettings();
}

public static void uninstall() {
Expand Down Expand Up @@ -359,8 +362,13 @@ public Installer invokeProvisioning(Phase phase) {
Paths.get(getDestination()).resolve(PROVISION_DIR);
ProvisionInstaller provisionInstaller = new ProvisionInstaller(provisionPath);
provisionInstaller.invoke(phase);

// Special case for custom websocket ports
if(phase == Phase.INSTALL) {
provisionInstaller.setCustomPorts(this);
}
} catch(Exception e) {
log.warn("An error occurred deleting provisioning directory \"phase\": \"{}\" entries", phase, e);
log.warn("An error occurred invoking provision \"phase\": \"{}\"", phase, e);
}
return this;
}
Expand All @@ -377,6 +385,22 @@ public Installer removeProvisioning() {
return this;
}

public void setSecurePorts(List<Integer> securePorts) {
this.securePorts = securePorts;
}

public void setInsecurePorts(List<Integer> insecurePorts) {
this.insecurePorts = insecurePorts;
}

public List<Integer> getSecurePorts() {
return securePorts;
}

public List<Integer> getInsecurePorts() {
return insecurePorts;
}

public static Properties persistProperties(File oldFile, Properties newProps) {
if(oldFile.exists()) {
Properties oldProps = new Properties();
Expand Down
2 changes: 1 addition & 1 deletion src/qz/installer/WindowsInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ public Installer addSystemSettings() {
WindowsUtilities.addNumberedRegValue(HKEY_LOCAL_MACHINE, "SOFTWARE\\Policies\\Google\\Chrome\\URLWhitelist", String.format("%s://*", DATA_DIR));

// Firewall rules
String ports = StringUtils.join(PrintSocketServer.SECURE_PORTS, ",") + "," + StringUtils.join(PrintSocketServer.INSECURE_PORTS, ",");
String ports = StringUtils.join(getSecurePorts(), ",") + "," + StringUtils.join(getInsecurePorts(), ",");
ShellUtilities.execute("netsh.exe", "advfirewall", "firewall", "delete", "rule", String.format("name=%s", ABOUT_TITLE));
ShellUtilities.execute("netsh.exe", "advfirewall", "firewall", "add", "rule", String.format("name=%s", ABOUT_TITLE),
"dir=in", "action=allow", "profile=any", String.format("localport=%s", ports), "localip=any", "protocol=tcp");
Expand Down
32 changes: 32 additions & 0 deletions src/qz/installer/provision/ProvisionInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.codehaus.jettison.json.JSONArray;
Expand All @@ -10,10 +11,14 @@
import qz.build.provision.Step;
import qz.build.provision.params.Os;
import qz.build.provision.params.Phase;
import qz.build.provision.params.Type;
import qz.build.provision.params.types.Script;
import qz.build.provision.params.types.Software;
import qz.common.Constants;
import qz.installer.Installer;
import qz.installer.provision.invoker.*;
import qz.utils.ArgValue;
import qz.utils.PrefsSearch;
import qz.utils.ShellUtilities;
import qz.utils.SystemUtilities;

Expand All @@ -22,7 +27,9 @@
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.AbstractMap;
import java.util.ArrayList;
import java.util.List;

import static qz.common.Constants.*;
import static qz.utils.FileUtilities.*;
Expand Down Expand Up @@ -129,6 +136,31 @@ private boolean invokeStep(Step step) throws Exception {
return invoker.invoke();
}

/**
* Loops through steps searching for a property which sets a custom websocket ports
*/
public void setCustomPorts(Installer installer) {
for(Step step : steps) {
if(step.getType() == Type.PROPERTY) {
for(AbstractMap.SimpleEntry<String,String> pair : PropertyInvoker.parsePropertyPairs(step)) {
Vzor- marked this conversation as resolved.
Show resolved Hide resolved
if(pair.getKey().equals(ArgValue.WEBSOCKET_SECURE_PORTS.getMatch())) {
List<Integer> securePorts = PrefsSearch.parseIntegerArray(pair.getValue());
if(!securePorts.isEmpty()) {
installer.setSecurePorts(securePorts);
log.info("Picked up custom secure ports from {}: [{}]", PROVISION_FILE, StringUtils.join(securePorts, ","));
}
} else if(pair.getKey().equals(ArgValue.WEBSOCKET_INSECURE_PORTS.getMatch())) {
List<Integer> insecurePorts = PrefsSearch.parseIntegerArray(pair.getValue());
if(!insecurePorts.isEmpty()) {
installer.setInsecurePorts(insecurePorts);
log.info("Picked up custom insecure ports from {}: [{}]", PROVISION_FILE, StringUtils.join(insecurePorts, ","));
}
}
}
}
}
}

private static ArrayList<Step> parse(JSONArray jsonArray, Object relativeObject) throws JSONException {
ArrayList<Step> steps = new ArrayList<>();
for(int i = 0; i < jsonArray.length(); i++) {
Expand Down
43 changes: 24 additions & 19 deletions src/qz/installer/provision/invoker/PropertyInvoker.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,27 +20,16 @@ public PropertyInvoker(Step step, PropertyHelper properties) {
}

public boolean invoke() {
if(step.getData() != null && !step.getData().trim().isEmpty()) {
String[] props = step.getData().split("\\|");
ArrayList<AbstractMap.SimpleEntry<String,String>> pairs = new ArrayList<>();
for(String prop : props) {
AbstractMap.SimpleEntry<String,String> pair = parsePropertyPair(step, prop);
if (pair != null) {
pairs.add(pair);
}
ArrayList<AbstractMap.SimpleEntry<String,String>> pairs = parsePropertyPairs(step);
if (!pairs.isEmpty()) {
for(AbstractMap.SimpleEntry<String,String> pair : pairs) {
properties.setProperty(pair.getKey(), pair.getValue());
}
if (!pairs.isEmpty()) {
for(AbstractMap.SimpleEntry<String,String> pair : pairs) {
properties.setProperty(pair.getKey(), pair.getValue());
}
if (properties.save()) {
log.info("Successfully provisioned '{}' '{}'", pairs.size(), step.getType());
return true;
}
log.error("An error occurred saving properties '{}' to file", step.getData());
if (properties.save()) {
log.info("Successfully provisioned '{}' '{}'", pairs.size(), step.getType());
return true;
}
} else {
log.error("Skipping Step '{}', Data is null or empty", step.getType());
log.error("An error occurred saving properties '{}' to file", step.getData());
}
return false;
}
Expand All @@ -63,6 +52,22 @@ public static PropertyHelper getPreferences(Step step) {
return new PropertyHelper(FileUtilities.USER_DIR + File.separator + Constants.PREFS_FILE + ".properties");
}

public static ArrayList<AbstractMap.SimpleEntry<String,String>> parsePropertyPairs(Step step) {
ArrayList<AbstractMap.SimpleEntry<String,String>> pairs = new ArrayList<>();
if(step.getData() != null && !step.getData().trim().isEmpty()) {
String[] props = step.getData().split("\\|");
for(String prop : props) {
AbstractMap.SimpleEntry<String,String> pair = parsePropertyPair(step, prop);
if (pair != null) {
pairs.add(pair);
}
}
} else {
log.error("Skipping Step '{}', Data is null or empty", step.getType());
}
return pairs;
}


private static AbstractMap.SimpleEntry<String, String> parsePropertyPair(Step step, String prop) {
if(prop.contains("=")) {
Expand Down
5 changes: 5 additions & 0 deletions src/qz/utils/ArgValue.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package qz.utils;

import org.apache.commons.lang3.StringUtils;
import qz.common.Constants;

import java.util.ArrayList;
Expand Down Expand Up @@ -81,6 +82,10 @@ public enum ArgValue {
"security.wss.httpsonly"),
SECURITY_WSS_HOST(PREFERENCES, "Influences which physical adapter to bind to by setting the host parameter for http/websocket listening", null, "0.0.0.0",
"security.wss.host"),
WEBSOCKET_SECURE_PORTS(PREFERENCES, "Comma separated list of secure websocket (wss://) ports to use", null, StringUtils.join(Constants.DEFAULT_WSS_PORTS, ","),
"websocket.secure.ports"),
WEBSOCKET_INSECURE_PORTS(PREFERENCES, "Comma separated list of insecure websocket (ws://) ports to use", null, StringUtils.join(Constants.DEFAULT_WS_PORTS, ","),
"websocket.insecure.ports"),
LOG_DISABLE(PREFERENCES, "Disable/enable logging features", null, false,
"log.disable"),
LOG_ROTATE(PREFERENCES, "Number of log files to retain when the size fills up", null, 5,
Expand Down
22 changes: 22 additions & 0 deletions src/qz/utils/PrefsSearch.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
import qz.installer.certificate.CertificateManager;
import qz.installer.certificate.KeyPairWrapper;

import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

import static qz.installer.certificate.KeyPairWrapper.Type.CA;
Expand Down Expand Up @@ -90,6 +92,26 @@ public static int getInt(ArgValue argValue, Properties ... propsArray) {
return getInt(argValue, true, propsArray);
}

public static Integer[] getIntegerArray(ArgValue argValue, Properties ... propsArray) {
List<Integer> parsed = parseIntegerArray(getString(argValue, propsArray));
return parsed.toArray(new Integer[parsed.size()]);
}

public static List<Integer> parseIntegerArray(String commaSeparated) {
List<Integer> parsed = new ArrayList<>();
tresf marked this conversation as resolved.
Show resolved Hide resolved
try {
if (commaSeparated != null && !commaSeparated.isEmpty()) {
String[] split = commaSeparated.split(",");
for(String item : split) {
parsed.add(Integer.parseInt(item));
}
}
} catch(NumberFormatException nfe) {
log.warn("Failed parsing {} as a valid integer array", commaSeparated, nfe);
}
return parsed;
}

public static boolean getBoolean(ArgValue argValue, Properties ... propsArray) {
return getBoolean(argValue, true, propsArray);
}
Expand Down
39 changes: 33 additions & 6 deletions src/qz/ws/PrintSocketServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ public class PrintSocketServer {
private static final Logger log = LogManager.getLogger(PrintSocketServer.class);

private static final int MAX_MESSAGE_SIZE = Integer.MAX_VALUE;
public static final List<Integer> SECURE_PORTS = Collections.unmodifiableList(Arrays.asList(Constants.WSS_PORTS));
public static final List<Integer> INSECURE_PORTS = Collections.unmodifiableList(Arrays.asList(Constants.WS_PORTS));
public static List<Integer> securePorts;
public static List<Integer> insecurePorts;

private static final AtomicInteger securePortIndex = new AtomicInteger(0);
private static final AtomicInteger insecurePortIndex = new AtomicInteger(0);
Expand All @@ -65,6 +65,8 @@ public static void runServer(CertificateManager certManager, boolean headless) t
wssHost = PrefsSearch.getString(ArgValue.SECURITY_WSS_HOST, certManager.getProperties());
httpsOnly = PrefsSearch.getBoolean(ArgValue.SECURITY_WSS_HTTPSONLY, certManager.getProperties());
sniStrict = PrefsSearch.getBoolean(ArgValue.SECURITY_WSS_SNISTRICT, certManager.getProperties());
parseWebSocketPorts();

server = findAvailableSecurePort(certManager);

Connector secureConnector = null;
Expand All @@ -77,7 +79,7 @@ public static void runServer(CertificateManager certManager, boolean headless) t
return;
}

while(!running.get() && insecurePortIndex.get() < INSECURE_PORTS.size()) {
while(!running.get() && insecurePortIndex.get() < insecurePorts.size()) {
try {
ServerConnector connector = new ServerConnector(server);
connector.setPort(getInsecurePortInUse());
Expand Down Expand Up @@ -155,7 +157,7 @@ private static Server findAvailableSecurePort(CertificateManager certManager) {

if (certManager != null) {
final AtomicBoolean runningSecure = new AtomicBoolean(false);
while(!runningSecure.get() && securePortIndex.get() < SECURE_PORTS.size()) {
while(!runningSecure.get() && securePortIndex.get() < securePorts.size()) {
try {
// Bind the secure socket on the proper port number (i.e. 8181), add it as an additional connector
SslConnectionFactory sslConnection = new SslConnectionFactory(certManager.configureSslContextFactory(), HttpVersion.HTTP_1_1.asString());
Expand Down Expand Up @@ -223,12 +225,37 @@ public static void main(String ... args) {
App.main(args);
}

/**
* Parses WebSocket ports from preferences or fallback to defaults is a problem is found
*/
public static void parseWebSocketPorts() {
Integer[] secure = PrefsSearch.getIntegerArray(ArgValue.WEBSOCKET_SECURE_PORTS);
Integer[] insecure = PrefsSearch.getIntegerArray(ArgValue.WEBSOCKET_INSECURE_PORTS);
boolean fallback = false;
if(secure.length == 0 || insecure.length == 0) {
log.warn("One or more WebSocket ports is empty, falling back to defaults");
fallback = true;
}
if(secure.length != insecure.length) {
log.warn("Secure ({}) and insecure ({}) WebSocket port counts mismatch, falling back to defaults", secure, insecure);
fallback = true;
}
if(fallback) {
log.warn("Falling back to default WebSocket ports: ({}), ({})", secure, insecure);
secure = Constants.DEFAULT_WSS_PORTS;
insecure = Constants.DEFAULT_WS_PORTS;
}

securePorts = Collections.unmodifiableList(Arrays.asList(secure));
insecurePorts = Collections.unmodifiableList(Arrays.asList(insecure));
Vzor- marked this conversation as resolved.
Show resolved Hide resolved
}

public static int getSecurePortInUse() {
return SECURE_PORTS.get(securePortIndex.get());
return securePorts.get(securePortIndex.get());
}

public static int getInsecurePortInUse() {
return INSECURE_PORTS.get(insecurePortIndex.get());
return insecurePorts.get(insecurePortIndex.get());
}

/**
Expand Down
12 changes: 12 additions & 0 deletions test/qz/installer/provision/resources/provision.json
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,18 @@
"os": "*",
"data": "cert1.crt"
},
{
"description": "[PROPERTY] at 'certgen' (qz-tray.properties)",
"type": "property",
"os": "*",
"data": "websocket.secure.ports=9191,9292,9393,9494"
},
{
"description": "[PROPERTY] at 'certgen' (qz-tray.properties)",
"type": "property",
"os": "*",
"data": "websocket.insecure.ports=9192,9293,9394,9495"
},
{
"description": "[PROPERTY] at 'certgen' (qz-tray.properties)",
"type": "property",
Expand Down
Loading