diff --git a/edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/isc/IscReceiveSrv.java b/edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/isc/IscReceiveSrv.java index c155fe5b86..ed3ae70482 100644 --- a/edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/isc/IscReceiveSrv.java +++ b/edexOsgi/com.raytheon.edex.plugin.gfe/src/com/raytheon/edex/plugin/gfe/isc/IscReceiveSrv.java @@ -19,15 +19,17 @@ **/ package com.raytheon.edex.plugin.gfe.isc; -import java.io.File; -import java.io.FilenameFilter; import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardCopyOption; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; @@ -51,8 +53,6 @@ import com.raytheon.uf.common.python.concurrent.PythonJobCoordinator; import com.raytheon.uf.common.status.IUFStatusHandler; import com.raytheon.uf.common.status.UFStatus; import com.raytheon.uf.common.status.UFStatus.Priority; -import com.raytheon.uf.common.util.FileUtil; -import com.raytheon.uf.common.util.file.FilenameFilters; /** * ISC data receive service. Takes incoming request and executes iscDataRec @@ -69,6 +69,7 @@ import com.raytheon.uf.common.util.file.FilenameFilters; * Mar 14, 2013 #1794 djohnson Consolidate common FilenameFilter implementations. * Dec 10, 2014 #4953 randerso Properly handle single file reception * May 06, 2015 #4383 dgilling Properly XML parse incoming XML file. + * May 20, 2015 #4491 dgilling Remediate path manipulation possibilities. * * * @@ -81,12 +82,23 @@ public class IscReceiveSrv { private static final transient IUFStatusHandler statusHandler = UFStatus .getHandler(IscReceiveSrv.class); - private static final String ISC_REQUEST = "iscrequest"; - private static final String METHOD_NAME = "main"; - private static final FilenameFilter docFileFilter = FilenameFilters - .byFileExtension(".doc"); + /* + * TODO: determine if this constant and the cleanup of DOC files in the + * finally block of prepareIscDataRec() is still necessary. + */ + private static final String DOC_FILE_FILTER = "*.doc"; + + private static final Path ISC_PRODUCTS_DIR = Paths.get("/awips2", + "GFESuite", "products", "ISC"); + + private static final Collection WHITELISTED_PATHS = Arrays + .asList(ISC_PRODUCTS_DIR); + + private static final String COPY_ERROR_MSG = "Failed to copy: [%s] to %s. Unable to execute iscDataRec for %s."; + + private static final String UNSAFE_PATH_MSG = "Skipping iscDataRec processing because file %s comes from an unsafe location."; private static final IPythonJobListener jobListener = new IPythonJobListener() { @@ -150,131 +162,102 @@ public class IscReceiveSrv { throws IOException, InterruptedException, GfeConfigurationException, SAXException, ParserConfigurationException { - Map siteMap = new HashMap(); + Map siteMap = new HashMap<>(); String[] incomingFiles = args[2].split(","); - String xmlFileName = ""; - String dataFileName = null; - if (incomingFiles.length == 1) { - xmlFileName = incomingFiles[0]; - } else { - dataFileName = incomingFiles[0]; - xmlFileName = incomingFiles[1]; - } + String xmlPathString = (incomingFiles.length == 1) ? incomingFiles[0] + : incomingFiles[1]; + String dataPathString = (incomingFiles.length == 1) ? null + : incomingFiles[0]; + Path xmlFilePath = Paths.get(xmlPathString); + Path dataFilePath = (dataPathString != null) ? Paths + .get(dataPathString) : null; - final File incomingXMLFile = new File(xmlFileName); - DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); - DocumentBuilder db = dbf.newDocumentBuilder(); - Document doc = db.parse(incomingXMLFile); - doc.getDocumentElement().normalize(); + try { + if (!isSafePathToProcess(xmlFilePath)) { + statusHandler.warn(String.format(UNSAFE_PATH_MSG, xmlFilePath)); + return Collections.emptyMap(); + } - Collection siteList = getXMLDestinations(doc); - Set activeSites = IFPServer.getActiveSites(); + if ((dataFilePath != null) && (!isSafePathToProcess(dataFilePath))) { + statusHandler + .warn(String.format(UNSAFE_PATH_MSG, dataFilePath)); + return Collections.emptyMap(); + } - if (ISC_REQUEST.equals(doc.getDocumentElement().getNodeName())) { - /* - * This case is for processing an ISC Request/Reply message - * requesting our site's grids. - */ - for (String siteId : siteList) { - if (activeSites.contains(siteId)) { - if (IFPServerConfigManager.getServerConfig(siteId) - .requestISC()) { - String[] newArgs = new String[args.length]; - System.arraycopy(args, 0, newArgs, 0, args.length); - String newXmlFileName = xmlFileName + "." + siteId; - FileUtil.copyFile(incomingXMLFile, new File( - newXmlFileName)); - newArgs[2] = newXmlFileName; - siteMap.put(siteId, newArgs); + String xmlFileName = xmlFilePath.getFileName().toString(); + String dataFileName = (dataPathString != null) ? dataFilePath + .getFileName().toString() : null; + + Collection destinations = getXMLDestinations(xmlFilePath); + Set activeSites = IFPServer.getActiveSites(); + Set activeDestinations = new HashSet<>(activeSites); + activeDestinations.retainAll(destinations); + + for (String siteId : activeDestinations) { + if (IFPServerConfigManager.getServerConfig(siteId).requestISC()) { + String[] modifiedArgs = new String[args.length]; + System.arraycopy(args, 0, modifiedArgs, 0, args.length); + + if (dataFilePath != null) { + String newDataFileName = dataFileName + "." + siteId; + Path newDataFilePath = dataFilePath + .resolveSibling(newDataFileName); + + try { + Files.copy(dataFilePath, newDataFilePath, + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + statusHandler.error(String.format(COPY_ERROR_MSG, + dataFilePath, newDataFilePath, siteId), e); + continue; + } + + modifiedArgs[2] = modifiedArgs[2].replace( + dataPathString, newDataFilePath.toString()); } + + String newXmlFileName = xmlFileName + "." + siteId; + Path newXmlFilePath = xmlFilePath + .resolveSibling(newXmlFileName); + + try { + Files.copy(xmlFilePath, newXmlFilePath, + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + statusHandler.error(String.format(COPY_ERROR_MSG, + xmlFilePath, newXmlFilePath, siteId), e); + continue; + } + + modifiedArgs[2] = modifiedArgs[2].replace(xmlPathString, + newXmlFilePath.toString()); + + siteMap.put(siteId, modifiedArgs); } } - incomingXMLFile.delete(); - } else { - try { - for (String site : siteList) { - if (activeSites.contains(site) - && IFPServerConfigManager.getServerConfig(site) - .requestISC()) { - String[] modifiedArgs = new String[args.length]; - System.arraycopy(args, 0, modifiedArgs, 0, args.length); + } finally { + Collection filesToDelete = new HashSet<>(); - if (dataFileName != null) { - String newFileName = dataFileName + "." + site; - try { - FileUtil.copyFile(new File(dataFileName), - new File(newFileName)); - } catch (IOException e) { - statusHandler - .error("Failed to copy: [" - + dataFileName - + "] to " - + newFileName - + ". Unable to execute iscDataRec for " - + site, e); - continue; - } - - if (!new File(newFileName).exists()) { - statusHandler - .error("Failed to copy: [" - + dataFileName - + "] to " - + newFileName - + ". Unable to execute iscDataRec for " - + site); - continue; - } - modifiedArgs[2] = modifiedArgs[2].replace( - dataFileName, newFileName); - } - - String newXmlFileName = xmlFileName + "." + site; - try { - FileUtil.copyFile(new File(xmlFileName), new File( - newXmlFileName)); - } catch (IOException e) { - statusHandler.error("Failed to copy: [" - + xmlFileName + "] to " + newXmlFileName - + ". Unable to execute iscDataRec for " - + site, e); - continue; - } - if (!new File(newXmlFileName).exists()) { - statusHandler.error("Failed to copy: [" - + xmlFileName + "] to " + newXmlFileName - + ". Unable to execute iscDataRec for " - + site); - continue; - } - - modifiedArgs[2] = modifiedArgs[2].replace(xmlFileName, - newXmlFileName); - siteMap.put(site, modifiedArgs); - } - } - } finally { - if (dataFileName != null) { - File dataFile = new File(dataFileName); - if (dataFile.exists()) { - if (!dataFile.delete()) { - statusHandler.error("Unable to delete " - + dataFileName); - } - } + filesToDelete.add(xmlFilePath); + if (dataFilePath != null) { + filesToDelete.add(dataFilePath); + } + try (DirectoryStream stream = Files.newDirectoryStream( + xmlFilePath.getParent(), DOC_FILE_FILTER)) { + for (Path entry : stream) { + filesToDelete.add(entry); } + } catch (IOException e) { + statusHandler.error("Unable to list .doc files in directory " + + xmlFilePath.getParent(), e); + } - File xmlFile = incomingXMLFile; - if (xmlFile.exists()) { - if (!xmlFile.delete()) { - statusHandler.error("Unable to delete " + xmlFileName); - } - } - List docFiles = FileUtil.listFiles( - xmlFile.getParentFile(), docFileFilter, false); - for (File docFile : docFiles) { - docFile.delete(); + for (Path toDelete : filesToDelete) { + try { + Files.deleteIfExists(toDelete); + } catch (IOException e) { + statusHandler.error("Unable to delete file " + toDelete, e); } } } @@ -282,9 +265,29 @@ public class IscReceiveSrv { return siteMap; } - private Collection getXMLDestinations(final Document doc) + private boolean isSafePathToProcess(Path path) { + try { + Path realPath = path.toRealPath(); + for (Path safePath : WHITELISTED_PATHS) { + if (realPath.startsWith(safePath)) { + return true; + } + } + } catch (IOException e) { + statusHandler.error("Unable to resolve the real path for " + path, + e); + } + + return false; + } + + private Collection getXMLDestinations(final Path xmlDocumentPath) throws SAXException, IOException, ParserConfigurationException { - Collection destinations = new HashSet<>(); + DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); + dbf.setExpandEntityReferences(false); + DocumentBuilder db = dbf.newDocumentBuilder(); + Document doc = db.parse(xmlDocumentPath.toFile()); + doc.getDocumentElement().normalize(); // Expected XML format: // @@ -295,6 +298,7 @@ public class IscReceiveSrv { // // // + Collection destinations = new HashSet<>(); NodeList destNodes = doc.getElementsByTagName("destinations"); if (destNodes.getLength() > 0) { Node destNode = destNodes.item(0);