Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Provide virtual SCP/SFTP hosts for providing player-related data #48

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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.+'
Expand Down
2 changes: 0 additions & 2 deletions src/core/main/java/org/comroid/mcsd/core/MCSD.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> worldPaths;
@ElementCollection Map<User, String> userPasswords;
@ElementCollection Map<User, String> userComputers;
}
Original file line number Diff line number Diff line change
@@ -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<ComputerCraftPlayerFileSystemProviderModulePrototype> {
private SshServer sshd;

public ComputerCraftPlayerFileSystemProviderModule(Server server, ComputerCraftPlayerFileSystemProviderModulePrototype proto) {
super(server, proto);
}

@Override
public Stream<Object> 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<String, Object> resolveReportedFileAttributes(SftpSubsystemProxy subsystem, Path file, int flags, NavigableMap<String, Object> attrs, LinkOption... options) throws IOException {
}

@Override
public void applyExtensionFileAttributes(SftpSubsystemProxy subsystem, Path file, Map<String, byte[]> 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<? extends OpenOption> 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<? extends OpenOption> options) throws IOException {
}

@Override
public DirectoryStream<Path> 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<Path> ds) throws IOException {
}

@Override
public Map<String, ?> 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<PosixFilePermission> perms, LinkOption... options) throws IOException {
}

@Override
public void setFileAccessControl(SftpSubsystemProxy subsystem, Path file, List<AclEntry> 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<CopyOption> opts) throws IOException {
}

@Override
public void copyFile(SftpSubsystemProxy subsystem, Path src, Path dst, Collection<CopyOption> opts) throws IOException {
}

@Override
public void removeFile(SftpSubsystemProxy subsystem, Path path, boolean isDirectory) throws IOException {
}

@Override
public boolean noFollow(Collection<?> opts) {
}
}
}