/*
* Autopsy Forensic Browser
*
* Copyright 2011-2016 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.coreutils;
import java.io.File;
import java.io.IOException;
import java.net.InetAddress;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Scanner;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public class UNCPathUtilities {
private static Map<String, String> drives;
private static final String MAPPED_DRIVES = "_mapped_drives.txt"; //NON-NLS
private static final String TEMP_FOLDER = "TEMP"; //NON-NLS
private static final String DATA_TRIGGER = "----------"; //NON-NLS
private static final String OK_TXT = "OK"; //NON-NLS
private static final String COLON = ":"; //NON-NLS
private static final String UNC_PATH_START = "\\\\"; //NON-NLS
private static final String C_DRIVE = "C:"; //NON-NLS
private static final int DRIVE_LEN = 2;
private static final int STARTING_OFFSET = 0;
private static final int REPLACEMENT_SIZE = 2;
private static final int FIRST_ITEM = 0;
private final String nameString;
/**
* Constructor
*/
public UNCPathUtilities() {
// get UUID for this instance
this.nameString = UUID.randomUUID().toString();
drives = getMappedDrives();
}
/**
* This method converts a passed in path to UNC if it is not already UNC.
* The UNC path will end up in one of the following two forms:
* "\\hostname\somefolder\otherfolder" or
* "\\IP_ADDRESS\somefolder\otherfolder"
*
* This is accomplished by checking the mapped drives list the operating
* system maintains and substituting where required. If the drive of the
* path passed in does not exist in the cached mapped drives list, you can
* force a rescan of the mapped drives list with rescanDrives(), then call
* this method again. This would be of use if the end user added a mapped
* drive while your dialog was up, for example.
*
* @param inputPath a String of the path to convert
*
* @return returns a successfully converted inputPath or null if unable to
* find a matching drive and convert it to UNC
*/
synchronized public String mappedDriveToUNC(String inputPath) {
if (inputPath != null) {
// If it is a C:, do not attempt to convert. This is for the single-user case.
if (inputPath.toUpperCase().startsWith(C_DRIVE)) {
return null;
}
if (false == isUNC(inputPath)) {
String uncPath = null;
try {
String currentDrive = Paths.get(inputPath).getRoot().toString().substring(STARTING_OFFSET, REPLACEMENT_SIZE);
String uncMapping = drives.get(currentDrive.toUpperCase());
if (uncMapping != null) {
uncPath = uncMapping + inputPath.substring(REPLACEMENT_SIZE, inputPath.length());
}
} catch (Exception ex) {
// Didn't work. Skip it.
}
return uncPath;
} else {
return inputPath;
}
} else {
return null;
}
}
/**
* This method converts a passed in path to UNC if it is not already UNC.
* The UNC path will end up in one of the following two forms:
* \\\\hostname\\somefolder\\otherfolder or
* \\\\IP_ADDRESS\\somefolder\\otherfolder
*
* This is accomplished by checking the mapped drives list the operating
* system maintains and substituting where required. If the drive of the
* path passed in does not exist in the cached mapped drives list, you can
* force a rescan of the mapped drives list with rescanDrives(), then call
* this method again. This would be of use if the end user added a mapped
* drive while your dialog was up, for example.
*
* @param inputPath the path to convert
*
* @return returns a successfully converted inputPath or null if unable to
* find a matching drive and convert it to UNC
*/
synchronized public Path mappedDriveToUNC(Path inputPath) {
if (inputPath != null) {
String uncPath = UNCPathUtilities.this.mappedDriveToUNC(inputPath.toString());
if (uncPath == null) {
return null;
} else {
return Paths.get(uncPath);
}
} else {
return null;
}
}
/**
* Tests if the drive in the passed in path is a mapped drive.
*
* @param inputPath the Path to test.
*
* @return true if the passed in drive is mapped, false otherwise
*/
synchronized public boolean isDriveMapped(Path inputPath) {
if (inputPath != null) {
return isDriveMapped(inputPath.toString());
} else {
return false;
}
}
/**
* Tests if the drive in the passed in path is a mapped drive.
*
* @param inputPath the Path to test.
*
* @return true if the passed in drive is mapped, false otherwise
*/
synchronized public boolean isDriveMapped(String inputPath) {
if (inputPath != null) {
String shortenedPath = inputPath.substring(STARTING_OFFSET, DRIVE_LEN);
for (String s : drives.keySet()) {
if (shortenedPath.equals(s)) {
return true;
}
}
}
return false;
}
/**
* Takes a UNC path that may have an IP address in it and converts it to
* hostname, if it can resolve the hostname. Given
* "\\10.11.12.13\some\folder", the result will be
* "\\TEDS_COMPUTER\some\folder" if the IP address 10.11.12.13 belongs to a
* machine with the hostname TEDS_COMPUTER and the local machine is able to
* resolve the hostname.
*
* @param inputPath the path to convert to a hostname UNC path
*
* @return the successfully converted path or null if unable to resolve
*/
synchronized public Path ipToHostName(Path inputPath) {
if (inputPath != null) {
return Paths.get(ipToHostName(inputPath.toString()));
} else {
return null;
}
}
/**
* Takes a UNC path that may have an IP address in it and converts it to
* hostname, if it can resolve the hostname. Given
* "\\10.11.12.13\some\folder", the result will be
* "\\TEDS_COMPUTER\some\folder" if the IP address 10.11.12.13 belongs to a
* machine with the hostname TEDS_COMPUTER and the local machine is able to
* resolve the hostname.
*
* @param inputPath a String of the path to convert to a hostname UNC path
*
* @return the successfully converted path or null if unable to resolve
*/
synchronized public String ipToHostName(String inputPath) {
if (inputPath != null) {
String result = null;
try {
if (isUNC(Paths.get(inputPath))) {
String potentialIP = Paths.get(inputPath.substring(REPLACEMENT_SIZE)).getName(FIRST_ITEM).toString();
String hostname = InetAddress.getByName(potentialIP).getHostName();
result = inputPath.replaceAll(potentialIP, hostname);
}
} catch (Exception ex) {
// Could not resolve hostname for IP address, return null result
}
return result;
} else {
return null;
}
}
/**
* Test if a Path is UNC. It is considered UNC if it begins with \\
*
* @param inputPath the path to check
*
* @return true if the passed in Path is UNC, false otherwise
*/
synchronized public static boolean isUNC(Path inputPath) {
if (inputPath != null) {
return isUNC(inputPath.toString());
} else {
return false;
}
}
/**
* Test if a String path is UNC. It is considered UNC if it begins with \\
*
* @param inputPath the String of the path to check
*
* @return true if the passed in Path is UNC, false otherwise
*/
synchronized public static boolean isUNC(String inputPath) {
if (inputPath != null) {
return inputPath.startsWith(UNC_PATH_START);
} else {
return false;
}
}
/**
* Updates the list of mapped drives this class contains. This list is used
* to resolve mappedDriveToUNC and isDriveMapped calls. This is useful to
* call if the user has potentially added mapped drives to their system
* after the module calling mappedDriveToUNC has already begun running. Note
* this uses system I/O, so call it with some care.
*
*/
synchronized public void rescanDrives() {
drives = getMappedDrives();
}
/**
* Populates the list of mapped drives this class contains. The list is used
* to resolve mappedDriveToUNC and isDriveMapped calls. Note this uses
* system I/O, so call it with some care.
*
* @return the hashmap
*/
synchronized private Map<String, String> getMappedDrives() {
Map<String, String> driveMap = new HashMap<>();
if (PlatformUtil.isWindowsOS() == false) {
return driveMap;
}
File mappedDrive = Paths.get(System.getenv(TEMP_FOLDER), nameString + MAPPED_DRIVES).toFile();
try {
Files.deleteIfExists(mappedDrive.toPath());
ProcessBuilder builder = new ProcessBuilder("cmd", "/c", "net", "use"); //NON-NLS
builder.redirectOutput(mappedDrive);
builder.redirectError(mappedDrive);
Process p = builder.start(); // throws IOException
p.waitFor(10, TimeUnit.SECONDS);
try (Scanner scanner = new Scanner(mappedDrive)) {
// parse the data and place it in the hashmap
while (scanner.hasNext()) {
String entry1 = scanner.next();
if (entry1.startsWith(DATA_TRIGGER)) {
continue;
}
String entry2 = scanner.next();
if (entry2.startsWith(DATA_TRIGGER)) {
continue;
}
String entry3 = scanner.next();
if (entry3.startsWith(DATA_TRIGGER)) {
continue;
}
scanner.nextLine();
if (entry1.length() == DRIVE_LEN && !entry1.equals(OK_TXT) && entry1.endsWith(COLON)) {
driveMap.put(entry1, entry2); // if there was no leading status, populate drive
} else if (entry2.length() == DRIVE_LEN && entry2.endsWith(COLON)) {
driveMap.put(entry2, entry3); // if there was a leading status, populate drive
}
}
}
} catch (IOException | InterruptedException | NoSuchElementException | IllegalStateException ex) {
// if we couldn't do it, no big deal
Logger.getLogger(UNCPathUtilities.class.getName()).log(Level.WARNING, "Unable to parse 'net use' output", ex); //NON-NLS
} finally {
try {
Files.deleteIfExists(mappedDrive.toPath());
} catch (IOException ex) {
// if we couldn't do it, no big deal
}
}
return driveMap;
}
}