/* * Copyright 2014 Samoilenko Yuri <kinnalru@gmail.com> * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License as * published by the Free Software Foundation; either version 2 of * the License or (at your option) version 3 or any later version * accepted by the membership of KDE e.V. (or its successor approved * by the membership of KDE e.V.), which shall act as a proxy * defined in Section 14 of version 3 of the license. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kde.kdeconnect.Plugins.SftpPlugin; import android.content.Context; import android.net.Uri; import android.util.Log; import org.apache.sshd.SshServer; import org.apache.sshd.common.NamedFactory; import org.apache.sshd.common.Session; import org.apache.sshd.common.util.SecurityUtils; import org.apache.sshd.server.Command; import org.apache.sshd.server.FileSystemFactory; import org.apache.sshd.server.FileSystemView; import org.apache.sshd.server.PasswordAuthenticator; import org.apache.sshd.server.PublickeyAuthenticator; import org.apache.sshd.server.SshFile; import org.apache.sshd.server.command.ScpCommandFactory; import org.apache.sshd.server.filesystem.NativeFileSystemView; import org.apache.sshd.server.filesystem.NativeSshFile; import org.apache.sshd.server.kex.DHG1; import org.apache.sshd.server.kex.DHG14; import org.apache.sshd.server.keyprovider.SimpleGeneratorHostKeyProvider; import org.apache.sshd.server.session.ServerSession; import org.apache.sshd.server.sftp.SftpSubsystem; import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Helpers.MediaStoreHelper; import org.kde.kdeconnect.Helpers.RandomHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import java.io.File; import java.io.IOException; import java.net.Inet4Address; import java.net.InetAddress; import java.net.NetworkInterface; import java.net.SocketException; import java.security.PublicKey; import java.security.Security; import java.util.Arrays; import java.util.Collections; import java.util.Enumeration; class SimpleSftpServer { private static final int STARTPORT = 1739; private static final int ENDPORT = 1764; static final String USER = "kdeconnect"; private int port = -1; private boolean started = false; private final SimplePasswordAuthenticator passwordAuth = new SimplePasswordAuthenticator(); private final SimplePublicKeyAuthenticator keyAuth = new SimplePublicKeyAuthenticator(); static { Security.insertProviderAt(SslHelper.BC, 1); SecurityUtils.setRegisterBouncyCastle(false); } private final SshServer sshd = SshServer.setUpDefaultServer(); public void init(Context context, Device device) { sshd.setKeyExchangeFactories(Arrays.asList( new DHG14.Factory(), new DHG1.Factory())); sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider(context.getFilesDir() + "/sftpd.ser")); sshd.setFileSystemFactory(new AndroidFileSystemFactory(context)); sshd.setCommandFactory(new ScpCommandFactory()); sshd.setSubsystemFactories(Collections.singletonList((NamedFactory<Command>)new SftpSubsystem.Factory())); if (device.publicKey != null) { keyAuth.deviceKey = device.publicKey; sshd.setPublickeyAuthenticator(keyAuth); } sshd.setPasswordAuthenticator(passwordAuth); } public boolean start() { if (!started) { passwordAuth.password = RandomHelper.randomString(28); port = STARTPORT; while(!started) { try { sshd.setPort(port); sshd.start(); started = true; } catch(Exception e) { e.printStackTrace(); port++; if (port >= ENDPORT) { port = -1; Log.e("SftpServer", "No more ports available"); return false; } } } } return true; } public void stop() { try { started = false; sshd.stop(); } catch (Exception e) { e.printStackTrace(); } } public String getPassword() { return passwordAuth.password; } public int getPort() { return port; } public String getLocalIpAddress() { String ip6 = null; try { for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { NetworkInterface intf = en.nextElement(); for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { InetAddress inetAddress = enumIpAddr.nextElement(); if (!inetAddress.isLoopbackAddress()) { String address = inetAddress.getHostAddress(); if(inetAddress instanceof Inet4Address) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6 return address; } else { ip6 = address; } } } } } catch (SocketException ex) { } return ip6; } static class AndroidFileSystemFactory implements FileSystemFactory { final private Context context; public AndroidFileSystemFactory(Context context) { this.context = context; } @Override public FileSystemView createFileSystemView(final Session username) { return new AndroidFileSystemView(username.getUsername(), context); } } static class AndroidFileSystemView extends NativeFileSystemView { final private String userName; final private Context context; public AndroidFileSystemView(final String userName, Context context) { super(userName, true); this.userName = userName; this.context = context; } @Override protected SshFile getFile(final String dir, final String file) { File fileObj = new File(dir, file); return new AndroidSshFile(fileObj, userName, context); } } static class AndroidSshFile extends NativeSshFile { final private Context context; final private File file; public AndroidSshFile(final File file, final String userName, Context context) { super(file.getAbsolutePath(), file, userName); this.context = context; this.file = file; } @Override public boolean delete() { //Log.e("Sshd", "deleting file"); boolean ret = super.delete(); if (ret) { MediaStoreHelper.indexFile(context, Uri.fromFile(file)); } return ret; } @Override public boolean create() throws IOException { //Log.e("Sshd", "creating file"); boolean ret = super.create(); if (ret) { MediaStoreHelper.indexFile(context, Uri.fromFile(file)); } return ret; } } static class SimplePasswordAuthenticator implements PasswordAuthenticator { public String password; @Override public boolean authenticate(String user, String password, ServerSession session) { return user.equals(SimpleSftpServer.USER) && password.equals(this.password); } } static class SimplePublicKeyAuthenticator implements PublickeyAuthenticator { public PublicKey deviceKey; @Override public boolean authenticate(String user, PublicKey key, ServerSession session) { return deviceKey.equals(key); } } }