diff --git a/configure.ac b/configure.ac index 5425cce59c..3e55204403 100644 --- a/configure.ac +++ b/configure.ac @@ -1128,7 +1128,16 @@ AG_GST_CHECK_FEATURE(CURL, [Curl plugin], curl, [ ]) AC_SUBST(CURL_CFLAGS) AC_SUBST(CURL_LIBS) -]) + PKG_CHECK_MODULES(SSH2, libssh2 >= 1.4.3, [ + HAVE_SSH2="yes" + AC_DEFINE(HAVE_SSH2, 1, [Define if libssh2 is available]) + ], [ + HAVE_SSH2="no" + ]) + AM_CONDITIONAL(USE_SSH2, test "x$HAVE_SSH2" = "xyes") + AC_SUBST(SSH2_CFLAGS) + AC_SUBST(SSH2_LIBS) +],,,[AM_CONDITIONAL(USE_SSH2, false)]) dnl **** DASH **** translit(dnm, m, l) AM_CONDITIONAL(USE_DASH, true) @@ -2180,6 +2189,7 @@ AM_CONDITIONAL(USE_APEXSINK, false) AM_CONDITIONAL(USE_BZ2, false) AM_CONDITIONAL(USE_CHROMAPRINT, false) AM_CONDITIONAL(USE_CURL, false) +AM_CONDITIONAL(USE_SSH2, false) AM_CONDITIONAL(USE_DASH, false) AM_CONDITIONAL(USE_DC1394, false) AM_CONDITIONAL(USE_DECKLINK, false) diff --git a/ext/curl/Makefile.am b/ext/curl/Makefile.am index cb9002de0d..44386536af 100644 --- a/ext/curl/Makefile.am +++ b/ext/curl/Makefile.am @@ -1,22 +1,31 @@ plugin_LTLIBRARIES = libgstcurl.la +if USE_SSH2 +gstcurlsshsink_SOURCES = gstcurlsshsink.c gstcurlsftpsink.c +else +gstcurlsshsink_SOURCES = +endif + libgstcurl_la_SOURCES = gstcurl.c \ gstcurlbasesink.c \ gstcurltlssink.c \ gstcurlhttpsink.c \ gstcurlfilesink.c \ gstcurlftpsink.c \ + $(gstcurlsshsink_SOURCES) \ gstcurlsmtpsink.c libgstcurl_la_CFLAGS = \ $(GST_PLUGINS_BAD_CFLAGS) \ $(GST_BASE_CFLAGS) \ $(GST_CFLAGS) \ + $(SSH2_CFLAGS) \ $(CURL_CFLAGS) libgstcurl_la_LIBADD = \ $(GST_PLUGINS_BASE_LIBS) \ $(GST_BASE_LIBS) \ $(GST_LIBS) \ $(WINSOCK2_LIBS) \ + $(SSH2_LIBS) \ $(CURL_LIBS) libgstcurl_la_LDFLAGS = $(GST_PLUGIN_LDFLAGS) libgstcurl_la_LIBTOOLFLAGS = $(GST_PLUGIN_LIBTOOLFLAGS) @@ -26,4 +35,6 @@ noinst_HEADERS = gstcurlbasesink.h \ gstcurlhttpsink.h \ gstcurlfilesink.h \ gstcurlftpsink.h \ - gstcurlsmtpsink.h + gstcurlsmtpsink.h \ + gstcurlsshsink.h \ + gstcurlsftpsink.h diff --git a/ext/curl/gstcurl.c b/ext/curl/gstcurl.c index 7c1286e688..b3e79bca4b 100644 --- a/ext/curl/gstcurl.c +++ b/ext/curl/gstcurl.c @@ -26,6 +26,9 @@ #include "gstcurlfilesink.h" #include "gstcurlftpsink.h" #include "gstcurlsmtpsink.h" +#ifdef HAVE_SSH2 +#include "gstcurlsftpsink.h" +#endif static gboolean plugin_init (GstPlugin * plugin) @@ -47,6 +50,12 @@ plugin_init (GstPlugin * plugin) GST_TYPE_CURL_SMTP_SINK)) return FALSE; +#ifdef HAVE_SSH2 + if (!gst_element_register (plugin, "curlsftpsink", GST_RANK_NONE, + GST_TYPE_CURL_SFTP_SINK)) + return FALSE; +#endif + return TRUE; } diff --git a/ext/curl/gstcurlsftpsink.c b/ext/curl/gstcurlsftpsink.c new file mode 100644 index 0000000000..2a68d3afc7 --- /dev/null +++ b/ext/curl/gstcurlsftpsink.c @@ -0,0 +1,228 @@ +/* GStreamer + * Copyright (C) 2011 Axis Communications + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-curlsftpsink + * @short_description: sink that uploads data to a server using libcurl + * @see_also: + * + * This is a network sink that uses libcurl as a client to upload data to + * a SFTP (SSH File Transfer Protocol) server. + * + * + * Example launch line (upload a file to /home/john/sftp_tests/) + * |[ + * gst-launch filesrc location=/home/jdoe/some.file ! curlsftpsink \ + * file-name=some.file.backup \ + * user=john location=sftp://192.168.0.1/~/sftp_tests/ \ + * ssh-auth-type=1 ssh-key-passphrase=blabla \ + * ssh-pub-keyfile=/home/jdoe/.ssh/id_rsa.pub \ + * ssh-priv-keyfile=/home/jdoe/.ssh/id_rsa \ + * create-dirs=TRUE + * ]| + * + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gstcurlsshsink.h" +#include "gstcurlsftpsink.h" + +/* Default values */ +#define GST_CAT_DEFAULT gst_curl_sftp_sink_debug + + +/* Plugin specific settings */ + +GST_DEBUG_CATEGORY_STATIC (gst_curl_sftp_sink_debug); + +enum +{ + PROP_0, + PROP_CREATE_DIRS +}; + +/* private functions */ +static void gst_curl_sftp_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_curl_sftp_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_curl_sftp_sink_finalize (GObject * gobject); + +static gboolean set_sftp_options_unlocked (GstCurlBaseSink * curlbasesink); +static gboolean set_sftp_dynamic_options_unlocked (GstCurlBaseSink * + curlbasesink); + + +#define gst_curl_sftp_sink_parent_class parent_class +G_DEFINE_TYPE (GstCurlSftpSink, gst_curl_sftp_sink, GST_TYPE_CURL_SSH_SINK); + +static void +gst_curl_sftp_sink_class_init (GstCurlSftpSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstCurlBaseSinkClass *gstcurlbasesink_class = (GstCurlBaseSinkClass *) klass; + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (gst_curl_sftp_sink_debug, "curlsftpsink", 0, + "curl sftp sink element"); + + GST_DEBUG_OBJECT (klass, "class_init"); + + gst_element_class_set_static_metadata (element_class, + "Curl sftp sink", + "Sink/Network", + "Upload data over the SFTP protocol using libcurl", + "Sorin L. "); + + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_sftp_sink_finalize); + + gobject_class->set_property = gst_curl_sftp_sink_set_property; + gobject_class->get_property = gst_curl_sftp_sink_get_property; + + gstcurlbasesink_class->set_protocol_dynamic_options_unlocked = + set_sftp_dynamic_options_unlocked; + gstcurlbasesink_class->set_options_unlocked = set_sftp_options_unlocked; + + g_object_class_install_property (gobject_class, PROP_CREATE_DIRS, + g_param_spec_boolean ("create-dirs", "Create missing directories", + "Attempt to create missing directories", + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_curl_sftp_sink_init (GstCurlSftpSink * sink) +{ +} + +static void +gst_curl_sftp_sink_finalize (GObject * gobject) +{ + GST_DEBUG ("finalizing curlsftpsink"); + G_OBJECT_CLASS (parent_class)->finalize (gobject); +} + +static gboolean +set_sftp_dynamic_options_unlocked (GstCurlBaseSink * basesink) +{ + GstCurlSftpSink *sink = GST_CURL_SFTP_SINK (basesink); + gchar *tmp = g_strdup_printf ("%s%s", basesink->url, basesink->file_name); + gint curl_err = CURLE_OK; + + if ((curl_err = + curl_easy_setopt (basesink->curl, CURLOPT_URL, tmp)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, "curl error: %d setting URL to: %s", curl_err, tmp); + } + + g_free (tmp); + + return TRUE; +} + +static gboolean +set_sftp_options_unlocked (GstCurlBaseSink * basesink) +{ + GstCurlSftpSink *sink = GST_CURL_SFTP_SINK (basesink); + GstCurlSshSinkClass *parent_class; + gint curl_err = CURLE_OK; + + if ((curl_err = + curl_easy_setopt (basesink->curl, CURLOPT_UPLOAD, 1L)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, "curl error: %d setting CURLOPT_UPLOAD to 1", + curl_err); + } + + if (sink->create_dirs) { + if ((curl_err = curl_easy_setopt (basesink->curl, + CURLOPT_FTP_CREATE_MISSING_DIRS, 1L)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, + "curl error: %d setting FTP_CREATE_MISSING_DIRS to 1", curl_err); + } + } + + parent_class = GST_CURL_SSH_SINK_GET_CLASS (sink); + + /* chain to parent as well */ + return parent_class->set_options_unlocked (basesink); +} + +static void +gst_curl_sftp_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCurlSftpSink *sink; + GstState cur_state; + + g_return_if_fail (GST_IS_CURL_SFTP_SINK (object)); + sink = GST_CURL_SFTP_SINK (object); + + gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0); + if (cur_state != GST_STATE_PLAYING && cur_state != GST_STATE_PAUSED) { + GST_OBJECT_LOCK (sink); + + switch (prop_id) { + case PROP_CREATE_DIRS: + sink->create_dirs = g_value_get_boolean (value); + GST_DEBUG_OBJECT (sink, "create-dirs set to %d", sink->create_dirs); + break; + + default: + GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id); + break; + } + + GST_OBJECT_UNLOCK (sink); + } +} + +static void +gst_curl_sftp_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCurlSftpSink *sink; + + g_return_if_fail (GST_IS_CURL_SFTP_SINK (object)); + sink = GST_CURL_SFTP_SINK (object); + + switch (prop_id) { + case PROP_CREATE_DIRS: + g_value_set_boolean (value, sink->create_dirs); + break; + + default: + GST_DEBUG_OBJECT (sink, "invalid property id"); + break; + } +} diff --git a/ext/curl/gstcurlsftpsink.h b/ext/curl/gstcurlsftpsink.h new file mode 100644 index 0000000000..ed57fd5f42 --- /dev/null +++ b/ext/curl/gstcurlsftpsink.h @@ -0,0 +1,62 @@ +/* GStreamer + * Copyright (C) 2011 Axis Communications + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CURL_SFTP_SINK__ +#define __GST_CURL_SFTP_SINK__ + +#include +#include +#include +#include "gstcurlsshsink.h" + +G_BEGIN_DECLS + +#define GST_TYPE_CURL_SFTP_SINK \ + (gst_curl_sftp_sink_get_type()) +#define GST_CURL_SFTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CURL_SFTP_SINK,GstCurlSftpSink)) +#define GST_CURL_SFTP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CURL_SFTP_SINK,GstCurlSftpSinkClass)) +#define GST_IS_CURL_SFTP_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CURL_SFTP_SINK)) +#define GST_IS_CURL_SFTP_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CURL_SFTP_SINK)) + +typedef struct _GstCurlSftpSink GstCurlSftpSink; +typedef struct _GstCurlSftpSinkClass GstCurlSftpSinkClass; + + +struct _GstCurlSftpSink +{ + GstCurlSshSink parent; + + /*< private >*/ + gboolean create_dirs; +}; + +struct _GstCurlSftpSinkClass +{ + GstCurlSshSinkClass parent_class; +}; + +GType gst_curl_sftp_sink_get_type (void); + +G_END_DECLS + +#endif diff --git a/ext/curl/gstcurlsshsink.c b/ext/curl/gstcurlsshsink.c new file mode 100644 index 0000000000..91f05f9836 --- /dev/null +++ b/ext/curl/gstcurlsshsink.c @@ -0,0 +1,454 @@ +/* GStreamer + * Copyright (C) 2011 Axis Communications + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +/** + * SECTION:element-curlsshsink + * @short_description: sink that uploads data to a server using libcurl + * @see_also: + * + * This is a network sink that uses libcurl. + * + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "gstcurlbasesink.h" +#include "gstcurlsshsink.h" + +/* Default values */ +#define GST_CAT_DEFAULT gst_curl_ssh_sink_debug +#define DEFAULT_INSECURE TRUE + + +/* Plugin specific settings */ + +GST_DEBUG_CATEGORY_STATIC (gst_curl_ssh_sink_debug); + +enum +{ + PROP_0, + PROP_SSH_AUTH_TYPE, + PROP_SSH_PUB_KEYFILE, + PROP_SSH_PRIV_KEYFILE, + PROP_SSH_KEY_PASSPHRASE, + PROP_SSH_KNOWNHOSTS, + PROP_SSH_ACCEPT_UNKNOWNHOST +}; + + +/* curl SSH-key matching callback */ +static gint curl_ssh_sink_sshkey_cb (CURL * easy_handle, + const struct curl_khkey *knownkey, const struct curl_khkey *foundkey, + enum curl_khmatch, void *clientp); + + +/* Object class function declarations */ + +static void gst_curl_ssh_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec); +static void gst_curl_ssh_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec); +static void gst_curl_ssh_sink_finalize (GObject * gobject); +static gboolean gst_curl_ssh_sink_set_options_unlocked (GstCurlBaseSink * + bcsink); + + +/* private functions */ + +#define gst_curl_ssh_sink_parent_class parent_class +G_DEFINE_TYPE (GstCurlSshSink, gst_curl_ssh_sink, GST_TYPE_CURL_BASE_SINK); + +/* Register the auth types with the GLib type system */ +#define GST_TYPE_CURL_SSH_SINK_AUTH_TYPE (gst_curl_ssh_sink_auth_get_type ()) +static GType +gst_curl_ssh_sink_auth_get_type (void) +{ + static GType gtype = 0; + + if (!gtype) { + static const GEnumValue auth_types[] = { + {GST_CURLSSH_AUTH_NONE, "Not allowed", "none"}, + {GST_CURLSSH_AUTH_PUBLICKEY, "Public/private key files", "pubkey"}, + {GST_CURLSSH_AUTH_PASSWORD, "Password authentication", "password"}, + {0, NULL, NULL} + }; + gtype = g_enum_register_static ("GstCurlSshAuthType", auth_types); + } + return gtype; +} + +static void +gst_curl_ssh_sink_class_init (GstCurlSshSinkClass * klass) +{ + GObjectClass *gobject_class = G_OBJECT_CLASS (klass); + GstElementClass *element_class = GST_ELEMENT_CLASS (klass); + + GST_DEBUG_CATEGORY_INIT (gst_curl_ssh_sink_debug, "curlsshsink", 0, + "curl ssh sink element"); + + GST_DEBUG_OBJECT (klass, "class_init"); + + gst_element_class_set_static_metadata (element_class, + "Curl SSH sink", "Sink/Network", + "Upload data over SSH/SFTP using libcurl", "Sorin L. "); + + gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_ssh_sink_finalize); + + gobject_class->set_property = gst_curl_ssh_sink_set_property; + gobject_class->get_property = gst_curl_ssh_sink_get_property; + + klass->set_options_unlocked = gst_curl_ssh_sink_set_options_unlocked; + + g_object_class_install_property (gobject_class, PROP_SSH_AUTH_TYPE, + g_param_spec_enum ("ssh-auth-type", "SSH authentication type", + "SSH authentication method to authenticate on the SSH/SFTP server", + GST_TYPE_CURL_SSH_SINK_AUTH_TYPE, GST_CURLSSH_AUTH_NONE, + G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SSH_PUB_KEYFILE, + g_param_spec_string ("ssh-pub-keyfile", + "SSH public key file", + "The complete path & filename of the SSH public key file", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SSH_PRIV_KEYFILE, + g_param_spec_string ("ssh-priv-keyfile", + "SSH private key file", + "The complete path & filename of the SSH private key file", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SSH_KEY_PASSPHRASE, + g_param_spec_string ("ssh-key-passphrase", "Passphrase of the priv key", + "The passphrase used to protect the SSH private key file", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SSH_KNOWNHOSTS, + g_param_spec_string ("ssh-knownhosts", + "SSH known hosts", + "The complete path & filename of the SSH 'known_hosts' file", + NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); + + g_object_class_install_property (gobject_class, PROP_SSH_ACCEPT_UNKNOWNHOST, + g_param_spec_boolean ("ssh-accept-unknownhost", + "SSH accept unknown host", + "Accept an unknown remote public host key", + FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS)); +} + +static void +gst_curl_ssh_sink_init (GstCurlSshSink * sink) +{ + sink->ssh_auth_type = CURLSSH_AUTH_NONE; + sink->ssh_pub_keyfile = NULL; + sink->ssh_priv_keyfile = NULL; + sink->ssh_key_passphrase = NULL; + sink->ssh_knownhosts = NULL; + sink->ssh_accept_unknownhost = FALSE; +} + +static void +gst_curl_ssh_sink_finalize (GObject * gobject) +{ + GstCurlSshSink *this = GST_CURL_SSH_SINK (gobject); + + GST_DEBUG ("finalizing curlsshsink"); + + g_free (this->ssh_pub_keyfile); + g_free (this->ssh_priv_keyfile); + g_free (this->ssh_key_passphrase); + g_free (this->ssh_knownhosts); + + G_OBJECT_CLASS (parent_class)->finalize (gobject); +} + +static void +gst_curl_ssh_sink_set_property (GObject * object, guint prop_id, + const GValue * value, GParamSpec * pspec) +{ + GstCurlSshSink *sink; + GstState cur_state; + + g_return_if_fail (GST_IS_CURL_SSH_SINK (object)); + sink = GST_CURL_SSH_SINK (object); + + gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0); + + if (cur_state == GST_STATE_PLAYING || cur_state == GST_STATE_PAUSED) { + return; + } + + GST_OBJECT_LOCK (sink); + switch (prop_id) { + case PROP_SSH_AUTH_TYPE: + sink->ssh_auth_type = g_value_get_enum (value); + GST_DEBUG_OBJECT (sink, "ssh_auth_type set to %d", sink->ssh_auth_type); + break; + + case PROP_SSH_PUB_KEYFILE: + g_free (sink->ssh_pub_keyfile); + sink->ssh_pub_keyfile = g_value_dup_string (value); + GST_DEBUG_OBJECT (sink, "ssh_pub_keyfile set to %s", + sink->ssh_pub_keyfile); + break; + + case PROP_SSH_PRIV_KEYFILE: + g_free (sink->ssh_priv_keyfile); + sink->ssh_priv_keyfile = g_value_dup_string (value); + GST_DEBUG_OBJECT (sink, "ssh_priv_keyfile set to %s", + sink->ssh_priv_keyfile); + break; + + case PROP_SSH_KEY_PASSPHRASE: + g_free (sink->ssh_key_passphrase); + sink->ssh_key_passphrase = g_value_dup_string (value); + GST_DEBUG_OBJECT (sink, "ssh_key_passphrase set to %s", + sink->ssh_key_passphrase); + break; + + case PROP_SSH_KNOWNHOSTS: + g_free (sink->ssh_knownhosts); + sink->ssh_knownhosts = g_value_dup_string (value); + GST_DEBUG_OBJECT (sink, "ssh_knownhosts set to %s", sink->ssh_knownhosts); + break; + + case PROP_SSH_ACCEPT_UNKNOWNHOST: + sink->ssh_accept_unknownhost = g_value_get_boolean (value); + GST_DEBUG_OBJECT (sink, "ssh_accept_unknownhost set to %d", + sink->ssh_accept_unknownhost); + break; + + default: + GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id); + break; + } + GST_OBJECT_UNLOCK (sink); +} + +static void +gst_curl_ssh_sink_get_property (GObject * object, guint prop_id, + GValue * value, GParamSpec * pspec) +{ + GstCurlSshSink *sink; + + g_return_if_fail (GST_IS_CURL_SSH_SINK (object)); + sink = GST_CURL_SSH_SINK (object); + + switch (prop_id) { + case PROP_SSH_AUTH_TYPE: + g_value_set_enum (value, sink->ssh_auth_type); + break; + + case PROP_SSH_PUB_KEYFILE: + g_value_set_string (value, sink->ssh_pub_keyfile); + break; + + case PROP_SSH_PRIV_KEYFILE: + g_value_set_string (value, sink->ssh_priv_keyfile); + break; + + case PROP_SSH_KEY_PASSPHRASE: + g_value_set_string (value, sink->ssh_key_passphrase); + break; + + case PROP_SSH_KNOWNHOSTS: + g_value_set_string (value, sink->ssh_knownhosts); + break; + + case PROP_SSH_ACCEPT_UNKNOWNHOST: + g_value_set_boolean (value, sink->ssh_accept_unknownhost); + break; + + default: + GST_DEBUG_OBJECT (sink, "invalid property id"); + break; + } +} + +static gboolean +gst_curl_ssh_sink_set_options_unlocked (GstCurlBaseSink * bcsink) +{ + GstCurlSshSink *sink = GST_CURL_SSH_SINK (bcsink); + gint curl_err = CURLE_OK; + + /* set SSH specific options here */ + if (sink->ssh_pub_keyfile) { + if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_PUBLIC_KEYFILE, + sink->ssh_pub_keyfile)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, "curl error: %d setting public key file: %s.", + curl_err, sink->ssh_pub_keyfile); + return FALSE; + } + } + + if (sink->ssh_priv_keyfile) { + if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_PRIVATE_KEYFILE, + sink->ssh_priv_keyfile)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, "curl error: %d setting private key file: %s.", + curl_err, sink->ssh_priv_keyfile); + return FALSE; + } + } + + if (sink->ssh_knownhosts) { + if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KNOWNHOSTS, + sink->ssh_knownhosts)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, "curl error: %d setting known_hosts file: %s.", + curl_err, sink->ssh_knownhosts); + return FALSE; + } + } + + /* make sure we only accept PASSWORD or PUBLICKEY auth methods + * (can be extended later) */ + if (sink->ssh_auth_type == CURLSSH_AUTH_PASSWORD || + sink->ssh_auth_type == CURLSSH_AUTH_PUBLICKEY) { + + /* set the SSH_AUTH_TYPE */ + if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_AUTH_TYPES, + sink->ssh_auth_type)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, "curl error: %d setting auth type: %d.", curl_err, + sink->ssh_auth_type); + return FALSE; + } + + /* if key authentication -> provide the private key passphrase as well */ + if (sink->ssh_auth_type == CURLSSH_AUTH_PUBLICKEY) { + if (sink->ssh_key_passphrase) { + if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_KEYPASSWD, + sink->ssh_key_passphrase)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, + "curl error: %d setting private key passphrase: %s.", curl_err, + sink->ssh_key_passphrase); + return FALSE; + } + } else { + /* The user did not provide the passphrase for the private key. + * This can still be a valid situation, if (s)he chose not to + * protect the private key with a passphrase - but not recommended! */ + GST_WARNING_OBJECT (sink, "Warning: key authentication chosen but " + "'ssh-key-passphrase' not provided!"); + } + } + + } else { + GST_ERROR_OBJECT (sink, "Error: unsupported authentication type: %d.", + sink->ssh_auth_type); + return FALSE; + } + + /* Install the SSH_KEYFUNCTION callback... + * IMPORTANT: this callback gets called only if CURLOPT_SSH_KNOWNHOSTS + * is also set! */ + if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KEYFUNCTION, + curl_ssh_sink_sshkey_cb)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, "curl error: %d setting CURLOPT_SSH_KEYFUNCTION.", + curl_err); + return FALSE; + } else { + /* SSH_KEYFUNCTION callback successfully installed so go on and + * set the '*clientp' parameter as well */ + if ((curl_err = + curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KEYDATA, + sink)) != CURLE_OK) { + GST_ERROR_OBJECT (sink, "curl error: %d setting CURLOPT_SSH_KEYDATA.", + curl_err); + } + } + + return TRUE; +} + + +/* A 'curl_sshkey_cb' callback function. It gets called when the known_host + * matching has been done, to allow the application to act and decide for + * libcurl how to proceed. + * The callback will only be called if CURLOPT_SSH_KNOWNHOSTS is also set! + * NOTE: + * * use CURLOPT_SSH_KEYFUNCTION to install the callback func + * * use CURLOPT_SSH_KEYDATA to pass in the actual "*clientp" + */ +static gint +curl_ssh_sink_sshkey_cb (CURL * easy_handle, /* easy handle */ + const struct curl_khkey *knownkey, /* known - key from known_hosts */ + const struct curl_khkey *foundkey, /* found - key from remote end */ + enum curl_khmatch match, /* libcurl's view on the keys */ + void *clientp) +{ + GstCurlSshSink *sink = (GstCurlSshSink *) clientp; + + /* the default action to be taken upon pub key matching */ + guint res_action = CURLKHSTAT_REJECT; + + switch (match) { + case CURLKHMATCH_OK: + res_action = CURLKHSTAT_FINE; + GST_INFO_OBJECT (sink, + "Remote public host key is matching known_hosts, OK to proceed."); + break; + + case CURLKHMATCH_MISMATCH: + GST_WARNING_OBJECT (sink, + "Remote public host key mismatch in known_hosts, aborting " + "connection."); + /* Reject the connection. The old mismatching key has to be manually + * removed from 'known_hosts' before being able to connect again to + * the respective host. */ + break; + + case CURLKHMATCH_MISSING: + GST_OBJECT_LOCK (sink); + if (sink->ssh_accept_unknownhost == TRUE) { + /* the key was not found in known_hosts but the user chose to + * accept it */ + res_action = CURLKHSTAT_FINE_ADD_TO_FILE; + GST_INFO_OBJECT (sink, "Accepting and adding new public host key to " + "known_hosts."); + } else { + /* the key was not found in known_hosts and the user chose not + * to accept connections to unknown hosts */ + GST_WARNING_OBJECT (sink, + "Remote public host key is unknown, rejecting connection."); + } + GST_OBJECT_UNLOCK (sink); + break; + + default: + /* something went wrong, we got some bogus key match result */ + GST_ERROR_OBJECT (sink, + "libcurl internal error encountered during known_host matching"); + break; + } + + return res_action; +} diff --git a/ext/curl/gstcurlsshsink.h b/ext/curl/gstcurlsshsink.h new file mode 100644 index 0000000000..c5be246416 --- /dev/null +++ b/ext/curl/gstcurlsshsink.h @@ -0,0 +1,88 @@ +/* GStreamer + * Copyright (C) 2011 Axis Communications + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Library General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library 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 + * Library General Public License for more details. + * + * You should have received a copy of the GNU Library General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, + * Boston, MA 02110-1301, USA. + */ + +#ifndef __GST_CURL_SSH_SINK__ +#define __GST_CURL_SSH_SINK__ + +#include +#include +#include +#include "gstcurlbasesink.h" + +G_BEGIN_DECLS + +#define GST_TYPE_CURL_SSH_SINK \ + (gst_curl_ssh_sink_get_type()) +#define GST_CURL_SSH_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_CAST((obj),GST_TYPE_CURL_SSH_SINK,GstCurlSshSink)) +#define GST_CURL_SSH_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_CAST((klass),GST_TYPE_CURL_SSH_SINK,GstCurlSshSinkClass)) +#define GST_CURL_SSH_SINK_GET_CLASS(obj) \ + (G_TYPE_INSTANCE_GET_CLASS ((obj),GST_TYPE_CURL_SSH_SINK,GstCurlSshSinkClass)) +#define GST_IS_CURL_SSH_SINK(obj) \ + (G_TYPE_CHECK_INSTANCE_TYPE((obj),GST_TYPE_CURL_SSH_SINK)) +#define GST_IS_CURL_SSH_SINK_CLASS(klass) \ + (G_TYPE_CHECK_CLASS_TYPE((klass),GST_TYPE_CURL_SSH_SINK)) + +/* see */ +typedef enum { + GST_CURLSSH_AUTH_NONE = CURLSSH_AUTH_NONE, + GST_CURLSSH_AUTH_PUBLICKEY = CURLSSH_AUTH_PUBLICKEY, + GST_CURLSSH_AUTH_PASSWORD = CURLSSH_AUTH_PASSWORD +} GstCurlSshAuthType; + +typedef struct _GstCurlSshSink GstCurlSshSink; +typedef struct _GstCurlSshSinkClass GstCurlSshSinkClass; + + +struct _GstCurlSshSink +{ + GstCurlBaseSink parent; + + /*< private >*/ + guint ssh_auth_type; /* for now, supporting only: + CURLSSH_AUTH_PASSWORD (passwd auth) OR + CURLSSH_AUTH_PUBLICKEY (pub/pvt key auth) */ + + gchar *ssh_pub_keyfile; /* filename for the public key: + CURLOPT_SSH_PUBLIC_KEYFILE */ + gchar *ssh_priv_keyfile; /* filename for the private key: + CURLOPT_SSH_PRIVATE_KEYFILE */ + gchar *ssh_key_passphrase; /* passphrase for the pvt key: + CURLOPT_KEYPASSWD */ + + gchar *ssh_knownhosts; /* filename of the 'known_hosts' file: + CURLOPT_SSH_KNOWN_HOSTS */ + gboolean ssh_accept_unknownhost; /* accept or reject unknown public key + from remote host */ +}; + +struct _GstCurlSshSinkClass +{ + GstCurlBaseSinkClass parent_class; + + /* vmethods */ + gboolean (*set_options_unlocked) (GstCurlBaseSink *sink); +}; + +GType gst_curl_ssh_sink_get_type (void); + +G_END_DECLS + +#endif diff --git a/tests/check/Makefile.am b/tests/check/Makefile.am index ac4cf03a4a..11e1dd4eb1 100644 --- a/tests/check/Makefile.am +++ b/tests/check/Makefile.am @@ -142,10 +142,17 @@ else check_opus = endif +if USE_SSH2 +check_curl_sftp = elements/curlsftpsink +else +check_curl_sftp = +endif + if USE_CURL check_curl = elements/curlhttpsink \ elements/curlfilesink \ elements/curlftpsink \ + $(check_curl_sftp) \ elements/curlsmtpsink else check_curl = diff --git a/tests/check/elements/.gitignore b/tests/check/elements/.gitignore index f722151657..24322e1ce5 100644 --- a/tests/check/elements/.gitignore +++ b/tests/check/elements/.gitignore @@ -9,6 +9,7 @@ camerabin camerabin2 curlfilesink curlftpsink +curlsftpsink curlhttpsink curlsmtpsink deinterleave diff --git a/tests/check/elements/curlsftpsink.c b/tests/check/elements/curlsftpsink.c new file mode 100644 index 0000000000..458a348cec --- /dev/null +++ b/tests/check/elements/curlsftpsink.c @@ -0,0 +1,205 @@ +/* + * very basic unit test for curlsftpsink + */ + +#include +#include +#include + +static GstStaticPadTemplate srctemplate = GST_STATIC_PAD_TEMPLATE ("src", + GST_PAD_SRC, + GST_PAD_ALWAYS, + GST_STATIC_CAPS_ANY); + +static GstPad *srcpad; + +static GstElement *sink; + +static GstElement * +setup_curlsftpsink (void) +{ + GST_DEBUG ("setup_curlsftpsink"); + sink = gst_check_setup_element ("curlsftpsink"); + srcpad = gst_check_setup_src_pad (sink, &srctemplate); + gst_pad_set_active (srcpad, TRUE); + + return sink; +} + +static void +cleanup_curlsftpsink (GstElement * sink) +{ + GST_DEBUG ("cleanup_curlsftpsink"); + + gst_check_teardown_src_pad (sink); + gst_check_teardown_element (sink); +} + +GST_START_TEST (test_properties) +{ + + GstElement *sink; + + gchar *res_location = NULL; + gchar *res_user = NULL; + gchar *res_passwd = NULL; + gchar *res_file_name = NULL; + gint res_timeout = 0; + gint res_qos_dscp = 0; + + gchar *res_pubkey_file = NULL; + gchar *res_privkey_file = NULL; + gchar *res_passphrase = NULL; + gchar *res_kh_file = NULL; + guint res_auth_type = 0; + gboolean res_accept_unkh = FALSE; + + gboolean res_create_dirs = FALSE; + + sink = setup_curlsftpsink (); + + /* props from GstCurlBaseSink */ + g_object_set (G_OBJECT (sink), "location", "test_location", NULL); + g_object_set (G_OBJECT (sink), "user", "test_user", NULL); + g_object_set (G_OBJECT (sink), "passwd", "test_passwd", NULL); + g_object_set (G_OBJECT (sink), "file-name", "test_filename", NULL); + g_object_set (G_OBJECT (sink), "timeout", 123, NULL); + g_object_set (G_OBJECT (sink), "qos-dscp", 11, NULL); /* DSCP_MIN = 0, + DSCP_MAX = 63 + gstcurlbasesink.c */ + + /* props from GstCurlSshSink */ + g_object_set (G_OBJECT (sink), "ssh-auth-type", CURLSSH_AUTH_PUBLICKEY, NULL); + g_object_set (G_OBJECT (sink), "ssh-pub-keyfile", "public_key_file", NULL); + g_object_set (G_OBJECT (sink), "ssh-priv-keyfile", "private_key_file", NULL); + g_object_set (G_OBJECT (sink), "ssh-knownhosts", "known_hosts", NULL); + g_object_set (G_OBJECT (sink), "ssh-accept-unknownhost", TRUE, NULL); + g_object_set (G_OBJECT (sink), "ssh-key-passphrase", "SoMePaSsPhRaSe", NULL); + + /* props from GstCurlSftpSink */ + g_object_set (G_OBJECT (sink), "create-dirs", TRUE, NULL); + + + /* run a 'get' on all the above props */ + g_object_get (sink, "location", &res_location, + "user", &res_user, "passwd", &res_passwd, "file-name", &res_file_name, + "timeout", &res_timeout, "qos-dscp", &res_qos_dscp, + "ssh-auth-type", &res_auth_type, "ssh-pub-keyfile", &res_pubkey_file, + "ssh-priv-keyfile", &res_privkey_file, "ssh-knownhosts", &res_kh_file, + "ssh-accept-unknownhost", &res_accept_unkh, + "create-dirs", &res_create_dirs, "ssh-key-passphrase", &res_passphrase, + NULL); + + fail_unless (strncmp (res_location, "test_location", strlen ("test_location")) + == 0); + fail_unless (strncmp (res_user, "test_user", strlen ("test_user")) == 0); + fail_unless (strncmp (res_passwd, "test_passwd", strlen ("test_passwd")) + == 0); + fail_unless (strncmp (res_file_name, "test_filename", + strlen ("test_filename")) == 0); + fail_unless (res_timeout == 123); + fail_unless (res_qos_dscp == 11); + + fail_unless (res_auth_type == CURLSSH_AUTH_PUBLICKEY); + fail_unless (strncmp (res_pubkey_file, "public_key_file", + strlen ("public_key_file")) == 0); + fail_unless (strncmp (res_privkey_file, "private_key_file", + strlen ("private_key_file")) == 0); + fail_unless (strncmp (res_kh_file, "known_hosts", strlen ("known_hosts")) + == 0); + fail_unless (strncmp (res_passphrase, "SoMePaSsPhRaSe", + strlen ("SoMePaSsPhRaSe")) == 0); + fail_unless (res_accept_unkh == TRUE); + fail_unless (res_create_dirs == TRUE); + + g_free (res_location); + g_free (res_user); + g_free (res_passwd); + g_free (res_file_name); + g_free (res_pubkey_file); + g_free (res_privkey_file); + g_free (res_passphrase); + g_free (res_kh_file); + + /* ------- change properties ------------- */ + + /* props from GstCurlBaseSink */ + g_object_set (G_OBJECT (sink), "location", "new_location", NULL); + g_object_set (G_OBJECT (sink), "user", "new_user", NULL); + g_object_set (G_OBJECT (sink), "passwd", "new_passwd", NULL); + g_object_set (G_OBJECT (sink), "file-name", "new_filename", NULL); + g_object_set (G_OBJECT (sink), "timeout", 321, NULL); + g_object_set (G_OBJECT (sink), "qos-dscp", 22, NULL); + + /* props from GstCurlSshSink */ + g_object_set (G_OBJECT (sink), "ssh-auth-type", CURLSSH_AUTH_PASSWORD, NULL); + g_object_set (G_OBJECT (sink), "ssh-pub-keyfile", "/xxx/pub_key", NULL); + g_object_set (G_OBJECT (sink), "ssh-priv-keyfile", "/yyy/pvt_key", NULL); + g_object_set (G_OBJECT (sink), "ssh-knownhosts", "/zzz/known_hosts", NULL); + g_object_set (G_OBJECT (sink), "ssh-accept-unknownhost", FALSE, NULL); + g_object_set (G_OBJECT (sink), "ssh-key-passphrase", "OtherPASSphrase", NULL); + + /* props from GstCurlSftpSink */ + g_object_set (G_OBJECT (sink), "create-dirs", FALSE, NULL); + + + /* run a 'get' on all the above props */ + g_object_get (sink, "location", &res_location, "user", &res_user, + "passwd", &res_passwd, "file-name", &res_file_name, + "timeout", &res_timeout, "qos-dscp", &res_qos_dscp, + "ssh-auth-type", &res_auth_type, "ssh-pub-keyfile", &res_pubkey_file, + "ssh-priv-keyfile", &res_privkey_file, "ssh-knownhosts", &res_kh_file, + "ssh-accept-unknownhost", &res_accept_unkh, + "ssh-key-passphrase", &res_passphrase, "create-dirs", &res_create_dirs, + NULL); + + fail_unless (strncmp (res_location, "new_location", strlen ("new_location")) + == 0); + fail_unless (strncmp (res_user, "new_user", strlen ("new_user")) == 0); + fail_unless (strncmp (res_passwd, "new_passwd", strlen ("new_passwd")) + == 0); + fail_unless (strncmp (res_file_name, "new_filename", + strlen ("new_filename")) == 0); + fail_unless (res_timeout == 321); + fail_unless (res_qos_dscp == 22); + + fail_unless (res_auth_type == CURLSSH_AUTH_PASSWORD); + fail_unless (strncmp (res_pubkey_file, "/xxx/pub_key", + strlen ("/xxx/pub_key")) == 0); + fail_unless (strncmp (res_privkey_file, "/yyy/pvt_key", + strlen ("/yyy/pvt_key")) == 0); + fail_unless (strncmp (res_kh_file, "/zzz/known_hosts", + strlen ("/zzz/known_host")) == 0); + fail_unless (strncmp (res_passphrase, "OtherPASSphrase", + strlen ("OtherPASSphrase")) == 0); + fail_unless (res_accept_unkh == FALSE); + fail_unless (res_create_dirs == FALSE); + + g_free (res_location); + g_free (res_user); + g_free (res_passwd); + g_free (res_file_name); + g_free (res_pubkey_file); + g_free (res_privkey_file); + g_free (res_passphrase); + g_free (res_kh_file); + + cleanup_curlsftpsink (sink); +} + +GST_END_TEST; + +static Suite * +curlsink_suite (void) +{ + Suite *s = suite_create ("curlsftpsink"); + TCase *tc_chain = tcase_create ("sftpsink props"); + + suite_add_tcase (s, tc_chain); + tcase_set_timeout (tc_chain, 20); + tcase_add_test (tc_chain, test_properties); + + return s; +} + +GST_CHECK_MAIN (curlsink);