Skip to content

Commit

Permalink
Whitelist qz protocol handler for Google Chrome (qzind#582)
Browse files Browse the repository at this point in the history
* Whitelist qz protocol handler for Google Chrome
  • Loading branch information
tresf authored Feb 19, 2020
1 parent 506093c commit 89a0db3
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 16 deletions.
41 changes: 34 additions & 7 deletions src/qz/installer/LinuxInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@
import qz.utils.FileUtilities;
import qz.utils.ShellUtilities;
import qz.utils.SystemUtilities;

import java.io.File;
import java.io.IOException;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.*;
import java.util.regex.Pattern;

import static qz.common.Constants.*;
Expand All @@ -27,6 +23,8 @@ public class LinuxInstaller extends Installer {
public static final String APP_DIR = "/usr/share/applications/";
public static final String APP_LAUNCHER = APP_DIR + SHORTCUT_NAME;
public static final String UDEV_RULES = "/lib/udev/rules.d/99-udev-override.rules";
public static final String[] CHROME_POLICY_DIRS = {"/etc/chromium/policies/managed", "/etc/opt/chrome/policies/managed" };
public static final String CHROME_POLICY = "{ \"URLWhitelist\": [\"" + DATA_DIR + "://*\"] }";

private String destination = "/opt/" + PROPS_FILE;

Expand Down Expand Up @@ -100,6 +98,27 @@ public Installer addSystemSettings() {
}
}

// Chrome protocol handler
for (String policyDir : CHROME_POLICY_DIRS) {
log.info("Installing chrome protocol handler {}/{}...", policyDir, PROPS_FILE + ".json");
try {
FileUtilities.setPermissionsParentally(Files.createDirectories(Paths.get(policyDir)), false);
} catch(IOException e) {
log.warn("An error occurred creating {}", policyDir);
}

Path policy = Paths.get(policyDir, PROPS_FILE + ".json");
try (BufferedWriter writer = new BufferedWriter(new FileWriter(policy.toFile()))){
writer.write(CHROME_POLICY);
policy.toFile().setReadable(true, false);
}
catch(IOException e) {
log.warn("Unable to write chrome policy: {} ({}:launch will fail)", policy, DATA_DIR);
}

}

// USB permissions
try {
File udev = new File(UDEV_RULES);
if (udev.exists()) {
Expand All @@ -118,6 +137,14 @@ public Installer addSystemSettings() {
}

public Installer removeSystemSettings() {
// Chrome protocol handler
for (String policyDir : CHROME_POLICY_DIRS) {
log.info("Removing chrome protocol handler {}/{}...", policyDir, PROPS_FILE + ".json");
Path policy = Paths.get(policyDir, PROPS_FILE + ".json");
policy.toFile().delete();
}

// USB permissions
File udev = new File(UDEV_RULES);
if (udev.exists()) {
udev.delete();
Expand Down
9 changes: 8 additions & 1 deletion src/qz/installer/MacInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,14 @@ public String getDestination() {
return destination;
}

public Installer addSystemSettings() { return this; }
public Installer addSystemSettings() {
// Chrome protocol handler
String plist = "/Library/Preferences/com.google.Chrome.plist";
if(ShellUtilities.execute(new String[] { "/usr/bin/defaults", "write", plist }, new String[] { "qz://*" }).isEmpty()) {
ShellUtilities.execute("/usr/bin/defaults", "write", plist, "URLWhitelist", "-array-add", "qz://*");
}
return this;
}
public Installer removeSystemSettings() {
// Remove startup entry
File dest = new File(String.format("/Library/LaunchAgents/%s.plist", PACKAGE_NAME));
Expand Down
9 changes: 7 additions & 2 deletions src/qz/installer/WindowsInstaller.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,11 @@
public class WindowsInstaller extends Installer {
protected static final Logger log = LoggerFactory.getLogger(WindowsInstaller.class);
private String destination = getDefaultDestination();
private String destinationExe;
private String destinationExe = getDefaultDestination() + File.separator + PROPS_FILE + ".exe";

public void setDestination(String destination) {
this.destination = destination;
this.destinationExe = destination + File.separator + PROPS_FILE+ ".exe";
this.destinationExe = destination + File.separator + PROPS_FILE + ".exe";
}

/**
Expand Down Expand Up @@ -89,6 +89,8 @@ public Installer removeSystemSettings() {
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\" + ABOUT_TITLE);
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, "Software\\" + ABOUT_TITLE);
WindowsUtilities.deleteRegKey(HKEY_LOCAL_MACHINE, DATA_DIR);
// Chrome protocol handler
WindowsUtilities.deleteRegData(HKEY_LOCAL_MACHINE, "SOFTWARE\\Policies\\Google\\Chrome\\URLWhitelist", String.format("%s://*", DATA_DIR));

// Cleanup launchers
for(WindowsSpecialFolders folder : new WindowsSpecialFolders[] { START_MENU, COMMON_START_MENU, DESKTOP, PUBLIC_DESKTOP }) {
Expand Down Expand Up @@ -134,6 +136,9 @@ public Installer addSystemSettings() {
WindowsUtilities.addRegValue(HKEY_LOCAL_MACHINE, uninstallKey, "DisplayVersion", VERSION.toString());
WindowsUtilities.addRegValue(HKEY_LOCAL_MACHINE, uninstallKey, "EstimatedSize", FileUtils.sizeOfDirectoryAsBigInteger(new File(destination)).intValue() / 1024);

// Chrome protocol handler
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, ",");
ShellUtilities.execute("netsh.exe", "advfirewall", "delete", "rule", String.format("name=\"%s\"", ABOUT_TITLE));
Expand Down
15 changes: 15 additions & 0 deletions src/qz/utils/FileUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -666,6 +666,21 @@ public static Path getTempDirectory() {
return null;
}

public static void setPermissionsParentally(Path toTraverse, boolean worldWrite) {
Path stepper = toTraverse.toAbsolutePath();
// Assume we shouldn't go higher than 2nd-level (e.g. "/etc", "C:\Program Files\", etc)
while(stepper.getParent() != null && !stepper.getRoot().equals(stepper.getParent())) {
File file = stepper.toFile();
file.setReadable(true, false);
file.setExecutable(true, false);
file.setWritable(true, !worldWrite);
if (SystemUtilities.isWindows() && worldWrite) {
WindowsUtilities.setWritable(stepper);
}
stepper = stepper.getParent();
}
}

public static void setPermissionsRecursively(Path toRecurse, boolean worldWrite) {
try (Stream<Path> paths = Files.walk(toRecurse)) {
paths.forEach((path)->{
Expand Down
96 changes: 90 additions & 6 deletions src/qz/utils/WindowsUtilities.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@

import java.awt.*;
import java.io.IOException;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.*;
import java.util.List;
import java.util.Map;

import static com.sun.jna.platform.win32.WinReg.*;

Expand Down Expand Up @@ -59,7 +61,7 @@ public static Integer getRegInt(HKEY root, String key, String value) {
return Advapi32Util.registryGetIntValue(root, key, value);
}
} catch(Exception e) {
log.warn("Couldn't get registry value {}\\{}\\{}", root, key, value);
log.warn("Couldn't get registry value {}\\\\{}\\\\{}", getHkeyName(root), key, value);
}
return null;
}
Expand All @@ -71,25 +73,48 @@ public static String getRegString(HKEY root, String key, String value) {
return Advapi32Util.registryGetStringValue(root, key, value);
}
} catch(Exception e) {
log.warn("Couldn't get registry value {}\\{}\\{}", root, key, value);
log.warn("Couldn't get registry value {}\\\\{}\\\\{}", getHkeyName(root), key, value);
}
return null;
}

/**
* Deletes all matching data values directly beneath the specified key
*/
public static boolean deleteRegData(HKEY root, String key, String data) {
boolean success = true;
if (Advapi32Util.registryKeyExists(root, key)) {
for(Map.Entry<String, Object> entry : Advapi32Util.registryGetValues(root, key).entrySet()) {
if(entry.getValue().equals(data)) {
try {
Advapi32Util.registryDeleteValue(root, key, entry.getKey());
} catch(Exception e) {
log.warn("Couldn't delete value {}\\\\{}\\\\{}", getHkeyName(root), key, entry.getKey());
success = false;
}
}
}
}
return success;
}

// gracefully swallow InvocationTargetException
public static boolean deleteRegKey(WinReg.HKEY root, String key) {
public static boolean deleteRegKey(HKEY root, String key) {
try {
if (Advapi32Util.registryKeyExists(root, key)) {
Advapi32Util.registryDeleteKey(root, key);
return true;
}
} catch(Exception e) {
log.warn("Couldn't delete value {}\\{}\\{}", root, key);
log.warn("Couldn't delete value {}\\\\{}", getHkeyName(root), key);
}
return false;
}

public static boolean addRegValue(WinReg.HKEY root, String key, String value, Object data) {
/**
* Adds a registry entry at <code>key</code>/<code>0</code>, incrementing as needed
*/
public static boolean addNumberedRegValue(HKEY root, String key, Object data) {
try {
// Recursively create keys as needed
String partialKey = "";
Expand All @@ -103,6 +128,19 @@ public static boolean addRegValue(WinReg.HKEY root, String key, String value, Ob
Advapi32Util.registryCreateKey(root, partialKey);
}
}
// Make sure it doesn't already exist
for(Map.Entry<String, Object> entry : Advapi32Util.registryGetValues(root, key).entrySet()) {
if(entry.getValue().equals(data)) {
log.info("Registry data {}\\\\{}\\\\{} already has {}, skipping.", getHkeyName(root), key, entry.getKey(), data);
return true;
}
}
// Find the next available number and iterate
int counter=0;
while(Advapi32Util.registryValueExists(root, key, counter + "")) {
counter++;
}
String value = String.valueOf(counter);
if (data instanceof String) {
Advapi32Util.registrySetStringValue(root, key, value, (String)data);
} else if (data instanceof Integer) {
Expand All @@ -112,11 +150,57 @@ public static boolean addRegValue(WinReg.HKEY root, String key, String value, Ob
}
return true;
} catch(Exception e) {
log.error("Could not write registry value {}\\{}\\{}", root, key, value, e);
log.error("Could not write numbered registry value at {}\\\\{}", getHkeyName(root), key, e);
}
return false;
}

public static boolean addRegValue(HKEY root, String key, String value, Object data) {
try {
// Recursively create keys as needed
String partialKey = "";
for(String section : key.split("\\\\")) {
if (partialKey.isEmpty()) {
partialKey += section;
} else {
partialKey += "\\" + section;
}
if(!Advapi32Util.registryKeyExists(root, partialKey)) {
Advapi32Util.registryCreateKey(root, partialKey);
}
}
if (data instanceof String) {
Advapi32Util.registrySetStringValue(root, key, value, (String)data);
} else if (data instanceof Integer) {
Advapi32Util.registrySetIntValue(root, key, value, (Integer)data);
} else {
throw new Exception("Registry values of type " + data.getClass() + " aren't supported");
}
return true;
} catch(Exception e) {
log.error("Could not write registry value {}\\\\{}\\\\{}", getHkeyName(root), key, value, e);
}
return false;
}

/**
* Use reflection to get readable <code>HKEY</code> name, useful for debugging errors
*/
private static String getHkeyName(HKEY hkey) {
for(Field f : WinReg.class.getFields()) {
if (f.getName().startsWith("HKEY_")) {
try {
if (f.get(HKEY.class).equals(hkey)) {
return f.getName();
}
} catch(IllegalAccessException e) {
log.warn("Can't get name of HKEY", e);
}
}
}
return "UNKNOWN";
}

public static void setWritable(Path path) {
try {
UserPrincipal authenticatedUsers = path.getFileSystem().getUserPrincipalLookupService()
Expand Down

0 comments on commit 89a0db3

Please sign in to comment.