Wie rufe ich eine Datei von einem Server über SFTP ab?


228

Ich versuche, eine Datei mit SFTP (im Gegensatz zu FTPS) mit Java von einem Server abzurufen. Wie kann ich das machen?

Antworten:


198

Eine andere Möglichkeit besteht darin, einen Blick auf die JSch-Bibliothek zu werfen . JSch scheint die bevorzugte Bibliothek für einige große Open-Source-Projekte zu sein, darunter Eclipse, Ant und Apache Commons HttpClient.

Es unterstützt sowohl Benutzer- / Pass- als auch zertifikatbasierte Anmeldungen sowie alle anderen leckeren SSH2-Funktionen.

Hier ist eine einfache Remote-Datei, die über SFTP abgerufen wird. Die Fehlerbehandlung bleibt dem Leser als Übung :-)

JSch jsch = new JSch();

String knownHostsFilename = "/home/username/.ssh/known_hosts";
jsch.setKnownHosts( knownHostsFilename );

Session session = jsch.getSession( "remote-username", "remote-host" );    
{
  // "interactive" version
  // can selectively update specified known_hosts file 
  // need to implement UserInfo interface
  // MyUserInfo is a swing implementation provided in 
  //  examples/Sftp.java in the JSch dist
  UserInfo ui = new MyUserInfo();
  session.setUserInfo(ui);

  // OR non-interactive version. Relies in host key being in known-hosts file
  session.setPassword( "remote-password" );
}

session.connect();

Channel channel = session.openChannel( "sftp" );
channel.connect();

ChannelSftp sftpChannel = (ChannelSftp) channel;

sftpChannel.get("remote-file", "local-file" );
// OR
InputStream in = sftpChannel.get( "remote-file" );
  // process inputstream as needed

sftpChannel.exit();
session.disconnect();

1
Cheekysoft, ich habe festgestellt, dass das Entfernen von Dateien auf dem SFTP-Server bei Verwendung von Jsch nicht funktioniert. Auch das Umbenennen von Dateien funktioniert nicht. Irgendwelche Ideen bitte ??? Andy

1
Entschuldigung, damit arbeite ich momentan nicht. (Bitte versuchen Sie, diese Art von Antworten als Kommentare - wie diese Nachricht - und nicht als neue Antwort auf die ursprüngliche Frage zu
hinterlassen

1
Was ist dieser Codeblock nach der Zuweisung der Sitzung? Ist das eine ausgefallene Java-Syntax, die ich noch nie gesehen habe? Wenn ja - was bringt es, wenn man so schreibt?
Michael Peterson

3
Mit der Standard-Java-Syntax @ p1x3l5 kann ein Block an einer beliebigen Stelle eingefügt werden. Wenn Sie möchten, können Sie damit den variablen Bereich genauer steuern. In diesem Fall ist es jedoch nur eine visuelle Hilfe, um die beiden Implementierungsoptionen anzugeben: Verwenden Sie entweder die interaktive Version, die ein Kennwort vom Benutzer anfordert, oder verwenden Sie ein fest codiertes Kennwort, das keinen Benutzereingriff erfordert, aber möglicherweise ein zusätzliches Sicherheitsrisiko darstellt.
Cheekysoft

109

Hier ist der vollständige Quellcode eines Beispiels mit JSch, ohne sich um die Überprüfung des SSH-Schlüssels kümmern zu müssen.

import com.jcraft.jsch.*;

public class TestJSch {
    public static void main(String args[]) {
        JSch jsch = new JSch();
        Session session = null;
        try {
            session = jsch.getSession("username", "127.0.0.1", 22);
            session.setConfig("StrictHostKeyChecking", "no");
            session.setPassword("password");
            session.connect();

            Channel channel = session.openChannel("sftp");
            channel.connect();
            ChannelSftp sftpChannel = (ChannelSftp) channel;
            sftpChannel.get("remotefile.txt", "localfile.txt");
            sftpChannel.exit();
            session.disconnect();
        } catch (JSchException e) {
            e.printStackTrace();  
        } catch (SftpException e) {
            e.printStackTrace();
        }
    }
}

15
Ein finallyBlock sollte verwendet werden, um den Kanalbereinigungscode einzuschließen, um sicherzustellen, dass er immer ausgeführt wird.
hotshot309

Ich bekomme jetzt diese Ausnahme: com.jcraft.jsch.JSchException: Session.connect: java.security.InvalidAlgorithmParameterException: Prime size must be multiple of 64, and can only range from 512 to 2048 (inclusive)
anon58192932

Ich fand, dass JSCH 0 oder 1 zusätzliche Abhängigkeiten hat. Sie können die JZLIB-Abhängigkeit ignorieren, wenn Sie die Komprimierung deaktivieren. // Komprimierung deaktivieren session.setConfig ("compress.s2c", "none"); session.setConfig ("compress.c2s", "none");
Englebart

1
Ohne strenge Host-Überprüfung sind Sie anfällig für einen Man-in-the-Middle-Angriff.
Rustyx

44

Unten finden Sie ein Beispiel für die Verwendung von Apache Common VFS:

FileSystemOptions fsOptions = new FileSystemOptions();
SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(fsOptions, "no");
FileSystemManager fsManager = VFS.getManager();
String uri = "sftp://user:password@host:port/absolute-path";
FileObject fo = fsManager.resolveFile(uri, fsOptions);

5
Eine weitere nette Sache ist es, das Timeout so einzustellen, dass Sie nicht für immer dort hängen bleiben, wenn das Remote-System offline ist. Sie können dies genauso tun wie bei der Deaktivierung der Hostschlüsselprüfung: SftpFileSystemConfigBuilder.getInstance (). SetTimeout (fsOptions, 5000);
Scott Jones

Wie würden Sie empfehlen, diese Verbindung zu schließen, wenn Sie mehrere SFTP-Clients gleichzeitig verwenden?
2Big2BeSmall

2
Was ist, wenn mein Passwort das @ -Symbol enthält?
User3

23

Dies war die Lösung, die ich mit http://sourceforge.net/projects/sshtools/ gefunden habe (die meisten Fehler wurden aus Gründen der Übersichtlichkeit weggelassen). Dies ist ein Auszug aus meinem Blog

SshClient ssh = new SshClient();
ssh.connect(host, port);
//Authenticate
PasswordAuthenticationClient passwordAuthenticationClient = new PasswordAuthenticationClient();
passwordAuthenticationClient.setUsername(userName);
passwordAuthenticationClient.setPassword(password);
int result = ssh.authenticate(passwordAuthenticationClient);
if(result != AuthenticationProtocolState.COMPLETE){
     throw new SFTPException("Login to " + host + ":" + port + " " + userName + "/" + password + " failed");
}
//Open the SFTP channel
SftpClient client = ssh.openSftpClient();
//Send the file
client.put(filePath);
//disconnect
client.quit();
ssh.disconnect();

7
Ich stimme (verspätet) zu, es hat gut funktioniert für die ursprüngliche Site / den Download, die ich benötigt habe, aber es hat sich geweigert, für die neue zu funktionieren. Ich bin gerade dabei, zu JSch zu wechseln
David Hayes

23

Eine nette Abstraktion über Jsch ist Apache commons-vfs, das eine virtuelle Dateisystem-API bietet, die den Zugriff auf und das Schreiben von SFTP-Dateien nahezu transparent macht. Hat bei uns gut funktioniert.


1
Ist es möglich, vorinstallierte Schlüssel in Kombination mit commons-vfs zu verwenden?
Benedikt Waldvogel

2
Ja, so ist es. Wenn Sie nicht standardmäßige Identitäten benötigen, können Sie SftpFileSystemConfigBuilder.getInstance (). SetIdentities (...) aufrufen.
Russ Hayward

Sie können vorinstallierte Schlüssel verwenden. Diese Schlüssel müssen jedoch ohne Passwort sein. OtrosLogViewer verwendet die SSH-Schlüsselautorisierung mit VFS, muss jedoch die Passphrase aus dem Schlüssel entfernen ( code.google.com/p/otroslogviewer/wiki/SftpAuthPubKey )
KrzyH

19

Es gibt einen schönen Vergleich der 3 ausgereiften Java-Bibliotheken für SFTP: Commons VFS, SSHJ und JSch

Zusammenfassend lässt sich sagen, dass SSHJ die klarste API hat und die beste davon ist, wenn Sie keine Unterstützung für andere Speicher benötigen, die von Commons VFS bereitgestellt wird.

Hier ist ein SSHJ-Beispiel von github bearbeitet :

final SSHClient ssh = new SSHClient();
ssh.loadKnownHosts(); // or, to skip host verification: ssh.addHostKeyVerifier(new PromiscuousVerifier())
ssh.connect("localhost");
try {
    ssh.authPassword("user", "password"); // or ssh.authPublickey(System.getProperty("user.name"))
    final SFTPClient sftp = ssh.newSFTPClient();
    try {
        sftp.get("test_file", "/tmp/test.tmp");
    } finally {
        sftp.close();
    }
} finally {
    ssh.disconnect();
}

2
Gibt es eine Möglichkeit, die Datei als InputStream abzurufen?
Johan

2
sshj im Jahr 2019 ist immer noch gut gepflegt und wird von Alpakka (Akka) Projekt verwendet
Maxence

13

Apache Commons SFTP-Bibliothek

Gemeinsame Java-Eigenschaftendatei für alle Beispiele

serverAddress = 111.222.333.444

userId = myUserId

Passwort = meinPasswort

remoteDirectory = products /

localDirectory = import /

Laden Sie die Datei mit SFTP auf den Remote-Server hoch

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.Selectors;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;

public class SendMyFiles {

 static Properties props;

 public static void main(String[] args) {

  SendMyFiles sendMyFiles = new SendMyFiles();
  if (args.length < 1)
  {
   System.err.println("Usage: java " + sendMyFiles.getClass().getName()+
     " Properties_file File_To_FTP ");
   System.exit(1);
  }

  String propertiesFile = args[0].trim();
  String fileToFTP = args[1].trim();
  sendMyFiles.startFTP(propertiesFile, fileToFTP);

 }

 public boolean startFTP(String propertiesFilename, String fileToFTP){

  props = new Properties();
  StandardFileSystemManager manager = new StandardFileSystemManager();

  try {

   props.load(new FileInputStream("properties/" + propertiesFilename));
   String serverAddress = props.getProperty("serverAddress").trim();
   String userId = props.getProperty("userId").trim();
   String password = props.getProperty("password").trim();
   String remoteDirectory = props.getProperty("remoteDirectory").trim();
   String localDirectory = props.getProperty("localDirectory").trim();

   //check if the file exists
   String filepath = localDirectory +  fileToFTP;
   File file = new File(filepath);
   if (!file.exists())
    throw new RuntimeException("Error. Local file not found");

   //Initializes the file manager
   manager.init();

   //Setup our SFTP configuration
   FileSystemOptions opts = new FileSystemOptions();
   SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(
     opts, "no");
   SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, true);
   SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);

   //Create the SFTP URI using the host name, userid, password,  remote path and file name
   String sftpUri = "sftp://" + userId + ":" + password +  "@" + serverAddress + "/" + 
     remoteDirectory + fileToFTP;

   // Create local file object
   FileObject localFile = manager.resolveFile(file.getAbsolutePath());

   // Create remote file object
   FileObject remoteFile = manager.resolveFile(sftpUri, opts);

   // Copy local file to sftp server
   remoteFile.copyFrom(localFile, Selectors.SELECT_SELF);
   System.out.println("File upload successful");

  }
  catch (Exception ex) {
   ex.printStackTrace();
   return false;
  }
  finally {
   manager.close();
  }

  return true;
 }


}

Laden Sie die Datei mit SFTP vom Remote-Server herunter

import java.io.File;
import java.io.FileInputStream;
import java.util.Properties;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.Selectors;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;

public class GetMyFiles {

 static Properties props;

 public static void main(String[] args) {

  GetMyFiles getMyFiles = new GetMyFiles();
  if (args.length < 1)
  {
   System.err.println("Usage: java " + getMyFiles.getClass().getName()+
   " Properties_filename File_To_Download ");
   System.exit(1);
  }

  String propertiesFilename = args[0].trim();
  String fileToDownload = args[1].trim();
  getMyFiles.startFTP(propertiesFilename, fileToDownload);

 }

 public boolean startFTP(String propertiesFilename, String fileToDownload){

  props = new Properties();
  StandardFileSystemManager manager = new StandardFileSystemManager();

  try {

   props.load(new FileInputStream("properties/" + propertiesFilename));
   String serverAddress = props.getProperty("serverAddress").trim();
   String userId = props.getProperty("userId").trim();
   String password = props.getProperty("password").trim();
   String remoteDirectory = props.getProperty("remoteDirectory").trim();
   String localDirectory = props.getProperty("localDirectory").trim();


   //Initializes the file manager
   manager.init();

   //Setup our SFTP configuration
   FileSystemOptions opts = new FileSystemOptions();
   SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(
     opts, "no");
   SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, true);
   SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);

   //Create the SFTP URI using the host name, userid, password,  remote path and file name
   String sftpUri = "sftp://" + userId + ":" + password +  "@" + serverAddress + "/" + 
     remoteDirectory + fileToDownload;

   // Create local file object
   String filepath = localDirectory +  fileToDownload;
   File file = new File(filepath);
   FileObject localFile = manager.resolveFile(file.getAbsolutePath());

   // Create remote file object
   FileObject remoteFile = manager.resolveFile(sftpUri, opts);

   // Copy local file to sftp server
   localFile.copyFrom(remoteFile, Selectors.SELECT_SELF);
   System.out.println("File download successful");

  }
  catch (Exception ex) {
   ex.printStackTrace();
   return false;
  }
  finally {
   manager.close();
  }

  return true;
 }

}

Löschen Sie eine Datei auf dem Remote-Server mit SFTP

import java.io.FileInputStream;
import java.util.Properties;

import org.apache.commons.vfs2.FileObject;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.impl.StandardFileSystemManager;
import org.apache.commons.vfs2.provider.sftp.SftpFileSystemConfigBuilder;

public class DeleteRemoteFile {

 static Properties props;

 public static void main(String[] args) {

  DeleteRemoteFile getMyFiles = new DeleteRemoteFile();
  if (args.length < 1)
  {
   System.err.println("Usage: java " + getMyFiles.getClass().getName()+
   " Properties_filename File_To_Delete ");
   System.exit(1);
  }

  String propertiesFilename = args[0].trim();
  String fileToDownload = args[1].trim();
  getMyFiles.startFTP(propertiesFilename, fileToDownload);

 }

 public boolean startFTP(String propertiesFilename, String fileToDownload){

  props = new Properties();
  StandardFileSystemManager manager = new StandardFileSystemManager();

  try {

   props.load(new FileInputStream("properties/" + propertiesFilename));
   String serverAddress = props.getProperty("serverAddress").trim();
   String userId = props.getProperty("userId").trim();
   String password = props.getProperty("password").trim();
   String remoteDirectory = props.getProperty("remoteDirectory").trim();


   //Initializes the file manager
   manager.init();

   //Setup our SFTP configuration
   FileSystemOptions opts = new FileSystemOptions();
   SftpFileSystemConfigBuilder.getInstance().setStrictHostKeyChecking(
     opts, "no");
   SftpFileSystemConfigBuilder.getInstance().setUserDirIsRoot(opts, true);
   SftpFileSystemConfigBuilder.getInstance().setTimeout(opts, 10000);

   //Create the SFTP URI using the host name, userid, password,  remote path and file name
   String sftpUri = "sftp://" + userId + ":" + password +  "@" + serverAddress + "/" + 
     remoteDirectory + fileToDownload;

   //Create remote file object
   FileObject remoteFile = manager.resolveFile(sftpUri, opts);

   //Check if the file exists
   if(remoteFile.exists()){
    remoteFile.delete();
    System.out.println("File delete successful");
   }

  }
  catch (Exception ex) {
   ex.printStackTrace();
   return false;
  }
  finally {
   manager.close();
  }

  return true;
 }

}


Konfigurieren mit ssh-key (öffentlicher Schlüssel) zum Kopieren von Dateien auf den Server. Weil ich ssh_trust zwischen meinem Server und dem Remote-Server machen muss.
MS Parmar

7

hierynomus / sshj hat eine vollständige Implementierung von SFTP Version 3 (was OpenSSH implementiert)

Beispielcode aus SFTPUpload.java

package net.schmizz.sshj.examples;

import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.xfer.FileSystemFile;

import java.io.File;
import java.io.IOException;

/** This example demonstrates uploading of a file over SFTP to the SSH server. */
public class SFTPUpload {

    public static void main(String[] args)
            throws IOException {
        final SSHClient ssh = new SSHClient();
        ssh.loadKnownHosts();
        ssh.connect("localhost");
        try {
            ssh.authPublickey(System.getProperty("user.name"));
            final String src = System.getProperty("user.home") + File.separator + "test_file";
            final SFTPClient sftp = ssh.newSFTPClient();
            try {
                sftp.put(new FileSystemFile(src), "/tmp");
            } finally {
                sftp.close();
            }
        } finally {
            ssh.disconnect();
        }
    }

}

2
gut gemacht!! Ein Beispiel auf der Hauptseite könnte jedoch hilfreich sein.
OhadR

4

Die JSch-Bibliothek ist die leistungsstarke Bibliothek, mit der Dateien vom SFTP-Server gelesen werden können. Unten finden Sie den getesteten Code zum zeilenweisen Lesen der Datei vom SFTP-Speicherort

JSch jsch = new JSch();
        Session session = null;
        try {
            session = jsch.getSession("user", "127.0.0.1", 22);
            session.setConfig("StrictHostKeyChecking", "no");
            session.setPassword("password");
            session.connect();

            Channel channel = session.openChannel("sftp");
            channel.connect();
            ChannelSftp sftpChannel = (ChannelSftp) channel;

            InputStream stream = sftpChannel.get("/usr/home/testfile.txt");
            try {
                BufferedReader br = new BufferedReader(new InputStreamReader(stream));
                String line;
                while ((line = br.readLine()) != null) {
                    System.out.println(line);
                }

            } catch (IOException io) {
                System.out.println("Exception occurred during reading file from SFTP server due to " + io.getMessage());
                io.getMessage();

            } catch (Exception e) {
                System.out.println("Exception occurred during reading file from SFTP server due to " + e.getMessage());
                e.getMessage();

            }

            sftpChannel.exit();
            session.disconnect();
        } catch (JSchException e) {
            e.printStackTrace();
        } catch (SftpException e) {
            e.printStackTrace();
        }

Bitte beziehen Sie sich auf den Blog für das gesamte Programm.


3

Andy, um eine Datei auf einem Remote-System zu löschen, müssen Sie (channelExec)JSch verwenden und Unix / Linux-Befehle übergeben, um sie zu löschen.


2

Probieren Sie edtFTPj / PRO aus , eine ausgereifte, robuste SFTP- Clientbibliothek , die Verbindungspools und asynchrone Vorgänge unterstützt. Unterstützt auch FTP und FTPS, sodass alle Grundlagen für die sichere Dateiübertragung abgedeckt sind.



2

Obwohl die obigen Antworten sehr hilfreich waren, habe ich einen Tag damit verbracht, sie zum Laufen zu bringen, und dabei verschiedene Ausnahmen wie "defekter Kanal", "RSA-Schlüssel unbekannt" und "Paket beschädigt" festgestellt.

Unten finden Sie eine funktionierende wiederverwendbare Klasse für das Hochladen / Herunterladen von SFTP-Dateien mithilfe der JSch-Bibliothek.

Verwendung hochladen:

SFTPFileCopy upload = new SFTPFileCopy(true, /path/to/sourcefile.png", /path/to/destinationfile.png");

Download-Nutzung:

SFTPFileCopy download = new SFTPFileCopy(false, "/path/to/sourcefile.png", "/path/to/destinationfile.png");

Der Klassencode:

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.UIKeyboardInteractive;
import com.jcraft.jsch.UserInfo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import javax.swing.JOptionPane;
import menue.Menue;

public class SFTPFileCopy1 {

    public SFTPFileCopy1(boolean upload, String sourcePath, String destPath) throws FileNotFoundException, IOException {
        Session session = null;
        Channel channel = null;
        ChannelSftp sftpChannel = null;
        try {
            JSch jsch = new JSch();
            //jsch.setKnownHosts("/home/user/.putty/sshhostkeys");
            session = jsch.getSession("login", "mysite.com", 22);
            session.setPassword("password");

            UserInfo ui = new MyUserInfo() {
                public void showMessage(String message) {

                    JOptionPane.showMessageDialog(null, message);

                }

                public boolean promptYesNo(String message) {

                    Object[] options = {"yes", "no"};

                    int foo = JOptionPane.showOptionDialog(null,
                            message,
                            "Warning",
                            JOptionPane.DEFAULT_OPTION,
                            JOptionPane.WARNING_MESSAGE,
                            null, options, options[0]);

                    return foo == 0;

                }
            };
            session.setUserInfo(ui);

            session.setConfig("StrictHostKeyChecking", "no");
            session.connect();
            channel = session.openChannel("sftp");
            channel.setInputStream(System.in);
            channel.setOutputStream(System.out);
            channel.connect();
            sftpChannel = (ChannelSftp) channel;

            if (upload) { // File upload.
                byte[] bufr = new byte[(int) new File(sourcePath).length()];
                FileInputStream fis = new FileInputStream(new File(sourcePath));
                fis.read(bufr);
                ByteArrayInputStream fileStream = new ByteArrayInputStream(bufr);
                sftpChannel.put(fileStream, destPath);
                fileStream.close();
            } else { // File download.
                byte[] buffer = new byte[1024];
                BufferedInputStream bis = new BufferedInputStream(sftpChannel.get(sourcePath));
                OutputStream os = new FileOutputStream(new File(destPath));
                BufferedOutputStream bos = new BufferedOutputStream(os);
                int readCount;
                while ((readCount = bis.read(buffer)) > 0) {
                    bos.write(buffer, 0, readCount);
                }
                bis.close();
                bos.close();
            }
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            if (sftpChannel != null) {
                sftpChannel.exit();
            }
            if (channel != null) {
                channel.disconnect();
            }
            if (session != null) {
                session.disconnect();
            }
        }
    }

    public static abstract class MyUserInfo
            implements UserInfo, UIKeyboardInteractive {

        public String getPassword() {
            return null;
        }

        public boolean promptYesNo(String str) {
            return false;
        }

        public String getPassphrase() {
            return null;
        }

        public boolean promptPassphrase(String message) {
            return false;
        }

        public boolean promptPassword(String message) {
            return false;
        }

        public void showMessage(String message) {
        }

        public String[] promptKeyboardInteractive(String destination,
                String name,
                String instruction,
                String[] prompt,
                boolean[] echo) {

            return null;
        }
    }
}


1

Ich benutze diese SFTP-API namens Zehon, sie ist großartig und mit viel Beispielcode einfach zu verwenden. Hier ist die Website http://www.zehon.com


2
Zehon scheint tot zu sein. Und wo ist die Quelle? Welche "Lizenz" steckt hinter "kostenlos"?
rü-

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.