290 lines
9.9 KiB
Java
290 lines
9.9 KiB
Java
package ru.kirillius.mktbk;
|
|
|
|
import me.legrange.mikrotik.ApiConnection;
|
|
import me.legrange.mikrotik.ApiConnectionException;
|
|
import me.legrange.mikrotik.MikrotikApiException;
|
|
import net.schmizz.sshj.SSHClient;
|
|
import net.schmizz.sshj.sftp.SFTPClient;
|
|
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
|
|
import ru.kirillius.utils.logging.SystemLogger;
|
|
|
|
import java.io.Closeable;
|
|
import java.io.IOException;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
import java.util.regex.Pattern;
|
|
|
|
public class Device implements Closeable {
|
|
|
|
private final static String LOG_CONTEXT = Device.class.getSimpleName();
|
|
private final static String LIST_COMMAND = "ls --color=never -lAnpR --full-time /";
|
|
private final static String BEGIN_PATTERN = "///BEGIN-OF-STREAM///";
|
|
private final static String END_PATTERN = "///END-OF-STREAM///";
|
|
private final String host;
|
|
private final String username;
|
|
private final String password;
|
|
private SFTPClient sftpClient;
|
|
private SSHClient sshClient;
|
|
private ApiConnection apiConnection;
|
|
|
|
public Device(String host, String username, String password) {
|
|
this.host = host;
|
|
this.username = username;
|
|
this.password = password;
|
|
|
|
|
|
}
|
|
|
|
public SFTPClient getSftpClient() throws IOException {
|
|
if (sftpClient == null) {
|
|
SystemLogger.message("Initializing sftp connection", LOG_CONTEXT);
|
|
sftpClient = getSshClient().newSFTPClient();
|
|
}
|
|
return sftpClient;
|
|
}
|
|
|
|
public List<ContainerMetadata> getContainers() throws MikrotikApiException {
|
|
return getApiConnection().execute("/container/print").stream().map(ContainerMetadata::new).toList();
|
|
}
|
|
|
|
private String bytesToString(List<Byte> list) {
|
|
var bytes = new byte[list.size()];
|
|
for (var i = 0; i < list.size(); i++) {
|
|
bytes[i] = list.get(i);
|
|
}
|
|
return new String(bytes);
|
|
}
|
|
|
|
private String stripBashSlashes(String path) {
|
|
var escaped = false;
|
|
var doubleQuotes = false;
|
|
if (path.startsWith("\"")) {
|
|
escaped = true;
|
|
doubleQuotes = true;
|
|
} else if (path.startsWith("'")) {
|
|
escaped = true;
|
|
}
|
|
if (!escaped) {
|
|
return path;
|
|
}
|
|
|
|
if (path.contains("'$'")) {
|
|
var builder = new StringBuilder();
|
|
var first = true;
|
|
for (var part : path.split(Pattern.quote("'$'"))) {
|
|
if (first) {
|
|
first = false;
|
|
builder.append(part);
|
|
continue;
|
|
}
|
|
var group = part.split(Pattern.quote("''"));
|
|
if (group.length != 2) {
|
|
SystemLogger.warning("Unable to parse file path: " + path, LOG_CONTEXT);
|
|
builder.append(part);
|
|
} else {
|
|
|
|
|
|
var encoded = group[0];
|
|
var bytes = new ArrayList<Byte>();
|
|
for (var code : Arrays.stream(encoded.split(Pattern.quote("\\"))).skip(1).toArray(String[]::new)) {
|
|
bytes.add((byte) Integer.parseInt(code, 8));
|
|
}
|
|
builder.append(bytesToString(bytes));
|
|
builder.append(group[1]);
|
|
}
|
|
}
|
|
path = builder.toString();
|
|
}
|
|
|
|
|
|
path = path.substring(1, path.length() - 1);
|
|
|
|
if (doubleQuotes) {
|
|
var special = List.of("$", "\\");
|
|
|
|
for (var symbol : special) {
|
|
path = path.replaceAll(Pattern.quote("\\" + symbol), symbol);
|
|
}
|
|
return path;
|
|
} else {
|
|
return path.replaceAll(Pattern.quote("'\\''"), "'");
|
|
}
|
|
}
|
|
|
|
public List<FileMetadata> listFilesystem(ContainerMetadata container) throws IOException, InterruptedException {
|
|
var files = new ArrayList<FileMetadata>();
|
|
|
|
|
|
try (var session = getSshClient().startSession()) {
|
|
session.allocateDefaultPTY();
|
|
try (var terminal = new VirtualTerminal(session)) {
|
|
SystemLogger.message("Waiting for terminal be ready", LOG_CONTEXT);
|
|
terminal.newLine().newLine().newLine().expect("[" + username + "@", 30000).expect("]", 5000);
|
|
SystemLogger.message("Opening container shell", LOG_CONTEXT);
|
|
terminal.skipEverything().write("/container/shell " + container.getId()).write("\r\n\r\n").expect("#", 20000);
|
|
terminal.skipEverything().write("echo " + BEGIN_PATTERN).newLine().expect(BEGIN_PATTERN, 5000).skipEverything();
|
|
terminal.write(LIST_COMMAND).newLine().write("echo " + END_PATTERN).newLine();
|
|
|
|
|
|
var reader = terminal.getReader();
|
|
String line;
|
|
String directory = null;
|
|
while ((line = reader.readLine()) != null) {
|
|
if (line.trim().endsWith(LIST_COMMAND)) {
|
|
continue;
|
|
}
|
|
if (line.contains(BEGIN_PATTERN)) {
|
|
continue;
|
|
}
|
|
if (line.endsWith(END_PATTERN)) {
|
|
break;
|
|
}
|
|
|
|
if (line.isBlank()) {
|
|
continue;
|
|
}
|
|
if ((line.startsWith("/") || line.startsWith("'/")) && line.endsWith(":")) {
|
|
directory = line.substring(0, line.length() - 1);
|
|
if (directory.indexOf('\'') == 0) {
|
|
directory = directory.substring(1, directory.length() - 2);
|
|
}
|
|
if (!directory.endsWith("/")) {
|
|
directory = directory + "/";
|
|
}
|
|
continue;
|
|
}
|
|
|
|
|
|
if (line.trim().isEmpty()) {
|
|
continue;
|
|
}
|
|
if (line.startsWith("total")) {
|
|
continue;
|
|
}
|
|
|
|
line = stripSpaces(line);
|
|
|
|
var split = line.split(Pattern.quote(" "), 9);
|
|
|
|
if (split.length < 9) {
|
|
SystemLogger.warning("Unable to parse line " + line, LOG_CONTEXT);
|
|
continue;
|
|
}
|
|
|
|
|
|
var path = stripBashSlashes(split[8].trim());
|
|
String target = null;
|
|
|
|
if (path.contains("->")) {
|
|
var parts = path.split(Pattern.quote("->"));
|
|
path = stripBashSlashes(parts[0].trim());
|
|
target = stripBashSlashes(parts[1].trim());
|
|
}
|
|
|
|
if (directory == null) {
|
|
SystemLogger.warning("Current directory is not set! Skipping", LOG_CONTEXT);
|
|
continue;
|
|
}
|
|
|
|
path = directory.trim() + path;
|
|
|
|
var file = FileMetadata.builder().permissions(split[0]).owner(split[2]).group(split[3]).path(path).target(target).build();
|
|
|
|
files.add(file);
|
|
if (files.size() % 10000 == 0) {
|
|
SystemLogger.message("Processed " + files.size() + " files", LOG_CONTEXT);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
return files;
|
|
}
|
|
|
|
private String stripSpaces(String line) {
|
|
|
|
String[] parts;
|
|
var singleQuote = line.indexOf("'");
|
|
var doubleQuote = line.indexOf("\"");
|
|
|
|
if (singleQuote == -1 && doubleQuote == -1) {
|
|
parts = new String[]{line};
|
|
} else {
|
|
var offset = -1;
|
|
if (singleQuote == -1) {
|
|
offset = doubleQuote;
|
|
} else if (doubleQuote == -1) {
|
|
offset = singleQuote;
|
|
} else {
|
|
offset = Math.min(singleQuote, doubleQuote);
|
|
}
|
|
parts = new String[]{line.substring(0, offset), line.substring(offset)};
|
|
}
|
|
|
|
while (parts[0].contains(" ")) {
|
|
parts[0] = parts[0].replaceAll(Pattern.quote(" "), " ");
|
|
}
|
|
|
|
var builder = new StringBuilder();
|
|
for (var part : parts) {
|
|
builder.append(part);
|
|
}
|
|
|
|
return builder.toString();
|
|
}
|
|
|
|
public SSHClient getSshClient() throws IOException {
|
|
if (sshClient == null) {
|
|
SystemLogger.message("Initializing ssh connection", LOG_CONTEXT);
|
|
sshClient = new SSHClient();
|
|
sshClient.addHostKeyVerifier(new PromiscuousVerifier());
|
|
sshClient.connect(host);
|
|
sshClient.useCompression();
|
|
sshClient.authPassword(username, password);
|
|
}
|
|
return sshClient;
|
|
}
|
|
|
|
public ApiConnection getApiConnection() {
|
|
if (apiConnection == null) {
|
|
SystemLogger.message("Initializing api connection", LOG_CONTEXT);
|
|
try {
|
|
apiConnection = ApiConnection.connect(host);
|
|
apiConnection.login(username, password);
|
|
} catch (MikrotikApiException e) {
|
|
throw new RuntimeException(e);
|
|
}
|
|
}
|
|
return apiConnection;
|
|
}
|
|
|
|
@Override
|
|
public void close() {
|
|
|
|
if (apiConnection != null) {
|
|
try {
|
|
apiConnection.close();
|
|
} catch (ApiConnectionException e) {
|
|
SystemLogger.error("Error closing api connection", LOG_CONTEXT, e);
|
|
}
|
|
}
|
|
if (sftpClient != null) {
|
|
try {
|
|
sftpClient.close();
|
|
} catch (IOException e) {
|
|
SystemLogger.error("Error closing sftp connection", LOG_CONTEXT, e);
|
|
}
|
|
}
|
|
if (sshClient != null) {
|
|
try {
|
|
sshClient.close();
|
|
} catch (IOException e) {
|
|
SystemLogger.error("Error closing ssh connection", LOG_CONTEXT, e);
|
|
}
|
|
}
|
|
}
|
|
}
|