• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GStreamer
2  * Copyright (C) 2011 Axis Communications <dev-gstreamer@axis.com>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Library General Public
6  * License as published by the Free Software Foundation; either
7  * version 2 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Library General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this library; if not, write to the
16  * Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
17  * Boston, MA 02110-1301, USA.
18  */
19 
20 /**
21  * SECTION:element-curlsshsink
22  * @title: curlsshsink
23  * @short_description: sink that uploads data to a server using libcurl
24  *
25  * This is a network sink that uses libcurl.
26  *
27  */
28 
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 
33 #include "gstcurlbasesink.h"
34 #include "gstcurlsshsink.h"
35 
36 #include <curl/curl.h>
37 #include <string.h>
38 #include <stdio.h>
39 
40 #ifdef G_OS_WIN32
41 #include <winsock2.h>
42 #else
43 #include <sys/socket.h>
44 #include <netinet/in.h>
45 #include <netinet/ip.h>
46 #include <netinet/tcp.h>
47 #endif
48 #include <sys/types.h>
49 #include <unistd.h>
50 #include <sys/stat.h>
51 #include <fcntl.h>
52 
53 /* Default values */
54 #define GST_CAT_DEFAULT    gst_curl_ssh_sink_debug
55 
56 /* Plugin specific settings */
57 
58 GST_DEBUG_CATEGORY_STATIC (gst_curl_ssh_sink_debug);
59 
60 enum
61 {
62   PROP_0,
63   PROP_SSH_AUTH_TYPE,
64   PROP_SSH_PUB_KEYFILE,
65   PROP_SSH_PRIV_KEYFILE,
66   PROP_SSH_KEY_PASSPHRASE,
67   PROP_SSH_KNOWNHOSTS,
68   PROP_SSH_HOST_PUBLIC_KEY_MD5,
69   PROP_SSH_HOST_PUBLIC_KEY_SHA256,
70   PROP_SSH_ACCEPT_UNKNOWNHOST
71 };
72 
73 
74 /* curl SSH-key matching callback */
75 static gint curl_ssh_sink_sshkey_cb (CURL * easy_handle,
76     const struct curl_khkey *knownkey, const struct curl_khkey *foundkey,
77     enum curl_khmatch, void *clientp);
78 
79 
80 /* Object class function declarations */
81 
82 static void gst_curl_ssh_sink_set_property (GObject * object, guint prop_id,
83     const GValue * value, GParamSpec * pspec);
84 static void gst_curl_ssh_sink_get_property (GObject * object, guint prop_id,
85     GValue * value, GParamSpec * pspec);
86 static void gst_curl_ssh_sink_finalize (GObject * gobject);
87 static gboolean gst_curl_ssh_sink_set_options_unlocked (GstCurlBaseSink *
88     bcsink);
89 
90 
91 /* private functions */
92 
93 #define gst_curl_ssh_sink_parent_class      parent_class
94 G_DEFINE_TYPE (GstCurlSshSink, gst_curl_ssh_sink, GST_TYPE_CURL_BASE_SINK);
95 
96 /* Register the auth types with the GLib type system */
97 #define GST_TYPE_CURL_SSH_SINK_AUTH_TYPE (gst_curl_ssh_sink_auth_get_type ())
98 static GType
gst_curl_ssh_sink_auth_get_type(void)99 gst_curl_ssh_sink_auth_get_type (void)
100 {
101   static GType gtype = 0;
102 
103   if (!gtype) {
104     static const GEnumValue auth_types[] = {
105       {GST_CURLSSH_AUTH_NONE, "Not allowed", "none"},
106       {GST_CURLSSH_AUTH_PUBLICKEY, "Public/private key files", "pubkey"},
107       {GST_CURLSSH_AUTH_PASSWORD, "Password authentication", "password"},
108       {0, NULL, NULL}
109     };
110     gtype = g_enum_register_static ("GstCurlSshAuthType", auth_types);
111   }
112   return gtype;
113 }
114 
115 static void
gst_curl_ssh_sink_class_init(GstCurlSshSinkClass * klass)116 gst_curl_ssh_sink_class_init (GstCurlSshSinkClass * klass)
117 {
118   GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
119   GstElementClass *element_class = GST_ELEMENT_CLASS (klass);
120 
121   GST_DEBUG_CATEGORY_INIT (gst_curl_ssh_sink_debug, "curlsshsink", 0,
122       "curl ssh sink element");
123 
124   gst_element_class_set_static_metadata (element_class,
125       "Curl SSH sink", "Sink/Network",
126       "Upload data over SSH/SFTP using libcurl", "Sorin L. <sorin@axis.com>");
127 
128   gobject_class->finalize = GST_DEBUG_FUNCPTR (gst_curl_ssh_sink_finalize);
129 
130   gobject_class->set_property = gst_curl_ssh_sink_set_property;
131   gobject_class->get_property = gst_curl_ssh_sink_get_property;
132 
133   klass->set_options_unlocked = gst_curl_ssh_sink_set_options_unlocked;
134 
135   g_object_class_install_property (gobject_class, PROP_SSH_AUTH_TYPE,
136       g_param_spec_enum ("ssh-auth-type", "SSH authentication type",
137           "SSH authentication method to authenticate on the SSH/SFTP server",
138           GST_TYPE_CURL_SSH_SINK_AUTH_TYPE, GST_CURLSSH_AUTH_NONE,
139           G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
140 
141   g_object_class_install_property (gobject_class, PROP_SSH_PUB_KEYFILE,
142       g_param_spec_string ("ssh-pub-keyfile",
143           "SSH public key file",
144           "The complete path & filename of the SSH public key file",
145           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
146 
147   g_object_class_install_property (gobject_class, PROP_SSH_PRIV_KEYFILE,
148       g_param_spec_string ("ssh-priv-keyfile",
149           "SSH private key file",
150           "The complete path & filename of the SSH private key file",
151           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
152 
153   g_object_class_install_property (gobject_class, PROP_SSH_KEY_PASSPHRASE,
154       g_param_spec_string ("ssh-key-passphrase", "Passphrase of the priv key",
155           "The passphrase used to protect the SSH private key file",
156           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
157 
158   g_object_class_install_property (gobject_class, PROP_SSH_KNOWNHOSTS,
159       g_param_spec_string ("ssh-knownhosts",
160           "SSH known hosts",
161           "The complete path & filename of the SSH 'known_hosts' file",
162           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
163 
164   g_object_class_install_property (gobject_class, PROP_SSH_HOST_PUBLIC_KEY_MD5,
165       g_param_spec_string ("ssh-host-pubkey-md5",
166           "MD5 checksum of the remote host's public key",
167           "MD5 checksum (32 hexadecimal digits, case-insensitive) of the "
168           "remote host's public key",
169           NULL, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
170 
171 #if CURL_AT_LEAST_VERSION(7, 80, 0)
172   g_object_class_install_property (gobject_class,
173       PROP_SSH_HOST_PUBLIC_KEY_SHA256,
174       g_param_spec_string ("ssh-host-pubkey-sha256",
175           "SHA256 checksum of the remote host's public key",
176           "SHA256 checksum (Base64 encoded) of the remote host's public key",
177           NULL, G_PARAM_READWRITE | GST_PARAM_CONDITIONALLY_AVAILABLE |
178           G_PARAM_STATIC_STRINGS));
179 #endif
180 
181   g_object_class_install_property (gobject_class, PROP_SSH_ACCEPT_UNKNOWNHOST,
182       g_param_spec_boolean ("ssh-accept-unknownhost",
183           "SSH accept unknown host",
184           "Accept an unknown remote public host key",
185           FALSE, G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS));
186 
187   gst_type_mark_as_plugin_api (GST_TYPE_CURL_SSH_SINK_AUTH_TYPE, 0);
188   gst_type_mark_as_plugin_api (GST_TYPE_CURL_SSH_SINK, 0);
189 }
190 
191 static void
gst_curl_ssh_sink_init(GstCurlSshSink * sink)192 gst_curl_ssh_sink_init (GstCurlSshSink * sink)
193 {
194   sink->ssh_auth_type = GST_CURLSSH_AUTH_NONE;
195   sink->ssh_pub_keyfile = NULL;
196   sink->ssh_priv_keyfile = NULL;
197   sink->ssh_key_passphrase = NULL;
198   sink->ssh_knownhosts = NULL;
199   sink->ssh_host_public_key_md5 = NULL;
200   sink->ssh_host_public_key_sha256 = NULL;
201   sink->ssh_accept_unknownhost = FALSE;
202 }
203 
204 static void
gst_curl_ssh_sink_finalize(GObject * gobject)205 gst_curl_ssh_sink_finalize (GObject * gobject)
206 {
207   GstCurlSshSink *this = GST_CURL_SSH_SINK (gobject);
208 
209   GST_DEBUG ("finalizing curlsshsink");
210 
211   g_free (this->ssh_pub_keyfile);
212   g_free (this->ssh_priv_keyfile);
213   g_free (this->ssh_key_passphrase);
214   g_free (this->ssh_knownhosts);
215   g_free (this->ssh_host_public_key_md5);
216   g_free (this->ssh_host_public_key_sha256);
217 
218   G_OBJECT_CLASS (parent_class)->finalize (gobject);
219 }
220 
221 static void
gst_curl_ssh_sink_set_property(GObject * object,guint prop_id,const GValue * value,GParamSpec * pspec)222 gst_curl_ssh_sink_set_property (GObject * object, guint prop_id,
223     const GValue * value, GParamSpec * pspec)
224 {
225   GstCurlSshSink *sink;
226   GstState cur_state;
227 
228   g_return_if_fail (GST_IS_CURL_SSH_SINK (object));
229   sink = GST_CURL_SSH_SINK (object);
230 
231   gst_element_get_state (GST_ELEMENT (sink), &cur_state, NULL, 0);
232 
233   if (cur_state == GST_STATE_PLAYING || cur_state == GST_STATE_PAUSED) {
234     return;
235   }
236 
237   GST_OBJECT_LOCK (sink);
238   switch (prop_id) {
239     case PROP_SSH_AUTH_TYPE:
240       sink->ssh_auth_type = g_value_get_enum (value);
241       GST_DEBUG_OBJECT (sink, "ssh_auth_type set to %d", sink->ssh_auth_type);
242       break;
243 
244     case PROP_SSH_PUB_KEYFILE:
245       g_free (sink->ssh_pub_keyfile);
246       sink->ssh_pub_keyfile = g_value_dup_string (value);
247       GST_DEBUG_OBJECT (sink, "ssh_pub_keyfile set to %s",
248           sink->ssh_pub_keyfile);
249       break;
250 
251     case PROP_SSH_PRIV_KEYFILE:
252       g_free (sink->ssh_priv_keyfile);
253       sink->ssh_priv_keyfile = g_value_dup_string (value);
254       GST_DEBUG_OBJECT (sink, "ssh_priv_keyfile set to %s",
255           sink->ssh_priv_keyfile);
256       break;
257 
258     case PROP_SSH_KEY_PASSPHRASE:
259       g_free (sink->ssh_key_passphrase);
260       sink->ssh_key_passphrase = g_value_dup_string (value);
261       GST_DEBUG_OBJECT (sink, "ssh_key_passphrase set to %s",
262           sink->ssh_key_passphrase);
263       break;
264 
265     case PROP_SSH_KNOWNHOSTS:
266       g_free (sink->ssh_knownhosts);
267       sink->ssh_knownhosts = g_value_dup_string (value);
268       GST_DEBUG_OBJECT (sink, "ssh_knownhosts set to %s", sink->ssh_knownhosts);
269       break;
270 
271     case PROP_SSH_HOST_PUBLIC_KEY_MD5:
272       g_free (sink->ssh_host_public_key_md5);
273       sink->ssh_host_public_key_md5 = g_value_dup_string (value);
274       GST_DEBUG_OBJECT (sink, "ssh_host_public_key_md5 set to %s",
275           sink->ssh_host_public_key_md5);
276       break;
277 
278     case PROP_SSH_HOST_PUBLIC_KEY_SHA256:
279       g_free (sink->ssh_host_public_key_sha256);
280       sink->ssh_host_public_key_sha256 = g_value_dup_string (value);
281       GST_DEBUG_OBJECT (sink, "ssh_host_public_key_sha256 set to %s",
282           sink->ssh_host_public_key_sha256);
283       break;
284 
285     case PROP_SSH_ACCEPT_UNKNOWNHOST:
286       sink->ssh_accept_unknownhost = g_value_get_boolean (value);
287       GST_DEBUG_OBJECT (sink, "ssh_accept_unknownhost set to %d",
288           sink->ssh_accept_unknownhost);
289       break;
290 
291     default:
292       GST_DEBUG_OBJECT (sink, "invalid property id %d", prop_id);
293       break;
294   }
295   GST_OBJECT_UNLOCK (sink);
296 }
297 
298 static void
gst_curl_ssh_sink_get_property(GObject * object,guint prop_id,GValue * value,GParamSpec * pspec)299 gst_curl_ssh_sink_get_property (GObject * object, guint prop_id,
300     GValue * value, GParamSpec * pspec)
301 {
302   GstCurlSshSink *sink;
303 
304   g_return_if_fail (GST_IS_CURL_SSH_SINK (object));
305   sink = GST_CURL_SSH_SINK (object);
306 
307   switch (prop_id) {
308     case PROP_SSH_AUTH_TYPE:
309       g_value_set_enum (value, sink->ssh_auth_type);
310       break;
311 
312     case PROP_SSH_PUB_KEYFILE:
313       g_value_set_string (value, sink->ssh_pub_keyfile);
314       break;
315 
316     case PROP_SSH_PRIV_KEYFILE:
317       g_value_set_string (value, sink->ssh_priv_keyfile);
318       break;
319 
320     case PROP_SSH_KEY_PASSPHRASE:
321       g_value_set_string (value, sink->ssh_key_passphrase);
322       break;
323 
324     case PROP_SSH_KNOWNHOSTS:
325       g_value_set_string (value, sink->ssh_knownhosts);
326       break;
327 
328     case PROP_SSH_HOST_PUBLIC_KEY_MD5:
329       g_value_set_string (value, sink->ssh_host_public_key_md5);
330       break;
331 
332     case PROP_SSH_HOST_PUBLIC_KEY_SHA256:
333       g_value_set_string (value, sink->ssh_host_public_key_sha256);
334       break;
335 
336     case PROP_SSH_ACCEPT_UNKNOWNHOST:
337       g_value_set_boolean (value, sink->ssh_accept_unknownhost);
338       break;
339 
340     default:
341       GST_DEBUG_OBJECT (sink, "invalid property id");
342       break;
343   }
344 }
345 
346 static gboolean
gst_curl_ssh_sink_set_options_unlocked(GstCurlBaseSink * bcsink)347 gst_curl_ssh_sink_set_options_unlocked (GstCurlBaseSink * bcsink)
348 {
349   GstCurlSshSink *sink = GST_CURL_SSH_SINK (bcsink);
350   CURLcode curl_err = CURLE_OK;
351 
352   /* set SSH specific options here */
353   if (sink->ssh_pub_keyfile) {
354     if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_PUBLIC_KEYFILE,
355                 sink->ssh_pub_keyfile)) != CURLE_OK) {
356       bcsink->error = g_strdup_printf ("failed to set public key file: %s",
357           curl_easy_strerror (curl_err));
358       return FALSE;
359     }
360   }
361 
362   if (sink->ssh_priv_keyfile) {
363     if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_PRIVATE_KEYFILE,
364                 sink->ssh_priv_keyfile)) != CURLE_OK) {
365       bcsink->error = g_strdup_printf ("failed to set private key file: %s",
366           curl_easy_strerror (curl_err));
367       return FALSE;
368     }
369   }
370 
371   if (sink->ssh_knownhosts) {
372     if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KNOWNHOSTS,
373                 sink->ssh_knownhosts)) != CURLE_OK) {
374       bcsink->error = g_strdup_printf ("failed to set known_hosts file: %s",
375           curl_easy_strerror (curl_err));
376       return FALSE;
377     }
378   }
379 
380   if (sink->ssh_host_public_key_md5) {
381     /* libcurl is freaking tricky. If the input string is not exactly 32
382      * hexdigits long it silently ignores CURLOPT_SSH_HOST_PUBLIC_KEY_MD5 and
383      * performs the transfer without authenticating the server! */
384     if (strlen (sink->ssh_host_public_key_md5) != 32) {
385       bcsink->error = g_strdup ("MD5-hash string has invalid length, "
386           "must be exactly 32 hexdigits!");
387       return FALSE;
388     }
389 
390     if ((curl_err =
391             curl_easy_setopt (bcsink->curl, CURLOPT_SSH_HOST_PUBLIC_KEY_MD5,
392                 sink->ssh_host_public_key_md5)) != CURLE_OK) {
393       bcsink->error = g_strdup_printf ("failed to set remote host's public "
394           "key MD5: %s", curl_easy_strerror (curl_err));
395       return FALSE;
396     }
397   }
398 #if CURL_AT_LEAST_VERSION(7, 80, 0)
399   if (sink->ssh_host_public_key_sha256) {
400     if ((curl_err =
401             curl_easy_setopt (bcsink->curl, CURLOPT_SSH_HOST_PUBLIC_KEY_SHA256,
402                 sink->ssh_host_public_key_sha256)) != CURLE_OK) {
403       bcsink->error = g_strdup_printf ("failed to set remote host's public "
404           "key SHA256: %s", curl_easy_strerror (curl_err));
405       return FALSE;
406     }
407   }
408 #endif
409 
410   /* make sure we only accept PASSWORD or PUBLICKEY auth methods
411    * (can be extended later) */
412   if (sink->ssh_auth_type == GST_CURLSSH_AUTH_PASSWORD ||
413       sink->ssh_auth_type == GST_CURLSSH_AUTH_PUBLICKEY) {
414 
415     /* set the SSH_AUTH_TYPE */
416     if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_AUTH_TYPES,
417                 sink->ssh_auth_type)) != CURLE_OK) {
418       bcsink->error = g_strdup_printf ("failed to set authentication type: %s",
419           curl_easy_strerror (curl_err));
420       return FALSE;
421     }
422 
423     /* if key authentication -> provide the private key passphrase as well */
424     if (sink->ssh_auth_type == GST_CURLSSH_AUTH_PUBLICKEY) {
425       if (sink->ssh_key_passphrase) {
426         if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_KEYPASSWD,
427                     sink->ssh_key_passphrase)) != CURLE_OK) {
428           bcsink->error = g_strdup_printf ("failed to set private key "
429               "passphrase: %s", curl_easy_strerror (curl_err));
430           return FALSE;
431         }
432       } else {
433         /* The user did not provide the passphrase for the private key.
434          * This can still be a valid situation, if (s)he chose not to
435          * protect the private key with a passphrase - but not recommended! */
436         GST_WARNING_OBJECT (sink, "Warning: key authentication chosen but "
437             "'ssh-key-passphrase' not provided!");
438       }
439     }
440 
441   } else {
442     bcsink->error = g_strdup_printf ("Error: unsupported authentication type: "
443         "%d.", sink->ssh_auth_type);
444     return FALSE;
445   }
446 
447   /* Install the SSH_KEYFUNCTION callback...
448    * IMPORTANT: this callback gets called only if CURLOPT_SSH_KNOWNHOSTS
449    * is also set! */
450   if ((curl_err = curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KEYFUNCTION,
451               curl_ssh_sink_sshkey_cb)) != CURLE_OK) {
452     bcsink->error = g_strdup_printf ("failed to set SSH_KEYFUNCTION callback: "
453         "%s", curl_easy_strerror (curl_err));
454     return FALSE;
455   }
456   /* SSH_KEYFUNCTION callback successfully installed so go on and
457    * set the '*clientp' parameter as well */
458   if ((curl_err =
459           curl_easy_setopt (bcsink->curl, CURLOPT_SSH_KEYDATA,
460               sink)) != CURLE_OK) {
461     bcsink->error = g_strdup_printf ("failed to set CURLOPT_SSH_KEYDATA: %s",
462         curl_easy_strerror (curl_err));
463     return FALSE;
464   }
465 
466   return TRUE;
467 }
468 
469 
470 /* A 'curl_sshkey_cb' callback function. It gets called when the known_host
471  * matching has been done, to allow the application to act and decide for
472  * libcurl how to proceed.
473  * The callback will only be called if CURLOPT_SSH_KNOWNHOSTS is also set!
474  * NOTE:
475  *  * use CURLOPT_SSH_KEYFUNCTION to install the callback func
476  *  * use CURLOPT_SSH_KEYDATA to pass in the actual "*clientp"
477  */
478 static gint
curl_ssh_sink_sshkey_cb(CURL * easy_handle,const struct curl_khkey * knownkey,const struct curl_khkey * foundkey,enum curl_khmatch match,void * clientp)479 curl_ssh_sink_sshkey_cb (CURL * easy_handle,    /* easy handle */
480     const struct curl_khkey *knownkey,  /* known - key from known_hosts */
481     const struct curl_khkey *foundkey,  /* found - key from remote end */
482     enum curl_khmatch match,    /* libcurl's view on the keys */
483     void *clientp)
484 {
485   GstCurlSshSink *sink = (GstCurlSshSink *) clientp;
486 
487   /* the default action to be taken upon pub key matching */
488   guint res_action = CURLKHSTAT_REJECT;
489 
490   switch (match) {
491     case CURLKHMATCH_OK:
492       res_action = CURLKHSTAT_FINE;
493       GST_INFO_OBJECT (sink,
494           "Remote public host key is matching known_hosts, OK to proceed.");
495       break;
496 
497     case CURLKHMATCH_MISMATCH:
498       GST_WARNING_OBJECT (sink,
499           "Remote public host key mismatch in known_hosts, aborting "
500           "connection.");
501       /* Reject the connection. The old mismatching key has to be manually
502        * removed from 'known_hosts' before being able to connect again to
503        * the respective host. */
504       break;
505 
506     case CURLKHMATCH_MISSING:
507       GST_OBJECT_LOCK (sink);
508       if (sink->ssh_accept_unknownhost == TRUE) {
509         /* the key was not found in known_hosts but the user chose to
510          * accept it */
511         res_action = CURLKHSTAT_FINE_ADD_TO_FILE;
512         GST_INFO_OBJECT (sink, "Accepting and adding new public host key to "
513             "known_hosts.");
514       } else {
515         /* the key was not found in known_hosts and the user chose not
516          * to accept connections to unknown hosts */
517         GST_WARNING_OBJECT (sink,
518             "Remote public host key is unknown, rejecting connection.");
519       }
520       GST_OBJECT_UNLOCK (sink);
521       break;
522 
523     default:
524       /* something went wrong, we got some bogus key match result */
525       GST_CURL_BASE_SINK (sink)->error =
526           g_strdup ("libcurl internal error during known_host matching");
527       break;
528   }
529 
530   return res_action;
531 }
532