diff --git a/core/build.gradle b/core/build.gradle index a2e056ed..ce604f82 100644 --- a/core/build.gradle +++ b/core/build.gradle @@ -98,6 +98,7 @@ dependencies { // ssh implementation 'org.apache.sshd:sshd-core:2.+' implementation 'org.apache.sshd:sshd-sftp:2.+' + implementation 'org.apache.sshd:sshd-scp:2.+' // minecraft implementation 'io.github.fragland:MineStat:3.+' diff --git a/src/core/main/java/org/comroid/mcsd/core/MCSD.java b/src/core/main/java/org/comroid/mcsd/core/MCSD.java index 1f3dc209..50df6bcc 100644 --- a/src/core/main/java/org/comroid/mcsd/core/MCSD.java +++ b/src/core/main/java/org/comroid/mcsd/core/MCSD.java @@ -10,12 +10,10 @@ import org.apache.sshd.client.SshClient; import org.apache.sshd.client.keyverifier.AcceptAllServerKeyVerifier; import org.comroid.api.Polyfill; -import org.comroid.api.data.seri.DataStructure; import org.comroid.api.func.util.Debug; import org.comroid.api.func.util.DelegateStream; import org.comroid.api.func.util.Streams; import org.comroid.api.io.FileHandle; -import org.comroid.api.java.JITAssistant; import org.comroid.api.net.REST; import org.comroid.api.os.OS; import org.comroid.mcsd.api.dto.McsdConfig; diff --git a/src/core/main/java/org/comroid/mcsd/core/entity/module/game/ComputerCraftPlayerFileSystemProviderModulePrototype.java b/src/core/main/java/org/comroid/mcsd/core/entity/module/game/ComputerCraftPlayerFileSystemProviderModulePrototype.java new file mode 100644 index 00000000..a9f1748b --- /dev/null +++ b/src/core/main/java/org/comroid/mcsd/core/entity/module/game/ComputerCraftPlayerFileSystemProviderModulePrototype.java @@ -0,0 +1,27 @@ +package org.comroid.mcsd.core.entity.module.game; + +import jakarta.persistence.ElementCollection; +import jakarta.persistence.Entity; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.comroid.mcsd.core.entity.module.ModulePrototype; +import org.comroid.mcsd.core.entity.system.User; + +import java.util.List; +import java.util.Map; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class ComputerCraftPlayerFileSystemProviderModulePrototype extends ModulePrototype { + public static final String ComputerIdSeparator = ","; + + short serverPort; + @ElementCollection List worldPaths; + @ElementCollection Map userPasswords; + @ElementCollection Map userComputers; +} diff --git a/src/core/main/java/org/comroid/mcsd/core/module/game/ComputerCraftPlayerFileSystemProviderModule.java b/src/core/main/java/org/comroid/mcsd/core/module/game/ComputerCraftPlayerFileSystemProviderModule.java new file mode 100644 index 00000000..3f878308 --- /dev/null +++ b/src/core/main/java/org/comroid/mcsd/core/module/game/ComputerCraftPlayerFileSystemProviderModule.java @@ -0,0 +1,199 @@ +package org.comroid.mcsd.core.module.game; + +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; +import lombok.Value; +import org.apache.sshd.common.util.buffer.Buffer; +import org.apache.sshd.scp.server.ScpCommandFactory; +import org.apache.sshd.server.SshServer; +import org.apache.sshd.server.auth.AsyncAuthException; +import org.apache.sshd.server.auth.password.PasswordAuthenticator; +import org.apache.sshd.server.auth.password.PasswordChangeRequiredException; +import org.apache.sshd.server.session.ServerSession; +import org.apache.sshd.sftp.server.*; +import org.comroid.api.net.Token; +import org.comroid.mcsd.core.MCSD; +import org.comroid.mcsd.core.entity.module.game.ComputerCraftPlayerFileSystemProviderModulePrototype; +import org.comroid.mcsd.core.entity.server.Server; +import org.comroid.mcsd.core.entity.system.User; +import org.comroid.mcsd.core.exception.EntityNotFoundException; +import org.comroid.mcsd.core.module.ServerModule; +import org.comroid.mcsd.core.repo.system.UserRepo; + +import java.io.IOException; +import java.nio.channels.Channel; +import java.nio.channels.FileLock; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.*; +import java.nio.file.attribute.*; +import java.security.Principal; +import java.util.*; +import java.util.stream.Stream; + +import static org.comroid.mcsd.core.util.ApplicationContextProvider.bean; + +@Getter +@Setter +public abstract class ComputerCraftPlayerFileSystemProviderModule extends ServerModule { + private SshServer sshd; + + public ComputerCraftPlayerFileSystemProviderModule(Server server, ComputerCraftPlayerFileSystemProviderModulePrototype proto) { + super(server, proto); + } + + @Override + public Stream streamOwnChildren() { + return Stream.of(sshd); + } + + @Override + @SneakyThrows + protected void $initialize() { + sshd = SshServer.setUpDefaultServer(); + sshd.setPort(proto.getServerPort()); + + sshd.setSubsystemFactories(List.of(new SftpSubsystemFactory.Builder().build())); + sshd.setCommandFactory(new ScpCommandFactory.Builder().build()); + + var sftpSubsystemFactory = new SftpSubsystemFactory.Builder() + .withFileSystemAccessor(new VirtualFileSystem()) + .build(); + var userAuthFactory = new UserProvider(); + + sshd.setSubsystemFactories(List.of(sftpSubsystemFactory)); + sshd.setPasswordAuthenticator(new UserProvider()); + sshd.start(); + } + + @Value + private class UserProvider implements PasswordAuthenticator { + @Override + public boolean authenticate(String username, String password, ServerSession serverSession) + throws PasswordChangeRequiredException, AsyncAuthException { + return bean(UserRepo.class).findByName(username) + .map(user -> proto.getUserPasswords().getOrDefault(user, null)) + .filter(password::equals) + .or(() -> { + var pw = Token.random(12, false); + bean(MCSD.class).getModules_computercraft() + .updateModuleState(); + return Optional.of(pw); + }) + } + + @Override + public boolean handleClientPasswordChangeRequest(ServerSession session, String username, String oldPassword, String newPassword) { + var pw = bean(UserRepo.class).findByName(username) + .map(user -> proto.getUserPasswords().getOrDefault(user, null)) + .orElseThrow(() -> new EntityNotFoundException(User.class, username)); + } + } + + @Value + private class VirtualFileSystem implements SftpFileSystemAccessor { + @Override + public Path resolveLocalFilePath(SftpSubsystemProxy subsystem, Path rootDir, String remotePath) throws IOException, InvalidPathException { + } + + @Override + public LinkOption[] resolveFileAccessLinkOptions(SftpSubsystemProxy subsystem, Path file, int cmd, String extension, boolean followLinks) throws IOException { + } + + @Override + public NavigableMap resolveReportedFileAttributes(SftpSubsystemProxy subsystem, Path file, int flags, NavigableMap attrs, LinkOption... options) throws IOException { + } + + @Override + public void applyExtensionFileAttributes(SftpSubsystemProxy subsystem, Path file, Map extensions, LinkOption... options) throws IOException { + } + + @Override + public void putRemoteFileName(SftpSubsystemProxy subsystem, Path path, Buffer buf, String name, boolean shortName) throws IOException { + } + + @Override + public SeekableByteChannel openFile(SftpSubsystemProxy subsystem, FileHandle fileHandle, Path file, String handle, Set options, FileAttribute... attrs) throws IOException { + } + + @Override + public FileLock tryLock(SftpSubsystemProxy subsystem, FileHandle fileHandle, Path file, String handle, Channel channel, long position, long size, boolean shared) throws IOException { + } + + @Override + public void syncFileData(SftpSubsystemProxy subsystem, FileHandle fileHandle, Path file, String handle, Channel channel) throws IOException { + } + + @Override + public void closeFile(SftpSubsystemProxy subsystem, FileHandle fileHandle, Path file, String handle, Channel channel, Set options) throws IOException { + } + + @Override + public DirectoryStream openDirectory(SftpSubsystemProxy subsystem, DirectoryHandle dirHandle, Path dir, String handle, LinkOption... linkOptions) throws IOException { + } + + @Override + public void closeDirectory(SftpSubsystemProxy subsystem, DirectoryHandle dirHandle, Path dir, String handle, DirectoryStream ds) throws IOException { + } + + @Override + public Map readFileAttributes(SftpSubsystemProxy subsystem, Path file, String view, LinkOption... options) throws IOException { + } + + @Override + public void setFileAttribute(SftpSubsystemProxy subsystem, Path file, String view, String attribute, Object value, LinkOption... options) throws IOException { + } + + @Override + public UserPrincipal resolveFileOwner(SftpSubsystemProxy subsystem, Path file, UserPrincipal name) throws IOException { + } + + @Override + public void setFileOwner(SftpSubsystemProxy subsystem, Path file, Principal value, LinkOption... options) throws IOException { + } + + @Override + public GroupPrincipal resolveGroupOwner(SftpSubsystemProxy subsystem, Path file, GroupPrincipal name) throws IOException { + } + + @Override + public void setGroupOwner(SftpSubsystemProxy subsystem, Path file, Principal value, LinkOption... options) throws IOException { + } + + @Override + public void setFilePermissions(SftpSubsystemProxy subsystem, Path file, Set perms, LinkOption... options) throws IOException { + } + + @Override + public void setFileAccessControl(SftpSubsystemProxy subsystem, Path file, List acl, LinkOption... options) throws IOException { + } + + @Override + public void createDirectory(SftpSubsystemProxy subsystem, Path path) throws IOException { + } + + @Override + public void createLink(SftpSubsystemProxy subsystem, Path link, Path existing, boolean symLink) throws IOException { + } + + @Override + public String resolveLinkTarget(SftpSubsystemProxy subsystem, Path link) throws IOException { + } + + @Override + public void renameFile(SftpSubsystemProxy subsystem, Path oldPath, Path newPath, Collection opts) throws IOException { + } + + @Override + public void copyFile(SftpSubsystemProxy subsystem, Path src, Path dst, Collection opts) throws IOException { + } + + @Override + public void removeFile(SftpSubsystemProxy subsystem, Path path, boolean isDirectory) throws IOException { + } + + @Override + public boolean noFollow(Collection opts) { + } + } +}