Merge remote-tracking branch 'upstream/develop' into 3784_BitlockerDetection

This commit is contained in:
U-BASIS\dgrove 2018-05-10 10:49:28 -04:00
commit 702f46d911
20 changed files with 425 additions and 146 deletions

View File

@ -72,10 +72,12 @@ public class ImageDSProcessor implements DataSourceProcessor, AutoIngestDataSour
filtersList.add(allFilter); filtersList.add(allFilter);
filtersList.add(rawFilter); filtersList.add(rawFilter);
filtersList.add(encaseFilter); filtersList.add(encaseFilter);
filtersList.add(virtualMachineFilter);
allExt.addAll(GeneralFilter.RAW_IMAGE_EXTS); allExt.addAll(GeneralFilter.RAW_IMAGE_EXTS);
allExt.addAll(GeneralFilter.ENCASE_IMAGE_EXTS); allExt.addAll(GeneralFilter.ENCASE_IMAGE_EXTS);
allExt.addAll(GeneralFilter.VIRTUAL_MACHINE_EXTS); if(!System.getProperty("os.name").toLowerCase().contains("mac")){
filtersList.add(virtualMachineFilter);
allExt.addAll(GeneralFilter.VIRTUAL_MACHINE_EXTS);
}
} }
/** /**

View File

@ -0,0 +1,70 @@
/*
* Autopsy
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.ingest;
import java.util.HashMap;
import java.util.Map;
/**
* Generic Ingest Job settings class.
* Primary use of this class is for Python modules because classes created in Python
* cannot be serialized / deserialized in Java.
*/
public class GenericIngestModuleJobSettings implements IngestModuleIngestJobSettings {
private static final long serialVersionUID = 1L;
@Override
public long getVersionNumber(){
return serialVersionUID;
}
private final Map<String, String> settings;
public GenericIngestModuleJobSettings(){
this.settings = new HashMap<>();
}
/**
* Return the string value for passed key parameter.
*
* @param key The key to lookup
* @return The value or null if the key was not found.
*/
public String getSetting(String key){
return settings.get(key);
}
/**
* Adds the passed key value pair
*
* @param key The key to be added to the settings
* @param value The value to be added for the key
*/
public void setSetting(String key, String value){
settings.put(key, value);
}
/**
* Removes the key from the settings.
* @param key The key to be removed
*/
public void removeSetting(String key){
settings.remove(key);
}
}

View File

@ -36,7 +36,6 @@ import java.util.logging.Level;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.util.io.NbObjectInputStream; import org.openide.util.io.NbObjectInputStream;
import org.openide.util.io.NbObjectOutputStream; import org.openide.util.io.NbObjectOutputStream;
import org.python.util.PythonObjectInputStream;
import org.sleuthkit.autopsy.coreutils.Logger; import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.coreutils.ModuleSettings; import org.sleuthkit.autopsy.coreutils.ModuleSettings;
import org.sleuthkit.autopsy.coreutils.PlatformUtil; import org.sleuthkit.autopsy.coreutils.PlatformUtil;
@ -492,25 +491,12 @@ public final class IngestJobSettings {
String moduleSettingsFilePath = getModuleSettingsFilePath(factory); String moduleSettingsFilePath = getModuleSettingsFilePath(factory);
File settingsFile = new File(moduleSettingsFilePath); File settingsFile = new File(moduleSettingsFilePath);
if (settingsFile.exists()) { if (settingsFile.exists()) {
if (!isPythonModuleSettingsFile(moduleSettingsFilePath)) { try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) {
try (NbObjectInputStream in = new NbObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) { settings = (IngestModuleIngestJobSettings) in.readObject();
settings = (IngestModuleIngestJobSettings) in.readObject(); } catch (IOException | ClassNotFoundException ex) {
} catch (IOException | ClassNotFoundException ex) { String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS
String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS logger.log(Level.WARNING, warning, ex);
logger.log(Level.WARNING, warning, ex); this.warnings.add(warning);
this.warnings.add(warning);
}
} else {
// @@@ BC Jython serialization is currently broken and this
// throws an exception. (-2323). Commenting out so that
// Python modules will at least load with default settings.
// try (PythonObjectInputStream in = new PythonObjectInputStream(new FileInputStream(settingsFile.getAbsolutePath()))) {
// settings = (IngestModuleIngestJobSettings) in.readObject();
// } catch (IOException | ClassNotFoundException exception) {
// String warning = NbBundle.getMessage(IngestJobSettings.class, "IngestJobSettings.moduleSettingsLoad.warning", factory.getModuleDisplayName(), this.executionContext); //NON-NLS
// logger.log(Level.WARNING, warning, exception);
// this.warnings.add(warning);
// }
} }
} }
if (settings == null) { if (settings == null) {

View File

@ -37,6 +37,7 @@ import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser; import org.apache.tika.parser.AutoDetectParser;
import org.apache.tika.parser.ParseContext; import org.apache.tika.parser.ParseContext;
import org.apache.tika.sax.BodyContentHandler; import org.apache.tika.sax.BodyContentHandler;
import org.openide.util.NbBundle.Messages;
import org.sleuthkit.autopsy.casemodule.Case; import org.sleuthkit.autopsy.casemodule.Case;
import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException; import org.sleuthkit.autopsy.casemodule.NoCurrentCaseException;
import org.sleuthkit.autopsy.casemodule.services.Blackboard; import org.sleuthkit.autopsy.casemodule.services.Blackboard;
@ -50,21 +51,20 @@ import org.sleuthkit.autopsy.ingest.ModuleDataEvent;
import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector; import org.sleuthkit.autopsy.modules.filetypeid.FileTypeDetector;
import org.sleuthkit.datamodel.AbstractFile; import org.sleuthkit.datamodel.AbstractFile;
import org.sleuthkit.datamodel.BlackboardArtifact; import org.sleuthkit.datamodel.BlackboardArtifact;
import org.sleuthkit.datamodel.BlackboardAttribute;
import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException; import org.sleuthkit.datamodel.ReadContentInputStream.ReadContentInputStreamException;
import org.sleuthkit.datamodel.TskCoreException; import org.sleuthkit.datamodel.TskCoreException;
import org.sleuthkit.datamodel.TskData; import org.sleuthkit.datamodel.TskData;
import org.xml.sax.ContentHandler; import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException; import org.xml.sax.SAXException;
/** /**
* File ingest module to detect encryption and password protection. * File ingest module to detect encryption and password protection.
*/ */
final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter { final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter {
private static final int FILE_SIZE_MODULUS = 512; private static final int FILE_SIZE_MODULUS = 512;
private static final String MIME_TYPE_OOXML_PROTECTED = "application/x-ooxml-protected"; private static final String MIME_TYPE_OOXML_PROTECTED = "application/x-ooxml-protected";
private static final String MIME_TYPE_MSWORD = "application/msword"; private static final String MIME_TYPE_MSWORD = "application/msword";
private static final String MIME_TYPE_MSEXCEL = "application/vnd.ms-excel"; private static final String MIME_TYPE_MSEXCEL = "application/vnd.ms-excel";
@ -110,6 +110,10 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
} }
} }
@Messages({
"EncryptionDetectionFileIngestModule.artifactComment.password=Password protection detected.",
"EncryptionDetectionFileIngestModule.artifactComment.suspected=Suspected encryption due to high entropy (%f)."
})
@Override @Override
public IngestModule.ProcessResult process(AbstractFile file) { public IngestModule.ProcessResult process(AbstractFile file) {
@ -132,11 +136,13 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
String mimeType = fileTypeDetector.getMIMEType(file); String mimeType = fileTypeDetector.getMIMEType(file);
if (mimeType.equals("application/octet-stream")) { if (mimeType.equals("application/octet-stream")) {
if (isFileEncryptionSuspected(file)) { if (isFileEncryptionSuspected(file)) {
return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED); return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_SUSPECTED,
String.format(Bundle.EncryptionDetectionFileIngestModule_artifactComment_suspected(), calculatedEntropy));
} }
} else { } else {
if (isFilePasswordProtected(file)) { if (isFilePasswordProtected(file)) {
return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED); return flagFile(file, BlackboardArtifact.ARTIFACT_TYPE.TSK_ENCRYPTION_DETECTED,
Bundle.EncryptionDetectionFileIngestModule_artifactComment_password());
} }
} }
} }
@ -168,14 +174,18 @@ final class EncryptionDetectionFileIngestModule extends FileIngestModuleAdapter
* *
* @param file The file to be processed. * @param file The file to be processed.
* @param artifactType The type of artifact to create. * @param artifactType The type of artifact to create.
* @param comment A comment to be attached to the artifact.
* *
* @return 'OK' if the file was processed successfully, or 'ERROR' if there * @return 'OK' if the file was processed successfully, or 'ERROR' if there
* was a problem. * was a problem.
*/ */
private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType) { private IngestModule.ProcessResult flagFile(AbstractFile file, BlackboardArtifact.ARTIFACT_TYPE artifactType, String comment) {
try { try {
BlackboardArtifact artifact = file.newArtifact(artifactType); BlackboardArtifact artifact = file.newArtifact(artifactType);
artifact.addAttribute(new BlackboardAttribute(BlackboardAttribute.ATTRIBUTE_TYPE.TSK_COMMENT,
EncryptionDetectionModuleFactory.getModuleName(), comment));
try { try {
/* /*
* Index the artifact for keyword search. * Index the artifact for keyword search.

View File

@ -927,7 +927,7 @@ public class TimeLineController {
* @param o The object to un-register. * @param o The object to un-register.
*/ */
synchronized public void unRegisterForEvents(Object o) { synchronized public void unRegisterForEvents(Object o) {
eventbus.unregister(0); eventbus.unregister(o);
} }
static synchronized public void setTimeZone(TimeZone timeZone) { static synchronized public void setTimeZone(TimeZone timeZone) {

View File

@ -509,7 +509,7 @@ public final class FilteredEventsModel {
* @param o The object to un-register. * @param o The object to un-register.
*/ */
synchronized public void unRegisterForEvents(Object o) { synchronized public void unRegisterForEvents(Object o) {
eventbus.unregister(0); eventbus.unregister(o);
} }
/** /**

View File

@ -52,6 +52,14 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer {
autoIngestMonitor.addObserver(this); autoIngestMonitor.addObserver(this);
} }
AutoIngestMonitor getMonitor() {
return autoIngestMonitor;
}
AinStatusPanel getNodesStatusPanel() {
return nodesPanel;
}
/** /**
* This method is called from within the constructor to initialize the form. * This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always * WARNING: Do NOT modify this code. The content of this method is always
@ -143,9 +151,10 @@ final class AinStatusDashboard extends javax.swing.JPanel implements Observer {
@Override @Override
public void update(Observable o, Object arg) { public void update(Observable o, Object arg) {
if (arg instanceof AutoIngestNodeState) if (arg instanceof AutoIngestNodeState) {
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
refreshTables(); refreshTables();
}); });
}
} }
} }

View File

@ -18,6 +18,7 @@
*/ */
package org.sleuthkit.autopsy.experimental.autoingest; package org.sleuthkit.autopsy.experimental.autoingest;
import java.awt.Component;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -74,8 +75,6 @@ final class AinStatusDashboardTopComponent extends TopComponent {
tc.add(nodeTab); tc.add(nodeTab);
tc.open(); tc.open();
} }
tc.toFront();
tc.requestActive();
} }
} }
@ -115,6 +114,19 @@ final class AinStatusDashboardTopComponent extends TopComponent {
WindowManager.getDefault().setTopComponentFloating(this, true); WindowManager.getDefault().setTopComponentFloating(this, true);
} }
/**
* Get the current AutoIngestDashboard if there is one.
*
* @return the current AutoIngestDashboard or null if there is not one
*/
AinStatusDashboard getAinStatusDashboard() {
for (Component comp : getComponents()) {
if (comp instanceof AinStatusDashboard) {
return (AinStatusDashboard) comp;
}
}
return null;
}
/** /**
* This method is called from within the constructor to initialize the form. * This method is called from within the constructor to initialize the form.
* WARNING: Do NOT modify this code. The content of this method is always * WARNING: Do NOT modify this code. The content of this method is always

View File

@ -97,6 +97,7 @@ final class AinStatusNode extends AbstractNode {
@Messages({"AinStatusNode.hostName.title=Host Name", @Messages({"AinStatusNode.hostName.title=Host Name",
"AinStatusNode.status.title=Status", "AinStatusNode.status.title=Status",
"AinStatusNode.status.running=Running", "AinStatusNode.status.running=Running",
"AinStatusNode.status.pauseRequested=Pause Requested",
"AinStatusNode.status.pausedByUser=Paused By User", "AinStatusNode.status.pausedByUser=Paused By User",
"AinStatusNode.status.pausedForError=Paused Due to System Error", "AinStatusNode.status.pausedForError=Paused Due to System Error",
"AinStatusNode.status.startingup=Starting Up", "AinStatusNode.status.startingup=Starting Up",
@ -129,6 +130,9 @@ final class AinStatusNode extends AbstractNode {
case PAUSED_DUE_TO_SYSTEM_ERROR: case PAUSED_DUE_TO_SYSTEM_ERROR:
status = Bundle.AinStatusNode_status_pausedForError(); status = Bundle.AinStatusNode_status_pausedForError();
break; break;
case PAUSE_REQUESTED:
status = Bundle.AinStatusNode_status_pauseRequested();
break;
default: default:
break; break;
} }
@ -142,12 +146,11 @@ final class AinStatusNode extends AbstractNode {
List<Action> actions = new ArrayList<>(); List<Action> actions = new ArrayList<>();
if (AutoIngestDashboard.isAdminAutoIngestDashboard()) { if (AutoIngestDashboard.isAdminAutoIngestDashboard()) {
if (nodeState.getState() == AutoIngestNodeState.State.PAUSED_BY_REQUEST if (nodeState.getState() == AutoIngestNodeState.State.PAUSED_BY_REQUEST
|| nodeState.getState() == AutoIngestNodeState.State.PAUSED_DUE_TO_SYSTEM_ERROR) { || nodeState.getState() == AutoIngestNodeState.State.PAUSED_DUE_TO_SYSTEM_ERROR
actions.add(new AutoIngestAdminActions.ResumeAction()); || nodeState.getState() == AutoIngestNodeState.State.RUNNING) {
} else if (nodeState.getState() == AutoIngestNodeState.State.RUNNING){ actions.add(new AutoIngestAdminActions.AutoIngestNodeControlAction.PauseResumeAction(nodeState));
actions.add(new AutoIngestAdminActions.PauseAction());
} }
actions.add(new AutoIngestAdminActions.ShutdownAction()); actions.add(new AutoIngestAdminActions.AutoIngestNodeControlAction.ShutdownAction(nodeState));
} }
return actions.toArray(new Action[actions.size()]); return actions.toArray(new Action[actions.size()]);
} }

View File

@ -99,12 +99,12 @@ final class AinStatusPanel extends javax.swing.JPanel implements ExplorerManager
void refresh(AutoIngestMonitor monitor) { void refresh(AutoIngestMonitor monitor) {
outline.setRowSelectionAllowed(false); outline.setRowSelectionAllowed(false);
Node[] selectedNodes = explorerManager.getSelectedNodes(); Node[] selectedNodes = explorerManager.getSelectedNodes();
AinStatusNode autoIngestNode = new AinStatusNode(monitor); AinStatusNode ainStatusNode = new AinStatusNode(monitor);
explorerManager.setRootContext(autoIngestNode); explorerManager.setRootContext(ainStatusNode);
outline.setRowSelectionAllowed(true); outline.setRowSelectionAllowed(true);
if (selectedNodes.length > 0 && autoIngestNode.getChildren().findChild(selectedNodes[0].getName()) != null && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored if (selectedNodes.length > 0 && ainStatusNode.getChildren().findChild(selectedNodes[0].getName()) != null && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored
try { try {
explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); explorerManager.setSelectedNodes(new Node[]{ainStatusNode.getChildren().findChild(selectedNodes[0].getName())});
} catch (PropertyVetoException ignore) { } catch (PropertyVetoException ignore) {
//Unable to select previously selected node //Unable to select previously selected node
} }

View File

@ -18,14 +18,122 @@
*/ */
package org.sleuthkit.autopsy.experimental.autoingest; package org.sleuthkit.autopsy.experimental.autoingest;
import java.awt.Cursor;
import java.awt.EventQueue;
import java.awt.event.ActionEvent; import java.awt.event.ActionEvent;
import java.util.logging.Level;
import javax.swing.AbstractAction; import javax.swing.AbstractAction;
import javax.swing.JOptionPane;
import org.openide.util.NbBundle; import org.openide.util.NbBundle;
import org.openide.windows.WindowManager; import org.openide.windows.WindowManager;
import org.sleuthkit.autopsy.coreutils.Logger;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestMonitor.AutoIngestNodeState;
import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog; import org.sleuthkit.autopsy.ingest.IngestProgressSnapshotDialog;
final class AutoIngestAdminActions { final class AutoIngestAdminActions {
static abstract class AutoIngestNodeControlAction extends AbstractAction {
private final AutoIngestNodeState nodeState;
private final Logger logger = Logger.getLogger(AutoIngestNodeControlAction.class.getName());
AutoIngestNodeControlAction(AutoIngestNodeState nodeState, String title) {
super(title);
this.nodeState = nodeState;
}
@Override
public void actionPerformed(ActionEvent e) {
if (nodeState != null) {
final AinStatusDashboardTopComponent tc = (AinStatusDashboardTopComponent) WindowManager.getDefault().findTopComponent(AinStatusDashboardTopComponent.PREFERRED_ID);
if (tc != null) {
AinStatusDashboard dashboard = tc.getAinStatusDashboard();
if (dashboard != null) {
dashboard.getNodesStatusPanel().setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
EventQueue.invokeLater(() -> {
try {
controlAutoIngestNode(dashboard);
} catch (AutoIngestMonitor.AutoIngestMonitorException ex) {
logger.log(Level.WARNING, "Error sending control event to node", ex);
} finally {
dashboard.getNodesStatusPanel().setCursor(Cursor.getDefaultCursor());
}
});
}
}
}
}
protected abstract void controlAutoIngestNode(AinStatusDashboard dashboard) throws AutoIngestMonitor.AutoIngestMonitorException;
AutoIngestNodeState getNodeState() {
return nodeState;
}
@NbBundle.Messages({"AutoIngestAdminActions.pause.title=Pause Node",
"AutoIngestAdminActions.resume.title=Resume Node"})
static final class PauseResumeAction extends AutoIngestNodeControlAction {
private static final long serialVersionUID = 1L;
PauseResumeAction(AutoIngestNodeState nodeState) {
super(nodeState, nodeState.getState() == AutoIngestNodeState.State.RUNNING
? Bundle.AutoIngestAdminActions_pause_title() : Bundle.AutoIngestAdminActions_resume_title());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
@Override
protected void controlAutoIngestNode(AinStatusDashboard dashboard) throws AutoIngestMonitor.AutoIngestMonitorException {
if (getNodeState().getState() == AutoIngestNodeState.State.RUNNING) {
dashboard.getMonitor().pauseAutoIngestNode(getNodeState().getName());
} else {
dashboard.getMonitor().resumeAutoIngestNode(getNodeState().getName());
}
}
}
@NbBundle.Messages({"AutoIngestAdminActions.shutdown.title=Shutdown Node",
"AutoIngestAdminActions.shutdown.OK=OK", "AutoIngestAdminActions.shutdown.Cancel=Cancel",
"AutoIngestAdminActions.shutdown.consequences=This will cancel any currently running job on this host. Exiting while a job is running potentially leaves the case in an inconsistent or corrupted state."})
static final class ShutdownAction extends AutoIngestNodeControlAction {
private static final long serialVersionUID = 1L;
ShutdownAction(AutoIngestNodeState nodeState) {
super(nodeState, Bundle.AutoIngestAdminActions_shutdown_title());
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
@Override
protected void controlAutoIngestNode(AinStatusDashboard dashboard) throws AutoIngestMonitor.AutoIngestMonitorException {
Object[] options = {Bundle.AutoIngestAdminActions_shutdown_OK(),
Bundle.AutoIngestAdminActions_shutdown_Cancel()
};
int reply = JOptionPane.showOptionDialog(dashboard.getNodesStatusPanel(),
Bundle.AutoIngestAdminActions_shutdown_consequences(),
NbBundle.getMessage(AutoIngestControlPanel.class, "ConfirmationDialog.ConfirmExitHeader"),
JOptionPane.DEFAULT_OPTION,
JOptionPane.WARNING_MESSAGE,
null,
options,
options[JOptionPane.NO_OPTION]);
if (reply == JOptionPane.OK_OPTION) {
dashboard.getMonitor().shutdownAutoIngestNode(getNodeState().getName());
}
}
}
}
@NbBundle.Messages({"AutoIngestAdminActions.progressDialogAction.title=Ingest Progress"}) @NbBundle.Messages({"AutoIngestAdminActions.progressDialogAction.title=Ingest Progress"})
static final class ProgressDialogAction extends AbstractAction { static final class ProgressDialogAction extends AbstractAction {
@ -152,65 +260,4 @@ final class AutoIngestAdminActions {
return super.clone(); //To change body of generated methods, choose Tools | Templates. return super.clone(); //To change body of generated methods, choose Tools | Templates.
} }
} }
@NbBundle.Messages({"AutoIngestAdminActions.pause.title=Pause Node"})
static final class PauseAction extends AbstractAction {
private static final long serialVersionUID = 1L;
PauseAction() {
super(Bundle.AutoIngestAdminActions_pause_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.resume.title=Resume Node"})
static final class ResumeAction extends AbstractAction {
private static final long serialVersionUID = 1L;
ResumeAction() {
super(Bundle.AutoIngestAdminActions_resume_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
@NbBundle.Messages({"AutoIngestAdminActions.shutdown.title=Shutdown Node"})
static final class ShutdownAction extends AbstractAction {
private static final long serialVersionUID = 1L;
ShutdownAction() {
super(Bundle.AutoIngestAdminActions_shutdown_title());
}
@Override
public void actionPerformed(ActionEvent e) {
//TODO JIRA-
}
@Override
public Object clone() throws CloneNotSupportedException {
return super.clone(); //To change body of generated methods, choose Tools | Templates.
}
}
} }

View File

@ -852,7 +852,7 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
case CASE_DELETED: case CASE_DELETED:
updateExecutor.submit(new UpdateAllJobsTablesTask()); updateExecutor.submit(new UpdateAllJobsTablesTask());
break; break;
case PAUSED_BY_REQUEST: case PAUSED_BY_USER_REQUEST:
EventQueue.invokeLater(() -> { EventQueue.invokeLater(() -> {
tbStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.bnPause.paused")); tbStatusMessage.setText(org.openide.util.NbBundle.getMessage(AutoIngestControlPanel.class, "AutoIngestControlPanel.bnPause.paused"));
bnOptions.setEnabled(true); bnOptions.setEnabled(true);
@ -881,6 +881,9 @@ public final class AutoIngestControlPanel extends JPanel implements Observer {
case JOB_STATUS_UPDATED: case JOB_STATUS_UPDATED:
updateExecutor.submit(new UpdateRunningJobsTablesTask()); updateExecutor.submit(new UpdateRunningJobsTablesTask());
break; break;
case SHUTTING_DOWN:
LifecycleManager.getDefault().exit();
break;
default: default:
break; break;
} }

View File

@ -170,7 +170,7 @@ final class AutoIngestJobsPanel extends javax.swing.JPanel implements ExplorerMa
AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status); AutoIngestJobsNode autoIngestNode = new AutoIngestJobsNode(jobsSnapshot, status);
explorerManager.setRootContext(autoIngestNode); explorerManager.setRootContext(autoIngestNode);
outline.setRowSelectionAllowed(true); outline.setRowSelectionAllowed(true);
if (selectedNodes.length > 0 && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored if (selectedNodes.length > 0 && autoIngestNode.getChildren().findChild(selectedNodes[0].getName()) != null && outline.isFocusable()) { //don't allow saved selections of empty nodes to be restored
try { try {
explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())}); explorerManager.setSelectedNodes(new Node[]{autoIngestNode.getChildren().findChild(selectedNodes[0].getName())});
} catch (PropertyVetoException ignore) { } catch (PropertyVetoException ignore) {

View File

@ -94,6 +94,7 @@ import org.sleuthkit.autopsy.experimental.configuration.SharedConfiguration.Shar
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor;
import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException; import org.sleuthkit.autopsy.datasourceprocessors.AutoIngestDataSourceProcessor.AutoIngestDataSourceProcessorException;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.AutoIngestJobException; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.AutoIngestJobException;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType;
import org.sleuthkit.autopsy.ingest.IngestJob; import org.sleuthkit.autopsy.ingest.IngestJob;
import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason; import org.sleuthkit.autopsy.ingest.IngestJob.CancellationReason;
import org.sleuthkit.autopsy.ingest.IngestJobSettings; import org.sleuthkit.autopsy.ingest.IngestJobSettings;
@ -134,7 +135,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
Event.JOB_COMPLETED.toString(), Event.JOB_COMPLETED.toString(),
Event.CASE_PRIORITIZED.toString(), Event.CASE_PRIORITIZED.toString(),
Event.JOB_STARTED.toString(), Event.JOB_STARTED.toString(),
Event.REPORT_STATE.toString()})); Event.REPORT_STATE.toString(),
ControlEventType.PAUSE.toString(),
ControlEventType.RESUME.toString(),
ControlEventType.SHUTDOWN.toString()}));
private static final long JOB_STATUS_EVENT_INTERVAL_SECONDS = 10; private static final long JOB_STATUS_EVENT_INTERVAL_SECONDS = 10;
private static final String JOB_STATUS_PUBLISHING_THREAD_NAME = "AIM-job-status-event-publisher-%d"; private static final String JOB_STATUS_PUBLISHING_THREAD_NAME = "AIM-job-status-event-publisher-%d";
private static final long MAX_MISSED_JOB_STATUS_UPDATES = 10; private static final long MAX_MISSED_JOB_STATUS_UPDATES = 10;
@ -282,6 +286,8 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
handleRemoteCaseDeletedEvent((AutoIngestCaseDeletedEvent) event); handleRemoteCaseDeletedEvent((AutoIngestCaseDeletedEvent) event);
} else if (event instanceof AutoIngestRequestNodeStateEvent) { } else if (event instanceof AutoIngestRequestNodeStateEvent) {
handleRemoteRequestNodeStateEvent(); handleRemoteRequestNodeStateEvent();
} else if (event instanceof AutoIngestNodeControlEvent) {
handleRemoteNodeControlEvent((AutoIngestNodeControlEvent) event);
} }
} }
} }
@ -405,6 +411,28 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
eventPublisher.publishRemotely(lastPublishedStateEvent); eventPublisher.publishRemotely(lastPublishedStateEvent);
} }
private void handleRemoteNodeControlEvent(AutoIngestNodeControlEvent event) {
if (event.getNodeName().compareToIgnoreCase(LOCAL_HOST_NAME) == 0) {
switch (event.getControlEventType()) {
case PAUSE:
pause();
break;
case RESUME:
resume();
break;
case SHUTDOWN:
shutDown();
// Notify the front end (if any) to shutdown.
setChanged();
notifyObservers(Event.SHUTTING_DOWN);
break;
default:
SYS_LOGGER.log(Level.WARNING, "Received unsupported control event: {0}", event.getControlEventType());
break;
}
}
}
/** /**
* Shuts down auto ingest. * Shuts down auto ingest.
*/ */
@ -419,6 +447,7 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
eventPublisher.removeSubscriber(EVENT_LIST, instance); eventPublisher.removeSubscriber(EVENT_LIST, instance);
stopInputFolderScans(); stopInputFolderScans();
stopJobProcessing(); stopJobProcessing();
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.SHUTDOWN, AutoIngestManager.LOCAL_HOST_NAME));
eventPublisher.closeRemoteEventChannel(); eventPublisher.closeRemoteEventChannel();
cleanupJobs(); cleanupJobs();
@ -1696,14 +1725,13 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
* object. * object.
*/ */
setChanged(); setChanged();
notifyObservers(Event.PAUSED_BY_REQUEST); notifyObservers(Event.PAUSED_BY_USER_REQUEST);
/**
* Publish an event to let remote listeners know that the
* node has been paused.
*/
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_REQUEST, AutoIngestManager.LOCAL_HOST_NAME));
} }
/**
* Publish an event to let remote listeners know that a pause
* has been requested.
*/
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSE_REQUESTED, AutoIngestManager.LOCAL_HOST_NAME));
} }
} }
@ -1746,18 +1774,22 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
* if auto ingest is shutting down. * if auto ingest is shutting down.
*/ */
private void pauseIfRequested() throws InterruptedException { private void pauseIfRequested() throws InterruptedException {
if (State.SHUTTING_DOWN == state) {
return;
}
synchronized (pauseLock) { synchronized (pauseLock) {
if (pauseRequested) { if (pauseRequested) {
sysLogger.log(Level.INFO, "Job processing paused by request"); sysLogger.log(Level.INFO, "Job processing paused by request");
pauseRequested = false; pauseRequested = false;
setChanged(); setChanged();
notifyObservers(Event.PAUSED_BY_REQUEST); notifyObservers(Event.PAUSED_BY_USER_REQUEST);
/** /**
* Publish an event to let remote listeners know that the * Publish an event to let remote listeners know that the
* node has been paused. * node has been paused.
*/ */
eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_REQUEST, AutoIngestManager.LOCAL_HOST_NAME)); eventPublisher.publishRemotely(lastPublishedStateEvent = new AutoIngestNodeStateEvent(Event.PAUSED_BY_USER_REQUEST, AutoIngestManager.LOCAL_HOST_NAME));
pauseLock.wait(); pauseLock.wait();
sysLogger.log(Level.INFO, "Job processing resumed after pause request"); sysLogger.log(Level.INFO, "Job processing resumed after pause request");
@ -1781,6 +1813,10 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
* if auto ingest is shutting down. * if auto ingest is shutting down.
*/ */
private void pauseForSystemError() throws InterruptedException { private void pauseForSystemError() throws InterruptedException {
if (State.SHUTTING_DOWN == state) {
return;
}
synchronized (pauseLock) { synchronized (pauseLock) {
sysLogger.log(Level.SEVERE, "Job processing paused for system error"); sysLogger.log(Level.SEVERE, "Job processing paused for system error");
setChanged(); setChanged();
@ -3062,12 +3098,14 @@ final class AutoIngestManager extends Observable implements PropertyChangeListen
JOB_COMPLETED, JOB_COMPLETED,
CASE_PRIORITIZED, CASE_PRIORITIZED,
CASE_DELETED, CASE_DELETED,
PAUSED_BY_REQUEST, PAUSE_REQUESTED,
PAUSED_BY_USER_REQUEST,
PAUSED_FOR_SYSTEM_ERROR, PAUSED_FOR_SYSTEM_ERROR,
RESUMED, RESUMED,
STARTING_UP, STARTING_UP,
RUNNING, RUNNING,
SHUTTING_DOWN, SHUTTING_DOWN,
SHUTDOWN,
REPORT_STATE REPORT_STATE
} }

View File

@ -42,6 +42,7 @@ import org.sleuthkit.autopsy.events.AutopsyEventException;
import org.sleuthkit.autopsy.events.AutopsyEventPublisher; import org.sleuthkit.autopsy.events.AutopsyEventPublisher;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestJob.ProcessingStatus;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event; import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestManager.Event;
import org.sleuthkit.autopsy.experimental.autoingest.AutoIngestNodeControlEvent.ControlEventType;
/** /**
* An auto ingest monitor responsible for monitoring and reporting the * An auto ingest monitor responsible for monitoring and reporting the
* processing of auto ingest jobs. * processing of auto ingest jobs.
@ -61,10 +62,12 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
AutoIngestManager.Event.CASE_PRIORITIZED.toString(), AutoIngestManager.Event.CASE_PRIORITIZED.toString(),
AutoIngestManager.Event.JOB_STARTED.toString(), AutoIngestManager.Event.JOB_STARTED.toString(),
AutoIngestManager.Event.RUNNING.toString(), AutoIngestManager.Event.RUNNING.toString(),
AutoIngestManager.Event.PAUSED_BY_REQUEST.toString(), AutoIngestManager.Event.PAUSE_REQUESTED.toString(),
AutoIngestManager.Event.PAUSED_BY_USER_REQUEST.toString(),
AutoIngestManager.Event.PAUSED_FOR_SYSTEM_ERROR.toString(), AutoIngestManager.Event.PAUSED_FOR_SYSTEM_ERROR.toString(),
AutoIngestManager.Event.STARTING_UP.toString(), AutoIngestManager.Event.STARTING_UP.toString(),
AutoIngestManager.Event.SHUTTING_DOWN.toString(), AutoIngestManager.Event.SHUTTING_DOWN.toString(),
AutoIngestManager.Event.SHUTDOWN.toString(),
AutoIngestManager.Event.RESUMED.toString()})); AutoIngestManager.Event.RESUMED.toString()}));
private final AutopsyEventPublisher eventPublisher; private final AutopsyEventPublisher eventPublisher;
private CoordinationService coordinationService; private CoordinationService coordinationService;
@ -221,9 +224,10 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
* @param event A node state change event. * @param event A node state change event.
*/ */
private void handleAutoIngestNodeStateEvent(AutoIngestNodeStateEvent event) { private void handleAutoIngestNodeStateEvent(AutoIngestNodeStateEvent event) {
if (event.getEventType() == AutoIngestManager.Event.SHUTTING_DOWN) { AutoIngestNodeState oldNodeState = null;
if (event.getEventType() == AutoIngestManager.Event.SHUTDOWN) {
// Remove node from collection. // Remove node from collection.
nodeStates.remove(event.getNodeName()); oldNodeState = nodeStates.remove(event.getNodeName());
} else { } else {
// Otherwise either create an entry for the given node name or update // Otherwise either create an entry for the given node name or update
// an existing entry in the map. // an existing entry in the map.
@ -231,7 +235,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
} }
setChanged(); setChanged();
// Trigger a dashboard refresh. // Trigger a dashboard refresh.
notifyObservers(nodeStates.get(event.getNodeName())); notifyObservers(oldNodeState == null ? nodeStates.get(event.getNodeName()) : oldNodeState);
} }
/** /**
@ -522,6 +526,45 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
} }
} }
/**
* Send the given control event to the given node.
*
* @param eventType The type of control event to send.
* @param nodeName The name of the node to send it to.
*/
private void sendControlEventToNode(ControlEventType eventType, String nodeName) {
new Thread(() -> {
eventPublisher.publishRemotely(new AutoIngestNodeControlEvent(eventType, nodeName));
}).start();
}
/**
* Tell the specified node to pause.
*
* @param nodeName
*/
void pauseAutoIngestNode(String nodeName) {
sendControlEventToNode(ControlEventType.PAUSE, nodeName);
}
/**
* Tell the specified node to resume.
*
* @param nodeName
*/
void resumeAutoIngestNode(String nodeName) {
sendControlEventToNode(ControlEventType.RESUME, nodeName);
}
/**
* Tell the specified node to shutdown.
*
* @param nodeName
*/
void shutdownAutoIngestNode(String nodeName) {
sendControlEventToNode(ControlEventType.SHUTDOWN, nodeName);
}
/** /**
* A task that queries the coordination service for auto ingest manifest * A task that queries the coordination service for auto ingest manifest
* node data and converts it to auto ingest jobs for publication top its * node data and converts it to auto ingest jobs for publication top its
@ -677,6 +720,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
STARTING_UP, STARTING_UP,
SHUTTING_DOWN, SHUTTING_DOWN,
RUNNING, RUNNING,
PAUSE_REQUESTED,
PAUSED_BY_REQUEST, PAUSED_BY_REQUEST,
PAUSED_DUE_TO_SYSTEM_ERROR, PAUSED_DUE_TO_SYSTEM_ERROR,
UNKNOWN UNKNOWN
@ -697,7 +741,7 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
case RUNNING: case RUNNING:
nodeState = State.RUNNING; nodeState = State.RUNNING;
break; break;
case PAUSED_BY_REQUEST: case PAUSED_BY_USER_REQUEST:
nodeState = State.PAUSED_BY_REQUEST; nodeState = State.PAUSED_BY_REQUEST;
break; break;
case PAUSED_FOR_SYSTEM_ERROR: case PAUSED_FOR_SYSTEM_ERROR:
@ -706,6 +750,9 @@ final class AutoIngestMonitor extends Observable implements PropertyChangeListen
case RESUMED: case RESUMED:
nodeState = State.RUNNING; nodeState = State.RUNNING;
break; break;
case PAUSE_REQUESTED:
nodeState = State.PAUSE_REQUESTED;
break;
default: default:
nodeState = State.UNKNOWN; nodeState = State.UNKNOWN;
break; break;

View File

@ -0,0 +1,55 @@
/*
* Autopsy Forensic Browser
*
* Copyright 2018 Basis Technology Corp.
* Contact: carrier <at> sleuthkit <dot> org
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.sleuthkit.autopsy.experimental.autoingest;
import java.io.Serializable;
import org.sleuthkit.autopsy.events.AutopsyEvent;
/**
* Event published to pause, resume or shutdown an AIN.
*/
final class AutoIngestNodeControlEvent extends AutopsyEvent implements Serializable {
/**
* The set of available controls.
*/
enum ControlEventType {
PAUSE,
RESUME,
SHUTDOWN
}
private static final long serialVersionUID = 1L;
private final String nodeName;
private final ControlEventType eventType;
AutoIngestNodeControlEvent(ControlEventType eventType, String nodeName) {
super(eventType.toString(), null, null);
this.eventType = eventType;
this.nodeName = nodeName;
}
String getNodeName() {
return nodeName;
}
ControlEventType getControlEventType() {
return eventType;
}
}

View File

@ -1,16 +0,0 @@
Known Issues
Last Reviewed: June 12, 2012
This lists the bugs and issues thare are known and could effect
investigation results. There are other minor interface bugs that
are not listed here.
Keyword Search module:
- Slack space of files is not added to the index and therefore will
not be searched.
- Files larger than 100MB AND that are file types that are supported
by Tika (word docs, PDF, HTML, JPEG, etc.) are now added to the index as fully extracted text.
However, due to memory limitations of certain parsers, if full text extraction fails,
only strings extracted from the large file will be added to the index.

View File

@ -1,23 +1,26 @@
---------------- VERSION 4.7.0 -------------- ---------------- VERSION 4.7.0 --------------
New Features: New Features:
- A graph visualization was added to the Communications tool to make it easier to find messages and relationships. - A graph visualization was added to the Communications tool to make it easier to find messages and relationships.
- A new Application content viewer provides custom views of media files, SQLite files, and Plists. - A new "Application" content viewer (lower right) that will contain file-type specific viewers (to reduce number of tabs).
- A data source processor that runs Volatility was added to support ingesting memory images. - New viewer for SQLite databases (in Application content viewer)
- Reports (e.g., RegRipper output) generated by ingest modules are now indexed for keyword search. - New viewer for binary PLists (in Appilcation content viewer)
- Passwords to open password protected archive files can be entered.
- PhotoRec carving module can be configured to keep corrupted files.
- Filters to reduce files processed by ingest modules can have data range conditions.
- L01 files can be imported as data sources. - L01 files can be imported as data sources.
- Block size can be supplied for local drives and for images for which SleuthKit auto detect fails. - Ingest filters can now use date range conditions for triage.
- Passwords to open password protected archive files can be entered (by right clicking on the file).
- Reports (e.g., RegRipper output) generated by ingest modules are now indexed for keyword search.
- PhotoRec carving module can be configured to keep corrupted files.
- Sector size can be specified for local drives and images when E01 is wrong or it is a raw image.
- New data source processor in Experimental module that runs Volatility, adds the outputs as files, and parses the reports to provide INTERESTING_FILE artifacts.
- Assorted small enhancements are included. - Assorted small enhancements are included.
Bug Fixes: Bug Fixes:
- Memory leaks and other issues revealed by fuzzing the SleuthKit have - Memory leaks and other issues revealed by fuzzing the The Sleuth Kit have
been fixed. been fixed.
- Result views (upper right) and content views (lower right) stay in synch when switching result views. - Result views (upper right) and content views (lower right) stay in synch when switching result views.
- Concurrency bugs in the ingest tasks scheduler have been fixed. - Concurrency bugs in the ingest tasks scheduler have been fixed.
- Assorted small bug fixes are included. - Assorted small bug fixes are included.
---------------- VERSION 4.6.0 -------------- ---------------- VERSION 4.6.0 --------------
New Features: New Features:
- A new Message content viewer was added to make it easier to view email message contents. - A new Message content viewer was added to make it easier to view email message contents.

View File

@ -43,3 +43,4 @@ Autopsy depends on a specific version of The Sleuth Kit. You need the Java libr
* Limitations (Updated May 2018) * * Limitations (Updated May 2018) *
- Timeline does not work on OS X - Timeline does not work on OS X
- Video thumbnails are not generated (need to get a consistent version of OpenCV) - Video thumbnails are not generated (need to get a consistent version of OpenCV)
- VHD and VMDK files not supported on OS X

View File

@ -67,6 +67,16 @@
<echo> TSK_HOME: ${env.TSK_HOME}</echo> <echo> TSK_HOME: ${env.TSK_HOME}</echo>
</target> </target>
<target name="clean" depends="suite.clean">
<delete includeEmptyDirs="true" failonerror="false">
<fileset dir="docs\doxygen-user\user-docs" includes="**/*"/>
</delete>
<delete includeEmptyDirs="true" failonerror="false">
<fileset dir="docs\doxygen\doxygen_docs\api-docs" includes="**/*"/>
</delete>
</target>
<!-- This target will create a custom ZIP file for us. It first uses the general <!-- This target will create a custom ZIP file for us. It first uses the general
ZIP target and then opens it up and adds in any files that we want. This is where we customize the ZIP target and then opens it up and adds in any files that we want. This is where we customize the
version number. --> version number. -->
@ -90,7 +100,6 @@
<copy file="${basedir}/README.txt" tofile="${zip-tmp}/${app.name}/README.txt"/> <copy file="${basedir}/README.txt" tofile="${zip-tmp}/${app.name}/README.txt"/>
<copy file="${basedir}/LICENSE-2.0.txt" tofile="${zip-tmp}/${app.name}/LICENSE-2.0.txt"/> <copy file="${basedir}/LICENSE-2.0.txt" tofile="${zip-tmp}/${app.name}/LICENSE-2.0.txt"/>
<copy file="${basedir}/NEWS.txt" tofile="${zip-tmp}/${app.name}/NEWS.txt"/> <copy file="${basedir}/NEWS.txt" tofile="${zip-tmp}/${app.name}/NEWS.txt"/>
<copy file="${basedir}/KNOWN_ISSUES.txt" tofile="${zip-tmp}/${app.name}/KNOWN_ISSUES.txt"/>
<copy file="${basedir}/Running_Linux_OSX.txt" tofile="${zip-tmp}/${app.name}/Running_Linux_OSX.txt"/> <copy file="${basedir}/Running_Linux_OSX.txt" tofile="${zip-tmp}/${app.name}/Running_Linux_OSX.txt"/>
<copy file="${basedir}/unix_setup.sh" tofile="${zip-tmp}/${app.name}/unix_setup.sh"/> <copy file="${basedir}/unix_setup.sh" tofile="${zip-tmp}/${app.name}/unix_setup.sh"/>
<replaceregexp file="${zip-tmp}/${app.name}/unix_setup.sh" match="TSK_VERSION=(.*)" replace="TSK_VERSION=${TSK_VERSION}" byline="true"/> <replaceregexp file="${zip-tmp}/${app.name}/unix_setup.sh" match="TSK_VERSION=(.*)" replace="TSK_VERSION=${TSK_VERSION}" byline="true"/>