diff --git a/jami-dht/pom.xml b/jami-dht/pom.xml index fb9ebf5324ce1af65023ea161bb96920628099d4..af19fca0f03453e6e329a36d54527fe36d59759a 100644 --- a/jami-dht/pom.xml +++ b/jami-dht/pom.xml @@ -25,7 +25,7 @@ <dependency> <groupId>net.jami</groupId> <artifactId>jams-common</artifactId> - <version>2.0</version> + <version>${revision}</version> <scope>compile</scope> </dependency> </dependencies> diff --git a/jams-common/src/main/java/module-info.java b/jams-common/src/main/java/module-info.java index 4358de73c8dc950dee314af827dfc1df62752566..2bff57ef304db992ffb7668133b0b82f81cac2a3 100644 --- a/jams-common/src/main/java/module-info.java +++ b/jams-common/src/main/java/module-info.java @@ -57,5 +57,6 @@ module jams.common { requires java.sql; requires org.apache.xbean.classloader; requires jeromq; + requires java.naming; } diff --git a/jams-launcher/pom.xml b/jams-launcher/pom.xml index 9a63478b6d47c33f5454300045db1a48467958f2..1deb8d0b7b72a27dc01a9752ed93b22ac841f41e 100644 --- a/jams-launcher/pom.xml +++ b/jams-launcher/pom.xml @@ -36,11 +36,6 @@ <artifactId>guava</artifactId> <version>20.0</version> </dependency> - <dependency> - <groupId>com.blockfeed</groupId> - <artifactId>messaging</artifactId> - <version>1.0-SNAPSHOT</version> - </dependency> </dependencies> <build> diff --git a/jams-launcher/src/main/java/launcher/AppStarter.java b/jams-launcher/src/main/java/launcher/AppStarter.java index ad34217fa3b915c7295420ca34192744f906c291..89365bcb45f1562ea2f1927ea002163780eb7014 100644 --- a/jams-launcher/src/main/java/launcher/AppStarter.java +++ b/jams-launcher/src/main/java/launcher/AppStarter.java @@ -2,6 +2,7 @@ package launcher; import com.google.common.hash.HashCode; import com.google.common.hash.Hashing; +import lombok.extern.slf4j.Slf4j; import org.zeromq.ZMQ; import java.io.BufferedReader; @@ -10,14 +11,14 @@ import java.io.IOException; import java.io.InputStreamReader; import java.util.Timer; import java.util.TimerTask; -import java.util.logging.Logger; +import java.util.logging.log; +@Slf4j public class AppStarter extends Thread { private static Process applicationProcess; private static long jamsPID; private static Timer timer; - private final static Logger logger = Logger.getLogger(AppStarter.class.getName()); public static ZMQ.Context context = ZMQ.context(1); @@ -31,11 +32,10 @@ public class AppStarter extends Thread { try { - logger.info("md5 for all packages: "); - logger.info("Core: " + checksum(System.getProperty("user.dir") + "/" + "jams-server.jar")); - logger.info("AD Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "ad-connector.jar")); - logger.info("HSQL Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "hsqlconnector.jar")); - logger.info("LDAP Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "ldap-connector.jar")); + log.info("md5 for all packages: "); + log.info("Core: " + checksum(System.getProperty("user.dir") + "/" + "jams-server.jar")); + log.info("AD Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "ad-connector.jar")); + log.info("LDAP Connector: " + checksum(System.getProperty("user.dir") + "/libs/" + "ldap-connector.jar")); } catch (IOException e) { e.printStackTrace(); } @@ -58,13 +58,13 @@ public class AppStarter extends Thread { if (!isStillAlive(Long.toString(jamsPID))) { try { - logger.info("Process with pid: " + jamsPID + " found dead. Resurrecting..."); + log.info("Process with pid: " + jamsPID + " found dead. Resurrecting..."); ProcessBuilder pb = startAccountManagementServer(parentArgs); if (pb != null) { pb.inheritIO(); pb.directory(new File(System.getProperty("user.dir"))); - logger.fine("Directory: " + pb.directory().getAbsolutePath()); + log.fine("Directory: " + pb.directory().getAbsolutePath()); applicationProcess = pb.start(); } @@ -72,9 +72,9 @@ public class AppStarter extends Thread { if (applicationProcess.isAlive()) jamsPID = applicationProcess.pid(); - logger.info("New PID: " + jamsPID); + log.info("New PID: " + jamsPID); } catch (IOException e) { - logger.severe("Exception thrown when attempting to startup jams process again!"); + log.severe("Exception thrown when attempting to startup jams process again!"); } } @@ -91,13 +91,13 @@ public class AppStarter extends Thread { String OS = System.getProperty("os.name").toLowerCase(); String command; if (OS.indexOf("win") >= 0) { - logger.info("Check if pid " + pidStr + " is alive..."); + log.info("Check if pid " + pidStr + " is alive..."); command = "cmd /c tasklist /FI \"PID eq " + pidStr + "\""; } else if (OS.indexOf("nix") >= 0 || OS.indexOf("nux") >= 0 || OS.contains("mac")) { - logger.info("Check if pid " + pidStr + " is alive..."); + log.info("Check if pid " + pidStr + " is alive..."); command = "ps -p " + pidStr; } else { - logger.warning("Unsupported OS, failing gracefully... "); + log.warning("Unsupported OS, failing gracefully... "); return false; } return isProcessIdRunning(pidStr, command); // call generic implementation @@ -119,7 +119,7 @@ public class AppStarter extends Thread { return false; } catch (Exception ex) { - logger.severe("Got exception using system command!"); + log.severe("Got exception using system command!"); return false; } } @@ -131,7 +131,7 @@ public class AppStarter extends Thread { if (pb != null) { pb.inheritIO(); pb.directory(new File(System.getProperty("user.dir"))); - logger.info("Directory: " + pb.directory().getAbsolutePath()); + log.info("Directory: " + pb.directory().getAbsolutePath()); applicationProcess = pb.start(); } @@ -141,7 +141,7 @@ public class AppStarter extends Thread { } catch (NullPointerException | IOException e) { - logger.severe("An exception occurred! " + e.toString()); + log.severe("An exception occurred! " + e.toString()); } UpdateThread updateThread = new UpdateThread(applicationProcess, parentArgs); updateThread.start(); diff --git a/jams-launcher/src/main/java/launcher/UpdateThread.java b/jams-launcher/src/main/java/launcher/UpdateThread.java index 821a50f9115f85224655a9e950c95b05d3c46b11..8eecf729d054976fcb3223f3c577ace925651580 100644 --- a/jams-launcher/src/main/java/launcher/UpdateThread.java +++ b/jams-launcher/src/main/java/launcher/UpdateThread.java @@ -1,11 +1,7 @@ package launcher; - -import com.google.common.hash.HashCode; -import com.google.common.hash.Hashing; -import com.jsoniter.JsonIterator; -import com.jsoniter.any.Any; import lombok.Getter; import lombok.Setter; +<<<<<<< Updated upstream import org.apache.http.HttpResponse; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; @@ -15,399 +11,30 @@ import org.codehaus.plexus.util.FileUtils; import org.zeromq.SocketType; import org.zeromq.ZMQ; -import javax.net.ssl.SSLContext; import java.io.*; -import java.net.URL; -import java.nio.charset.Charset; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyFactory; -import java.security.KeyStore; -import java.security.PrivateKey; -import java.security.cert.Certificate; -import java.security.cert.CertificateFactory; -import java.security.cert.X509Certificate; -import java.security.spec.PKCS8EncodedKeySpec; import java.util.*; import java.util.concurrent.atomic.AtomicBoolean; -import java.util.jar.JarEntry; -import java.util.jar.JarFile; -import java.util.jar.Manifest; -import java.util.logging.Logger; @Getter @Setter public class UpdateThread extends Thread { - private HashMap<String, String> localVersions = new HashMap<>(); - private HashMap<String, String> localExecs = new HashMap<>(); - private HashMap<String, String> remoteVersions = new HashMap<>(); - private HashMap<String, String> remoteExecs = new HashMap<>(); - private HashMap<String, String> remoteChecksums = new HashMap<>(); - private String[] parentArgs; - private final static Logger logger = Logger.getLogger(UpdateThread.class.getName()); - private static volatile String CORE_PACKAGE_MAIN_CLASS_NAME; - private static volatile String UPDATE_SERVER_URL; - private static volatile Long UPDATE_INTERVAL; - private SSLContext sslContext = null; private Process applicationProcess; - private ZMQ.Socket publisher; - private ZMQ.Socket receiver; - private AtomicBoolean doUpdate = new AtomicBoolean(false); - private MessageReceiver messageReceiver; - - public UpdateThread() { - } public UpdateThread(Process applicationProcess, String[] parentArgs) { - try { - //Use this to notify - publisher = AppStarter.context.socket(SocketType.PUB); - publisher.bind("tcp://*:4572"); - //Use this to updater. - receiver = AppStarter.context.socket(SocketType.REP); - receiver.bind("tcp://*:4573"); - messageReceiver = new MessageReceiver(receiver,doUpdate); - messageReceiver.start(); - } catch (Exception e) { - logger.warning("Could not create and bind publisher and/or receiver! Please contact software developer"); - System.exit(-1); - } - - try { - InputStream input = AppStarter.class.getClassLoader().getResourceAsStream("oem/config.json"); - Any any = JsonIterator.deserialize(input.readAllBytes()); - CORE_PACKAGE_MAIN_CLASS_NAME = any.get("CORE_PACKAGE_MAIN_CLASS_NAME").toString(); - UPDATE_SERVER_URL = any.get("UPDATE_URL").toString(); - UPDATE_INTERVAL = any.get("UPDATE_INTERVAL").toLong(); - - } catch (IOException e) { - logger.warning("Missing OEM configuration! Please contact software developer"); - System.exit(-1); - } - System.out.println("Update thread created..."); this.parentArgs = parentArgs; this.applicationProcess = applicationProcess; } - //It is a VERY bad idea to use version numbers inside file names as this will break at some point and cause MAJOR - //user problems, we can therefore by-pass this by doing the following. - - public static String[] unzipJar(String destinationDir, String jarPath) throws IOException { - String[] resp = new String[2]; - File file = new File(jarPath); - JarFile jar = new JarFile(file); - - // fist get all directories, - // then make those directory on the destination Path - for (Enumeration<JarEntry> enums = jar.entries(); enums.hasMoreElements(); ) { - JarEntry entry = (JarEntry) enums.nextElement(); - - String fileName = destinationDir + File.separator + entry.getName(); - File f = new File(fileName); - - if (fileName.endsWith("/")) { - f.mkdirs(); - } - } - - //now create all files - for (Enumeration<JarEntry> enums = jar.entries(); enums.hasMoreElements(); ) { - JarEntry entry = (JarEntry) enums.nextElement(); - if (entry.toString().contains("META-INF/MANIFEST.MF")) { - String fileName = destinationDir + File.separator + entry.getName(); - File f = new File(fileName); - - if (!fileName.endsWith("/")) { - InputStream is = jar.getInputStream(entry); - FileOutputStream fos = new FileOutputStream(f); - - // write contents of 'is' to 'fos' - while (is.available() > 0) { - fos.write(is.read()); - } - - fos.close(); - is.close(); - } - } - } - - String manifestPath = destinationDir + "/META-INF/MANIFEST.MF"; - Manifest manifest = new Manifest(new URL("file:///" + manifestPath).openStream()); - resp[0] = manifest.getMainAttributes().getValue("Implementation-Version"); - resp[1] = manifest.getMainAttributes().getValue("Main-Class"); - return resp; - } - - public void detectCurrentVersions(String folder) { - try { - File folderExec = new File(folder.replace("/jams-launcher", "")); - File folderLibs = new File(folder.replace("/jams-launcher", "") + "/libs"); - discoverVersions(folderExec); - discoverVersions(folderLibs); - } catch (Exception e) { - logger.warning(e.toString()); - } - } - - private void discoverVersions(File folderLibs) throws IOException { - for (File f : Objects.requireNonNull(folderLibs.listFiles())) { - if (f.isFile() && f.getName().contains(".jar") && !f.getName().contains("jams-launcher")) { - String[] resp = unzipJar(System.getProperty("user.dir") + "/tmpjar/" + f.getName(), f.getAbsolutePath()); - localVersions.put(resp[1], resp[0]); - localExecs.put(resp[1], f.getName()); - } - } - } - - // This reads a file on the server which contains some basic info - // about what new versions of WHAT are available. - private void getLatestVersion() { - try { - //Step 1: Download a file called versions.json - HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); - HttpResponse response = httpClient.execute(new HttpGet(UPDATE_SERVER_URL + "/versions.json")); - //Step 2: Load the file into the hashmaps - Any any = JsonIterator.deserialize(response.getEntity().getContent().readAllBytes()); - any.asMap().forEach((k, v) -> { - remoteVersions.put(k, v.get("version").toString()); - remoteExecs.put(k, v.get("filename").toString()); - remoteChecksums.put(k, v.get("md5").toString()); - }); - } catch (Exception e) { - logger.warning("Could not establish connection to JAMS Update Center with error: " + e.toString()); - } - } - - //You only need to load the keystore once, never do it again, you - //can theoretically put it as timer if you don't want users to - //restart the app. - public boolean loadLicense() { - try { - //Because apparently we should display versions even though the user has no license, we have to - //do this in two steps: - CertificateFactory cf = CertificateFactory.getInstance("X.509"); - InputStream input = AppStarter.class.getClassLoader().getResourceAsStream("oem/ca.crt"); - if (input == null) { - System.out.println("No CA Found... this is critical!"); - System.exit(-1); - } - Certificate ca = cf.generateCertificate(input); - KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); - trustStore.load(null, null); - trustStore.setCertificateEntry("ca", ca); - //Inject the SSL Connection here for a first time. - sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, null) - .build(); - //The below will either pass or fail.. - KeyStore ks = KeyStore.getInstance("JKS"); - ks.load(null); - if(!Files.exists(Path.of(System.getProperty("user.dir") + File.separator + "license.dat"))){ - System.out.println("You do not have a valid license - ignoring the rest of this function."); - System.out.println("You will still be notified about updates!"); - return false; - } - //Assuming the file exists, we just read the file. - String b64License = new String(Files.readAllBytes(Path.of(System.getProperty("user.dir") + File.separator + "license.dat"))); - //Since this is base64, we need to decode it. - String strLicense = new String(Base64.getDecoder().decode(b64License)); - //Now we need to split it. This is actually easy. - int cutPoint = strLicense.indexOf("-----BEGIN PRIVATE KEY-----"); - - String publicKey = strLicense.substring(0, cutPoint); - String privateKey = strLicense.substring(cutPoint); - - privateKey = privateKey.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", ""); - KeyFactory kf = KeyFactory.getInstance("RSA"); - PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)); - PrivateKey pvk = kf.generatePrivate(keysp); - - InputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(Charset.forName("UTF-8"))); - Certificate c = cf.generateCertificate(inputStream); - Certificate[] chain = new Certificate[]{c}; - - String alias1 = ((X509Certificate) c).getSubjectX500Principal().getName(); - ks.setCertificateEntry(alias1, c); - ks.setKeyEntry("", pvk, "".toCharArray(), chain); - try { - ((X509Certificate) c).checkValidity(); - c.verify(ca.getPublicKey()); - } catch (Exception e) { - logger.warning("Your license is no longer valid or has been tampered with - " + e.toString()); - return false; - } - sslContext = SSLContexts.custom().loadKeyMaterial(ks, "".toCharArray()).loadTrustMaterial(trustStore, null) - .build(); - } catch (Exception e) { - logger.warning("Could not read license file with error " + e.toString()); - return false; - } - return true; - } - - public void downloadFiles(HashMap<String, String> files) { - //I know this contradicts my dogma, but this really would have been an overkill for this project, - //I just claim that everything which is not core gets dumped to the lib directory. - - // temp folder for safe download and integrity check - File tmpFolder = new File(System.getProperty("user.dir") + "/tmp/"); - tmpFolder.mkdirs(); - - files.forEach((k, v) -> { - try { - HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); - HttpResponse httpResponse = httpClient.execute(new HttpGet(UPDATE_SERVER_URL + "/updates/" + v)); - if (httpResponse.getStatusLine().getStatusCode() == 200) { - logger.info(tmpFolder.getPath() + "/" + files.get(k)); - FileOutputStream fos = new FileOutputStream(tmpFolder.getPath() + "/" + files.get(k)); - if (k.equals(CORE_PACKAGE_MAIN_CLASS_NAME)) { - httpResponse.getEntity().writeTo(fos); - fos.close(); - - if (checksum(tmpFolder.getPath() + "/" + files.get(k)).equals(remoteChecksums.get(k))) { - logger.info("Successfully downloaded the core package!"); - remoteChecksums.remove(CORE_PACKAGE_MAIN_CLASS_NAME); - } - - } else { - httpResponse.getEntity().writeTo(fos); - fos.close(); - - if (checksum(tmpFolder.getPath() + "/" + files.get(k)).equals(remoteChecksums.get(k))) { - logger.info("Successfully downloaded a library package!"); - remoteChecksums.remove(remoteChecksums.get(k)); - } - } - - } else { - logger.warning("The server declared an update but does not have the required files?!"); - } - } catch (Exception e1) { - logger.warning("Could not download an update with error " + e1.toString()); - } - }); - } - - //If the user clicks start update then kill, download, start - public void checkUpdate(){ - //Step 1: download the JSON file description (this would also contain the signatures). - getLatestVersion(); - //Step 2: compare the versions - we assume no admin is stupid enough to regress versions. - detectCurrentVersions(System.getProperty("user.dir")); - localVersions.forEach((k, v) -> { - if (remoteVersions.containsKey(k) && remoteVersions.get(k).equals(v)) { - remoteVersions.remove(k); - remoteExecs.remove(k); - } - }); - //Step 3: If there are no valid remote versions available, send an empty JSON. - if (remoteVersions.size() == 0){ - publisher.sendMore("UPDATE"); - publisher.send("{}"); - return; - } - else { - //In the new paradigm, we notify the server via zmq. - publisher.sendMore("UPDATE"); - publisher.send(remoteVersions.toString()); - } - } - - public void doUpdate() throws IOException{ - //Nothing to do here if the size was 0. - if(remoteVersions.size() == 0) return; - downloadFiles(remoteExecs); - if (!remoteChecksums.containsKey(CORE_PACKAGE_MAIN_CLASS_NAME)) { - - // stop AppStarter timer - AppStarter.getTimer().cancel(); - - // Stop current process - this.applicationProcess.destroy(); - - if (this.applicationProcess.isAlive()) - this.applicationProcess.destroyForcibly(); - - System.out.println("Murdered process with pid: " + AppStarter.getJamsPID()); - // delete old files - remoteExecs.forEach((k, v) -> { - logger.info(remoteExecs.get(k)); - - if (k.equals(CORE_PACKAGE_MAIN_CLASS_NAME)) { - File f = new File(System.getProperty("user.dir") + "/" + remoteExecs.get(k)); - f.delete(); - f = new File(System.getProperty("user.dir") + "/tmp/" + remoteExecs.get(k)); - if (!f.renameTo(new File(System.getProperty("user.dir") + "/" + remoteExecs.get(k)))) - logger.warning("An error occurred while attempting to move the file!"); - else - f.delete(); - } else { - File f = new File(System.getProperty("user.dir") + "/libs/" + remoteExecs.get(k)); - f.delete(); - f = new File(System.getProperty("user.dir") + "/tmp/" + remoteExecs.get(k)); - if (!f.renameTo(new File(System.getProperty("user.dir") + "/libs/" + remoteExecs.get(k)))) - logger.warning("An error occurred while attempting to move the file!"); - else - f.delete(); - } - }); - - // Finally, start main jar - - ProcessBuilder pb = AppStarter.startAccountManagementServer(parentArgs); - pb.inheritIO(); - pb.directory(new File(System.getProperty("user.dir"))); - this.applicationProcess = pb.start(); - AppStarter.setJamsPID(this.applicationProcess.pid()); - //Start process check timer again - AppStarter.setTimer(AppStarter.startProcessCheckTimer(20000, parentArgs)); - - System.out.println("New PID: " + AppStarter.getJamsPID()); - System.out.println("Update complete. Now running newest version"); - - } else - System.out.println("An error occurred during the core package update process, the application has not been restarted."); - - // delete tmp folder - try { - FileUtils.deleteDirectory(System.getProperty("user.dir") + "/tmp/"); - } catch (IOException e) { - logger.warning("An error occurred while attempting to delete /tmp/ folder!"); - } - } - - private static String checksum(String filepath) throws IOException { - - HashCode hash = com.google.common.io.Files - .hash(new File(filepath), Hashing.md5()); - - logger.info("Calculated md5: " + hash.toString()); - return hash.toString(); - } - @Override public void run() { - Timer timer = new Timer(); + UpdateDaemon timer = new UpdateDaemon(); // check if license is valid, then attempt SSL handshake with update server. try { System.out.println("Update timer started"); - TimerTask pollTask = new TimerTask() { - @Override - public void run() { - try { - loadLicense(); - checkUpdate(); - if(doUpdate.get()) { - doUpdate(); - } - } catch (Exception e) { - System.out.println("Exception thrown when attempting to update! " + e.toString()); - } - } - }; + UpdateCheckTask pollTask = new UpdateCheckTask(); timer.scheduleAtFixedRate(pollTask, 15000, UPDATE_INTERVAL); } catch (Exception e) { logger.warning("error! " + e.toString()); diff --git a/jams-launcher/src/main/java/module-info.java b/jams-launcher/src/main/java/module-info.java index a8b0509b3f6070fd92906c5615c6f4602f92022c..32a36368e867545021d6ed6944677092af229360 100644 --- a/jams-launcher/src/main/java/module-info.java +++ b/jams-launcher/src/main/java/module-info.java @@ -14,5 +14,6 @@ module jams.launcher { requires org.apache.httpcomponents.httpcore; requires org.apache.httpcomponents.httpclient; requires plexus.utils; + requires updater; } diff --git a/jams-server/src/main/java/module-info.java b/jams-server/src/main/java/module-info.java index b695686d7f4179a841768289a220bac0b94f7e9f..7ab7f5dd99500436d31941ced10ad7f15bf96377 100644 --- a/jams-server/src/main/java/module-info.java +++ b/jams-server/src/main/java/module-info.java @@ -18,6 +18,7 @@ module jams.server { requires java.naming; requires java.logging; requires javax.servlet.api; + requires org.json; exports net.jami.jams.server.servlets.general to org.apache.tomcat.embed.core; exports net.jami.jams.server.servlets.filters to org.apache.tomcat.embed.core; exports net.jami.jams.server.servlets.api.auth.login to org.apache.tomcat.embed.core; diff --git a/jams-server/src/main/java/net/jami/jams/server/Server.java b/jams-server/src/main/java/net/jami/jams/server/Server.java index ad882a203f2578ae7a07c59057c00f6bce0cffa9..df374d4627f78dc9c51ce7d3ea68fcd185623a30 100644 --- a/jams-server/src/main/java/net/jami/jams/server/Server.java +++ b/jams-server/src/main/java/net/jami/jams/server/Server.java @@ -15,6 +15,7 @@ import net.jami.jams.common.utils.UpdateInterface; import net.jami.jams.nameserver.LocalNameServer; import net.jami.jams.nameserver.PublicNameServer; import net.jami.jams.server.core.TomcatLauncher; +import net.jami.jams.server.licensing.LicenseService; import net.jami.jams.server.startup.AuthModuleLoader; import net.jami.jams.server.startup.CryptoEngineLoader; @@ -80,6 +81,9 @@ public class Server { else nameServer = new LocalNameServer(dataStore,userAuthenticationModule,serverSettings.getServerPublicURI()); } else nameServer = new LocalNameServer(dataStore,userAuthenticationModule,serverSettings.getServerPublicURI()); + + new LicenseService().loadLicense(); + appUpdater = UpdaterLoader.loadUpdater(); log.info("All services are UP and ready for use..."); } catch (Exception e){ diff --git a/jams-server/src/main/java/net/jami/jams/server/licensing/LicenseService.java b/jams-server/src/main/java/net/jami/jams/server/licensing/LicenseService.java index 1e0f30935cfbc0a0deb2e834faba4d43aadafa00..0b91c031008879bbc25afa0c3dc77d2fb6a38cd1 100644 --- a/jams-server/src/main/java/net/jami/jams/server/licensing/LicenseService.java +++ b/jams-server/src/main/java/net/jami/jams/server/licensing/LicenseService.java @@ -64,14 +64,14 @@ public class LicenseService { try{ ((X509Certificate) c).checkValidity(); c.verify(ca.getPublicKey()); - Server.setActivated(true); + Server.activated.set(true); if (array != null) { JSONObject jsonObject = new JSONObject(new String(array)); licenseType = (String) jsonObject.get("type"); } } catch (Exception e){ - Server.setActivated(false); + Server.activated.set(false); licenseType = "COMMUNITY"; log.warn("Your license is no longer valid or has been tampered with - " + e.toString()); } diff --git a/jams-server/src/main/java/net/jami/jams/server/servlets/api/update/LicenseServlet.java b/jams-server/src/main/java/net/jami/jams/server/servlets/api/update/LicenseServlet.java index de2518e1b2e002f8e5be1458852755164d38260d..0f78fc043f24f1c222d106b6c4d2c4d82f889317 100644 --- a/jams-server/src/main/java/net/jami/jams/server/servlets/api/update/LicenseServlet.java +++ b/jams-server/src/main/java/net/jami/jams/server/servlets/api/update/LicenseServlet.java @@ -26,7 +26,7 @@ public class LicenseServlet extends HttpServlet { try { resp.setStatus(200); HashMap<String,Object> payload = new HashMap<>(); - payload.put("isActive", Server.isActivated()); + payload.put("isActive", Server.activated.get()); payload.put("licenseType", new LicenseService().getLicenseType()); payload.put("currentVersion", LicenseUtils.checkVersion(System.getProperty("user.dir") + "/tmpjar/", System.getProperty("user.dir") + "/jams-server.jar")); resp.getOutputStream().write(JsonStream.serialize(payload).getBytes()); diff --git a/pom.xml b/pom.xml index e55eb9726a217afc9d8e262b4cb5eb0eacd5ff85..5d2948267ac00c703dba9620d5d742629898b9b4 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,9 @@ <properties> <revision>2.0</revision> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <revision>0.5A</revision> <maven.compiler.version>3.8.1</maven.compiler.version> <java.version>11</java.version> <bouncy.castle.version>1.65</bouncy.castle.version> diff --git a/updater/pom.xml b/updater/pom.xml new file mode 100644 index 0000000000000000000000000000000000000000..5e4e56891f9cbd9e19ca32e76b873731e3bd9dcf --- /dev/null +++ b/updater/pom.xml @@ -0,0 +1,104 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <parent> + <artifactId>jams3-parent</artifactId> + <groupId>net.jami</groupId> + <version>${revision}</version> + </parent> + <modelVersion>4.0.0</modelVersion> + + <artifactId>updater</artifactId> + <dependencies> + <dependency> + <groupId>net.jami</groupId> + <artifactId>jams-common</artifactId> + <version>${revision}</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpcore</artifactId> + <version>${apache.httpcore.version}</version> + </dependency> + <dependency> + <groupId>org.apache.httpcomponents</groupId> + <artifactId>httpclient</artifactId> + <version>${apache.httpclient.version}</version> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>20.0</version> + </dependency> + <dependency> + <groupId>org.codehaus.plexus</groupId> + <artifactId>plexus-utils</artifactId> + <version>3.1.0</version> + </dependency> + <dependency> + <groupId>org.zeromq</groupId> + <artifactId>jeromq</artifactId> + <version>${jeromq.version}</version> + </dependency> + <dependency> + <groupId>org.zeromq</groupId> + <artifactId>jeromq</artifactId> + <version>0.5.2</version> + </dependency> + <dependency> + <groupId>net.jami</groupId> + <artifactId>jams-common</artifactId> + <version>0.5A</version> + <scope>compile</scope> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-shade-plugin</artifactId> + <version>${maven.shade.version}</version> + <executions> + <!-- Run shade goal on package phase --> + <execution> + <phase>package</phase> + <goals> + <goal>shade</goal> + </goals> + <configuration> + <outputFile>../jams/libs/${project.artifactId}.jar</outputFile> + <filters> + <filter> + <artifact>*:*</artifact> + <excludes> + <exclude>META-INF/*.SF</exclude> + <exclude>META-INF/*.DSA</exclude> + <exclude>META-INF/*.RSA</exclude> + </excludes> + </filter> + </filters> + <transformers> + <!-- add Main-Class to manifest file --> + <transformer + implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer"> + <manifestEntries> + <Main-Class>net.jami.jams.server.Server</Main-Class> + <Implementation-Title>${project.artifactId}</Implementation-Title> + <Implementation-Version>${project.version}</Implementation-Version> + <Class-Path>.</Class-Path> + </manifestEntries> + </transformer> + <transformer + implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/> + </transformers> + </configuration> + </execution> + </executions> + </plugin> + </plugins> + </build> + +</project> \ No newline at end of file diff --git a/updater/src/main/java/module-info.java b/updater/src/main/java/module-info.java new file mode 100644 index 0000000000000000000000000000000000000000..205d6d9200703e631b5c0efb4308dbc47a2305d0 --- /dev/null +++ b/updater/src/main/java/module-info.java @@ -0,0 +1,13 @@ +module updater { + exports net.jami.jams.updater; + requires jams.common; + requires lombok; + requires org.slf4j; + requires jsoniter; + requires guava; + requires org.apache.httpcomponents.httpclient; + requires org.apache.httpcomponents.httpcore; + requires plexus.utils; + requires jeromq; + +} \ No newline at end of file diff --git a/updater/src/main/java/net/jami/jams/updater/JAMSUpdater.java b/updater/src/main/java/net/jami/jams/updater/JAMSUpdater.java new file mode 100644 index 0000000000000000000000000000000000000000..7df011d43d97b6f758b1ca3982bc73e60551d257 --- /dev/null +++ b/updater/src/main/java/net/jami/jams/updater/JAMSUpdater.java @@ -0,0 +1,236 @@ +package net.jami.jams.updater; + +import net.jami.jams.common.updater.AppUpdater; +import org.apache.http.ssl.SSLContexts; +import org.zeromq.SocketType; +import org.zeromq.ZMQ; + +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import java.io.*; +import java.net.URL; +import java.nio.charset.Charset; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.KeyFactory; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.Certificate; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.security.spec.PKCS8EncodedKeySpec; +import java.util.Base64; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Objects; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.Manifest; + +public class JAMSUpdater implements AppUpdater { + + UpdateDownloader updateDownloader = new UpdateDownloader(); + private SSLSocketFactory sslSocketFactory; + private SSLContext sslContext = null; + private ZMQ.Socket publisher; + private ZMQ.Socket receiver; + private MessageReceiver messageReceiver; + private AtomicBoolean doUpdate = new AtomicBoolean(false); + + private HashMap<String, String> localVersions = new HashMap<>(); + private HashMap<String, String> localExecs = new HashMap<>(); + private HashMap<String, String> remoteVersions = new HashMap<>(); + private HashMap<String, String> remoteExecs = new HashMap<>(); + private HashMap<String, String> remoteChecksums = new HashMap<>(); + + public JAMSUpdater() { + + try { + //Use this to notify + publisher = AppStarter.context.socket(SocketType.PUB); + publisher.bind("tcp://*:4572"); + //Use this to updater. + receiver = AppStarter.context.socket(SocketType.REP); + receiver.bind("tcp://*:4573"); + messageReceiver = new MessageReceiver(receiver, doUpdate); + messageReceiver.start(); + } catch (Exception e) { + log.warn("Could not create and bind publisher and/or receiver! Please contact software developer"); + System.exit(-1); + } + } + + //It is a VERY bad idea to use version numbers inside file names as this will break at some point and cause MAJOR + //user problems, we can therefore by-pass this by doing the following. + + public static String[] unzipJar(String destinationDir, String jarPath) throws IOException { + String[] resp = new String[2]; + File file = new File(jarPath); + JarFile jar = new JarFile(file); + + // fist get all directories, + // then make those directory on the destination Path + for (Enumeration<JarEntry> enums = jar.entries(); enums.hasMoreElements(); ) { + JarEntry entry = (JarEntry) enums.nextElement(); + + String fileName = destinationDir + File.separator + entry.getName(); + File f = new File(fileName); + + if (fileName.endsWith("/")) { + f.mkdirs(); + } + } + + //now create all files + for (Enumeration<JarEntry> enums = jar.entries(); enums.hasMoreElements(); ) { + JarEntry entry = (JarEntry) enums.nextElement(); + if (entry.toString().contains("META-INF/MANIFEST.MF")) { + String fileName = destinationDir + File.separator + entry.getName(); + File f = new File(fileName); + + if (!fileName.endsWith("/")) { + InputStream is = jar.getInputStream(entry); + FileOutputStream fos = new FileOutputStream(f); + + // write contents of 'is' to 'fos' + while (is.available() > 0) { + fos.write(is.read()); + } + + fos.close(); + is.close(); + } + } + } + + String manifestPath = destinationDir + "/META-INF/MANIFEST.MF"; + Manifest manifest = new Manifest(new URL("file:///" + manifestPath).openStream()); + resp[0] = manifest.getMainAttributes().getValue("Implementation-Version"); + resp[1] = manifest.getMainAttributes().getValue("Main-Class"); + return resp; + } + + private void discoverVersions(File folderLibs) throws IOException { + for (File f : Objects.requireNonNull(folderLibs.listFiles())) { + if (f.isFile() && f.getName().contains(".jar") && !f.getName().contains("jams-launcher")) { + String[] resp = unzipJar(System.getProperty("user.dir") + "/tmpjar/" + f.getName(), f.getAbsolutePath()); + localVersions.put(resp[1], resp[0]); + localExecs.put(resp[1], f.getName()); + } + } + } + + @Override + public String getCurrentVersions(String folder) { + + try { + File folderExec = new File(folder.replace("/jams-launcher", "")); + File folderLibs = new File(folder.replace("/jams-launcher", "") + "/libs"); + discoverVersions(folderExec); + discoverVersions(folderLibs); + } catch (Exception e) { + log.warn(e.toString()); + } + } + + public boolean loadLicense() { + try { + //Because apparently we should display versions even though the user has no license, we have to + //do this in two steps: + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + InputStream input = this.getClass().getClassLoader().getResourceAsStream("oem/ca.crt"); + if (input == null) { + System.out.println("No CA Found... this is critical!"); + System.exit(-1); + } + Certificate ca = cf.generateCertificate(input); + KeyStore trustStore = KeyStore.getInstance(KeyStore.getDefaultType()); + trustStore.load(null, null); + trustStore.setCertificateEntry("ca", ca); + //Inject the SSL Connection here for a first time. + sslContext = SSLContexts.custom().loadTrustMaterial(trustStore, null) + .build(); + //The below will either pass or fail.. + KeyStore ks = KeyStore.getInstance("JKS"); + ks.load(null); + if(!Files.exists(Path.of(System.getProperty("user.dir") + File.separator + "license.dat"))){ + System.out.println("You do not have a valid license - ignoring the rest of this function."); + System.out.println("You will still be notified about updates!"); + return false; + } + //Assuming the file exists, we just read the file. + String b64License = new String(Files.readAllBytes(Path.of(System.getProperty("user.dir") + File.separator + "license.dat"))); + //Since this is base64, we need to decode it. + String strLicense = new String(Base64.getDecoder().decode(b64License)); + //Now we need to split it. This is actually easy. + int cutPoint = strLicense.indexOf("-----BEGIN PRIVATE KEY-----"); + + String publicKey = strLicense.substring(0, cutPoint); + String privateKey = strLicense.substring(cutPoint); + + privateKey = privateKey.replaceAll("\\n", "").replace("-----BEGIN PRIVATE KEY-----", "").replace("-----END PRIVATE KEY-----", ""); + KeyFactory kf = KeyFactory.getInstance("RSA"); + PKCS8EncodedKeySpec keysp = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey)); + PrivateKey pvk = kf.generatePrivate(keysp); + + InputStream inputStream = new ByteArrayInputStream(publicKey.getBytes(Charset.forName("UTF-8"))); + Certificate c = cf.generateCertificate(inputStream); + Certificate[] chain = new Certificate[]{c}; + + String alias1 = ((X509Certificate) c).getSubjectX500Principal().getName(); + ks.setCertificateEntry(alias1, c); + ks.setKeyEntry("", pvk, "".toCharArray(), chain); + try { + ((X509Certificate) c).checkValidity(); + c.verify(ca.getPublicKey()); + } catch (Exception e) { + log.warn("Your license is no longer valid or has been tampered with - " + e.toString()); + return false; + } + sslContext = SSLContexts.custom().loadKeyMaterial(ks, "".toCharArray()).loadTrustMaterial(trustStore, null) + .build(); + } catch (Exception e) { + log.warn("Could not read license file with error " + e.toString()); + return false; + } + return true; + } + + //If the user clicks start update then kill, download, start + public void checkUpdate(){ + //Step 1: download the JSON file description (this would also contain the signatures). + getLatestVersion(); + //Step 2: compare the versions - we assume no admin is stupid enough to regress versions. + getCurrentVersions(System.getProperty("user.dir")); + localVersions.forEach((k, v) -> { + if (remoteVersions.containsKey(k) && remoteVersions.get(k).equals(v)) { + remoteVersions.remove(k); + remoteExecs.remove(k); + } + }); + //Step 3: If there are no valid remote versions available, send an empty JSON. + if (remoteVersions.size() == 0){ + publisher.sendMore("UPDATE"); + publisher.send("{}"); + return; + } + else { + //In the new paradigm, we notify the server via zmq. + publisher.sendMore("UPDATE"); + publisher.send(remoteVersions.toString()); + } + } + + + + @Override + public String getLatestVersion() { + return null; + } + + @Override + public boolean downloadUpdates() { + return updateDownloader.doUpdate(); + } +} diff --git a/jams-launcher/src/main/java/launcher/MessageReceiver.java b/updater/src/main/java/net/jami/jams/updater/MessageReceiver.java similarity index 95% rename from jams-launcher/src/main/java/launcher/MessageReceiver.java rename to updater/src/main/java/net/jami/jams/updater/MessageReceiver.java index 8c153312537536d9660afe06effeec1cdc77ea6f..e25571d4ba778dd462b716f7229d1d0d945f039b 100644 --- a/jams-launcher/src/main/java/launcher/MessageReceiver.java +++ b/updater/src/main/java/net/jami/jams/updater/MessageReceiver.java @@ -1,4 +1,4 @@ -package launcher; +package net.jami.jams.updater; import org.zeromq.ZMQ; diff --git a/updater/src/main/java/net/jami/jams/updater/UpdateCheckTask.java b/updater/src/main/java/net/jami/jams/updater/UpdateCheckTask.java new file mode 100644 index 0000000000000000000000000000000000000000..94ca31d924f7d80af620ef6103f9b6b6fcca5a8f --- /dev/null +++ b/updater/src/main/java/net/jami/jams/updater/UpdateCheckTask.java @@ -0,0 +1,63 @@ +package net.jami.jams.updater; + +import com.jsoniter.JsonIterator; +import com.jsoniter.any.Any; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClients; + +import javax.net.ssl.HttpsURLConnection; +import java.net.URL; +import java.util.List; +import java.util.TimerTask; +import java.util.concurrent.atomic.AtomicBoolean; + +import static net.jami.jams.updater.UpdateDaemon.UPDATE_SERVER_URI; + +@Slf4j +public class UpdateCheckTask extends TimerTask { + + // private final List<String> files; + private AtomicBoolean doUpdate = new AtomicBoolean(false); + + public UpdateCheckTask() { + // this.files = files; + } + + @Override + public void run() { + + try { + JAMSUpdater updater = new JAMSUpdater(); + updater.loadLicense(); + updater.checkUpdate(); + if(doUpdate.get()) { + doUpdate(); + } + } catch (Exception e) { + System.out.println("Exception thrown when attempting to update! " + e.toString()); + } + + } + + // This reads a file on the server which contains some basic info + // about what new versions of WHAT are available. + private void getLatestVersion() { + try { + //Step 1: Download a file called versions.json + HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); + HttpResponse response = httpClient.execute(new HttpGet(UPDATE_SERVER_URL + "/versions.json")); + //Step 2: Load the file into the hashmaps + Any any = JsonIterator.deserialize(response.getEntity().getContent().readAllBytes()); + any.asMap().forEach((k, v) -> { + remoteVersions.put(k, v.get("version").toString()); + remoteExecs.put(k, v.get("filename").toString()); + remoteChecksums.put(k, v.get("md5").toString()); + }); + } catch (Exception e) { + log.warn("Could not establish connection to JAMS Update Center with error: " + e.toString()); + } + } +} diff --git a/updater/src/main/java/net/jami/jams/updater/UpdateDaemon.java b/updater/src/main/java/net/jami/jams/updater/UpdateDaemon.java new file mode 100644 index 0000000000000000000000000000000000000000..1fba028ecd97dcde7f612825b67eb390b0cfae77 --- /dev/null +++ b/updater/src/main/java/net/jami/jams/updater/UpdateDaemon.java @@ -0,0 +1,30 @@ +package net.jami.jams.updater; + +import com.jsoniter.JsonIterator; +import com.jsoniter.any.Any; +import lombok.Getter; +import lombok.Setter; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; + +@Getter +@Setter +public class UpdateDaemon extends Timer { + + private static volatile String UPDATE_SERVER_URL; + private static volatile Long UPDATE_INTERVAL; + public static final List<String> updateFiles = new ArrayList<>(); + + public UpdateDaemon() { + + InputStream input = this.getClass().getClassLoader().getResourceAsStream("oem/config.json"); + Any any = JsonIterator.deserialize(input.readAllBytes()); + UPDATE_SERVER_URL = any.get("UPDATE_URL").toString(); + UPDATE_INTERVAL = any.get("UPDATE_INTERVAL").toLong(); + this.schedule(new UpdateCheckTask(updateFiles),0,UPDATE_INTERVAL); + } + +} diff --git a/updater/src/main/java/net/jami/jams/updater/UpdateDownloader.java b/updater/src/main/java/net/jami/jams/updater/UpdateDownloader.java new file mode 100644 index 0000000000000000000000000000000000000000..f7a4784328ce07935788074c5cdaf9860f61edeb --- /dev/null +++ b/updater/src/main/java/net/jami/jams/updater/UpdateDownloader.java @@ -0,0 +1,165 @@ +package net.jami.jams.updater; + +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; +import com.jsoniter.JsonIterator; +import com.jsoniter.any.Any; +import lombok.extern.slf4j.Slf4j; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.config.RequestConfig; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.impl.client.HttpClients; +import org.apache.http.ssl.SSLContexts; +import org.codehaus.plexus.util.FileUtils; + +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSocketFactory; +import java.io.*; +import java.util.HashMap; + +@Slf4j +public class UpdateDownloader { + private SSLSocketFactory sslSocketFactory; + private SSLContext sslContext = null; + private Process applicationProcess; + + + private static volatile String CORE_PACKAGE_MAIN_CLASS_NAME; + private static volatile String UPDATE_SERVER_URL; + private static volatile Long UPDATE_INTERVAL; + + + public UpdateDownloader() { + try { + InputStream input = this.getClass().getClassLoader().getResourceAsStream("oem/config.json"); + Any any = JsonIterator.deserialize(input.readAllBytes()); + CORE_PACKAGE_MAIN_CLASS_NAME = any.get("CORE_PACKAGE_MAIN_CLASS_NAME").toString(); + UPDATE_SERVER_URL = any.get("UPDATE_URL").toString(); + UPDATE_INTERVAL = any.get("UPDATE_INTERVAL").toLong(); + + } catch (IOException e) { + log.warn("Missing OEM configuration! Please contact software developer"); + System.exit(-1); + } + } + + public void downloadFiles(HashMap<String, String> files) { + //I know this contradicts my dogma, but this really would have been an overkill for this project, + //I just claim that everything which is not core gets dumped to the lib directory. + + // temp folder for safe download and integrity check + File tmpFolder = new File(System.getProperty("user.dir") + "/tmp/"); + tmpFolder.mkdirs(); + + files.forEach((k, v) -> { + try { + HttpClient httpClient = HttpClients.custom().setSSLContext(sslContext).build(); + HttpResponse httpResponse = httpClient.execute(new HttpGet(UPDATE_SERVER_URL + "/updates/" + v)); + if (httpResponse.getStatusLine().getStatusCode() == 200) { + log.info(tmpFolder.getPath() + "/" + files.get(k)); + FileOutputStream fos = new FileOutputStream(tmpFolder.getPath() + "/" + files.get(k)); + if (k.equals(CORE_PACKAGE_MAIN_CLASS_NAME)) { + httpResponse.getEntity().writeTo(fos); + fos.close(); + + if (checksum(tmpFolder.getPath() + "/" + files.get(k)).equals(remoteChecksums.get(k))) { + log.info("Successfully downloaded the core package!"); + remoteChecksums.remove(CORE_PACKAGE_MAIN_CLASS_NAME); + } + + } else { + httpResponse.getEntity().writeTo(fos); + fos.close(); + + if (checksum(tmpFolder.getPath() + "/" + files.get(k)).equals(remoteChecksums.get(k))) { + log.info("Successfully downloaded a library package!"); + remoteChecksums.remove(remoteChecksums.get(k)); + } + } + + } else { + log.warn("The server declared an update but does not have the required files?!"); + } + } catch (Exception e1) { + log.warn("Could not download an update with error " + e1.toString()); + } + }); + } + + private static String checksum(String filepath) throws IOException { + + HashCode hash = com.google.common.io.Files + .hash(new File(filepath), Hashing.md5()); + + log.warn("Calculated md5: " + hash.toString()); + return hash.toString(); + } + + + public void doUpdate() throws IOException { + //Nothing to do here if the size was 0. + if(remoteVersions.size() == 0) return; + downloadFiles(remoteExecs); + if (!remoteChecksums.containsKey(CORE_PACKAGE_MAIN_CLASS_NAME)) { + + // stop AppStarter timer + AppStarter.getTimer().cancel(); + + // Stop current process + this.applicationProcess.destroy(); + + if (this.applicationProcess.isAlive()) + this.applicationProcess.destroyForcibly(); + + System.out.println("Murdered process with pid: " + AppStarter.getJamsPID()); + // delete old files + remoteExecs.forEach((k, v) -> { + log.info(remoteExecs.get(k)); + + if (k.equals(CORE_PACKAGE_MAIN_CLASS_NAME)) { + File f = new File(System.getProperty("user.dir") + "/" + remoteExecs.get(k)); + f.delete(); + f = new File(System.getProperty("user.dir") + "/tmp/" + remoteExecs.get(k)); + if (!f.renameTo(new File(System.getProperty("user.dir") + "/" + remoteExecs.get(k)))) + log.warn("An error occurred while attempting to move the file!"); + else + f.delete(); + } else { + File f = new File(System.getProperty("user.dir") + "/libs/" + remoteExecs.get(k)); + f.delete(); + f = new File(System.getProperty("user.dir") + "/tmp/" + remoteExecs.get(k)); + if (!f.renameTo(new File(System.getProperty("user.dir") + "/libs/" + remoteExecs.get(k)))) + log.warn("An error occurred while attempting to move the file!"); + else + f.delete(); + } + }); + + // Finally, start main jar + + ProcessBuilder pb = AppStarter.startAccountManagementServer(parentArgs); + pb.inheritIO(); + pb.directory(new File(System.getProperty("user.dir"))); + this.applicationProcess = pb.start(); + AppStarter.setJamsPID(this.applicationProcess.pid()); + //Start process check timer again + AppStarter.setTimer(AppStarter.startProcessCheckTimer(20000, parentArgs)); + + System.out.println("New PID: " + AppStarter.getJamsPID()); + System.out.println("Update complete. Now running newest version"); + + } else + System.out.println("An error occurred during the core package update process, the application has not been restarted."); + + // delete tmp folder + try { + FileUtils.deleteDirectory(System.getProperty("user.dir") + "/tmp/"); + } catch (IOException e) { + log.warn("An error occurred while attempting to delete /tmp/ folder!"); + } + } + + +}