---
plugins/curl/nbdkit-curl-plugin.pod | 22 +-
plugins/ssh/nbdkit-ssh-plugin.pod | 150 ++++++++
configure.ac | 17 +
plugins/ssh/ssh.c | 521 ++++++++++++++++++++++++++++
README | 4 +
TODO | 10 +-
plugins/ssh/Makefile.am | 69 ++++
7 files changed, 779 insertions(+), 14 deletions(-)
diff --git a/plugins/curl/nbdkit-curl-plugin.pod b/plugins/curl/nbdkit-curl-plugin.pod
index ef07890..3909baa 100644
--- a/plugins/curl/nbdkit-curl-plugin.pod
+++ b/plugins/curl/nbdkit-curl-plugin.pod
@@ -1,6 +1,6 @@
=head1 NAME
-nbdkit-curl-plugin - nbdkit curl plugin (HTTP, FTP, SSH and other protocols)
+nbdkit-curl-plugin - nbdkit curl plugin (HTTP, FTP and other protocols)
=head1 SYNOPSIS
@@ -11,12 +11,12 @@ nbdkit-curl-plugin - nbdkit curl plugin (HTTP, FTP, SSH and other
protocols)
=head1 DESCRIPTION
C<nbdkit-curl-plugin> is a plugin for L<nbdkit(1)> which turns content
-served over HTTP, FTP, SSH, and more, into a Network Block Device. It
-uses a library called libcurl (also known as cURL) to read data from
-URLs. The exact list of protocols that libcurl can handle depends on
-how it was compiled, but most versions will handle HTTP, HTTPS, FTP,
-FTPS and SFTP (see: S<C<curl -V>>). For more information about
-libcurl, see L<http://curl.haxx.se>.
+served over HTTP, FTP, and more, into a Network Block Device. It uses
+a library called libcurl (also known as cURL) to read data from URLs.
+The exact list of protocols that libcurl can handle depends on how it
+was compiled, but most versions will handle HTTP, HTTPS, FTP, FTPS and
+SFTP (see: S<C<curl -V>>). For more information about libcurl, see
+L<http://curl.haxx.se>.
B<Note:> This plugin supports writes. However:
@@ -47,8 +47,11 @@ control ports and protocols used to serve NBD see L<nbdkit(1)>).
=head3 Accessing SSH servers
-You can also access SSH servers. This uses the SFTP protocol which is
-built into most SSH servers:
+You can also access SSH servers via this plugin, although it is
+usually better to use the L<nbdkit-ssh-plugin(1)> instead.
+
+This plugin uses the SFTP protocol which is built into most SSH
+servers:
nbdkit -r curl
sftp://example.com/~/disk.img
@@ -131,6 +134,7 @@ L<curl(1)>,
L<libcurl(3)>,
L<CURLOPT_VERBOSE(3)>,
L<nbdkit(1)>,
+L<nbdkit-ssh-plugin(1)>,
L<nbdkit-plugin(3)>.
=head1 AUTHORS
diff --git a/plugins/ssh/nbdkit-ssh-plugin.pod b/plugins/ssh/nbdkit-ssh-plugin.pod
new file mode 100644
index 0000000..bcedf4f
--- /dev/null
+++ b/plugins/ssh/nbdkit-ssh-plugin.pod
@@ -0,0 +1,150 @@
+=head1 NAME
+
+nbdkit-ssh-plugin - access disk images over the SSH protocol
+
+=head1 SYNOPSIS
+
+ nbdkit ssh host=HOST path=PATH
+ [port=PORT] [user=USER] [password=PASSWORD|-|+FILENAME]
+ [config=CONFIG_FILE]
+
+=head1 DESCRIPTION
+
+This is an L<nbdkit(1)> plugin which lets you access remote disk
+images over Secure Shell (SSH). Any server which hosts disk images
+and runs an SSH server can be turned into an NBD source using this
+plugin.
+
+=head2 EXAMPLES
+
+=over 4
+
+=item nbdkit ssh
host=ssh.example.com path=disk.img
+
+Open a file called F<disk.img> on remote host C<ssh.example.com>.
+Because the pathname is relative, it is opened relative to the user’s
+home directory on the remote server.
+
+The remote file can be read or written. To force read-only access add
+the I<-r> flag.
+
+=item nbdkit ssh
host=ssh.example.com path=disk.img user=bob
+
+As above but log in using username C<bob> (instead of trying the local
+username).
+
+=back
+
+=head1 PARAMETERS
+
+=over 4
+
+=item B<config=>CONFIG_FILE
+
+Read local SSH configuration from an alternate configuration file.
+
+=item B<config=>
+
+Do not read any local SSH configuration.
+
+The C<config> parameter is optional. If not specified then
+F<~/.ssh/config> is read.
+
+=item B<host=>HOST
+
+Specify the name or IP address of the remote host.
+
+This parameter is required.
+
+=item B<password=>PASSWORD
+
+Set the password to use when connecting to the remote server.
+
+Note that passing this on the command line is not secure on shared
+machines.
+
+=item B<password=->
+
+Ask for the password (interactively) when nbdkit starts up.
+
+=item B<password=+>FILENAME
+
+Read the password from the named file. This is the most secure method
+to supply a password, as long as you set the permissions on the file
+appropriately.
+
+=item B<path=>PATH
+
+Specify the path to the remote file. This can be a relative path in
+which case it is relative to the remote home directory.
+
+This parameter is required.
+
+=item B<port=>PORT
+
+Specify the SSH protocol port name or number.
+
+This parameter is optional. If not given then the default ssh port is
+used.
+
+=item B<user=>USER
+
+Specify the remote username.
+
+This parameter is optional. If not given then the local username is
+used.
+
+=back
+
+=head1 NOTES
+
+=head2 Known hosts
+
+The SSH server’s host key is checked at connection time, and must be
+present and correct in the local "known hosts" file (usually
+F<~/.ssh/known_hosts>). If you have never connected to the SSH server
+before then the connection will usually fail. You may have to connect
+to the server first using L<ssh(1)> so you can manually accept the
+host key.
+
+=head2 Supported authentication methods
+
+This plugin supports only the following authentication methods:
+C<none>, C<publickey> or C<password>. In particular note that
+C<keyboard-interactive> is I<not> supported.
+
+=head2 SSH agent
+
+There is no means for nbdkit to ask for the public key passphrase when
+it is running as a server. Therefore C<publickey> authentication must
+be done in conjunction with L<ssh-agent(1)>.
+
+=head1 FILES
+
+=over 4
+
+=item F<~/.ssh/config>
+
+This is the default local SSH config file which is read to get other
+options. You can change this using the C<config> option.
+
+=back
+
+=head1 SEE ALSO
+
+L<nbdkit(1)>,
+L<nbdkit-curl-plugin(1)>,
+L<nbdkit-plugin(3)>,
+L<ssh(1)>,
+L<ssh-agent(1)>,
+L<https://libssh.org>.
+
+=head1 AUTHORS
+
+Richard W.M. Jones
+
+Parts derived from Pino Toscano’s qemu libssh driver.
+
+=head1 COPYRIGHT
+
+Copyright (C) 2014-2019 Red Hat Inc.
diff --git a/configure.ac b/configure.ac
index 467d48f..748e5aa 100644
--- a/configure.ac
+++ b/configure.ac
@@ -630,6 +630,21 @@ AS_IF([test "$with_curl" != "no"],[
])
AM_CONDITIONAL([HAVE_CURL],[test "x$CURL_LIBS" != "x"])
+dnl Check for libssh (only if you want to compile the ssh plugin).
+AC_ARG_WITH([ssh],
+ [AS_HELP_STRING([--without-ssh],
+ [disable ssh plugin @<:@default=check@:>@])],
+ [],
+ [with_ssh=check])
+AS_IF([test "$with_ssh" != "no"],[
+ PKG_CHECK_MODULES([SSH], [libssh],[
+ AC_SUBST([SSH_CFLAGS])
+ AC_SUBST([SSH_LIBS])
+ ],
+ [AC_MSG_WARN([libssh not found, ssh plugin will be disabled])])
+])
+AM_CONDITIONAL([HAVE_SSH],[test "x$SSH_LIBS" != "x"])
+
dnl Check for genisoimage or mkisofs
dnl (only if you want to compile the iso plugin).
ISOPROG="no"
@@ -800,6 +815,7 @@ non_lang_plugins="\
partitioning \
pattern \
random \
+ ssh \
split \
streaming \
tar \
@@ -872,6 +888,7 @@ AC_CONFIG_FILES([Makefile
plugins/rust/Cargo.toml
plugins/rust/Makefile
plugins/sh/Makefile
+ plugins/ssh/Makefile
plugins/split/Makefile
plugins/streaming/Makefile
plugins/tar/Makefile
diff --git a/plugins/ssh/ssh.c b/plugins/ssh/ssh.c
new file mode 100644
index 0000000..562f1a3
--- /dev/null
+++ b/plugins/ssh/ssh.c
@@ -0,0 +1,521 @@
+/* nbdkit
+ * Copyright (C) 2014-2019 Red Hat Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met:
+ *
+ * * Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * * Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * * Neither the name of Red Hat nor the names of its contributors may be
+ * used to endorse or promote products derived from this software without
+ * specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+ * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+ * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+ * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <config.h>
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <string.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include <libssh/libssh.h>
+#include <libssh/sftp.h>
+
+#include <nbdkit-plugin.h>
+
+#include "minmax.h"
+
+static const char *host = NULL;
+static const char *path = NULL;
+static const char *port = NULL;
+static const char *user = NULL;
+static char *password = NULL;
+
+/* config can be:
+ * NULL => parse options from default file
+ * "" => do NOT parse options
+ * some filename => parse options from filename
+ */
+static const char *config = NULL;
+
+/* Use '-D ssh.log=N' to set.
+ *
+ * The log levels (N) are:
+ *
+ * SSH_LOG_NONE -> 0
+ * SSH_LOG_WARN -> 1
+ * SSH_LOG_INFO -> 2
+ * SSH_LOG_DEBUG -> 3
+ * SSH_LOG_TRACE -> 4
+ */
+int ssh_debug_log = 0;
+
+static void
+ssh_unload (void)
+{
+ free (password);
+}
+
+/* Called for each key=value passed on the command line. */
+static int
+ssh_config (const char *key, const char *value)
+{
+ if (strcmp (key, "host") == 0)
+ host = value;
+
+ else if (strcmp (key, "path") == 0)
+ path = value;
+
+ else if (strcmp (key, "port") == 0)
+ port = value;
+
+ else if (strcmp (key, "user") == 0)
+ user = value;
+
+ else if (strcmp (key, "password") == 0) {
+ free (password);
+ if (nbdkit_read_password (value, &password) == -1)
+ return -1;
+ }
+
+ else if (strcmp (key, "config") == 0)
+ config = value;
+
+ else {
+ nbdkit_error ("unknown parameter '%s'", key);
+ return -1;
+ }
+
+ return 0;
+}
+
+/* The host and path parameters are mandatory. */
+static int
+ssh_config_complete (void)
+{
+ if (host == NULL || path == NULL) {
+ nbdkit_error ("you must supply the host and path parameters "
+ "after the plugin name on the command line");
+ return -1;
+ }
+
+ ssh_set_log_level (ssh_debug_log);
+
+ return 0;
+}
+
+#define ssh_config_help \
+ "host=<HOST> (required) SSH server hostname.\n" \
+ "path=<PATH> (required) SSH remote path.\n" \
+ "port=<PORT> SSH protocol port number.\n" \
+ "user=<USER> SSH user name.\n" \
+ "password=<PASSWORD> SSH password.\n" \
+ "config=<CONFIG> Alternate local SSH configuration file." \
+
+/* The per-connection handle. */
+struct ssh_handle {
+ ssh_session session;
+ sftp_session sftp;
+ sftp_file file;
+};
+
+/* Verify the remote host.
+ * See:
http://api.libssh.org/master/libssh_tutor_guided_tour.html
+ */
+static int
+verify_remote_host (struct ssh_handle *h)
+{
+ enum ssh_known_hosts_e state;
+ unsigned char *hash = NULL;
+ ssh_key srv_pubkey = NULL;
+ size_t hlen;
+ int rc;
+
+ rc = ssh_get_server_publickey (h->session, &srv_pubkey);
+ if (rc < 0) {
+ nbdkit_error ("could not get server public key");
+ return -1;
+ }
+ rc = ssh_get_publickey_hash (srv_pubkey,
+ SSH_PUBLICKEY_HASH_SHA1,
+ &hash, &hlen);
+ ssh_key_free(srv_pubkey);
+ if (rc < 0) {
+ nbdkit_error ("could not get server public key SHA1 hash");
+ return -1;
+ }
+
+ state = ssh_session_is_known_server (h->session);
+ switch (state) {
+ case SSH_KNOWN_HOSTS_OK:
+ /* OK */
+ break;
+
+ case SSH_KNOWN_HOSTS_CHANGED:
+ nbdkit_error ("host key for server changed");
+ ssh_clean_pubkey_hash (&hash);
+ return -1;
+
+ case SSH_KNOWN_HOSTS_OTHER:
+ nbdkit_error ("host key for server was not found "
+ "but another type of key exists");
+ ssh_clean_pubkey_hash (&hash);
+ return -1;
+
+ case SSH_KNOWN_HOSTS_NOT_FOUND:
+ /* This is not actually an error, but the user must ensure the
+ * host key is set up before using nbdkit so we error out here.
+ */
+ nbdkit_error ("could not find known_hosts file");
+ ssh_clean_pubkey_hash (&hash);
+ return -1;
+
+ case SSH_KNOWN_HOSTS_UNKNOWN:
+ nbdkit_error ("host key is unknown, you must use ssh first "
+ "and accept the host key");
+ ssh_clean_pubkey_hash (&hash);
+ return -1;
+
+ case SSH_KNOWN_HOSTS_ERROR:
+ nbdkit_error ("known hosts error: %s", ssh_get_error (h->session));
+ ssh_clean_pubkey_hash (&hash);
+ return -1;
+ }
+
+ ssh_clean_pubkey_hash (&hash);
+ return 0;
+}
+
+/* Authenticate.
+ * See:
http://api.libssh.org/master/libssh_tutor_authentication.html
+ */
+static int
+authenticate_pubkey (ssh_session session)
+{
+ int rc;
+
+ rc = ssh_userauth_publickey_auto (session, NULL, NULL);
+ if (rc == SSH_AUTH_ERROR)
+ nbdkit_debug ("public key authentication failed: %s",
+ ssh_get_error (session));
+
+ return rc;
+}
+
+static int
+authenticate_password (ssh_session session, const char *password)
+{
+ int rc;
+
+ rc = ssh_userauth_password (session, NULL, password);
+ if (rc == SSH_AUTH_ERROR)
+ nbdkit_debug ("password authentication failed: %s",
+ ssh_get_error (session));
+ return rc;
+}
+
+static int
+authenticate (struct ssh_handle *h)
+{
+ int method, rc;
+
+ rc = ssh_userauth_none (h->session, NULL);
+ if (rc == SSH_AUTH_SUCCESS)
+ return 0;
+ if (rc == SSH_AUTH_ERROR)
+ return -1;
+
+ method = ssh_userauth_list (h->session, NULL);
+
+ if (method & SSH_AUTH_METHOD_PUBLICKEY) {
+ rc = authenticate_pubkey (h->session);
+ if (rc == SSH_AUTH_SUCCESS) return 0;
+ }
+
+ /* Example code tries keyboard-interactive here, but we cannot use
+ * that method from a server.
+ */
+
+ if (password != NULL && (method & SSH_AUTH_METHOD_PASSWORD)) {
+ rc = authenticate_password (h->session, password);
+ if (rc == SSH_AUTH_SUCCESS) return 0;
+ }
+
+ nbdkit_error ("all possible authentication methods failed");
+ return -1;
+}
+
+/* Create the per-connection handle. */
+static void *
+ssh_open (int readonly)
+{
+ struct ssh_handle *h;
+ int r;
+ int access_type;
+
+ h = calloc (1, sizeof *h);
+ if (h == NULL) {
+ nbdkit_error ("calloc: %m");
+ return NULL;
+ }
+
+ /* Set up the SSH session. */
+ h->session = ssh_new ();
+ if (!h->session) {
+ nbdkit_error ("failed to initialize libssh session");
+ goto err;
+ }
+
+ r = ssh_options_set (h->session, SSH_OPTIONS_HOST, host);
+ if (r != SSH_OK) {
+ nbdkit_error ("failed to set host in libssh session: %s: %s",
+ host, ssh_get_error (h->session));
+ goto err;
+ }
+ if (port != NULL) {
+ r = ssh_options_set (h->session, SSH_OPTIONS_PORT_STR, port);
+ if (r != SSH_OK) {
+ nbdkit_error ("failed to set port in libssh session: %s: %s",
+ port, ssh_get_error (h->session));
+ goto err;
+ }
+ }
+ if (user != NULL) {
+ r = ssh_options_set (h->session, SSH_OPTIONS_USER, user);
+ if (r != SSH_OK) {
+ nbdkit_error ("failed to set user in libssh session: %s: %s",
+ user, ssh_get_error (h->session));
+ goto err;
+ }
+ }
+
+ /* Read ~/.ssh/config or alternative file. */
+ if (config == NULL || strcmp (config, "") != 0) {
+ r = ssh_options_parse_config (h->session, config);
+ if (r != SSH_OK) {
+ nbdkit_error ("failed to parse local SSH configuration: %s",
+ ssh_get_error (h->session));
+ goto err;
+ }
+ }
+
+ /* Connect. */
+ r = ssh_connect (h->session);
+ if (r != SSH_OK) {
+ nbdkit_error ("failed to connect to remote host: %s: %s",
+ host, ssh_get_error (h->session));
+ goto err;
+ }
+
+ /* Verify the remote host. */
+ if (verify_remote_host (h) == -1)
+ goto err;
+
+ /* Authenticate. */
+ if (authenticate (h) == -1)
+ goto err;
+
+ /* Open the SFTP connection and file. */
+ h->sftp = sftp_new (h->session);
+ if (!h->sftp) {
+ nbdkit_error ("failed to allocate sftp session: %s",
+ ssh_get_error (h->session));
+ goto err;
+ }
+ r = sftp_init (h->sftp);
+ if (r != SSH_OK) {
+ nbdkit_error ("failed to initialize sftp session: %s",
+ ssh_get_error (h->session));
+ goto err;
+ }
+ access_type = readonly ? O_RDONLY : O_RDWR;
+ h->file = sftp_open (h->sftp, path, access_type, S_IRWXU);
+ if (!h->file) {
+ nbdkit_error ("cannot open file for %s: %s",
+ readonly ? "reading" : "writing",
+ ssh_get_error (h->session));
+ goto err;
+ }
+
+ nbdkit_debug ("opened libssh handle");
+
+ return h;
+
+ err:
+ if (h->file)
+ sftp_close (h->file);
+ if (h->sftp)
+ sftp_free (h->sftp);
+ if (h->session) {
+ ssh_disconnect (h->session);
+ ssh_free (h->session);
+ }
+ free (h);
+ return NULL;
+}
+
+/* Free up the per-connection handle. */
+static void
+ssh_close (void *handle)
+{
+ struct ssh_handle *h = handle;
+ int r;
+
+ r = sftp_close (h->file);
+ if (r != SSH_OK)
+ nbdkit_error ("cannot close file: %s", ssh_get_error (h->session));
+
+ sftp_free (h->sftp);
+ ssh_disconnect (h->session);
+ ssh_free (h->session);
+ free (h);
+}
+
+#define THREAD_MODEL NBDKIT_THREAD_MODEL_SERIALIZE_REQUESTS
+
+/* Get the file size. */
+static int64_t
+ssh_get_size (void *handle)
+{
+ struct ssh_handle *h = handle;
+ sftp_attributes attrs;
+ int64_t r;
+
+ attrs = sftp_fstat (h->file);
+ r = attrs->size;
+ sftp_attributes_free (attrs);
+
+ return r;
+}
+
+/* Read data from the remote server. */
+static int
+ssh_pread (void *handle, void *buf, uint32_t count, uint64_t offset)
+{
+ struct ssh_handle *h = handle;
+ int r;
+ ssize_t rs;
+
+ r = sftp_seek64 (h->file, offset);
+ if (r != SSH_OK) {
+ nbdkit_error ("seek64 failed: %s", ssh_get_error (h->session));
+ return -1;
+ }
+
+ while (count > 0) {
+ rs = sftp_read (h->file, buf, count);
+ if (rs < 0) {
+ nbdkit_error ("read failed: %s (%zd)", ssh_get_error (h->session),
rs);
+ return -1;
+ }
+ buf += rs;
+ count -= rs;
+ }
+
+ return 0;
+}
+
+/* Write data to the remote server. */
+static int
+ssh_pwrite (void *handle, const void *buf, uint32_t count, uint64_t offset)
+{
+ struct ssh_handle *h = handle;
+ int r;
+ ssize_t rs;
+
+ r = sftp_seek64 (h->file, offset);
+ if (r != SSH_OK) {
+ nbdkit_error ("seek64 failed: %s", ssh_get_error (h->session));
+ return -1;
+ }
+
+ while (count > 0) {
+ /* Openssh has a maximum packet size of 256K, so any write
+ * requests larger than this will fail in a peculiar way. (This
+ * limit doesn't seem to include the SFTP protocol overhead).
+ * Therefore if the count is larger than 128K, reduce the size of
+ * the request. I don't know whether 256K is a limit that applies
+ * to all servers.
+ */
+ rs = sftp_write (h->file, buf, MIN (count, 128*1024));
+ if (rs < 0) {
+ nbdkit_error ("write failed: %s (%zd)", ssh_get_error (h->session),
rs);
+ return -1;
+ }
+ buf += rs;
+ count -= rs;
+ }
+
+ return 0;
+}
+
+static int
+ssh_can_flush (void *handle)
+{
+ struct ssh_handle *h = handle;
+
+ /* I added this extension to openssh 6.5 (April 2013). It may not
+ * be available in other SSH servers.
+ */
+ return sftp_extension_supported (h->sftp, "fsync(a)openssh.com",
"1");
+}
+
+static int
+ssh_flush (void *handle)
+{
+ struct ssh_handle *h = handle;
+ int r;
+
+ again:
+ r = sftp_fsync (h->file);
+ if (r == SSH_AGAIN)
+ goto again;
+ else if (r != SSH_OK) {
+ nbdkit_error ("fsync failed: %s", ssh_get_error (h->session));
+ return -1;
+ }
+
+ return 0;
+}
+
+static struct nbdkit_plugin plugin = {
+ .name = "ssh",
+ .version = PACKAGE_VERSION,
+ .unload = ssh_unload,
+ .config = ssh_config,
+ .config_complete = ssh_config_complete,
+ .config_help = ssh_config_help,
+ .open = ssh_open,
+ .close = ssh_close,
+ .get_size = ssh_get_size,
+ .pread = ssh_pread,
+ .pwrite = ssh_pwrite,
+ .can_flush = ssh_can_flush,
+ .flush = ssh_flush,
+};
+
+NBDKIT_REGISTER_PLUGIN(plugin)
diff --git a/README b/README
index 9c4d844..e3b0149 100644
--- a/README
+++ b/README
@@ -77,6 +77,10 @@ For the curl (HTTP/FTP) plugin:
- libcurl
+For the ssh plugin:
+
+ - libssh (this is a different library from libssh2 - that will not work)
+
For the iso plugin:
- genisoimage or mkisofs
diff --git a/TODO b/TODO
index b589127..6e7464b 100644
--- a/TODO
+++ b/TODO
@@ -74,11 +74,11 @@ General ideas for improvements
Suggestions for plugins
-----------------------
-Note: qemu supports other formats such as libssh, libnfs, iscsi,
-gluster and ceph/rbd, and while similar plugins could be written for
-nbdkit there is no compelling reason unless the result is better than
-qemu-nbd. For the majority of users it would be better if they were
-directed to qemu-nbd for these use cases.
+Note: qemu supports other formats such as libnfs, iscsi, gluster and
+ceph/rbd, and while similar plugins could be written for nbdkit there
+is no compelling reason unless the result is better than qemu-nbd.
+For the majority of users it would be better if they were directed to
+qemu-nbd for these use cases.
* XVA files
diff --git a/plugins/ssh/Makefile.am b/plugins/ssh/Makefile.am
new file mode 100644
index 0000000..89f43a2
--- /dev/null
+++ b/plugins/ssh/Makefile.am
@@ -0,0 +1,69 @@
+# nbdkit
+# Copyright (C) 2014-2019 Red Hat Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# * Neither the name of Red Hat nor the names of its contributors may be
+# used to endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY RED HAT AND CONTRIBUTORS ''AS IS'' AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+# THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL RED HAT OR
+# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
+# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+# OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+# SUCH DAMAGE.
+
+include $(top_srcdir)/common-rules.mk
+
+EXTRA_DIST = nbdkit-ssh-plugin.pod
+
+if HAVE_SSH
+
+plugin_LTLIBRARIES = nbdkit-ssh-plugin.la
+
+nbdkit_ssh_plugin_la_SOURCES = \
+ ssh.c \
+ $(top_srcdir)/include/nbdkit-plugin.h
+
+nbdkit_ssh_plugin_la_CPPFLAGS = \
+ -I$(top_srcdir)/include \
+ -I$(top_srcdir)/common/include
+nbdkit_ssh_plugin_la_CFLAGS = \
+ $(WARNINGS_CFLAGS) \
+ $(SSH_CFLAGS)
+nbdkit_ssh_plugin_la_LIBADD = \
+ $(SSH_LIBS)
+nbdkit_ssh_plugin_la_LDFLAGS = \
+ -module -avoid-version -shared \
+ -Wl,--version-script=$(top_srcdir)/plugins/plugins.syms
+
+if HAVE_POD
+
+man_MANS = nbdkit-ssh-plugin.1
+CLEANFILES += $(man_MANS)
+
+nbdkit-ssh-plugin.1: nbdkit-ssh-plugin.pod
+ $(PODWRAPPER) --section=1 --man $@ \
+ --html $(top_builddir)/html/$@.html \
+ $<
+
+endif HAVE_POD
+
+endif
--
2.20.1